Skip to content

Commit 478b070

Browse files
committed
test(utils): improve report mock, clean up tests, executeProcess runner
1 parent 51d99cf commit 478b070

10 files changed

Lines changed: 314 additions & 160 deletions

packages/utils/src/lib/execute-process.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ export function executeProcess(cfg: ProcessConfig): Promise<ProcessResult> {
138138
const start = performance.now();
139139

140140
return new Promise((resolve, reject) => {
141-
const process = spawn(cfg.command, cfg.args, { cwd, shell: true }); // @TODO add comments on why shell: true
141+
// shell:true tells Windows to use shell command for spawning a child process
142+
const process = spawn(cfg.command, cfg.args, { cwd, shell: true });
142143
let stdout = '';
143144
let stderr = '';
144145

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,70 @@
11
import { describe, expect, it, vi } from 'vitest';
2-
import { getAsyncProcessRunnerConfig, mockProcessConfig } from '../../test';
3-
import { executeProcess } from './execute-process';
2+
import { getAsyncProcessRunnerConfig } from '@code-pushup/testing-utils';
3+
import { ProcessObserver, executeProcess } from './execute-process';
44

55
describe('executeProcess', () => {
6+
const spyObserver: ProcessObserver = {
7+
onStdout: vi.fn(),
8+
onError: vi.fn(),
9+
onComplete: vi.fn(),
10+
};
11+
const errorSpy = vi.fn();
12+
13+
beforeEach(() => {
14+
vi.clearAllMocks();
15+
});
16+
617
it('should work with node command `node -v`', async () => {
7-
const cfg = mockProcessConfig({ command: `node`, args: ['-v'] });
8-
const processResult = await executeProcess(cfg);
18+
const processResult = await executeProcess({
19+
command: `node`,
20+
args: ['-v'],
21+
observer: spyObserver,
22+
});
23+
expect(spyObserver?.onStdout).toHaveBeenCalledTimes(1);
24+
expect(spyObserver?.onComplete).toHaveBeenCalledTimes(1);
25+
expect(spyObserver?.onError).toHaveBeenCalledTimes(0);
926
expect(processResult.stdout).toMatch(/v[0-9]{1,2}(\.[0-9]{1,2}){0,2}/);
1027
});
1128

1229
it('should work with npx command `npx --help`', async () => {
13-
const cfg = mockProcessConfig({ command: `npx`, args: ['--help'] });
14-
const { observer } = cfg;
15-
const processResult = await executeProcess(cfg);
16-
expect(observer?.onStdout).toHaveBeenCalledTimes(1);
17-
expect(observer?.onComplete).toHaveBeenCalledTimes(1);
30+
const processResult = await executeProcess({
31+
command: `npx`,
32+
args: ['--help'],
33+
observer: spyObserver,
34+
});
35+
expect(spyObserver?.onStdout).toHaveBeenCalledTimes(1);
36+
expect(spyObserver?.onComplete).toHaveBeenCalledTimes(1);
37+
expect(spyObserver?.onError).toHaveBeenCalledTimes(0);
1838
expect(processResult.stdout).toContain('npm exec');
1939
});
2040

2141
it('should work with script `node custom-script.js`', async () => {
22-
const cfg = mockProcessConfig(
23-
getAsyncProcessRunnerConfig({ interval: 10 }),
24-
);
25-
const { observer } = cfg;
26-
const errorSpy = vi.fn();
27-
const processResult = await executeProcess(cfg).catch(errorSpy);
42+
const processResult = await executeProcess({
43+
...getAsyncProcessRunnerConfig({ interval: 10, runs: 4 }),
44+
observer: spyObserver,
45+
}).catch(errorSpy);
46+
2847
expect(errorSpy).toHaveBeenCalledTimes(0);
2948
expect(processResult.stdout).toContain('process:complete');
30-
expect(observer?.onStdout).toHaveBeenCalledTimes(6);
31-
expect(observer?.onError).toHaveBeenCalledTimes(0);
32-
expect(observer?.onComplete).toHaveBeenCalledTimes(1);
49+
expect(spyObserver?.onStdout).toHaveBeenCalledTimes(6); // intro + 4 runs + complete
50+
expect(spyObserver?.onError).toHaveBeenCalledTimes(0);
51+
expect(spyObserver?.onComplete).toHaveBeenCalledTimes(1);
3352
});
3453

35-
it('should work with async script `node custom-script.js --arg` that throws an error', async () => {
36-
const cfg = mockProcessConfig(
37-
getAsyncProcessRunnerConfig({ interval: 10, runs: 1, throwError: true }),
38-
);
39-
const { observer } = cfg;
40-
const errorSpy = vi.fn();
41-
const processResult = await executeProcess(cfg).catch(errorSpy);
54+
it('should work with async script `node custom-script.js` that throws an error', async () => {
55+
const processResult = await executeProcess({
56+
...getAsyncProcessRunnerConfig({
57+
interval: 10,
58+
runs: 1,
59+
throwError: true,
60+
}),
61+
observer: spyObserver,
62+
}).catch(errorSpy);
63+
4264
expect(errorSpy).toHaveBeenCalledTimes(1);
4365
expect(processResult).toBeUndefined();
44-
expect(observer?.onComplete).toHaveBeenCalledTimes(0);
45-
expect(observer?.onStdout).toHaveBeenCalledTimes(2);
46-
expect(observer?.onError).toHaveBeenCalledTimes(1);
66+
expect(spyObserver?.onStdout).toHaveBeenCalledTimes(2); // intro + 1 run before error
67+
expect(spyObserver?.onError).toHaveBeenCalledTimes(1);
68+
expect(spyObserver?.onComplete).toHaveBeenCalledTimes(0);
4769
});
4870
});

packages/utils/src/lib/formatting.unit.test.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,17 @@ describe('pluralize', () => {
3535
describe('formatBytes', () => {
3636
it.each([
3737
[0, '0 B'],
38-
[1000, '1000 B'],
39-
[10000, '9.77 kB'],
40-
[10000000, '9.54 MB'],
41-
[10000000000, '9.31 GB'],
42-
[10000000000000, '9.09 TB'],
43-
[10000000000000000, '8.88 PB'],
44-
[10000000000000000000, '8.67 EB'],
45-
[10000000000000000000000, '8.47 ZB'],
46-
[10000000000000000000000000, '8.27 YB'],
47-
[10000000000000000000000, '8.47 ZB'],
48-
[10000000000000000000000, '8.47 ZB'],
49-
])('should log file sizes correctly for %s`', (bytes, displayValue) => {
38+
[1_000, '1000 B'],
39+
[10_000, '9.77 kB'],
40+
[10_000_000, '9.54 MB'],
41+
[10_000_000_000, '9.31 GB'],
42+
[10_000_000_000_000, '9.09 TB'],
43+
[5_000_000_000_000_000, '4.44 PB'],
44+
])('should log file sizes correctly for %s', (bytes, displayValue) => {
5045
expect(formatBytes(bytes)).toBe(displayValue);
5146
});
5247

53-
it('should log file sizes correctly with correct decimal`', () => {
48+
it('should log file sizes correctly with correct decimal', () => {
5449
expect(formatBytes(10000, 1)).toBe('9.8 kB');
5550
});
5651
});
@@ -63,7 +58,7 @@ describe('pluralizeToken', () => {
6358
[0, '0 files'],
6459
[1, '1 file'],
6560
[2, '2 files'],
66-
])('should log correct plural for %s`', (times, plural) => {
61+
])('should log correct plural for %s', (times, plural) => {
6762
expect(pluralizeToken('file', times)).toBe(plural);
6863
});
6964
});
@@ -75,7 +70,7 @@ describe('formatDuration', () => {
7570
[1, '1 ms'],
7671
[2, '2 ms'],
7772
[1200, '1.20 s'],
78-
])('should log correct plural for %s`', (ms, displayValue) => {
73+
])('should log correctly formatted duration for %s', (ms, displayValue) => {
7974
expect(formatDuration(ms)).toBe(displayValue);
8075
});
8176
});
Lines changed: 79 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,105 @@
11
import { describe, expect } from 'vitest';
2-
import { report } from '@code-pushup/models/testing';
2+
import { REPORT_MOCK } from '@code-pushup/testing-utils';
33
import { calculateScore, scoreReport } from './scoring';
44

55
describe('calculateScore', () => {
6-
// https://googlechrome.github.io/lighthouse/scorecalc/#FCP=1200&LCP=1450&TBT=0&CLS=0&SI=1200&TTI=1200&FMP=1200&device=desktop&version=10.3.0
7-
const refs = [
8-
{ slug: 'first-contentful-paint', weight: 1 },
9-
{ slug: 'largest-contentful-paint', weight: 2.5 },
10-
{ slug: 'speed-index', weight: 1 },
11-
{ slug: 'total-blocking-time', weight: 3 },
12-
{ slug: 'cumulative-layout-shift', weight: 2.5 },
13-
];
14-
const scores = {
15-
'first-contentful-paint': 0.75,
16-
'largest-contentful-paint': 0.82,
17-
'speed-index': 0.93,
18-
'total-blocking-time': 1,
19-
'cumulative-layout-shift': 1,
20-
'unminified-javascript': 1,
21-
'uses-long-cache-ttl': 0,
22-
};
23-
const scoreFn = (ref: (typeof refs)[number]) =>
24-
scores[ref.slug as keyof typeof scores];
6+
it('should calculate the same score for one reference', () => {
7+
expect(
8+
calculateScore(
9+
[{ slug: 'first-contentful-paint', weight: 1, score: 0.75 }],
10+
ref => ref.score,
11+
),
12+
).toBe(0.75);
13+
});
2514

26-
it('Lighthouse performance group', () => {
27-
expect(calculateScore(refs, scoreFn)).toBeCloseTo(0.92);
15+
it('should calculate correct score for multiple references', () => {
16+
expect(
17+
calculateScore(
18+
[
19+
{ slug: 'first-contentful-paint', weight: 3, score: 0 },
20+
{ slug: 'cumulative-layout-shift', weight: 1, score: 1 },
21+
],
22+
ref => ref.score,
23+
),
24+
).toBe(0.25);
2825
});
2926

30-
it('ignore refs with weight 0', () => {
27+
it('should remove zero-weight reference from score calculation', () => {
3128
expect(
3229
calculateScore(
3330
[
34-
...refs,
35-
{ slug: 'unminified-javascript', weight: 0 },
36-
{ slug: 'uses-long-cache-ttl', weight: 0 },
31+
{ slug: 'first-contentful-paint', weight: 3, score: 0 },
32+
{ slug: 'cumulative-layout-shift', weight: 1, score: 1 },
33+
{ slug: 'speed-index', weight: 0, score: 1 },
3734
],
38-
scoreFn,
35+
ref => ref.score,
36+
),
37+
).toBe(0.25);
38+
});
39+
40+
it('should calculate correct score for realistic Lighthouse values', () => {
41+
// https://googlechrome.github.io/lighthouse/scorecalc/#FCP=1200&LCP=1450&TBT=0&CLS=0&SI=1200&TTI=1200&FMP=1200&device=desktop&version=10.3.0
42+
expect(
43+
calculateScore(
44+
[
45+
{ slug: 'first-contentful-paint', weight: 1, score: 0.75 },
46+
{ slug: 'largest-contentful-paint', weight: 2.5, score: 0.82 },
47+
{ slug: 'speed-index', weight: 1, score: 0.93 },
48+
{ slug: 'total-blocking-time', weight: 3, score: 1 },
49+
{ slug: 'cumulative-layout-shift', weight: 2.5, score: 1 },
50+
],
51+
ref => ref.score,
3952
),
4053
).toBeCloseTo(0.92);
4154
});
4255

43-
it('throws for empty refs (weight 0 is ignored internally)', () => {
56+
it('should throw for an empty reference array', () => {
4457
expect(() =>
45-
calculateScore([{ slug: 'uses-long-cache-ttl', weight: 0 }], scoreFn),
46-
).toThrow(
47-
'0 division for score. This can be caused by refs only weighted with 0 or empty refs',
48-
);
58+
calculateScore<{ weight: number }>([], ref => ref.weight),
59+
).toThrow('0 division for score');
4960
});
5061

51-
it('works for 0 scores', () => {
52-
expect(
53-
calculateScore([{ slug: 'uses-long-cache-ttl', weight: 1 }], scoreFn),
54-
).toBe(0);
62+
it('should throw for a reference array full of zero weights', () => {
63+
expect(() =>
64+
calculateScore(
65+
[
66+
{ slug: 'first-contentful-paint', weight: 0, score: 0 },
67+
{ slug: 'cumulative-layout-shift', weight: 0, score: 1 },
68+
],
69+
ref => ref.weight,
70+
),
71+
).toThrow('0 division for score');
5572
});
5673
});
5774

5875
describe('scoreReport', () => {
59-
it('should score valid report', () => {
60-
const reportA = report();
61-
const scoredReport = scoreReport(reportA);
76+
it('should correctly score a valid report', () => {
77+
/*
78+
The report mock has the following data:
79+
Test results
80+
-> Cypress E2E: score 0.5 | weight 2
81+
-> CyCT: score 1 | weight 1
82+
=> Overall score = 1.5/3 = 0.5
83+
84+
Bug prevention
85+
-> TypeScript ESLint group: score 0.25 weight 8
86+
-> ts-eslint-typing score 0 weight 3
87+
-> ts-eslint-enums score 1 weight 1
88+
-> ts-eslint-experimental score 0 weight 0
89+
90+
-> ESLint Jest naming: score 1 weight 1
91+
-> TypeScript ESLint functional: score 0 weight 1
92+
-> ESLint Cypress: score 1 weight 0
93+
=> Overall score = 3/10 = 0.3
94+
*/
6295

63-
// enriched audits
64-
expect(scoredReport.plugins[1]?.audits[0]?.plugin).toBe('lighthouse');
65-
expect(scoredReport.plugins[1]?.audits[0]?.slug).toBe(
66-
'first-contentful-paint',
96+
expect(scoreReport(REPORT_MOCK)).toEqual(
97+
expect.objectContaining({
98+
categories: [
99+
expect.objectContaining({ slug: 'test-results', score: 0.625 }),
100+
expect.objectContaining({ slug: 'bug-prevention', score: 0.3 }),
101+
],
102+
}),
67103
);
68-
expect(scoredReport.plugins[1]?.audits[0]?.score).toBeCloseTo(0.76);
69-
// enriched and scored groups
70-
expect(scoredReport.plugins[1]?.groups[0]?.plugin).toBe('lighthouse');
71-
expect(scoredReport.plugins[1]?.groups[0]?.slug).toBe('performance');
72-
expect(scoredReport.plugins[1]?.groups[0]?.score).toBeCloseTo(0.92);
73-
// enriched and scored categories
74-
expect(scoredReport.categories[0]?.slug).toBe('performance');
75-
expect(scoredReport?.categories?.[0]?.score).toBeCloseTo(0.92);
76-
expect(scoredReport.categories[1]?.slug).toBe('bug-prevention');
77-
expect(scoredReport?.categories?.[1]?.score).toBeCloseTo(0.68);
78-
expect(scoredReport.categories[2]?.slug).toBe('code-style');
79-
expect(scoredReport?.categories?.[2]?.score).toBeCloseTo(0.54);
80104
});
81105
});
Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,28 @@
11
import { describe, expect, it } from 'vitest';
22
import { verboseUtils } from './verbose-utils';
33

4-
const verboseHelper = verboseUtils(true);
5-
const noVerboseHelper = verboseUtils(false);
6-
74
describe('verbose-utils', () => {
85
it('exec should work verbose', () => {
96
const spy = vi.fn();
10-
verboseHelper.exec(spy);
7+
verboseUtils(true).exec(spy);
118
expect(spy).toHaveBeenCalled();
129
expect(console.info).not.toHaveBeenCalled();
1310
});
1411

1512
it('exec should work no-verbose', () => {
1613
const spy = vi.fn();
17-
noVerboseHelper.exec(spy);
14+
verboseUtils(false).exec(spy);
1815
expect(spy).not.toHaveBeenCalled();
1916
expect(console.info).not.toHaveBeenCalled();
2017
});
2118

2219
it('log should work verbose', () => {
23-
verboseHelper.log('42');
20+
verboseUtils(true).log('42');
2421
expect(console.info).toHaveBeenCalledWith('42');
2522
});
2623

2724
it('log should work no-verbose', () => {
28-
noVerboseHelper.log('42');
25+
verboseUtils(false).log('42');
2926
expect(console.info).not.toHaveBeenCalled();
3027
});
3128
});

0 commit comments

Comments
 (0)