lighthouse 8.6.0-dev.20211101 → 8.6.0-dev.20211105
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 +14 -14
- package/dist/report/standalone.js +41 -35
- package/flow-report/.eslintrc.cjs +2 -0
- package/flow-report/assets/standalone-flow-template.html +3 -2
- package/flow-report/assets/styles.css +1 -1
- package/flow-report/src/common.tsx +11 -4
- package/flow-report/src/i18n/i18n.tsx +10 -6
- package/flow-report/src/icons.tsx +30 -9
- package/flow-report/src/sidebar/sidebar.tsx +27 -8
- package/flow-report/src/summary/category.tsx +7 -2
- package/flow-report/src/summary/summary.tsx +9 -3
- package/flow-report/src/util.ts +21 -9
- package/flow-report/src/wrappers/report-renderer.tsx +10 -3
- package/flow-report/src/wrappers/report.tsx +9 -5
- package/flow-report/standalone-flow.tsx +4 -3
- package/flow-report/test/setup/env-setup.ts +1 -1
- package/flow-report/test/sidebar/sidebar-test.tsx +43 -1
- package/lighthouse-cli/.eslintrc.cjs +2 -0
- package/lighthouse-cli/cli-flags.js +1 -1
- package/lighthouse-core/audits/audit.js +1 -1
- package/lighthouse-core/computed/resource-summary.js +1 -1
- package/lighthouse-core/config/default-config.js +0 -2
- package/lighthouse-core/gather/connections/cri.js +1 -1
- package/lighthouse-core/lib/proto-preprocessor.js +14 -2
- package/lighthouse-core/lib/url-shim.js +1 -1
- package/lighthouse-core/util-commonjs.js +24 -9
- package/package.json +3 -3
- package/report/.eslintrc.cjs +2 -0
- package/report/assets/standalone-template.html +2 -3
- package/report/assets/styles.css +227 -251
- package/report/assets/templates.html +19 -29
- 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 +22 -18
- package/report/renderer/components.js +46 -79
- package/report/renderer/crc-details-renderer.js +15 -12
- 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/open-tab.js +9 -3
- package/report/renderer/performance-category-renderer.js +31 -19
- package/report/renderer/report-renderer.js +34 -10
- package/report/renderer/report-ui-features.js +36 -33
- package/report/renderer/swap-locale-feature.js +3 -3
- package/report/renderer/topbar-features.js +52 -48
- package/report/renderer/util.js +25 -9
- package/report/test/clients/bundle-test.js +8 -0
- package/report/test/renderer/category-renderer-test.js +2 -2
- package/report/test/renderer/performance-category-renderer-test.js +1 -1
- 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 +17 -10
- package/report/test-assets/faux-psi-template.html +2 -7
- package/report/test-assets/faux-psi.js +20 -39
- package/report/types/html-renderer.d.ts +2 -0
- package/report/types/report-renderer.d.ts +23 -0
- package/shared/localization/locales/ar-XB.json +0 -15
- package/shared/localization/locales/ar.json +0 -15
- package/shared/localization/locales/bg.json +0 -15
- package/shared/localization/locales/ca.json +0 -15
- package/shared/localization/locales/cs.json +0 -15
- package/shared/localization/locales/da.json +0 -15
- package/shared/localization/locales/de.json +0 -15
- package/shared/localization/locales/el.json +0 -15
- package/shared/localization/locales/en-GB.json +0 -15
- package/shared/localization/locales/en-US.json +18 -15
- package/shared/localization/locales/en-XA.json +0 -15
- package/shared/localization/locales/en-XL.json +18 -15
- package/shared/localization/locales/es-419.json +0 -15
- package/shared/localization/locales/es.json +0 -15
- package/shared/localization/locales/fi.json +0 -15
- package/shared/localization/locales/fil.json +0 -15
- package/shared/localization/locales/fr.json +0 -15
- package/shared/localization/locales/he.json +0 -15
- package/shared/localization/locales/hi.json +0 -15
- package/shared/localization/locales/hr.json +0 -15
- package/shared/localization/locales/hu.json +0 -15
- package/shared/localization/locales/id.json +0 -15
- package/shared/localization/locales/it.json +0 -15
- package/shared/localization/locales/ja.json +0 -15
- package/shared/localization/locales/ko.json +0 -15
- package/shared/localization/locales/lt.json +0 -15
- package/shared/localization/locales/lv.json +0 -15
- package/shared/localization/locales/nl.json +0 -15
- package/shared/localization/locales/no.json +0 -15
- package/shared/localization/locales/pl.json +0 -15
- package/shared/localization/locales/pt-PT.json +0 -15
- package/shared/localization/locales/pt.json +0 -15
- package/shared/localization/locales/ro.json +0 -15
- package/shared/localization/locales/ru.json +0 -15
- package/shared/localization/locales/sk.json +0 -15
- package/shared/localization/locales/sl.json +0 -15
- package/shared/localization/locales/sr-Latn.json +0 -15
- package/shared/localization/locales/sr.json +0 -15
- package/shared/localization/locales/sv.json +0 -15
- package/shared/localization/locales/ta.json +0 -15
- package/shared/localization/locales/te.json +0 -15
- package/shared/localization/locales/th.json +0 -15
- package/shared/localization/locales/tr.json +0 -15
- package/shared/localization/locales/uk.json +0 -15
- package/shared/localization/locales/vi.json +0 -15
- package/shared/localization/locales/zh-HK.json +0 -15
- package/shared/localization/locales/zh-TW.json +0 -15
- package/shared/localization/locales/zh.json +0 -15
- package/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js +0 -96
|
@@ -24,15 +24,13 @@ 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} */
|
|
32
30
|
this.topbarEl; // eslint-disable-line no-unused-expressions
|
|
33
31
|
/** @type {HTMLElement} */
|
|
34
|
-
this.
|
|
35
|
-
/** @type {HTMLElement} */
|
|
32
|
+
this.categoriesEl; // eslint-disable-line no-unused-expressions
|
|
33
|
+
/** @type {HTMLElement?} */
|
|
36
34
|
this.stickyHeaderEl; // eslint-disable-line no-unused-expressions
|
|
37
35
|
/** @type {HTMLElement} */
|
|
38
36
|
this.highlightEl; // eslint-disable-line no-unused-expressions
|
|
@@ -40,7 +38,6 @@ export class TopbarFeatures {
|
|
|
40
38
|
this.onKeyUp = this.onKeyUp.bind(this);
|
|
41
39
|
this.onCopy = this.onCopy.bind(this);
|
|
42
40
|
this.collapseAllDetails = this.collapseAllDetails.bind(this);
|
|
43
|
-
this._updateStickyHeaderOnScroll = this._updateStickyHeaderOnScroll.bind(this);
|
|
44
41
|
}
|
|
45
42
|
|
|
46
43
|
/**
|
|
@@ -48,33 +45,15 @@ export class TopbarFeatures {
|
|
|
48
45
|
*/
|
|
49
46
|
enable(lhr) {
|
|
50
47
|
this.lhr = lhr;
|
|
51
|
-
this.
|
|
52
|
-
this.
|
|
48
|
+
this._dom.rootEl.addEventListener('keyup', this.onKeyUp);
|
|
49
|
+
this._dom.document().addEventListener('copy', this.onCopy);
|
|
53
50
|
this._dropDownMenu.setup(this.onDropDownMenuClick);
|
|
54
51
|
this._setUpCollapseDetailsAfterPrinting();
|
|
55
52
|
|
|
56
|
-
const topbarLogo = this._dom.find('.lh-topbar__logo', this.
|
|
53
|
+
const topbarLogo = this._dom.find('.lh-topbar__logo', this._dom.rootEl);
|
|
57
54
|
topbarLogo.addEventListener('click', () => toggleDarkTheme(this._dom));
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
if (Object.keys(this.lhr.categories).length >= 2) {
|
|
61
|
-
this._setupStickyHeaderElements();
|
|
62
|
-
const containerEl = this._dom.find('.lh-container', this._document);
|
|
63
|
-
const elToAddScrollListener = this._getScrollParent(containerEl);
|
|
64
|
-
elToAddScrollListener.addEventListener('scroll', this._updateStickyHeaderOnScroll);
|
|
65
|
-
|
|
66
|
-
// Use ResizeObserver where available.
|
|
67
|
-
// TODO: there is an issue with incorrect position numbers and, as a result, performance
|
|
68
|
-
// issues due to layout thrashing.
|
|
69
|
-
// See https://github.com/GoogleChrome/lighthouse/pull/9023/files#r288822287 for details.
|
|
70
|
-
// For now, limit to DevTools.
|
|
71
|
-
if (this._dom.isDevTools()) {
|
|
72
|
-
const resizeObserver = new window.ResizeObserver(this._updateStickyHeaderOnScroll);
|
|
73
|
-
resizeObserver.observe(containerEl);
|
|
74
|
-
} else {
|
|
75
|
-
window.addEventListener('resize', this._updateStickyHeaderOnScroll);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
56
|
+
this._setupStickyHeader();
|
|
78
57
|
}
|
|
79
58
|
|
|
80
59
|
/**
|
|
@@ -112,7 +91,7 @@ export class TopbarFeatures {
|
|
|
112
91
|
try {
|
|
113
92
|
this._reportUIFeatures._saveFile(new Blob([htmlStr], {type: 'text/html'}));
|
|
114
93
|
} catch (e) {
|
|
115
|
-
this._dom.fireEventOn('lh-log', this.
|
|
94
|
+
this._dom.fireEventOn('lh-log', this._dom.document(), {
|
|
116
95
|
cmd: 'error', msg: 'Could not export as HTML. ' + e.message,
|
|
117
96
|
});
|
|
118
97
|
}
|
|
@@ -152,7 +131,7 @@ export class TopbarFeatures {
|
|
|
152
131
|
e.preventDefault();
|
|
153
132
|
e.clipboardData.setData('text/plain', JSON.stringify(this.lhr, null, 2));
|
|
154
133
|
|
|
155
|
-
this._dom.fireEventOn('lh-log', this.
|
|
134
|
+
this._dom.fireEventOn('lh-log', this._dom.document(), {
|
|
156
135
|
cmd: 'log', msg: 'Report JSON copied to clipboard',
|
|
157
136
|
});
|
|
158
137
|
}
|
|
@@ -164,28 +143,28 @@ export class TopbarFeatures {
|
|
|
164
143
|
* Copies the report JSON to the clipboard (if supported by the browser).
|
|
165
144
|
*/
|
|
166
145
|
onCopyButtonClick() {
|
|
167
|
-
this._dom.fireEventOn('lh-analytics', this.
|
|
146
|
+
this._dom.fireEventOn('lh-analytics', this._dom.document(), {
|
|
168
147
|
cmd: 'send',
|
|
169
148
|
fields: {hitType: 'event', eventCategory: 'report', eventAction: 'copy'},
|
|
170
149
|
});
|
|
171
150
|
|
|
172
151
|
try {
|
|
173
|
-
if (this.
|
|
152
|
+
if (this._dom.document().queryCommandSupported('copy')) {
|
|
174
153
|
this._copyAttempt = true;
|
|
175
154
|
|
|
176
155
|
// Note: In Safari 10.0.1, execCommand('copy') returns true if there's
|
|
177
156
|
// a valid text selection on the page. See http://caniuse.com/#feat=clipboard.
|
|
178
|
-
if (!this.
|
|
157
|
+
if (!this._dom.document().execCommand('copy')) {
|
|
179
158
|
this._copyAttempt = false; // Prevent event handler from seeing this as a copy attempt.
|
|
180
159
|
|
|
181
|
-
this._dom.fireEventOn('lh-log', this.
|
|
160
|
+
this._dom.fireEventOn('lh-log', this._dom.document(), {
|
|
182
161
|
cmd: 'warn', msg: 'Your browser does not support copy to clipboard.',
|
|
183
162
|
});
|
|
184
163
|
}
|
|
185
164
|
}
|
|
186
165
|
} catch (e) {
|
|
187
166
|
this._copyAttempt = false;
|
|
188
|
-
this._dom.fireEventOn('lh-log', this.
|
|
167
|
+
this._dom.fireEventOn('lh-log', this._dom.document(), {cmd: 'log', msg: e.message});
|
|
189
168
|
}
|
|
190
169
|
}
|
|
191
170
|
|
|
@@ -206,7 +185,7 @@ export class TopbarFeatures {
|
|
|
206
185
|
* open a `<details>` element.
|
|
207
186
|
*/
|
|
208
187
|
expandAllDetails() {
|
|
209
|
-
const details = this._dom.findAll('.lh-categories details', this.
|
|
188
|
+
const details = this._dom.findAll('.lh-categories details', this._dom.rootEl);
|
|
210
189
|
details.map(detail => detail.open = true);
|
|
211
190
|
}
|
|
212
191
|
|
|
@@ -215,7 +194,7 @@ export class TopbarFeatures {
|
|
|
215
194
|
* open a `<details>` element.
|
|
216
195
|
*/
|
|
217
196
|
collapseAllDetails() {
|
|
218
|
-
const details = this._dom.findAll('.lh-categories details', this.
|
|
197
|
+
const details = this._dom.findAll('.lh-categories details', this._dom.rootEl);
|
|
219
198
|
details.map(detail => detail.open = false);
|
|
220
199
|
}
|
|
221
200
|
|
|
@@ -235,7 +214,7 @@ export class TopbarFeatures {
|
|
|
235
214
|
/**
|
|
236
215
|
* Finds the first scrollable ancestor of `element`. Falls back to the document.
|
|
237
216
|
* @param {Element} element
|
|
238
|
-
* @return {
|
|
217
|
+
* @return {Element | Document}
|
|
239
218
|
*/
|
|
240
219
|
_getScrollParent(element) {
|
|
241
220
|
const {overflowY} = window.getComputedStyle(element);
|
|
@@ -273,24 +252,49 @@ export class TopbarFeatures {
|
|
|
273
252
|
}
|
|
274
253
|
}
|
|
275
254
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
this.
|
|
279
|
-
this.
|
|
255
|
+
_setupStickyHeader() {
|
|
256
|
+
// Cache these elements to avoid qSA on each onscroll.
|
|
257
|
+
this.topbarEl = this._dom.find('div.lh-topbar', this._dom.rootEl);
|
|
258
|
+
this.categoriesEl = this._dom.find('div.lh-categories', this._dom.rootEl);
|
|
259
|
+
|
|
260
|
+
// Defer behind rAF to avoid forcing layout.
|
|
261
|
+
window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
|
|
262
|
+
// Only present in the DOM if it'll be used (>=2 categories)
|
|
263
|
+
try {
|
|
264
|
+
this.stickyHeaderEl = this._dom.find('div.lh-sticky-header', this._dom.rootEl);
|
|
265
|
+
} catch {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
280
268
|
|
|
281
|
-
|
|
282
|
-
|
|
269
|
+
// Highlighter will be absolutely positioned at first gauge, then transformed on scroll.
|
|
270
|
+
this.highlightEl = this._dom.createChildOf(this.stickyHeaderEl, 'div', 'lh-highlighter');
|
|
271
|
+
|
|
272
|
+
// Update sticky header visibility and highlight when page scrolls/resizes.
|
|
273
|
+
const scrollParent = this._getScrollParent(this._dom.rootEl);
|
|
274
|
+
// The 'scroll' handler must be should be on {Element | Document}...
|
|
275
|
+
scrollParent.addEventListener('scroll', () => this._updateStickyHeader());
|
|
276
|
+
// However resizeObserver needs an element, *not* the document.
|
|
277
|
+
const resizeTarget = scrollParent instanceof window.Document
|
|
278
|
+
? document.documentElement
|
|
279
|
+
: scrollParent;
|
|
280
|
+
new window.ResizeObserver(() => this._updateStickyHeader()).observe(resizeTarget);
|
|
281
|
+
}));
|
|
283
282
|
}
|
|
284
283
|
|
|
285
|
-
|
|
286
|
-
|
|
284
|
+
/**
|
|
285
|
+
* Toggle visibility and update highlighter position
|
|
286
|
+
*/
|
|
287
|
+
_updateStickyHeader() {
|
|
288
|
+
if (!this.stickyHeaderEl) return;
|
|
289
|
+
|
|
290
|
+
// Show sticky header when the main 5 gauges clear the topbar.
|
|
287
291
|
const topbarBottom = this.topbarEl.getBoundingClientRect().bottom;
|
|
288
|
-
const
|
|
289
|
-
const showStickyHeader = topbarBottom >=
|
|
292
|
+
const categoriesTop = this.categoriesEl.getBoundingClientRect().top;
|
|
293
|
+
const showStickyHeader = topbarBottom >= categoriesTop;
|
|
290
294
|
|
|
291
295
|
// Highlight mini gauge when section is in view.
|
|
292
296
|
// In view = the last category that starts above the middle of the window.
|
|
293
|
-
const categoryEls = Array.from(this.
|
|
297
|
+
const categoryEls = Array.from(this._dom.rootEl.querySelectorAll('.lh-category'));
|
|
294
298
|
const categoriesAboveTheMiddle =
|
|
295
299
|
categoryEls.filter(el => el.getBoundingClientRect().top - window.innerHeight / 2 < 0);
|
|
296
300
|
const highlightIndex =
|
package/report/renderer/util.js
CHANGED
|
@@ -38,7 +38,11 @@ const listOfTlds = [
|
|
|
38
38
|
'web', 'spb', 'blog', 'jus', 'kiev', 'mil', 'wi', 'qc', 'ca', 'bel', 'on',
|
|
39
39
|
];
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
class Util {
|
|
42
|
+
/** @type {I18n<typeof UIStrings>} */
|
|
43
|
+
// @ts-expect-error: Is set in report renderer.
|
|
44
|
+
static i18n = null;
|
|
45
|
+
|
|
42
46
|
static get PASS_THRESHOLD() {
|
|
43
47
|
return PASS_THRESHOLD;
|
|
44
48
|
}
|
|
@@ -417,7 +421,7 @@ export class Util {
|
|
|
417
421
|
throttling.downloadThroughputKbps === 1.6 * 1024 * 0.9 &&
|
|
418
422
|
throttling.uploadThroughputKbps === 750 * 0.9;
|
|
419
423
|
};
|
|
420
|
-
summary =
|
|
424
|
+
summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom;
|
|
421
425
|
break;
|
|
422
426
|
}
|
|
423
427
|
case 'simulate': {
|
|
@@ -429,7 +433,7 @@ export class Util {
|
|
|
429
433
|
const isSlow4G = () => {
|
|
430
434
|
return rttMs === 150 && throughputKbps === 1.6 * 1024;
|
|
431
435
|
};
|
|
432
|
-
summary = isSlow4G() ?
|
|
436
|
+
summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom;
|
|
433
437
|
break;
|
|
434
438
|
}
|
|
435
439
|
default:
|
|
@@ -554,14 +558,10 @@ Util.getUniqueSuffix = (() => {
|
|
|
554
558
|
};
|
|
555
559
|
})();
|
|
556
560
|
|
|
557
|
-
/** @type {I18n<typeof Util['UIStrings']>} */
|
|
558
|
-
// @ts-expect-error: Is set in report renderer.
|
|
559
|
-
Util.i18n = null;
|
|
560
|
-
|
|
561
561
|
/**
|
|
562
562
|
* Report-renderer-specific strings.
|
|
563
563
|
*/
|
|
564
|
-
|
|
564
|
+
const UIStrings = {
|
|
565
565
|
/** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */
|
|
566
566
|
varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://web.dev/performance-scoring/) directly from these metrics.',
|
|
567
567
|
/** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */
|
|
@@ -661,6 +661,22 @@ Util.UIStrings = {
|
|
|
661
661
|
|
|
662
662
|
/** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */
|
|
663
663
|
throttlingProvided: 'Provided by environment',
|
|
664
|
+
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */
|
|
665
|
+
show: 'Show',
|
|
666
|
+
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */
|
|
667
|
+
hide: 'Hide',
|
|
668
|
+
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */
|
|
669
|
+
expandView: 'Expand view',
|
|
670
|
+
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */
|
|
671
|
+
collapseView: 'Collapse view',
|
|
672
|
+
/** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */
|
|
673
|
+
runtimeSlow4g: 'Slow 4G throttling',
|
|
674
|
+
/** Label indicating that Lighthouse throttled the page using custom throttling settings. */
|
|
675
|
+
runtimeCustom: 'Custom throttling',
|
|
664
676
|
};
|
|
677
|
+
Util.UIStrings = UIStrings;
|
|
665
678
|
|
|
666
|
-
export
|
|
679
|
+
export {
|
|
680
|
+
Util,
|
|
681
|
+
UIStrings,
|
|
682
|
+
};
|
|
@@ -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,16 +23,23 @@ 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
|
|
|
28
31
|
global.window = global.self = window;
|
|
32
|
+
global.window.requestAnimationFrame = fn => fn();
|
|
29
33
|
// Stub out matchMedia for Node.
|
|
30
34
|
global.self.matchMedia = function() {
|
|
31
35
|
return {
|
|
32
36
|
addListener: function() {},
|
|
33
37
|
};
|
|
34
38
|
};
|
|
39
|
+
global.window.ResizeObserver = class ResizeObserver {
|
|
40
|
+
observe() { }
|
|
41
|
+
unobserve() { }
|
|
42
|
+
};
|
|
35
43
|
});
|
|
36
44
|
|
|
37
45
|
afterAll(() => {
|
|
@@ -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-
|
|
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');
|
|
@@ -192,7 +192,7 @@ describe('PerfCategoryRenderer', () => {
|
|
|
192
192
|
|
|
193
193
|
it('renders the passed audits', () => {
|
|
194
194
|
const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
|
|
195
|
-
const passedSection = categoryDOM.querySelector('.lh-
|
|
195
|
+
const passedSection = categoryDOM.querySelector('.lh-clump--passed');
|
|
196
196
|
|
|
197
197
|
const passedAudits = category.auditRefs.filter(audit =>
|
|
198
198
|
!audit.group &&
|
|
@@ -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 {
|
|
@@ -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
|
|
260
|
-
const
|
|
261
|
-
dom.find('#render-blocking-resources .lh-3p-filter
|
|
262
|
-
expect(
|
|
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
|
|
267
|
-
const
|
|
268
|
-
expect(
|
|
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
|
});
|
|
@@ -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>
|
|
@@ -26,43 +26,25 @@ const lighthouseRenderer = window['report'];
|
|
|
26
26
|
for (const [tabId, lhr] of Object.entries(lhrs)) {
|
|
27
27
|
await distinguishLHR(lhr, tabId);
|
|
28
28
|
|
|
29
|
-
const container = document.querySelector(
|
|
29
|
+
const container = document.querySelector(`section#${tabId}`);
|
|
30
30
|
if (!container) throw new Error('Unexpected DOM. Bailing.');
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const dom = new lighthouseRenderer.DOM(doc);
|
|
46
|
-
return new lighthouseRenderer.ReportRenderer(dom);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const renderer = getRenderer(reportContainer.ownerDocument);
|
|
50
|
-
reportContainer.classList.add('lh-root', 'lh-vars');
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
renderer.renderReport(lhrData, reportContainer);
|
|
54
|
-
// TODO: handle topbar removal better
|
|
55
|
-
// TODO: display warnings if appropriate.
|
|
56
|
-
for (const el of reportContainer.querySelectorAll('.lh-topbar, .lh-warnings')) {
|
|
57
|
-
el.setAttribute('hidden', 'true');
|
|
32
|
+
try {
|
|
33
|
+
const reportRootEl = lighthouseRenderer.renderReport(lhr, {
|
|
34
|
+
omitTopbar: true,
|
|
35
|
+
disableAutoDarkModeAndFireworks: true,
|
|
36
|
+
});
|
|
37
|
+
// TODO: display warnings if appropriate.
|
|
38
|
+
for (const el of reportRootEl.querySelectorAll('.lh-warnings')) {
|
|
39
|
+
el.setAttribute('hidden', 'true');
|
|
40
|
+
}
|
|
41
|
+
container.append(reportRootEl);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.error(e);
|
|
44
|
+
container.textContent = 'Error: LHR failed to render.';
|
|
58
45
|
}
|
|
59
|
-
const features = new lighthouseRenderer.ReportUIFeatures(renderer._dom);
|
|
60
|
-
features.initFeatures(lhrData);
|
|
61
|
-
} catch (e) {
|
|
62
|
-
console.error(e);
|
|
63
|
-
reportContainer.textContent = 'Error: LHR failed to render.';
|
|
64
46
|
}
|
|
65
|
-
}
|
|
47
|
+
})();
|
|
66
48
|
|
|
67
49
|
|
|
68
50
|
/**
|
|
@@ -72,7 +54,6 @@ function renderLHReport(lhrData, reportContainer) {
|
|
|
72
54
|
* @param {string} tabId
|
|
73
55
|
*/
|
|
74
56
|
async function distinguishLHR(lhr, tabId) {
|
|
75
|
-
lhr.categories.performance.title += ` ${tabId}`; // for easier identification
|
|
76
57
|
if (tabId === 'desktop') {
|
|
77
58
|
lhr.categories.performance.score = 0.81;
|
|
78
59
|
}
|
|
@@ -101,15 +82,15 @@ async function decorateScreenshot(datauri, tabId) {
|
|
|
101
82
|
img.src = datauri;
|
|
102
83
|
});
|
|
103
84
|
const c = document.createElement('canvas');
|
|
104
|
-
c.width = img.width;
|
|
105
|
-
c.height = img.height;
|
|
85
|
+
c.width = tabId === 'desktop' ? 280 : img.width;
|
|
86
|
+
c.height = tabId === 'desktop' ? 194 : img.height;
|
|
106
87
|
|
|
107
88
|
const ctx = c.getContext('2d');
|
|
108
89
|
if (!ctx) throw new Error();
|
|
109
|
-
ctx.drawImage(img, 0, 0);
|
|
110
|
-
ctx.font = `${
|
|
90
|
+
ctx.drawImage(img, 0, 0, c.width, c.height);
|
|
91
|
+
ctx.font = `${c.width / 2}px serif`;
|
|
111
92
|
ctx.textAlign = 'center';
|
|
112
93
|
ctx.globalAlpha = 0.7;
|
|
113
|
-
ctx.fillText(tabId === 'mobile' ? '📱' : '💻',
|
|
94
|
+
ctx.fillText(tabId === 'mobile' ? '📱' : '💻', c.width / 2, Math.min(c.height / 2, 700));
|
|
114
95
|
return c.toDataURL();
|
|
115
96
|
}
|
|
@@ -8,6 +8,7 @@ import AuditDetails from '../../types/lhr/audit-details';
|
|
|
8
8
|
import {FormattedIcu as FormattedIcu_, IcuMessage as IcuMessage_} from '../../types/lhr/i18n';
|
|
9
9
|
import LHResult from '../../types/lhr/lhr';
|
|
10
10
|
import ReportResult_ from './report-result';
|
|
11
|
+
import Renderer_ from './report-renderer';
|
|
11
12
|
import * as Settings from '../../types/lhr/settings';
|
|
12
13
|
import Treemap_ from '../../types/lhr/treemap';
|
|
13
14
|
|
|
@@ -16,6 +17,7 @@ declare global {
|
|
|
16
17
|
module LH {
|
|
17
18
|
export import Result = LHResult;
|
|
18
19
|
export import ReportResult = ReportResult_;
|
|
20
|
+
export import Renderer = Renderer_;
|
|
19
21
|
export import Locale = Settings.Locale;
|
|
20
22
|
export type IcuMessage = IcuMessage_;
|
|
21
23
|
export type FormattedIcu<T> = FormattedIcu_<T>;
|