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.
Files changed (36) hide show
  1. package/dist/report/bundle.esm.js +19 -4
  2. package/dist/report/flow.js +88 -32
  3. package/dist/report/standalone.js +3 -3
  4. package/flow-report/assets/standalone-flow-template.html +2 -4
  5. package/flow-report/src/app.tsx +7 -10
  6. package/flow-report/src/topbar.tsx +4 -7
  7. package/flow-report/src/util.ts +28 -1
  8. package/flow-report/src/wrappers/category-score.tsx +9 -24
  9. package/flow-report/src/wrappers/markdown.tsx +4 -13
  10. package/flow-report/src/wrappers/report.tsx +25 -57
  11. package/flow-report/standalone-flow.tsx +2 -2
  12. package/flow-report/test/setup/env-setup.ts +11 -5
  13. package/flow-report/test/summary/category-test.tsx +3 -6
  14. package/flow-report/test/summary/summary-test.tsx +3 -6
  15. package/flow-report/test/topbar-test.tsx +14 -14
  16. package/flow-report/test/util-test.tsx +38 -1
  17. package/flow-report/test/wrappers/category-score-test.tsx +3 -6
  18. package/flow-report/test/wrappers/markdown-test.tsx +1 -13
  19. package/lighthouse-core/audits/installable-manifest.js +11 -4
  20. package/lighthouse-core/lib/page-functions.js +2 -1
  21. package/lighthouse-core/lib/statistics.js +26 -39
  22. package/package.json +1 -1
  23. package/report/assets/styles.css +2 -2
  24. package/report/renderer/api.js +35 -1
  25. package/report/renderer/category-renderer.js +17 -3
  26. package/report/renderer/components.js +1 -1
  27. package/report/renderer/report-renderer.js +1 -0
  28. package/report/test/renderer/category-renderer-test.js +28 -0
  29. package/report/test/renderer/report-renderer-axe-test.js +2 -2
  30. package/report/test/renderer/report-renderer-test.js +29 -0
  31. package/report/types/report-renderer.d.ts +5 -0
  32. package/shared/localization/locales/en-US.json +3 -0
  33. package/shared/localization/locales/en-XL.json +3 -0
  34. package/third-party/chromium-synchronization/installability-errors-test.js +1 -2
  35. package/third-party/snyk/snapshot.json +4 -1
  36. 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 class="flow-vars lh-root lh-vars"><!-- report populated here --></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>
@@ -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
- <ReportRendererProvider>
56
- <I18nProvider>
57
- <div className={classNames('App', {'App--collapsed': collapsed})} data-testid="App">
58
- <Topbar onMenuClick={() => setCollapsed(c => !c)} />
59
- <Sidebar/>
60
- <Content/>
61
- </div>
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 {useReportRenderer} from './wrappers/report-renderer';
15
+ import {saveFile} from '../../report/renderer/api';
16
16
 
17
- import type {DOM} from '../../report/renderer/dom';
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
- dom.saveFile(blob, filename);
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, dom)}
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}} />
@@ -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 {useReportRenderer} from './report-renderer';
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 {categoryRenderer} = useReportRenderer();
18
- const ref = useRef<HTMLDivElement>(null);
19
-
20
- useLayoutEffect(() => {
21
- const el = categoryRenderer.renderCategoryScore(category, {}, {gatherMode});
22
-
23
- // Category label is displayed in the navigation header.
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 {useReportRenderer} from '../wrappers/report-renderer';
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 {dom} = useReportRenderer();
14
- const ref = useRef<HTMLSpanElement>(null);
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 {ElementScreenshotRenderer} from '../../../report/renderer/element-screenshot-renderer';
11
- import {getFullPageScreenshot} from '../util';
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 any anchor links under the provided element to a flow report link.
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 convertChildAnchors(element: HTMLElement, index: number) {
20
- const links = element.querySelectorAll('a[href]') as NodeListOf<HTMLAnchorElement>;
21
- for (const link of links) {
22
- // Check if the link destination is in the report.
23
- const currentUrl = new URL(location.href);
24
- currentUrl.hash = '';
25
- currentUrl.search = '';
26
- const linkUrl = new URL(link.href);
27
- linkUrl.hash = '';
28
- linkUrl.search = '';
29
- if (currentUrl.href !== linkUrl.href || !link.hash) continue;
30
-
31
- const nodeId = link.hash.substr(1);
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 {dom, reportRenderer} = useReportRenderer();
44
- const ref = useRef<HTMLDivElement>(null);
45
-
46
- useLayoutEffect(() => {
47
- if (ref.current) {
48
- dom.clearComponentCache();
49
- reportRenderer.renderReport(hashState.currentLhr, ref.current);
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} className="lh-root" data-testid="Report"/>
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
- function setupJsDom() {
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
- // Function not implemented in JSDOM.
27
+ // Functions not implemented in JSDOM.
23
28
  window.Element.prototype.scrollIntoView = jest.fn();
24
- }
25
-
26
- global.beforeEach(setupJsDom);
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
- <ReportRendererProvider>
23
- <I18nProvider>
24
- {children}
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
- <ReportRendererProvider>
22
- <I18nProvider>
23
- {children}
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 = jest.fn();
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
- <ReportRendererContext.Provider value={reportRendererValue}>
38
- <I18nProvider>
39
- {children}
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
- <ReportRendererProvider>
22
- <I18nProvider>
23
- {children}
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"/>, {wrapper});
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
- * @description Error message explaining that the manifest does not supply an icon of the correct format.
60
- * @example {192} value0
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'; // eslint-disable-line no-unused-vars
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
  }