Skip to content

Commit 37063e0

Browse files
authored
add unit-tests (#2)
* typescript chore: add verbatimModuleSyntax * extract environment consts from app consts * add unit-tests
1 parent d345227 commit 37063e0

30 files changed

+3018
-288
lines changed

Makefile

+10-9
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,20 @@ install:
1010
dev:
1111
rm -rf ./public/build
1212
NODE_OPTIONS="--import=tsx" \
13-
npx webpack --progress --watch --mode=development
13+
pnpm exec webpack --progress --watch --mode=development
1414

1515
lint:
16-
npx prettier . --write
17-
npx svelte-check
16+
pnpm exec prettier . --write
17+
pnpm exec svelte-check
1818

19-
prod:
20-
rm -rf ./public/build
21-
make lint
19+
test:
20+
pnpm exec jest --config jest/jest.config.js
21+
22+
prod: lint test
23+
rm -rf ./public/build $(ZIP_CHROME_FILE)
2224
NODE_OPTIONS="--import=tsx" \
23-
time npx webpack --mode=production
24-
rm -rf $(ZIP_CHROME_FILE)
25+
time pnpm exec webpack --mode=production
2526
zip -r $(ZIP_CHROME_FILE) ./public ./manifest.json > /dev/null
2627

27-
.PHONY: clean install dev lint prod
28+
.PHONY: clean install dev lint prod test
2829
.DEFAULT_GOAL := dev

jest/jest.config.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
export default {
3+
preset: 'ts-jest',
4+
testEnvironment: 'jsdom',
5+
extensionsToTreatAsEsm: ['.ts'],
6+
transform: {
7+
'^.+\\.tsx?$': [
8+
'ts-jest',
9+
{
10+
// 2024-05-09 to disable verbatimModuleSyntax
11+
tsconfig: './jest/tsconfig.json',
12+
useESM: true,
13+
defaultsESM: true,
14+
},
15+
],
16+
},
17+
rootDir: '..', // . - /
18+
roots: ['jest/tests'], // . - /jest
19+
moduleNameMapper: {
20+
// duplicates tsconfig.json/compilerOptions/path & webpack.config.ts/resolve/alias
21+
'@/(.*)': '<rootDir>/src/$1',
22+
},
23+
};

jest/tests/time.test.ts

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { describe, expect, test, beforeEach } from '@jest/globals';
2+
import { Stopper, Timer, Fps, MeanAggregator } from '../../src/api/time.ts';
3+
4+
function wait(timeout: number) {
5+
return new Promise((resolve) => {
6+
setTimeout(resolve, timeout);
7+
});
8+
}
9+
10+
describe('Stopper', () => {
11+
let stopper: Stopper;
12+
13+
beforeEach(() => {
14+
stopper = new Stopper();
15+
});
16+
17+
test('start/stop', async () => {
18+
const DELAY = 10;
19+
stopper.start();
20+
await wait(DELAY);
21+
stopper.stop();
22+
const value = stopper.value();
23+
await wait(DELAY);
24+
const value2 = stopper.value();
25+
26+
expect(value).toBe(value2);
27+
expect(/\d+ms/.test(Stopper.toString(stopper.value()))).toBe(true);
28+
});
29+
30+
test('elapsed continues counting after stop', async () => {
31+
const DELAY = 10;
32+
stopper.start();
33+
await wait(DELAY);
34+
stopper.stop();
35+
const value = stopper.value();
36+
await wait(3 * DELAY);
37+
const elapsed = stopper.elapsed();
38+
39+
expect(elapsed > 2 * value).toBe(true);
40+
});
41+
});
42+
43+
describe('Timer - default options', () => {
44+
test('start', async () => {
45+
let counter = 0;
46+
const DELAY = 10;
47+
const timer = new Timer(() => {
48+
counter++;
49+
}, DELAY);
50+
51+
expect(timer.isPending()).toBe(false);
52+
timer.start();
53+
expect(timer.isPending()).toBe(true);
54+
await wait(3 * DELAY);
55+
expect(timer.isPending()).toBe(false);
56+
expect(counter).toBe(1);
57+
});
58+
59+
test('stop before expected', async () => {
60+
let counter = 0;
61+
const DELAY = 10;
62+
const timer = new Timer(() => {
63+
counter++;
64+
}, 2 * DELAY);
65+
66+
timer.start();
67+
expect(timer.isPending()).toBe(true);
68+
await wait(DELAY);
69+
timer.stop();
70+
expect(timer.isPending()).toBe(false);
71+
await wait(3 * DELAY);
72+
expect(counter).toBe(0);
73+
});
74+
});
75+
76+
describe('Timer - interval option', () => {
77+
test('start/stop', async () => {
78+
let counter = 0;
79+
const DELAY = 10;
80+
const interval = new Timer(
81+
() => {
82+
counter++;
83+
},
84+
DELAY,
85+
{ interval: true }
86+
);
87+
88+
interval.start();
89+
await wait(DELAY + DELAY / 2);
90+
interval.stop();
91+
await wait(DELAY);
92+
expect(counter).toBe(1);
93+
});
94+
});
95+
96+
describe('Timer - animation + interval + measurable', () => {
97+
test('start/stop', async () => {
98+
let count = 0;
99+
const animation = new Timer(
100+
() => {
101+
count++;
102+
},
103+
undefined,
104+
{ animation: true, interval: true, measurable: true }
105+
);
106+
const SECOND = 1e3;
107+
108+
expect(animation.isPending()).toBe(false);
109+
animation.start();
110+
expect(animation.isPending()).toBe(true);
111+
await wait(SECOND);
112+
animation.stop();
113+
expect(animation.isPending()).toBe(false);
114+
expect(animation.executionTime < 0.5).toBe(true);
115+
expect(58 <= count && count <= 61).toBe(true);
116+
});
117+
});
118+
119+
describe('Fps', () => {
120+
test('collects ticks after a second', async () => {
121+
return new Promise<void>((resolve) => {
122+
const COUNT = 20;
123+
const fps = new Fps((value) => {
124+
fps.stop();
125+
expect(value).toBe(COUNT);
126+
resolve();
127+
});
128+
129+
fps.start();
130+
131+
for (let i = 0, I = COUNT; i < I; i++) {
132+
fps.tick();
133+
}
134+
});
135+
});
136+
});
137+
138+
describe('MeanAggregator', () => {
139+
test('mean', () => {
140+
const SAMPLES = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
141+
const mean = new MeanAggregator();
142+
let sum = 0;
143+
144+
for (let i = 0, I = SAMPLES.length; i < I; i++) {
145+
mean.add(SAMPLES[i]);
146+
sum += SAMPLES[i];
147+
}
148+
149+
const sumMean = sum / SAMPLES.length;
150+
expect(mean.mean).toBe(sumMean);
151+
});
152+
});

jest/tests/wrappers.test.ts

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { describe, expect, test, beforeEach, afterEach } from '@jest/globals';
2+
import { Wrapper } from '../../src/api/wrappers.ts';
3+
import { TAG_EXCEPTION, TAG_UNDEFINED } from '../../src/api/clone.ts';
4+
5+
describe('wrappers', () => {
6+
let wrapper: Wrapper;
7+
8+
beforeEach(() => {
9+
wrapper = new Wrapper();
10+
wrapper.wrapApis();
11+
});
12+
13+
afterEach(() => {
14+
wrapper.unwrapApis();
15+
wrapper.cleanHistory();
16+
});
17+
18+
test('onlineTimers emptied after setTimeout expires', () => {
19+
return new Promise<void>((resolve) => {
20+
const DELAY = 5;
21+
const handler = setTimeout(() => {}, DELAY);
22+
23+
// typecasting handler to number since here its having NodeJS.Timeout type
24+
expect(wrapper.onlineTimers.size).toBe(1);
25+
expect(wrapper.onlineTimers.has(Number(handler))).toBe(true);
26+
27+
setTimeout(() => {
28+
expect(wrapper.onlineTimers.size).toBe(0);
29+
resolve();
30+
}, 2 * DELAY);
31+
});
32+
});
33+
34+
test('setTimeoutHistory & clearTimeoutHistory - recorded', () => {
35+
expect(wrapper.setTimeoutHistory.length).toBe(0);
36+
expect(wrapper.clearTimeoutHistory.length).toBe(0);
37+
38+
const handler = setTimeout(() => {}, 123);
39+
expect(wrapper.onlineTimers.size).toBe(1);
40+
clearTimeout(handler);
41+
42+
expect(wrapper.onlineTimers.size).toBe(0);
43+
expect(wrapper.clearTimeoutHistory.length).toBe(1);
44+
expect(wrapper.setTimeoutHistory.length).toBe(1);
45+
});
46+
47+
test('setTimeoutHistory - valid delay', () => {
48+
const DELAY = 123;
49+
const handler = setTimeout(() => {}, DELAY);
50+
const rec = wrapper.setTimeoutHistory[0];
51+
52+
expect(rec.individualInvocations).toBe(1);
53+
expect(rec.handlerDelay).toBe(DELAY);
54+
expect(rec.hasError).toBe(false);
55+
expect(rec.isEval).toBe(false);
56+
expect(rec.trace.length).toBeGreaterThan(1);
57+
expect(rec.traceId).toBeTruthy();
58+
59+
clearTimeout(handler);
60+
});
61+
62+
test('setTimeoutHistory - invalid delay', () => {
63+
setTimeout(() => {}, -1);
64+
const rec = wrapper.setTimeoutHistory[0];
65+
66+
expect(rec.individualInvocations).toBe(1);
67+
expect(rec.handlerDelay).toBe(TAG_EXCEPTION('-1'));
68+
expect(rec.hasError).toBe(true);
69+
expect(rec.isEval).toBe(false);
70+
});
71+
72+
test('clearTimeoutHistory - valid handler', () => {
73+
const handler = setTimeout(() => {}, 1e3);
74+
clearTimeout(handler);
75+
const rec = wrapper.clearTimeoutHistory[0];
76+
expect(rec.handlerDelay).toBe(1e3);
77+
});
78+
79+
test('clearTimeoutHistory - non existent handler', () => {
80+
clearTimeout(1000);
81+
const rec = wrapper.clearTimeoutHistory[0];
82+
83+
expect(rec.handlerDelay).toBe('N/A');
84+
expect(rec.hasError).toBe(false);
85+
});
86+
87+
test('clearTimeoutHistory - invalid handler', () => {
88+
clearTimeout(0);
89+
90+
const rec = wrapper.clearTimeoutHistory[0];
91+
92+
expect(rec.handlerDelay).toBe('N/A');
93+
expect(rec.hasError).toBe(true);
94+
});
95+
96+
test('setIntervalHistory & clearIntervalHistory - recorded', () => {
97+
expect(wrapper.setIntervalHistory.length).toBe(0);
98+
expect(wrapper.clearIntervalHistory.length).toBe(0);
99+
100+
const handler = setInterval(() => {}, 123);
101+
expect(wrapper.onlineTimers.size).toBe(1);
102+
clearInterval(handler);
103+
104+
expect(wrapper.onlineTimers.size).toBe(0);
105+
expect(wrapper.clearIntervalHistory.length).toBe(1);
106+
expect(wrapper.setIntervalHistory.length).toBe(1);
107+
});
108+
109+
test('setIntervalHistory - valid delay', () => {
110+
const DELAY = 123;
111+
const handler = setInterval(() => {}, DELAY);
112+
const rec = wrapper.setIntervalHistory[0];
113+
114+
expect(rec.individualInvocations).toBe(1);
115+
expect(rec.handlerDelay).toBe(DELAY);
116+
expect(rec.hasError).toBe(false);
117+
expect(rec.isEval).toBe(false);
118+
expect(rec.trace.length).toBeGreaterThan(1);
119+
expect(rec.traceId).toBeTruthy();
120+
121+
clearInterval(handler);
122+
});
123+
124+
test('setIntervalHistory - invalid delay', () => {
125+
const handler = setInterval(() => {}, -1);
126+
const rec = wrapper.setIntervalHistory[0];
127+
128+
expect(rec.individualInvocations).toBe(1);
129+
expect(rec.handlerDelay).toBe(TAG_EXCEPTION('-1'));
130+
expect(rec.hasError).toBe(true);
131+
expect(rec.isEval).toBe(false);
132+
133+
clearInterval(handler);
134+
});
135+
136+
test('clearIntervalHistory - valid handler', () => {
137+
const handler = setInterval(() => {}, 1e3);
138+
clearInterval(handler);
139+
const rec = wrapper.clearIntervalHistory[0];
140+
141+
expect(rec.handlerDelay).toBe(1e3);
142+
});
143+
144+
test('clearIntervalHistory - non existent handler', () => {
145+
clearInterval(1000);
146+
147+
const rec = wrapper.clearIntervalHistory[0];
148+
expect(rec.handlerDelay).toBe('N/A');
149+
expect(rec.hasError).toBe(false);
150+
});
151+
152+
test('clearIntervalHistory - invalid handler', () => {
153+
clearInterval(0);
154+
155+
const rec = wrapper.clearIntervalHistory[0];
156+
expect(rec.handlerDelay).toBe('N/A');
157+
expect(rec.hasError).toBe(true);
158+
});
159+
160+
test('evalHistory - recorded', () => {
161+
const NUMBER_OF_INVOCATIONS = 2;
162+
const CODE = '(1+2)';
163+
const RESULT = 3;
164+
165+
for (let i = 0, I = NUMBER_OF_INVOCATIONS; i < I; i++) {
166+
window.eval(CODE);
167+
}
168+
expect(wrapper.evalHistory.length).toBe(1);
169+
170+
const rec = wrapper.evalHistory[0];
171+
expect(rec.individualInvocations).toBe(NUMBER_OF_INVOCATIONS);
172+
expect(rec.usesLocalScope).toBe(false);
173+
expect(rec.code).toBe(CODE);
174+
expect(rec.returnedValue).toBe(RESULT);
175+
expect(rec.trace.length).toBeGreaterThan(1);
176+
expect(rec.traceId).toBeTruthy();
177+
});
178+
179+
test('evalHistory - detects local scope usage', () => {
180+
const local_variable = 0;
181+
window.eval('(local_variable++)');
182+
183+
const rec = wrapper.evalHistory[0];
184+
expect(rec.individualInvocations).toBe(1);
185+
expect(local_variable).toBe(0);
186+
expect(rec.usesLocalScope).toBe(true);
187+
expect(rec.returnedValue).toBe(TAG_UNDEFINED);
188+
});
189+
});

0 commit comments

Comments
 (0)