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.
- package/changelog-pre10.md +6085 -0
- package/core/audits/dobetterweb/doctype.d.ts +3 -2
- package/core/audits/dobetterweb/doctype.js +15 -4
- package/core/audits/dobetterweb/paste-preventing-inputs.js +2 -2
- package/core/gather/driver/network-monitor.js +1 -3
- package/core/gather/gatherers/bf-cache-failures.js +7 -8
- package/package.json +2 -2
- package/shared/localization/locales/en-US.json +1 -1
- package/shared/localization/locales/en-XL.json +1 -1
- package/.codecov.yml +0 -22
- package/.cz-config.js +0 -24
- package/.gitattributes +0 -5
- package/.mailmap +0 -6
- package/CODE_OF_CONDUCT.md +0 -93
- package/eslint-local-rules.cjs +0 -106
- package/flow-report/test/app-test.d.ts +0 -7
- package/flow-report/test/app-test.tsx +0 -52
- package/flow-report/test/common-test.d.ts +0 -7
- package/flow-report/test/common-test.tsx +0 -103
- package/flow-report/test/flow-report-pptr-test.d.ts +0 -7
- package/flow-report/test/flow-report-pptr-test.ts +0 -59
- package/flow-report/test/header-test.d.ts +0 -7
- package/flow-report/test/header-test.tsx +0 -55
- package/flow-report/test/run-flow-report-tests.sh +0 -20
- package/flow-report/test/sample-flow.d.ts +0 -7
- package/flow-report/test/sample-flow.ts +0 -17
- package/flow-report/test/setup/env-setup.d.ts +0 -11
- package/flow-report/test/setup/env-setup.ts +0 -55
- package/flow-report/test/sidebar/flow-test.d.ts +0 -7
- package/flow-report/test/sidebar/flow-test.tsx +0 -65
- package/flow-report/test/sidebar/sidebar-test.d.ts +0 -7
- package/flow-report/test/sidebar/sidebar-test.tsx +0 -116
- package/flow-report/test/summary/category-test.d.ts +0 -7
- package/flow-report/test/summary/category-test.tsx +0 -206
- package/flow-report/test/summary/summary-test.d.ts +0 -7
- package/flow-report/test/summary/summary-test.tsx +0 -124
- package/flow-report/test/topbar-test.d.ts +0 -7
- package/flow-report/test/topbar-test.tsx +0 -84
- package/flow-report/test/util-test.d.ts +0 -7
- package/flow-report/test/util-test.tsx +0 -133
- package/flow-report/test/wrappers/category-score-test.d.ts +0 -7
- package/flow-report/test/wrappers/category-score-test.tsx +0 -84
- package/flow-report/test/wrappers/markdown-test.d.ts +0 -7
- package/flow-report/test/wrappers/markdown-test.tsx +0 -17
- package/report/test/clients/bundle-test.js +0 -65
- package/report/test/generator/file-namer-test.js +0 -27
- package/report/test/generator/report-generator-test.js +0 -160
- package/report/test/renderer/__snapshots__/report-renderer-axe-test.js.snap +0 -15
- package/report/test/renderer/category-renderer-test.js +0 -575
- package/report/test/renderer/components-test.js +0 -124
- package/report/test/renderer/crc-details-renderer-test.js +0 -116
- package/report/test/renderer/details-renderer-test.js +0 -882
- package/report/test/renderer/dom-test.js +0 -257
- package/report/test/renderer/element-screenshot-renderer-test.js +0 -183
- package/report/test/renderer/i18n-formatter-test.js +0 -179
- package/report/test/renderer/performance-category-renderer-test.js +0 -417
- package/report/test/renderer/pwa-category-renderer-test.js +0 -301
- package/report/test/renderer/report-renderer-axe-test.js +0 -85
- package/report/test/renderer/report-renderer-test.js +0 -268
- package/report/test/renderer/report-ui-features-test.js +0 -773
- package/report/test/renderer/report-utils-test.js +0 -219
- package/report/test/renderer/snippet-renderer-test.js +0 -291
- package/report/test/renderer/text-encoding-test.js +0 -43
- package/report/test-assets/faux-psi.d.ts +0 -34
- package/shared/test/localization/format-test.js +0 -430
- package/shared/test/localization/locales-test.js +0 -45
- package/shared/test/localization/swap-locale-test.js +0 -127
- package/shared/test/util-test.js +0 -214
- package/vercel.json +0 -6
|
@@ -1,773 +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
|
-
import jestMock from 'jest-mock';
|
|
11
|
-
|
|
12
|
-
import {reportAssets} from '../../generator/report-assets.js';
|
|
13
|
-
import {ReportUtils, UIStrings} from '../../renderer/report-utils.js';
|
|
14
|
-
import {DOM} from '../../renderer/dom.js';
|
|
15
|
-
import {DetailsRenderer} from '../../renderer/details-renderer.js';
|
|
16
|
-
import {ReportUIFeatures} from '../../renderer/report-ui-features.js';
|
|
17
|
-
import {CategoryRenderer} from '../../renderer/category-renderer.js';
|
|
18
|
-
import {ReportRenderer} from '../../renderer/report-renderer.js';
|
|
19
|
-
import {readJson} from '../../../core/test/test-utils.js';
|
|
20
|
-
|
|
21
|
-
const sampleResultsOrig = readJson('../../../core/test/results/sample_v2.json', import.meta);
|
|
22
|
-
|
|
23
|
-
describe('ReportUIFeatures', () => {
|
|
24
|
-
let sampleResults;
|
|
25
|
-
let dom;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @param {LH.JSON} lhr
|
|
29
|
-
* @param {LH.Renderer.Options=} opts
|
|
30
|
-
* @return {HTMLElement}
|
|
31
|
-
*/
|
|
32
|
-
function render(lhr, opts) {
|
|
33
|
-
const detailsRenderer = new DetailsRenderer(dom);
|
|
34
|
-
const categoryRenderer = new CategoryRenderer(dom, detailsRenderer);
|
|
35
|
-
const renderer = new ReportRenderer(dom, categoryRenderer);
|
|
36
|
-
const reportUIFeatures = new ReportUIFeatures(dom, opts);
|
|
37
|
-
const container = dom.find('body', dom.document());
|
|
38
|
-
renderer.renderReport(lhr, container, opts);
|
|
39
|
-
reportUIFeatures.initFeatures(lhr);
|
|
40
|
-
return container;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
before(() => {
|
|
44
|
-
global.console.warn = jestMock.fn();
|
|
45
|
-
|
|
46
|
-
// Stub out matchMedia for Node.
|
|
47
|
-
global.matchMedia = function() {
|
|
48
|
-
return {
|
|
49
|
-
addListener: function() {},
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const document = new jsdom.JSDOM(reportAssets.REPORT_TEMPLATE);
|
|
54
|
-
global.self = document.window;
|
|
55
|
-
global.self.matchMedia = function() {
|
|
56
|
-
return {
|
|
57
|
-
addListener: function() {},
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
global.HTMLElement = document.window.HTMLElement;
|
|
62
|
-
global.HTMLInputElement = document.window.HTMLInputElement;
|
|
63
|
-
global.CustomEvent = document.window.CustomEvent;
|
|
64
|
-
|
|
65
|
-
global.window = document.window;
|
|
66
|
-
global.window.requestAnimationFrame = fn => fn();
|
|
67
|
-
global.window.getComputedStyle = function() {
|
|
68
|
-
return {
|
|
69
|
-
marginTop: '10px',
|
|
70
|
-
height: '10px',
|
|
71
|
-
};
|
|
72
|
-
};
|
|
73
|
-
global.window.ResizeObserver = class ResizeObserver {
|
|
74
|
-
observe() { }
|
|
75
|
-
unobserve() { }
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
dom = new DOM(document.window.document);
|
|
79
|
-
sampleResults = ReportUtils.prepareReportResult(sampleResultsOrig);
|
|
80
|
-
render(sampleResults);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
after(() => {
|
|
84
|
-
global.window = undefined;
|
|
85
|
-
global.HTMLElement = undefined;
|
|
86
|
-
global.HTMLInputElement = undefined;
|
|
87
|
-
global.CustomEvent = undefined;
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('initFeatures', () => {
|
|
91
|
-
it('should init a report', () => {
|
|
92
|
-
const container = render(sampleResults);
|
|
93
|
-
assert.equal(dom.findAll('.lh-category', container).length, 5);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should init a report with a single category', () => {
|
|
97
|
-
const lhr = JSON.parse(JSON.stringify(sampleResults));
|
|
98
|
-
lhr.categories = {
|
|
99
|
-
performance: lhr.categories.performance,
|
|
100
|
-
};
|
|
101
|
-
const container = render(lhr);
|
|
102
|
-
assert.equal(dom.findAll('.lh-category', container).length, 1);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe('third-party filtering', () => {
|
|
106
|
-
let lhrJson;
|
|
107
|
-
|
|
108
|
-
before(() => {
|
|
109
|
-
const lhr = JSON.parse(JSON.stringify(sampleResults));
|
|
110
|
-
lhr.requestedUrl = lhr.finalDisplayedUrl = 'http://www.example.com';
|
|
111
|
-
|
|
112
|
-
const webpAuditItemTemplate = {
|
|
113
|
-
...sampleResults.audits['modern-image-formats'].details.items[0],
|
|
114
|
-
wastedBytes: 8.8 * 1024,
|
|
115
|
-
entity: undefined, // Remove entity classification from previous result.
|
|
116
|
-
};
|
|
117
|
-
const renderBlockingAuditItemTemplate = {
|
|
118
|
-
...sampleResults.audits['render-blocking-resources'].details.items[0],
|
|
119
|
-
entity: undefined, // Remove entity classification from previous result.
|
|
120
|
-
};
|
|
121
|
-
const textCompressionAuditItemTemplate = {
|
|
122
|
-
...sampleResults.audits['uses-text-compression'].details.items[0],
|
|
123
|
-
entity: undefined, // Remove entity classification from previous result.
|
|
124
|
-
};
|
|
125
|
-
const bootupTimeAuditItemTemplate = {
|
|
126
|
-
...sampleResults.audits['bootup-time'].details.items[0],
|
|
127
|
-
entity: undefined, // Remove entity classification from previous result.
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// Interleave first/third party URLs to test restoring order.
|
|
131
|
-
lhr.audits['modern-image-formats'].details.items = [
|
|
132
|
-
{
|
|
133
|
-
...webpAuditItemTemplate,
|
|
134
|
-
url: 'http://www.cdn.com/img1.jpg', // Third party, will be filtered.
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
...webpAuditItemTemplate,
|
|
138
|
-
url: 'http://www.example.com/img2.jpg', // First party, not filtered.
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
...webpAuditItemTemplate,
|
|
142
|
-
url: 'http://www.notexample.com/img3.jpg', // Third party, will be filtered.
|
|
143
|
-
},
|
|
144
|
-
];
|
|
145
|
-
|
|
146
|
-
// Test sub-item rows.
|
|
147
|
-
lhr.audits['unused-javascript'].details.items = [
|
|
148
|
-
{
|
|
149
|
-
...webpAuditItemTemplate,
|
|
150
|
-
url: 'http://www.cdn.com/script1.js', // Third party, will be filtered.
|
|
151
|
-
subItems: {
|
|
152
|
-
type: 'subitems',
|
|
153
|
-
items: [
|
|
154
|
-
{source: '1', sourceBytes: 1, sourceWastedBytes: 1},
|
|
155
|
-
{source: '2', sourceBytes: 2, sourceWastedBytes: 2},
|
|
156
|
-
],
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
...webpAuditItemTemplate,
|
|
161
|
-
url: 'http://www.example.com/script2.js', // First party, not filtered.
|
|
162
|
-
subItems: {
|
|
163
|
-
type: 'subitems',
|
|
164
|
-
items: [
|
|
165
|
-
{source: '3', sourceBytes: 3, sourceWastedBytes: 3},
|
|
166
|
-
{source: '4', sourceBytes: 4, sourceWastedBytes: 4},
|
|
167
|
-
],
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
...webpAuditItemTemplate,
|
|
172
|
-
url: 'http://www.notexample.com/script3.js', // Third party, will be filtered.
|
|
173
|
-
subItems: {
|
|
174
|
-
type: 'subitems',
|
|
175
|
-
items: [
|
|
176
|
-
{source: '5', sourceBytes: 5, sourceWastedBytes: 5},
|
|
177
|
-
{source: '6', sourceBytes: 6, sourceWastedBytes: 6},
|
|
178
|
-
],
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
];
|
|
182
|
-
|
|
183
|
-
// Only third party URLs to test that checkbox is hidden
|
|
184
|
-
lhr.audits['render-blocking-resources'].details.items = [
|
|
185
|
-
{
|
|
186
|
-
...renderBlockingAuditItemTemplate,
|
|
187
|
-
url: 'http://www.cdn.com/script1.js', // Third party.
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
...renderBlockingAuditItemTemplate,
|
|
191
|
-
url: 'http://www.google.com/script2.js', // Third party.
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
...renderBlockingAuditItemTemplate,
|
|
195
|
-
url: 'http://www.notexample.com/script3.js', // Third party.
|
|
196
|
-
},
|
|
197
|
-
];
|
|
198
|
-
|
|
199
|
-
// Only first party URLs to test that checkbox is hidden
|
|
200
|
-
lhr.audits['uses-text-compression'].details.items = [
|
|
201
|
-
{
|
|
202
|
-
...textCompressionAuditItemTemplate,
|
|
203
|
-
url: 'http://www.example.com/font1.ttf', // First party.
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
...textCompressionAuditItemTemplate,
|
|
207
|
-
url: 'http://www.example.com/font2.ttf', // First party.
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
...textCompressionAuditItemTemplate,
|
|
211
|
-
url: 'http://www.example.com/font3.ttf', // First party.
|
|
212
|
-
},
|
|
213
|
-
];
|
|
214
|
-
|
|
215
|
-
// A mix of 3P, 1P and Unattributable.
|
|
216
|
-
lhr.audits['bootup-time'].details.items = [
|
|
217
|
-
{
|
|
218
|
-
...bootupTimeAuditItemTemplate,
|
|
219
|
-
url: 'http://www.example.com/dobetterweb/dbw_tester.html',
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
...bootupTimeAuditItemTemplate,
|
|
223
|
-
url: 'Unattributable',
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
...bootupTimeAuditItemTemplate,
|
|
227
|
-
url: 'http://www.example.com/dobetterweb/third_party/aggressive-promise-polyfill.js',
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
...bootupTimeAuditItemTemplate,
|
|
231
|
-
url: 'http://www.cdn.com/script1.js',
|
|
232
|
-
},
|
|
233
|
-
];
|
|
234
|
-
|
|
235
|
-
lhrJson = lhr;
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
describe('entity-classification based filtering works', () => {
|
|
239
|
-
let container;
|
|
240
|
-
|
|
241
|
-
before(() => {
|
|
242
|
-
// Setup entity-classification with recognized entities first.
|
|
243
|
-
lhrJson.entities = [
|
|
244
|
-
{
|
|
245
|
-
name: 'example.com',
|
|
246
|
-
isFirstParty: true,
|
|
247
|
-
isUnrecognized: true,
|
|
248
|
-
origins: ['http://www.example.com'],
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
name: 'cdn.com',
|
|
252
|
-
isUnrecognized: true,
|
|
253
|
-
origins: ['http://www.cdn.com'],
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
name: 'notexample.com',
|
|
257
|
-
isUnrecognized: true,
|
|
258
|
-
origins: ['http://www.notexample.com'],
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: 'google.com',
|
|
262
|
-
origins: ['http://www.google.com'],
|
|
263
|
-
},
|
|
264
|
-
];
|
|
265
|
-
|
|
266
|
-
// Entity resolution is done during prepareReportResult
|
|
267
|
-
const result = ReportUtils.prepareReportResult(lhrJson);
|
|
268
|
-
|
|
269
|
-
// render a report onto the UIFeature dom
|
|
270
|
-
container = render(result);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('all items that contain a url have been marked with an entity for filtering', () => {
|
|
274
|
-
['modern-image-formats', 'render-blocking-resources', 'unused-javascript',
|
|
275
|
-
'uses-text-compression'].forEach(audit => {
|
|
276
|
-
dom.findAll(`#${audit} tr`, container)
|
|
277
|
-
.filter(el => dom.findAll('.lh-text__url', el).length > 0)
|
|
278
|
-
.forEach(el => expect(el.dataset.entity).toBeTruthy());
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it('filters out third party resources in on click', () => {
|
|
283
|
-
const filterCheckbox = dom.find('#modern-image-formats .lh-3p-filter-input', container);
|
|
284
|
-
|
|
285
|
-
function getUrlsInTable() {
|
|
286
|
-
return dom
|
|
287
|
-
.findAll('#modern-image-formats tr:not(.lh-row--hidden) .lh-text__url a:first-child', container) // eslint-disable-line max-len
|
|
288
|
-
.map(el => el.textContent);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
|
|
292
|
-
filterCheckbox.click();
|
|
293
|
-
expect(getUrlsInTable()).toEqual(['/img2.jpg']);
|
|
294
|
-
filterCheckbox.click();
|
|
295
|
-
expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
|
|
296
|
-
|
|
297
|
-
const filter3pCount = dom.find('#modern-image-formats .lh-3p-filter-count', container);
|
|
298
|
-
expect(filter3pCount.textContent).toEqual('2');
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it('filters out sub-item rows of third party resources on click', () => {
|
|
302
|
-
dom.find('#unused-javascript', container);
|
|
303
|
-
const filterCheckbox = dom.find('#unused-javascript .lh-3p-filter-input', container);
|
|
304
|
-
|
|
305
|
-
function getRowIdentifiers() {
|
|
306
|
-
return dom
|
|
307
|
-
.findAll(
|
|
308
|
-
'#unused-javascript tbody tr:not(.lh-row--hidden)', container)
|
|
309
|
-
.map(el => el.textContent);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const initialExpected = [
|
|
313
|
-
'/script1.js(www.cdn.com)24.0 KiB8.8 KiB',
|
|
314
|
-
'10.0 KiB0.0 KiB',
|
|
315
|
-
'20.0 KiB0.0 KiB',
|
|
316
|
-
'/script2.js(www.example.com)24.0 KiB8.8 KiB',
|
|
317
|
-
'30.0 KiB0.0 KiB',
|
|
318
|
-
'40.0 KiB0.0 KiB',
|
|
319
|
-
'/script3.js(www.notexample.com)24.0 KiB8.8 KiB',
|
|
320
|
-
'50.0 KiB0.0 KiB',
|
|
321
|
-
'60.0 KiB0.0 KiB',
|
|
322
|
-
];
|
|
323
|
-
|
|
324
|
-
expect(getRowIdentifiers()).toEqual(initialExpected);
|
|
325
|
-
filterCheckbox.click();
|
|
326
|
-
expect(getRowIdentifiers()).toEqual([
|
|
327
|
-
'/script2.js(www.example.com)24.0 KiB8.8 KiB',
|
|
328
|
-
'30.0 KiB0.0 KiB',
|
|
329
|
-
'40.0 KiB0.0 KiB',
|
|
330
|
-
]);
|
|
331
|
-
filterCheckbox.click();
|
|
332
|
-
expect(getRowIdentifiers()).toEqual(initialExpected);
|
|
333
|
-
|
|
334
|
-
const filter3pCount = dom.find('#unused-javascript .lh-3p-filter-count', container);
|
|
335
|
-
expect(filter3pCount.textContent).toEqual('2');
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
it('adds no filter for audits in thirdPartyFilterAuditExclusions', () => {
|
|
339
|
-
const checkboxClassName = 'lh-3p-filter-input';
|
|
340
|
-
|
|
341
|
-
const yesCheckbox = dom.find(`#modern-image-formats .${checkboxClassName}`, container);
|
|
342
|
-
expect(yesCheckbox).toBeTruthy();
|
|
343
|
-
|
|
344
|
-
expect(() => dom.find(`#uses-rel-preconnect .${checkboxClassName}`, container))
|
|
345
|
-
.toThrowError('query #uses-rel-preconnect .lh-3p-filter-input not found');
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it('treats non-classifiable url values as first-party', () => {
|
|
349
|
-
const auditEl = dom.find('#bootup-time', container);
|
|
350
|
-
const filterCheckboxEl = dom.find('.lh-3p-filter-input', auditEl);
|
|
351
|
-
|
|
352
|
-
// Ensure 3p filter is visible.
|
|
353
|
-
expect(dom.find('.lh-3p-filter', auditEl).hidden).toBeFalsy();
|
|
354
|
-
|
|
355
|
-
function getUrlsInTable() {
|
|
356
|
-
return dom.findAll('tbody tr:not(.lh-row--hidden) td:first-child', auditEl)
|
|
357
|
-
.map(el => el.textContent);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const preFilterRowSet = [
|
|
361
|
-
'/dobetterweb/dbw_tester.html(www.example.com)',
|
|
362
|
-
'Unattributable',
|
|
363
|
-
'…third_party/aggressive-promise-polyfill.js(www.example.com)',
|
|
364
|
-
'/script1.js(www.cdn.com)',
|
|
365
|
-
];
|
|
366
|
-
|
|
367
|
-
expect(getUrlsInTable()).toEqual(preFilterRowSet);
|
|
368
|
-
filterCheckboxEl.click();
|
|
369
|
-
expect(getUrlsInTable()).toEqual(preFilterRowSet.filter(
|
|
370
|
-
text => !text.includes('cdn.com')));
|
|
371
|
-
filterCheckboxEl.click();
|
|
372
|
-
expect(getUrlsInTable()).toEqual(preFilterRowSet);
|
|
373
|
-
|
|
374
|
-
const filter3pCountEl = dom.find('.lh-3p-filter-count', auditEl);
|
|
375
|
-
expect(filter3pCountEl.textContent).toEqual('1');
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it('filter is hidden when just third party resources', () => {
|
|
379
|
-
const filterControl =
|
|
380
|
-
dom.find('#render-blocking-resources .lh-3p-filter', container);
|
|
381
|
-
expect(filterControl.hidden).toEqual(true);
|
|
382
|
-
// Expect the hidden filter to still count third parties correctly.
|
|
383
|
-
const filter3pCount = dom.find('#render-blocking-resources .lh-3p-filter-count',
|
|
384
|
-
container);
|
|
385
|
-
expect(filter3pCount.textContent).toEqual('3');
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it('filter is hidden for just first party resources', () => {
|
|
389
|
-
const filterControl = dom.find('#uses-text-compression .lh-3p-filter', container);
|
|
390
|
-
expect(filterControl.hidden).toEqual(true);
|
|
391
|
-
// Expect the hidden filter to still count third parties correctly.
|
|
392
|
-
const filter3pCount = dom.find('#uses-text-compression .lh-3p-filter-count', container);
|
|
393
|
-
expect(filter3pCount.textContent).toEqual('0');
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
describe('legacy third-party filtering continues to work', () => {
|
|
398
|
-
let container;
|
|
399
|
-
|
|
400
|
-
before(() => {
|
|
401
|
-
// Remove entity-classification audit to fall back to origin string match
|
|
402
|
-
// based third-party filtering (legacy)
|
|
403
|
-
delete lhrJson.entities;
|
|
404
|
-
|
|
405
|
-
const result = ReportUtils.prepareReportResult(lhrJson);
|
|
406
|
-
|
|
407
|
-
// render a report onto the UIFeature dom
|
|
408
|
-
container = render(result);
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
it('filters out third party resources in on click', () => {
|
|
412
|
-
const filterCheckbox = dom.find('#modern-image-formats .lh-3p-filter-input', container);
|
|
413
|
-
|
|
414
|
-
function getUrlsInTable() {
|
|
415
|
-
return dom
|
|
416
|
-
.findAll('#modern-image-formats tr:not(.lh-row--hidden) .lh-text__url a:first-child', container) // eslint-disable-line max-len
|
|
417
|
-
.map(el => el.textContent);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
|
|
421
|
-
filterCheckbox.click();
|
|
422
|
-
expect(getUrlsInTable()).toEqual(['/img2.jpg']);
|
|
423
|
-
filterCheckbox.click();
|
|
424
|
-
expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
|
|
425
|
-
const filter3pCount = dom.find('#modern-image-formats .lh-3p-filter-count', container);
|
|
426
|
-
expect(filter3pCount.textContent).toEqual('2');
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
it('filters out sub-item rows of third party resources on click', () => {
|
|
430
|
-
dom.find('#unused-javascript', container);
|
|
431
|
-
const filterCheckbox = dom.find('#unused-javascript .lh-3p-filter-input', container);
|
|
432
|
-
|
|
433
|
-
function getRowIdentifiers() {
|
|
434
|
-
return dom
|
|
435
|
-
.findAll(
|
|
436
|
-
'#unused-javascript tbody tr:not(.lh-row--hidden)', container)
|
|
437
|
-
.map(el => el.textContent);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const initialExpected = [
|
|
441
|
-
'/script1.js(www.cdn.com)24.0 KiB8.8 KiB',
|
|
442
|
-
'10.0 KiB0.0 KiB',
|
|
443
|
-
'20.0 KiB0.0 KiB',
|
|
444
|
-
'/script2.js(www.example.com)24.0 KiB8.8 KiB',
|
|
445
|
-
'30.0 KiB0.0 KiB',
|
|
446
|
-
'40.0 KiB0.0 KiB',
|
|
447
|
-
'/script3.js(www.notexample.com)24.0 KiB8.8 KiB',
|
|
448
|
-
'50.0 KiB0.0 KiB',
|
|
449
|
-
'60.0 KiB0.0 KiB',
|
|
450
|
-
];
|
|
451
|
-
|
|
452
|
-
expect(getRowIdentifiers()).toEqual(initialExpected);
|
|
453
|
-
filterCheckbox.click();
|
|
454
|
-
expect(getRowIdentifiers()).toEqual([
|
|
455
|
-
'/script2.js(www.example.com)24.0 KiB8.8 KiB',
|
|
456
|
-
'30.0 KiB0.0 KiB',
|
|
457
|
-
'40.0 KiB0.0 KiB',
|
|
458
|
-
]);
|
|
459
|
-
|
|
460
|
-
filterCheckbox.click();
|
|
461
|
-
expect(getRowIdentifiers()).toEqual(initialExpected);
|
|
462
|
-
const filter3pCount = dom.find('#unused-javascript .lh-3p-filter-count', container);
|
|
463
|
-
expect(filter3pCount.textContent).toEqual('2');
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
it('adds no filter for audits in thirdPartyFilterAuditExclusions', () => {
|
|
467
|
-
const checkboxClassName = 'lh-3p-filter-input';
|
|
468
|
-
|
|
469
|
-
const yesCheckbox = dom.find(`#modern-image-formats .${checkboxClassName}`, container);
|
|
470
|
-
expect(yesCheckbox).toBeTruthy();
|
|
471
|
-
|
|
472
|
-
expect(() => dom.find(`#uses-rel-preconnect .${checkboxClassName}`, container))
|
|
473
|
-
.toThrowError('query #uses-rel-preconnect .lh-3p-filter-input not found');
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
it('filter is hidden when just third party resources', () => {
|
|
477
|
-
const filterControl =
|
|
478
|
-
dom.find('#render-blocking-resources .lh-3p-filter', container);
|
|
479
|
-
expect(filterControl.hidden).toEqual(true);
|
|
480
|
-
// Expect the hidden filter to still count third parties correctly.
|
|
481
|
-
const filter3pCount = dom.find('#render-blocking-resources .lh-3p-filter-count',
|
|
482
|
-
container);
|
|
483
|
-
expect(filter3pCount.textContent).toEqual('3');
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
it('filter is hidden for just first party resources', () => {
|
|
487
|
-
const filterControl = dom.find('#uses-text-compression .lh-3p-filter', container);
|
|
488
|
-
expect(filterControl.hidden).toEqual(true);
|
|
489
|
-
// Expect the hidden filter to still count third parties correctly.
|
|
490
|
-
const filter3pCount = dom.find('#uses-text-compression .lh-3p-filter-count',
|
|
491
|
-
container);
|
|
492
|
-
expect(filter3pCount.textContent).toEqual('0');
|
|
493
|
-
});
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
it('save-html option enabled if callback present', () => {
|
|
498
|
-
let container = render(sampleResults);
|
|
499
|
-
const getSaveEl = () => dom.find('a[data-action="save-html"]', container);
|
|
500
|
-
expect(getSaveEl().classList.contains('lh-hidden')).toBeTruthy();
|
|
501
|
-
|
|
502
|
-
const getHtmlMock = jestMock.fn();
|
|
503
|
-
container = render(sampleResults, {
|
|
504
|
-
getStandaloneReportHTML: getHtmlMock,
|
|
505
|
-
});
|
|
506
|
-
expect(getSaveEl().classList.contains('lh-hidden')).toBeFalsy();
|
|
507
|
-
|
|
508
|
-
expect(getHtmlMock).not.toBeCalled();
|
|
509
|
-
getSaveEl().click();
|
|
510
|
-
expect(getHtmlMock).toBeCalled();
|
|
511
|
-
});
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
describe('fireworks', () => {
|
|
515
|
-
it('should render an non-all 100 report without fireworks', () => {
|
|
516
|
-
const lhr = JSON.parse(JSON.stringify(sampleResults));
|
|
517
|
-
lhr.categories.performance.score = 0.5;
|
|
518
|
-
const container = render(lhr);
|
|
519
|
-
assert.ok(container.querySelector('.lh-score100') === null, 'has no fireworks treatment');
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
it('should render an all 100 report with fireworks', () => {
|
|
523
|
-
const lhr = JSON.parse(JSON.stringify(sampleResults));
|
|
524
|
-
Object.values(lhr.categories).forEach(element => {
|
|
525
|
-
element.score = 1;
|
|
526
|
-
});
|
|
527
|
-
const container = render(lhr);
|
|
528
|
-
assert.ok(container.querySelector('.lh-score100'), 'has fireworks treatment');
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
it('should show fireworks for all 100s except PWA', () => {
|
|
532
|
-
const lhr = JSON.parse(JSON.stringify(sampleResults));
|
|
533
|
-
Object.values(lhr.categories).forEach(element => {
|
|
534
|
-
element.score = 1;
|
|
535
|
-
});
|
|
536
|
-
lhr.categories.pwa.score = 0;
|
|
537
|
-
|
|
538
|
-
const container = render(lhr);
|
|
539
|
-
assert.ok(container.querySelector('.lh-score100'), 'has fireworks treatment');
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
it('should not render fireworks if all core categories are not present', () => {
|
|
543
|
-
const lhr = JSON.parse(JSON.stringify(sampleResults));
|
|
544
|
-
delete lhr.categories.performance;
|
|
545
|
-
delete lhr.categoryGroups.performace;
|
|
546
|
-
Object.values(lhr.categories).forEach(element => {
|
|
547
|
-
element.score = 1;
|
|
548
|
-
});
|
|
549
|
-
const container = render(lhr);
|
|
550
|
-
assert.ok(container.querySelector('.lh-score100') === null, 'has no fireworks treatment');
|
|
551
|
-
});
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
describe('metric descriptions', () => {
|
|
555
|
-
it('with no errors, hide by default', () => {
|
|
556
|
-
const lhr = JSON.parse(JSON.stringify(sampleResults));
|
|
557
|
-
const container = render(lhr);
|
|
558
|
-
assert.ok(!container.querySelector('.lh-metrics-toggle__input').checked);
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
it('with error, show by default', () => {
|
|
562
|
-
const lhr = JSON.parse(JSON.stringify(sampleResults));
|
|
563
|
-
lhr.audits['first-contentful-paint'].errorMessage = 'Error.';
|
|
564
|
-
const container = render(lhr);
|
|
565
|
-
assert.ok(container.querySelector('.lh-metrics-toggle__input').checked);
|
|
566
|
-
});
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
describe('tools button', () => {
|
|
570
|
-
let window;
|
|
571
|
-
let dropDown;
|
|
572
|
-
|
|
573
|
-
beforeEach(() => {
|
|
574
|
-
window = dom.document().defaultView;
|
|
575
|
-
const features = new ReportUIFeatures(dom);
|
|
576
|
-
features.initFeatures(sampleResults);
|
|
577
|
-
dropDown = features._topbar._dropDownMenu;
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
it('click should toggle active class', () => {
|
|
581
|
-
dropDown._toggleEl.click();
|
|
582
|
-
assert.ok(dropDown._toggleEl.classList.contains('lh-active'));
|
|
583
|
-
|
|
584
|
-
dropDown._toggleEl.click();
|
|
585
|
-
assert.ok(!dropDown._toggleEl.classList.contains('lh-active'));
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
it('Escape key removes active class', () => {
|
|
589
|
-
dropDown._toggleEl.click();
|
|
590
|
-
assert.ok(dropDown._toggleEl.classList.contains('lh-active'));
|
|
591
|
-
|
|
592
|
-
const escape = new window.KeyboardEvent('keydown', {keyCode: /* ESC */ 27});
|
|
593
|
-
dom.document().dispatchEvent(escape);
|
|
594
|
-
assert.ok(!dropDown._toggleEl.classList.contains('lh-active'));
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
['ArrowUp', 'ArrowDown', 'Enter', ' '].forEach((code) => {
|
|
598
|
-
it(`'${code}' adds active class`, () => {
|
|
599
|
-
const event = new window.KeyboardEvent('keydown', {code});
|
|
600
|
-
dropDown._toggleEl.dispatchEvent(event);
|
|
601
|
-
assert.ok(dropDown._toggleEl.classList.contains('lh-active'));
|
|
602
|
-
});
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
it('ArrowUp on the first menu element should focus the last element', () => {
|
|
606
|
-
dropDown._toggleEl.click();
|
|
607
|
-
|
|
608
|
-
const arrowUp = new window.KeyboardEvent('keydown', {bubbles: true, code: 'ArrowUp'});
|
|
609
|
-
dropDown._menuEl.firstElementChild.dispatchEvent(arrowUp);
|
|
610
|
-
|
|
611
|
-
assert.strictEqual(dom.document().activeElement, dropDown._menuEl.lastElementChild);
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
it('ArrowDown on the first menu element should focus the second element', () => {
|
|
615
|
-
dropDown._toggleEl.click();
|
|
616
|
-
|
|
617
|
-
const {nextElementSibling} = dropDown._menuEl.firstElementChild;
|
|
618
|
-
const arrowDown = new window.KeyboardEvent('keydown', {bubbles: true, code: 'ArrowDown'});
|
|
619
|
-
dropDown._menuEl.firstElementChild.dispatchEvent(arrowDown);
|
|
620
|
-
|
|
621
|
-
assert.strictEqual(dom.document().activeElement, nextElementSibling);
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
it('Home on the last menu element should focus the first element', () => {
|
|
625
|
-
dropDown._toggleEl.click();
|
|
626
|
-
|
|
627
|
-
const {firstElementChild} = dropDown._menuEl;
|
|
628
|
-
const home = new window.KeyboardEvent('keydown', {bubbles: true, code: 'Home'});
|
|
629
|
-
dropDown._menuEl.lastElementChild.dispatchEvent(home);
|
|
630
|
-
|
|
631
|
-
assert.strictEqual(dom.document().activeElement, firstElementChild);
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
it('End on the first menu element should focus the last element', () => {
|
|
635
|
-
dropDown._toggleEl.click();
|
|
636
|
-
|
|
637
|
-
const {lastElementChild} = dropDown._menuEl;
|
|
638
|
-
const end = new window.KeyboardEvent('keydown', {bubbles: true, code: 'End'});
|
|
639
|
-
dropDown._menuEl.firstElementChild.dispatchEvent(end);
|
|
640
|
-
|
|
641
|
-
assert.strictEqual(dom.document().activeElement, lastElementChild);
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
describe('_getNextSelectableNode', () => {
|
|
645
|
-
let createDiv;
|
|
646
|
-
|
|
647
|
-
before(() => {
|
|
648
|
-
createDiv = () => dom.document().createElement('div');
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
it('should return undefined when nodes is empty', () => {
|
|
652
|
-
const nodes = [];
|
|
653
|
-
|
|
654
|
-
const nextNode = dropDown._getNextSelectableNode(nodes);
|
|
655
|
-
|
|
656
|
-
assert.strictEqual(nextNode, undefined);
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
it('should return the only node when start is defined', () => {
|
|
660
|
-
const node = createDiv();
|
|
661
|
-
|
|
662
|
-
const nextNode = dropDown._getNextSelectableNode([node], node);
|
|
663
|
-
|
|
664
|
-
assert.strictEqual(nextNode, node);
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
it('should return first node when start is undefined', () => {
|
|
668
|
-
const nodes = [createDiv(), createDiv()];
|
|
669
|
-
|
|
670
|
-
const nextNode = dropDown._getNextSelectableNode(nodes);
|
|
671
|
-
|
|
672
|
-
assert.strictEqual(nextNode, nodes[0]);
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
it('should return second node when start is first node', () => {
|
|
676
|
-
const nodes = [createDiv(), createDiv()];
|
|
677
|
-
|
|
678
|
-
const nextNode = dropDown._getNextSelectableNode(nodes, nodes[0]);
|
|
679
|
-
|
|
680
|
-
assert.strictEqual(nextNode, nodes[1]);
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
it('should return first node when start is second node', () => {
|
|
684
|
-
const nodes = [createDiv(), createDiv()];
|
|
685
|
-
|
|
686
|
-
const nextNode = dropDown._getNextSelectableNode(nodes, nodes[1]);
|
|
687
|
-
|
|
688
|
-
assert.strictEqual(nextNode, nodes[0]);
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
it('should skip the undefined node', () => {
|
|
692
|
-
const nodes = [createDiv(), undefined, createDiv()];
|
|
693
|
-
|
|
694
|
-
const nextNode = dropDown._getNextSelectableNode(nodes, nodes[0]);
|
|
695
|
-
|
|
696
|
-
assert.strictEqual(nextNode, nodes[2]);
|
|
697
|
-
});
|
|
698
|
-
|
|
699
|
-
it('should skip the disabled node', () => {
|
|
700
|
-
const disabledNode = createDiv();
|
|
701
|
-
disabledNode.setAttribute('disabled', true);
|
|
702
|
-
const nodes = [createDiv(), disabledNode, createDiv()];
|
|
703
|
-
|
|
704
|
-
const nextNode = dropDown._getNextSelectableNode(nodes, nodes[0]);
|
|
705
|
-
|
|
706
|
-
assert.strictEqual(nextNode, nodes[2]);
|
|
707
|
-
});
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
describe('onMenuFocusOut', () => {
|
|
711
|
-
beforeEach(() => {
|
|
712
|
-
dropDown._toggleEl.click();
|
|
713
|
-
assert.ok(dropDown._toggleEl.classList.contains('lh-active'));
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
it('should toggle active class when focus relatedTarget is null', () => {
|
|
717
|
-
const event = new window.FocusEvent('focusout', {relatedTarget: null});
|
|
718
|
-
dropDown.onMenuFocusOut(event);
|
|
719
|
-
|
|
720
|
-
assert.ok(!dropDown._toggleEl.classList.contains('lh-active'));
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
it('should toggle active class when focus relatedTarget is document.body', () => {
|
|
724
|
-
const relatedTarget = dom.document().body;
|
|
725
|
-
const event = new window.FocusEvent('focusout', {relatedTarget});
|
|
726
|
-
dropDown.onMenuFocusOut(event);
|
|
727
|
-
|
|
728
|
-
assert.ok(!dropDown._toggleEl.classList.contains('lh-active'));
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
it('should toggle active class when focus relatedTarget is _toggleEl', () => {
|
|
732
|
-
const relatedTarget = dropDown._toggleEl;
|
|
733
|
-
const event = new window.FocusEvent('focusout', {relatedTarget});
|
|
734
|
-
dropDown.onMenuFocusOut(event);
|
|
735
|
-
|
|
736
|
-
assert.ok(!dropDown._toggleEl.classList.contains('lh-active'));
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
it('should not toggle active class when focus relatedTarget is a menu item', () => {
|
|
740
|
-
const relatedTarget = dropDown._getNextMenuItem();
|
|
741
|
-
const event = new window.FocusEvent('focusout', {relatedTarget});
|
|
742
|
-
dropDown.onMenuFocusOut(event);
|
|
743
|
-
|
|
744
|
-
assert.ok(dropDown._toggleEl.classList.contains('lh-active'));
|
|
745
|
-
});
|
|
746
|
-
});
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
describe('treemap button', () => {
|
|
750
|
-
it('should only show button if treemap data is available', () => {
|
|
751
|
-
const lhr = JSON.parse(JSON.stringify(sampleResults));
|
|
752
|
-
|
|
753
|
-
expect(lhr.audits['script-treemap-data']).not.toBeUndefined();
|
|
754
|
-
expect(render(lhr).querySelector('.lh-button.lh-report-icon--treemap')).toBeTruthy();
|
|
755
|
-
|
|
756
|
-
delete lhr.audits['script-treemap-data'];
|
|
757
|
-
const newAuditRefs = lhr.categories['performance'].auditRefs
|
|
758
|
-
.filter(a => a.id !== 'script-treemap-data');
|
|
759
|
-
lhr.categories['performance'].auditRefs = newAuditRefs;
|
|
760
|
-
expect(render(lhr).querySelector('.lh-button.lh-report-icon--treemap')).toBeNull();
|
|
761
|
-
});
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
describe('data-i18n', () => {
|
|
765
|
-
it('should have only valid data-i18n values in template', () => {
|
|
766
|
-
const container = render(sampleResults);
|
|
767
|
-
for (const node of dom.findAll('[data-i18n]', container)) {
|
|
768
|
-
const val = node.getAttribute('data-i18n');
|
|
769
|
-
assert.ok(val in UIStrings, `Invalid data-i18n value of: "${val}" not found.`);
|
|
770
|
-
}
|
|
771
|
-
});
|
|
772
|
-
});
|
|
773
|
-
});
|