Skip to content

Commit 5493cc4

Browse files
committed
feat(core): implement compare reports functions
1 parent 72d6c14 commit 5493cc4

File tree

7 files changed

+213
-2
lines changed

7 files changed

+213
-2
lines changed

packages/core/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export {
22
CollectAndPersistReportsOptions,
33
collectAndPersistReports,
44
} from './lib/collect-and-persist';
5+
export { compareReportFiles, compareReports } from './lib/compare';
56
export { CollectOptions, collect } from './lib/implementation/collect';
67
export {
78
PluginOutputMissingAuditError,

packages/core/src/lib/compare.ts

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { writeFile } from 'node:fs/promises';
2+
import { Report, ReportsDiff, reportSchema } from '@code-pushup/models';
3+
import {
4+
Diff,
5+
calcDuration,
6+
readJsonFile,
7+
scoreReport,
8+
} from '@code-pushup/utils';
9+
import { name as packageName, version } from '../../package.json';
10+
import {
11+
ReportsToCompare,
12+
compareAudits,
13+
compareCategories,
14+
compareGroups,
15+
} from './implementation/compare-scorables';
16+
17+
export function compareReports(reports: Diff<Report>): ReportsDiff {
18+
const start = performance.now();
19+
const date = new Date().toISOString();
20+
21+
const commits: ReportsDiff['commits'] =
22+
reports.before.commit != null && reports.after.commit != null
23+
? { before: reports.before.commit, after: reports.after.commit }
24+
: null;
25+
26+
const scoredReports: ReportsToCompare = {
27+
before: scoreReport(reports.before),
28+
after: scoreReport(reports.after),
29+
};
30+
31+
const categories = compareCategories(scoredReports);
32+
const groups = compareGroups(scoredReports);
33+
const audits = compareAudits(scoredReports);
34+
35+
const duration = calcDuration(start);
36+
37+
return {
38+
commits,
39+
categories,
40+
groups,
41+
audits,
42+
packageName,
43+
version,
44+
date,
45+
duration,
46+
};
47+
}
48+
49+
export async function compareReportFiles(
50+
inputPaths: Diff<string>,
51+
outputPath: string,
52+
): Promise<void> {
53+
const [reportBefore, reportAfter] = await Promise.all([
54+
readJsonFile(inputPaths.before),
55+
readJsonFile(inputPaths.after),
56+
]);
57+
const reports: Diff<Report> = {
58+
before: reportSchema.parse(reportBefore),
59+
after: reportSchema.parse(reportAfter),
60+
};
61+
62+
const reportsDiff = compareReports(reports);
63+
64+
await writeFile(outputPath, JSON.stringify(reportsDiff, null, 2));
65+
}
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { vol } from 'memfs';
2+
import { join } from 'node:path';
3+
import { Commit, Report, reportsDiffSchema } from '@code-pushup/models';
4+
import {
5+
COMMIT_ALT_MOCK,
6+
COMMIT_MOCK,
7+
MEMFS_VOLUME,
8+
MINIMAL_REPORT_MOCK,
9+
REPORT_MOCK,
10+
reportMock,
11+
} from '@code-pushup/test-utils';
12+
import { Diff, readJsonFile } from '@code-pushup/utils';
13+
import { compareReportFiles, compareReports } from './compare';
14+
15+
describe('compareReports', () => {
16+
const mockCommits: Diff<Commit> = {
17+
before: COMMIT_MOCK,
18+
after: COMMIT_ALT_MOCK,
19+
};
20+
21+
describe('unchanged reports', () => {
22+
const mockReport = reportMock();
23+
const mockReports: Diff<Report> = {
24+
before: { ...mockReport, commit: mockCommits.before },
25+
after: { ...mockReport, commit: mockCommits.after },
26+
};
27+
28+
it('should create valid report diff', () => {
29+
const reportsDiff = compareReports(mockReports);
30+
expect(() => reportsDiffSchema.parse(reportsDiff)).not.toThrow();
31+
});
32+
33+
it('should include commits from both reports', () => {
34+
const reportsDiff = compareReports(mockReports);
35+
expect(reportsDiff.commits).toEqual(mockCommits);
36+
});
37+
38+
it('should have no changes, additions or removals', () => {
39+
const reportsDiff = compareReports(mockReports);
40+
expect(reportsDiff.categories.changed).toHaveLength(0);
41+
expect(reportsDiff.categories.added).toHaveLength(0);
42+
expect(reportsDiff.categories.removed).toHaveLength(0);
43+
expect(reportsDiff.groups.changed).toHaveLength(0);
44+
expect(reportsDiff.groups.added).toHaveLength(0);
45+
expect(reportsDiff.groups.removed).toHaveLength(0);
46+
expect(reportsDiff.audits.changed).toHaveLength(0);
47+
expect(reportsDiff.audits.added).toHaveLength(0);
48+
expect(reportsDiff.audits.removed).toHaveLength(0);
49+
});
50+
51+
it('should contain all categories/groups/audits in unchanged arrays', () => {
52+
const reportsDiff = compareReports(mockReports);
53+
expect(reportsDiff.categories.unchanged).toHaveLength(
54+
mockReport.categories.length,
55+
);
56+
expect(reportsDiff.groups.unchanged).toHaveLength(
57+
mockReport.plugins.reduce((acc, { groups }) => acc + groups!.length, 0),
58+
);
59+
expect(reportsDiff.audits.unchanged).toHaveLength(
60+
mockReport.plugins.reduce((acc, { audits }) => acc + audits.length, 0),
61+
);
62+
});
63+
});
64+
65+
describe('changed reports', () => {
66+
const mockReports: Diff<Report> = {
67+
before: { ...MINIMAL_REPORT_MOCK, commit: mockCommits.before },
68+
after: { ...REPORT_MOCK, commit: mockCommits.after },
69+
};
70+
71+
it('should create valid report diff', () => {
72+
const reportsDiff = compareReports(mockReports);
73+
expect(() => reportsDiffSchema.parse(reportsDiff)).not.toThrow();
74+
});
75+
76+
it('should include commits from both reports', () => {
77+
const reportsDiff = compareReports(mockReports);
78+
expect(reportsDiff.commits).toEqual(mockCommits);
79+
});
80+
81+
it('should only have added categories (minimal report has none)', () => {
82+
const reportsDiff = compareReports(mockReports);
83+
expect(reportsDiff.categories.added).toHaveLength(
84+
REPORT_MOCK.categories.length,
85+
);
86+
expect(reportsDiff.categories.removed).toHaveLength(0);
87+
expect(reportsDiff.categories.changed).toHaveLength(0);
88+
expect(reportsDiff.categories.unchanged).toHaveLength(0);
89+
});
90+
91+
it('should only have added groups (minimal report has none)', () => {
92+
const reportsDiff = compareReports(mockReports);
93+
expect(reportsDiff.groups.added.length).toBeGreaterThan(0);
94+
expect(reportsDiff.groups.removed).toHaveLength(0);
95+
expect(reportsDiff.groups.changed).toHaveLength(0);
96+
expect(reportsDiff.groups.unchanged).toHaveLength(0);
97+
});
98+
99+
it('should mark multiple audits as added and 1 as removed (single audit from minimal report unmatched)', () => {
100+
const reportsDiff = compareReports(mockReports);
101+
expect(reportsDiff.audits.added.length).toBeGreaterThan(1);
102+
expect(reportsDiff.audits.removed).toHaveLength(1);
103+
expect(reportsDiff.audits.changed).toHaveLength(0);
104+
expect(reportsDiff.audits.unchanged).toHaveLength(0);
105+
});
106+
});
107+
});
108+
109+
describe('compareReportFiles', () => {
110+
beforeEach(() => {
111+
vol.fromJSON(
112+
{
113+
'source-report.json': JSON.stringify(MINIMAL_REPORT_MOCK),
114+
'target-report.json': JSON.stringify(REPORT_MOCK),
115+
},
116+
MEMFS_VOLUME,
117+
);
118+
});
119+
120+
it('should create valid reports-diff.json from report.json files', async () => {
121+
await compareReportFiles(
122+
{
123+
before: join(MEMFS_VOLUME, 'source-report.json'),
124+
after: join(MEMFS_VOLUME, 'target-report.json'),
125+
},
126+
join(MEMFS_VOLUME, 'reports-diff.json'),
127+
);
128+
129+
const reportsDiffPromise = readJsonFile(
130+
join(MEMFS_VOLUME, 'reports-diff.json'),
131+
);
132+
await expect(reportsDiffPromise).resolves.toBeTruthy();
133+
134+
const reportsDiff = await reportsDiffPromise;
135+
expect(() => reportsDiffSchema.parse(reportsDiff)).not.toThrow();
136+
});
137+
});

testing/test-utils/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from './lib/utils/execute-process-helper.mock';
33
export * from './lib/utils/os-agnostic-paths';
44

55
// static mocks
6+
export * from './lib/utils/commit.mock';
67
export * from './lib/utils/core-config.mock';
78
export * from './lib/utils/minimal-config.mock';
89
export * from './lib/utils/report.mock';

testing/test-utils/src/lib/utils/dynamic-mocks/commit.mock.ts renamed to testing/test-utils/src/lib/utils/commit.mock.ts

+7
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@ export const COMMIT_MOCK: Commit = {
66
author: 'John Doe',
77
date: new Date('2023-08-16T08:30:00.000Z'),
88
};
9+
10+
export const COMMIT_ALT_MOCK: Commit = {
11+
hash: '0123456789abcdef0123456789abcdef01234567',
12+
message: 'Major fixes',
13+
author: 'Jane Doe',
14+
date: new Date('2023-08-16T10:00:00.000Z'),
15+
};

testing/test-utils/src/lib/utils/dynamic-mocks/report.mock.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Report, reportSchema } from '@code-pushup/models';
2+
import { COMMIT_MOCK } from '../commit.mock';
23
import { categoryConfigsMock } from './categories.mock';
3-
import { COMMIT_MOCK } from './commit.mock';
44
import { eslintPluginReportMock } from './eslint-plugin.mock';
55
import { lighthousePluginReportMock } from './lighthouse-plugin.mock';
66

testing/test-utils/src/lib/utils/report.mock.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { PluginConfig, PluginReport, Report } from '@code-pushup/models';
2-
import { COMMIT_MOCK } from './dynamic-mocks/commit.mock';
2+
import { COMMIT_MOCK } from './commit.mock';
33
import {
44
auditReportMock,
55
pluginConfigMock,

0 commit comments

Comments
 (0)