lighthouse 10.0.0 → 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 (69) hide show
  1. package/changelog-pre10.md +6085 -0
  2. package/core/audits/dobetterweb/doctype.d.ts +3 -2
  3. package/core/audits/dobetterweb/doctype.js +15 -4
  4. package/core/audits/dobetterweb/paste-preventing-inputs.js +2 -2
  5. package/core/gather/driver/network-monitor.js +1 -3
  6. package/core/gather/gatherers/bf-cache-failures.js +7 -8
  7. package/package.json +2 -2
  8. package/shared/localization/locales/en-US.json +1 -1
  9. package/shared/localization/locales/en-XL.json +1 -1
  10. package/.codecov.yml +0 -22
  11. package/.cz-config.js +0 -24
  12. package/.gitattributes +0 -5
  13. package/.mailmap +0 -6
  14. package/CODE_OF_CONDUCT.md +0 -93
  15. package/eslint-local-rules.cjs +0 -106
  16. package/flow-report/test/app-test.d.ts +0 -7
  17. package/flow-report/test/app-test.tsx +0 -52
  18. package/flow-report/test/common-test.d.ts +0 -7
  19. package/flow-report/test/common-test.tsx +0 -103
  20. package/flow-report/test/flow-report-pptr-test.d.ts +0 -7
  21. package/flow-report/test/flow-report-pptr-test.ts +0 -59
  22. package/flow-report/test/header-test.d.ts +0 -7
  23. package/flow-report/test/header-test.tsx +0 -55
  24. package/flow-report/test/run-flow-report-tests.sh +0 -20
  25. package/flow-report/test/sample-flow.d.ts +0 -7
  26. package/flow-report/test/sample-flow.ts +0 -17
  27. package/flow-report/test/setup/env-setup.d.ts +0 -11
  28. package/flow-report/test/setup/env-setup.ts +0 -55
  29. package/flow-report/test/sidebar/flow-test.d.ts +0 -7
  30. package/flow-report/test/sidebar/flow-test.tsx +0 -65
  31. package/flow-report/test/sidebar/sidebar-test.d.ts +0 -7
  32. package/flow-report/test/sidebar/sidebar-test.tsx +0 -116
  33. package/flow-report/test/summary/category-test.d.ts +0 -7
  34. package/flow-report/test/summary/category-test.tsx +0 -206
  35. package/flow-report/test/summary/summary-test.d.ts +0 -7
  36. package/flow-report/test/summary/summary-test.tsx +0 -124
  37. package/flow-report/test/topbar-test.d.ts +0 -7
  38. package/flow-report/test/topbar-test.tsx +0 -84
  39. package/flow-report/test/util-test.d.ts +0 -7
  40. package/flow-report/test/util-test.tsx +0 -133
  41. package/flow-report/test/wrappers/category-score-test.d.ts +0 -7
  42. package/flow-report/test/wrappers/category-score-test.tsx +0 -84
  43. package/flow-report/test/wrappers/markdown-test.d.ts +0 -7
  44. package/flow-report/test/wrappers/markdown-test.tsx +0 -17
  45. package/report/test/clients/bundle-test.js +0 -65
  46. package/report/test/generator/file-namer-test.js +0 -27
  47. package/report/test/generator/report-generator-test.js +0 -160
  48. package/report/test/renderer/__snapshots__/report-renderer-axe-test.js.snap +0 -15
  49. package/report/test/renderer/category-renderer-test.js +0 -575
  50. package/report/test/renderer/components-test.js +0 -124
  51. package/report/test/renderer/crc-details-renderer-test.js +0 -116
  52. package/report/test/renderer/details-renderer-test.js +0 -882
  53. package/report/test/renderer/dom-test.js +0 -257
  54. package/report/test/renderer/element-screenshot-renderer-test.js +0 -183
  55. package/report/test/renderer/i18n-formatter-test.js +0 -179
  56. package/report/test/renderer/performance-category-renderer-test.js +0 -417
  57. package/report/test/renderer/pwa-category-renderer-test.js +0 -301
  58. package/report/test/renderer/report-renderer-axe-test.js +0 -85
  59. package/report/test/renderer/report-renderer-test.js +0 -268
  60. package/report/test/renderer/report-ui-features-test.js +0 -773
  61. package/report/test/renderer/report-utils-test.js +0 -219
  62. package/report/test/renderer/snippet-renderer-test.js +0 -291
  63. package/report/test/renderer/text-encoding-test.js +0 -43
  64. package/report/test-assets/faux-psi.d.ts +0 -34
  65. package/shared/test/localization/format-test.js +0 -430
  66. package/shared/test/localization/locales-test.js +0 -45
  67. package/shared/test/localization/swap-locale-test.js +0 -127
  68. package/shared/test/util-test.js +0 -214
  69. 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
- });