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.
- package/dist/report/flow.js +8 -8
- package/dist/report/standalone.js +40 -34
- package/flow-report/assets/standalone-flow-template.html +3 -2
- package/flow-report/src/icons.tsx +9 -0
- package/flow-report/src/sidebar/sidebar.tsx +18 -5
- package/flow-report/src/wrappers/report-renderer.tsx +1 -0
- package/flow-report/src/wrappers/report.tsx +2 -3
- package/flow-report/standalone-flow.tsx +4 -3
- package/flow-report/test/sidebar/sidebar-test.tsx +43 -1
- package/package.json +1 -1
- package/report/assets/standalone-template.html +2 -3
- package/report/assets/styles.css +1 -1
- package/report/clients/bundle.js +1 -0
- package/report/clients/standalone.js +6 -13
- package/report/renderer/api.js +32 -0
- package/report/renderer/category-renderer.js +1 -1
- package/report/renderer/components.js +26 -26
- package/report/renderer/dom.js +16 -1
- package/report/renderer/drop-down-menu.js +2 -2
- package/report/renderer/element-screenshot-renderer.js +6 -6
- package/report/renderer/features-util.js +1 -1
- package/report/renderer/report-renderer.js +32 -9
- package/report/renderer/report-ui-features.js +34 -30
- package/report/renderer/swap-locale-feature.js +3 -3
- package/report/renderer/topbar-features.js +19 -21
- package/report/test/clients/bundle-test.js +3 -0
- package/report/test/renderer/report-renderer-axe-test.js +5 -2
- package/report/test/renderer/report-renderer-test.js +13 -10
- package/report/test/renderer/report-ui-features-test.js +4 -1
- package/report/test-assets/faux-psi-template.html +2 -7
- package/report/test-assets/faux-psi.js +15 -34
- package/report/types/html-renderer.d.ts +2 -0
- 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}
|
|
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,
|
|
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 (
|
|
150
|
-
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
52
|
-
|
|
72
|
+
this._dom.rootEl.textContent = ''; // Remove previous report.
|
|
73
|
+
this._dom.rootEl.appendChild(this._renderReport(report));
|
|
53
74
|
|
|
54
|
-
return
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
65
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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 {{
|
|
126
|
+
* @param {{text: string, icon?: string, onClick: () => void}} opts
|
|
123
127
|
*/
|
|
124
128
|
addButton(opts) {
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
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 =
|
|
133
|
-
if (!buttonsEl) buttonsEl = this._dom.createChildOf(
|
|
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
|
|
154
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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}
|
|
282
|
+
* @param {Element} rootEl
|
|
279
283
|
*/
|
|
280
|
-
_setupElementScreenshotOverlay(
|
|
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
|
-
|
|
291
|
-
overlayContainerEl:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
52
|
-
this.
|
|
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.
|
|
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
|
|
63
|
-
const elToAddScrollListener = this._getScrollParent(
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
278
|
-
this.scoreScaleEl = this._dom.find('div.lh-scorescale', this.
|
|
279
|
-
this.stickyHeaderEl = this._dom.find('div.lh-sticky-header', this.
|
|
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.
|
|
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.
|
|
53
|
+
const container = renderer._dom.document().createElement('main');
|
|
51
54
|
const output = renderer.renderReport(sampleResults, container);
|
|
52
55
|
|
|
53
|
-
renderer._dom.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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('
|
|
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>
|