Skip to content

Commit 5f453ef

Browse files
committed
feat(utils): implement markdown diff formatting
1 parent 3d57208 commit 5f453ef

13 files changed

+439
-3
lines changed

packages/utils/src/lib/formatting.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ export function slugify(text: string): string {
1212
.replace(/[^a-z\d-]/g, '');
1313
}
1414

15-
export function pluralize(text: string): string {
15+
export function pluralize(text: string, amount?: number): string {
16+
if (amount != null && Math.abs(amount) === 1) {
17+
return text;
18+
}
19+
1620
if (text.endsWith('y')) {
1721
return `${text.slice(0, -1)}ies`;
1822
}
@@ -41,7 +45,7 @@ export function formatBytes(bytes: number, decimals = 2) {
4145
}`;
4246
}
4347

44-
export function pluralizeToken(token: string, times = 0): string {
48+
export function pluralizeToken(token: string, times: number): string {
4549
return `${times} ${Math.abs(times) === 1 ? token : pluralize(token)}`;
4650
}
4751

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ describe('pluralize', () => {
3131
])('should pluralize "%s" as "%s"', (singular, plural) => {
3232
expect(pluralize(singular)).toBe(plural);
3333
});
34+
35+
it('should not pluralize if 1 passed in as amount', () => {
36+
expect(pluralize('audit', 1)).toBe('audit');
37+
});
38+
39+
it('should pluralize if amount is other than 1/-1', () => {
40+
expect(pluralize('audit', 2)).toBe('audits');
41+
});
3442
});
3543

3644
describe('formatBytes', () => {
@@ -53,7 +61,6 @@ describe('formatBytes', () => {
5361

5462
describe('pluralizeToken', () => {
5563
it.each([
56-
[undefined, '0 files'],
5764
[-2, '-2 files'],
5865
[-1, '-1 file'],
5966
[0, '0 files'],

packages/utils/src/lib/reports/__snapshots__/generate-md-report.integration.test.ts.snap

+40
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ Performance metrics [📖 Docs](https://developers.google.com/web/fundamentals/p
7373
7474
<details>
7575
<summary>🟥 <b>6 warnings</b> (score: 0)</summary>
76+
7677
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>'onCreate' is missing in props validation</td><td><code>src/components/CreateTodo.jsx</code></td><td>15</td></tr><tr><td>⚠️ <i>warning</i></td><td>'setQuery' is missing in props validation</td><td><code>src/components/TodoFilter.jsx</code></td><td>10</td></tr><tr><td>⚠️ <i>warning</i></td><td>'setHideComplete' is missing in props validation</td><td><code>src/components/TodoFilter.jsx</code></td><td>18</td></tr><tr><td>⚠️ <i>warning</i></td><td>'todos' is missing in props validation</td><td><code>src/components/TodoList.jsx</code></td><td>6</td></tr><tr><td>⚠️ <i>warning</i></td><td>'todos.map' is missing in props validation</td><td><code>src/components/TodoList.jsx</code></td><td>6</td></tr><tr><td>⚠️ <i>warning</i></td><td>'onEdit' is missing in props validation</td><td><code>src/components/TodoList.jsx</code></td><td>13</td></tr></table>
78+
7779
</details>
7880
7981
@@ -83,7 +85,9 @@ ESLint rule **prop-types**, from _react_ plugin. [📖 Docs](https://github.com/
8385
8486
<details>
8587
<summary>🟥 <b>3 warnings</b> (score: 0)</summary>
88+
8689
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>'data' is already declared in the upper scope on line 5 column 10.</td><td><code>src/hooks/useTodos.js</code></td><td>11</td></tr><tr><td>⚠️ <i>warning</i></td><td>'data' is already declared in the upper scope on line 5 column 10.</td><td><code>src/hooks/useTodos.js</code></td><td>29</td></tr><tr><td>⚠️ <i>warning</i></td><td>'data' is already declared in the upper scope on line 5 column 10.</td><td><code>src/hooks/useTodos.js</code></td><td>41</td></tr></table>
90+
8791
</details>
8892
8993
@@ -93,7 +97,9 @@ ESLint rule **no-shadow**. [📖 Docs](https://eslint.org/docs/latest/rules/no-s
9397
9498
<details>
9599
<summary>🟥 <b>3 warnings</b> (score: 0)</summary>
100+
96101
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>Expected property shorthand.</td><td><code>src/hooks/useTodos.js</code></td><td>19</td></tr><tr><td>⚠️ <i>warning</i></td><td>Expected property shorthand.</td><td><code>src/hooks/useTodos.js</code></td><td>32</td></tr><tr><td>⚠️ <i>warning</i></td><td>Expected property shorthand.</td><td><code>src/hooks/useTodos.js</code></td><td>33</td></tr></table>
102+
97103
</details>
98104
99105
@@ -103,7 +109,9 @@ ESLint rule **object-shorthand**. [📖 Docs](https://eslint.org/docs/latest/rul
103109
104110
<details>
105111
<summary>🟥 <b>2 warnings</b> (score: 0)</summary>
112+
106113
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?</td><td><code>src/hooks/useTodos.js</code></td><td>17</td></tr><tr><td>⚠️ <i>warning</i></td><td>React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?</td><td><code>src/hooks/useTodos.js</code></td><td>40</td></tr></table>
114+
107115
</details>
108116
109117
@@ -113,7 +121,9 @@ ESLint rule **exhaustive-deps**, from _react-hooks_ plugin. [📖 Docs](https://
113121
114122
<details>
115123
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
124+
116125
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>Missing "key" prop for element in iterator</td><td><code>src/components/TodoList.jsx</code></td><td>7-28</td></tr></table>
126+
117127
</details>
118128
119129
@@ -123,7 +133,9 @@ ESLint rule **jsx-key**, from _react_ plugin. [📖 Docs](https://github.com/jsx
123133
124134
<details>
125135
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
136+
126137
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>'loading' is assigned a value but never used.</td><td><code>src/App.jsx</code></td><td>8</td></tr></table>
138+
127139
</details>
128140
129141
@@ -133,7 +145,9 @@ ESLint rule **no-unused-vars**. [📖 Docs](https://eslint.org/docs/latest/rules
133145
134146
<details>
135147
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
148+
136149
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>Arrow function has too many lines (71). Maximum allowed is 50.</td><td><code>src/hooks/useTodos.js</code></td><td>3-73</td></tr></table>
150+
137151
</details>
138152
139153
@@ -143,7 +157,9 @@ ESLint rule **max-lines-per-function**. [📖 Docs](https://eslint.org/docs/late
143157
144158
<details>
145159
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
160+
146161
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>'root' is never reassigned. Use 'const' instead.</td><td><code>src/index.jsx</code></td><td>5</td></tr></table>
162+
147163
</details>
148164
149165
@@ -153,7 +169,9 @@ ESLint rule **prefer-const**. [📖 Docs](https://eslint.org/docs/latest/rules/p
153169
154170
<details>
155171
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
172+
156173
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>Unexpected block statement surrounding arrow body; move the returned value immediately after the \`=>\`.</td><td><code>src/components/TodoFilter.jsx</code></td><td>3-25</td></tr></table>
174+
157175
</details>
158176
159177
@@ -163,7 +181,9 @@ ESLint rule **arrow-body-style**. [📖 Docs](https://eslint.org/docs/latest/rul
163181
164182
<details>
165183
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
184+
166185
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>Expected '===' and instead saw '=='.</td><td><code>src/hooks/useTodos.js</code></td><td>41</td></tr></table>
186+
167187
</details>
168188
169189
@@ -450,7 +470,9 @@ exports[`generateMdReport > should not contain category sections when categories
450470
451471
<details>
452472
<summary>🟥 <b>6 warnings</b> (score: 0)</summary>
473+
453474
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>'onCreate' is missing in props validation</td><td><code>src/components/CreateTodo.jsx</code></td><td>15</td></tr><tr><td>⚠️ <i>warning</i></td><td>'setQuery' is missing in props validation</td><td><code>src/components/TodoFilter.jsx</code></td><td>10</td></tr><tr><td>⚠️ <i>warning</i></td><td>'setHideComplete' is missing in props validation</td><td><code>src/components/TodoFilter.jsx</code></td><td>18</td></tr><tr><td>⚠️ <i>warning</i></td><td>'todos' is missing in props validation</td><td><code>src/components/TodoList.jsx</code></td><td>6</td></tr><tr><td>⚠️ <i>warning</i></td><td>'todos.map' is missing in props validation</td><td><code>src/components/TodoList.jsx</code></td><td>6</td></tr><tr><td>⚠️ <i>warning</i></td><td>'onEdit' is missing in props validation</td><td><code>src/components/TodoList.jsx</code></td><td>13</td></tr></table>
475+
454476
</details>
455477
456478
@@ -460,7 +482,9 @@ ESLint rule **prop-types**, from _react_ plugin. [📖 Docs](https://github.com/
460482
461483
<details>
462484
<summary>🟥 <b>3 warnings</b> (score: 0)</summary>
485+
463486
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>'data' is already declared in the upper scope on line 5 column 10.</td><td><code>src/hooks/useTodos.js</code></td><td>11</td></tr><tr><td>⚠️ <i>warning</i></td><td>'data' is already declared in the upper scope on line 5 column 10.</td><td><code>src/hooks/useTodos.js</code></td><td>29</td></tr><tr><td>⚠️ <i>warning</i></td><td>'data' is already declared in the upper scope on line 5 column 10.</td><td><code>src/hooks/useTodos.js</code></td><td>41</td></tr></table>
487+
464488
</details>
465489
466490
@@ -470,7 +494,9 @@ ESLint rule **no-shadow**. [📖 Docs](https://eslint.org/docs/latest/rules/no-s
470494
471495
<details>
472496
<summary>🟥 <b>3 warnings</b> (score: 0)</summary>
497+
473498
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>Expected property shorthand.</td><td><code>src/hooks/useTodos.js</code></td><td>19</td></tr><tr><td>⚠️ <i>warning</i></td><td>Expected property shorthand.</td><td><code>src/hooks/useTodos.js</code></td><td>32</td></tr><tr><td>⚠️ <i>warning</i></td><td>Expected property shorthand.</td><td><code>src/hooks/useTodos.js</code></td><td>33</td></tr></table>
499+
474500
</details>
475501
476502
@@ -480,7 +506,9 @@ ESLint rule **object-shorthand**. [📖 Docs](https://eslint.org/docs/latest/rul
480506
481507
<details>
482508
<summary>🟥 <b>2 warnings</b> (score: 0)</summary>
509+
483510
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?</td><td><code>src/hooks/useTodos.js</code></td><td>17</td></tr><tr><td>⚠️ <i>warning</i></td><td>React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?</td><td><code>src/hooks/useTodos.js</code></td><td>40</td></tr></table>
511+
484512
</details>
485513
486514
@@ -490,7 +518,9 @@ ESLint rule **exhaustive-deps**, from _react-hooks_ plugin. [📖 Docs](https://
490518
491519
<details>
492520
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
521+
493522
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>Missing "key" prop for element in iterator</td><td><code>src/components/TodoList.jsx</code></td><td>7-28</td></tr></table>
523+
494524
</details>
495525
496526
@@ -500,7 +530,9 @@ ESLint rule **jsx-key**, from _react_ plugin. [📖 Docs](https://github.com/jsx
500530
501531
<details>
502532
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
533+
503534
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>'loading' is assigned a value but never used.</td><td><code>src/App.jsx</code></td><td>8</td></tr></table>
535+
504536
</details>
505537
506538
@@ -510,7 +542,9 @@ ESLint rule **no-unused-vars**. [📖 Docs](https://eslint.org/docs/latest/rules
510542
511543
<details>
512544
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
545+
513546
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>Arrow function has too many lines (71). Maximum allowed is 50.</td><td><code>src/hooks/useTodos.js</code></td><td>3-73</td></tr></table>
547+
514548
</details>
515549
516550
@@ -520,7 +554,9 @@ ESLint rule **max-lines-per-function**. [📖 Docs](https://eslint.org/docs/late
520554
521555
<details>
522556
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
557+
523558
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>'root' is never reassigned. Use 'const' instead.</td><td><code>src/index.jsx</code></td><td>5</td></tr></table>
559+
524560
</details>
525561
526562
@@ -530,7 +566,9 @@ ESLint rule **prefer-const**. [📖 Docs](https://eslint.org/docs/latest/rules/p
530566
531567
<details>
532568
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
569+
533570
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>Unexpected block statement surrounding arrow body; move the returned value immediately after the \`=>\`.</td><td><code>src/components/TodoFilter.jsx</code></td><td>3-25</td></tr></table>
571+
534572
</details>
535573
536574
@@ -540,7 +578,9 @@ ESLint rule **arrow-body-style**. [📖 Docs](https://eslint.org/docs/latest/rul
540578
541579
<details>
542580
<summary>🟥 <b>1 warning</b> (score: 0)</summary>
581+
543582
<h4>Issues</h4><table><tr><th>Severity</th><th>Message</th><th>Source file</th><th>Line(s)</th></tr><tr><td>⚠️ <i>warning</i></td><td>Expected '===' and instead saw '=='.</td><td><code>src/hooks/useTodos.js</code></td><td>41</td></tr></table>
583+
544584
</details>
545585
546586
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`generateMdReportsDiff > should format Markdown comment summarizing changes between reports 1`] = `
4+
"# Code PushUp
5+
6+
🙌 Code PushUp report has **improved** – compared target commit \`0123456\` with source commit \`abcdef0\`.
7+
8+
## 🏷️ Categories
9+
10+
|🏷️ Category|⭐ Current score|⭐ Previous score|🗠 Score change|
11+
|:--|:--:|:--:|:--:|
12+
|Performance|🟢 **94**|🟢 92|<span style="color: green">▲ **+2**</span>|
13+
|Bug prevention|🟢 **95**|🟡 68|<span style="color: green">▲ **+27**</span>|
14+
|Code style|🟢 **100**|🟡 54|<span style="color: green">▲ **+46**</span>|
15+
16+
## 🎗️ Groups
17+
18+
<details>
19+
<summary>👍 <strong>2</strong> groups improved</summary>
20+
21+
|🔌 Plugin|🎗️ Group|⭐ Current score|⭐ Previous score|🗠 Score change|
22+
|:--|:--|:--:|:--:|:--:|
23+
|ESLint|Maximum lines limitation|🟢 **100**|🟡 50|<span style="color: green">▲ **+50**</span>|
24+
|Lighthouse|Performance|🟢 **94**|🟢 92|<span style="color: green">▲ **+2**</span>|
25+
26+
</details>
27+
28+
29+
## 🛡️ Audits
30+
31+
<details>
32+
<summary>👍 <strong>12</strong> audits improved</summary>
33+
34+
|🔌 Plugin|🛡️ Audit|📏 Current value|📏 Previous value|🗠 Value change|
35+
|:--|:--|:--:|:--:|:--:|
36+
|ESLint|Disallow unused variables|🟩 **passed**|🟥 1 warning|<span style="color: green">▼ **-100%**</span>|
37+
|ESLint|Require braces around arrow function bodies|🟩 **passed**|🟥 1 warning|<span style="color: green">▼ **-100%**</span>|
38+
|ESLint|Require the use of \`===\` and \`!==\`|🟩 **passed**|🟥 1 warning|<span style="color: green">▼ **-100%**</span>|
39+
|ESLint|Enforce a maximum number of lines of code in a function|🟩 **passed**|🟥 1 warning|<span style="color: green">▼ **-100%**</span>|
40+
|ESLint|Disallow variable declarations from shadowing variables declared in the outer scope|🟩 **passed**|🟥 3 warnings|<span style="color: green">▼ **-100%**</span>|
41+
|ESLint|Require or disallow method and property shorthand syntax for object literals|🟩 **passed**|🟥 3 warnings|<span style="color: green">▼ **-100%**</span>|
42+
|ESLint|Require \`const\` declarations for variables that are never reassigned after declared|🟩 **passed**|🟥 1 warning|<span style="color: green">▼ **-100%**</span>|
43+
|ESLint|Disallow missing \`key\` props in iterators/collection literals|🟩 **passed**|🟥 1 warning|<span style="color: green">▼ **-100%**</span>|
44+
|ESLint|verifies the list of dependencies for Hooks like useEffect and similar|🟩 **passed**|🟥 2 warnings|<span style="color: green">▼ **-100%**</span>|
45+
|Lighthouse|First Contentful Paint|🟨 **1.1 s**|🟨 1.2 s|<span style="color: green">▼ **-4%**</span>|
46+
|Lighthouse|Largest Contentful Paint|🟨 **1.4 s**|🟨 1.5 s|<span style="color: green">▼ **-8%**</span>|
47+
|Lighthouse|Speed Index|🟩 **1.1 s**|🟩 1.2 s|<span style="color: green">▼ **-4%**</span>|
48+
49+
Other 40 audits are unchanged.
50+
51+
</details>
52+
"
53+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { reportsDiffMock } from '@code-pushup/test-utils';
2+
import { generateMdReportsDiff } from './generate-md-reports-diff';
3+
4+
describe('generateMdReportsDiff', () => {
5+
it('should format Markdown comment summarizing changes between reports', () => {
6+
expect(generateMdReportsDiff(reportsDiffMock())).toMatchSnapshot();
7+
});
8+
});

0 commit comments

Comments
 (0)