Skip to content

Commit e68ce12

Browse files
authored
test: add custom matcher for markdown table rows (#926)
1 parent ec834db commit e68ce12

File tree

8 files changed

+173
-32
lines changed

8 files changed

+173
-32
lines changed

packages/core/src/lib/implementation/persist.unit.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,11 @@ describe('persistReport', () => {
7171
'utf8',
7272
);
7373
expect(mdReport).toContain('Code PushUp Report');
74-
expect(mdReport).toMatch(
75-
/\|\s*🏷 Category\s*\|\s* Score\s*\|\s*🛡 Audits\s*\|/,
76-
);
74+
expect(mdReport).toContainMarkdownTableRow([
75+
'🏷 Category',
76+
'⭐ Score',
77+
'🛡 Audits',
78+
]);
7779

7880
const jsonReport: Report = JSON.parse(
7981
await readFile(path.join(MEMFS_VOLUME, 'report.json'), 'utf8'),

packages/core/vite.config.unit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default defineConfig({
2929
'../../testing/test-setup/src/lib/reset.mocks.ts',
3030
'../../testing/test-setup/src/lib/portal-client.mock.ts',
3131
'../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts',
32+
'../../testing/test-setup/src/lib/extend/markdown-table.matcher.ts',
3233
],
3334
},
3435
});

packages/utils/src/lib/reports/generate-md-report.unit.test.ts

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ describe('auditDetailsIssues', () => {
176176
severity: 'warning',
177177
} as Issue,
178178
])?.toString(),
179-
).toMatch(/\|\s*4-7\s*\|/);
179+
).toContainMarkdownTableRow(['⚠️ _warning_', '', '`index.js`', '4-7']);
180180
});
181181
});
182182

@@ -286,8 +286,8 @@ describe('auditDetails', () => {
286286
} as AuditReport).toString();
287287
expect(md).toMatch('<details>');
288288
expect(md).toMatch('#### Elements');
289-
expect(md).toMatch(/\|\s*button\s*\|/);
290-
expect(md).toMatch(/\|\s*div\s*\|/);
289+
expect(md).toContainMarkdownTableRow(['button']);
290+
expect(md).toContainMarkdownTableRow(['div']);
291291
expect(md).not.toMatch('#### Issues');
292292
});
293293

@@ -375,10 +375,13 @@ describe('auditsSection', () => {
375375
],
376376
} as ScoredReport).toString();
377377
expect(md).toMatch('#### Issues');
378-
expect(md).toMatch(
379-
/\|\s*Severity\s*\|\s*Message\s*\|\s*Source file\s*\|\s*Line\(s\)\s*\|/,
380-
);
381-
expect(md).toMatch(/\|\s*value\s*\|/);
378+
expect(md).toContainMarkdownTableRow([
379+
'Severity',
380+
'Message',
381+
'Source file',
382+
'Line(s)',
383+
]);
384+
expect(md).toContainMarkdownTableRow(['value']);
382385
});
383386

384387
it('should render audit meta information', () => {
@@ -472,12 +475,23 @@ describe('aboutSection', () => {
472475
],
473476
categories: Array.from({ length: 3 }),
474477
} as ScoredReport).toString();
475-
expect(md).toMatch(
476-
/\|\s*Commit\s*\|\s*Version\s*\|\s*Duration\s*\|\s*Plugins\s*\|\s*Categories\s*\|\s*Audits\s*\|/,
477-
);
478-
expect(md).toMatch(
479-
/\|\s*ci: update action \(535b8e9e557336618a764f3fa45609d224a62837\)\s*\|\s*`v1.0.0`\s*\|\s*4.20 s\s*\|\s*1\s*\|\s*3\s*\|\s*3\s*\|/,
480-
);
478+
479+
expect(md).toContainMarkdownTableRow([
480+
'Commit',
481+
'Version',
482+
'Duration',
483+
'Plugins',
484+
'Categories',
485+
'Audits',
486+
]);
487+
expect(md).toContainMarkdownTableRow([
488+
'ci: update action (535b8e9e557336618a764f3fa45609d224a62837)',
489+
'`v1.0.0`',
490+
'4.20 s',
491+
'1',
492+
'3',
493+
'3',
494+
]);
481495
});
482496

483497
it('should return plugins section with content', () => {
@@ -498,15 +512,25 @@ describe('aboutSection', () => {
498512
},
499513
],
500514
} as ScoredReport).toString();
501-
expect(md).toMatch(
502-
/\|\s*Plugin\s*\|\s*Audits\s*\|\s*Version\s*\|\s*Duration\s*\|/,
503-
);
504-
expect(md).toMatch(
505-
/\|\s*Lighthouse\s*\|\s*78\s*\|\s*`1.0.1`\s*\|\s*15.37 s\s*\|/,
506-
);
507-
expect(md).toMatch(
508-
/\|\s*File Size\s*\|\s*2\s*\|\s*`0.3.12`\s*\|\s*260 ms\s*\|/,
509-
);
515+
516+
expect(md).toContainMarkdownTableRow([
517+
'Plugin',
518+
'Audits',
519+
'Version',
520+
'Duration',
521+
]);
522+
expect(md).toContainMarkdownTableRow([
523+
'Lighthouse',
524+
'78',
525+
'`1.0.1`',
526+
'15.37 s',
527+
]);
528+
expect(md).toContainMarkdownTableRow([
529+
'File Size',
530+
'2',
531+
'`0.3.12`',
532+
'260 ms',
533+
]);
510534
});
511535

512536
it('should return full about section', () => {
@@ -534,19 +558,24 @@ describe('generateMdReport', () => {
534558
// report title
535559
expect(md).toMatch('# Code PushUp Report');
536560
// categories section heading
537-
expect(md).toMatch(
538-
/\|\s*🏷 Category\s*\|\s* Score\s*\|\s*🛡 Audits\s*\|/,
539-
);
561+
expect(md).toContainMarkdownTableRow([
562+
'🏷 Category',
563+
'⭐ Score',
564+
'🛡 Audits',
565+
]);
540566
// categories section heading
541567
expect(md).toMatch('## 🏷 Categories');
542568
// audits heading
543569
expect(md).toMatch('## 🛡️ Audits');
544570
// about section heading
545571
expect(md).toMatch('## About');
546572
// plugin table
547-
expect(md).toMatch(
548-
/\|\s*Plugin\s*\|\s*Audits\s*\|\s*Version\s*\|\s*Duration\s*\|/,
549-
);
573+
expect(md).toContainMarkdownTableRow([
574+
'Plugin',
575+
'Audits',
576+
'Version',
577+
'Duration',
578+
]);
550579
// made with <3
551580
expect(md).toMatch('Made with ❤ by [Code PushUp]');
552581
});

packages/utils/vite.config.unit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export default defineConfig({
2727
'../../testing/test-setup/src/lib/console.mock.ts',
2828
'../../testing/test-setup/src/lib/reset.mocks.ts',
2929
'../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts',
30+
'../../testing/test-setup/src/lib/extend/markdown-table.matcher.ts',
3031
],
3132
},
3233
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { SyncExpectationResult } from '@vitest/expect';
2+
import { expect } from 'vitest';
3+
4+
export type CustomMarkdownTableMatchers = {
5+
toContainMarkdownTableRow: (cells: string[]) => void;
6+
};
7+
8+
expect.extend({
9+
toContainMarkdownTableRow: assertMarkdownTableRow,
10+
});
11+
12+
function assertMarkdownTableRow(
13+
actual: string,
14+
expected: string[],
15+
): SyncExpectationResult {
16+
const rows = actual
17+
.split('\n')
18+
.map(line => line.trim())
19+
.filter(line => line.startsWith('|') && line.endsWith('|'))
20+
.map(line =>
21+
line
22+
.slice(1, -1)
23+
.split(/(?<!\\)\|/)
24+
.map(cell => cell.replace(/\\\|/g, '|').trim()),
25+
);
26+
27+
const pass = rows.some(
28+
row =>
29+
row.length === expected.length &&
30+
row.every((cell, i) => cell === expected[i]),
31+
);
32+
return pass
33+
? {
34+
pass,
35+
message: () =>
36+
`Expected markdown not to contain a table row with cells: ${expected.join(', ')}`,
37+
}
38+
: {
39+
pass,
40+
message: () =>
41+
`Expected markdown to contain a table row with cells: ${expected.join(', ')}`,
42+
};
43+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
describe('markdown-table-matcher', () => {
4+
it('should match header and data rows in a markdown table', () => {
5+
const markdown = `
6+
| 🏷 Category | ⭐ Score | 🛡 Audits |
7+
| :-------------------------- | :-------: | :-------: |
8+
| [Security](#security) | 🟡 **81** | 2 |
9+
| [Performance](#performance) | 🟡 **64** | 56 |
10+
| [SEO](#seo) | 🟡 **61** | 11 |
11+
`;
12+
13+
expect(markdown).toContainMarkdownTableRow([
14+
'🏷 Category',
15+
'⭐ Score',
16+
'🛡 Audits',
17+
]);
18+
expect(markdown).toContainMarkdownTableRow([
19+
':--------------------------',
20+
':-------:',
21+
':-------:',
22+
]);
23+
expect(markdown).toContainMarkdownTableRow([
24+
'[Performance](#performance)',
25+
'🟡 **64**',
26+
'56',
27+
]);
28+
expect(markdown).not.toContainMarkdownTableRow([
29+
'Non-existent cell',
30+
'Row cell',
31+
'Test cell',
32+
]);
33+
});
34+
35+
it('should match table rows containing escaped pipe symbols', () => {
36+
const markdown = `
37+
| Package | Versions |
38+
| :--------- | :----------------------- |
39+
| \`eslint\` | \`^8.0.0 \\|\\| ^9.0.0\` |
40+
`;
41+
expect(markdown).toContainMarkdownTableRow([
42+
'`eslint`',
43+
'`^8.0.0 || ^9.0.0`',
44+
]);
45+
});
46+
47+
it('should match table rows with an empty cell', () => {
48+
const markdown = `
49+
| Severity | Message | Source file | Line(s) |
50+
| :--------: | :------------------------ | :-------------------- | :-----: |
51+
| 🚨 _error_ | File size is 20KB too big | \`list.component.ts\` | |
52+
`;
53+
expect(markdown).toContainMarkdownTableRow([
54+
'🚨 _error_',
55+
'File size is 20KB too big',
56+
'`list.component.ts`',
57+
'',
58+
]);
59+
});
60+
});

testing/test-setup/src/vitest.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
/* eslint-disable @typescript-eslint/consistent-type-definitions */
2+
import type { CustomMarkdownTableMatchers } from './lib/extend/markdown-table.matcher.js';
23
import type {
34
CustomAsymmetricPathMatchers,
45
CustomPathMatchers,
56
} from './lib/extend/path.matcher.js';
67
import type { CustomUiLoggerMatchers } from './lib/extend/ui-logger.matcher.js';
78

89
declare module 'vitest' {
9-
interface Assertion extends CustomPathMatchers, CustomUiLoggerMatchers {}
10+
interface Assertion
11+
extends CustomPathMatchers,
12+
CustomUiLoggerMatchers,
13+
CustomMarkdownTableMatchers {}
1014
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
1115
interface AsymmetricMatchersContaining extends CustomAsymmetricPathMatchers {}
1216
}

testing/test-setup/vite.config.unit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export default defineConfig({
2323
setupFiles: [
2424
'../test-setup/src/lib/reset.mocks.ts',
2525
'../test-setup/src/lib/extend/path.matcher.ts',
26+
'../test-setup/src/lib/extend/markdown-table.matcher.ts',
2627
],
2728
},
2829
});

0 commit comments

Comments
 (0)