lighthouse 9.0.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +5681 -0
- package/dist/report/bundle.esm.js +36 -7
- package/dist/report/flow.js +97 -30
- package/dist/report/standalone.js +5 -5
- package/flow-report/api.ts +18 -0
- package/flow-report/assets/standalone-flow-template.html +2 -5
- package/flow-report/{standalone-flow.tsx → clients/standalone.ts} +5 -6
- package/flow-report/src/app.tsx +13 -8
- package/flow-report/src/sidebar/flow.tsx +1 -3
- package/flow-report/src/sidebar/sidebar.tsx +1 -3
- package/flow-report/src/topbar.tsx +24 -18
- package/flow-report/src/util.ts +35 -1
- package/flow-report/src/wrappers/category-score.tsx +9 -24
- package/flow-report/src/wrappers/markdown.tsx +4 -13
- package/flow-report/src/wrappers/report.tsx +26 -57
- package/flow-report/src/wrappers/styles.tsx +16 -0
- package/flow-report/test/setup/env-setup.ts +11 -5
- package/flow-report/test/summary/category-test.tsx +3 -6
- package/flow-report/test/summary/summary-test.tsx +3 -6
- package/flow-report/test/topbar-test.tsx +30 -14
- package/flow-report/test/util-test.tsx +38 -1
- package/flow-report/test/wrappers/category-score-test.tsx +3 -6
- package/flow-report/test/wrappers/markdown-test.tsx +1 -13
- package/flow-report/types/flow-report.d.ts +4 -0
- package/lighthouse-cli/cli-flags.js +4 -4
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +0 -0
- package/lighthouse-core/audits/audit.js +39 -7
- package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +1 -26
- package/lighthouse-core/audits/deprecations.js +10 -14
- package/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +5 -4
- package/lighthouse-core/audits/dobetterweb/js-libraries.js +1 -1
- package/lighthouse-core/audits/dobetterweb/no-document-write.js +6 -4
- package/lighthouse-core/audits/dobetterweb/notification-on-start.js +6 -4
- package/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js +6 -4
- package/lighthouse-core/audits/errors-in-console.js +7 -4
- package/lighthouse-core/audits/installable-manifest.js +11 -4
- package/lighthouse-core/audits/no-unload-listeners.js +9 -10
- package/lighthouse-core/audits/seo/canonical.js +0 -14
- package/lighthouse-core/audits/seo/font-size.js +4 -2
- package/lighthouse-core/audits/seo/is-crawlable.js +2 -2
- package/lighthouse-core/audits/third-party-summary.js +2 -2
- package/lighthouse-core/audits/valid-source-maps.js +1 -1
- package/lighthouse-core/audits/violation-audit.js +10 -3
- package/lighthouse-core/computed/js-bundles.js +1 -3
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +3 -3
- package/lighthouse-core/gather/driver/execution-context.js +1 -1
- package/lighthouse-core/gather/gatherers/full-page-screenshot.js +7 -7
- package/lighthouse-core/gather/gatherers/stacks.js +0 -3
- package/lighthouse-core/lib/page-functions.js +2 -1
- package/lighthouse-core/lib/stack-packs.js +12 -8
- package/lighthouse-core/lib/statistics.js +26 -39
- package/lighthouse-core/runner.js +7 -7
- package/lighthouse-core/scripts/package.json +4 -0
- package/package.json +4 -6
- package/report/assets/styles.css +2 -2
- package/report/generator/file-namer.js +12 -1
- package/report/generator/flow-report-assets.js +2 -1
- package/report/generator/report-assets.js +2 -5
- package/report/generator/report-generator.js +0 -1
- package/report/renderer/api.js +49 -1
- package/report/renderer/category-renderer.js +17 -3
- package/report/renderer/components.js +1 -1
- package/report/renderer/report-renderer.js +5 -1
- package/report/renderer/report-ui-features.js +1 -1
- package/report/renderer/swap-locale-feature.js +0 -5
- package/report/test/clients/bundle-test.js +2 -2
- package/report/test/renderer/category-renderer-test.js +28 -0
- package/report/test/renderer/report-renderer-axe-test.js +2 -2
- package/report/test/renderer/report-renderer-test.js +29 -0
- package/report/test/renderer/report-ui-features-test.js +0 -1
- package/report/types/report-renderer.d.ts +7 -0
- package/shared/localization/locales/ar-XB.json +0 -3
- package/shared/localization/locales/ar.json +0 -3
- package/shared/localization/locales/bg.json +17 -20
- package/shared/localization/locales/ca.json +0 -3
- package/shared/localization/locales/cs.json +0 -3
- package/shared/localization/locales/da.json +16 -19
- package/shared/localization/locales/de.json +0 -3
- package/shared/localization/locales/el.json +0 -3
- package/shared/localization/locales/en-GB.json +19 -22
- package/shared/localization/locales/en-US.json +33 -3
- package/shared/localization/locales/en-XA.json +0 -3
- package/shared/localization/locales/en-XL.json +33 -3
- package/shared/localization/locales/es-419.json +17 -20
- package/shared/localization/locales/es.json +0 -3
- package/shared/localization/locales/fi.json +16 -19
- package/shared/localization/locales/fil.json +33 -36
- package/shared/localization/locales/fr.json +16 -19
- package/shared/localization/locales/he.json +16 -19
- package/shared/localization/locales/hi.json +0 -3
- package/shared/localization/locales/hr.json +16 -19
- package/shared/localization/locales/hu.json +16 -19
- package/shared/localization/locales/id.json +16 -19
- package/shared/localization/locales/it.json +37 -40
- package/shared/localization/locales/ja.json +16 -19
- package/shared/localization/locales/ko.json +16 -19
- package/shared/localization/locales/lt.json +17 -20
- package/shared/localization/locales/lv.json +0 -3
- package/shared/localization/locales/nl.json +16 -19
- package/shared/localization/locales/no.json +17 -20
- package/shared/localization/locales/pl.json +16 -19
- package/shared/localization/locales/pt-PT.json +37 -40
- package/shared/localization/locales/pt.json +16 -19
- package/shared/localization/locales/ro.json +0 -3
- package/shared/localization/locales/ru.json +16 -19
- package/shared/localization/locales/sk.json +0 -3
- package/shared/localization/locales/sl.json +0 -3
- package/shared/localization/locales/sr-Latn.json +16 -19
- package/shared/localization/locales/sr.json +16 -19
- package/shared/localization/locales/sv.json +17 -20
- package/shared/localization/locales/ta.json +0 -3
- package/shared/localization/locales/te.json +0 -3
- package/shared/localization/locales/th.json +0 -3
- package/shared/localization/locales/tr.json +0 -3
- package/shared/localization/locales/uk.json +17 -20
- package/shared/localization/locales/vi.json +0 -3
- package/shared/localization/locales/zh-HK.json +0 -3
- package/shared/localization/locales/zh-TW.json +0 -3
- package/shared/localization/locales/zh.json +0 -3
- package/third-party/chromium-synchronization/installability-errors-test.js +1 -2
- package/third-party/snyk/snapshot.json +4 -1
- package/types/externs.d.ts +5 -24
- package/flow-report/src/wrappers/report-renderer.tsx +0 -53
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {render, h} from 'preact';
|
|
8
|
+
|
|
9
|
+
import {App} from './src/app';
|
|
10
|
+
|
|
11
|
+
export function renderFlowReport(
|
|
12
|
+
flowResult: LH.FlowResult,
|
|
13
|
+
root: HTMLElement,
|
|
14
|
+
options?: LH.FlowReportOptions
|
|
15
|
+
) {
|
|
16
|
+
root.classList.add('flow-vars', 'lh-vars', 'lh-root');
|
|
17
|
+
render(h(App, {flowResult, options}), root);
|
|
18
|
+
}
|
|
@@ -21,19 +21,16 @@ limitations under the License.
|
|
|
21
21
|
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
|
|
22
22
|
<link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEhklEQVR4AWJxL/BhIAesev1U5tcflpncgNrKIsqNIwzC9feMpDUzs70kOczMzMzJJcxwCTMzncPMnOwtzBwzMzPb0vRfeZPp0VhPS5I39V5fdiXV1/VD+9QC7OVn9BsyH1XIoEI1PfmJvLFowVV564+34DFUHudbmfDh4kVXh//7XwE+WjS/YfXZe3yr4j2rqj1AIhSB7hZ8ZtPZu/zw8cK523U4wE1/rvPfWrz4zs0m9ZdC9yUJAlASdBAgocRegfF/f3/h/PuaFsxMdwjAR0vm1+06eMMfIrhLqTWqdH4EumU2SPfMhigJAlRQbZrgrRsl9U+Y2DYDFCz3ILC9kiAiqSrMwbWT0nceEnR+9Kggc2zjOJCASDENkg0a5HfZZgDP81CM3CrQs2Z1+o7DJ6ePr8sK0AOCHv5Jjdt3evyYSaZ351VIStIxPRAUtrBYbxC6w+BZ0ivVSBKkIhJhemSyZpfB00EiPO2VjzYkxhcqXQqCWCShGplvi3y0QxqbuBurMjyJeWnkHZuAEgIQGsUBqwrfjZ+IlBgKyRJzVVYF8O6qFWdh86YzQzMrZigYmxAyfvHgLZQ/LC1CbeniW2Hkqr/PH16SgvGuf2/uzNMBwJA/njxizGPtSyAf7EziJCMGRDRdhoAC4PL1A/SrKQMAAQkEfpJAcRQdrBJ7gNwjSpJsdwK+CANBkqa1LgQB4IicV9nYUct7gaxuDJUErQIiEAiMxLVOFlKzIktPpT0ggpdpC/8YAHnxbgkUY4tAAFSR7AAXNyAAWHJrA/kHGjzg5nleuwFO7Nd/IoDw4Pm58+4jNLmYG0wRA5bErc2Mr3Y+dXTDW1VvwqbJkzMCHQ4S1GTCBOIgUHJrGdEwqzR+jAp/o2qAZelUDoQnruEEdDclJI6576AlNVfc+22XN/+Y1vnJD0Yind6UpEEvn/Hqq15EYjCW7jZCJEpnNvDgkyelDjs106kuux2AAXCSobULOWP8mLhYlpoDMK4qAFXJGk+grtH8YXVz5KJblqaG1+VUdTc0I290bmUQAriGITRbdQnom0aoFj8kx1+wMD2ifncAXUQE4SkDqN1hE0jEophs1SUwZAOhUAiMCLwRtamtTZtbbmZErSAUHbSysaoEmnrsakiMiUAURi283gN6wans9oX8rOCrj7/JP35DFD+iQ7Au/K2KE1jzx6ujjUnXFH9KjEq6ZlhsTBICrNLJf47Pv/pkHzvup1w4dmUbEei0+bcXRqJuh5kVARQ8byyYxOwNGr7A87xh1tp8sGT+uMInrwi++Xj7TQz2d27NvwEkrOflAFQGIDA5khASBCGdO2/Z/MnLPwYfv5TFhjW7QhVKAB6afwe2LpFlFsCnlQEosgQgDsdOG1/LKeNqJS4JCSPJ/i+TakwEARor7gER1Iva5JmPOJK0RUqmoPnnlzFCtmIAhAAQEIQRgDaiYPIauNXcnDlRIrWNFY3hm7PG9YRqr7IV7HrCgAC17befjEvRq2nGhAHtBqDpOuI/I1diUUAMYIxEdyejBJqLnNoszGZtfiX/CztGv2mq+sdaAAAAAElFTkSuQmCC">
|
|
23
23
|
<title>Lighthouse Flow Report</title>
|
|
24
|
-
<style>/*%%LIGHTHOUSE_CSS%%*/</style>
|
|
25
24
|
<style>/*%%LIGHTHOUSE_FLOW_CSS%%*/</style>
|
|
26
25
|
<style>body {margin: 0}</style>
|
|
27
26
|
</head>
|
|
28
27
|
<body>
|
|
29
28
|
<noscript>Lighthouse report requires JavaScript. Please enable.</noscript>
|
|
30
29
|
|
|
31
|
-
<main
|
|
30
|
+
<main><!-- report populated here --></main>
|
|
32
31
|
|
|
33
32
|
<script>window.__LIGHTHOUSE_FLOW_JSON__ = %%LIGHTHOUSE_FLOW_JSON%%;</script>
|
|
34
|
-
<script>%%LIGHTHOUSE_FLOW_JAVASCRIPT
|
|
35
|
-
__initLighthouseFlowReport__();
|
|
36
|
-
</script>
|
|
33
|
+
<script>%%LIGHTHOUSE_FLOW_JAVASCRIPT%%</script>
|
|
37
34
|
<script>console.log('window.__LIGHTHOUSE_FLOW_JSON__', __LIGHTHOUSE_FLOW_JSON__);</script>
|
|
38
35
|
</body>
|
|
39
36
|
</html>
|
|
@@ -9,16 +9,15 @@
|
|
|
9
9
|
* The renderer code is bundled and injected into the report HTML along with the JSON report.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import {renderFlowReport} from '../api';
|
|
13
13
|
|
|
14
|
-
import {App} from './src/app';
|
|
15
|
-
|
|
16
|
-
// Used by standalone-flow.html
|
|
17
14
|
function __initLighthouseFlowReport__() {
|
|
18
|
-
// TODO(adamraine): add lh-vars, etc classes programmatically instead of in the HTML template
|
|
19
15
|
const container = document.body.querySelector('main');
|
|
20
16
|
if (!container) throw Error('Container element not found');
|
|
21
|
-
|
|
17
|
+
renderFlowReport(window.__LIGHTHOUSE_FLOW_JSON__, container, {
|
|
18
|
+
getReportHtml: () => document.documentElement.outerHTML,
|
|
19
|
+
});
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
window.__initLighthouseFlowReport__ = __initLighthouseFlowReport__;
|
|
23
|
+
window.__initLighthouseFlowReport__();
|
package/flow-report/src/app.tsx
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {FunctionComponent} from 'preact';
|
|
8
|
-
import {useLayoutEffect, useRef, useState} from 'preact/hooks';
|
|
8
|
+
import {useLayoutEffect, useMemo, useRef, useState} from 'preact/hooks';
|
|
9
9
|
|
|
10
|
-
import {ReportRendererProvider} from './wrappers/report-renderer';
|
|
11
10
|
import {Sidebar} from './sidebar/sidebar';
|
|
12
11
|
import {Summary} from './summary/summary';
|
|
13
|
-
import {classNames, FlowResultContext, useHashState} from './util';
|
|
12
|
+
import {classNames, FlowResultContext, OptionsContext, useHashState} from './util';
|
|
14
13
|
import {Report} from './wrappers/report';
|
|
15
14
|
import {Topbar} from './topbar';
|
|
16
15
|
import {Header} from './header';
|
|
17
16
|
import {I18nProvider} from './i18n/i18n';
|
|
17
|
+
import {Styles} from './wrappers/styles';
|
|
18
18
|
|
|
19
19
|
function getAnchorElement(hashState: LH.FlowResult.HashState|null) {
|
|
20
20
|
if (!hashState || !hashState.anchor) return null;
|
|
@@ -48,19 +48,24 @@ const Content: FunctionComponent = () => {
|
|
|
48
48
|
);
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
export const App: FunctionComponent<{
|
|
51
|
+
export const App: FunctionComponent<{
|
|
52
|
+
flowResult: LH.FlowResult,
|
|
53
|
+
options?: LH.FlowReportOptions
|
|
54
|
+
}> = ({flowResult, options}) => {
|
|
52
55
|
const [collapsed, setCollapsed] = useState(false);
|
|
56
|
+
const optionsValue = useMemo(() => options || {}, [options]);
|
|
53
57
|
return (
|
|
54
|
-
<
|
|
55
|
-
<
|
|
58
|
+
<OptionsContext.Provider value={optionsValue}>
|
|
59
|
+
<FlowResultContext.Provider value={flowResult}>
|
|
56
60
|
<I18nProvider>
|
|
61
|
+
<Styles/>
|
|
57
62
|
<div className={classNames('App', {'App--collapsed': collapsed})} data-testid="App">
|
|
58
63
|
<Topbar onMenuClick={() => setCollapsed(c => !c)} />
|
|
59
64
|
<Sidebar/>
|
|
60
65
|
<Content/>
|
|
61
66
|
</div>
|
|
62
67
|
</I18nProvider>
|
|
63
|
-
</
|
|
64
|
-
</
|
|
68
|
+
</FlowResultContext.Provider>
|
|
69
|
+
</OptionsContext.Provider>
|
|
65
70
|
);
|
|
66
71
|
};
|
|
@@ -48,8 +48,6 @@ export const SidebarFlow: FunctionComponent = () => {
|
|
|
48
48
|
{
|
|
49
49
|
flowResult.steps.map((step, index) => {
|
|
50
50
|
const {lhr, name} = step;
|
|
51
|
-
const url = new URL(location.href);
|
|
52
|
-
url.hash = `#index=${index}`;
|
|
53
51
|
return <>
|
|
54
52
|
{
|
|
55
53
|
lhr.gatherMode === 'navigation' && index !== 0 ?
|
|
@@ -59,7 +57,7 @@ export const SidebarFlow: FunctionComponent = () => {
|
|
|
59
57
|
<SidebarFlowStep
|
|
60
58
|
key={lhr.fetchTime}
|
|
61
59
|
mode={lhr.gatherMode}
|
|
62
|
-
href={
|
|
60
|
+
href={`#index=${index}`}
|
|
63
61
|
label={name}
|
|
64
62
|
isCurrent={index === (hashState && hashState.index)}
|
|
65
63
|
/>
|
|
@@ -17,11 +17,9 @@ const SidebarSummary: FunctionComponent = () => {
|
|
|
17
17
|
const hashState = useHashState();
|
|
18
18
|
const strings = useLocalizedStrings();
|
|
19
19
|
|
|
20
|
-
const url = new URL(location.href);
|
|
21
|
-
url.hash = '#';
|
|
22
20
|
return (
|
|
23
21
|
<a
|
|
24
|
-
href=
|
|
22
|
+
href="#"
|
|
25
23
|
className={classNames('SidebarSummary', {'Sidebar--current': hashState === null})}
|
|
26
24
|
data-testid="SidebarSummary"
|
|
27
25
|
>
|
|
@@ -8,23 +8,16 @@ import {FunctionComponent, JSX} from 'preact';
|
|
|
8
8
|
import {useState} from 'preact/hooks';
|
|
9
9
|
|
|
10
10
|
import {HelpDialog} from './help-dialog';
|
|
11
|
-
import {
|
|
11
|
+
import {getFlowResultFilenamePrefix} from '../../report/generator/file-namer';
|
|
12
12
|
import {useLocalizedStrings} from './i18n/i18n';
|
|
13
13
|
import {HamburgerIcon, InfoIcon} from './icons';
|
|
14
|
-
import {useFlowResult} from './util';
|
|
15
|
-
import {
|
|
14
|
+
import {useFlowResult, useOptions} from './util';
|
|
15
|
+
import {saveFile} from '../../report/renderer/api';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function saveHtml(flowResult: LH.FlowResult, dom: DOM) {
|
|
20
|
-
const htmlStr = document.documentElement.outerHTML;
|
|
17
|
+
function saveHtml(flowResult: LH.FlowResult, htmlStr: string) {
|
|
21
18
|
const blob = new Blob([htmlStr], {type: 'text/html'});
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const name = flowResult.name.replace(/\s/g, '-');
|
|
25
|
-
const filename = getFilenamePrefix(name, lhr.fetchTime);
|
|
26
|
-
|
|
27
|
-
dom.saveFile(blob, filename);
|
|
19
|
+
const filename = getFlowResultFilenamePrefix(flowResult);
|
|
20
|
+
saveFile(blob, filename);
|
|
28
21
|
}
|
|
29
22
|
|
|
30
23
|
/* eslint-disable max-len */
|
|
@@ -78,9 +71,9 @@ const TopbarButton: FunctionComponent<{
|
|
|
78
71
|
export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLButtonElement>}> =
|
|
79
72
|
({onMenuClick}) => {
|
|
80
73
|
const flowResult = useFlowResult();
|
|
81
|
-
const {dom} = useReportRenderer();
|
|
82
74
|
const strings = useLocalizedStrings();
|
|
83
75
|
const [showHelpDialog, setShowHelpDialog] = useState(false);
|
|
76
|
+
const {getReportHtml, saveAsGist} = useOptions();
|
|
84
77
|
|
|
85
78
|
return (
|
|
86
79
|
<div className="Topbar">
|
|
@@ -91,10 +84,23 @@ export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLB
|
|
|
91
84
|
<Logo/>
|
|
92
85
|
</div>
|
|
93
86
|
<div className="Topbar__title">{strings.title}</div>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
87
|
+
{
|
|
88
|
+
getReportHtml &&
|
|
89
|
+
<TopbarButton
|
|
90
|
+
onClick={() => {
|
|
91
|
+
const htmlStr = getReportHtml(flowResult);
|
|
92
|
+
saveHtml(flowResult, htmlStr);
|
|
93
|
+
}}
|
|
94
|
+
label="Button that saves the report as HTML"
|
|
95
|
+
>{strings.save}</TopbarButton>
|
|
96
|
+
}
|
|
97
|
+
{
|
|
98
|
+
saveAsGist &&
|
|
99
|
+
<TopbarButton
|
|
100
|
+
onClick={() => saveAsGist(flowResult)}
|
|
101
|
+
label="Button that saves the report to a gist"
|
|
102
|
+
>{strings.dropdownSaveGist}</TopbarButton>
|
|
103
|
+
}
|
|
98
104
|
<div style={{flexGrow: 1}} />
|
|
99
105
|
<TopbarButton
|
|
100
106
|
onClick={() => setShowHelpDialog(previous => !previous)}
|
package/flow-report/src/util.ts
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {createContext} from 'preact';
|
|
8
|
-
import {useContext, useEffect, useMemo, useState} from 'preact/hooks';
|
|
8
|
+
import {useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'preact/hooks';
|
|
9
9
|
|
|
10
10
|
import type {UIStringsType} from './i18n/ui-strings';
|
|
11
11
|
|
|
12
12
|
const FlowResultContext = createContext<LH.FlowResult|undefined>(undefined);
|
|
13
|
+
const OptionsContext = createContext<LH.FlowReportOptions>({});
|
|
13
14
|
|
|
14
15
|
function getHashParam(param: string): string|null {
|
|
15
16
|
const params = new URLSearchParams(location.hash.replace('#', '?'));
|
|
@@ -121,8 +122,39 @@ function useHashState(): LH.FlowResult.HashState|null {
|
|
|
121
122
|
}, [indexString, flowResult, anchor]);
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Creates a DOM subtree from non-preact code (e.g. LH report renderer).
|
|
127
|
+
* @param renderCallback Callback that renders a DOM subtree.
|
|
128
|
+
* @param inputs Changes to these values will trigger a re-render of the DOM subtree.
|
|
129
|
+
* @return Reference to the element that will contain the DOM subtree.
|
|
130
|
+
*/
|
|
131
|
+
function useExternalRenderer<T extends Element>(
|
|
132
|
+
renderCallback: () => Node,
|
|
133
|
+
inputs?: ReadonlyArray<unknown>
|
|
134
|
+
) {
|
|
135
|
+
const ref = useRef<T>(null);
|
|
136
|
+
|
|
137
|
+
useLayoutEffect(() => {
|
|
138
|
+
if (!ref.current) return;
|
|
139
|
+
|
|
140
|
+
const root = renderCallback();
|
|
141
|
+
ref.current.appendChild(root);
|
|
142
|
+
|
|
143
|
+
return () => {
|
|
144
|
+
if (ref.current?.contains(root)) ref.current.removeChild(root);
|
|
145
|
+
};
|
|
146
|
+
}, inputs);
|
|
147
|
+
|
|
148
|
+
return ref;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function useOptions() {
|
|
152
|
+
return useContext(OptionsContext);
|
|
153
|
+
}
|
|
154
|
+
|
|
124
155
|
export {
|
|
125
156
|
FlowResultContext,
|
|
157
|
+
OptionsContext,
|
|
126
158
|
classNames,
|
|
127
159
|
getScreenDimensions,
|
|
128
160
|
getFullPageScreenshot,
|
|
@@ -131,4 +163,6 @@ export {
|
|
|
131
163
|
useFlowResult,
|
|
132
164
|
useHashParams,
|
|
133
165
|
useHashState,
|
|
166
|
+
useExternalRenderer,
|
|
167
|
+
useOptions,
|
|
134
168
|
};
|
|
@@ -5,37 +5,22 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {FunctionComponent} from 'preact';
|
|
8
|
-
import {useEffect, useLayoutEffect, useRef} from 'preact/hooks';
|
|
9
8
|
|
|
10
|
-
import {
|
|
9
|
+
import {renderCategoryScore} from '../../../report/renderer/api';
|
|
10
|
+
import {useExternalRenderer} from '../util';
|
|
11
11
|
|
|
12
12
|
export const CategoryScore: FunctionComponent<{
|
|
13
13
|
category: LH.ReportResult.Category,
|
|
14
14
|
href: string,
|
|
15
15
|
gatherMode: LH.Result.GatherMode,
|
|
16
16
|
}> = ({category, href, gatherMode}) => {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const label = el.querySelector('.lh-gauge__label,.lh-fraction__label');
|
|
25
|
-
if (label) label.remove();
|
|
26
|
-
|
|
27
|
-
if (ref.current) ref.current.append(el);
|
|
28
|
-
return () => {
|
|
29
|
-
if (ref.current && ref.current.contains(el)) {
|
|
30
|
-
ref.current.removeChild(el);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}, [categoryRenderer, category]);
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
const anchor = ref.current && ref.current.querySelector('a') as HTMLAnchorElement;
|
|
37
|
-
if (anchor) anchor.href = href;
|
|
38
|
-
}, [href]);
|
|
17
|
+
const ref = useExternalRenderer<HTMLDivElement>(() => {
|
|
18
|
+
return renderCategoryScore(category, {
|
|
19
|
+
gatherMode,
|
|
20
|
+
omitLabel: true,
|
|
21
|
+
onPageAnchorRendered: link => link.href = href,
|
|
22
|
+
});
|
|
23
|
+
}, [category, href]);
|
|
39
24
|
|
|
40
25
|
return (
|
|
41
26
|
<div ref={ref} data-testid="CategoryScore"/>
|
|
@@ -5,22 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {FunctionComponent} from 'preact';
|
|
8
|
-
import {useLayoutEffect, useRef} from 'preact/hooks';
|
|
9
8
|
|
|
10
|
-
import {
|
|
9
|
+
import {convertMarkdownCodeSnippets} from '../../../report/renderer/api';
|
|
10
|
+
import {useExternalRenderer} from '../util';
|
|
11
11
|
|
|
12
12
|
export const Markdown: FunctionComponent<{text: string}> = ({text}) => {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
useLayoutEffect(() => {
|
|
17
|
-
if (ref.current) {
|
|
18
|
-
const md = dom.convertMarkdownCodeSnippets(text);
|
|
19
|
-
ref.current.appendChild(md);
|
|
20
|
-
}
|
|
21
|
-
return () => {
|
|
22
|
-
if (ref.current) ref.current.innerHTML = '';
|
|
23
|
-
};
|
|
13
|
+
const ref = useExternalRenderer<HTMLSpanElement>(() => {
|
|
14
|
+
return convertMarkdownCodeSnippets(text);
|
|
24
15
|
}, [text]);
|
|
25
16
|
|
|
26
17
|
return <span ref={ref}/>;
|
|
@@ -5,73 +5,42 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {FunctionComponent} from 'preact';
|
|
8
|
-
import {useLayoutEffect, useRef} from 'preact/hooks';
|
|
9
8
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {useReportRenderer} from './report-renderer';
|
|
9
|
+
import {renderReport} from '../../../report/renderer/api.js';
|
|
10
|
+
import {useExternalRenderer} from '../util';
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* The default behavior of anchor links is not compatible with the flow report's hash navigation.
|
|
16
|
-
* This function converts
|
|
14
|
+
* This function converts a category score anchor link to a flow report link.
|
|
17
15
|
* e.g. <a href="#link"> -> <a href="#index=0&anchor=link">
|
|
18
16
|
*/
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
link.hash = `#index=${index}&anchor=${nodeId}`;
|
|
33
|
-
link.onclick = e => {
|
|
34
|
-
e.preventDefault();
|
|
35
|
-
const el = document.getElementById(nodeId);
|
|
36
|
-
if (el) el.scrollIntoView();
|
|
37
|
-
};
|
|
38
|
-
}
|
|
17
|
+
function convertAnchor(link: HTMLAnchorElement, index: number) {
|
|
18
|
+
// Clear existing event listeners by cloning node.
|
|
19
|
+
const newLink = link.cloneNode(true) as HTMLAnchorElement;
|
|
20
|
+
if (!newLink.hash) return newLink;
|
|
21
|
+
|
|
22
|
+
const nodeId = link.hash.substr(1);
|
|
23
|
+
newLink.hash = `#index=${index}&anchor=${nodeId}`;
|
|
24
|
+
newLink.onclick = e => {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
const el = document.getElementById(nodeId);
|
|
27
|
+
if (el) el.scrollIntoView();
|
|
28
|
+
};
|
|
29
|
+
link.replaceWith(newLink);
|
|
39
30
|
}
|
|
40
31
|
|
|
41
|
-
const Report: FunctionComponent<{hashState: LH.FlowResult.HashState}> =
|
|
32
|
+
export const Report: FunctionComponent<{hashState: LH.FlowResult.HashState}> =
|
|
42
33
|
({hashState}) => {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const fullPageScreenshot = getFullPageScreenshot(hashState.currentLhr);
|
|
52
|
-
if (fullPageScreenshot) {
|
|
53
|
-
ElementScreenshotRenderer.installOverlayFeature({
|
|
54
|
-
dom,
|
|
55
|
-
rootEl: ref.current,
|
|
56
|
-
overlayContainerEl: ref.current,
|
|
57
|
-
fullPageScreenshot,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
const topbar = ref.current.querySelector('.lh-topbar');
|
|
61
|
-
if (topbar) topbar.remove();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return () => {
|
|
65
|
-
if (ref.current) ref.current.textContent = '';
|
|
66
|
-
};
|
|
67
|
-
}, [reportRenderer, hashState]);
|
|
34
|
+
const ref = useExternalRenderer<HTMLDivElement>(() => {
|
|
35
|
+
return renderReport(hashState.currentLhr, {
|
|
36
|
+
disableAutoDarkModeAndFireworks: true,
|
|
37
|
+
omitTopbar: true,
|
|
38
|
+
omitGlobalStyles: true,
|
|
39
|
+
onPageAnchorRendered: link => convertAnchor(link, hashState.index),
|
|
40
|
+
});
|
|
41
|
+
}, [hashState]);
|
|
68
42
|
|
|
69
43
|
return (
|
|
70
|
-
<div ref={ref}
|
|
44
|
+
<div ref={ref} data-testid="Report"/>
|
|
71
45
|
);
|
|
72
46
|
};
|
|
73
|
-
|
|
74
|
-
export {
|
|
75
|
-
convertChildAnchors,
|
|
76
|
-
Report,
|
|
77
|
-
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {FunctionComponent} from 'preact';
|
|
8
|
+
|
|
9
|
+
import {createStylesElement} from '../../../report/renderer/api';
|
|
10
|
+
import {useExternalRenderer} from '../util';
|
|
11
|
+
|
|
12
|
+
export const Styles: FunctionComponent = () => {
|
|
13
|
+
const ref = useExternalRenderer<HTMLDivElement>(createStylesElement);
|
|
14
|
+
|
|
15
|
+
return <div ref={ref}/>;
|
|
16
|
+
};
|
|
@@ -9,18 +9,24 @@ import {JSDOM} from 'jsdom';
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* The jest environment "jsdom" does not work when preact is combined with the report renderer.
|
|
12
|
+
* This sets up our own environment with JSDOM globals.
|
|
12
13
|
*/
|
|
13
|
-
|
|
14
|
+
beforeEach(() => {
|
|
14
15
|
const {window} = new JSDOM(undefined, {
|
|
15
16
|
url: 'file:///Users/example/report.html/',
|
|
16
17
|
});
|
|
17
18
|
global.window = window as any;
|
|
18
19
|
global.document = window.document;
|
|
19
20
|
global.location = window.location;
|
|
21
|
+
global.self = global.window;
|
|
22
|
+
|
|
23
|
+
// Use JSDOM types as necessary.
|
|
20
24
|
global.Blob = window.Blob;
|
|
25
|
+
global.HTMLInputElement = window.HTMLInputElement;
|
|
21
26
|
|
|
22
|
-
//
|
|
27
|
+
// Functions not implemented in JSDOM.
|
|
23
28
|
window.Element.prototype.scrollIntoView = jest.fn();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
global.self.matchMedia = jest.fn<any, any>(() => ({
|
|
30
|
+
addListener: jest.fn(),
|
|
31
|
+
}));
|
|
32
|
+
});
|
|
@@ -11,7 +11,6 @@ import {SummaryTooltip} from '../../src/summary/category';
|
|
|
11
11
|
import {flowResult} from '../sample-flow';
|
|
12
12
|
import {I18nProvider} from '../../src/i18n/i18n';
|
|
13
13
|
import {FlowResultContext} from '../../src/util';
|
|
14
|
-
import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
|
|
15
14
|
|
|
16
15
|
let wrapper: FunctionComponent;
|
|
17
16
|
|
|
@@ -19,11 +18,9 @@ beforeEach(() => {
|
|
|
19
18
|
// Include sample flowResult for locale in I18nProvider.
|
|
20
19
|
wrapper = ({children}) => (
|
|
21
20
|
<FlowResultContext.Provider value={flowResult}>
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
</I18nProvider>
|
|
26
|
-
</ReportRendererProvider>
|
|
21
|
+
<I18nProvider>
|
|
22
|
+
{children}
|
|
23
|
+
</I18nProvider>
|
|
27
24
|
</FlowResultContext.Provider>
|
|
28
25
|
);
|
|
29
26
|
});
|
|
@@ -10,7 +10,6 @@ import {FunctionComponent} from 'preact';
|
|
|
10
10
|
import {I18nProvider} from '../../src/i18n/i18n';
|
|
11
11
|
import {SummaryHeader, SummaryFlowStep} from '../../src/summary/summary';
|
|
12
12
|
import {FlowResultContext} from '../../src/util';
|
|
13
|
-
import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
|
|
14
13
|
import {flowResult} from '../sample-flow';
|
|
15
14
|
|
|
16
15
|
let wrapper: FunctionComponent;
|
|
@@ -18,11 +17,9 @@ let wrapper: FunctionComponent;
|
|
|
18
17
|
beforeEach(() => {
|
|
19
18
|
wrapper = ({children}) => (
|
|
20
19
|
<FlowResultContext.Provider value={flowResult}>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
</I18nProvider>
|
|
25
|
-
</ReportRendererProvider>
|
|
20
|
+
<I18nProvider>
|
|
21
|
+
{children}
|
|
22
|
+
</I18nProvider>
|
|
26
23
|
</FlowResultContext.Provider>
|
|
27
24
|
);
|
|
28
25
|
});
|
|
@@ -8,11 +8,19 @@ import {jest} from '@jest/globals';
|
|
|
8
8
|
import {FunctionComponent} from 'preact';
|
|
9
9
|
import {act, render} from '@testing-library/preact';
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import {FlowResultContext} from '../src/util';
|
|
13
|
-
import {ReportRendererContext} from '../src/wrappers/report-renderer';
|
|
11
|
+
import {FlowResultContext, OptionsContext} from '../src/util';
|
|
14
12
|
import {I18nProvider} from '../src/i18n/i18n';
|
|
15
13
|
|
|
14
|
+
const mockSaveFile = jest.fn();
|
|
15
|
+
jest.unstable_mockModule('../../../report/renderer/api.js', () => ({
|
|
16
|
+
saveFile: mockSaveFile,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
let Topbar: typeof import('../src/topbar').Topbar;
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
Topbar = (await import('../src/topbar')).Topbar;
|
|
22
|
+
});
|
|
23
|
+
|
|
16
24
|
const flowResult = {
|
|
17
25
|
name: 'User flow',
|
|
18
26
|
steps: [{lhr: {
|
|
@@ -23,27 +31,24 @@ const flowResult = {
|
|
|
23
31
|
} as any;
|
|
24
32
|
|
|
25
33
|
let wrapper: FunctionComponent;
|
|
26
|
-
let
|
|
34
|
+
let options: LH.FlowReportOptions;
|
|
27
35
|
|
|
28
36
|
beforeEach(() => {
|
|
29
|
-
mockSaveFile
|
|
30
|
-
|
|
31
|
-
dom: {
|
|
32
|
-
saveFile: mockSaveFile,
|
|
33
|
-
},
|
|
34
|
-
};
|
|
37
|
+
mockSaveFile.mockReset();
|
|
38
|
+
options = {};
|
|
35
39
|
wrapper = ({children}) => (
|
|
36
|
-
<
|
|
37
|
-
<
|
|
40
|
+
<OptionsContext.Provider value={options}>
|
|
41
|
+
<FlowResultContext.Provider value={flowResult}>
|
|
38
42
|
<I18nProvider>
|
|
39
43
|
{children}
|
|
40
44
|
</I18nProvider>
|
|
41
|
-
</
|
|
42
|
-
</
|
|
45
|
+
</FlowResultContext.Provider>
|
|
46
|
+
</OptionsContext.Provider>
|
|
43
47
|
);
|
|
44
48
|
});
|
|
45
49
|
|
|
46
50
|
it('save button opens save dialog for HTML file', async () => {
|
|
51
|
+
options = {getReportHtml: () => '<html></html>'};
|
|
47
52
|
const root = render(<Topbar onMenuClick={() => {}}/>, {wrapper});
|
|
48
53
|
|
|
49
54
|
const saveButton = root.getByText('Save');
|
|
@@ -55,6 +60,17 @@ it('save button opens save dialog for HTML file', async () => {
|
|
|
55
60
|
);
|
|
56
61
|
});
|
|
57
62
|
|
|
63
|
+
it('provides save as gist option if defined', async () => {
|
|
64
|
+
const saveAsGist = jest.fn();
|
|
65
|
+
options = {saveAsGist};
|
|
66
|
+
const root = render(<Topbar onMenuClick={() => {}}/>, {wrapper});
|
|
67
|
+
|
|
68
|
+
const saveButton = root.getByText('Save as Gist');
|
|
69
|
+
saveButton.click();
|
|
70
|
+
|
|
71
|
+
expect(saveAsGist).toHaveBeenCalledWith(flowResult);
|
|
72
|
+
});
|
|
73
|
+
|
|
58
74
|
it('toggles help dialog', async () => {
|
|
59
75
|
const root = render(<Topbar onMenuClick={() => {}}/>, {wrapper});
|
|
60
76
|
|