lighthouse 9.5.0-dev.20220418 → 9.5.0-dev.20220421

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 (68) hide show
  1. package/dist/report/bundle.esm.js +117 -70
  2. package/dist/report/flow.js +23 -23
  3. package/dist/report/standalone.js +14 -14
  4. package/flow-report/src/summary/category.tsx +1 -1
  5. package/lighthouse-core/audits/accessibility/accesskeys.js +1 -1
  6. package/lighthouse-core/audits/accessibility/aria-allowed-attr.js +1 -1
  7. package/lighthouse-core/audits/accessibility/aria-command-name.js +1 -1
  8. package/lighthouse-core/audits/accessibility/aria-hidden-body.js +1 -1
  9. package/lighthouse-core/audits/accessibility/aria-hidden-focus.js +1 -1
  10. package/lighthouse-core/audits/accessibility/aria-input-field-name.js +1 -1
  11. package/lighthouse-core/audits/accessibility/aria-meter-name.js +1 -1
  12. package/lighthouse-core/audits/accessibility/aria-progressbar-name.js +1 -1
  13. package/lighthouse-core/audits/accessibility/aria-required-attr.js +1 -1
  14. package/lighthouse-core/audits/accessibility/aria-required-children.js +1 -1
  15. package/lighthouse-core/audits/accessibility/aria-required-parent.js +1 -1
  16. package/lighthouse-core/audits/accessibility/aria-roles.js +1 -1
  17. package/lighthouse-core/audits/accessibility/aria-toggle-field-name.js +1 -1
  18. package/lighthouse-core/audits/accessibility/aria-tooltip-name.js +1 -1
  19. package/lighthouse-core/audits/accessibility/aria-treeitem-name.js +1 -1
  20. package/lighthouse-core/audits/accessibility/aria-valid-attr-value.js +1 -1
  21. package/lighthouse-core/audits/accessibility/aria-valid-attr.js +1 -1
  22. package/lighthouse-core/audits/accessibility/button-name.js +1 -1
  23. package/lighthouse-core/audits/accessibility/bypass.js +1 -1
  24. package/lighthouse-core/audits/accessibility/color-contrast.js +1 -1
  25. package/lighthouse-core/audits/accessibility/definition-list.js +1 -1
  26. package/lighthouse-core/audits/accessibility/dlitem.js +1 -1
  27. package/lighthouse-core/audits/accessibility/document-title.js +1 -1
  28. package/lighthouse-core/audits/accessibility/duplicate-id-active.js +1 -1
  29. package/lighthouse-core/audits/accessibility/duplicate-id-aria.js +1 -1
  30. package/lighthouse-core/audits/accessibility/form-field-multiple-labels.js +1 -1
  31. package/lighthouse-core/audits/accessibility/frame-title.js +1 -1
  32. package/lighthouse-core/audits/accessibility/heading-order.js +1 -1
  33. package/lighthouse-core/audits/accessibility/html-has-lang.js +1 -1
  34. package/lighthouse-core/audits/accessibility/html-lang-valid.js +1 -1
  35. package/lighthouse-core/audits/accessibility/image-alt.js +1 -1
  36. package/lighthouse-core/audits/accessibility/input-image-alt.js +1 -1
  37. package/lighthouse-core/audits/accessibility/label.js +1 -1
  38. package/lighthouse-core/audits/accessibility/link-name.js +1 -1
  39. package/lighthouse-core/audits/accessibility/list.js +1 -1
  40. package/lighthouse-core/audits/accessibility/listitem.js +1 -1
  41. package/lighthouse-core/audits/accessibility/meta-refresh.js +1 -1
  42. package/lighthouse-core/audits/accessibility/meta-viewport.js +1 -1
  43. package/lighthouse-core/audits/accessibility/object-alt.js +1 -1
  44. package/lighthouse-core/audits/accessibility/tabindex.js +1 -1
  45. package/lighthouse-core/audits/accessibility/td-headers-attr.js +1 -1
  46. package/lighthouse-core/audits/accessibility/th-has-data-cells.js +1 -1
  47. package/lighthouse-core/audits/accessibility/valid-lang.js +1 -1
  48. package/lighthouse-core/audits/accessibility/video-caption.js +1 -1
  49. package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +1 -1
  50. package/lighthouse-core/config/default-config.js +1 -1
  51. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +16 -2
  52. package/lighthouse-core/index.js +2 -9
  53. package/lighthouse-core/util-commonjs.js +13 -7
  54. package/package.json +2 -2
  55. package/report/assets/styles.css +10 -5
  56. package/report/assets/templates.html +10 -9
  57. package/report/renderer/components.js +13 -12
  58. package/report/renderer/element-screenshot-renderer.js +0 -3
  59. package/report/renderer/i18n.js +91 -48
  60. package/report/renderer/util.js +13 -7
  61. package/report/test/renderer/element-screenshot-renderer-test.js +3 -3
  62. package/report/test/renderer/i18n-test.js +26 -7
  63. package/report/test/renderer/util-test.js +2 -2
  64. package/report/test-assets/faux-psi.js +2 -0
  65. package/shared/localization/locales/en-US.json +46 -46
  66. package/shared/localization/locales/en-XL.json +46 -46
  67. package/third-party/snyk/snapshot.json +2 -3
  68. package/types/config.d.ts +2 -0
@@ -21,9 +21,7 @@ export class I18n {
21
21
  // When testing, use a locale with more exciting numeric formatting.
22
22
  if (locale === 'en-XA') locale = 'de';
23
23
 
24
- this._numberDateLocale = locale;
25
- this._numberFormatter = new Intl.NumberFormat(locale);
26
- this._percentFormatter = new Intl.NumberFormat(locale, {style: 'percent'});
24
+ this._locale = locale;
27
25
  this._strings = strings;
28
26
  }
29
27
 
@@ -31,6 +29,32 @@ export class I18n {
31
29
  return this._strings;
32
30
  }
33
31
 
32
+ /**
33
+ * @param {number} number
34
+ * @param {number} granularity
35
+ * @param {Intl.NumberFormatOptions} opts
36
+ * @return {string}
37
+ */
38
+ _formatNumberWithGranularity(number, granularity, opts = {}) {
39
+ opts = {...opts};
40
+ const log10 = -Math.log10(granularity);
41
+ if (!Number.isFinite(log10) || (granularity > 1 && !Number.isInteger(log10))) {
42
+ console.warn(`granularity of ${granularity} is invalid, defaulting to value of 1`);
43
+ granularity = 1;
44
+ }
45
+
46
+ if (granularity < 1) {
47
+ opts.minimumFractionDigits = opts.maximumFractionDigits = Math.ceil(log10);
48
+ }
49
+
50
+ number = Math.round(number / granularity) * granularity;
51
+
52
+ // Avoid displaying a negative value that rounds to zero as "0".
53
+ if (Object.is(number, -0)) number = 0;
54
+
55
+ return new Intl.NumberFormat(this._locale, opts).format(number).replace(' ', NBSP2);
56
+ }
57
+
34
58
  /**
35
59
  * Format number.
36
60
  * @param {number} number
@@ -38,8 +62,18 @@ export class I18n {
38
62
  * @return {string}
39
63
  */
40
64
  formatNumber(number, granularity = 0.1) {
41
- const coarseValue = Math.round(number / granularity) * granularity;
42
- return this._numberFormatter.format(coarseValue);
65
+ return this._formatNumberWithGranularity(number, granularity);
66
+ }
67
+
68
+ /**
69
+ * Format integer.
70
+ * Just like {@link formatNumber} but uses a granularity of 1, rounding to the nearest
71
+ * whole number.
72
+ * @param {number} number
73
+ * @return {string}
74
+ */
75
+ formatInteger(number) {
76
+ return this._formatNumberWithGranularity(number, 1);
43
77
  }
44
78
 
45
79
  /**
@@ -48,7 +82,7 @@ export class I18n {
48
82
  * @return {string}
49
83
  */
50
84
  formatPercent(number) {
51
- return this._percentFormatter.format(number);
85
+ return new Intl.NumberFormat(this._locale, {style: 'percent'}).format(number);
52
86
  }
53
87
 
54
88
  /**
@@ -57,9 +91,7 @@ export class I18n {
57
91
  * @return {string}
58
92
  */
59
93
  formatBytesToKiB(size, granularity = 0.1) {
60
- const formatter = this._byteFormatterForGranularity(granularity);
61
- const kbs = formatter.format(Math.round(size / 1024 / granularity) * granularity);
62
- return `${kbs}${NBSP2}KiB`;
94
+ return this._formatNumberWithGranularity(size / KiB, granularity) + `${NBSP2}KiB`;
63
95
  }
64
96
 
65
97
  /**
@@ -68,9 +100,7 @@ export class I18n {
68
100
  * @return {string}
69
101
  */
70
102
  formatBytesToMiB(size, granularity = 0.1) {
71
- const formatter = this._byteFormatterForGranularity(granularity);
72
- const kbs = formatter.format(Math.round(size / (1024 ** 2) / granularity) * granularity);
73
- return `${kbs}${NBSP2}MiB`;
103
+ return this._formatNumberWithGranularity(size / MiB, granularity) + `${NBSP2}MiB`;
74
104
  }
75
105
 
76
106
  /**
@@ -79,9 +109,11 @@ export class I18n {
79
109
  * @return {string}
80
110
  */
81
111
  formatBytes(size, granularity = 1) {
82
- const formatter = this._byteFormatterForGranularity(granularity);
83
- const kbs = formatter.format(Math.round(size / granularity) * granularity);
84
- return `${kbs}${NBSP2}bytes`;
112
+ return this._formatNumberWithGranularity(size, granularity, {
113
+ style: 'unit',
114
+ unit: 'byte',
115
+ unitDisplay: 'long',
116
+ });
85
117
  }
86
118
 
87
119
  /**
@@ -92,25 +124,23 @@ export class I18n {
92
124
  formatBytesWithBestUnit(size, granularity = 0.1) {
93
125
  if (size >= MiB) return this.formatBytesToMiB(size, granularity);
94
126
  if (size >= KiB) return this.formatBytesToKiB(size, granularity);
95
- return this.formatNumber(size, granularity) + '\xa0B';
127
+ return this._formatNumberWithGranularity(size, granularity, {
128
+ style: 'unit',
129
+ unit: 'byte',
130
+ unitDisplay: 'narrow',
131
+ });
96
132
  }
97
133
 
98
134
  /**
99
- * Format bytes with a constant number of fractional digits, i.e. for a granularity of 0.1, 10 becomes '10.0'
100
- * @param {number} granularity Controls how coarse the displayed value is
101
- * @return {Intl.NumberFormat}
135
+ * @param {number} size
136
+ * @param {number=} granularity Controls how coarse the displayed value is, defaults to 1
137
+ * @return {string}
102
138
  */
103
- _byteFormatterForGranularity(granularity) {
104
- // assume any granularity above 1 will not contain fractional parts, i.e. will never be 1.5
105
- let numberOfFractionDigits = 0;
106
- if (granularity < 1) {
107
- numberOfFractionDigits = -Math.floor(Math.log10(granularity));
108
- }
109
-
110
- return new Intl.NumberFormat(this._numberDateLocale, {
111
- ...this._numberFormatter.resolvedOptions(),
112
- maximumFractionDigits: numberOfFractionDigits,
113
- minimumFractionDigits: numberOfFractionDigits,
139
+ formatKbps(size, granularity = 1) {
140
+ return this._formatNumberWithGranularity(size, granularity, {
141
+ style: 'unit',
142
+ unit: 'kilobit-per-second',
143
+ unitDisplay: 'short',
114
144
  });
115
145
  }
116
146
 
@@ -120,10 +150,11 @@ export class I18n {
120
150
  * @return {string}
121
151
  */
122
152
  formatMilliseconds(ms, granularity = 10) {
123
- const coarseTime = Math.round(ms / granularity) * granularity;
124
- return coarseTime === 0
125
- ? `${this._numberFormatter.format(0)}${NBSP2}ms`
126
- : `${this._numberFormatter.format(coarseTime)}${NBSP2}ms`;
153
+ return this._formatNumberWithGranularity(ms, granularity, {
154
+ style: 'unit',
155
+ unit: 'millisecond',
156
+ unitDisplay: 'short',
157
+ });
127
158
  }
128
159
 
129
160
  /**
@@ -132,8 +163,11 @@ export class I18n {
132
163
  * @return {string}
133
164
  */
134
165
  formatSeconds(ms, granularity = 0.1) {
135
- const coarseTime = Math.round(ms / 1000 / granularity) * granularity;
136
- return `${this._numberFormatter.format(coarseTime)}${NBSP2}s`;
166
+ return this._formatNumberWithGranularity(ms / 1000, granularity, {
167
+ style: 'unit',
168
+ unit: 'second',
169
+ unitDisplay: 'short',
170
+ });
137
171
  }
138
172
 
139
173
  /**
@@ -153,10 +187,10 @@ export class I18n {
153
187
  // and https://github.com/GoogleChrome/lighthouse/pull/9822
154
188
  let formatter;
155
189
  try {
156
- formatter = new Intl.DateTimeFormat(this._numberDateLocale, options);
190
+ formatter = new Intl.DateTimeFormat(this._locale, options);
157
191
  } catch (err) {
158
192
  options.timeZone = 'UTC';
159
- formatter = new Intl.DateTimeFormat(this._numberDateLocale, options);
193
+ formatter = new Intl.DateTimeFormat(this._locale, options);
160
194
  }
161
195
 
162
196
  return formatter.format(new Date(date));
@@ -168,6 +202,10 @@ export class I18n {
168
202
  * @return {string}
169
203
  */
170
204
  formatDuration(timeInMilliseconds) {
205
+ // There is a proposal for a Intl.DurationFormat.
206
+ // https://github.com/tc39/proposal-intl-duration-format
207
+ // Until then, we do things a bit more manually.
208
+
171
209
  let timeInSeconds = timeInMilliseconds / 1000;
172
210
  if (Math.round(timeInSeconds) === 0) {
173
211
  return 'None';
@@ -176,19 +214,24 @@ export class I18n {
176
214
  /** @type {Array<string>} */
177
215
  const parts = [];
178
216
  /** @type {Record<string, number>} */
179
- const unitLabels = {
180
- d: 60 * 60 * 24,
181
- h: 60 * 60,
182
- m: 60,
183
- s: 1,
217
+ const unitToSecondsPer = {
218
+ day: 60 * 60 * 24,
219
+ hour: 60 * 60,
220
+ minute: 60,
221
+ second: 1,
184
222
  };
185
223
 
186
- Object.keys(unitLabels).forEach(label => {
187
- const unit = unitLabels[label];
188
- const numberOfUnits = Math.floor(timeInSeconds / unit);
224
+ Object.keys(unitToSecondsPer).forEach(unit => {
225
+ const secondsPerUnit = unitToSecondsPer[unit];
226
+ const numberOfUnits = Math.floor(timeInSeconds / secondsPerUnit);
189
227
  if (numberOfUnits > 0) {
190
- timeInSeconds -= numberOfUnits * unit;
191
- parts.push(`${numberOfUnits}\xa0${label}`);
228
+ timeInSeconds -= numberOfUnits * secondsPerUnit;
229
+ const part = this._formatNumberWithGranularity(numberOfUnits, 1, {
230
+ style: 'unit',
231
+ unit,
232
+ unitDisplay: 'narrow',
233
+ });
234
+ parts.push(part);
192
235
  }
193
236
  });
194
237
 
@@ -429,10 +429,13 @@ class Util {
429
429
  break;
430
430
  case 'devtools': {
431
431
  const {cpuSlowdownMultiplier, requestLatencyMs} = throttling;
432
- cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`;
433
- networkThrottling = `${Util.i18n.formatNumber(requestLatencyMs)}${NBSP}ms HTTP RTT, ` +
434
- `${Util.i18n.formatNumber(throttling.downloadThroughputKbps)}${NBSP}Kbps down, ` +
435
- `${Util.i18n.formatNumber(throttling.uploadThroughputKbps)}${NBSP}Kbps up (DevTools)`;
432
+ // TODO: better api in i18n formatter such that this isn't needed.
433
+ const cpuGranularity = Number.isInteger(cpuSlowdownMultiplier) ? 1 : 0.1;
434
+ // eslint-disable-next-line max-len
435
+ cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier, cpuGranularity)}x slowdown (DevTools)`;
436
+ networkThrottling = `${Util.i18n.formatMilliseconds(requestLatencyMs, 1)} HTTP RTT, ` +
437
+ `${Util.i18n.formatKbps(throttling.downloadThroughputKbps)} down, ` +
438
+ `${Util.i18n.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`;
436
439
 
437
440
  const isSlow4G = () => {
438
441
  return requestLatencyMs === 150 * 3.75 &&
@@ -444,9 +447,12 @@ class Util {
444
447
  }
445
448
  case 'simulate': {
446
449
  const {cpuSlowdownMultiplier, rttMs, throughputKbps} = throttling;
447
- cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`;
448
- networkThrottling = `${Util.i18n.formatNumber(rttMs)}${NBSP}ms TCP RTT, ` +
449
- `${Util.i18n.formatNumber(throughputKbps)}${NBSP}Kbps throughput (Simulated)`;
450
+ // TODO: better api in i18n formatter such that this isn't needed.
451
+ const cpuGranularity = Number.isInteger(cpuSlowdownMultiplier) ? 1 : 0.1;
452
+ // eslint-disable-next-line max-len
453
+ cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier, cpuGranularity)}x slowdown (Simulated)`;
454
+ networkThrottling = `${Util.i18n.formatMilliseconds(rttMs)} TCP RTT, ` +
455
+ `${Util.i18n.formatKbps(throughputKbps)} throughput (Simulated)`;
450
456
 
451
457
  const isSlow4G = () => {
452
458
  return rttMs === 150 && throughputKbps === 1.6 * 1024;
@@ -65,7 +65,8 @@ describe('ElementScreenshotRenderer', () => {
65
65
  /* eslint-disable max-len */
66
66
  expect(htmlFormatted).toMatchInlineSnapshot(`
67
67
  "
68
- <div class=\\"lh-element-screenshot__content\\" style=\\"top: -500px;\\">
68
+ <div class=\\"lh-element-screenshot__content\\">
69
+ <div class=\\"lh-element-screenshot__image\\" style=\\"width: 500px; height: 500px; background-position-y: 0px; background-position-x: 0px; background-size: 1000px 1000px;\\">
69
70
  <div class=\\"lh-element-screenshot__mask\\" style=\\"width: 500px; height: 500px; clip-path: url(#clip-0);\\">
70
71
  <svg height=\\"0\\" width=\\"0\\"> <defs>
71
72
  <clipPath clipPathUnits=\\"objectBoundingBox\\" id=\\"clip-0\\">
@@ -73,8 +74,7 @@ describe('ElementScreenshotRenderer', () => {
73
74
  <polygon points=\\"0,0.7 1,0.7 1,1 0,1\\"></polygon>
74
75
  <polygon points=\\"0,0.1 0.1,0.1 0.1,0.7 0,0.7\\"></polygon>
75
76
  <polygon points=\\"0.5,0.1 1,0.1 1,0.7 0.5,0.7\\"></polygon></clipPath> </defs> </svg> </div>
76
- <div class=\\"lh-element-screenshot__image\\" style=\\"width: 500px; height: 500px; background-position-y: 0px; background-position-x: 0px; background-size: 1000px 1000px;\\"></div>
77
- <div class=\\"lh-element-screenshot__element-marker\\" style=\\"width: 200px; height: 300px; left: 50px; top: 50px;\\"></div> </div> "
77
+ <div class=\\"lh-element-screenshot__element-marker\\" style=\\"width: 200px; height: 300px; left: 50px; top: 50px;\\"></div> </div> </div> "
78
78
  `);
79
79
  /* eslint-enable max-len */
80
80
  });
@@ -21,9 +21,12 @@ const NBSP = '\xa0';
21
21
  describe('util helpers', () => {
22
22
  it('formats a number', () => {
23
23
  const i18n = new I18n('en', {...Util.UIStrings});
24
- assert.strictEqual(i18n.formatNumber(10), '10');
25
- assert.strictEqual(i18n.formatNumber(100.01), '100');
24
+ assert.strictEqual(i18n.formatNumber(10), '10.0');
25
+ assert.strictEqual(i18n.formatNumber(100.01), '100.0');
26
26
  assert.strictEqual(i18n.formatNumber(13000.456), '13,000.5');
27
+ assert.strictEqual(i18n.formatInteger(10), '10');
28
+ assert.strictEqual(i18n.formatInteger(100.01), '100');
29
+ assert.strictEqual(i18n.formatInteger(13000.6), '13,001');
27
30
  });
28
31
 
29
32
  it('formats a date', () => {
@@ -100,9 +103,25 @@ describe('util helpers', () => {
100
103
 
101
104
  it('formats a duration', () => {
102
105
  const i18n = new I18n('en', {...Util.UIStrings});
103
- assert.equal(i18n.formatDuration(60 * 1000), `1${NBSP}m`);
104
- assert.equal(i18n.formatDuration(60 * 60 * 1000 + 5000), `1${NBSP}h 5${NBSP}s`);
105
- assert.equal(i18n.formatDuration(28 * 60 * 60 * 1000 + 5000), `1${NBSP}d 4${NBSP}h 5${NBSP}s`);
106
+ assert.equal(i18n.formatDuration(60 * 1000), '1m');
107
+ assert.equal(i18n.formatDuration(60 * 60 * 1000 + 5000), '1h 5s');
108
+ assert.equal(i18n.formatDuration(28 * 60 * 60 * 1000 + 5000), '1d 4h 5s');
109
+ });
110
+
111
+ it('formats a duration based on locale', () => {
112
+ let i18n = new I18n('de', {...Util.UIStrings});
113
+ assert.equal(i18n.formatDuration(60 * 1000), `1${NBSP}Min.`);
114
+ assert.equal(i18n.formatDuration(60 * 60 * 1000 + 5000), `1${NBSP}Std. 5${NBSP}Sek.`);
115
+ assert.equal(
116
+ i18n.formatDuration(28 * 60 * 60 * 1000 + 5000), `1${NBSP}T 4${NBSP}Std. 5${NBSP}Sek.`);
117
+
118
+ // Yes, this is actually backwards (s h d).
119
+ i18n = new I18n('ar', {...Util.UIStrings});
120
+ /* eslint-disable no-irregular-whitespace */
121
+ assert.equal(i18n.formatDuration(60 * 1000), `١${NBSP}د`);
122
+ assert.equal(i18n.formatDuration(60 * 60 * 1000 + 5000), `١${NBSP}س ٥${NBSP}ث`);
123
+ assert.equal(i18n.formatDuration(28 * 60 * 60 * 1000 + 5000), `١ ي ٤ س ٥ ث`);
124
+ /* eslint-enable no-irregular-whitespace */
106
125
  });
107
126
 
108
127
  it('formats numbers based on locale', () => {
@@ -113,7 +132,7 @@ describe('util helpers', () => {
113
132
  assert.strictEqual(i18n.formatNumber(number), '12.346,9');
114
133
  assert.strictEqual(i18n.formatBytesToKiB(number), `12,1${NBSP}KiB`);
115
134
  assert.strictEqual(i18n.formatMilliseconds(number), `12.350${NBSP}ms`);
116
- assert.strictEqual(i18n.formatSeconds(number), `12,3${NBSP}s`);
135
+ assert.strictEqual(i18n.formatSeconds(number), `12,3${NBSP}Sek.`);
117
136
  });
118
137
 
119
138
  it('uses decimal comma with en-XA test locale', () => {
@@ -124,7 +143,7 @@ describe('util helpers', () => {
124
143
  assert.strictEqual(i18n.formatNumber(number), '12.346,9');
125
144
  assert.strictEqual(i18n.formatBytesToKiB(number), `12,1${NBSP}KiB`);
126
145
  assert.strictEqual(i18n.formatMilliseconds(number), `12.350${NBSP}ms`);
127
- assert.strictEqual(i18n.formatSeconds(number), `12,3${NBSP}s`);
146
+ assert.strictEqual(i18n.formatSeconds(number), `12,3${NBSP}Sek.`);
128
147
  });
129
148
 
130
149
  it('should not crash on unknown locales', () => {
@@ -56,7 +56,7 @@ describe('util helpers', () => {
56
56
  });
57
57
 
58
58
  // eslint-disable-next-line max-len
59
- assert.equal(descriptions.networkThrottling, '565\xa0ms HTTP RTT, 1,400\xa0Kbps down, 600\xa0Kbps up (DevTools)');
59
+ assert.equal(descriptions.networkThrottling, '565\xa0ms HTTP RTT, 1,400\xa0kb/s down, 600\xa0kb/s up (DevTools)');
60
60
  assert.equal(descriptions.cpuThrottling, '4.5x slowdown (DevTools)');
61
61
  });
62
62
 
@@ -71,7 +71,7 @@ describe('util helpers', () => {
71
71
  });
72
72
 
73
73
  // eslint-disable-next-line max-len
74
- assert.equal(descriptions.networkThrottling, '150\xa0ms TCP RTT, 1,600\xa0Kbps throughput (Simulated)');
74
+ assert.equal(descriptions.networkThrottling, '150\xa0ms TCP RTT, 1,600\xa0kb/s throughput (Simulated)');
75
75
  assert.equal(descriptions.cpuThrottling, '2x slowdown (Simulated)');
76
76
  });
77
77
 
@@ -60,6 +60,8 @@ async function renderLHReport() {
60
60
  metaItemsEl,
61
61
  reportRootEl.querySelector('.lh-buttons')
62
62
  );
63
+ reportRootEl.querySelector('.lh-metrics-container')?.closest('.lh-category')?.classList
64
+ .add('lh--hoisted-meta');
63
65
  }
64
66
 
65
67
  container.append(reportRootEl);