lighthouse 8.6.0-dev.20211102 → 8.6.0-dev.20211103

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 (33) hide show
  1. package/dist/report/flow.js +8 -8
  2. package/dist/report/standalone.js +40 -34
  3. package/flow-report/assets/standalone-flow-template.html +3 -2
  4. package/flow-report/src/icons.tsx +9 -0
  5. package/flow-report/src/sidebar/sidebar.tsx +18 -5
  6. package/flow-report/src/wrappers/report-renderer.tsx +1 -0
  7. package/flow-report/src/wrappers/report.tsx +2 -3
  8. package/flow-report/standalone-flow.tsx +4 -3
  9. package/flow-report/test/sidebar/sidebar-test.tsx +43 -1
  10. package/package.json +1 -1
  11. package/report/assets/standalone-template.html +2 -3
  12. package/report/assets/styles.css +1 -1
  13. package/report/clients/bundle.js +1 -0
  14. package/report/clients/standalone.js +6 -13
  15. package/report/renderer/api.js +32 -0
  16. package/report/renderer/category-renderer.js +1 -1
  17. package/report/renderer/components.js +26 -26
  18. package/report/renderer/dom.js +16 -1
  19. package/report/renderer/drop-down-menu.js +2 -2
  20. package/report/renderer/element-screenshot-renderer.js +6 -6
  21. package/report/renderer/features-util.js +1 -1
  22. package/report/renderer/report-renderer.js +32 -9
  23. package/report/renderer/report-ui-features.js +34 -30
  24. package/report/renderer/swap-locale-feature.js +3 -3
  25. package/report/renderer/topbar-features.js +19 -21
  26. package/report/test/clients/bundle-test.js +3 -0
  27. package/report/test/renderer/report-renderer-axe-test.js +5 -2
  28. package/report/test/renderer/report-renderer-test.js +13 -10
  29. package/report/test/renderer/report-ui-features-test.js +4 -1
  30. package/report/test-assets/faux-psi-template.html +2 -7
  31. package/report/test-assets/faux-psi.js +15 -34
  32. package/report/types/html-renderer.d.ts +2 -0
  33. package/report/types/report-renderer.d.ts +23 -0
@@ -18,7 +18,7 @@
18
18
  /**
19
19
  * @typedef InstallOverlayFeatureParams
20
20
  * @property {DOM} dom
21
- * @property {Element} reportEl
21
+ * @property {Element} rootEl
22
22
  * @property {Element} overlayContainerEl
23
23
  * @property {LH.Audit.Details.FullPageScreenshot} fullPageScreenshot
24
24
  */
@@ -135,7 +135,7 @@ export class ElementScreenshotRenderer {
135
135
  * @param {LH.Audit.Details.FullPageScreenshot['screenshot']} screenshot
136
136
  */
137
137
  static installFullPageScreenshot(el, screenshot) {
138
- el.style.setProperty('--element-screenshot-url', `url(${screenshot.data})`);
138
+ el.style.setProperty('--element-screenshot-url', `url('${screenshot.data}')`);
139
139
  }
140
140
 
141
141
  /**
@@ -143,14 +143,14 @@ export class ElementScreenshotRenderer {
143
143
  * @param {InstallOverlayFeatureParams} opts
144
144
  */
145
145
  static installOverlayFeature(opts) {
146
- const {dom, reportEl, overlayContainerEl, fullPageScreenshot} = opts;
146
+ const {dom, rootEl, overlayContainerEl, fullPageScreenshot} = opts;
147
147
  const screenshotOverlayClass = 'lh-screenshot-overlay--enabled';
148
148
  // Don't install the feature more than once.
149
- if (reportEl.classList.contains(screenshotOverlayClass)) return;
150
- reportEl.classList.add(screenshotOverlayClass);
149
+ if (rootEl.classList.contains(screenshotOverlayClass)) return;
150
+ rootEl.classList.add(screenshotOverlayClass);
151
151
 
152
152
  // Add a single listener to the provided element to handle all clicks within (event delegation).
153
- reportEl.addEventListener('click', e => {
153
+ rootEl.addEventListener('click', e => {
154
154
  const target = /** @type {?HTMLElement} */ (e.target);
155
155
  if (!target) return;
156
156
  // Only activate the overlay for clicks on the screenshot *preview* of an element, not the full-size too.
@@ -14,7 +14,7 @@
14
14
  * @param {boolean} [force]
15
15
  */
16
16
  export function toggleDarkTheme(dom, force) {
17
- const el = dom.find('.lh-vars', dom.document());
17
+ const el = dom.rootEl;
18
18
  // This seems unnecessary, but in DevTools, passing "undefined" as the second
19
19
  // parameter acts like passing "false".
20
20
  // https://github.com/ChromeDevTools/devtools-frontend/blob/dd6a6d4153647c2a4203c327c595692c5e0a4256/front_end/dom_extension/DOMExtension.js#L809-L819
@@ -36,22 +36,43 @@ export class ReportRenderer {
36
36
  constructor(dom) {
37
37
  /** @type {DOM} */
38
38
  this._dom = dom;
39
+ /** @type {LH.Renderer.Options} */
40
+ this._opts = {};
39
41
  }
40
42
 
41
43
  /**
42
44
  * @param {LH.Result} lhr
43
- * @param {Element} container Parent element to render the report into.
45
+ * @param {HTMLElement?} rootEl Report root element containing the report
46
+ * @param {LH.Renderer.Options=} opts
44
47
  * @return {!Element}
45
48
  */
46
- renderReport(lhr, container) {
49
+ renderReport(lhr, rootEl, opts) {
50
+ // Allow legacy report rendering API
51
+ if (!this._dom.rootEl && rootEl) {
52
+ console.warn('Please adopt the new report API in renderer/api.js.');
53
+ const closestRoot = rootEl.closest('.lh-root');
54
+ if (closestRoot) {
55
+ this._dom.rootEl = /** @type {HTMLElement} */ (closestRoot);
56
+ } else {
57
+ rootEl.classList.add('lh-root', 'lh-vars');
58
+ this._dom.rootEl = rootEl;
59
+ }
60
+ } else if (this._dom.rootEl && rootEl) {
61
+ // Handle legacy flow-report case
62
+ this._dom.rootEl = rootEl;
63
+ }
64
+ if (opts) {
65
+ this._opts = opts;
66
+ }
67
+
47
68
  this._dom.setLighthouseChannel(lhr.configSettings.channel || 'unknown');
48
69
 
49
70
  const report = Util.prepareReportResult(lhr);
50
71
 
51
- container.textContent = ''; // Remove previous report.
52
- container.appendChild(this._renderReport(report));
72
+ this._dom.rootEl.textContent = ''; // Remove previous report.
73
+ this._dom.rootEl.appendChild(this._renderReport(report));
53
74
 
54
- return container;
75
+ return this._dom.rootEl;
55
76
  }
56
77
 
57
78
  /**
@@ -197,7 +218,7 @@ export class ReportRenderer {
197
218
  gaugeWrapperEl.addEventListener('click', e => {
198
219
  if (!gaugeWrapperEl.matches('[href^="#"]')) return;
199
220
  const selector = gaugeWrapperEl.getAttribute('href');
200
- const reportRoot = gaugeWrapperEl.closest('.lh-vars');
221
+ const reportRoot = this._dom.rootEl;
201
222
  if (!selector || !reportRoot) return;
202
223
  const destEl = this._dom.find(selector, reportRoot);
203
224
  e.preventDefault();
@@ -301,9 +322,11 @@ export class ReportRenderer {
301
322
 
302
323
  const reportFragment = this._dom.createFragment();
303
324
  reportFragment.append(this._dom.createComponent('styles'));
304
- const topbarDocumentFragment = this._renderReportTopbar(report);
305
325
 
306
- reportFragment.appendChild(topbarDocumentFragment);
326
+ if (!this._opts.omitTopbar) {
327
+ reportFragment.appendChild(this._renderReportTopbar(report));
328
+ }
329
+
307
330
  reportFragment.appendChild(reportContainer);
308
331
  reportContainer.appendChild(headerContainer);
309
332
  reportContainer.appendChild(reportSection);
@@ -311,7 +334,7 @@ export class ReportRenderer {
311
334
 
312
335
  if (fullPageScreenshot) {
313
336
  ElementScreenshotRenderer.installFullPageScreenshot(
314
- reportContainer, fullPageScreenshot.screenshot);
337
+ this._dom.rootEl, fullPageScreenshot.screenshot);
315
338
  }
316
339
 
317
340
  return reportFragment;
@@ -40,16 +40,17 @@ function getTableRows(tableEl) {
40
40
  export class ReportUIFeatures {
41
41
  /**
42
42
  * @param {DOM} dom
43
+ * @param {LH.Renderer.Options} opts
43
44
  */
44
- constructor(dom) {
45
+ constructor(dom, opts = {}) {
45
46
  /** @type {LH.Result} */
46
47
  this.json; // eslint-disable-line no-unused-expressions
47
48
  /** @type {DOM} */
48
49
  this._dom = dom;
49
- /** @type {Document} */
50
- this._document = this._dom.document();
51
- this._topbar = new TopbarFeatures(this, dom);
52
50
 
51
+ this._opts = opts;
52
+
53
+ this._topbar = opts.omitTopbar ? null : new TopbarFeatures(this, dom);
53
54
  this.onMediaQueryChange = this.onMediaQueryChange.bind(this);
54
55
  }
55
56
 
@@ -61,16 +62,19 @@ export class ReportUIFeatures {
61
62
  initFeatures(lhr) {
62
63
  this.json = lhr;
63
64
 
64
- this._topbar.enable(lhr);
65
- this._topbar.resetUIState();
65
+ if (this._topbar) {
66
+ this._topbar.enable(lhr);
67
+ this._topbar.resetUIState();
68
+ }
66
69
  this._setupMediaQueryListeners();
67
70
  this._setupThirdPartyFilter();
68
- this._setupElementScreenshotOverlay(this._dom.find('.lh-container', this._document));
71
+ this._setupElementScreenshotOverlay(this._dom.rootEl);
69
72
 
70
73
  let turnOffTheLights = false;
71
74
  // Do not query the system preferences for DevTools - DevTools should only apply dark theme
72
75
  // if dark is selected in the settings panel.
73
- if (!this._dom.isDevTools() && window.matchMedia('(prefers-color-scheme: dark)').matches) {
76
+ const disableDarkMode = this._dom.isDevTools() || this._opts.disableAutoDarkModeAndFireworks;
77
+ if (!disableDarkMode && window.matchMedia('(prefers-color-scheme: dark)').matches) {
74
78
  turnOffTheLights = true;
75
79
  }
76
80
 
@@ -94,7 +98,7 @@ export class ReportUIFeatures {
94
98
  const hasMetricError = lhr.categories.performance && lhr.categories.performance.auditRefs
95
99
  .some(audit => Boolean(audit.group === 'metrics' && lhr.audits[audit.id].errorMessage));
96
100
  if (hasMetricError) {
97
- const toggleInputEl = this._dom.find('input.lh-metrics-toggle__input', this._document);
101
+ const toggleInputEl = this._dom.find('input.lh-metrics-toggle__input', this._dom.rootEl);
98
102
  toggleInputEl.checked = true;
99
103
  }
100
104
 
@@ -109,7 +113,7 @@ export class ReportUIFeatures {
109
113
  }
110
114
 
111
115
  // Fill in all i18n data.
112
- for (const node of this._dom.findAll('[data-i18n]', this._dom.document())) {
116
+ for (const node of this._dom.findAll('[data-i18n]', this._dom.rootEl)) {
113
117
  // These strings are guaranteed to (at least) have a default English string in Util.UIStrings,
114
118
  // so this cannot be undefined as long as `report-ui-features.data-i18n` test passes.
115
119
  const i18nKey = node.getAttribute('data-i18n');
@@ -119,18 +123,15 @@ export class ReportUIFeatures {
119
123
  }
120
124
 
121
125
  /**
122
- * @param {{container?: Element, text: string, icon?: string, onClick: () => void}} opts
126
+ * @param {{text: string, icon?: string, onClick: () => void}} opts
123
127
  */
124
128
  addButton(opts) {
125
- // report-ui-features doesn't have a reference to the root report el, and PSI has
126
- // 2 reports on the page (and not even attached to DOM when installFeatures is called..)
127
- // so we need a container option to specify where the element should go.
128
- const metricsEl = this._document.querySelector('.lh-audit-group--metrics');
129
- const containerEl = opts.container || metricsEl;
130
- if (!containerEl) return;
129
+ // Use qSA directly to as we don't want to throw (if this element is missing).
130
+ const metricsEl = this._dom.rootEl.querySelector('.lh-audit-group--metrics');
131
+ if (!metricsEl) return;
131
132
 
132
- let buttonsEl = containerEl.querySelector('.lh-buttons');
133
- if (!buttonsEl) buttonsEl = this._dom.createChildOf(containerEl, 'div', 'lh-buttons');
133
+ let buttonsEl = metricsEl.querySelector('.lh-buttons');
134
+ if (!buttonsEl) buttonsEl = this._dom.createChildOf(metricsEl, 'div', 'lh-buttons');
134
135
 
135
136
  const classes = [
136
137
  'lh-button',
@@ -150,8 +151,10 @@ export class ReportUIFeatures {
150
151
  * @return {string}
151
152
  */
152
153
  getReportHtml() {
153
- this._topbar.resetUIState();
154
- return this._document.documentElement.outerHTML;
154
+ if (this._topbar) {
155
+ this._topbar.resetUIState();
156
+ }
157
+ return `<!doctype html><body>${this._dom.rootEl.outerHTML}`;
155
158
  }
156
159
 
157
160
  /**
@@ -163,7 +166,7 @@ export class ReportUIFeatures {
163
166
  }
164
167
 
165
168
  _enableFireworks() {
166
- const scoresContainer = this._dom.find('.lh-scores-container', this._document);
169
+ const scoresContainer = this._dom.find('.lh-scores-container', this._dom.rootEl);
167
170
  scoresContainer.classList.add('lh-score100');
168
171
  scoresContainer.addEventListener('click', _ => {
169
172
  scoresContainer.classList.toggle('lh-fireworks-paused');
@@ -183,7 +186,9 @@ export class ReportUIFeatures {
183
186
  * be in their closed state (not opened) and the templates should be unstamped.
184
187
  */
185
188
  _resetUIState() {
186
- this._topbar.resetUIState();
189
+ if (this._topbar) {
190
+ this._topbar.resetUIState();
191
+ }
187
192
  }
188
193
 
189
194
  /**
@@ -191,8 +196,7 @@ export class ReportUIFeatures {
191
196
  * @param {MediaQueryList|MediaQueryListEvent} mql
192
197
  */
193
198
  onMediaQueryChange(mql) {
194
- const root = this._dom.find('.lh-root', this._document);
195
- root.classList.toggle('lh-narrow', mql.matches);
199
+ this._dom.rootEl.classList.toggle('lh-narrow', mql.matches);
196
200
  }
197
201
 
198
202
  _setupThirdPartyFilter() {
@@ -209,7 +213,7 @@ export class ReportUIFeatures {
209
213
  ];
210
214
 
211
215
  // Get all tables with a text url column.
212
- const tables = Array.from(this._document.querySelectorAll('table.lh-table'));
216
+ const tables = Array.from(this._dom.rootEl.querySelectorAll('table.lh-table'));
213
217
  const tablesWithUrls = tables
214
218
  .filter(el =>
215
219
  el.querySelector('td.lh-table-column--url, td.lh-table-column--source-location'))
@@ -275,9 +279,9 @@ export class ReportUIFeatures {
275
279
  }
276
280
 
277
281
  /**
278
- * @param {Element} el
282
+ * @param {Element} rootEl
279
283
  */
280
- _setupElementScreenshotOverlay(el) {
284
+ _setupElementScreenshotOverlay(rootEl) {
281
285
  const fullPageScreenshot =
282
286
  this.json.audits['full-page-screenshot'] &&
283
287
  this.json.audits['full-page-screenshot'].details &&
@@ -287,8 +291,8 @@ export class ReportUIFeatures {
287
291
 
288
292
  ElementScreenshotRenderer.installOverlayFeature({
289
293
  dom: this._dom,
290
- reportEl: el,
291
- overlayContainerEl: el,
294
+ rootEl: rootEl,
295
+ overlayContainerEl: rootEl,
292
296
  fullPageScreenshot,
293
297
  });
294
298
  }
@@ -33,17 +33,17 @@ export class SwapLocaleFeature {
33
33
  throw new Error('missing icuMessagePaths');
34
34
  }
35
35
 
36
- this._dom.find('.lh-tools-locale', this._dom.document()).classList.remove('lh-hidden');
36
+ this._dom.find('.lh-tools-locale', this._dom.rootEl).classList.remove('lh-hidden');
37
37
 
38
38
  const currentLocale = this._reportUIFeatures.json.configSettings.locale;
39
39
 
40
- const containerEl = this._dom.find('.lh-tools-locale__selector-wrapper', this._dom.document());
40
+ const containerEl = this._dom.find('.lh-tools-locale__selector-wrapper', this._dom.rootEl);
41
41
  containerEl.removeAttribute('aria-hidden');
42
42
  const selectEl = this._dom.createChildOf(containerEl, 'select', 'lh-locale-selector');
43
43
  selectEl.name = 'lh-locale-list';
44
44
  selectEl.setAttribute('role', 'menuitem');
45
45
 
46
- const toggleEl = this._dom.find('.lh-tool-locale__button', this._dom.document());
46
+ const toggleEl = this._dom.find('.lh-tool-locale__button', this._dom.rootEl);
47
47
  toggleEl.addEventListener('click', () => {
48
48
  toggleEl.classList.toggle('lh-active');
49
49
  });
@@ -24,8 +24,6 @@ export class TopbarFeatures {
24
24
  this.lhr; // eslint-disable-line no-unused-expressions
25
25
  this._reportUIFeatures = reportUIFeatures;
26
26
  this._dom = dom;
27
- /** @type {Document} */
28
- this._document = this._dom.document();
29
27
  this._dropDownMenu = new DropDownMenu(this._dom);
30
28
  this._copyAttempt = false;
31
29
  /** @type {HTMLElement} */
@@ -48,19 +46,19 @@ export class TopbarFeatures {
48
46
  */
49
47
  enable(lhr) {
50
48
  this.lhr = lhr;
51
- this._document.addEventListener('keyup', this.onKeyUp);
52
- this._document.addEventListener('copy', this.onCopy);
49
+ this._dom.rootEl.addEventListener('keyup', this.onKeyUp);
50
+ this._dom.document().addEventListener('copy', this.onCopy);
53
51
  this._dropDownMenu.setup(this.onDropDownMenuClick);
54
52
  this._setUpCollapseDetailsAfterPrinting();
55
53
 
56
- const topbarLogo = this._dom.find('.lh-topbar__logo', this._document);
54
+ const topbarLogo = this._dom.find('.lh-topbar__logo', this._dom.rootEl);
57
55
  topbarLogo.addEventListener('click', () => toggleDarkTheme(this._dom));
58
56
 
59
57
  // There is only a sticky header when at least 2 categories are present.
60
58
  if (Object.keys(this.lhr.categories).length >= 2) {
61
59
  this._setupStickyHeaderElements();
62
- const containerEl = this._dom.find('.lh-container', this._document);
63
- const elToAddScrollListener = this._getScrollParent(containerEl);
60
+ const reportRootEl = this._dom.rootEl;
61
+ const elToAddScrollListener = this._getScrollParent(reportRootEl);
64
62
  elToAddScrollListener.addEventListener('scroll', this._updateStickyHeaderOnScroll);
65
63
 
66
64
  // Use ResizeObserver where available.
@@ -70,7 +68,7 @@ export class TopbarFeatures {
70
68
  // For now, limit to DevTools.
71
69
  if (this._dom.isDevTools()) {
72
70
  const resizeObserver = new window.ResizeObserver(this._updateStickyHeaderOnScroll);
73
- resizeObserver.observe(containerEl);
71
+ resizeObserver.observe(reportRootEl);
74
72
  } else {
75
73
  window.addEventListener('resize', this._updateStickyHeaderOnScroll);
76
74
  }
@@ -112,7 +110,7 @@ export class TopbarFeatures {
112
110
  try {
113
111
  this._reportUIFeatures._saveFile(new Blob([htmlStr], {type: 'text/html'}));
114
112
  } catch (e) {
115
- this._dom.fireEventOn('lh-log', this._document, {
113
+ this._dom.fireEventOn('lh-log', this._dom.document(), {
116
114
  cmd: 'error', msg: 'Could not export as HTML. ' + e.message,
117
115
  });
118
116
  }
@@ -152,7 +150,7 @@ export class TopbarFeatures {
152
150
  e.preventDefault();
153
151
  e.clipboardData.setData('text/plain', JSON.stringify(this.lhr, null, 2));
154
152
 
155
- this._dom.fireEventOn('lh-log', this._document, {
153
+ this._dom.fireEventOn('lh-log', this._dom.document(), {
156
154
  cmd: 'log', msg: 'Report JSON copied to clipboard',
157
155
  });
158
156
  }
@@ -164,28 +162,28 @@ export class TopbarFeatures {
164
162
  * Copies the report JSON to the clipboard (if supported by the browser).
165
163
  */
166
164
  onCopyButtonClick() {
167
- this._dom.fireEventOn('lh-analytics', this._document, {
165
+ this._dom.fireEventOn('lh-analytics', this._dom.document(), {
168
166
  cmd: 'send',
169
167
  fields: {hitType: 'event', eventCategory: 'report', eventAction: 'copy'},
170
168
  });
171
169
 
172
170
  try {
173
- if (this._document.queryCommandSupported('copy')) {
171
+ if (this._dom.document().queryCommandSupported('copy')) {
174
172
  this._copyAttempt = true;
175
173
 
176
174
  // Note: In Safari 10.0.1, execCommand('copy') returns true if there's
177
175
  // a valid text selection on the page. See http://caniuse.com/#feat=clipboard.
178
- if (!this._document.execCommand('copy')) {
176
+ if (!this._dom.document().execCommand('copy')) {
179
177
  this._copyAttempt = false; // Prevent event handler from seeing this as a copy attempt.
180
178
 
181
- this._dom.fireEventOn('lh-log', this._document, {
179
+ this._dom.fireEventOn('lh-log', this._dom.document(), {
182
180
  cmd: 'warn', msg: 'Your browser does not support copy to clipboard.',
183
181
  });
184
182
  }
185
183
  }
186
184
  } catch (e) {
187
185
  this._copyAttempt = false;
188
- this._dom.fireEventOn('lh-log', this._document, {cmd: 'log', msg: e.message});
186
+ this._dom.fireEventOn('lh-log', this._dom.document(), {cmd: 'log', msg: e.message});
189
187
  }
190
188
  }
191
189
 
@@ -206,7 +204,7 @@ export class TopbarFeatures {
206
204
  * open a `<details>` element.
207
205
  */
208
206
  expandAllDetails() {
209
- const details = this._dom.findAll('.lh-categories details', this._document);
207
+ const details = this._dom.findAll('.lh-categories details', this._dom.rootEl);
210
208
  details.map(detail => detail.open = true);
211
209
  }
212
210
 
@@ -215,7 +213,7 @@ export class TopbarFeatures {
215
213
  * open a `<details>` element.
216
214
  */
217
215
  collapseAllDetails() {
218
- const details = this._dom.findAll('.lh-categories details', this._document);
216
+ const details = this._dom.findAll('.lh-categories details', this._dom.rootEl);
219
217
  details.map(detail => detail.open = false);
220
218
  }
221
219
 
@@ -274,9 +272,9 @@ export class TopbarFeatures {
274
272
  }
275
273
 
276
274
  _setupStickyHeaderElements() {
277
- this.topbarEl = this._dom.find('div.lh-topbar', this._document);
278
- this.scoreScaleEl = this._dom.find('div.lh-scorescale', this._document);
279
- this.stickyHeaderEl = this._dom.find('div.lh-sticky-header', this._document);
275
+ this.topbarEl = this._dom.find('div.lh-topbar', this._dom.rootEl);
276
+ this.scoreScaleEl = this._dom.find('div.lh-scorescale', this._dom.rootEl);
277
+ this.stickyHeaderEl = this._dom.find('div.lh-sticky-header', this._dom.rootEl);
280
278
 
281
279
  // Highlighter will be absolutely positioned at first gauge, then transformed on scroll.
282
280
  this.highlightEl = this._dom.createChildOf(this.stickyHeaderEl, 'div', 'lh-highlighter');
@@ -290,7 +288,7 @@ export class TopbarFeatures {
290
288
 
291
289
  // Highlight mini gauge when section is in view.
292
290
  // In view = the last category that starts above the middle of the window.
293
- const categoryEls = Array.from(this._document.querySelectorAll('.lh-category'));
291
+ const categoryEls = Array.from(this._dom.rootEl.querySelectorAll('.lh-category'));
294
292
  const categoriesAboveTheMiddle =
295
293
  categoryEls.filter(el => el.getBoundingClientRect().top - window.innerHeight / 2 < 0);
296
294
  const highlightIndex =
@@ -9,6 +9,7 @@
9
9
  import fs from 'fs';
10
10
 
11
11
  import jsdom from 'jsdom';
12
+ import {jest} from '@jest/globals';
12
13
 
13
14
  import * as lighthouseRenderer from '../../clients/bundle.js';
14
15
  import {LH_ROOT} from '../../../root.js';
@@ -22,6 +23,8 @@ const sampleResultsStr =
22
23
  describe('lighthouseRenderer bundle', () => {
23
24
  let document;
24
25
  beforeAll(() => {
26
+ global.console.warn = jest.fn();
27
+
25
28
  const {window} = new jsdom.JSDOM();
26
29
  document = window.document;
27
30
 
@@ -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);
@@ -47,10 +50,10 @@ describe('ReportRendererAxe', () => {
47
50
  });
48
51
 
49
52
  it('renders without axe violations', async () => {
50
- const container = renderer._dom._document.createElement('main');
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: {
@@ -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,7 +89,7 @@ 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) {
@@ -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');
@@ -196,7 +199,7 @@ describe('ReportRenderer', () => {
196
199
  // Make sure we have a channel in the LHR.
197
200
  assert.ok(lhrChannel.length > 2);
198
201
 
199
- const container = renderer._dom._document.body;
202
+ const container = renderer._dom.document().body;
200
203
  const output = renderer.renderReport(sampleResults, container);
201
204
 
202
205
  const DOCS_ORIGINS = ['https://developers.google.com', 'https://web.dev'];
@@ -225,7 +228,7 @@ describe('ReportRenderer', () => {
225
228
 
226
229
  assert.ok(notApplicableCount > 20); // Make sure something's being tested.
227
230
 
228
- const container = renderer._dom._document.body;
231
+ const container = renderer._dom.document().body;
229
232
  const reportElement = renderer.renderReport(sampleResults, container);
230
233
  const notApplicableElementCount = reportElement
231
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 {
@@ -66,7 +66,6 @@
66
66
  }
67
67
 
68
68
  .tab-panel {
69
- padding: 30px 0;
70
69
  border-top: 1px solid #ccc;
71
70
  }
72
71
 
@@ -101,13 +100,9 @@ body {
101
100
  <label for="tab2">Desktop</label>
102
101
 
103
102
  <div class="tab-panels">
104
- <section id="mobile" class="tab-panel">
105
- <main></main>
106
- </section>
103
+ <section id="mobile" class="tab-panel"></section>
107
104
 
108
- <section id="desktop" class="tab-panel">
109
- <main></main>
110
- </section>
105
+ <section id="desktop" class="tab-panel"></section>
111
106
 
112
107
  </div>
113
108
  </div>