lighthouse 9.0.0-dev.20211115 → 9.0.0-dev.20211119
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/bundle.esm.js +19 -4
- package/dist/report/flow.js +88 -32
- package/dist/report/standalone.js +3 -3
- package/flow-report/assets/standalone-flow-template.html +2 -4
- package/flow-report/src/app.tsx +7 -10
- package/flow-report/src/topbar.tsx +4 -7
- package/flow-report/src/util.ts +28 -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 +25 -57
- package/flow-report/standalone-flow.tsx +2 -2
- 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 +14 -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/lighthouse-core/audits/installable-manifest.js +11 -4
- package/lighthouse-core/lib/page-functions.js +2 -1
- package/lighthouse-core/lib/statistics.js +26 -39
- package/package.json +1 -1
- package/report/assets/styles.css +2 -2
- package/report/renderer/api.js +35 -1
- package/report/renderer/category-renderer.js +17 -3
- package/report/renderer/components.js +1 -1
- package/report/renderer/report-renderer.js +1 -0
- 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/types/report-renderer.d.ts +5 -0
- package/shared/localization/locales/en-US.json +3 -0
- package/shared/localization/locales/en-XL.json +3 -0
- package/third-party/chromium-synchronization/installability-errors-test.js +1 -2
- package/third-party/snyk/snapshot.json +4 -1
- package/flow-report/src/wrappers/report-renderer.tsx +0 -53
|
@@ -28,12 +28,10 @@ limitations under the License.
|
|
|
28
28
|
<body>
|
|
29
29
|
<noscript>Lighthouse report requires JavaScript. Please enable.</noscript>
|
|
30
30
|
|
|
31
|
-
<main
|
|
31
|
+
<main><!-- report populated here --></main>
|
|
32
32
|
|
|
33
33
|
<script>window.__LIGHTHOUSE_FLOW_JSON__ = %%LIGHTHOUSE_FLOW_JSON%%;</script>
|
|
34
|
-
<script>%%LIGHTHOUSE_FLOW_JAVASCRIPT
|
|
35
|
-
__initLighthouseFlowReport__();
|
|
36
|
-
</script>
|
|
34
|
+
<script>%%LIGHTHOUSE_FLOW_JAVASCRIPT%%</script>
|
|
37
35
|
<script>console.log('window.__LIGHTHOUSE_FLOW_JSON__', __LIGHTHOUSE_FLOW_JSON__);</script>
|
|
38
36
|
</body>
|
|
39
37
|
</html>
|
package/flow-report/src/app.tsx
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import {FunctionComponent} from 'preact';
|
|
8
8
|
import {useLayoutEffect, 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
12
|
import {classNames, FlowResultContext, useHashState} from './util';
|
|
@@ -52,15 +51,13 @@ export const App: FunctionComponent<{flowResult: LH.FlowResult}> = ({flowResult}
|
|
|
52
51
|
const [collapsed, setCollapsed] = useState(false);
|
|
53
52
|
return (
|
|
54
53
|
<FlowResultContext.Provider value={flowResult}>
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
</I18nProvider>
|
|
63
|
-
</ReportRendererProvider>
|
|
54
|
+
<I18nProvider>
|
|
55
|
+
<div className={classNames('App', {'App--collapsed': collapsed})} data-testid="App">
|
|
56
|
+
<Topbar onMenuClick={() => setCollapsed(c => !c)} />
|
|
57
|
+
<Sidebar/>
|
|
58
|
+
<Content/>
|
|
59
|
+
</div>
|
|
60
|
+
</I18nProvider>
|
|
64
61
|
</FlowResultContext.Provider>
|
|
65
62
|
);
|
|
66
63
|
};
|
|
@@ -12,11 +12,9 @@ import {getFilenamePrefix} from '../../report/generator/file-namer';
|
|
|
12
12
|
import {useLocalizedStrings} from './i18n/i18n';
|
|
13
13
|
import {HamburgerIcon, InfoIcon} from './icons';
|
|
14
14
|
import {useFlowResult} from './util';
|
|
15
|
-
import {
|
|
15
|
+
import {saveFile} from '../../report/renderer/api';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function saveHtml(flowResult: LH.FlowResult, dom: DOM) {
|
|
17
|
+
function saveHtml(flowResult: LH.FlowResult) {
|
|
20
18
|
const htmlStr = document.documentElement.outerHTML;
|
|
21
19
|
const blob = new Blob([htmlStr], {type: 'text/html'});
|
|
22
20
|
|
|
@@ -24,7 +22,7 @@ function saveHtml(flowResult: LH.FlowResult, dom: DOM) {
|
|
|
24
22
|
const name = flowResult.name.replace(/\s/g, '-');
|
|
25
23
|
const filename = getFilenamePrefix(name, lhr.fetchTime);
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
saveFile(blob, filename);
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
/* eslint-disable max-len */
|
|
@@ -78,7 +76,6 @@ const TopbarButton: FunctionComponent<{
|
|
|
78
76
|
export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLButtonElement>}> =
|
|
79
77
|
({onMenuClick}) => {
|
|
80
78
|
const flowResult = useFlowResult();
|
|
81
|
-
const {dom} = useReportRenderer();
|
|
82
79
|
const strings = useLocalizedStrings();
|
|
83
80
|
const [showHelpDialog, setShowHelpDialog] = useState(false);
|
|
84
81
|
|
|
@@ -92,7 +89,7 @@ export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLB
|
|
|
92
89
|
</div>
|
|
93
90
|
<div className="Topbar__title">{strings.title}</div>
|
|
94
91
|
<TopbarButton
|
|
95
|
-
onClick={() => saveHtml(flowResult
|
|
92
|
+
onClick={() => saveHtml(flowResult)}
|
|
96
93
|
label="Button that saves the report as HTML"
|
|
97
94
|
>{strings.save}</TopbarButton>
|
|
98
95
|
<div style={{flexGrow: 1}} />
|
package/flow-report/src/util.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
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
|
|
|
@@ -121,6 +121,32 @@ function useHashState(): LH.FlowResult.HashState|null {
|
|
|
121
121
|
}, [indexString, flowResult, anchor]);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Creates a DOM subtree from non-preact code (e.g. LH report renderer).
|
|
126
|
+
* @param renderCallback Callback that renders a DOM subtree.
|
|
127
|
+
* @param inputs Changes to these values will trigger a re-render of the DOM subtree.
|
|
128
|
+
* @return Reference to the element that will contain the DOM subtree.
|
|
129
|
+
*/
|
|
130
|
+
function useExternalRenderer<T extends Element>(
|
|
131
|
+
renderCallback: () => Node,
|
|
132
|
+
inputs?: ReadonlyArray<unknown>
|
|
133
|
+
) {
|
|
134
|
+
const ref = useRef<T>(null);
|
|
135
|
+
|
|
136
|
+
useLayoutEffect(() => {
|
|
137
|
+
if (!ref.current) return;
|
|
138
|
+
|
|
139
|
+
const root = renderCallback();
|
|
140
|
+
ref.current.appendChild(root);
|
|
141
|
+
|
|
142
|
+
return () => {
|
|
143
|
+
if (ref.current?.contains(root)) ref.current.removeChild(root);
|
|
144
|
+
};
|
|
145
|
+
}, inputs);
|
|
146
|
+
|
|
147
|
+
return ref;
|
|
148
|
+
}
|
|
149
|
+
|
|
124
150
|
export {
|
|
125
151
|
FlowResultContext,
|
|
126
152
|
classNames,
|
|
@@ -131,4 +157,5 @@ export {
|
|
|
131
157
|
useFlowResult,
|
|
132
158
|
useHashParams,
|
|
133
159
|
useHashState,
|
|
160
|
+
useExternalRenderer,
|
|
134
161
|
};
|
|
@@ -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,41 @@
|
|
|
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
|
-
convertChildAnchors(ref.current, hashState.index);
|
|
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
|
+
onPageAnchorRendered: link => convertAnchor(link, hashState.index),
|
|
39
|
+
});
|
|
40
|
+
}, [hashState]);
|
|
68
41
|
|
|
69
42
|
return (
|
|
70
|
-
<div ref={ref}
|
|
43
|
+
<div ref={ref} data-testid="Report"/>
|
|
71
44
|
);
|
|
72
45
|
};
|
|
73
|
-
|
|
74
|
-
export {
|
|
75
|
-
convertChildAnchors,
|
|
76
|
-
Report,
|
|
77
|
-
};
|
|
@@ -13,12 +13,12 @@ import {render} from 'preact';
|
|
|
13
13
|
|
|
14
14
|
import {App} from './src/app';
|
|
15
15
|
|
|
16
|
-
// Used by standalone-flow.html
|
|
17
16
|
function __initLighthouseFlowReport__() {
|
|
18
|
-
// TODO(adamraine): add lh-vars, etc classes programmatically instead of in the HTML template
|
|
19
17
|
const container = document.body.querySelector('main');
|
|
20
18
|
if (!container) throw Error('Container element not found');
|
|
19
|
+
container.classList.add('flow-vars', 'lh-root', 'lh-vars');
|
|
21
20
|
render(<App flowResult={window.__LIGHTHOUSE_FLOW_JSON__} />, container);
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
window.__initLighthouseFlowReport__ = __initLighthouseFlowReport__;
|
|
24
|
+
window.__initLighthouseFlowReport__();
|
|
@@ -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 {Topbar} from '../src/topbar';
|
|
12
11
|
import {FlowResultContext} from '../src/util';
|
|
13
|
-
import {ReportRendererContext} from '../src/wrappers/report-renderer';
|
|
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,22 +31,14 @@ const flowResult = {
|
|
|
23
31
|
} as any;
|
|
24
32
|
|
|
25
33
|
let wrapper: FunctionComponent;
|
|
26
|
-
let mockSaveFile = jest.fn();
|
|
27
34
|
|
|
28
35
|
beforeEach(() => {
|
|
29
|
-
mockSaveFile
|
|
30
|
-
const reportRendererValue: any = {
|
|
31
|
-
dom: {
|
|
32
|
-
saveFile: mockSaveFile,
|
|
33
|
-
},
|
|
34
|
-
};
|
|
36
|
+
mockSaveFile.mockReset();
|
|
35
37
|
wrapper = ({children}) => (
|
|
36
38
|
<FlowResultContext.Provider value={flowResult}>
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</I18nProvider>
|
|
41
|
-
</ReportRendererContext.Provider>
|
|
39
|
+
<I18nProvider>
|
|
40
|
+
{children}
|
|
41
|
+
</I18nProvider>
|
|
42
42
|
</FlowResultContext.Provider>
|
|
43
43
|
);
|
|
44
44
|
});
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {jest} from '@jest/globals';
|
|
8
|
+
import {render} from '@testing-library/preact';
|
|
8
9
|
import {renderHook} from '@testing-library/preact-hooks';
|
|
9
10
|
import {FunctionComponent} from 'preact';
|
|
10
11
|
import {act} from 'preact/test-utils';
|
|
11
12
|
|
|
12
|
-
import {FlowResultContext, useHashState} from '../src/util';
|
|
13
|
+
import {FlowResultContext, useExternalRenderer, useHashState} from '../src/util';
|
|
13
14
|
import {flowResult} from './sample-flow';
|
|
14
15
|
|
|
15
16
|
let wrapper: FunctionComponent;
|
|
@@ -94,3 +95,39 @@ describe('useHashState', () => {
|
|
|
94
95
|
expect(result.current).toBeNull();
|
|
95
96
|
});
|
|
96
97
|
});
|
|
98
|
+
|
|
99
|
+
describe('useExternalRenderer', () => {
|
|
100
|
+
it('attaches DOM subtree of render callback', () => {
|
|
101
|
+
const Container: FunctionComponent = () => {
|
|
102
|
+
const ref = useExternalRenderer<HTMLDivElement>(() => {
|
|
103
|
+
const el = document.createElement('div');
|
|
104
|
+
el.textContent = 'Some text';
|
|
105
|
+
return el;
|
|
106
|
+
});
|
|
107
|
+
return <div ref={ref}/>;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const root = render(<Container/>);
|
|
111
|
+
|
|
112
|
+
expect(root.getByText('Some text')).toBeTruthy();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('re-renders DOM subtree when input changes', () => {
|
|
116
|
+
const Container: FunctionComponent<{text: string}> = ({text}) => {
|
|
117
|
+
const ref = useExternalRenderer<HTMLDivElement>(() => {
|
|
118
|
+
const el = document.createElement('div');
|
|
119
|
+
el.textContent = text;
|
|
120
|
+
return el;
|
|
121
|
+
}, [text]);
|
|
122
|
+
return <div ref={ref}/>;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const root = render(<Container text="Some text"/>);
|
|
126
|
+
|
|
127
|
+
expect(root.getByText('Some text')).toBeTruthy();
|
|
128
|
+
|
|
129
|
+
root.rerender(<Container text="New text"/>);
|
|
130
|
+
|
|
131
|
+
expect(root.getByText('New text')).toBeTruthy();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -9,7 +9,6 @@ import {render} from '@testing-library/preact';
|
|
|
9
9
|
|
|
10
10
|
import {CategoryScore} from '../../src/wrappers/category-score';
|
|
11
11
|
import {FlowResultContext} from '../../src/util';
|
|
12
|
-
import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
|
|
13
12
|
import {I18nProvider} from '../../src/i18n/i18n';
|
|
14
13
|
import {flowResult} from '../sample-flow';
|
|
15
14
|
|
|
@@ -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
|
});
|
|
@@ -5,24 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {render} from '@testing-library/preact';
|
|
8
|
-
import {FunctionComponent} from 'preact';
|
|
9
8
|
|
|
10
9
|
import {Markdown} from '../../src/wrappers/markdown';
|
|
11
|
-
import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
|
|
12
|
-
|
|
13
|
-
let wrapper: FunctionComponent;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
wrapper = ({children}) => (
|
|
17
|
-
<ReportRendererProvider>
|
|
18
|
-
{children}
|
|
19
|
-
</ReportRendererProvider>
|
|
20
|
-
);
|
|
21
|
-
});
|
|
22
10
|
|
|
23
11
|
describe('Markdown', () => {
|
|
24
12
|
it('renders markdown text', () => {
|
|
25
|
-
const root = render(<Markdown text="Some `fancy` text"
|
|
13
|
+
const root = render(<Markdown text="Some `fancy` text"/>);
|
|
26
14
|
const text = root.getByText(/^Some.*text$/);
|
|
27
15
|
expect(text.innerHTML).toEqual('Some <code>fancy</code> text');
|
|
28
16
|
});
|
|
@@ -21,7 +21,7 @@ const UIStrings = {
|
|
|
21
21
|
'columnValue': 'Failure reason',
|
|
22
22
|
/**
|
|
23
23
|
* @description [ICU Syntax] Label for an audit identifying the number of installability errors found in the page.
|
|
24
|
-
|
|
24
|
+
*/
|
|
25
25
|
'displayValue': `{itemCount, plural,
|
|
26
26
|
=1 {1 reason}
|
|
27
27
|
other {# reasons}
|
|
@@ -56,9 +56,9 @@ const UIStrings = {
|
|
|
56
56
|
'manifest-missing-suitable-icon': `Manifest does not contain a suitable icon - PNG, SVG or WebP format of at least {value0}\xa0px is required, the sizes attribute must be set, and the purpose attribute, if set, must include "any".`,
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
* @description Error message explaining that the manifest does not supply an icon of the correct format.
|
|
60
|
+
* @example {192} value0
|
|
61
|
+
*/
|
|
62
62
|
'no-acceptable-icon': `No supplied icon is at least {value0}\xa0px square in PNG, SVG or WebP format, with the purpose attribute unset or set to "any"`,
|
|
63
63
|
|
|
64
64
|
/** Error message explaining that the icon could not be downloaded. */
|
|
@@ -95,6 +95,8 @@ const UIStrings = {
|
|
|
95
95
|
'warn-not-offline-capable': `Page does not work offline. The page will not be regarded as installable after Chrome 93, stable release August 2021.`,
|
|
96
96
|
/** Error message explaining that Lighthouse failed while detecting a service worker, and directing the user to try again in a new Chrome. */
|
|
97
97
|
'protocol-timeout': `Lighthouse could not determine if there was a service worker. Please try with a newer version of Chrome.`,
|
|
98
|
+
/** Message logged when the web app has been uninstalled o desktop, signalling that the install banner state is being reset. */
|
|
99
|
+
'pipeline-restarted': 'PWA has been uninstalled and installability checks resetting.',
|
|
98
100
|
};
|
|
99
101
|
/* eslint-enable max-len */
|
|
100
102
|
|
|
@@ -146,6 +148,11 @@ class InstallableManifest extends Audit {
|
|
|
146
148
|
continue;
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
// Filter out errorId 'pipeline-restarted' since it only applies when the PWA is uninstalled.
|
|
152
|
+
if (err.errorId === 'pipeline-restarted') {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
149
156
|
// @ts-expect-error errorIds from protocol should match up against the strings dict
|
|
150
157
|
const matchingString = UIStrings[err.errorId];
|
|
151
158
|
|
|
@@ -208,7 +208,8 @@ function computeBenchmarkIndex() {
|
|
|
208
208
|
|
|
209
209
|
while (Date.now() - start < 500) {
|
|
210
210
|
let s = '';
|
|
211
|
-
for (let j = 0; j < 10000; j++) s += 'a';
|
|
211
|
+
for (let j = 0; j < 10000; j++) s += 'a';
|
|
212
|
+
if (s.length === 1) throw new Error('will never happen, but prevents compiler optimizations');
|
|
212
213
|
|
|
213
214
|
iterations++;
|
|
214
215
|
}
|