@mui/internal-bundle-size-checker 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,559 @@
1
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
2
+ import { renderMarkdownReport } from './renderMarkdownReport.js';
3
+ import * as fetchSnapshotModule from './fetchSnapshot.js';
4
+
5
+ // Mock the fetchSnapshot module
6
+ vi.mock('./fetchSnapshot.js');
7
+
8
+ describe('renderMarkdownReport', () => {
9
+ const mockFetchSnapshot = vi.mocked(fetchSnapshotModule.fetchSnapshot);
10
+ const mockFetchSnapshotWithFallback = vi.mocked(fetchSnapshotModule.fetchSnapshotWithFallback);
11
+
12
+ /** @type {PrInfo} */
13
+ const mockPrInfo = {
14
+ number: 42,
15
+ base: {
16
+ ref: 'master',
17
+ sha: 'abc123',
18
+ repo: { full_name: 'mui/material-ui' },
19
+ },
20
+ head: {
21
+ ref: 'feature-branch',
22
+ sha: 'def456',
23
+ },
24
+ };
25
+
26
+ beforeEach(() => {
27
+ mockFetchSnapshot.mockClear();
28
+ mockFetchSnapshotWithFallback.mockClear();
29
+ });
30
+
31
+ it('should generate markdown report with size increases', async () => {
32
+ const baseSnapshot = {
33
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
34
+ '@mui/material/TextField/index.js': { parsed: 22000, gzip: 6500 },
35
+ };
36
+
37
+ const prSnapshot = {
38
+ '@mui/material/Button/index.js': { parsed: 15400, gzip: 4600 }, // +400/+100
39
+ '@mui/material/TextField/index.js': { parsed: 22000, gzip: 6500 }, // no change
40
+ };
41
+
42
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
43
+ snapshot: baseSnapshot,
44
+ actualCommit: 'abc123',
45
+ });
46
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
47
+
48
+ const result = await renderMarkdownReport(mockPrInfo);
49
+
50
+ expect(result).toMatchInlineSnapshot(`
51
+ "**Total Size Change:** 🔺+400B<sup>(+1.08%)</sup> - **Total Gzip Change:** 🔺+100B<sup>(+0.91%)</sup>
52
+ Files: 2 total (0 added, 0 removed, 1 changed)
53
+
54
+ <details>
55
+ <summary>Show details for 2 more bundles</summary>
56
+
57
+ **@mui/material/Button/index.js**&emsp;**parsed:** 🔺+400B<sup>(+2.67%)</sup> **gzip:** 🔺+100B<sup>(+2.22%)</sup>
58
+ **@mui/material/TextField/index.js**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:** 0B<sup>(0.00%)</sup>
59
+
60
+ </details>
61
+
62
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456)"
63
+ `);
64
+ });
65
+
66
+ it('should handle new files added in PR', async () => {
67
+ const baseSnapshot = {
68
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
69
+ };
70
+
71
+ const prSnapshot = {
72
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
73
+ '@mui/material/Chip/index.js': { parsed: 3500, gzip: 1200 }, // new file
74
+ };
75
+
76
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
77
+ snapshot: baseSnapshot,
78
+ actualCommit: 'abc123',
79
+ });
80
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
81
+
82
+ const result = await renderMarkdownReport(mockPrInfo);
83
+
84
+ expect(result).toMatchInlineSnapshot(`
85
+ "**Total Size Change:** 🔺+3.5KB<sup>(+23.33%)</sup> - **Total Gzip Change:** 🔺+1.2KB<sup>(+26.67%)</sup>
86
+ Files: 2 total (1 added, 0 removed, 0 changed)
87
+
88
+ <details>
89
+ <summary>Show details for 2 more bundles</summary>
90
+
91
+ **@mui/material/Chip/index.js**&emsp;**parsed:** 🔺+3.5KB<sup>(new)</sup> **gzip:** 🔺+1.2KB<sup>(new)</sup>
92
+ **@mui/material/Button/index.js**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:** 0B<sup>(0.00%)</sup>
93
+
94
+ </details>
95
+
96
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456)"
97
+ `);
98
+ });
99
+
100
+ it('should handle missing base snapshot when GitHub API also fails', async () => {
101
+ const prSnapshot = {
102
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
103
+ };
104
+
105
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({ snapshot: null, actualCommit: null });
106
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
107
+
108
+ const result = await renderMarkdownReport(mockPrInfo);
109
+
110
+ expect(result).toContain(
111
+ 'No bundle size snapshot found for base commit abc123 or any of its 3 parent commits.',
112
+ );
113
+ });
114
+
115
+ it('should handle size decreases', async () => {
116
+ const baseSnapshot = {
117
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
118
+ };
119
+
120
+ const prSnapshot = {
121
+ '@mui/material/Button/index.js': { parsed: 14500, gzip: 4300 }, // -500/-200
122
+ };
123
+
124
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
125
+ snapshot: baseSnapshot,
126
+ actualCommit: 'abc123',
127
+ });
128
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
129
+
130
+ const result = await renderMarkdownReport(mockPrInfo);
131
+
132
+ expect(result).toMatchInlineSnapshot(`
133
+ "**Total Size Change:** ▼-500B<sup>(-3.33%)</sup> - **Total Gzip Change:** ▼-200B<sup>(-4.44%)</sup>
134
+ Files: 1 total (0 added, 0 removed, 1 changed)
135
+
136
+ <details>
137
+ <summary>Show details for 1 more bundle</summary>
138
+
139
+ **@mui/material/Button/index.js**&emsp;**parsed:** ▼-500B<sup>(-3.33%)</sup> **gzip:** ▼-200B<sup>(-4.44%)</sup>
140
+
141
+ </details>
142
+
143
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456)"
144
+ `);
145
+ });
146
+
147
+ it('should handle removed files', async () => {
148
+ const baseSnapshot = {
149
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
150
+ '@mui/material/TextField/index.js': { parsed: 22000, gzip: 6500 },
151
+ };
152
+
153
+ const prSnapshot = {
154
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
155
+ // TextField removed
156
+ };
157
+
158
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
159
+ snapshot: baseSnapshot,
160
+ actualCommit: 'abc123',
161
+ });
162
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
163
+
164
+ const result = await renderMarkdownReport(mockPrInfo);
165
+
166
+ expect(result).toMatchInlineSnapshot(`
167
+ "**Total Size Change:** ▼-22KB<sup>(-59.46%)</sup> - **Total Gzip Change:** ▼-6.5KB<sup>(-59.09%)</sup>
168
+ Files: 2 total (0 added, 1 removed, 0 changed)
169
+
170
+ <details>
171
+ <summary>Show details for 2 more bundles</summary>
172
+
173
+ **@mui/material/TextField/index.js**&emsp;**parsed:** ▼-22KB<sup>(removed)</sup> **gzip:** ▼-6.5KB<sup>(removed)</sup>
174
+ **@mui/material/Button/index.js**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:** 0B<sup>(0.00%)</sup>
175
+
176
+ </details>
177
+
178
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456)"
179
+ `);
180
+ });
181
+
182
+ it('should show collapsible section for many small changes', async () => {
183
+ /** @type {import('./sizeDiff.js').SizeSnapshot} */
184
+ const baseSnapshot = {};
185
+ /** @type {import('./sizeDiff.js').SizeSnapshot} */
186
+ const prSnapshot = {};
187
+
188
+ // Create many small changes (under threshold)
189
+ for (let i = 1; i <= 15; i += 1) {
190
+ const filename = `@mui/icons-material/Icon${i}.js`;
191
+ baseSnapshot[filename] = { parsed: 1000, gzip: 300 };
192
+ prSnapshot[filename] = { parsed: 1050, gzip: 310 }; // +50/+10 (under threshold)
193
+ }
194
+
195
+ // Add one significant change
196
+ baseSnapshot['@mui/material/Button/index.js'] = { parsed: 15000, gzip: 4500 };
197
+ prSnapshot['@mui/material/Button/index.js'] = { parsed: 15400, gzip: 4600 }; // +400/+100 (over threshold)
198
+
199
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
200
+ snapshot: baseSnapshot,
201
+ actualCommit: 'abc123',
202
+ });
203
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
204
+
205
+ const result = await renderMarkdownReport(mockPrInfo);
206
+
207
+ expect(result).toMatchInlineSnapshot(`
208
+ "**Total Size Change:** 🔺+1.15KB<sup>(+3.83%)</sup> - **Total Gzip Change:** 🔺+250B<sup>(+2.78%)</sup>
209
+ Files: 16 total (0 added, 0 removed, 16 changed)
210
+
211
+ <details>
212
+ <summary>Show details for 16 more bundles</summary>
213
+
214
+ **@mui/material/Button/index.js**&emsp;**parsed:** 🔺+400B<sup>(+2.67%)</sup> **gzip:** 🔺+100B<sup>(+2.22%)</sup>
215
+ **@mui/icons-material/Icon1.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
216
+ **@mui/icons-material/Icon10.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
217
+ **@mui/icons-material/Icon11.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
218
+ **@mui/icons-material/Icon12.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
219
+ **@mui/icons-material/Icon13.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
220
+ **@mui/icons-material/Icon14.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
221
+ **@mui/icons-material/Icon15.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
222
+ **@mui/icons-material/Icon2.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
223
+ **@mui/icons-material/Icon3.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
224
+ **@mui/icons-material/Icon4.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
225
+ **@mui/icons-material/Icon5.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
226
+ **@mui/icons-material/Icon6.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
227
+ **@mui/icons-material/Icon7.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
228
+ **@mui/icons-material/Icon8.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
229
+ **@mui/icons-material/Icon9.js**&emsp;**parsed:** 🔺+50B<sup>(+5.00%)</sup> **gzip:** 🔺+10B<sup>(+3.33%)</sup>
230
+
231
+ </details>
232
+
233
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456)"
234
+ `);
235
+ });
236
+
237
+ it('should include CircleCI build number in details URL', async () => {
238
+ const baseSnapshot = {
239
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
240
+ };
241
+
242
+ const prSnapshot = {
243
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
244
+ };
245
+
246
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
247
+ snapshot: baseSnapshot,
248
+ actualCommit: 'abc123',
249
+ });
250
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
251
+
252
+ const result = await renderMarkdownReport(mockPrInfo, '12345');
253
+
254
+ expect(result).toContain('circleCIBuildNumber=12345');
255
+ expect(result).toMatchInlineSnapshot(`
256
+ "**Total Size Change:** 0B<sup>(0.00%)</sup> - **Total Gzip Change:** 0B<sup>(0.00%)</sup>
257
+ Files: 1 total (0 added, 0 removed, 0 changed)
258
+
259
+ <details>
260
+ <summary>Show details for 1 more bundle</summary>
261
+
262
+ **@mui/material/Button/index.js**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:** 0B<sup>(0.00%)</sup>
263
+
264
+ </details>
265
+
266
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456&circleCIBuildNumber=12345)"
267
+ `);
268
+ });
269
+
270
+ it('should handle no changes', async () => {
271
+ const baseSnapshot = {
272
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
273
+ };
274
+
275
+ const prSnapshot = {
276
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
277
+ };
278
+
279
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
280
+ snapshot: baseSnapshot,
281
+ actualCommit: 'abc123',
282
+ });
283
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
284
+
285
+ const result = await renderMarkdownReport(mockPrInfo);
286
+
287
+ expect(result).toMatchInlineSnapshot(`
288
+ "**Total Size Change:** 0B<sup>(0.00%)</sup> - **Total Gzip Change:** 0B<sup>(0.00%)</sup>
289
+ Files: 1 total (0 added, 0 removed, 0 changed)
290
+
291
+ <details>
292
+ <summary>Show details for 1 more bundle</summary>
293
+
294
+ **@mui/material/Button/index.js**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:** 0B<sup>(0.00%)</sup>
295
+
296
+ </details>
297
+
298
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456)"
299
+ `);
300
+ });
301
+
302
+ it('should handle track option with tracked bundles shown prominently', async () => {
303
+ const baseSnapshot = {
304
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
305
+ '@mui/material/TextField/index.js': { parsed: 22000, gzip: 6500 },
306
+ '@mui/icons-material/Add.js': { parsed: 1000, gzip: 300 },
307
+ };
308
+
309
+ const prSnapshot = {
310
+ '@mui/material/Button/index.js': { parsed: 15400, gzip: 4600 }, // +400/+100
311
+ '@mui/material/TextField/index.js': { parsed: 22200, gzip: 6600 }, // +200/+100
312
+ '@mui/icons-material/Add.js': { parsed: 1100, gzip: 350 }, // +100/+50
313
+ };
314
+
315
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
316
+ snapshot: baseSnapshot,
317
+ actualCommit: 'abc123',
318
+ });
319
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
320
+
321
+ const result = await renderMarkdownReport(mockPrInfo, undefined, {
322
+ track: ['@mui/material/Button/index.js', '@mui/material/TextField/index.js'],
323
+ });
324
+
325
+ expect(result).toMatchInlineSnapshot(`
326
+ "**@mui/material/Button/index.js**&emsp;**parsed:** 🔺+400B<sup>(+2.67%)</sup> **gzip:** 🔺+100B<sup>(+2.22%)</sup>
327
+ **@mui/material/TextField/index.js**&emsp;**parsed:** 🔺+200B<sup>(+0.91%)</sup> **gzip:** 🔺+100B<sup>(+1.54%)</sup><details>
328
+ <summary>Show details for 1 more bundle</summary>
329
+
330
+ **@mui/icons-material/Add.js**&emsp;**parsed:** 🔺+100B<sup>(+10.00%)</sup> **gzip:** 🔺+50B<sup>(+16.67%)</sup>
331
+
332
+ </details>
333
+
334
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456)"
335
+ `);
336
+ });
337
+
338
+ it('should calculate totals only for tracked bundles', async () => {
339
+ const baseSnapshot = {
340
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
341
+ '@mui/material/TextField/index.js': { parsed: 22000, gzip: 6500 },
342
+ '@mui/icons-material/Add.js': { parsed: 1000, gzip: 300 },
343
+ };
344
+
345
+ const prSnapshot = {
346
+ '@mui/material/Button/index.js': { parsed: 15500, gzip: 4650 }, // +500/+150
347
+ '@mui/material/TextField/index.js': { parsed: 22300, gzip: 6650 }, // +300/+150
348
+ '@mui/icons-material/Add.js': { parsed: 2000, gzip: 600 }, // +1000/+300 (not tracked)
349
+ };
350
+
351
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
352
+ snapshot: baseSnapshot,
353
+ actualCommit: 'abc123',
354
+ });
355
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
356
+
357
+ const result = await renderMarkdownReport(mockPrInfo, undefined, {
358
+ track: ['@mui/material/Button/index.js', '@mui/material/TextField/index.js'],
359
+ });
360
+
361
+ expect(result).toMatchInlineSnapshot(`
362
+ "**@mui/material/Button/index.js**&emsp;**parsed:** 🔺+500B<sup>(+3.33%)</sup> **gzip:** 🔺+150B<sup>(+3.33%)</sup>
363
+ **@mui/material/TextField/index.js**&emsp;**parsed:** 🔺+300B<sup>(+1.36%)</sup> **gzip:** 🔺+150B<sup>(+2.31%)</sup><details>
364
+ <summary>Show details for 1 more bundle</summary>
365
+
366
+ **@mui/icons-material/Add.js**&emsp;**parsed:** 🔺+1KB<sup>(+100.00%)</sup> **gzip:** 🔺+300B<sup>(+100.00%)</sup>
367
+
368
+ </details>
369
+
370
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456)"
371
+ `);
372
+ });
373
+
374
+ it('should put non-tracked bundles in details when track is specified', async () => {
375
+ const baseSnapshot = {
376
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
377
+ '@mui/icons-material/Add.js': { parsed: 1000, gzip: 300 },
378
+ '@mui/icons-material/Delete.js': { parsed: 1200, gzip: 350 },
379
+ };
380
+
381
+ const prSnapshot = {
382
+ '@mui/material/Button/index.js': { parsed: 15400, gzip: 4600 }, // +400/+100 (tracked)
383
+ '@mui/icons-material/Add.js': { parsed: 1100, gzip: 350 }, // +100/+50 (not tracked)
384
+ '@mui/icons-material/Delete.js': { parsed: 1300, gzip: 400 }, // +100/+50 (not tracked)
385
+ };
386
+
387
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
388
+ snapshot: baseSnapshot,
389
+ actualCommit: 'abc123',
390
+ });
391
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
392
+
393
+ const result = await renderMarkdownReport(mockPrInfo, undefined, {
394
+ track: ['@mui/material/Button/index.js'],
395
+ });
396
+
397
+ expect(result).toMatchInlineSnapshot(`
398
+ "**@mui/material/Button/index.js**&emsp;**parsed:** 🔺+400B<sup>(+2.67%)</sup> **gzip:** 🔺+100B<sup>(+2.22%)</sup><details>
399
+ <summary>Show details for 2 more bundles</summary>
400
+
401
+ **@mui/icons-material/Add.js**&emsp;**parsed:** 🔺+100B<sup>(+10.00%)</sup> **gzip:** 🔺+50B<sup>(+16.67%)</sup>
402
+ **@mui/icons-material/Delete.js**&emsp;**parsed:** 🔺+100B<sup>(+8.33%)</sup> **gzip:** 🔺+50B<sup>(+14.29%)</sup>
403
+
404
+ </details>
405
+
406
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456)"
407
+ `);
408
+ });
409
+
410
+ it('should show tracked bundles prominently even when they have no changes', async () => {
411
+ const baseSnapshot = {
412
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
413
+ '@mui/material/TextField/index.js': { parsed: 22000, gzip: 6500 },
414
+ '@mui/material/Icon/index.js': { parsed: 8000, gzip: 2500 },
415
+ };
416
+
417
+ const prSnapshot = {
418
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 }, // tracked, no change
419
+ '@mui/material/TextField/index.js': { parsed: 22000, gzip: 6500 }, // tracked, no change
420
+ '@mui/material/Icon/index.js': { parsed: 8100, gzip: 2550 }, // untracked, has change
421
+ };
422
+
423
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
424
+ snapshot: baseSnapshot,
425
+ actualCommit: 'abc123',
426
+ });
427
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
428
+
429
+ const result = await renderMarkdownReport(mockPrInfo, undefined, {
430
+ track: ['@mui/material/Button/index.js', '@mui/material/TextField/index.js'],
431
+ });
432
+
433
+ expect(result).toMatchInlineSnapshot(`
434
+ "**@mui/material/Button/index.js**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:** 0B<sup>(0.00%)</sup>
435
+ **@mui/material/TextField/index.js**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:** 0B<sup>(0.00%)</sup><details>
436
+ <summary>Show details for 1 more bundle</summary>
437
+
438
+ **@mui/material/Icon/index.js**&emsp;**parsed:** 🔺+100B<sup>(+1.25%)</sup> **gzip:** 🔺+50B<sup>(+2.00%)</sup>
439
+
440
+ </details>
441
+
442
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456)"
443
+ `);
444
+ });
445
+
446
+ it('should show message when tracking is enabled but no untracked bundles have changes', async () => {
447
+ const baseSnapshot = {
448
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
449
+ '@mui/material/TextField/index.js': { parsed: 22000, gzip: 6500 },
450
+ };
451
+
452
+ const prSnapshot = {
453
+ '@mui/material/Button/index.js': { parsed: 15400, gzip: 4600 }, // tracked, has change
454
+ '@mui/material/TextField/index.js': { parsed: 22000, gzip: 6500 }, // untracked, no change
455
+ };
456
+
457
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
458
+ snapshot: baseSnapshot,
459
+ actualCommit: 'abc123',
460
+ });
461
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
462
+
463
+ const result = await renderMarkdownReport(mockPrInfo, undefined, {
464
+ track: ['@mui/material/Button/index.js'],
465
+ });
466
+
467
+ expect(result).toMatchInlineSnapshot(`
468
+ "**@mui/material/Button/index.js**&emsp;**parsed:** 🔺+400B<sup>(+2.67%)</sup> **gzip:** 🔺+100B<sup>(+2.22%)</sup><details>
469
+ <summary>Show details for 1 more bundle</summary>
470
+
471
+ **@mui/material/TextField/index.js**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:** 0B<sup>(0.00%)</sup>
472
+
473
+ </details>
474
+
475
+ [Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/material-ui/diff?prNumber=42&baseRef=master&baseCommit=abc123&headCommit=def456)"
476
+ `);
477
+ });
478
+
479
+ it('should throw error when tracked bundle is missing from head snapshot', async () => {
480
+ const baseSnapshot = {
481
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
482
+ };
483
+
484
+ const prSnapshot = {
485
+ '@mui/material/Button/index.js': { parsed: 15400, gzip: 4600 },
486
+ };
487
+
488
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
489
+ snapshot: baseSnapshot,
490
+ actualCommit: 'abc123',
491
+ });
492
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
493
+
494
+ await expect(
495
+ renderMarkdownReport(mockPrInfo, undefined, {
496
+ track: ['@mui/material/Button/index.js', '@mui/material/NonExistent/index.js'],
497
+ }),
498
+ ).rejects.toThrow(
499
+ 'Tracked bundle not found in head snapshot: @mui/material/NonExistent/index.js',
500
+ );
501
+ });
502
+
503
+ it('should fallback to parent commit when base snapshot is missing', async () => {
504
+ const parentSnapshot = {
505
+ '@mui/material/Button/index.js': { parsed: 14500, gzip: 4300 },
506
+ };
507
+
508
+ const prSnapshot = {
509
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
510
+ };
511
+
512
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
513
+ snapshot: parentSnapshot,
514
+ actualCommit: 'parent1',
515
+ });
516
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
517
+
518
+ const result = await renderMarkdownReport(mockPrInfo);
519
+
520
+ expect(result).toContain('Using snapshot from parent commit parent1 (fallback from abc123)');
521
+ expect(result).toContain('baseCommit=parent1');
522
+ });
523
+
524
+ it('should show no snapshot message when fallback fails', async () => {
525
+ const prSnapshot = {
526
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
527
+ };
528
+
529
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({ snapshot: null, actualCommit: null });
530
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
531
+
532
+ const result = await renderMarkdownReport(mockPrInfo);
533
+
534
+ expect(result).toContain(
535
+ 'No bundle size snapshot found for base commit abc123 or any of its 3 parent commits.',
536
+ );
537
+ });
538
+
539
+ it('should respect custom fallbackDepth option', async () => {
540
+ const parentSnapshot = {
541
+ '@mui/material/Button/index.js': { parsed: 14500, gzip: 4300 },
542
+ };
543
+
544
+ const prSnapshot = {
545
+ '@mui/material/Button/index.js': { parsed: 15000, gzip: 4500 },
546
+ };
547
+
548
+ mockFetchSnapshotWithFallback.mockResolvedValueOnce({
549
+ snapshot: parentSnapshot,
550
+ actualCommit: 'parent1',
551
+ });
552
+ mockFetchSnapshot.mockResolvedValueOnce(prSnapshot);
553
+
554
+ const result = await renderMarkdownReport(mockPrInfo, undefined, { fallbackDepth: 1 });
555
+
556
+ expect(result).toContain('Using snapshot from parent commit parent1 (fallback from abc123)');
557
+ expect(mockFetchSnapshotWithFallback).toHaveBeenCalledWith('mui/material-ui', 'abc123', 1);
558
+ });
559
+ });
package/src/types.d.ts CHANGED
@@ -18,15 +18,6 @@ interface StatsChunkGroup {
18
18
  assets: Array<{ name: string; size: number }>;
19
19
  }
20
20
 
21
- interface WebpackStats {
22
- hasErrors(): boolean;
23
- toJson(options: any): {
24
- assets?: StatsAsset[];
25
- entrypoints?: Record<string, StatsChunkGroup>;
26
- errors?: any[];
27
- };
28
- }
29
-
30
21
  // Upload configuration with optional properties
31
22
  interface UploadConfig {
32
23
  repo?: string; // The repository name (e.g., "mui/material-ui")
@@ -79,6 +70,7 @@ interface CommandLineArgs {
79
70
  verbose?: boolean;
80
71
  filter?: string[];
81
72
  concurrency?: number;
73
+ vite?: boolean;
82
74
  }
83
75
 
84
76
  // Diff command argument types
@@ -110,52 +102,3 @@ interface PrInfo {
110
102
  sha: string;
111
103
  };
112
104
  }
113
-
114
- // Check the PR message for available colors: https://github.com/mui/mui-public/pull/332
115
- type KatexColor =
116
- | 'blueviolet'
117
- | 'aquamarine'
118
- | 'brown'
119
- | 'cadetblue'
120
- | 'darkorchid'
121
- | 'chocolate'
122
- | 'cornflowerblue'
123
- | 'cyan'
124
- | 'darkgray'
125
- | 'darkslateblue'
126
- | 'forestgreen'
127
- | 'fuchsia'
128
- | 'gold'
129
- | 'goldenrod'
130
- | 'gray'
131
- | 'green'
132
- | 'greenyellow'
133
- | 'lavender'
134
- | 'lightgray'
135
- | 'limegreen'
136
- | 'magenta'
137
- | 'maroon'
138
- | 'mediumpurple'
139
- | 'midnightblue'
140
- | 'navajowhite'
141
- | 'olive'
142
- | 'orange'
143
- | 'orangered'
144
- | 'orchid'
145
- | 'pink'
146
- | 'plum'
147
- | 'purple'
148
- | 'red'
149
- | 'royalblue'
150
- | 'salmon'
151
- | 'seagreen'
152
- | 'silver'
153
- | 'skyblue'
154
- | 'springgreen'
155
- | 'tan'
156
- | 'thistle'
157
- | 'tomato'
158
- | 'turquoise'
159
- | 'violet'
160
- | 'yellow'
161
- | 'yellowgreen';