lighthouse 8.5.1 → 8.6.0-dev.20211013

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 (172) hide show
  1. package/changelog.md +5490 -0
  2. package/flow-report/.eslintrc.cjs +49 -0
  3. package/flow-report/assets/styles.css +272 -9
  4. package/flow-report/jest.config.js +1 -1
  5. package/flow-report/package.json +4 -0
  6. package/flow-report/src/app.tsx +31 -9
  7. package/flow-report/src/common.tsx +86 -7
  8. package/flow-report/src/header.tsx +88 -0
  9. package/flow-report/src/help-dialog.tsx +120 -0
  10. package/flow-report/src/i18n/i18n.tsx +44 -0
  11. package/flow-report/src/i18n/localized-strings.js +11 -0
  12. package/flow-report/src/i18n/ui-strings.js +86 -0
  13. package/flow-report/src/icons.tsx +20 -3
  14. package/flow-report/src/sidebar/sidebar.tsx +11 -16
  15. package/flow-report/src/summary/category.tsx +42 -24
  16. package/flow-report/src/summary/summary.tsx +32 -29
  17. package/flow-report/src/topbar.tsx +54 -4
  18. package/flow-report/src/util.ts +26 -1
  19. package/flow-report/src/wrappers/report-renderer.tsx +1 -1
  20. package/flow-report/src/wrappers/report.tsx +6 -21
  21. package/flow-report/test/app-test.tsx +17 -13
  22. package/flow-report/test/common-test.tsx +89 -0
  23. package/flow-report/test/header-test.tsx +55 -0
  24. package/flow-report/test/sample-flow.ts +17 -0
  25. package/flow-report/test/setup/env-setup.ts +1 -0
  26. package/flow-report/test/sidebar/flow-test.tsx +6 -12
  27. package/flow-report/test/sidebar/sidebar-test.tsx +7 -13
  28. package/flow-report/test/summary/category-test.tsx +79 -34
  29. package/flow-report/test/summary/summary-test.tsx +11 -17
  30. package/flow-report/test/topbar-test.tsx +68 -0
  31. package/flow-report/test/util-test.tsx +1 -12
  32. package/flow-report/tsconfig.json +1 -0
  33. package/flow-report/types/flow-report.d.ts +2 -1
  34. package/jest.config.js +1 -15
  35. package/lighthouse-cli/bin.js +5 -0
  36. package/lighthouse-cli/cli-flags.js +10 -3
  37. package/lighthouse-cli/commands/commands.js +1 -0
  38. package/lighthouse-cli/commands/list-locales.js +16 -0
  39. package/lighthouse-cli/run.js +2 -2
  40. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +0 -0
  41. package/lighthouse-cli/test/smokehouse/report-assert.js +20 -4
  42. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +2 -1
  43. package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +1 -1
  44. package/lighthouse-core/audits/seo/crawlable-anchors.js +10 -12
  45. package/lighthouse-core/computed/js-bundles.js +1 -2
  46. package/lighthouse-core/computed/unused-javascript-summary.js +3 -3
  47. package/lighthouse-core/config/config.js +2 -2
  48. package/lighthouse-core/config/default-config.js +5 -0
  49. package/lighthouse-core/fraggle-rock/api.js +10 -0
  50. package/lighthouse-core/fraggle-rock/config/filters.js +22 -3
  51. package/lighthouse-core/fraggle-rock/gather/driver.js +4 -0
  52. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +2 -2
  53. package/lighthouse-core/fraggle-rock/user-flow.js +35 -6
  54. package/lighthouse-core/gather/driver/navigation.js +5 -0
  55. package/lighthouse-core/gather/driver/prepare.js +14 -0
  56. package/lighthouse-core/gather/driver/storage.js +5 -0
  57. package/lighthouse-core/gather/gather-runner.js +2 -2
  58. package/lighthouse-core/gather/gatherers/dobetterweb/response-compression.js +5 -1
  59. package/lighthouse-core/gather/gatherers/full-page-screenshot.js +30 -24
  60. package/lighthouse-core/lib/cdt/Platform.js +55 -0
  61. package/lighthouse-core/lib/cdt/SDK.js +3 -123
  62. package/lighthouse-core/lib/cdt/generated/SourceMap.js +148 -312
  63. package/lighthouse-core/lib/csp-evaluator.js +2 -1
  64. package/lighthouse-core/lib/i18n/README.md +6 -6
  65. package/lighthouse-core/lib/i18n/i18n.js +9 -342
  66. package/lighthouse-core/lib/page-functions.js +4 -4
  67. package/lighthouse-core/lib/stack-packs.js +1 -13
  68. package/lighthouse-core/runner.js +6 -6
  69. package/lighthouse-core/scripts/manual-chrome-launcher.js +4 -1
  70. package/lighthouse-core/scripts/package.json +4 -0
  71. package/lighthouse-core/util-commonjs.js +20 -4
  72. package/package.json +19 -16
  73. package/readme.md +1 -1
  74. package/report/assets/standalone-template.html +0 -1
  75. package/report/assets/styles.css +16 -41
  76. package/report/assets/templates.html +42 -4
  77. package/report/clients/psi.js +1 -0
  78. package/report/clients/standalone.js +1 -2
  79. package/report/generator/file-namer.js +17 -8
  80. package/report/generator/report-generator.js +0 -1
  81. package/report/renderer/category-renderer.js +4 -16
  82. package/report/renderer/components.js +107 -71
  83. package/report/renderer/dom.js +19 -0
  84. package/report/renderer/logger.js +35 -2
  85. package/report/renderer/performance-category-renderer.js +23 -21
  86. package/report/renderer/pwa-category-renderer.js +1 -2
  87. package/report/renderer/report-renderer.js +21 -0
  88. package/report/renderer/report-ui-features.js +7 -19
  89. package/report/renderer/swap-locale-feature.js +82 -0
  90. package/report/renderer/util.js +20 -4
  91. package/report/test/clients/psi-test.js +0 -4
  92. package/report/test/generator/file-namer-test.js +2 -2
  93. package/report/test/renderer/category-renderer-test.js +1 -1
  94. package/report/test/renderer/performance-category-renderer-test.js +12 -0
  95. package/report/test/renderer/report-renderer-axe-test.js +86 -0
  96. package/report/test/renderer/report-renderer-test.js +0 -55
  97. package/report/test/renderer/util-test.js +68 -8
  98. package/report/test-assets/faux-psi-template.html +0 -1
  99. package/report/tsconfig.json +2 -1
  100. package/report/types/html-renderer.d.ts +2 -1
  101. package/root.js +18 -0
  102. package/shared/localization/format.js +422 -0
  103. package/{lighthouse-core/lib/cdt/Common.js → shared/localization/i18n-module.js} +3 -8
  104. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ar-XB.json +139 -4
  105. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ar.json +141 -6
  106. package/{lighthouse-core/lib/i18n → shared/localization}/locales/bg.json +139 -4
  107. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ca.json +140 -5
  108. package/{lighthouse-core/lib/i18n → shared/localization}/locales/cs.json +139 -4
  109. package/{lighthouse-core/lib/i18n → shared/localization}/locales/da.json +139 -4
  110. package/{lighthouse-core/lib/i18n → shared/localization}/locales/de.json +139 -4
  111. package/{lighthouse-core/lib/i18n → shared/localization}/locales/el.json +141 -6
  112. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-GB.json +139 -4
  113. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-US.json +111 -0
  114. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-XA.json +139 -4
  115. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-XL.json +111 -0
  116. package/{lighthouse-core/lib/i18n → shared/localization}/locales/es-419.json +139 -4
  117. package/{lighthouse-core/lib/i18n → shared/localization}/locales/es.json +139 -4
  118. package/{lighthouse-core/lib/i18n → shared/localization}/locales/fi.json +139 -4
  119. package/{lighthouse-core/lib/i18n → shared/localization}/locales/fil.json +139 -4
  120. package/{lighthouse-core/lib/i18n → shared/localization}/locales/fr.json +139 -4
  121. package/{lighthouse-core/lib/i18n → shared/localization}/locales/he.json +139 -4
  122. package/{lighthouse-core/lib/i18n → shared/localization}/locales/hi.json +142 -7
  123. package/{lighthouse-core/lib/i18n → shared/localization}/locales/hr.json +139 -4
  124. package/{lighthouse-core/lib/i18n → shared/localization}/locales/hu.json +139 -4
  125. package/{lighthouse-core/lib/i18n → shared/localization}/locales/id.json +139 -4
  126. package/{lighthouse-core/lib/i18n → shared/localization}/locales/it.json +139 -4
  127. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ja.json +139 -4
  128. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ko.json +139 -4
  129. package/{lighthouse-core/lib/i18n → shared/localization}/locales/lt.json +139 -4
  130. package/{lighthouse-core/lib/i18n → shared/localization}/locales/lv.json +139 -4
  131. package/{lighthouse-core/lib/i18n → shared/localization}/locales/nl.json +139 -4
  132. package/{lighthouse-core/lib/i18n → shared/localization}/locales/no.json +139 -4
  133. package/{lighthouse-core/lib/i18n → shared/localization}/locales/pl.json +139 -4
  134. package/{lighthouse-core/lib/i18n → shared/localization}/locales/pt-PT.json +139 -4
  135. package/{lighthouse-core/lib/i18n → shared/localization}/locales/pt.json +139 -4
  136. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ro.json +139 -4
  137. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ru.json +139 -4
  138. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sk.json +139 -4
  139. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sl.json +139 -4
  140. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sr-Latn.json +139 -4
  141. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sr.json +139 -4
  142. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sv.json +139 -4
  143. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ta.json +146 -11
  144. package/{lighthouse-core/lib/i18n → shared/localization}/locales/te.json +179 -44
  145. package/{lighthouse-core/lib/i18n → shared/localization}/locales/th.json +139 -4
  146. package/{lighthouse-core/lib/i18n → shared/localization}/locales/tr.json +139 -4
  147. package/{lighthouse-core/lib/i18n → shared/localization}/locales/uk.json +139 -4
  148. package/{lighthouse-core/lib/i18n → shared/localization}/locales/vi.json +139 -4
  149. package/{lighthouse-core/lib/i18n → shared/localization}/locales/zh-HK.json +139 -4
  150. package/{lighthouse-core/lib/i18n → shared/localization}/locales/zh-TW.json +139 -4
  151. package/{lighthouse-core/lib/i18n → shared/localization}/locales/zh.json +139 -4
  152. package/{lighthouse-core/lib/i18n → shared/localization}/locales.js +2 -1
  153. package/shared/localization/swap-flow-locale.js +20 -0
  154. package/{lighthouse-core/lib/i18n → shared/localization}/swap-locale.js +9 -7
  155. package/shared/test/localization/format-test.js +377 -0
  156. package/shared/test/localization/locales-test.js +47 -0
  157. package/shared/test/localization/swap-locale-test.js +140 -0
  158. package/shared/tsconfig.json +22 -0
  159. package/{lighthouse-core/lib → shared}/type-verifiers.js +0 -0
  160. package/shared/types/shared.d.ts +24 -0
  161. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +5 -3
  162. package/third-party/chromium-synchronization/installability-errors-test.js +2 -1
  163. package/tsconfig-all.json +1 -0
  164. package/tsconfig-base.json +1 -1
  165. package/tsconfig.json +2 -3
  166. package/types/config.d.ts +1 -0
  167. package/types/es-main.d.ts +18 -0
  168. package/types/externs.d.ts +2 -0
  169. package/types/lhr/flow.d.ts +7 -0
  170. package/types/lhr/i18n.d.ts +0 -3
  171. package/dist/report/flow.js +0 -149
  172. package/dist/report/standalone.js +0 -228
@@ -0,0 +1,49 @@
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
+ module.exports = {
8
+ extends: '../.eslintrc.js',
9
+ env: {
10
+ node: true,
11
+ browser: true,
12
+ },
13
+ plugins: [
14
+ '@typescript-eslint',
15
+ ],
16
+ rules: {
17
+ // TODO(esmodules): move to root eslint when all code is ESM
18
+ // or when this is resolved: https://github.com/import-js/eslint-plugin-import/issues/2214
19
+ 'import/order': [2, {
20
+ 'groups': [
21
+ 'builtin',
22
+ 'external',
23
+ ['sibling', 'parent'],
24
+ 'index',
25
+ 'object',
26
+ 'type',
27
+ ],
28
+ 'newlines-between': 'always',
29
+ }],
30
+ '@typescript-eslint/type-annotation-spacing': 2,
31
+ },
32
+ overrides: [
33
+ // TS already handles this issue.
34
+ // https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/FAQ.md#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
35
+ {
36
+ files: ['**/*.ts', '**/*.tsx', '**/*.js'],
37
+ rules: {
38
+ 'no-undef': 'off',
39
+ },
40
+ },
41
+ ],
42
+ parserOptions: {
43
+ ecmaVersion: 2019,
44
+ ecmaFeatures: {
45
+ jsx: true,
46
+ },
47
+ sourceType: 'module',
48
+ },
49
+ };
@@ -10,17 +10,28 @@
10
10
  --category-summary-font-size: 18px;
11
11
  --double-base-spacing: calc(var(--base-spacing) * 2);
12
12
  --gather-mode-icon-size: 16px;
13
+ --header-adjacent-title-font-size: 14px;
14
+ --header-adjacent-title-width: minmax(var(--min-text-width), 1fr);
15
+ --header-current-description-font-size: 12px;
16
+ --header-current-title-font-size: 20px;
17
+ --header-margin: minmax(0px, calc(var(--base-spacing) * 4));
13
18
  --half-base-spacing: calc(var(--base-spacing) / 2);
19
+ --min-text-width: 20ch;
20
+ --dialog-border-color: var(--color-gray-700);
14
21
  --separator-color: var(--color-gray-300);
15
22
  --sidebar-flow-step-navigation-font-size: 14px;
16
23
  --sidebar-selected-bg-color: #DDEEFF;
17
24
  --sidebar-summary-font-size: 14px;
18
25
  --sidebar-title-font-size: 16px;
26
+ --sidebar-max-width: 400px;
19
27
  --summary-flow-step-label-font-size: 16px;
20
28
  --summary-subtitle-font-size: 16px;
21
29
  --summary-title-font-size: 32px;
22
30
  --summary-tooltip-box-shadow-color: rgba(0, 0, 0, 0.25);
31
+ --topbar-button-font-size: 14px;
32
+ --topbar-button-size: 45px;
23
33
  --topbar-title-font-size: 14px;
34
+ --dialog-title-font-size: 20px;
24
35
  }
25
36
 
26
37
  .App {
@@ -41,23 +52,59 @@
41
52
  display: none;
42
53
  }
43
54
 
55
+ @media print {
56
+ .App {
57
+ grid-template-rows: 0px auto !important;
58
+ grid-template-columns: 0px auto !important;
59
+ }
60
+
61
+ .Topbar {
62
+ display: none !important;
63
+ }
64
+
65
+ .Sidebar {
66
+ display: none !important;
67
+ }
68
+
69
+ .Content {
70
+ overflow-y: visible !important;
71
+ }
72
+
73
+ .SummaryFlow {
74
+ grid-template-columns: repeat(7, min-content) !important;
75
+ }
76
+ }
77
+
44
78
  .Topbar {
79
+ position: relative;
80
+ padding-right: var(--base-spacing);
45
81
  grid-area: topbar;
46
82
  display: flex;
47
83
  align-items: center;
48
84
  border-bottom: 1px solid var(--separator-color);
49
85
  }
50
86
 
51
- .Topbar__menu {
52
- padding: var(--base-spacing);
87
+ .TopbarButton {
88
+ height: var(--topbar-button-size);
89
+ min-width: var(--topbar-button-size);
53
90
  cursor: pointer;
54
91
  display: flex;
92
+ align-items: center;
93
+ justify-content: center;
55
94
  -webkit-touch-callout: none;
56
95
  -webkit-user-select: none;
57
96
  -khtml-user-select: none;
58
97
  -moz-user-select: none;
59
98
  -ms-user-select: none;
60
99
  user-select: none;
100
+ cursor: pointer;
101
+ background: none;
102
+ color: inherit;
103
+ border: none;
104
+ font: inherit;
105
+ outline: inherit;
106
+ font-size: var(--topbar-button-font-size);
107
+ margin-right: var(--base-spacing);
61
108
  }
62
109
 
63
110
  .Topbar__logo {
@@ -68,6 +115,7 @@
68
115
  .Topbar__title {
69
116
  font-size: var(--topbar-title-font-size);
70
117
  font-weight: bold;
118
+ margin-right: var(--double-base-spacing);
71
119
  }
72
120
 
73
121
  .Content {
@@ -100,6 +148,10 @@
100
148
  .Sidebar {
101
149
  grid-area: sidebar;
102
150
  border-right: 1px solid var(--separator-color);
151
+ width: min-content;
152
+ max-width: var(--sidebar-max-width);
153
+ overflow-x: hidden;
154
+ overflow-y: auto;
103
155
  }
104
156
  .Sidebar a {
105
157
  color: var(--color-gray-800);
@@ -181,6 +233,8 @@
181
233
  grid-column: 2;
182
234
  margin: var(--half-base-spacing) 0px;
183
235
  justify-self: left;
236
+ overflow-x: hidden;
237
+ text-overflow: ellipsis;
184
238
  }
185
239
  .SidebarFlowStep__label--navigation {
186
240
  font-size: var(--sidebar-flow-step-navigation-font-size);
@@ -223,14 +277,14 @@
223
277
 
224
278
  .SummarySectionHeader__content {
225
279
  margin: var(--base-spacing) 0px;
280
+ text-transform: uppercase;
226
281
  }
227
282
 
228
283
  .SummaryFlow {
229
284
  width: 100%;
230
- max-width: max-content;
231
285
  display: grid;
232
286
  grid-auto-rows: min-content;
233
- grid-template-columns: min-content min-content 4fr 1fr 1fr 1fr 1fr;
287
+ grid-template-columns: min-content min-content minmax(var(--min-text-width), 4fr) 1fr 1fr 1fr 1fr;
234
288
  align-items: center;
235
289
  margin: var(--base-spacing) 0px;
236
290
  }
@@ -266,21 +320,24 @@
266
320
  .SummaryFlowStep .FlowSegment {
267
321
  grid-column: 2;
268
322
  }
323
+ .SummaryFlowStep .FlowStepThumbnail {
324
+ margin: var(--base-spacing) var(--half-base-spacing);
325
+ }
269
326
 
270
- .SummaryFlowStep__screenshot {
327
+ .FlowStepThumbnail {
271
328
  align-self: center;
272
329
  justify-self: center;
273
- margin: var(--base-spacing) var(--half-base-spacing);
274
330
  object-fit: cover;
275
331
  object-position: top left;
276
- }
277
- .SummaryFlowStep__screenshot[src] {
278
332
  border: 1px solid var(--color-gray);
279
333
  }
280
334
 
281
335
  .SummaryFlowStep__label {
282
336
  grid-column: 3;
283
337
  margin: var(--half-base-spacing);
338
+ width: 100%;
339
+ overflow-x: hidden;
340
+ text-overflow: ellipsis;
284
341
  }
285
342
 
286
343
  .SummaryFlowStep__mode {
@@ -291,6 +348,8 @@
291
348
  font-size: var(--summary-flow-step-label-font-size);
292
349
  color: var(--color-gray-800);
293
350
  text-decoration: underline;
351
+ width: 100%;
352
+ overflow-x: hidden;
294
353
  }
295
354
 
296
355
  .SummaryCategory__content {
@@ -314,7 +373,7 @@
314
373
  width: max-content;
315
374
  background-color: var(--report-background-color);
316
375
  border: 1px solid var(--color-gray-900);
317
- border-radius: 5px;
376
+ border-radius: 3px;
318
377
  padding: var(--base-spacing);
319
378
  right: 0;
320
379
  box-shadow: 0px 4px 4px var(--summary-tooltip-box-shadow-color);
@@ -360,6 +419,7 @@
360
419
  justify-content: center;
361
420
  padding: var(--half-base-spacing);
362
421
  background-color: var(--color-gray-100);
422
+ color: var(--color-gray-700);
363
423
  border-top: 1px solid var(--color-gray-300);
364
424
  text-align: center;
365
425
  }
@@ -367,6 +427,11 @@
367
427
  justify-content: start;
368
428
  text-align: left;
369
429
  }
430
+ .SummaryNavigationHeader__url > a {
431
+ color: inherit;
432
+ overflow-x: hidden;
433
+ text-overflow: ellipsis;
434
+ }
370
435
 
371
436
  .SummaryFlowStep__separator {
372
437
  display: contents;
@@ -383,3 +448,201 @@
383
448
  margin: auto;
384
449
  background-color: var(--color-gray-700);
385
450
  }
451
+
452
+ .Header {
453
+ display: grid;
454
+ grid-template-areas:
455
+ ". . . current-tn . . ."
456
+ "prev-seg prev-tn prev-tn current-tn next-tn next-tn next-seg"
457
+ ". prev-title . current-tn . next-title ."
458
+ ". prev-title current-title current-title current-title next-title .";
459
+ grid-template-rows: 1fr auto 1fr auto;
460
+ grid-template-columns: var(--header-margin) var(--header-adjacent-title-width) auto auto auto var(--header-adjacent-title-width) var(--header-margin);
461
+ align-items: center;
462
+ margin: var(--double-base-spacing) 0px;
463
+ }
464
+ .Header a {
465
+ color: var(--color-gray-800);
466
+ }
467
+ .Header > .Header__segment:first-child {
468
+ grid-area: prev-seg;
469
+ }
470
+ .Header > .Header__segment:last-child {
471
+ grid-area: next-seg;
472
+ }
473
+
474
+ .Header__prev-thumbnail {
475
+ display: flex;
476
+ align-items: center;
477
+ grid-area: prev-tn;
478
+ }
479
+ .Header__current-thumbnail {
480
+ display: flex;
481
+ align-items: center;
482
+ grid-area: current-tn;
483
+ }
484
+ .Header__next-thumbnail {
485
+ display: flex;
486
+ align-items: center;
487
+ grid-area: next-tn;
488
+ }
489
+ .Header__prev-thumbnail .Header__segment,
490
+ .Header__next-thumbnail .Header__segment {
491
+ flex-grow: 1;
492
+ }
493
+
494
+ .Header__segment {
495
+ height: 2px;
496
+ background-color: var(--color-blue-A700);
497
+ }
498
+
499
+ .HeaderThumbnail {
500
+ position: relative;
501
+ display: flex;
502
+ margin: var(--base-spacing) 0px;
503
+ }
504
+ .HeaderThumbnail--main .FlowStepThumbnail {
505
+ border: 2px solid var(--color-blue-A700);
506
+ }
507
+ .HeaderThumbnail--prev .HeaderThumbnail__icon {
508
+ top: 50%;
509
+ left: 100%;
510
+ }
511
+ .HeaderThumbnail--next .HeaderThumbnail__icon {
512
+ top: 50%;
513
+ left: 0;
514
+ }
515
+ .HeaderThumbnail--main .HeaderThumbnail__icon {
516
+ top: 100%;
517
+ left: 50%;
518
+ }
519
+
520
+ .HeaderThumbnail__icon {
521
+ position: absolute;
522
+ display: flex;
523
+ padding: calc(var(--base-spacing) * 0.25);
524
+ background-color: var(--report-background-color);
525
+ border-radius: 50%;
526
+ transform: translate(-50%, -50%);
527
+ color: var(--color-blue-A700)
528
+ }
529
+
530
+ .Header__prev-title {
531
+ grid-area: prev-title;
532
+ text-align: left;
533
+ }
534
+
535
+ .Header__next-title {
536
+ grid-area: next-title;
537
+ text-align: right;
538
+ justify-self: end;
539
+ }
540
+
541
+ .Header__prev-title,
542
+ .Header__next-title {
543
+ font-size: var(--header-adjacent-title-font-size);
544
+ align-self: start;
545
+ overflow-x: hidden;
546
+ text-overflow: ellipsis;
547
+ width: 100%;
548
+ }
549
+
550
+ .Header__current-title {
551
+ grid-area: current-title;
552
+ text-align: center;
553
+ font-size: var(--header-current-title-font-size);
554
+ font-weight: bold;
555
+ overflow-x: hidden;
556
+ text-overflow: ellipsis;
557
+ }
558
+
559
+ .Header__current-description {
560
+ text-align: center;
561
+ font-size: var(--header-current-description-font-size);
562
+ font-weight: normal;
563
+ }
564
+
565
+ .HelpDialog {
566
+ position: absolute;
567
+ top: 100%;
568
+ right: var(--base-spacing);
569
+ z-index: 100;
570
+ max-width: 720px;
571
+
572
+ background: var(--color-white);
573
+ color: var(--color-gray-800);
574
+ border-radius: 3px;
575
+ border: 1px solid var(--dialog-border-color);
576
+ box-shadow: 0px 4px 12px var(--summary-tooltip-box-shadow-color);
577
+
578
+ padding: var(--base-spacing) calc(var(--base-spacing) * 1.5);
579
+ }
580
+
581
+ .HelpDialog__title {
582
+ display: flex;
583
+ align-items: center;
584
+
585
+ font-size: var(--dialog-title-font-size);
586
+ font-weight: bold;
587
+
588
+ margin-bottom: calc(var(--base-spacing) * 2);
589
+ }
590
+
591
+ .HelpDialog__close {
592
+ background: none;
593
+ border: none;
594
+ cursor: pointer;
595
+ line-height: 0;
596
+ }
597
+
598
+ .HelpDialog__columns {
599
+ display: grid;
600
+ grid-auto-flow: column;
601
+ grid-template-rows: repeat(4, auto);
602
+ grid-template-columns: repeat(3, 1fr);
603
+ grid-column-gap: var(--base-spacing);
604
+ }
605
+
606
+ .HelpDialogColumn {
607
+ display: contents;
608
+ }
609
+
610
+ .HelpDialogColumn__header-title {
611
+ font-size: var(--category-summary-font-size);
612
+ font-weight: bold;
613
+ }
614
+
615
+ .HelpDialogColumn__legend-label {
616
+ color: var(--color-blue-A700);
617
+ font-weight: bold;
618
+ }
619
+
620
+ .HelpDialogColumn ul {
621
+ padding-left: var(--base-spacing);
622
+ }
623
+
624
+ .HelpDialogColumn__use-cases p,
625
+ .HelpDialogColumn__categories p {
626
+ font-weight: bold;
627
+ color: var(--color-gray-700);
628
+ }
629
+
630
+ .HelpDialogColumnTimeline {
631
+ display: flex;
632
+ align-items: center;
633
+
634
+ height: var(--double-base-spacing);
635
+ color: var(--color-blue-A700);
636
+ margin-right: calc(var(--base-spacing) * -1);
637
+ }
638
+
639
+ .HelpDialogColumn:last-child .HelpDialogColumnTimeline {
640
+ margin-right: 0;
641
+ }
642
+
643
+ .HelpDialogColumnTimeline__line {
644
+ height: 2px;
645
+ flex-grow: 1;
646
+
647
+ background: var(--color-blue-A700);
648
+ }
@@ -4,7 +4,7 @@
4
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
5
  */
6
6
 
7
- module.exports = {
7
+ export default {
8
8
  testEnvironment: 'node',
9
9
  preset: 'ts-jest',
10
10
  globalSetup: './test/setup/global-setup.ts',
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "module",
3
+ "//": "Any directory that uses `import ... from` or `export ...` must be type module. Temporary file until root package.json is type: module"
4
+ }
@@ -5,23 +5,43 @@
5
5
  */
6
6
 
7
7
  import {FunctionComponent} from 'preact';
8
- import {useState} from 'preact/hooks';
8
+ import {useEffect, useRef, useState} from 'preact/hooks';
9
9
 
10
10
  import {ReportRendererProvider} from './wrappers/report-renderer';
11
11
  import {Sidebar} from './sidebar/sidebar';
12
12
  import {Summary} from './summary/summary';
13
- import {classNames, FlowResultContext, useCurrentLhr} from './util';
13
+ import {classNames, FlowResultContext, useCurrentLhr, useHashParam} from './util';
14
14
  import {Report} from './wrappers/report';
15
15
  import {Topbar} from './topbar';
16
+ import {Header} from './header';
17
+ import {I18nProvider} from './i18n/i18n';
16
18
 
17
19
  const Content: FunctionComponent = () => {
18
20
  const currentLhr = useCurrentLhr();
21
+ const anchor = useHashParam('anchor');
22
+ const ref = useRef<HTMLDivElement>(null);
23
+
24
+ useEffect(() => {
25
+ if (anchor) {
26
+ const el = document.getElementById(anchor);
27
+ if (el) {
28
+ el.scrollIntoView({behavior: 'smooth'});
29
+ return;
30
+ }
31
+ }
32
+
33
+ // Scroll to top no anchor is found.
34
+ if (ref.current) ref.current.scrollTop = 0;
35
+ }, [anchor, currentLhr]);
19
36
 
20
37
  return (
21
- <div className="Content">
38
+ <div ref={ref} className="Content">
22
39
  {
23
40
  currentLhr ?
24
- <Report/> :
41
+ <>
42
+ <Header currentLhr={currentLhr}/>
43
+ <Report currentLhr={currentLhr}/>
44
+ </> :
25
45
  <Summary/>
26
46
  }
27
47
  </div>
@@ -33,11 +53,13 @@ export const App: FunctionComponent<{flowResult: LH.FlowResult}> = ({flowResult}
33
53
  return (
34
54
  <FlowResultContext.Provider value={flowResult}>
35
55
  <ReportRendererProvider>
36
- <div className={classNames('App', {'App--collapsed': collapsed})}>
37
- <Topbar onMenuClick={() => setCollapsed(c => !c)} />
38
- <Sidebar/>
39
- <Content/>
40
- </div>
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>
41
63
  </ReportRendererProvider>
42
64
  </FlowResultContext.Provider>
43
65
  );
@@ -5,27 +5,106 @@
5
5
  */
6
6
 
7
7
  import {FunctionComponent} from 'preact';
8
+ import {useEffect, useState} from 'preact/hooks';
8
9
 
9
10
  import {NavigationIcon, SnapshotIcon, TimespanIcon} from './icons';
11
+ import {getFilmstripFrames, getScreenDimensions, getScreenshot} from './util';
12
+
13
+ const ANIMATION_FRAME_DURATION_MS = 250;
10
14
 
11
15
  export const Separator: FunctionComponent = () => {
12
16
  return <div className="Separator" role="separator"></div>;
13
17
  };
14
18
 
19
+ export const FlowStepIcon: FunctionComponent<{mode: LH.Result.GatherMode}> = ({mode}) => {
20
+ return <>
21
+ {
22
+ mode === 'navigation' && <NavigationIcon/>
23
+ }
24
+ {
25
+ mode === 'timespan' && <TimespanIcon/>
26
+ }
27
+ {
28
+ mode === 'snapshot' && <SnapshotIcon/>
29
+ }
30
+ </>;
31
+ };
32
+
15
33
  export const FlowSegment: FunctionComponent<{mode?: LH.Result.GatherMode}> = ({mode}) => {
16
34
  return (
17
35
  <div className="FlowSegment">
18
36
  <div className="FlowSegment__top-line"/>
19
37
  {
20
- mode === 'navigation' && <NavigationIcon/>
21
- }
22
- {
23
- mode === 'timespan' && <TimespanIcon/>
24
- }
25
- {
26
- mode === 'snapshot' && <SnapshotIcon/>
38
+ mode && <FlowStepIcon mode={mode}/>
27
39
  }
28
40
  <div className="FlowSegment__bottom-line"/>
29
41
  </div>
30
42
  );
31
43
  };
44
+
45
+ const FlowStepAnimatedThumbnail: FunctionComponent<{
46
+ frames: Array<{data: string}>,
47
+ width: number,
48
+ height: number,
49
+ }> = ({frames, width, height}) => {
50
+ const [frameIndex, setFrameIndex] = useState(0);
51
+ // Handle a frame array of a different length being set.
52
+ const effectiveFrameIndex = frameIndex % frames.length;
53
+
54
+ useEffect(() => {
55
+ const interval = setInterval(
56
+ () => setFrameIndex(i => (i + 1) % frames.length),
57
+ ANIMATION_FRAME_DURATION_MS
58
+ );
59
+
60
+ return () => clearInterval(interval);
61
+ }, [frames.length]);
62
+
63
+ return (
64
+ <img
65
+ className="FlowStepThumbnail"
66
+ data-testid="FlowStepAnimatedThumbnail"
67
+ src={frames[effectiveFrameIndex].data}
68
+ style={{width, height}}
69
+ alt="Animated screenshots of a page tested by Lighthouse"
70
+ />
71
+ );
72
+ };
73
+
74
+ export const FlowStepThumbnail: FunctionComponent<{
75
+ reportResult: LH.ReportResult,
76
+ width?: number,
77
+ height?: number,
78
+ }> = ({reportResult, width, height}) => {
79
+ const screenshot = getScreenshot(reportResult);
80
+ const frames = getFilmstripFrames(reportResult);
81
+
82
+ // Resize the image to fit the viewport aspect ratio.
83
+ const dimensions = getScreenDimensions(reportResult);
84
+ if (width && height === undefined) {
85
+ height = dimensions.height * width / dimensions.width;
86
+ } else if (height && width === undefined) {
87
+ width = dimensions.width * height / dimensions.height;
88
+ }
89
+
90
+ if (!width || !height) {
91
+ console.warn(new Error('FlowStepThumbnail requested without any dimensions').stack);
92
+ return <></>;
93
+ }
94
+
95
+ if (reportResult.gatherMode === 'timespan' && frames && frames.length) {
96
+ return <FlowStepAnimatedThumbnail frames={frames} width={width} height={height} />;
97
+ }
98
+
99
+ return <>
100
+ {
101
+ screenshot &&
102
+ <img
103
+ className="FlowStepThumbnail"
104
+ src={screenshot}
105
+ style={{width, height}}
106
+ alt="Screenshot of a page tested by Lighthouse"
107
+ />
108
+ }
109
+ </>;
110
+ };