lighthouse 10.0.0 → 10.0.1

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.
Files changed (69) hide show
  1. package/changelog-pre10.md +6085 -0
  2. package/core/audits/dobetterweb/doctype.d.ts +3 -2
  3. package/core/audits/dobetterweb/doctype.js +15 -4
  4. package/core/audits/dobetterweb/paste-preventing-inputs.js +2 -2
  5. package/core/gather/driver/network-monitor.js +1 -3
  6. package/core/gather/gatherers/bf-cache-failures.js +7 -8
  7. package/package.json +2 -2
  8. package/shared/localization/locales/en-US.json +1 -1
  9. package/shared/localization/locales/en-XL.json +1 -1
  10. package/.codecov.yml +0 -22
  11. package/.cz-config.js +0 -24
  12. package/.gitattributes +0 -5
  13. package/.mailmap +0 -6
  14. package/CODE_OF_CONDUCT.md +0 -93
  15. package/eslint-local-rules.cjs +0 -106
  16. package/flow-report/test/app-test.d.ts +0 -7
  17. package/flow-report/test/app-test.tsx +0 -52
  18. package/flow-report/test/common-test.d.ts +0 -7
  19. package/flow-report/test/common-test.tsx +0 -103
  20. package/flow-report/test/flow-report-pptr-test.d.ts +0 -7
  21. package/flow-report/test/flow-report-pptr-test.ts +0 -59
  22. package/flow-report/test/header-test.d.ts +0 -7
  23. package/flow-report/test/header-test.tsx +0 -55
  24. package/flow-report/test/run-flow-report-tests.sh +0 -20
  25. package/flow-report/test/sample-flow.d.ts +0 -7
  26. package/flow-report/test/sample-flow.ts +0 -17
  27. package/flow-report/test/setup/env-setup.d.ts +0 -11
  28. package/flow-report/test/setup/env-setup.ts +0 -55
  29. package/flow-report/test/sidebar/flow-test.d.ts +0 -7
  30. package/flow-report/test/sidebar/flow-test.tsx +0 -65
  31. package/flow-report/test/sidebar/sidebar-test.d.ts +0 -7
  32. package/flow-report/test/sidebar/sidebar-test.tsx +0 -116
  33. package/flow-report/test/summary/category-test.d.ts +0 -7
  34. package/flow-report/test/summary/category-test.tsx +0 -206
  35. package/flow-report/test/summary/summary-test.d.ts +0 -7
  36. package/flow-report/test/summary/summary-test.tsx +0 -124
  37. package/flow-report/test/topbar-test.d.ts +0 -7
  38. package/flow-report/test/topbar-test.tsx +0 -84
  39. package/flow-report/test/util-test.d.ts +0 -7
  40. package/flow-report/test/util-test.tsx +0 -133
  41. package/flow-report/test/wrappers/category-score-test.d.ts +0 -7
  42. package/flow-report/test/wrappers/category-score-test.tsx +0 -84
  43. package/flow-report/test/wrappers/markdown-test.d.ts +0 -7
  44. package/flow-report/test/wrappers/markdown-test.tsx +0 -17
  45. package/report/test/clients/bundle-test.js +0 -65
  46. package/report/test/generator/file-namer-test.js +0 -27
  47. package/report/test/generator/report-generator-test.js +0 -160
  48. package/report/test/renderer/__snapshots__/report-renderer-axe-test.js.snap +0 -15
  49. package/report/test/renderer/category-renderer-test.js +0 -575
  50. package/report/test/renderer/components-test.js +0 -124
  51. package/report/test/renderer/crc-details-renderer-test.js +0 -116
  52. package/report/test/renderer/details-renderer-test.js +0 -882
  53. package/report/test/renderer/dom-test.js +0 -257
  54. package/report/test/renderer/element-screenshot-renderer-test.js +0 -183
  55. package/report/test/renderer/i18n-formatter-test.js +0 -179
  56. package/report/test/renderer/performance-category-renderer-test.js +0 -417
  57. package/report/test/renderer/pwa-category-renderer-test.js +0 -301
  58. package/report/test/renderer/report-renderer-axe-test.js +0 -85
  59. package/report/test/renderer/report-renderer-test.js +0 -268
  60. package/report/test/renderer/report-ui-features-test.js +0 -773
  61. package/report/test/renderer/report-utils-test.js +0 -219
  62. package/report/test/renderer/snippet-renderer-test.js +0 -291
  63. package/report/test/renderer/text-encoding-test.js +0 -43
  64. package/report/test-assets/faux-psi.d.ts +0 -34
  65. package/shared/test/localization/format-test.js +0 -430
  66. package/shared/test/localization/locales-test.js +0 -45
  67. package/shared/test/localization/swap-locale-test.js +0 -127
  68. package/shared/test/util-test.js +0 -214
  69. package/vercel.json +0 -6
@@ -1,773 +0,0 @@
1
- /**
2
- * @license Copyright 2017 The Lighthouse Authors. All Rights Reserved.
3
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
- */
6
-
7
- import assert from 'assert/strict';
8
-
9
- import jsdom from 'jsdom';
10
- import jestMock from 'jest-mock';
11
-
12
- import {reportAssets} from '../../generator/report-assets.js';
13
- import {ReportUtils, UIStrings} from '../../renderer/report-utils.js';
14
- import {DOM} from '../../renderer/dom.js';
15
- import {DetailsRenderer} from '../../renderer/details-renderer.js';
16
- import {ReportUIFeatures} from '../../renderer/report-ui-features.js';
17
- import {CategoryRenderer} from '../../renderer/category-renderer.js';
18
- import {ReportRenderer} from '../../renderer/report-renderer.js';
19
- import {readJson} from '../../../core/test/test-utils.js';
20
-
21
- const sampleResultsOrig = readJson('../../../core/test/results/sample_v2.json', import.meta);
22
-
23
- describe('ReportUIFeatures', () => {
24
- let sampleResults;
25
- let dom;
26
-
27
- /**
28
- * @param {LH.JSON} lhr
29
- * @param {LH.Renderer.Options=} opts
30
- * @return {HTMLElement}
31
- */
32
- function render(lhr, opts) {
33
- const detailsRenderer = new DetailsRenderer(dom);
34
- const categoryRenderer = new CategoryRenderer(dom, detailsRenderer);
35
- const renderer = new ReportRenderer(dom, categoryRenderer);
36
- const reportUIFeatures = new ReportUIFeatures(dom, opts);
37
- const container = dom.find('body', dom.document());
38
- renderer.renderReport(lhr, container, opts);
39
- reportUIFeatures.initFeatures(lhr);
40
- return container;
41
- }
42
-
43
- before(() => {
44
- global.console.warn = jestMock.fn();
45
-
46
- // Stub out matchMedia for Node.
47
- global.matchMedia = function() {
48
- return {
49
- addListener: function() {},
50
- };
51
- };
52
-
53
- const document = new jsdom.JSDOM(reportAssets.REPORT_TEMPLATE);
54
- global.self = document.window;
55
- global.self.matchMedia = function() {
56
- return {
57
- addListener: function() {},
58
- };
59
- };
60
-
61
- global.HTMLElement = document.window.HTMLElement;
62
- global.HTMLInputElement = document.window.HTMLInputElement;
63
- global.CustomEvent = document.window.CustomEvent;
64
-
65
- global.window = document.window;
66
- global.window.requestAnimationFrame = fn => fn();
67
- global.window.getComputedStyle = function() {
68
- return {
69
- marginTop: '10px',
70
- height: '10px',
71
- };
72
- };
73
- global.window.ResizeObserver = class ResizeObserver {
74
- observe() { }
75
- unobserve() { }
76
- };
77
-
78
- dom = new DOM(document.window.document);
79
- sampleResults = ReportUtils.prepareReportResult(sampleResultsOrig);
80
- render(sampleResults);
81
- });
82
-
83
- after(() => {
84
- global.window = undefined;
85
- global.HTMLElement = undefined;
86
- global.HTMLInputElement = undefined;
87
- global.CustomEvent = undefined;
88
- });
89
-
90
- describe('initFeatures', () => {
91
- it('should init a report', () => {
92
- const container = render(sampleResults);
93
- assert.equal(dom.findAll('.lh-category', container).length, 5);
94
- });
95
-
96
- it('should init a report with a single category', () => {
97
- const lhr = JSON.parse(JSON.stringify(sampleResults));
98
- lhr.categories = {
99
- performance: lhr.categories.performance,
100
- };
101
- const container = render(lhr);
102
- assert.equal(dom.findAll('.lh-category', container).length, 1);
103
- });
104
-
105
- describe('third-party filtering', () => {
106
- let lhrJson;
107
-
108
- before(() => {
109
- const lhr = JSON.parse(JSON.stringify(sampleResults));
110
- lhr.requestedUrl = lhr.finalDisplayedUrl = 'http://www.example.com';
111
-
112
- const webpAuditItemTemplate = {
113
- ...sampleResults.audits['modern-image-formats'].details.items[0],
114
- wastedBytes: 8.8 * 1024,
115
- entity: undefined, // Remove entity classification from previous result.
116
- };
117
- const renderBlockingAuditItemTemplate = {
118
- ...sampleResults.audits['render-blocking-resources'].details.items[0],
119
- entity: undefined, // Remove entity classification from previous result.
120
- };
121
- const textCompressionAuditItemTemplate = {
122
- ...sampleResults.audits['uses-text-compression'].details.items[0],
123
- entity: undefined, // Remove entity classification from previous result.
124
- };
125
- const bootupTimeAuditItemTemplate = {
126
- ...sampleResults.audits['bootup-time'].details.items[0],
127
- entity: undefined, // Remove entity classification from previous result.
128
- };
129
-
130
- // Interleave first/third party URLs to test restoring order.
131
- lhr.audits['modern-image-formats'].details.items = [
132
- {
133
- ...webpAuditItemTemplate,
134
- url: 'http://www.cdn.com/img1.jpg', // Third party, will be filtered.
135
- },
136
- {
137
- ...webpAuditItemTemplate,
138
- url: 'http://www.example.com/img2.jpg', // First party, not filtered.
139
- },
140
- {
141
- ...webpAuditItemTemplate,
142
- url: 'http://www.notexample.com/img3.jpg', // Third party, will be filtered.
143
- },
144
- ];
145
-
146
- // Test sub-item rows.
147
- lhr.audits['unused-javascript'].details.items = [
148
- {
149
- ...webpAuditItemTemplate,
150
- url: 'http://www.cdn.com/script1.js', // Third party, will be filtered.
151
- subItems: {
152
- type: 'subitems',
153
- items: [
154
- {source: '1', sourceBytes: 1, sourceWastedBytes: 1},
155
- {source: '2', sourceBytes: 2, sourceWastedBytes: 2},
156
- ],
157
- },
158
- },
159
- {
160
- ...webpAuditItemTemplate,
161
- url: 'http://www.example.com/script2.js', // First party, not filtered.
162
- subItems: {
163
- type: 'subitems',
164
- items: [
165
- {source: '3', sourceBytes: 3, sourceWastedBytes: 3},
166
- {source: '4', sourceBytes: 4, sourceWastedBytes: 4},
167
- ],
168
- },
169
- },
170
- {
171
- ...webpAuditItemTemplate,
172
- url: 'http://www.notexample.com/script3.js', // Third party, will be filtered.
173
- subItems: {
174
- type: 'subitems',
175
- items: [
176
- {source: '5', sourceBytes: 5, sourceWastedBytes: 5},
177
- {source: '6', sourceBytes: 6, sourceWastedBytes: 6},
178
- ],
179
- },
180
- },
181
- ];
182
-
183
- // Only third party URLs to test that checkbox is hidden
184
- lhr.audits['render-blocking-resources'].details.items = [
185
- {
186
- ...renderBlockingAuditItemTemplate,
187
- url: 'http://www.cdn.com/script1.js', // Third party.
188
- },
189
- {
190
- ...renderBlockingAuditItemTemplate,
191
- url: 'http://www.google.com/script2.js', // Third party.
192
- },
193
- {
194
- ...renderBlockingAuditItemTemplate,
195
- url: 'http://www.notexample.com/script3.js', // Third party.
196
- },
197
- ];
198
-
199
- // Only first party URLs to test that checkbox is hidden
200
- lhr.audits['uses-text-compression'].details.items = [
201
- {
202
- ...textCompressionAuditItemTemplate,
203
- url: 'http://www.example.com/font1.ttf', // First party.
204
- },
205
- {
206
- ...textCompressionAuditItemTemplate,
207
- url: 'http://www.example.com/font2.ttf', // First party.
208
- },
209
- {
210
- ...textCompressionAuditItemTemplate,
211
- url: 'http://www.example.com/font3.ttf', // First party.
212
- },
213
- ];
214
-
215
- // A mix of 3P, 1P and Unattributable.
216
- lhr.audits['bootup-time'].details.items = [
217
- {
218
- ...bootupTimeAuditItemTemplate,
219
- url: 'http://www.example.com/dobetterweb/dbw_tester.html',
220
- },
221
- {
222
- ...bootupTimeAuditItemTemplate,
223
- url: 'Unattributable',
224
- },
225
- {
226
- ...bootupTimeAuditItemTemplate,
227
- url: 'http://www.example.com/dobetterweb/third_party/aggressive-promise-polyfill.js',
228
- },
229
- {
230
- ...bootupTimeAuditItemTemplate,
231
- url: 'http://www.cdn.com/script1.js',
232
- },
233
- ];
234
-
235
- lhrJson = lhr;
236
- });
237
-
238
- describe('entity-classification based filtering works', () => {
239
- let container;
240
-
241
- before(() => {
242
- // Setup entity-classification with recognized entities first.
243
- lhrJson.entities = [
244
- {
245
- name: 'example.com',
246
- isFirstParty: true,
247
- isUnrecognized: true,
248
- origins: ['http://www.example.com'],
249
- },
250
- {
251
- name: 'cdn.com',
252
- isUnrecognized: true,
253
- origins: ['http://www.cdn.com'],
254
- },
255
- {
256
- name: 'notexample.com',
257
- isUnrecognized: true,
258
- origins: ['http://www.notexample.com'],
259
- },
260
- {
261
- name: 'google.com',
262
- origins: ['http://www.google.com'],
263
- },
264
- ];
265
-
266
- // Entity resolution is done during prepareReportResult
267
- const result = ReportUtils.prepareReportResult(lhrJson);
268
-
269
- // render a report onto the UIFeature dom
270
- container = render(result);
271
- });
272
-
273
- it('all items that contain a url have been marked with an entity for filtering', () => {
274
- ['modern-image-formats', 'render-blocking-resources', 'unused-javascript',
275
- 'uses-text-compression'].forEach(audit => {
276
- dom.findAll(`#${audit} tr`, container)
277
- .filter(el => dom.findAll('.lh-text__url', el).length > 0)
278
- .forEach(el => expect(el.dataset.entity).toBeTruthy());
279
- });
280
- });
281
-
282
- it('filters out third party resources in on click', () => {
283
- const filterCheckbox = dom.find('#modern-image-formats .lh-3p-filter-input', container);
284
-
285
- function getUrlsInTable() {
286
- return dom
287
- .findAll('#modern-image-formats tr:not(.lh-row--hidden) .lh-text__url a:first-child', container) // eslint-disable-line max-len
288
- .map(el => el.textContent);
289
- }
290
-
291
- expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
292
- filterCheckbox.click();
293
- expect(getUrlsInTable()).toEqual(['/img2.jpg']);
294
- filterCheckbox.click();
295
- expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
296
-
297
- const filter3pCount = dom.find('#modern-image-formats .lh-3p-filter-count', container);
298
- expect(filter3pCount.textContent).toEqual('2');
299
- });
300
-
301
- it('filters out sub-item rows of third party resources on click', () => {
302
- dom.find('#unused-javascript', container);
303
- const filterCheckbox = dom.find('#unused-javascript .lh-3p-filter-input', container);
304
-
305
- function getRowIdentifiers() {
306
- return dom
307
- .findAll(
308
- '#unused-javascript tbody tr:not(.lh-row--hidden)', container)
309
- .map(el => el.textContent);
310
- }
311
-
312
- const initialExpected = [
313
- '/script1.js(www.cdn.com)24.0 KiB8.8 KiB',
314
- '10.0 KiB0.0 KiB',
315
- '20.0 KiB0.0 KiB',
316
- '/script2.js(www.example.com)24.0 KiB8.8 KiB',
317
- '30.0 KiB0.0 KiB',
318
- '40.0 KiB0.0 KiB',
319
- '/script3.js(www.notexample.com)24.0 KiB8.8 KiB',
320
- '50.0 KiB0.0 KiB',
321
- '60.0 KiB0.0 KiB',
322
- ];
323
-
324
- expect(getRowIdentifiers()).toEqual(initialExpected);
325
- filterCheckbox.click();
326
- expect(getRowIdentifiers()).toEqual([
327
- '/script2.js(www.example.com)24.0 KiB8.8 KiB',
328
- '30.0 KiB0.0 KiB',
329
- '40.0 KiB0.0 KiB',
330
- ]);
331
- filterCheckbox.click();
332
- expect(getRowIdentifiers()).toEqual(initialExpected);
333
-
334
- const filter3pCount = dom.find('#unused-javascript .lh-3p-filter-count', container);
335
- expect(filter3pCount.textContent).toEqual('2');
336
- });
337
-
338
- it('adds no filter for audits in thirdPartyFilterAuditExclusions', () => {
339
- const checkboxClassName = 'lh-3p-filter-input';
340
-
341
- const yesCheckbox = dom.find(`#modern-image-formats .${checkboxClassName}`, container);
342
- expect(yesCheckbox).toBeTruthy();
343
-
344
- expect(() => dom.find(`#uses-rel-preconnect .${checkboxClassName}`, container))
345
- .toThrowError('query #uses-rel-preconnect .lh-3p-filter-input not found');
346
- });
347
-
348
- it('treats non-classifiable url values as first-party', () => {
349
- const auditEl = dom.find('#bootup-time', container);
350
- const filterCheckboxEl = dom.find('.lh-3p-filter-input', auditEl);
351
-
352
- // Ensure 3p filter is visible.
353
- expect(dom.find('.lh-3p-filter', auditEl).hidden).toBeFalsy();
354
-
355
- function getUrlsInTable() {
356
- return dom.findAll('tbody tr:not(.lh-row--hidden) td:first-child', auditEl)
357
- .map(el => el.textContent);
358
- }
359
-
360
- const preFilterRowSet = [
361
- '/dobetterweb/dbw_tester.html(www.example.com)',
362
- 'Unattributable',
363
- '…third_party/aggressive-promise-polyfill.js(www.example.com)',
364
- '/script1.js(www.cdn.com)',
365
- ];
366
-
367
- expect(getUrlsInTable()).toEqual(preFilterRowSet);
368
- filterCheckboxEl.click();
369
- expect(getUrlsInTable()).toEqual(preFilterRowSet.filter(
370
- text => !text.includes('cdn.com')));
371
- filterCheckboxEl.click();
372
- expect(getUrlsInTable()).toEqual(preFilterRowSet);
373
-
374
- const filter3pCountEl = dom.find('.lh-3p-filter-count', auditEl);
375
- expect(filter3pCountEl.textContent).toEqual('1');
376
- });
377
-
378
- it('filter is hidden when just third party resources', () => {
379
- const filterControl =
380
- dom.find('#render-blocking-resources .lh-3p-filter', container);
381
- expect(filterControl.hidden).toEqual(true);
382
- // Expect the hidden filter to still count third parties correctly.
383
- const filter3pCount = dom.find('#render-blocking-resources .lh-3p-filter-count',
384
- container);
385
- expect(filter3pCount.textContent).toEqual('3');
386
- });
387
-
388
- it('filter is hidden for just first party resources', () => {
389
- const filterControl = dom.find('#uses-text-compression .lh-3p-filter', container);
390
- expect(filterControl.hidden).toEqual(true);
391
- // Expect the hidden filter to still count third parties correctly.
392
- const filter3pCount = dom.find('#uses-text-compression .lh-3p-filter-count', container);
393
- expect(filter3pCount.textContent).toEqual('0');
394
- });
395
- });
396
-
397
- describe('legacy third-party filtering continues to work', () => {
398
- let container;
399
-
400
- before(() => {
401
- // Remove entity-classification audit to fall back to origin string match
402
- // based third-party filtering (legacy)
403
- delete lhrJson.entities;
404
-
405
- const result = ReportUtils.prepareReportResult(lhrJson);
406
-
407
- // render a report onto the UIFeature dom
408
- container = render(result);
409
- });
410
-
411
- it('filters out third party resources in on click', () => {
412
- const filterCheckbox = dom.find('#modern-image-formats .lh-3p-filter-input', container);
413
-
414
- function getUrlsInTable() {
415
- return dom
416
- .findAll('#modern-image-formats tr:not(.lh-row--hidden) .lh-text__url a:first-child', container) // eslint-disable-line max-len
417
- .map(el => el.textContent);
418
- }
419
-
420
- expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
421
- filterCheckbox.click();
422
- expect(getUrlsInTable()).toEqual(['/img2.jpg']);
423
- filterCheckbox.click();
424
- expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
425
- const filter3pCount = dom.find('#modern-image-formats .lh-3p-filter-count', container);
426
- expect(filter3pCount.textContent).toEqual('2');
427
- });
428
-
429
- it('filters out sub-item rows of third party resources on click', () => {
430
- dom.find('#unused-javascript', container);
431
- const filterCheckbox = dom.find('#unused-javascript .lh-3p-filter-input', container);
432
-
433
- function getRowIdentifiers() {
434
- return dom
435
- .findAll(
436
- '#unused-javascript tbody tr:not(.lh-row--hidden)', container)
437
- .map(el => el.textContent);
438
- }
439
-
440
- const initialExpected = [
441
- '/script1.js(www.cdn.com)24.0 KiB8.8 KiB',
442
- '10.0 KiB0.0 KiB',
443
- '20.0 KiB0.0 KiB',
444
- '/script2.js(www.example.com)24.0 KiB8.8 KiB',
445
- '30.0 KiB0.0 KiB',
446
- '40.0 KiB0.0 KiB',
447
- '/script3.js(www.notexample.com)24.0 KiB8.8 KiB',
448
- '50.0 KiB0.0 KiB',
449
- '60.0 KiB0.0 KiB',
450
- ];
451
-
452
- expect(getRowIdentifiers()).toEqual(initialExpected);
453
- filterCheckbox.click();
454
- expect(getRowIdentifiers()).toEqual([
455
- '/script2.js(www.example.com)24.0 KiB8.8 KiB',
456
- '30.0 KiB0.0 KiB',
457
- '40.0 KiB0.0 KiB',
458
- ]);
459
-
460
- filterCheckbox.click();
461
- expect(getRowIdentifiers()).toEqual(initialExpected);
462
- const filter3pCount = dom.find('#unused-javascript .lh-3p-filter-count', container);
463
- expect(filter3pCount.textContent).toEqual('2');
464
- });
465
-
466
- it('adds no filter for audits in thirdPartyFilterAuditExclusions', () => {
467
- const checkboxClassName = 'lh-3p-filter-input';
468
-
469
- const yesCheckbox = dom.find(`#modern-image-formats .${checkboxClassName}`, container);
470
- expect(yesCheckbox).toBeTruthy();
471
-
472
- expect(() => dom.find(`#uses-rel-preconnect .${checkboxClassName}`, container))
473
- .toThrowError('query #uses-rel-preconnect .lh-3p-filter-input not found');
474
- });
475
-
476
- it('filter is hidden when just third party resources', () => {
477
- const filterControl =
478
- dom.find('#render-blocking-resources .lh-3p-filter', container);
479
- expect(filterControl.hidden).toEqual(true);
480
- // Expect the hidden filter to still count third parties correctly.
481
- const filter3pCount = dom.find('#render-blocking-resources .lh-3p-filter-count',
482
- container);
483
- expect(filter3pCount.textContent).toEqual('3');
484
- });
485
-
486
- it('filter is hidden for just first party resources', () => {
487
- const filterControl = dom.find('#uses-text-compression .lh-3p-filter', container);
488
- expect(filterControl.hidden).toEqual(true);
489
- // Expect the hidden filter to still count third parties correctly.
490
- const filter3pCount = dom.find('#uses-text-compression .lh-3p-filter-count',
491
- container);
492
- expect(filter3pCount.textContent).toEqual('0');
493
- });
494
- });
495
- });
496
-
497
- it('save-html option enabled if callback present', () => {
498
- let container = render(sampleResults);
499
- const getSaveEl = () => dom.find('a[data-action="save-html"]', container);
500
- expect(getSaveEl().classList.contains('lh-hidden')).toBeTruthy();
501
-
502
- const getHtmlMock = jestMock.fn();
503
- container = render(sampleResults, {
504
- getStandaloneReportHTML: getHtmlMock,
505
- });
506
- expect(getSaveEl().classList.contains('lh-hidden')).toBeFalsy();
507
-
508
- expect(getHtmlMock).not.toBeCalled();
509
- getSaveEl().click();
510
- expect(getHtmlMock).toBeCalled();
511
- });
512
- });
513
-
514
- describe('fireworks', () => {
515
- it('should render an non-all 100 report without fireworks', () => {
516
- const lhr = JSON.parse(JSON.stringify(sampleResults));
517
- lhr.categories.performance.score = 0.5;
518
- const container = render(lhr);
519
- assert.ok(container.querySelector('.lh-score100') === null, 'has no fireworks treatment');
520
- });
521
-
522
- it('should render an all 100 report with fireworks', () => {
523
- const lhr = JSON.parse(JSON.stringify(sampleResults));
524
- Object.values(lhr.categories).forEach(element => {
525
- element.score = 1;
526
- });
527
- const container = render(lhr);
528
- assert.ok(container.querySelector('.lh-score100'), 'has fireworks treatment');
529
- });
530
-
531
- it('should show fireworks for all 100s except PWA', () => {
532
- const lhr = JSON.parse(JSON.stringify(sampleResults));
533
- Object.values(lhr.categories).forEach(element => {
534
- element.score = 1;
535
- });
536
- lhr.categories.pwa.score = 0;
537
-
538
- const container = render(lhr);
539
- assert.ok(container.querySelector('.lh-score100'), 'has fireworks treatment');
540
- });
541
-
542
- it('should not render fireworks if all core categories are not present', () => {
543
- const lhr = JSON.parse(JSON.stringify(sampleResults));
544
- delete lhr.categories.performance;
545
- delete lhr.categoryGroups.performace;
546
- Object.values(lhr.categories).forEach(element => {
547
- element.score = 1;
548
- });
549
- const container = render(lhr);
550
- assert.ok(container.querySelector('.lh-score100') === null, 'has no fireworks treatment');
551
- });
552
- });
553
-
554
- describe('metric descriptions', () => {
555
- it('with no errors, hide by default', () => {
556
- const lhr = JSON.parse(JSON.stringify(sampleResults));
557
- const container = render(lhr);
558
- assert.ok(!container.querySelector('.lh-metrics-toggle__input').checked);
559
- });
560
-
561
- it('with error, show by default', () => {
562
- const lhr = JSON.parse(JSON.stringify(sampleResults));
563
- lhr.audits['first-contentful-paint'].errorMessage = 'Error.';
564
- const container = render(lhr);
565
- assert.ok(container.querySelector('.lh-metrics-toggle__input').checked);
566
- });
567
- });
568
-
569
- describe('tools button', () => {
570
- let window;
571
- let dropDown;
572
-
573
- beforeEach(() => {
574
- window = dom.document().defaultView;
575
- const features = new ReportUIFeatures(dom);
576
- features.initFeatures(sampleResults);
577
- dropDown = features._topbar._dropDownMenu;
578
- });
579
-
580
- it('click should toggle active class', () => {
581
- dropDown._toggleEl.click();
582
- assert.ok(dropDown._toggleEl.classList.contains('lh-active'));
583
-
584
- dropDown._toggleEl.click();
585
- assert.ok(!dropDown._toggleEl.classList.contains('lh-active'));
586
- });
587
-
588
- it('Escape key removes active class', () => {
589
- dropDown._toggleEl.click();
590
- assert.ok(dropDown._toggleEl.classList.contains('lh-active'));
591
-
592
- const escape = new window.KeyboardEvent('keydown', {keyCode: /* ESC */ 27});
593
- dom.document().dispatchEvent(escape);
594
- assert.ok(!dropDown._toggleEl.classList.contains('lh-active'));
595
- });
596
-
597
- ['ArrowUp', 'ArrowDown', 'Enter', ' '].forEach((code) => {
598
- it(`'${code}' adds active class`, () => {
599
- const event = new window.KeyboardEvent('keydown', {code});
600
- dropDown._toggleEl.dispatchEvent(event);
601
- assert.ok(dropDown._toggleEl.classList.contains('lh-active'));
602
- });
603
- });
604
-
605
- it('ArrowUp on the first menu element should focus the last element', () => {
606
- dropDown._toggleEl.click();
607
-
608
- const arrowUp = new window.KeyboardEvent('keydown', {bubbles: true, code: 'ArrowUp'});
609
- dropDown._menuEl.firstElementChild.dispatchEvent(arrowUp);
610
-
611
- assert.strictEqual(dom.document().activeElement, dropDown._menuEl.lastElementChild);
612
- });
613
-
614
- it('ArrowDown on the first menu element should focus the second element', () => {
615
- dropDown._toggleEl.click();
616
-
617
- const {nextElementSibling} = dropDown._menuEl.firstElementChild;
618
- const arrowDown = new window.KeyboardEvent('keydown', {bubbles: true, code: 'ArrowDown'});
619
- dropDown._menuEl.firstElementChild.dispatchEvent(arrowDown);
620
-
621
- assert.strictEqual(dom.document().activeElement, nextElementSibling);
622
- });
623
-
624
- it('Home on the last menu element should focus the first element', () => {
625
- dropDown._toggleEl.click();
626
-
627
- const {firstElementChild} = dropDown._menuEl;
628
- const home = new window.KeyboardEvent('keydown', {bubbles: true, code: 'Home'});
629
- dropDown._menuEl.lastElementChild.dispatchEvent(home);
630
-
631
- assert.strictEqual(dom.document().activeElement, firstElementChild);
632
- });
633
-
634
- it('End on the first menu element should focus the last element', () => {
635
- dropDown._toggleEl.click();
636
-
637
- const {lastElementChild} = dropDown._menuEl;
638
- const end = new window.KeyboardEvent('keydown', {bubbles: true, code: 'End'});
639
- dropDown._menuEl.firstElementChild.dispatchEvent(end);
640
-
641
- assert.strictEqual(dom.document().activeElement, lastElementChild);
642
- });
643
-
644
- describe('_getNextSelectableNode', () => {
645
- let createDiv;
646
-
647
- before(() => {
648
- createDiv = () => dom.document().createElement('div');
649
- });
650
-
651
- it('should return undefined when nodes is empty', () => {
652
- const nodes = [];
653
-
654
- const nextNode = dropDown._getNextSelectableNode(nodes);
655
-
656
- assert.strictEqual(nextNode, undefined);
657
- });
658
-
659
- it('should return the only node when start is defined', () => {
660
- const node = createDiv();
661
-
662
- const nextNode = dropDown._getNextSelectableNode([node], node);
663
-
664
- assert.strictEqual(nextNode, node);
665
- });
666
-
667
- it('should return first node when start is undefined', () => {
668
- const nodes = [createDiv(), createDiv()];
669
-
670
- const nextNode = dropDown._getNextSelectableNode(nodes);
671
-
672
- assert.strictEqual(nextNode, nodes[0]);
673
- });
674
-
675
- it('should return second node when start is first node', () => {
676
- const nodes = [createDiv(), createDiv()];
677
-
678
- const nextNode = dropDown._getNextSelectableNode(nodes, nodes[0]);
679
-
680
- assert.strictEqual(nextNode, nodes[1]);
681
- });
682
-
683
- it('should return first node when start is second node', () => {
684
- const nodes = [createDiv(), createDiv()];
685
-
686
- const nextNode = dropDown._getNextSelectableNode(nodes, nodes[1]);
687
-
688
- assert.strictEqual(nextNode, nodes[0]);
689
- });
690
-
691
- it('should skip the undefined node', () => {
692
- const nodes = [createDiv(), undefined, createDiv()];
693
-
694
- const nextNode = dropDown._getNextSelectableNode(nodes, nodes[0]);
695
-
696
- assert.strictEqual(nextNode, nodes[2]);
697
- });
698
-
699
- it('should skip the disabled node', () => {
700
- const disabledNode = createDiv();
701
- disabledNode.setAttribute('disabled', true);
702
- const nodes = [createDiv(), disabledNode, createDiv()];
703
-
704
- const nextNode = dropDown._getNextSelectableNode(nodes, nodes[0]);
705
-
706
- assert.strictEqual(nextNode, nodes[2]);
707
- });
708
- });
709
-
710
- describe('onMenuFocusOut', () => {
711
- beforeEach(() => {
712
- dropDown._toggleEl.click();
713
- assert.ok(dropDown._toggleEl.classList.contains('lh-active'));
714
- });
715
-
716
- it('should toggle active class when focus relatedTarget is null', () => {
717
- const event = new window.FocusEvent('focusout', {relatedTarget: null});
718
- dropDown.onMenuFocusOut(event);
719
-
720
- assert.ok(!dropDown._toggleEl.classList.contains('lh-active'));
721
- });
722
-
723
- it('should toggle active class when focus relatedTarget is document.body', () => {
724
- const relatedTarget = dom.document().body;
725
- const event = new window.FocusEvent('focusout', {relatedTarget});
726
- dropDown.onMenuFocusOut(event);
727
-
728
- assert.ok(!dropDown._toggleEl.classList.contains('lh-active'));
729
- });
730
-
731
- it('should toggle active class when focus relatedTarget is _toggleEl', () => {
732
- const relatedTarget = dropDown._toggleEl;
733
- const event = new window.FocusEvent('focusout', {relatedTarget});
734
- dropDown.onMenuFocusOut(event);
735
-
736
- assert.ok(!dropDown._toggleEl.classList.contains('lh-active'));
737
- });
738
-
739
- it('should not toggle active class when focus relatedTarget is a menu item', () => {
740
- const relatedTarget = dropDown._getNextMenuItem();
741
- const event = new window.FocusEvent('focusout', {relatedTarget});
742
- dropDown.onMenuFocusOut(event);
743
-
744
- assert.ok(dropDown._toggleEl.classList.contains('lh-active'));
745
- });
746
- });
747
- });
748
-
749
- describe('treemap button', () => {
750
- it('should only show button if treemap data is available', () => {
751
- const lhr = JSON.parse(JSON.stringify(sampleResults));
752
-
753
- expect(lhr.audits['script-treemap-data']).not.toBeUndefined();
754
- expect(render(lhr).querySelector('.lh-button.lh-report-icon--treemap')).toBeTruthy();
755
-
756
- delete lhr.audits['script-treemap-data'];
757
- const newAuditRefs = lhr.categories['performance'].auditRefs
758
- .filter(a => a.id !== 'script-treemap-data');
759
- lhr.categories['performance'].auditRefs = newAuditRefs;
760
- expect(render(lhr).querySelector('.lh-button.lh-report-icon--treemap')).toBeNull();
761
- });
762
- });
763
-
764
- describe('data-i18n', () => {
765
- it('should have only valid data-i18n values in template', () => {
766
- const container = render(sampleResults);
767
- for (const node of dom.findAll('[data-i18n]', container)) {
768
- const val = node.getAttribute('data-i18n');
769
- assert.ok(val in UIStrings, `Invalid data-i18n value of: "${val}" not found.`);
770
- }
771
- });
772
- });
773
- });