lighthouse 10.0.0-dev.20230213 → 10.0.1

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 (75) hide show
  1. package/core/audits/dobetterweb/doctype.d.ts +3 -2
  2. package/core/audits/dobetterweb/doctype.js +15 -4
  3. package/core/audits/dobetterweb/paste-preventing-inputs.js +2 -2
  4. package/core/gather/driver/network-monitor.js +1 -3
  5. package/core/gather/gatherers/bf-cache-failures.js +7 -8
  6. package/package.json +1 -1
  7. package/shared/localization/locales/en-US.json +1 -1
  8. package/shared/localization/locales/en-XL.json +1 -1
  9. package/.codecov.yml +0 -22
  10. package/.cz-config.js +0 -24
  11. package/.gitattributes +0 -5
  12. package/.mailmap +0 -6
  13. package/CODE_OF_CONDUCT.md +0 -93
  14. package/eslint-local-rules.cjs +0 -106
  15. package/flow-report/test/app-test.d.ts +0 -7
  16. package/flow-report/test/app-test.tsx +0 -52
  17. package/flow-report/test/common-test.d.ts +0 -7
  18. package/flow-report/test/common-test.tsx +0 -103
  19. package/flow-report/test/flow-report-pptr-test.d.ts +0 -7
  20. package/flow-report/test/flow-report-pptr-test.ts +0 -59
  21. package/flow-report/test/header-test.d.ts +0 -7
  22. package/flow-report/test/header-test.tsx +0 -55
  23. package/flow-report/test/run-flow-report-tests.sh +0 -20
  24. package/flow-report/test/sample-flow.d.ts +0 -7
  25. package/flow-report/test/sample-flow.ts +0 -17
  26. package/flow-report/test/setup/env-setup.d.ts +0 -11
  27. package/flow-report/test/setup/env-setup.ts +0 -55
  28. package/flow-report/test/sidebar/flow-test.d.ts +0 -7
  29. package/flow-report/test/sidebar/flow-test.tsx +0 -65
  30. package/flow-report/test/sidebar/sidebar-test.d.ts +0 -7
  31. package/flow-report/test/sidebar/sidebar-test.tsx +0 -116
  32. package/flow-report/test/summary/category-test.d.ts +0 -7
  33. package/flow-report/test/summary/category-test.tsx +0 -206
  34. package/flow-report/test/summary/summary-test.d.ts +0 -7
  35. package/flow-report/test/summary/summary-test.tsx +0 -124
  36. package/flow-report/test/topbar-test.d.ts +0 -7
  37. package/flow-report/test/topbar-test.tsx +0 -84
  38. package/flow-report/test/util-test.d.ts +0 -7
  39. package/flow-report/test/util-test.tsx +0 -133
  40. package/flow-report/test/wrappers/category-score-test.d.ts +0 -7
  41. package/flow-report/test/wrappers/category-score-test.tsx +0 -84
  42. package/flow-report/test/wrappers/markdown-test.d.ts +0 -7
  43. package/flow-report/test/wrappers/markdown-test.tsx +0 -17
  44. package/report/test/clients/bundle-test.js +0 -65
  45. package/report/test/generator/file-namer-test.js +0 -27
  46. package/report/test/generator/report-generator-test.js +0 -160
  47. package/report/test/renderer/__snapshots__/report-renderer-axe-test.js.snap +0 -15
  48. package/report/test/renderer/category-renderer-test.js +0 -575
  49. package/report/test/renderer/components-test.js +0 -124
  50. package/report/test/renderer/crc-details-renderer-test.js +0 -116
  51. package/report/test/renderer/details-renderer-test.js +0 -882
  52. package/report/test/renderer/dom-test.js +0 -257
  53. package/report/test/renderer/element-screenshot-renderer-test.js +0 -183
  54. package/report/test/renderer/i18n-formatter-test.js +0 -179
  55. package/report/test/renderer/performance-category-renderer-test.js +0 -417
  56. package/report/test/renderer/pwa-category-renderer-test.js +0 -301
  57. package/report/test/renderer/report-renderer-axe-test.js +0 -85
  58. package/report/test/renderer/report-renderer-test.js +0 -268
  59. package/report/test/renderer/report-ui-features-test.js +0 -773
  60. package/report/test/renderer/report-utils-test.js +0 -219
  61. package/report/test/renderer/snippet-renderer-test.js +0 -291
  62. package/report/test/renderer/text-encoding-test.js +0 -43
  63. package/report/test-assets/faux-psi-template.html +0 -177
  64. package/report/test-assets/faux-psi.d.ts +0 -34
  65. package/report/test-assets/faux-psi.js +0 -168
  66. package/report/test-assets/lhr-3.0.0.json +0 -2332
  67. package/report/test-assets/lhr-4.3.0.json +0 -4754
  68. package/report/test-assets/lhr-5.0.0.json +0 -5994
  69. package/report/test-assets/lhr-6.0.0.json +0 -7222
  70. package/report/test-assets/lhr-8.5.0.json +0 -8679
  71. package/shared/test/localization/format-test.js +0 -430
  72. package/shared/test/localization/locales-test.js +0 -45
  73. package/shared/test/localization/swap-locale-test.js +0 -127
  74. package/shared/test/util-test.js +0 -214
  75. package/vercel.json +0 -6
@@ -1,882 +0,0 @@
1
- /**
2
- * @license Copyright 2017 The Lighthouse Authors. All Rights Reserved.
3
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
- */
6
-
7
- import assert from 'assert/strict';
8
-
9
- import jsdom from 'jsdom';
10
-
11
- import {DOM} from '../../renderer/dom.js';
12
- import {I18nFormatter} from '../../renderer/i18n-formatter.js';
13
- import {DetailsRenderer} from '../../renderer/details-renderer.js';
14
- import {Globals} from '../../renderer/report-globals.js';
15
-
16
- describe('DetailsRenderer', () => {
17
- let renderer;
18
-
19
- function createRenderer(options) {
20
- const {document} = new jsdom.JSDOM().window;
21
- const dom = new DOM(document);
22
- renderer = new DetailsRenderer(dom, options);
23
- }
24
-
25
- before(() => {
26
- Globals.apply({
27
- providedStrings: {},
28
- i18n: new I18nFormatter('en'),
29
- reportJson: null,
30
- });
31
- createRenderer();
32
- });
33
-
34
- after(() => {
35
- Globals.i18n = undefined;
36
- });
37
-
38
- describe('render', () => {
39
- it('renders filmstrips', () => {
40
- const el = renderer.render({
41
- type: 'filmstrip',
42
- items: [
43
- {timing: 1020, data: 'data:image/jpeg;base64,foobar'},
44
- {timing: 3030, data: 'data:image/jpeg;base64,foobaz'},
45
- ],
46
- });
47
-
48
- assert.ok(el.localName === 'div');
49
- assert.ok(el.classList.contains('lh-filmstrip'));
50
-
51
- const frames = [...el.querySelectorAll('.lh-filmstrip__frame')];
52
- assert.equal(frames.length, 2);
53
-
54
- const thumbnails = [...el.querySelectorAll('.lh-filmstrip__thumbnail')];
55
- assert.equal(thumbnails.length, 2);
56
- assert.equal(thumbnails[0].src, 'data:image/jpeg;base64,foobar');
57
- assert.ok(thumbnails[0].alt, 'did not set alt text');
58
- });
59
-
60
- it('renders tables', () => {
61
- const el = renderer.render({
62
- type: 'table',
63
- headings: [
64
- {text: 'First', key: 'a', valueType: 'text'},
65
- {text: 'Second', key: 'b', valueType: 'text'},
66
- {text: 'Preview', key: 'c', valueType: 'thumbnail'},
67
- ],
68
- items: [
69
- {
70
- a: 'value A.1',
71
- b: 'value A.2',
72
- c: 'http://example.com/image.jpg',
73
- },
74
- {
75
- a: 'value B.1',
76
- b: 'value B.2',
77
- c: 'unknown',
78
- },
79
- ],
80
- });
81
-
82
- assert.equal(el.localName, 'table', 'did not render table');
83
- assert.ok(el.querySelector('img'), 'did not render recursive items');
84
- assert.equal(el.querySelectorAll('th').length, 3, 'did not render header items');
85
- assert.equal(el.querySelectorAll('td').length, 6, 'did not render table cells');
86
- assert.equal(el.querySelectorAll('.lh-table-column--text').length, 6, '--text not set');
87
- assert.equal(el.querySelectorAll('.lh-table-column--thumbnail').length, 3,
88
- '--thumbnail not set');
89
- });
90
-
91
- it('renders with default granularity', () => {
92
- const el = renderer.render({
93
- type: 'table',
94
- headings: [
95
- {text: '', key: 'bytes', valueType: 'bytes'},
96
- {text: '', key: 'numeric', valueType: 'numeric'},
97
- {text: '', key: 'ms', valueType: 'ms'},
98
- // Verify that 0 is ignored.
99
- {text: '', key: 'ms', valueType: 'ms', granularity: 0},
100
- ],
101
- items: [
102
- {
103
- bytes: 1234.567,
104
- numeric: 1234.567,
105
- ms: 1234.567,
106
- },
107
- ],
108
- });
109
-
110
- assert.equal(el.querySelectorAll('td').length, 4, 'did not render table cells');
111
- assert.equal(el.querySelectorAll('td')[0].textContent, '1.2\xa0KiB');
112
- assert.equal(el.querySelectorAll('td')[1].textContent, '1,234.6');
113
- assert.equal(el.querySelectorAll('td')[2].textContent, '1,230\xa0ms');
114
- assert.equal(el.querySelectorAll('td')[3].textContent, '1,230\xa0ms');
115
- });
116
-
117
- it('renders with custom granularity', () => {
118
- const el = renderer.render({
119
- type: 'table',
120
- headings: [
121
- {text: '', key: 'bytes', valueType: 'bytes', granularity: 0.01},
122
- {text: '', key: 'numeric', valueType: 'numeric', granularity: 100},
123
- {text: '', key: 'ms', valueType: 'ms', granularity: 1},
124
- ],
125
- items: [
126
- {
127
- bytes: 1234.567,
128
- numeric: 1234.567,
129
- ms: 1234.567,
130
- },
131
- ],
132
- });
133
-
134
- assert.equal(el.querySelectorAll('td').length, 3, 'did not render table cells');
135
- assert.equal(el.querySelectorAll('td')[0].textContent, '1.21\xa0KiB');
136
- assert.equal(el.querySelectorAll('td')[1].textContent, '1,200');
137
- assert.equal(el.querySelectorAll('td')[2].textContent, '1,235\xa0ms');
138
- });
139
-
140
- it('renders critical request chains', () => {
141
- const details = {
142
- type: 'criticalrequestchain',
143
- longestChain: {
144
- duration: 2,
145
- length: 1,
146
- transferSize: 221,
147
- },
148
- chains: {
149
- F3B687683512E0F003DD41EB23E2091A: {
150
- request: {
151
- url: 'https://example.com',
152
- startTime: 0,
153
- endTime: 2,
154
- responseReceivedTime: 1,
155
- transferSize: 221,
156
- },
157
- children: {},
158
- },
159
- },
160
- };
161
-
162
- const crcEl = renderer.render(details);
163
- assert.ok(crcEl.classList.contains('lh-crc-container'));
164
- assert.strictEqual(crcEl.querySelectorAll('.lh-crc-node').length, 1);
165
- });
166
-
167
- it('renders opportunity details as a table', () => {
168
- const details = {
169
- type: 'opportunity',
170
- headings: [
171
- {key: 'url', valueType: 'url', label: 'URL'},
172
- {key: 'totalBytes', valueType: 'bytes', label: 'Size (KiB)'},
173
- {key: 'wastedBytes', valueType: 'bytes', label: 'Potential Savings (KiB)'},
174
- ],
175
- items: [{
176
- url: 'https://example.com',
177
- totalBytes: 71654,
178
- wastedBytes: 30470,
179
- wastedPercent: 42,
180
- }],
181
- overallSavingsMs: 150,
182
- overallSavingsBytes: 30470,
183
- };
184
-
185
- const oppEl = renderer.render(details);
186
- assert.equal(oppEl.localName, 'table');
187
- assert.ok(oppEl.querySelector('.lh-text__url').title === 'https://example.com', 'did not render recursive items');
188
- assert.equal(oppEl.querySelectorAll('th').length, 3, 'did not render header items');
189
- assert.equal(oppEl.querySelectorAll('td').length, 3, 'did not render table cells');
190
- assert.equal(oppEl.querySelectorAll('.lh-table-column--url').length, 2, 'url column not set');
191
- assert.equal(oppEl.querySelectorAll('.lh-table-column--bytes').length, 4, 'bytes not set');
192
- });
193
-
194
- it('renders lists', () => {
195
- const table = {
196
- type: 'table',
197
- headings: [{text: '', key: 'numeric', valueType: 'numeric'}],
198
- items: [{numeric: 1234.567}],
199
- };
200
-
201
- const el = renderer.render({
202
- type: 'list',
203
- items: [table, table],
204
- });
205
-
206
- assert.equal(el.localName, 'div');
207
- assert.ok(el.classList.contains('lh-list'), 'has list class');
208
- assert.ok(el.children.length, 2, 'renders all items');
209
- for (const child of el.children) {
210
- assert.ok(child.classList.contains('lh-table'));
211
- assert.equal(child.textContent, '1,234.6');
212
- }
213
- });
214
-
215
- it('does not render internal-only screenshot details', () => {
216
- const details = {
217
- type: 'screenshot',
218
- timestamp: 185600000,
219
- data: 'data:image/jpeg;base64,/9j/4AAQSkZJRYP/2Q==',
220
- };
221
-
222
- const screenshotEl = renderer.render(details);
223
- assert.strictEqual(screenshotEl, null);
224
- });
225
-
226
- it('does not render internal-only diagnostic details', () => {
227
- const details = {
228
- type: 'debugdata',
229
- items: [{
230
- failures: ['No manifest was fetched'],
231
- isParseFailure: true,
232
- parseFailureReason: 'No manifest was fetched',
233
- }],
234
- };
235
-
236
- const diagnosticEl = renderer.render(details);
237
- assert.strictEqual(diagnosticEl, null);
238
- });
239
-
240
- describe('unknown types', () => {
241
- // console.error is set to a no-op, to avoid distracting log spam in the test output.
242
- let consoleError;
243
- beforeEach(() => {
244
- consoleError = console.error;
245
- console.error = () => {};
246
- });
247
- afterEach(() => {
248
- console.error = consoleError;
249
- });
250
-
251
- it('renders an unknown details type', () => {
252
- // Disallowed by type system, but test that we get an error message out just in case.
253
- const details = {
254
- type: 'imaginary',
255
- items: 5,
256
- };
257
-
258
- const el = renderer.render(details);
259
- const summaryEl = el.querySelector('summary');
260
- expect(summaryEl.textContent)
261
- .toContain('We don\'t know how to render audit details of type `imaginary`');
262
- assert.strictEqual(el.lastChild.textContent, JSON.stringify(details, null, 2));
263
- });
264
- });
265
- });
266
-
267
- describe('Table rendering', () => {
268
- it('renders text values', () => {
269
- const details = {
270
- type: 'table',
271
- headings: [{key: 'content', valueType: 'text', label: 'Heading'}],
272
- items: [{content: 'My text content'}],
273
- };
274
-
275
- const el = renderer.render(details);
276
- const textEls = el.querySelectorAll('.lh-text');
277
- assert.strictEqual(textEls[0].textContent, 'Heading');
278
- assert.strictEqual(textEls[1].textContent, 'My text content');
279
- });
280
-
281
- it('renders not much if items are empty', () => {
282
- const details = {
283
- type: 'table',
284
- headings: [{key: 'content', valueType: 'text', label: 'Heading'}],
285
- items: [],
286
- };
287
-
288
- const el = renderer.render(details);
289
- assert.strictEqual(el.outerHTML, '<span></span>');
290
- });
291
-
292
- it('renders an empty cell if item is missing a property', () => {
293
- const details = {
294
- type: 'table',
295
- headings: [{key: 'content', valueType: 'text', label: 'Heading'}],
296
- items: [
297
- {},
298
- {content: undefined},
299
- {content: 'a thing'},
300
- ],
301
- };
302
-
303
- const el = renderer.render(details);
304
- const itemEls = el.querySelectorAll('td');
305
- assert.strictEqual(itemEls.length, 3);
306
-
307
- // missing prop
308
- assert.ok(itemEls[0].classList.contains('lh-table-column--empty'));
309
- assert.strictEqual(itemEls[0].innerHTML, '');
310
-
311
- // undefined prop
312
- assert.ok(itemEls[1].classList.contains('lh-table-column--empty'));
313
- assert.strictEqual(itemEls[1].innerHTML, '');
314
-
315
- // defined prop
316
- assert.ok(itemEls[2].classList.contains('lh-table-column--text'));
317
- assert.strictEqual(itemEls[2].textContent, 'a thing');
318
- });
319
-
320
- it('renders code values from a string', () => {
321
- const details = {
322
- type: 'table',
323
- headings: [{key: 'content', valueType: 'code', label: 'Heading'}],
324
- items: [{content: 'code snippet'}],
325
- };
326
-
327
- const el = renderer.render(details);
328
- const codeEl = el.querySelector('.lh-code');
329
- assert.ok(codeEl.localName === 'pre');
330
- assert.equal(codeEl.textContent, 'code snippet');
331
- });
332
-
333
- it('renders code values from a code details object', () => {
334
- const code = {
335
- type: 'code',
336
- value: 'code object',
337
- };
338
-
339
- const details = {
340
- type: 'table',
341
- headings: [{key: 'content', valueType: 'code', label: 'Heading'}],
342
- items: [{content: code}],
343
- };
344
-
345
- const el = renderer.render(details);
346
- const codeEl = el.querySelector('.lh-code');
347
- assert.ok(codeEl.localName === 'pre');
348
- assert.equal(codeEl.textContent, 'code object');
349
- });
350
-
351
- it('renders thumbnail values', () => {
352
- const details = {
353
- type: 'table',
354
- headings: [{key: 'content', valueType: 'thumbnail', label: 'Heading'}],
355
- items: [{content: 'http://example.com/my-image.jpg'}],
356
- };
357
-
358
- const el = renderer.render(details);
359
- const thumbnailEl = el.querySelector('img');
360
- assert.ok(thumbnailEl.classList.contains('lh-thumbnail'));
361
- assert.strictEqual(thumbnailEl.src, 'http://example.com/my-image.jpg');
362
- assert.strictEqual(thumbnailEl.title, 'http://example.com/my-image.jpg');
363
- assert.strictEqual(thumbnailEl.alt, '');
364
- });
365
-
366
- it('renders link values', () => {
367
- const linkText = 'Example Site';
368
- const linkUrl = 'https://example.com/';
369
- const link = {
370
- type: 'link',
371
- text: linkText,
372
- url: linkUrl,
373
- };
374
- const details = {
375
- type: 'table',
376
- headings: [{key: 'content', valueType: 'link', label: 'Heading'}],
377
- items: [{content: link}],
378
- };
379
-
380
- const el = renderer.render(details);
381
- const anchorEl = el.querySelector('a');
382
- assert.equal(anchorEl.textContent, linkText);
383
- assert.equal(anchorEl.href, linkUrl);
384
- assert.equal(anchorEl.rel, 'noopener');
385
- assert.equal(anchorEl.target, '_blank');
386
- });
387
-
388
- it('renders link value as text if URL is not allowed', () => {
389
- const linkText = 'Evil Link';
390
- const linkUrl = 'javascript:alert(5)';
391
- const link = {
392
- type: 'link',
393
- text: linkText,
394
- url: linkUrl,
395
- };
396
- const details = {
397
- type: 'table',
398
- headings: [{key: 'content', valueType: 'link', label: 'Heading'}],
399
- items: [{content: link}],
400
- };
401
-
402
- const el = renderer.render(details);
403
- const linkEl = el.querySelector('td.lh-table-column--link > .lh-text');
404
- assert.equal(linkEl.localName, 'div');
405
- assert.equal(linkEl.textContent, linkText);
406
- });
407
-
408
- it('renders link value as text if URL is invalid', () => {
409
- const linkText = 'Invalid Link';
410
- const linkUrl = 'link nonsense';
411
- const link = {
412
- type: 'link',
413
- text: linkText,
414
- url: linkUrl,
415
- };
416
- const details = {
417
- type: 'table',
418
- headings: [{key: 'content', valueType: 'link', label: 'Heading'}],
419
- items: [{content: link}],
420
- };
421
-
422
- const el = renderer.render(details);
423
- const linkEl = el.querySelector('td.lh-table-column--link > .lh-text');
424
- assert.equal(linkEl.localName, 'div');
425
- assert.equal(linkEl.textContent, linkText);
426
- });
427
-
428
- describe('renders node values', () => {
429
- it('renders', () => {
430
- const node = {
431
- type: 'node',
432
- path: '3,HTML,1,BODY,5,DIV,0,H2',
433
- selector: 'h2',
434
- snippet: '<h2>Do better web tester page</h2>',
435
- };
436
- const details = {
437
- type: 'table',
438
- headings: [{key: 'content', valueType: 'node', label: 'Heading'}],
439
- items: [{content: node}],
440
- };
441
-
442
- const el = renderer.render(details);
443
- const nodeEl = el.querySelector('.lh-node');
444
- assert.strictEqual(nodeEl.localName, 'span');
445
- assert.equal(nodeEl.textContent, node.snippet);
446
- assert.equal(nodeEl.title, node.selector);
447
- assert.equal(nodeEl.getAttribute('data-path'), node.path);
448
- assert.equal(nodeEl.getAttribute('data-selector'), node.selector);
449
- assert.equal(nodeEl.getAttribute('data-snippet'), node.snippet);
450
- });
451
-
452
- it('renders screenshot using FullPageScreenshot rect data', () => {
453
- createRenderer({
454
- fullPageScreenshot: {
455
- screenshot: {
456
- data: '',
457
- width: 1000,
458
- height: 1000,
459
- },
460
- nodes: {
461
- 'page-0-0': {left: 1, right: 101, top: 1, bottom: 101, width: 100, height: 100},
462
- },
463
- },
464
- });
465
-
466
- const node = {
467
- type: 'node',
468
- lhId: 'page-0-0',
469
- boundingRect: {left: 0, right: 100, top: 0, bottom: 100, width: 100, height: 100},
470
- };
471
- const details = {
472
- type: 'table',
473
- headings: [{key: 'content', valueType: 'node', label: 'Heading'}],
474
- items: [{content: node}],
475
- };
476
-
477
- const el = renderer.render(details);
478
- const nodeEl = el.querySelector('.lh-node');
479
- const screenshotEl = nodeEl.querySelector('.lh-element-screenshot');
480
- assert.equal(screenshotEl.dataset['rectLeft'], '1');
481
- assert.equal(screenshotEl.dataset['rectTop'], '1');
482
- });
483
-
484
- it('does not render screenshot if rect missing in FullPageScreenshot', () => {
485
- createRenderer({
486
- fullPageScreenshot: {
487
- screenshot: {
488
- data: '',
489
- width: 1000,
490
- height: 1000,
491
- },
492
- nodes: {
493
- 'ignore-me': {left: 1, right: 101, top: 1, bottom: 101, width: 100, height: 100},
494
- },
495
- },
496
- });
497
-
498
- const node = {
499
- type: 'node',
500
- lhId: 'page-0-0',
501
- boundingRect: {left: 0, right: 100, top: 0, bottom: 100, width: 100, height: 100},
502
- };
503
- const details = {
504
- type: 'table',
505
- headings: [{key: 'content', valueType: 'node', label: 'Heading'}],
506
- items: [{content: node}],
507
- };
508
-
509
- const el = renderer.render(details);
510
- const nodeEl = el.querySelector('.lh-node');
511
- const screenshotEl = nodeEl.querySelector('.lh-element-screenshot');
512
- expect(screenshotEl).toBeNull();
513
- });
514
- });
515
-
516
- it('renders source-location values', () => {
517
- const sourceLocation = {
518
- type: 'source-location',
519
- url: 'https://www.example.com/script.js',
520
- urlProvider: 'network',
521
- line: 10,
522
- column: 5,
523
- };
524
- const details = {
525
- type: 'table',
526
- headings: [{key: 'content', valueType: 'source-location', label: 'Heading'}],
527
- items: [{content: sourceLocation}],
528
- };
529
-
530
- const el = renderer.render(details);
531
- const sourceLocationEl = el.querySelector('.lh-source-location');
532
- const anchorEl = sourceLocationEl.querySelector('a');
533
- assert.strictEqual(sourceLocationEl.localName, 'div');
534
- assert.equal(anchorEl.href, 'https://www.example.com/script.js');
535
- assert.equal(sourceLocationEl.textContent, '/script.js:11:5(www.example.com)');
536
- assert.equal(sourceLocationEl.getAttribute('data-source-url'), sourceLocation.url);
537
- assert.equal(sourceLocationEl.getAttribute('data-source-line'), `${sourceLocation.line}`);
538
- assert.equal(sourceLocationEl.getAttribute('data-source-column'), `${sourceLocation.column}`);
539
- });
540
-
541
- it('renders source-location values using source map data', () => {
542
- const sourceLocation = {
543
- type: 'source-location',
544
- url: 'https://www.example.com/script.js',
545
- urlProvider: 'network',
546
- line: 10,
547
- column: 5,
548
- original: {
549
- file: 'main.js',
550
- line: 100,
551
- column: 10,
552
- },
553
- };
554
- const details = {
555
- type: 'table',
556
- headings: [{key: 'content', valueType: 'source-location', label: 'Heading'}],
557
- items: [{content: sourceLocation}],
558
- };
559
-
560
- const el = renderer.render(details);
561
- const sourceLocationEl = el.querySelector('.lh-source-location.lh-link');
562
- assert.strictEqual(sourceLocationEl.localName, 'a');
563
- assert.equal(sourceLocationEl.href, 'https://www.example.com/script.js');
564
- assert.equal(sourceLocationEl.textContent, 'main.js:101:10');
565
- assert.equal(sourceLocationEl.title, 'maps to generated location https://www.example.com/script.js:11:5');
566
- // DevTools should still use the generated location.
567
- assert.equal(sourceLocationEl.getAttribute('data-source-url'), sourceLocation.url);
568
- assert.equal(sourceLocationEl.getAttribute('data-source-line'), `${sourceLocation.line}`);
569
- assert.equal(sourceLocationEl.getAttribute('data-source-column'), `${sourceLocation.column}`);
570
- });
571
-
572
- it('renders source-location with lh-link class for relative url', () => {
573
- const sourceLocation = {
574
- type: 'source-location',
575
- url: '/root-relative-url/script.js',
576
- urlProvider: 'network',
577
- line: 0,
578
- column: 0,
579
- };
580
- const details = {
581
- type: 'table',
582
- headings: [{key: 'content', valueType: 'source-location', label: 'Heading'}],
583
- items: [{content: sourceLocation}],
584
- };
585
-
586
- const el = renderer.render(details);
587
- const sourceLocationEl = el.querySelector('.lh-source-location');
588
- const anchorEl = sourceLocationEl.querySelector('.lh-link');
589
- assert.ok(anchorEl);
590
- assert.strictEqual(sourceLocationEl.localName, 'div');
591
- assert.equal(sourceLocationEl.textContent, '/root-relative-url/script.js:1:0');
592
- assert.equal(sourceLocationEl.getAttribute('data-source-url'), sourceLocation.url);
593
- assert.equal(sourceLocationEl.getAttribute('data-source-line'), `${sourceLocation.line}`);
594
- assert.equal(sourceLocationEl.getAttribute('data-source-column'), `${sourceLocation.column}`);
595
- });
596
-
597
- it('renders source-location with lh-link class for invalid urls', () => {
598
- const sourceLocation = {
599
- type: 'source-location',
600
- url: 'thisisclearlynotavalidurl',
601
- urlProvider: 'network',
602
- line: 0,
603
- column: 0,
604
- };
605
- const details = {
606
- type: 'table',
607
- headings: [{key: 'content', valueType: 'source-location', label: 'Heading'}],
608
- items: [{content: sourceLocation}],
609
- };
610
-
611
- const el = renderer.render(details);
612
- const sourceLocationEl = el.querySelector('.lh-source-location');
613
- const anchorEl = sourceLocationEl.querySelector('.lh-link');
614
- assert.ok(anchorEl);
615
- assert.strictEqual(sourceLocationEl.localName, 'div');
616
- assert.equal(sourceLocationEl.textContent, 'thisisclearlynotavalidurl:1:0');
617
- assert.equal(sourceLocationEl.getAttribute('data-source-url'), sourceLocation.url);
618
- assert.equal(sourceLocationEl.getAttribute('data-source-line'), `${sourceLocation.line}`);
619
- assert.equal(sourceLocationEl.getAttribute('data-source-column'), `${sourceLocation.column}`);
620
- });
621
-
622
- it('renders source-location values that aren\'t network resources', () => {
623
- const sourceLocation = {
624
- type: 'source-location',
625
- url: 'https://www.example.com/script.js',
626
- urlProvider: 'comment',
627
- line: 0,
628
- column: 0,
629
- };
630
- const details = {
631
- type: 'table',
632
- headings: [{key: 'content', valueType: 'source-location', label: 'Heading'}],
633
- items: [{content: sourceLocation}],
634
- };
635
-
636
- const el = renderer.render(details);
637
- const sourceLocationEl = el.querySelector('.lh-source-location');
638
- const anchorEl = sourceLocationEl.querySelector('a');
639
- assert.ok(!anchorEl);
640
- assert.strictEqual(sourceLocationEl.localName, 'div');
641
- assert.equal(sourceLocationEl.textContent, 'https://www.example.com/script.js:1:0 (from sourceURL)');
642
- assert.equal(sourceLocationEl.getAttribute('data-source-url'), sourceLocation.url);
643
- assert.equal(sourceLocationEl.getAttribute('data-source-line'), `${sourceLocation.line}`);
644
- assert.equal(sourceLocationEl.getAttribute('data-source-column'), `${sourceLocation.column}`);
645
- });
646
-
647
- it('renders text URL values from a string', () => {
648
- const urlText = 'https://example.com/';
649
- const displayUrlText = 'https://example.com';
650
-
651
- const details = {
652
- type: 'table',
653
- headings: [{key: 'content', valueType: 'url', label: 'Heading'}],
654
- items: [{content: urlText}],
655
- };
656
-
657
- const el = renderer.render(details);
658
- const urlEl = el.querySelector('td.lh-table-column--url > .lh-text__url');
659
-
660
- assert.equal(urlEl.localName, 'div');
661
- assert.equal(urlEl.title, urlText);
662
- assert.equal(urlEl.dataset.url, urlText);
663
- assert.equal(urlEl.firstChild.nodeName, 'A');
664
- assert.equal(urlEl.firstChild.href, urlText);
665
- assert.equal(urlEl.firstChild.rel, 'noopener');
666
- assert.equal(urlEl.firstChild.target, '_blank');
667
- assert.equal(urlEl.textContent, displayUrlText);
668
- });
669
-
670
- it('renders text URL values from a url details object', () => {
671
- const urlText = 'https://example.com/';
672
- const displayUrlText = 'https://example.com';
673
- const url = {
674
- type: 'url',
675
- value: urlText,
676
- };
677
-
678
- const details = {
679
- type: 'table',
680
- headings: [{key: 'content', valueType: 'url', label: 'Heading'}],
681
- items: [{content: url}],
682
- overallSavingsMs: 100,
683
- };
684
-
685
- const el = renderer.render(details);
686
- const urlEl = el.querySelector('td.lh-table-column--url > .lh-text__url');
687
-
688
- assert.equal(urlEl.localName, 'div');
689
- assert.equal(urlEl.title, urlText);
690
- assert.equal(urlEl.dataset.url, urlText);
691
- assert.equal(urlEl.firstChild.nodeName, 'A');
692
- assert.equal(urlEl.textContent, displayUrlText);
693
- });
694
-
695
- it('renders text URL values as code if not an allowed URL', () => {
696
- const urlText = 'invalid-url://example.com/';
697
-
698
- const details = {
699
- type: 'table',
700
- headings: [{key: 'content', valueType: 'url', label: 'Heading'}],
701
- items: [{content: urlText}],
702
- };
703
-
704
- const el = renderer.render(details);
705
- const codeItemEl = el.querySelector('td.lh-table-column--url');
706
- assert.strictEqual(codeItemEl.innerHTML, '<pre class="lh-code">invalid-url://example.com/</pre>');
707
- });
708
-
709
- describe('unknown types', () => {
710
- let consoleError;
711
- beforeEach(() => {
712
- consoleError = console.error;
713
- console.error = () => {};
714
- });
715
- afterEach(() => {
716
- console.error = consoleError;
717
- });
718
-
719
- it('renders an unknown heading valueType', () => {
720
- // Disallowed by type system, but test that we get an error message out just in case.
721
- const details = {
722
- type: 'table',
723
- headings: [{key: 'content', valueType: 'notRealValueType', label: 'Heading'}],
724
- items: [{content: 'some string'}],
725
- };
726
-
727
- const el = renderer.render(details);
728
- const unknownEl = el.querySelector('td.lh-table-column--notRealValueType .lh-unknown');
729
- const summaryEl = unknownEl.querySelector('summary');
730
- expect(summaryEl.textContent)
731
- .toContain('We don\'t know how to render audit details of type `notRealValueType`');
732
- assert.strictEqual(unknownEl.lastChild.textContent, '"some string"');
733
- });
734
-
735
- it('renders an unknown item object type', () => {
736
- // Disallowed by type system, but test that we get an error message out just in case.
737
- const item = {
738
- type: 'imaginaryItem',
739
- items: 'alllll the items',
740
- };
741
-
742
- const details = {
743
- type: 'table',
744
- headings: [{key: 'content', valueType: 'url', label: 'Heading'}],
745
- items: [{content: item}],
746
- };
747
-
748
- const el = renderer.render(details);
749
- const unknownEl = el.querySelector('td.lh-table-column--url .lh-unknown');
750
- const summaryEl = unknownEl.querySelector('summary');
751
- expect(summaryEl.textContent)
752
- .toContain('We don\'t know how to render audit details of type `imaginaryItem`');
753
- assert.strictEqual(unknownEl.lastChild.textContent, JSON.stringify(item, null, 2));
754
- });
755
- });
756
-
757
- it('uses the item\'s type over the heading type', () => {
758
- const details = {
759
- type: 'table',
760
- // valueType is overriden by code object
761
- headings: [{key: 'content', valueType: 'url', label: 'Heading'}],
762
- items: [
763
- {content: {type: 'code', value: 'https://codeobject.com'}},
764
- {content: 'https://example.com'},
765
- ],
766
- };
767
-
768
- const el = renderer.render(details);
769
- const itemElements = el.querySelectorAll('td.lh-table-column--url');
770
-
771
- // First item's value uses its own type.
772
- const codeEl = itemElements[0].firstChild;
773
- assert.equal(codeEl.localName, 'pre');
774
- assert.ok(codeEl.classList.contains('lh-code'));
775
- assert.equal(codeEl.textContent, 'https://codeobject.com');
776
-
777
- // Second item uses the heading's specified type for the column.
778
- const urlEl = itemElements[1].firstChild;
779
- assert.equal(urlEl.localName, 'div');
780
- assert.ok(urlEl.classList.contains('lh-text__url'));
781
- assert.equal(urlEl.title, 'https://example.com');
782
- assert.equal(urlEl.textContent, 'https://example.com');
783
- });
784
-
785
- describe('subitems', () => {
786
- function makeSubitems(items) {
787
- return {
788
- type: 'subitems',
789
- items,
790
- };
791
- }
792
-
793
- it('renders', () => {
794
- const details = {
795
- type: 'table',
796
- headings: [
797
- {key: 'url', valueType: 'url', subItemsHeading: {key: 'source', valueType: 'code'}},
798
- ],
799
- items: [
800
- {
801
- url: 'https://www.example.com',
802
- subItems: makeSubitems([
803
- {source: 'a'},
804
- {source: 'b'},
805
- {source: 'c'},
806
- ]),
807
- },
808
- ],
809
- };
810
-
811
- const el = renderer.render(details);
812
-
813
- const rowEls = el.querySelectorAll('tbody tr');
814
- let rowEl;
815
- let columnEl;
816
-
817
- // First row contains a 'url' item type.
818
- rowEl = rowEls[0];
819
- columnEl = rowEl.querySelector('td.lh-table-column--url');
820
- const codeEl = columnEl.firstChild;
821
- assert.equal(codeEl.localName, 'div');
822
- assert.ok(codeEl.classList.contains('lh-text__url'));
823
- assert.equal(codeEl.textContent, 'https://www.example.com');
824
-
825
- // The subItems contain a 'code' item type.
826
- for (let i = 0; i < details.items[0].subItems.items; i++) {
827
- const source = details.items[0].subItems.items[i].source;
828
- rowEl = rowEls[i + 1];
829
- columnEl = rowEl.querySelector('td.lh-table-column--code');
830
- assert.ok(rowEl.classList.contains('lh-sub-item-row'));
831
- assert.ok(columnEl.firstChild.classList.contains('lh-code'));
832
- assert.equal(rowEl.textContent, source);
833
- }
834
- });
835
-
836
- it('renders, uses heading properties as fallback', () => {
837
- const details = {
838
- type: 'table',
839
- headings: [{key: 'url', valueType: 'url', subItemsHeading: {key: 'source'}}],
840
- items: [
841
- {
842
- url: 'https://www.example.com',
843
- subItems: makeSubitems([
844
- {source: 'https://www.a.com'},
845
- {source: {type: 'code', value: 'https://www.b.com'}},
846
- {source: 'https://www.c.com'},
847
- ]),
848
- },
849
- ],
850
- };
851
-
852
- const el = renderer.render(details);
853
- const rowEls = el.querySelectorAll('tbody tr');
854
- let rowEl;
855
- let columnEl;
856
-
857
- // First row contains a 'url' item type.
858
- rowEl = rowEls[0];
859
- columnEl = rowEl.querySelector('td.lh-table-column--url');
860
- const codeEl = columnEl.firstChild;
861
- assert.equal(codeEl.localName, 'div');
862
- assert.ok(codeEl.classList.contains('lh-text__url'));
863
- assert.equal(codeEl.textContent, 'https://www.example.com');
864
-
865
- // The sub-rows contain a 'url' item type, except for the second one, which is 'code'.
866
- for (let i = 0; i < details.items[0].subItems.items.length; i++) {
867
- const source = details.items[0].subItems.items[i].source;
868
- rowEl = rowEls[i + 1];
869
- assert.ok(rowEl.classList.contains('lh-sub-item-row'));
870
- columnEl = rowEl.querySelector('td');
871
- if (typeof source === 'string') {
872
- assert.ok(columnEl.firstChild.classList.contains('lh-text__url'));
873
- assert.equal(rowEl.textContent, source);
874
- } else {
875
- assert.ok(columnEl.firstChild.classList.contains('lh-code'));
876
- assert.equal(columnEl.textContent, source.value);
877
- }
878
- }
879
- });
880
- });
881
- });
882
- });