lighthouse 8.6.0 → 9.0.0

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 (209) hide show
  1. package/dist/report/bundle.esm.js +5994 -0
  2. package/dist/report/flow.js +30 -13
  3. package/dist/report/standalone.js +41 -35
  4. package/flow-report/.eslintrc.cjs +2 -0
  5. package/flow-report/assets/standalone-flow-template.html +3 -2
  6. package/flow-report/assets/styles.css +108 -31
  7. package/flow-report/src/app.tsx +18 -18
  8. package/flow-report/src/common.tsx +28 -15
  9. package/flow-report/src/header.tsx +12 -13
  10. package/flow-report/src/help-dialog.tsx +3 -4
  11. package/flow-report/src/i18n/i18n.tsx +57 -16
  12. package/flow-report/src/i18n/ui-strings.js +50 -0
  13. package/flow-report/src/icons.tsx +36 -8
  14. package/flow-report/src/sidebar/flow.tsx +3 -3
  15. package/flow-report/src/sidebar/sidebar.tsx +35 -14
  16. package/flow-report/src/summary/category.tsx +97 -24
  17. package/flow-report/src/summary/summary.tsx +21 -14
  18. package/flow-report/src/topbar.tsx +9 -5
  19. package/flow-report/src/util.ts +33 -26
  20. package/flow-report/src/wrappers/category-score.tsx +0 -3
  21. package/flow-report/src/wrappers/markdown.tsx +27 -0
  22. package/flow-report/src/wrappers/report-renderer.tsx +10 -3
  23. package/flow-report/src/wrappers/report.tsx +27 -6
  24. package/flow-report/standalone-flow.tsx +4 -3
  25. package/flow-report/test/common-test.tsx +32 -17
  26. package/flow-report/test/flow-report-pptr-test.ts +46 -0
  27. package/flow-report/test/header-test.tsx +6 -6
  28. package/flow-report/test/setup/env-setup.ts +1 -1
  29. package/flow-report/test/sidebar/sidebar-test.tsx +43 -1
  30. package/flow-report/test/summary/category-test.tsx +120 -24
  31. package/flow-report/test/summary/summary-test.tsx +1 -1
  32. package/flow-report/test/topbar-test.tsx +1 -0
  33. package/flow-report/test/util-test.tsx +32 -11
  34. package/flow-report/test/wrappers/category-score-test.tsx +87 -0
  35. package/flow-report/test/wrappers/markdown-test.tsx +29 -0
  36. package/flow-report/tsconfig.json +1 -0
  37. package/jest.config.js +4 -2
  38. package/lighthouse-cli/.eslintrc.cjs +2 -0
  39. package/lighthouse-cli/cli-flags.js +1 -1
  40. package/lighthouse-cli/run.js +1 -1
  41. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +0 -0
  42. package/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +4 -2
  43. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
  44. package/lighthouse-cli/test/smokehouse/smokehouse.js +1 -1
  45. package/lighthouse-core/audits/accessibility/axe-audit.js +7 -1
  46. package/lighthouse-core/audits/audit.js +1 -1
  47. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +2 -0
  48. package/lighthouse-core/audits/byte-efficiency/modern-image-formats.js +4 -2
  49. package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +11 -10
  50. package/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js +5 -3
  51. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images-snapshot.js +2 -1
  52. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +2 -1
  53. package/lighthouse-core/audits/deprecations.js +33 -11
  54. package/lighthouse-core/audits/dobetterweb/uses-http2.js +27 -11
  55. package/lighthouse-core/audits/errors-in-console.js +0 -2
  56. package/lighthouse-core/audits/image-aspect-ratio.js +5 -5
  57. package/lighthouse-core/audits/image-size-responsive.js +6 -6
  58. package/lighthouse-core/audits/installable-manifest.js +5 -3
  59. package/lighthouse-core/audits/preload-lcp-image.js +7 -5
  60. package/lighthouse-core/audits/script-treemap-data.js +1 -1
  61. package/lighthouse-core/audits/unsized-images.js +2 -3
  62. package/lighthouse-core/computed/image-records.js +4 -3
  63. package/lighthouse-core/computed/metrics/lantern-total-blocking-time.js +3 -4
  64. package/lighthouse-core/computed/metrics/tbt-utils.js +57 -0
  65. package/lighthouse-core/computed/metrics/total-blocking-time.js +3 -53
  66. package/lighthouse-core/computed/resource-summary.js +1 -1
  67. package/lighthouse-core/config/config-helpers.js +21 -10
  68. package/lighthouse-core/config/constants.js +3 -4
  69. package/lighthouse-core/config/default-config.js +61 -71
  70. package/lighthouse-core/fraggle-rock/config/config.js +1 -1
  71. package/lighthouse-core/fraggle-rock/config/default-config.js +4 -5
  72. package/lighthouse-core/fraggle-rock/gather/base-gatherer.js +6 -1
  73. package/lighthouse-core/fraggle-rock/gather/session.js +3 -4
  74. package/lighthouse-core/gather/connections/cri.js +1 -1
  75. package/lighthouse-core/gather/driver/storage.js +0 -1
  76. package/lighthouse-core/gather/driver.js +3 -7
  77. package/lighthouse-core/gather/fetcher.js +4 -7
  78. package/lighthouse-core/gather/gather-runner.js +29 -6
  79. package/lighthouse-core/gather/gatherers/accessibility.js +27 -0
  80. package/lighthouse-core/gather/gatherers/dobetterweb/response-compression.js +1 -0
  81. package/lighthouse-core/gather/gatherers/image-elements.js +0 -3
  82. package/lighthouse-core/gather/gatherers/inspector-issues.js +5 -0
  83. package/lighthouse-core/lib/asset-saver.js +7 -4
  84. package/lighthouse-core/lib/emulation.js +45 -1
  85. package/lighthouse-core/lib/i18n/i18n.js +19 -8
  86. package/lighthouse-core/lib/lh-env.js +2 -0
  87. package/lighthouse-core/lib/proto-preprocessor.js +14 -2
  88. package/lighthouse-core/lib/tappable-rects.js +8 -15
  89. package/lighthouse-core/lib/url-shim.js +1 -1
  90. package/lighthouse-core/runner.js +1 -1
  91. package/lighthouse-core/util-commonjs.js +65 -46
  92. package/package.json +27 -17
  93. package/readme.md +3 -3
  94. package/report/.eslintrc.cjs +2 -0
  95. package/report/README.md +1 -1
  96. package/report/assets/standalone-template.html +2 -3
  97. package/report/assets/styles.css +360 -254
  98. package/report/assets/templates.html +21 -76
  99. package/report/clients/bundle.js +1 -0
  100. package/report/clients/standalone.js +6 -15
  101. package/report/generator/README.md +1 -1
  102. package/report/renderer/api.js +32 -0
  103. package/report/renderer/category-renderer.js +59 -19
  104. package/report/renderer/components.js +59 -110
  105. package/report/renderer/crc-details-renderer.js +15 -12
  106. package/report/renderer/dom.js +16 -1
  107. package/report/renderer/drop-down-menu.js +2 -2
  108. package/report/renderer/element-screenshot-renderer.js +8 -8
  109. package/report/renderer/features-util.js +1 -1
  110. package/report/renderer/open-tab.js +9 -3
  111. package/report/renderer/performance-category-renderer.js +55 -34
  112. package/report/renderer/pwa-category-renderer.js +0 -10
  113. package/report/renderer/report-renderer.js +91 -44
  114. package/report/renderer/report-ui-features.js +36 -33
  115. package/report/renderer/swap-locale-feature.js +3 -3
  116. package/report/renderer/topbar-features.js +53 -48
  117. package/report/renderer/util.js +66 -46
  118. package/report/test/clients/bundle-test.js +70 -0
  119. package/report/test/renderer/category-renderer-test.js +48 -3
  120. package/report/test/renderer/i18n-test.js +0 -7
  121. package/report/test/renderer/performance-category-renderer-test.js +51 -14
  122. package/report/test/renderer/pwa-category-renderer-test.js +6 -6
  123. package/report/test/renderer/report-renderer-axe-test.js +10 -12
  124. package/report/test/renderer/report-renderer-test.js +23 -22
  125. package/report/test/renderer/report-ui-features-test.js +20 -10
  126. package/report/test/renderer/text-encoding-test.js +1 -1
  127. package/report/test/renderer/util-test.js +35 -31
  128. package/report/test-assets/faux-psi-template.html +3 -11
  129. package/report/test-assets/faux-psi.js +26 -22
  130. package/report/types/html-renderer.d.ts +2 -0
  131. package/report/types/report-renderer.d.ts +23 -0
  132. package/shared/localization/format.js +64 -30
  133. package/shared/localization/locales/ar-XB.json +72 -75
  134. package/shared/localization/locales/ar.json +72 -75
  135. package/shared/localization/locales/bg.json +72 -75
  136. package/shared/localization/locales/ca.json +72 -75
  137. package/shared/localization/locales/cs.json +89 -92
  138. package/shared/localization/locales/da.json +72 -75
  139. package/shared/localization/locales/de.json +89 -92
  140. package/shared/localization/locales/el.json +72 -75
  141. package/shared/localization/locales/en-GB.json +72 -75
  142. package/shared/localization/locales/en-US.json +75 -75
  143. package/shared/localization/locales/en-XA.json +72 -75
  144. package/shared/localization/locales/en-XL.json +75 -75
  145. package/shared/localization/locales/es-419.json +90 -93
  146. package/shared/localization/locales/es.json +90 -93
  147. package/shared/localization/locales/fi.json +72 -75
  148. package/shared/localization/locales/fil.json +72 -75
  149. package/shared/localization/locales/fr.json +89 -92
  150. package/shared/localization/locales/he.json +73 -76
  151. package/shared/localization/locales/hi.json +72 -75
  152. package/shared/localization/locales/hr.json +72 -75
  153. package/shared/localization/locales/hu.json +89 -92
  154. package/shared/localization/locales/id.json +89 -92
  155. package/shared/localization/locales/it.json +89 -92
  156. package/shared/localization/locales/ja.json +89 -92
  157. package/shared/localization/locales/ko.json +89 -92
  158. package/shared/localization/locales/lt.json +89 -92
  159. package/shared/localization/locales/lv.json +95 -98
  160. package/shared/localization/locales/nl.json +89 -92
  161. package/shared/localization/locales/no.json +72 -75
  162. package/shared/localization/locales/pl.json +72 -75
  163. package/shared/localization/locales/pt-PT.json +72 -75
  164. package/shared/localization/locales/pt.json +89 -92
  165. package/shared/localization/locales/ro.json +90 -93
  166. package/shared/localization/locales/ru.json +89 -92
  167. package/shared/localization/locales/sk.json +89 -92
  168. package/shared/localization/locales/sl.json +89 -92
  169. package/shared/localization/locales/sr-Latn.json +89 -92
  170. package/shared/localization/locales/sr.json +89 -92
  171. package/shared/localization/locales/sv.json +72 -75
  172. package/shared/localization/locales/ta.json +89 -92
  173. package/shared/localization/locales/te.json +89 -92
  174. package/shared/localization/locales/th.json +89 -92
  175. package/shared/localization/locales/tr.json +89 -92
  176. package/shared/localization/locales/uk.json +92 -95
  177. package/shared/localization/locales/vi.json +89 -92
  178. package/shared/localization/locales/zh-HK.json +89 -92
  179. package/shared/localization/locales/zh-TW.json +89 -92
  180. package/shared/localization/locales/zh.json +89 -92
  181. package/shared/localization/locales.js +6 -0
  182. package/shared/test/localization/format-test.js +45 -9
  183. package/shared/test/localization/swap-locale-test.js +0 -12
  184. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +19 -11
  185. package/third-party/chromium-synchronization/installability-errors-test.js +2 -0
  186. package/third-party/download-content-shell/download-content-shell.js +2 -2
  187. package/third-party/download-content-shell/utils.js +0 -24
  188. package/tsconfig-all.json +2 -2
  189. package/tsconfig.json +0 -1
  190. package/types/artifacts.d.ts +7 -7
  191. package/types/enquirer.d.ts +3 -1
  192. package/types/lhr/audit-details.d.ts +6 -5
  193. package/types/lhr/flow.d.ts +3 -2
  194. package/types/lhr/lhr.d.ts +1 -1
  195. package/types/node.d.ts +5 -5
  196. package/types/rollup-plugin-postprocess.d.ts +10 -0
  197. package/changelog.md +0 -5490
  198. package/lighthouse-core/audits/dobetterweb/appcache-manifest.js +0 -67
  199. package/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js +0 -96
  200. package/lighthouse-core/audits/redirects-http.js +0 -59
  201. package/lighthouse-core/gather/gatherers/dobetterweb/appcache.js +0 -34
  202. package/lighthouse-core/gather/gatherers/http-redirect.js +0 -46
  203. package/lighthouse-core/lib/i18n/locales/en-US.ctc.json +0 -6699
  204. package/lighthouse-core/lib/i18n/locales/en-XL.ctc.json +0 -6672
  205. package/lighthouse-core/scripts/package.json +0 -4
  206. package/report/clients/psi.js +0 -158
  207. package/report/test/clients/psi-test.js +0 -143
  208. package/shared/localization/locales/en-US.ctc.json +0 -6877
  209. package/shared/localization/locales/en-XL.ctc.json +0 -6852
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @license Copyright 2021 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
+ 'use strict';
8
+
9
+ import fs from 'fs';
10
+
11
+ import jsdom from 'jsdom';
12
+ import {jest} from '@jest/globals';
13
+
14
+ import * as lighthouseRenderer from '../../clients/bundle.js';
15
+ import {LH_ROOT} from '../../../root.js';
16
+
17
+ const sampleResultsStr =
18
+ fs.readFileSync(LH_ROOT + '/lighthouse-core/test/results/sample_v2.json', 'utf-8');
19
+
20
+
21
+ /* eslint-env jest */
22
+
23
+ describe('lighthouseRenderer bundle', () => {
24
+ let document;
25
+ beforeAll(() => {
26
+ global.console.warn = jest.fn();
27
+
28
+ const {window} = new jsdom.JSDOM();
29
+ document = window.document;
30
+
31
+ global.window = global.self = window;
32
+ global.window.requestAnimationFrame = fn => fn();
33
+ // Stub out matchMedia for Node.
34
+ global.self.matchMedia = function() {
35
+ return {
36
+ addListener: function() {},
37
+ };
38
+ };
39
+ global.window.ResizeObserver = class ResizeObserver {
40
+ observe() { }
41
+ unobserve() { }
42
+ };
43
+ });
44
+
45
+ afterAll(() => {
46
+ global.window = global.self = undefined;
47
+ });
48
+
49
+
50
+ it('renders an LHR to DOM', () => {
51
+ const lhr = /** @type {LH.Result} */ JSON.parse(sampleResultsStr);
52
+ const reportContainer = document.body;
53
+ reportContainer.classList.add('lh-vars', 'lh-root');
54
+
55
+ const dom = new lighthouseRenderer.DOM(reportContainer.ownerDocument);
56
+ const renderer = new lighthouseRenderer.ReportRenderer(dom);
57
+ renderer.renderReport(lhr, reportContainer);
58
+ const features = new lighthouseRenderer.ReportUIFeatures(renderer._dom);
59
+ features.initFeatures(lhr);
60
+
61
+
62
+ // Check that the report exists and has some content.
63
+ expect(reportContainer instanceof document.defaultView.Element).toBeTruthy();
64
+ expect(reportContainer.outerHTML.length).toBeGreaterThan(50000);
65
+
66
+ const title = reportContainer.querySelector('.lh-audit-group--metrics')
67
+ .querySelector('.lh-audit-group__title');
68
+ expect(title.textContent).toEqual('Metrics');
69
+ });
70
+ });
@@ -197,8 +197,8 @@ describe('CategoryRenderer', () => {
197
197
  'score shows informative and dash icon');
198
198
 
199
199
  assert.ok(pwaCategory.manualDescription);
200
- const description = categoryDOM
201
- .querySelector('.lh-clump--manual .lh-audit-group__description').textContent;
200
+ const description = categoryDOM.querySelector('.lh-clump--manual').closest('.lh-audit-group')
201
+ .querySelector('.lh-audit-group__description').textContent;
202
202
  // may need to be adjusted if description includes a link at the beginning
203
203
  assert.ok(description.startsWith(pwaCategory.manualDescription.substring(0, 20)),
204
204
  'no manual description');
@@ -409,6 +409,10 @@ describe('CategoryRenderer', () => {
409
409
  const passingRefs = categoryClone.auditRefs.filter(ref => ref.result.score === 1);
410
410
  passingRefs[0].result.warnings = ['Some warning'];
411
411
  passingRefs[1].result.warnings = ['Some warning'];
412
+ // Make one audit n/a
413
+ const audit = categoryClone.auditRefs.find(ref => ref.id === 'themed-omnibox');
414
+ audit.result.scoreDisplayMode = 'notApplicable';
415
+ audit.result.score = null;
412
416
 
413
417
  const elem = renderer.render(categoryClone, sampleResults.categoryGroups);
414
418
  const passedAudits = elem.querySelectorAll('.lh-clump--passed .lh-audit');
@@ -418,7 +422,7 @@ describe('CategoryRenderer', () => {
418
422
  const naAudits = elem.querySelectorAll('.lh-clump--notapplicable .lh-audit');
419
423
 
420
424
  assert.equal(passedAudits.length, 0);
421
- assert.equal(failedAudits.length, 6);
425
+ assert.equal(failedAudits.length, 5);
422
426
  assert.equal(warningAudits.length, 2);
423
427
  assert.equal(manualAudits.length, 3);
424
428
  assert.equal(naAudits.length, 1);
@@ -495,4 +499,45 @@ describe('CategoryRenderer', () => {
495
499
  assert.ok(shouldBeWarning[0].textContent.includes(passingWarning));
496
500
  });
497
501
  });
502
+
503
+ it('renders audits by weight', () => {
504
+ const defaultAuditRef = {
505
+ title: '',
506
+ description: '',
507
+ scoreDisplayMode: 'numeric',
508
+ score: 0,
509
+ warnings: [],
510
+ };
511
+ const category = {
512
+ id: 'test',
513
+ title: 'Test',
514
+ score: 0,
515
+ auditRefs: [{
516
+ id: 'audit-1',
517
+ weight: 0,
518
+ result: {
519
+ id: 'audit-1',
520
+ ...defaultAuditRef,
521
+ },
522
+ }, {
523
+ id: 'audit-2',
524
+ weight: 1,
525
+ result: {
526
+ id: 'audit-2',
527
+ ...defaultAuditRef,
528
+ },
529
+ }, {
530
+ id: 'audit-3',
531
+ weight: 0.5,
532
+ result: {
533
+ id: 'audit-3',
534
+ ...defaultAuditRef,
535
+ },
536
+ }],
537
+ };
538
+ const categoryDOM = renderer.render(category);
539
+
540
+ const auditEls = [...categoryDOM.querySelectorAll('.lh-audit')];
541
+ expect(auditEls.map(el => el.id)).toEqual(['audit-2', 'audit-3', 'audit-1']);
542
+ });
498
543
  });
@@ -9,7 +9,6 @@ import {strict as assert} from 'assert';
9
9
 
10
10
  import {Util} from '../../renderer/util.js';
11
11
  import {I18n} from '../../renderer/i18n.js';
12
- import {isNode12SmallIcu} from '../../../lighthouse-core/test/test-utils.js';
13
12
 
14
13
  // Require i18n to make sure Intl is polyfilled in Node without full-icu for testing.
15
14
  // When Util is run in a browser, Intl will be supplied natively (IE11+).
@@ -108,9 +107,6 @@ describe('util helpers', () => {
108
107
  });
109
108
 
110
109
  it('formats numbers based on locale', () => {
111
- // COMPAT: Node 12 only has 'en' by default.
112
- if (isNode12SmallIcu()) return;
113
-
114
110
  // Requires full-icu or Intl polyfill.
115
111
  const number = 12346.858558;
116
112
 
@@ -122,9 +118,6 @@ describe('util helpers', () => {
122
118
  });
123
119
 
124
120
  it('uses decimal comma with en-XA test locale', () => {
125
- // COMPAT: Node 12 only has 'en' by default.
126
- if (isNode12SmallIcu()) return;
127
-
128
121
  // Requires full-icu or Intl polyfill.
129
122
  const number = 12346.858558;
130
123
 
@@ -67,6 +67,17 @@ describe('PerfCategoryRenderer', () => {
67
67
  const timelineElements = metricsSection.querySelectorAll('.lh-metric');
68
68
  const nontimelineElements = metricsSection.querySelectorAll('.lh-audit');
69
69
  assert.equal(timelineElements.length + nontimelineElements.length, metricAudits.length);
70
+ assert.deepStrictEqual(
71
+ Array.from(timelineElements).map(el => el.id),
72
+ [
73
+ 'first-contentful-paint',
74
+ 'interactive',
75
+ 'speed-index',
76
+ 'total-blocking-time',
77
+ 'largest-contentful-paint',
78
+ 'cumulative-layout-shift',
79
+ ]
80
+ );
70
81
  });
71
82
 
72
83
  it('does not render metrics section if no metric group audits', () => {
@@ -84,7 +95,7 @@ describe('PerfCategoryRenderer', () => {
84
95
  it('renders the metrics variance disclaimer as markdown', () => {
85
96
  const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
86
97
  const disclaimerEl =
87
- categoryDOM.querySelector('.lh-audit-group--metrics > .lh-metrics__disclaimer');
98
+ categoryDOM.querySelector('.lh-category-header__description > .lh-metrics__disclaimer');
88
99
 
89
100
  assert.ok(disclaimerEl.textContent.includes('Values are estimated'));
90
101
  const disclamerLink = disclaimerEl.querySelector('a');
@@ -96,11 +107,23 @@ describe('PerfCategoryRenderer', () => {
96
107
  assert.strictEqual(new URL(calcLink.href).hostname, 'googlechrome.github.io');
97
108
  });
98
109
 
110
+ it('ignores hidden audits', () => {
111
+ const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
112
+
113
+ const hiddenAudits = category.auditRefs.filter(audit => audit.group === 'hidden');
114
+ const auditElements = [...categoryDOM.querySelectorAll('.lh-audit')];
115
+ const matchingElements = auditElements
116
+ .filter(el => hiddenAudits.some(audit => audit.id === el.id));
117
+ expect(matchingElements).toHaveLength(0);
118
+ });
119
+
99
120
  it('renders the failing performance opportunities', () => {
100
121
  const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
101
122
 
102
- const oppAudits = category.auditRefs.filter(audit => audit.group === 'load-opportunities' &&
103
- !Util.showAsPassed(audit.result));
123
+ const oppAudits = category.auditRefs.filter(audit =>
124
+ audit.result.details &&
125
+ audit.result.details.type === 'opportunity' &&
126
+ !Util.showAsPassed(audit.result));
104
127
  const oppElements = [...categoryDOM.querySelectorAll('.lh-audit--load-opportunity')];
105
128
  expect(oppElements.map(e => e.id).sort()).toEqual(oppAudits.map(a => a.id).sort());
106
129
  expect(oppElements.length).toBeGreaterThan(0);
@@ -120,10 +143,14 @@ describe('PerfCategoryRenderer', () => {
120
143
  it('renders performance opportunities with an errorMessage', () => {
121
144
  const auditWithError = {
122
145
  score: 0,
123
- group: 'load-opportunities',
124
146
  result: {
125
147
  score: null, scoreDisplayMode: 'error', errorMessage: 'Yikes!!', title: 'Bug #2',
126
148
  description: '',
149
+ details: {
150
+ overallSavingsMs: 0,
151
+ items: [],
152
+ type: 'opportunity',
153
+ },
127
154
  },
128
155
  };
129
156
 
@@ -137,10 +164,14 @@ describe('PerfCategoryRenderer', () => {
137
164
  it('renders performance opportunities\' explanation', () => {
138
165
  const auditWithExplanation = {
139
166
  score: 0,
140
- group: 'load-opportunities',
141
167
  result: {
142
168
  score: 0, scoreDisplayMode: 'numeric',
143
169
  numericValue: 100, explanation: 'Yikes!!', title: 'Bug #2', description: '',
170
+ details: {
171
+ overallSavingsMs: 0,
172
+ items: [],
173
+ type: 'opportunity',
174
+ },
144
175
  },
145
176
  };
146
177
 
@@ -159,7 +190,9 @@ describe('PerfCategoryRenderer', () => {
159
190
  '.lh-category > .lh-audit-group.lh-audit-group--diagnostics');
160
191
 
161
192
  const diagnosticAuditIds = category.auditRefs.filter(audit => {
162
- return audit.group === 'diagnostics' && !Util.showAsPassed(audit.result);
193
+ return !audit.group &&
194
+ !(audit.result.details && audit.result.details.type === 'opportunity') &&
195
+ !Util.showAsPassed(audit.result);
163
196
  }).map(audit => audit.id).sort();
164
197
  assert.ok(diagnosticAuditIds.length > 0);
165
198
 
@@ -170,11 +203,11 @@ describe('PerfCategoryRenderer', () => {
170
203
 
171
204
  it('renders the passed audits', () => {
172
205
  const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
173
- const passedSection = categoryDOM.querySelector('.lh-category > .lh-clump--passed');
206
+ const passedSection = categoryDOM.querySelector('.lh-clump--passed');
174
207
 
175
208
  const passedAudits = category.auditRefs.filter(audit =>
176
- audit.group && audit.group !== 'metrics' && audit.id !== 'performance-budget'
177
- && Util.showAsPassed(audit.result));
209
+ !audit.group &&
210
+ Util.showAsPassed(audit.result));
178
211
  const passedElements = passedSection.querySelectorAll('.lh-audit');
179
212
  assert.equal(passedElements.length, passedAudits.length);
180
213
  });
@@ -187,10 +220,14 @@ describe('PerfCategoryRenderer', () => {
187
220
  it('handles erroring opportunities', () => {
188
221
  const auditWithDebug = {
189
222
  score: 0,
190
- group: 'load-opportunities',
191
223
  result: {
192
224
  error: true, score: 0,
193
225
  numericValue: 100, explanation: 'Yikes!!', title: 'Bug #2',
226
+ details: {
227
+ overallSavingsMs: 0,
228
+ items: [],
229
+ type: 'opportunity',
230
+ },
194
231
  },
195
232
  };
196
233
  const wastedMs = renderer._getWastedMs(auditWithDebug);
@@ -256,10 +293,10 @@ describe('PerfCategoryRenderer', () => {
256
293
  expect(url.hash.split('&')).toMatchInlineSnapshot(`
257
294
  Array [
258
295
  "#FCP=6844",
259
- "SI=8114",
260
- "LCP=6844",
261
296
  "TTI=8191",
297
+ "SI=8114",
262
298
  "TBT=1221",
299
+ "LCP=6844",
263
300
  "CLS=0",
264
301
  "FMP=6844",
265
302
  ]
@@ -277,10 +314,10 @@ Array [
277
314
  expect(url.hash.split('&')).toMatchInlineSnapshot(`
278
315
  Array [
279
316
  "#FCP=6844",
280
- "SI=8114",
281
- "LCP=6844",
282
317
  "TTI=8191",
318
+ "SI=8114",
283
319
  "TBT=1221",
320
+ "LCP=6844",
284
321
  "CLS=0.14",
285
322
  "FMP=6844",
286
323
  "device=mobile",
@@ -107,12 +107,12 @@ describe('PwaCategoryRenderer', () => {
107
107
  const clone = JSON.parse(JSON.stringify(sampleResults));
108
108
  const category = clone.categories.pwa;
109
109
 
110
- // Set everything to passing, except redirects-http set to n/a (as it is on localhost)
110
+ // Set everything to passing, except for one. (themed-omnibox chosen randomly)
111
111
  for (const auditRef of category.auditRefs) {
112
112
  auditRef.result.score = 1;
113
113
  auditRef.result.scoreDisplayMode = 'binary';
114
114
  }
115
- const audit = category.auditRefs.find(ref => ref.id === 'redirects-http');
115
+ const audit = category.auditRefs.find(ref => ref.id === 'themed-omnibox');
116
116
  audit.result.scoreDisplayMode = 'notApplicable';
117
117
  audit.result.score = null;
118
118
 
@@ -249,10 +249,10 @@ describe('PwaCategoryRenderer', () => {
249
249
  });
250
250
  });
251
251
 
252
- describe('#renderScoreGauge', () => {
252
+ describe('#renderCategoryScore', () => {
253
253
  it('renders an error score gauge in case of category error', () => {
254
254
  category.score = null;
255
- const badgeGauge = pwaRenderer.renderScoreGauge(category, sampleResults.categoryGroups);
255
+ const badgeGauge = pwaRenderer.renderCategoryScore(category, sampleResults.categoryGroups);
256
256
 
257
257
  // Not a PWA gauge.
258
258
  assert.strictEqual(badgeGauge.querySelector('.lh-gauge--pwa__wrapper'), null);
@@ -263,11 +263,11 @@ describe('PwaCategoryRenderer', () => {
263
263
  });
264
264
 
265
265
  it('renders score gauges with unique ids for items in <defs>', () => {
266
- const gauge1 = pwaRenderer.renderScoreGauge(category, sampleResults.categoryGroups);
266
+ const gauge1 = pwaRenderer.renderCategoryScore(category, sampleResults.categoryGroups);
267
267
  const gauge1Ids = [...gauge1.querySelectorAll('defs [id]')].map(el => el.id);
268
268
  assert.ok(gauge1Ids.length > 2);
269
269
 
270
- const gauge2 = pwaRenderer.renderScoreGauge(category, sampleResults.categoryGroups);
270
+ const gauge2 = pwaRenderer.renderCategoryScore(category, sampleResults.categoryGroups);
271
271
  const gauge2Ids = [...gauge2.querySelectorAll('defs [id]')].map(el => el.id);
272
272
  assert.ok(gauge2Ids.length === gauge1Ids.length);
273
273
 
@@ -8,6 +8,7 @@
8
8
  /* eslint-env jest */
9
9
 
10
10
  import jsdom from 'jsdom';
11
+ import {jest} from '@jest/globals';
11
12
 
12
13
  import {Util} from '../../renderer/util.js';
13
14
  import {DOM} from '../../renderer/dom.js';
@@ -23,6 +24,8 @@ describe('ReportRendererAxe', () => {
23
24
  let sampleResults;
24
25
 
25
26
  beforeAll(async () => {
27
+ global.console.warn = jest.fn();
28
+
26
29
  const {window} = new jsdom.JSDOM();
27
30
  const dom = new DOM(window.document);
28
31
  const detailsRenderer = new DetailsRenderer(dom);
@@ -46,11 +49,11 @@ describe('ReportRendererAxe', () => {
46
49
  global.Element = undefined;
47
50
  });
48
51
 
49
- it('renders without axe violations', () => {
50
- const container = renderer._dom._document.createElement('main');
52
+ it('renders without axe violations', async () => {
53
+ const container = renderer._dom.document().createElement('main');
51
54
  const output = renderer.renderReport(sampleResults, container);
52
55
 
53
- renderer._dom._document.body.appendChild(container);
56
+ renderer._dom.document().body.appendChild(container);
54
57
 
55
58
  const config = {
56
59
  rules: {
@@ -70,16 +73,11 @@ describe('ReportRendererAxe', () => {
70
73
  },
71
74
  };
72
75
 
73
- return new Promise(resolve => {
74
- axe.run(output, config, (error, {violations}) => {
75
- expect(error).toBeNull();
76
- expect(violations).toEqual([]);
77
- resolve();
78
- });
79
- });
80
- // This test takes 40s on fast hardware, and 50-60s on GHA.
81
- // https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#timeout-issues
76
+ const axeResults = await axe.run(output, config);
77
+ expect(axeResults.violations).toEqual([]);
82
78
  },
79
+ // This test takes 40s on fast hardware, and 50-60s on GHA.
80
+ // https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#timeout-issues
83
81
  /* timeout= */ 100 * 1000
84
82
  );
85
83
  });
@@ -10,6 +10,7 @@
10
10
  import {strict as assert} from 'assert';
11
11
 
12
12
  import jsdom from 'jsdom';
13
+ import {jest} from '@jest/globals';
13
14
 
14
15
  import {Util} from '../../renderer/util.js';
15
16
  import URL from '../../../lighthouse-core/lib/url-shim.js';
@@ -26,6 +27,8 @@ describe('ReportRenderer', () => {
26
27
  let sampleResults;
27
28
 
28
29
  beforeAll(() => {
30
+ global.console.warn = jest.fn();
31
+
29
32
  // Stub out matchMedia for Node.
30
33
  global.matchMedia = function() {
31
34
  return {
@@ -50,7 +53,7 @@ describe('ReportRenderer', () => {
50
53
 
51
54
  describe('renderReport', () => {
52
55
  it('should render a report', () => {
53
- const container = renderer._dom._document.body;
56
+ const container = renderer._dom.document().body;
54
57
  const output = renderer.renderReport(sampleResults, container);
55
58
  assert.ok(output.querySelector('.lh-header-container'), 'has a header');
56
59
  assert.ok(output.querySelector('.lh-report'), 'has report body');
@@ -60,7 +63,7 @@ describe('ReportRenderer', () => {
60
63
  });
61
64
 
62
65
  it('renders additional reports by replacing the existing one', () => {
63
- const container = renderer._dom._document.body;
66
+ const container = renderer._dom.document().body;
64
67
  const oldReport = Array.from(renderer.renderReport(sampleResults, container).children);
65
68
  const newReport = Array.from(renderer.renderReport(sampleResults, container).children);
66
69
  assert.ok(!oldReport.find(node => container.contains(node)), 'old report was removed');
@@ -86,11 +89,11 @@ describe('ReportRenderer', () => {
86
89
  auditRefs: [],
87
90
  };
88
91
 
89
- const container = renderer._dom._document.body;
92
+ const container = renderer._dom.document().body;
90
93
  const output = renderer.renderReport(sampleResultsCopy, container);
91
94
 
92
95
  function isPWAGauge(el) {
93
- return el.querySelector('.lh-gauge__label').textContent === 'Progressive Web App';
96
+ return el.querySelector('.lh-gauge__label').textContent === 'PWA';
94
97
  }
95
98
  function isPluginGauge(el) {
96
99
  return el.querySelector('.lh-gauge__label').textContent === 'Some Plugin';
@@ -126,7 +129,7 @@ describe('ReportRenderer', () => {
126
129
  title: 'Some Plugin',
127
130
  auditRefs: [],
128
131
  };
129
- const container = renderer._dom._document.body;
132
+ const container = renderer._dom.document().body;
130
133
  const output = renderer.renderReport(sampleResultsCopy, container);
131
134
  const scoresHeaderElem = output.querySelector('.lh-scores-header');
132
135
 
@@ -140,7 +143,7 @@ describe('ReportRenderer', () => {
140
143
  });
141
144
 
142
145
  it('should not mutate a report object', () => {
143
- const container = renderer._dom._document.body;
146
+ const container = renderer._dom.document().body;
144
147
  const originalResults = JSON.parse(JSON.stringify(sampleResults));
145
148
  renderer.renderReport(sampleResults, container);
146
149
  assert.deepStrictEqual(sampleResults, originalResults);
@@ -148,13 +151,13 @@ describe('ReportRenderer', () => {
148
151
 
149
152
  it('renders no warning section when no lighthouseRunWarnings occur', () => {
150
153
  const warningResults = Object.assign({}, sampleResults, {runWarnings: []});
151
- const container = renderer._dom._document.body;
154
+ const container = renderer._dom.document().body;
152
155
  const output = renderer.renderReport(warningResults, container);
153
156
  assert.strictEqual(output.querySelector('.lh-warnings--toplevel'), null);
154
157
  });
155
158
 
156
159
  it('renders a warning section', () => {
157
- const container = renderer._dom._document.body;
160
+ const container = renderer._dom.document().body;
158
161
  const output = renderer.renderReport(sampleResults, container);
159
162
 
160
163
  const warningEls = output.querySelectorAll('.lh-warnings--toplevel > ul > li');
@@ -165,7 +168,7 @@ describe('ReportRenderer', () => {
165
168
  const warningResults = Object.assign({}, sampleResults, {
166
169
  runWarnings: ['[I am a link](https://example.com/)'],
167
170
  });
168
- const container = renderer._dom._document.body;
171
+ const container = renderer._dom.document().body;
169
172
  const output = renderer.renderReport(warningResults, container);
170
173
 
171
174
  const warningEls = output.querySelectorAll('.lh-warnings--toplevel ul li a');
@@ -179,17 +182,15 @@ describe('ReportRenderer', () => {
179
182
  assert.ok(/Generated by Lighthouse \d/.test(footerContent), 'includes lh version');
180
183
  assert.ok(footerContent.match(TIMESTAMP_REGEX), 'includes timestamp');
181
184
 
182
- // Check runtime settings were populated.
183
- const names = Array.from(footer.querySelectorAll('.lh-env__name'));
184
- const descriptions = Array.from(footer.querySelectorAll('.lh-env__description'));
185
- assert.ok(names.length >= 3);
186
- assert.ok(descriptions.length >= 3);
187
-
188
- const descriptionsTxt = descriptions.map(el => el.textContent).join('\n');
189
- expect(descriptionsTxt).toContain('Moto G4');
190
- expect(descriptionsTxt).toContain('RTT');
191
- expect(descriptionsTxt).toMatch(/\dx/);
192
- expect(descriptionsTxt).toContain(sampleResults.userAgent);
185
+ // Check env items were populated.
186
+ const items = Array.from(footer.querySelectorAll('.lh-meta__item'));
187
+ expect(items.length).toBeGreaterThanOrEqual(6);
188
+
189
+ const itemsTxt = items.map(el => `${el.textContent} ${el.title}`).join('\n');
190
+ expect(itemsTxt).toContain('Moto G4');
191
+ expect(itemsTxt).toContain('RTT');
192
+ expect(itemsTxt).toMatch(/\dx/);
193
+ expect(itemsTxt).toContain(sampleResults.environment.networkUserAgent);
193
194
  });
194
195
  });
195
196
 
@@ -198,7 +199,7 @@ describe('ReportRenderer', () => {
198
199
  // Make sure we have a channel in the LHR.
199
200
  assert.ok(lhrChannel.length > 2);
200
201
 
201
- const container = renderer._dom._document.body;
202
+ const container = renderer._dom.document().body;
202
203
  const output = renderer.renderReport(sampleResults, container);
203
204
 
204
205
  const DOCS_ORIGINS = ['https://developers.google.com', 'https://web.dev'];
@@ -227,7 +228,7 @@ describe('ReportRenderer', () => {
227
228
 
228
229
  assert.ok(notApplicableCount > 20); // Make sure something's being tested.
229
230
 
230
- const container = renderer._dom._document.body;
231
+ const container = renderer._dom.document().body;
231
232
  const reportElement = renderer.renderReport(sampleResults, container);
232
233
  const notApplicableElementCount = reportElement
233
234
  .querySelectorAll('.lh-audit--notapplicable').length;
@@ -10,6 +10,7 @@
10
10
  import {strict as assert} from 'assert';
11
11
 
12
12
  import jsdom from 'jsdom';
13
+ import {jest} from '@jest/globals';
13
14
 
14
15
  import reportAssets from '../../generator/report-assets.js';
15
16
  import {Util} from '../../renderer/util.js';
@@ -33,13 +34,15 @@ describe('ReportUIFeatures', () => {
33
34
  const categoryRenderer = new CategoryRenderer(dom, detailsRenderer);
34
35
  const renderer = new ReportRenderer(dom, categoryRenderer);
35
36
  const reportUIFeatures = new ReportUIFeatures(dom);
36
- const container = dom.find('main', dom._document);
37
+ const container = dom.find('body', dom.document());
37
38
  renderer.renderReport(lhr, container);
38
39
  reportUIFeatures.initFeatures(lhr);
39
40
  return container;
40
41
  }
41
42
 
42
43
  beforeAll(() => {
44
+ global.console.warn = jest.fn();
45
+
43
46
  // Stub out matchMedia for Node.
44
47
  global.matchMedia = function() {
45
48
  return {
@@ -57,14 +60,20 @@ describe('ReportUIFeatures', () => {
57
60
 
58
61
  global.HTMLElement = document.window.HTMLElement;
59
62
  global.HTMLInputElement = document.window.HTMLInputElement;
63
+ global.HTMLInputElement = document.window.HTMLInputElement;
60
64
 
61
65
  global.window = document.window;
66
+ global.window.requestAnimationFrame = fn => fn();
62
67
  global.window.getComputedStyle = function() {
63
68
  return {
64
69
  marginTop: '10px',
65
70
  height: '10px',
66
71
  };
67
72
  };
73
+ global.window.ResizeObserver = class ResizeObserver {
74
+ observe() { }
75
+ unobserve() { }
76
+ };
68
77
 
69
78
  dom = new DOM(document.window.document);
70
79
  sampleResults = Util.prepareReportResult(sampleResultsOrig);
@@ -256,17 +265,15 @@ describe('ReportUIFeatures', () => {
256
265
  .toThrowError('query #uses-rel-preconnect .lh-3p-filter-input not found');
257
266
  });
258
267
 
259
- it('filter is disabled and checked for when just third party resources', () => {
260
- const filterCheckbox =
261
- dom.find('#render-blocking-resources .lh-3p-filter-input', container);
262
- expect(filterCheckbox.disabled).toEqual(true);
263
- expect(filterCheckbox.checked).toEqual(true);
268
+ it('filter is hidden when just third party resources', () => {
269
+ const filterControl =
270
+ dom.find('#render-blocking-resources .lh-3p-filter', container);
271
+ expect(filterControl.hidden).toEqual(true);
264
272
  });
265
273
 
266
- it('filter is disabled and not checked for just first party resources', () => {
267
- const filterCheckbox = dom.find('#uses-text-compression .lh-3p-filter-input', container);
268
- expect(filterCheckbox.disabled).toEqual(true);
269
- expect(filterCheckbox.checked).toEqual(false);
274
+ it('filter is hidden for just first party resources', () => {
275
+ const filterControl = dom.find('#uses-text-compression .lh-3p-filter', container);
276
+ expect(filterControl.hidden).toEqual(true);
270
277
  });
271
278
  });
272
279
  });
@@ -515,6 +522,9 @@ describe('ReportUIFeatures', () => {
515
522
  expect(render(lhr).querySelector('.lh-button.lh-report-icon--treemap')).toBeTruthy();
516
523
 
517
524
  delete lhr.audits['script-treemap-data'];
525
+ const newAuditRefs = lhr.categories['performance'].auditRefs
526
+ .filter(a => a.id !== 'script-treemap-data');
527
+ lhr.categories['performance'].auditRefs = newAuditRefs;
518
528
  expect(render(lhr).querySelector('.lh-button.lh-report-icon--treemap')).toBeNull();
519
529
  });
520
530
  });
@@ -41,6 +41,6 @@ describe('TextEncoding', () => {
41
41
  await test('Some examples of emoji are 😃, 🧘🏻‍♂️, 🌍, 🍞, 🚗, 📞, 🎉, ♥️, 🍆, and 🏁.');
42
42
  await test('.'.repeat(125183));
43
43
  await test('😃'.repeat(125183));
44
- await test(fs.readFileSync(LH_ROOT + '/lighthouse-treemap/app/debug.json', 'utf-8'));
44
+ await test(fs.readFileSync(LH_ROOT + '/treemap/app/debug.json', 'utf-8'));
45
45
  });
46
46
  });