Skip to content
This repository has been archived by the owner on Apr 12, 2023. It is now read-only.

Commit

Permalink
add eslint (close #15)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkolson committed Nov 28, 2021
1 parent 730f1ac commit 864966a
Show file tree
Hide file tree
Showing 10 changed files with 3,508 additions and 504 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"eslint.workingDirectories": ["extension"]
}
4 changes: 4 additions & 0 deletions extension/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
transpiled
.eslintrc.js
6 changes: 6 additions & 0 deletions extension/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": ["airbnb-base", "airbnb-typescript/base"],
"parserOptions": {
"project": "./tsconfig.json"
}
}
121 changes: 62 additions & 59 deletions extension/advanzia-assistant.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,76 +4,79 @@ import { Response } from 'cross-fetch';
import { Script } from './advanzia-assistant';

describe('Content Script', () => {
const eventsRaised: { [key: string]: boolean } = {
noop: false,
ready: false,
done: false,
error: false,
};
const eventsRaised: { [key: string]: boolean } = {
noop: false,
ready: false,
done: false,
error: false,
};

const setRaised = (eventName: string) => () => { eventsRaised[eventName] = true };
const resetRaised = (eventName: string) => { eventsRaised[eventName] = false };
const resetAllRaised = () => Object.keys(eventsRaised).forEach(resetRaised);
const setRaised = (eventName: string) => () => { eventsRaised[eventName] = true; };
const resetRaised = (eventName: string) => { eventsRaised[eventName] = false; };
const resetAllRaised = () => Object.keys(eventsRaised).forEach(resetRaised);

let script: Script;
let script: Script;

beforeAll(() => Object.assign(global, chromeMock));
beforeEach(resetAllRaised);
beforeAll(() => Object.assign(global, chromeMock));
beforeEach(resetAllRaised);
beforeEach(() => {
script = new Script();
});
beforeEach(() => {
Object.keys(eventsRaised)
.forEach((eventName) => script.on(eventName, setRaised(eventName)));
});
beforeEach(jest.resetAllMocks);
beforeEach(jest.restoreAllMocks);
describe('when run on mein.gebuhrenfrei.com somewhere', () => {
beforeEach(() => {
script = new Script();
jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
...{ host: 'mein.gebuhrenfrei.com' },
});
});
beforeEach(() => Object.keys(eventsRaised).forEach(eventName => script.on(eventName, setRaised(eventName))));
beforeEach(jest.resetAllMocks);
beforeEach(jest.restoreAllMocks);
describe('when run on mein.gebuhrenfrei.com somewhere', () => {
beforeEach(() => {
jest.spyOn(window, "location", "get").mockReturnValue({
...window.location,
...{ host: 'mein.gebuhrenfrei.com' },
});
});
describe('when not on transactions page', () => {
it('should not do anything', async () => {
await script.execute();
describe('when not on transactions page', () => {
it('should not do anything', async () => {
await script.execute();

expect(eventsRaised.noop).toBe(true);
});
});
expect(eventsRaised.noop).toBe(true);
});
});

describe('when on transactions page', () => {
const simulateAdvanziaAppDoingStuff = () => setTimeout(() => {
const div = document.createElement('div');
div.className = 'card';
document.body.appendChild(div);
}, 2000);
describe('when on transactions page', () => {
const simulateAdvanziaAppDoingStuff = () => setTimeout(() => {
const div = document.createElement('div');
div.className = 'card';
document.body.appendChild(div);
}, 2000);

beforeEach(() => {
jest.spyOn(window, "location", "get").mockReturnValue({
...window.location,
...{ pathname: '/retail-app-de' },
});
global.fetch = async () => new Response(fs.readFileSync('../dist/advanzia-assistant.wasm'));
});
beforeEach(() => {
jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
...{ pathname: '/retail-app-de' },
});
global.fetch = async () => new Response(fs.readFileSync('../dist/advanzia-assistant.wasm'));
});

beforeEach(simulateAdvanziaAppDoingStuff);
beforeEach(() => script.execute());
it('should indicate ready status', async () => {
expect(eventsRaised.ready).toBe(true);
});
beforeEach(simulateAdvanziaAppDoingStuff);
beforeEach(() => script.execute());
it('should indicate ready status', async () => {
expect(eventsRaised.ready).toBe(true);
});

it('to insert correct dom modifications', async () => {
expect(document.body).toMatchSnapshot();
});
});
it('to insert correct dom modifications', async () => {
expect(document.body).toMatchSnapshot();
});
});
});

describe('otherwise', () => {
it('should raise error event and create script error indicating that the script cannot be run if not on mein.gebuhrenfrei.com', async () => {
await script.execute();
describe('otherwise', () => {
it('should raise error event and create script error indicating that the script cannot be run if not on mein.gebuhrenfrei.com', async () => {
await script.execute();

expect(eventsRaised.error).toBe(true);
expect(script.errors.length).toBe(1);
expect(script.errors[0]).toEqual(new Error('this script can only be run on mein.gebuhrenfrei.com'));
});
expect(eventsRaised.error).toBe(true);
expect(script.errors.length).toBe(1);
expect(script.errors[0]).toEqual(new Error('this script can only be run on mein.gebuhrenfrei.com'));
});
});
});
});
216 changes: 110 additions & 106 deletions extension/advanzia-assistant.ts
Original file line number Diff line number Diff line change
@@ -1,130 +1,134 @@
export interface ContentScript {
execute(): void;
};
execute(): void;
}

export enum ContentScriptStatus {
Initializing = "Initializing",
Noop = "Noop",
Ready = "Ready",
Done = "Done",
Err = "Error"
};
Initializing = 'Initializing',
Noop = 'Noop',
Ready = 'Ready',
Done = 'Done',
Err = 'Error',
}

export interface WasmExports extends WebAssembly.Exports {
add_one: (num: number) => number,
};
add_one: (num: number) => number,
}

export interface ContentScriptEvents {
readonly noop: Event;
readonly ready: Event;
readonly done: Event;
readonly error: ErrorEvent;
};
readonly noop: Event;
readonly ready: Event;
readonly done: Event;
readonly error: ErrorEvent;
}

export class Script extends EventTarget implements ContentScript, EventListenerObject {
private memory: WebAssembly.Memory;
private wasmExports?: WasmExports;


private events: ContentScriptEvents;

status: ContentScriptStatus;

errors: Error[];

constructor() {
super();
this.status = ContentScriptStatus.Initializing;
this.memory = new WebAssembly.Memory({ initial: 10 });
this.events = {
noop: new Event('noop'),
ready: new Event('ready'),
done: new Event('done'),
error: new ErrorEvent('error')
};

this.errors = [];

this.registerEventListeners();
}

private registerEventListeners() {
Object.values(this.events).forEach(e => this.registerEventListener(e.type));
private memory: WebAssembly.Memory;

private wasmExports?: WasmExports;

private events: ContentScriptEvents;

status: ContentScriptStatus;

errors: Error[];

constructor() {
super();
this.status = ContentScriptStatus.Initializing;
this.memory = new WebAssembly.Memory({ initial: 10 });
this.events = {
noop: new Event('noop'),
ready: new Event('ready'),
done: new Event('done'),
error: new ErrorEvent('error'),
};

this.errors = [];

this.registerEventListeners();
}

private registerEventListeners() {
Object.values(this.events).forEach((e) => this.registerEventListener(e.type));
}

private registerEventListener(name: string) {
this.addEventListener(name, this);
}

handleEvent(e: Event | ErrorEvent) {
switch (e.type) {
case this.events.ready.type:
this.status = ContentScriptStatus.Ready;
break;
case this.events.noop.type:
this.status = ContentScriptStatus.Noop;
break;
case this.events.done.type:
this.status = ContentScriptStatus.Done;
break;
case this.events.error.type:
this.status = ContentScriptStatus.Err;
break;
default:
break;
}
}

private registerEventListener(name: string) {
this.addEventListener(name, this);
}
on(eventName: string, handler: EventListenerOrEventListenerObject) {
this.addEventListener(eventName, handler);
return this;
}

handleEvent(e: Event | ErrorEvent) {
switch (e.type) {
case this.events.ready.type:
this.status = ContentScriptStatus.Ready;
break;
case this.events.noop.type:
this.status = ContentScriptStatus.Noop;
break;
case this.events.done.type:
this.status = ContentScriptStatus.Done;
break;
case this.events.error.type:
this.status = ContentScriptStatus.Err;
break;
}
async execute() {
if (window.location.host !== 'mein.gebuhrenfrei.com') {
this.error(new Error('this script can only be run on mein.gebuhrenfrei.com'));
return;
}

on(eventName: string, handler: EventListenerOrEventListenerObject) {
this.addEventListener(eventName, handler);
return this;
if (window.location.pathname.indexOf('retail-app') === -1) {
this.dispatchEvent(this.events.noop);
return;
}

async execute() {
if (location.host !== 'mein.gebuhrenfrei.com') {
this.error(new Error('this script can only be run on mein.gebuhrenfrei.com'));
return;
}
const wasmPath = chrome.runtime.getURL('advanzia-assistant.wasm');
const wasmResponse = await fetch(wasmPath);

if (location.pathname.indexOf('retail-app') === -1) {
this.dispatchEvent(this.events.noop);
return;
}
const wasmResponseArrayBuffer = await wasmResponse.arrayBuffer();

const wasmPath = chrome.runtime.getURL('advanzia-assistant.wasm');
const wasmResponse = await fetch(wasmPath);
const wasmImports = { env: { memory: this.memory } };

const wasmResponseArrayBuffer = await wasmResponse.arrayBuffer();
const wasm = await WebAssembly.instantiate(wasmResponseArrayBuffer, wasmImports);
this.wasmExports = wasm.instance.exports as WasmExports;

const wasm = await WebAssembly.instantiate(wasmResponseArrayBuffer, { env: { memory: this.memory } });
this.wasmExports = wasm.instance.exports as WasmExports;
await Script.pageReasonablyLoaded();

await this.pageReasonablyLoaded();
this.dispatchEvent(this.events.ready);
}

this.dispatchEvent(this.events.ready);
}
private error(e: Error) {
this.errors.push(e);
this.dispatchEvent(this.events.error);
}

private error(e: Error) {
this.errors.push(e);
this.dispatchEvent(this.events.error);
}

private pageReasonablyLoaded(): Promise<void> {
const signalSelector = '.card';
return new Promise((resolve) => {
const el = document.querySelector(signalSelector);
if (el) {
resolve();
return;
}
new MutationObserver((_mutationsList, observer) => {
if (document.querySelector(signalSelector)) {
resolve();
observer.disconnect();
}
})
.observe(document.documentElement, {
childList: true,
subtree: true
});
private static pageReasonablyLoaded(): Promise<void> {
const signalSelector = '.card';
return new Promise((resolve) => {
const el = document.querySelector(signalSelector);
if (el) {
resolve();
return;
}
new MutationObserver((_mutationsList, observer) => {
if (document.querySelector(signalSelector)) {
resolve();
observer.disconnect();
}
})
.observe(document.documentElement, {
childList: true,
subtree: true,
});
}
};
});
}
}
Loading

0 comments on commit 864966a

Please sign in to comment.