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,575 +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 {ReportUtils} from '../../renderer/report-utils.js';
|
|
12
|
-
import {I18nFormatter} from '../../renderer/i18n-formatter.js';
|
|
13
|
-
import {DOM} from '../../renderer/dom.js';
|
|
14
|
-
import {DetailsRenderer} from '../../renderer/details-renderer.js';
|
|
15
|
-
import {CategoryRenderer} from '../../renderer/category-renderer.js';
|
|
16
|
-
import {readJson} from '../../../core/test/test-utils.js';
|
|
17
|
-
import {Globals} from '../../renderer/report-globals.js';
|
|
18
|
-
|
|
19
|
-
const sampleResultsOrig = readJson('../../../core/test/results/sample_v2.json', import.meta);
|
|
20
|
-
|
|
21
|
-
describe('CategoryRenderer', () => {
|
|
22
|
-
let renderer;
|
|
23
|
-
let sampleResults;
|
|
24
|
-
|
|
25
|
-
before(() => {
|
|
26
|
-
Globals.apply({
|
|
27
|
-
providedStrings: {},
|
|
28
|
-
i18n: new I18nFormatter('en'),
|
|
29
|
-
reportJson: null,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const {document} = new jsdom.JSDOM().window;
|
|
33
|
-
const dom = new DOM(document);
|
|
34
|
-
const detailsRenderer = new DetailsRenderer(dom);
|
|
35
|
-
renderer = new CategoryRenderer(dom, detailsRenderer);
|
|
36
|
-
|
|
37
|
-
sampleResults = ReportUtils.prepareReportResult(sampleResultsOrig);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
after(() => {
|
|
41
|
-
Globals.i18n = undefined;
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('renders an audit', () => {
|
|
45
|
-
const auditRef = sampleResults.categories.pwa.auditRefs
|
|
46
|
-
.find(a => a.id === 'installable-manifest');
|
|
47
|
-
|
|
48
|
-
const auditDOM = renderer.renderAudit(auditRef);
|
|
49
|
-
assert.equal(auditDOM.nodeType, 1, 'Audit returns an element');
|
|
50
|
-
|
|
51
|
-
const title = auditDOM.querySelector('.lh-audit__title');
|
|
52
|
-
const description = auditDOM.querySelector('.lh-audit__description');
|
|
53
|
-
|
|
54
|
-
assert.equal(title.textContent, auditRef.result.title);
|
|
55
|
-
assert.ok(description.querySelector('a'), 'audit help text contains coverted markdown links');
|
|
56
|
-
assert.ok(auditDOM.classList.contains('lh-audit--fail'));
|
|
57
|
-
assert.ok(
|
|
58
|
-
auditDOM.classList.contains(`lh-audit--${auditRef.result.scoreDisplayMode.toLowerCase()}`));
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('renders an audit explanation when appropriate', () => {
|
|
62
|
-
const audit1 = renderer.renderAudit({
|
|
63
|
-
result: {
|
|
64
|
-
title: 'Audit title',
|
|
65
|
-
explanation: 'A reason',
|
|
66
|
-
description: 'help text',
|
|
67
|
-
scoreDisplayMode: 'binary',
|
|
68
|
-
score: 0,
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
assert.ok(audit1.querySelector('.lh-audit-explanation'));
|
|
72
|
-
|
|
73
|
-
const audit2 = renderer.renderAudit({
|
|
74
|
-
result: {
|
|
75
|
-
title: 'Audit title',
|
|
76
|
-
description: 'help text',
|
|
77
|
-
scoreDisplayMode: 'binary',
|
|
78
|
-
score: 0,
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
assert.ok(!audit2.querySelector('.lh-audit-explanation'));
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('renders an informative audit', () => {
|
|
85
|
-
const auditDOM = renderer.renderAudit({
|
|
86
|
-
id: 'informative',
|
|
87
|
-
result: {
|
|
88
|
-
title: 'It informs',
|
|
89
|
-
description: 'help text',
|
|
90
|
-
scoreDisplayMode: 'informative',
|
|
91
|
-
score: 0,
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
assert.ok(auditDOM.matches('.lh-audit--informative'));
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('adds a pass/average/fail class for non-informative audits', () => {
|
|
98
|
-
const auditDOM = renderer.renderAudit({
|
|
99
|
-
id: 'not-informative',
|
|
100
|
-
result: {
|
|
101
|
-
title: 'Not informative',
|
|
102
|
-
description: 'help text',
|
|
103
|
-
scoreDisplayMode: 'numeric',
|
|
104
|
-
score: 0,
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
assert.ok(auditDOM.matches('.lh-audit--fail'));
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('does not add a pass/average/fail class for informative audits', () => {
|
|
111
|
-
const auditDOM = renderer.renderAudit({
|
|
112
|
-
id: 'informative',
|
|
113
|
-
result: {
|
|
114
|
-
title: 'It informs',
|
|
115
|
-
description: 'help text',
|
|
116
|
-
scoreDisplayMode: 'informative',
|
|
117
|
-
score: 0,
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
assert.equal(auditDOM.classList.length, 2);
|
|
121
|
-
assert.ok(auditDOM.matches('.lh-audit'));
|
|
122
|
-
assert.ok(auditDOM.matches('.lh-audit--informative'));
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('renders audits with a warning', () => {
|
|
126
|
-
const auditResult = {
|
|
127
|
-
title: 'Audit',
|
|
128
|
-
description: 'Learn more',
|
|
129
|
-
scoreDisplayMode: 'informative',
|
|
130
|
-
warnings: ['It may not have worked!'],
|
|
131
|
-
score: 1,
|
|
132
|
-
};
|
|
133
|
-
const auditDOM = renderer.renderAudit({id: 'foo', result: auditResult});
|
|
134
|
-
const warningEl = auditDOM.querySelector('.lh-warnings');
|
|
135
|
-
assert.ok(warningEl, 'did not render warning message');
|
|
136
|
-
assert.ok(warningEl.textContent.includes(auditResult.warnings[0]), 'warning message provided');
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('renders audits with multiple warnings', () => {
|
|
140
|
-
const auditResult = {
|
|
141
|
-
title: 'Audit',
|
|
142
|
-
description: 'Learn more',
|
|
143
|
-
scoreDisplayMode: 'informative',
|
|
144
|
-
warnings: ['It may not have worked!', 'You should read this, though'],
|
|
145
|
-
score: 1,
|
|
146
|
-
};
|
|
147
|
-
const auditDOM = renderer.renderAudit({id: 'foo', result: auditResult});
|
|
148
|
-
const warningEl = auditDOM.querySelector('.lh-warnings');
|
|
149
|
-
assert.ok(warningEl, 'did not render warning message');
|
|
150
|
-
assert.ok(warningEl.textContent.includes(auditResult.warnings[0]), '1st warning provided');
|
|
151
|
-
assert.ok(warningEl.textContent.includes(auditResult.warnings[1]), '2nd warning provided');
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('renders a category', () => {
|
|
155
|
-
const category = sampleResults.categories.pwa;
|
|
156
|
-
const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
|
|
157
|
-
|
|
158
|
-
const categoryEl = categoryDOM.querySelector('.lh-category-header');
|
|
159
|
-
const value = categoryDOM.querySelector('.lh-gauge__percentage');
|
|
160
|
-
const title = categoryEl.querySelector('.lh-gauge__label');
|
|
161
|
-
|
|
162
|
-
assert.deepEqual(categoryEl, categoryEl.firstElementChild, 'first child is a score');
|
|
163
|
-
const scoreInDom = Number(value.textContent);
|
|
164
|
-
assert.ok(Number.isInteger(scoreInDom) && scoreInDom > 10, 'category score is rounded');
|
|
165
|
-
assert.equal(title.textContent, category.title, 'title is set');
|
|
166
|
-
|
|
167
|
-
const audits = categoryDOM.querySelectorAll('.lh-audit');
|
|
168
|
-
assert.equal(audits.length, category.auditRefs.length, 'renders correct number of audits');
|
|
169
|
-
|
|
170
|
-
// No plugin categories in sampleResults.
|
|
171
|
-
assert.equal(
|
|
172
|
-
categoryDOM.querySelector('.lh-gauge__wrapper--plugin'), null, 'renders no plugin badges');
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('plugin category has plugin badge', () => {
|
|
176
|
-
const category = JSON.parse(
|
|
177
|
-
JSON.stringify(sampleResults.categories.seo));
|
|
178
|
-
category.id = 'lighthouse-plugin-someplugin';
|
|
179
|
-
category.title = 'Some Plugin';
|
|
180
|
-
const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
|
|
181
|
-
assert.ok(categoryDOM.querySelector('.lh-gauge__wrapper--plugin'));
|
|
182
|
-
const label = categoryDOM.querySelector('.lh-gauge__label').textContent;
|
|
183
|
-
assert.equal(category.title, label);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('handles markdown in category descriptions a category', () => {
|
|
187
|
-
const category = sampleResults.categories.pwa;
|
|
188
|
-
const prevDesc = category.description;
|
|
189
|
-
category.description += ' [link text](http://example.com).';
|
|
190
|
-
const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
|
|
191
|
-
const description = categoryDOM.querySelector('.lh-category-header__description');
|
|
192
|
-
assert.ok(description.querySelector('a'), 'description contains converted markdown links');
|
|
193
|
-
category.description = prevDesc;
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('renders manual audits if the category contains them', () => {
|
|
197
|
-
const pwaCategory = sampleResults.categories.pwa;
|
|
198
|
-
const categoryDOM = renderer.render(pwaCategory, sampleResults.categoryGroups);
|
|
199
|
-
assert.ok(categoryDOM.querySelector('.lh-clump--manual .lh-audit-group__summary'));
|
|
200
|
-
assert.equal(categoryDOM.querySelectorAll('.lh-audit--manual').length, 3,
|
|
201
|
-
'score shows informative and dash icon');
|
|
202
|
-
|
|
203
|
-
assert.ok(pwaCategory.manualDescription);
|
|
204
|
-
const description = categoryDOM.querySelector('.lh-clump--manual').closest('.lh-audit-group')
|
|
205
|
-
.querySelector('.lh-audit-group__description').textContent;
|
|
206
|
-
// may need to be adjusted if description includes a link at the beginning
|
|
207
|
-
assert.ok(description.startsWith(pwaCategory.manualDescription.substring(0, 20)),
|
|
208
|
-
'no manual description');
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
describe('categories with not applicable audits', () => {
|
|
212
|
-
let a11yCategory;
|
|
213
|
-
|
|
214
|
-
beforeEach(()=> {
|
|
215
|
-
a11yCategory = JSON.parse(JSON.stringify(sampleResults.categories.accessibility));
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('renders not applicable audits if the category contains them', () => {
|
|
219
|
-
const categoryDOM = renderer.render(a11yCategory, sampleResults.categoryGroups);
|
|
220
|
-
assert.ok(categoryDOM.querySelector(
|
|
221
|
-
'.lh-clump--notapplicable .lh-audit-group__summary'));
|
|
222
|
-
|
|
223
|
-
const notApplicableCount = a11yCategory.auditRefs.reduce((sum, audit) =>
|
|
224
|
-
sum += audit.result.scoreDisplayMode === 'notApplicable' ? 1 : 0, 0);
|
|
225
|
-
assert.equal(
|
|
226
|
-
categoryDOM.querySelectorAll('.lh-clump--notapplicable .lh-audit').length,
|
|
227
|
-
notApplicableCount,
|
|
228
|
-
'score shows informative and dash icon'
|
|
229
|
-
);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('does not render a not applicable section if the category does not contain them', () => {
|
|
233
|
-
const category = sampleResults.categories['best-practices'];
|
|
234
|
-
const bestPracticeCat = {
|
|
235
|
-
...category,
|
|
236
|
-
auditRefs: category.auditRefs
|
|
237
|
-
.filter(ref => ref.result.scoreDisplayMode !== 'notApplicable'),
|
|
238
|
-
};
|
|
239
|
-
const categoryDOM2 = renderer.render(bestPracticeCat, sampleResults.categoryGroups);
|
|
240
|
-
assert.ok(!categoryDOM2.querySelector('.lh-clump--notapplicable'));
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('renders a dash score if the category contains 0 applicable audits', () => {
|
|
244
|
-
for (const auditRef of a11yCategory.auditRefs) {
|
|
245
|
-
auditRef.result.scoreDisplayMode = 'notApplicable';
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const categoryDOM = renderer.render(a11yCategory, sampleResults.categoryGroups);
|
|
249
|
-
const percentageEl = categoryDOM.querySelectorAll('[title="Not applicable"]');
|
|
250
|
-
|
|
251
|
-
assert.equal(percentageEl[0].textContent, '-', 'score shows a dash');
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('renders a non-dash score if the category contains at least 1 applicable audit', () => {
|
|
255
|
-
for (const auditRef of a11yCategory.auditRefs) {
|
|
256
|
-
auditRef.result.scoreDisplayMode = 'notApplicable';
|
|
257
|
-
}
|
|
258
|
-
a11yCategory.auditRefs[0].result.scoreDisplayMode = 'numeric';
|
|
259
|
-
|
|
260
|
-
const categoryDOM = renderer.render(a11yCategory, sampleResults.categoryGroups);
|
|
261
|
-
const percentageEl = categoryDOM.querySelectorAll('.lh-gauge__percentage');
|
|
262
|
-
|
|
263
|
-
const scoreText = percentageEl[0].textContent;
|
|
264
|
-
assert(!scoreText.includes('.'), 'score is integer');
|
|
265
|
-
assert(Number(scoreText) >= 0 && Number(scoreText) <= 100, 'score is 0-100');
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
describe('category with groups', () => {
|
|
270
|
-
let category;
|
|
271
|
-
|
|
272
|
-
beforeEach(() => {
|
|
273
|
-
category = sampleResults.categories.accessibility;
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('renders the category header', () => {
|
|
277
|
-
const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
|
|
278
|
-
|
|
279
|
-
const gauge = categoryDOM.querySelector('.lh-gauge__percentage');
|
|
280
|
-
const scoreText = gauge.textContent.trim();
|
|
281
|
-
assert(!scoreText.includes('.'), 'score is integer');
|
|
282
|
-
assert(Number(scoreText) >= 0 && Number(scoreText) <= 100, 'score is 0-100');
|
|
283
|
-
|
|
284
|
-
const score = categoryDOM.querySelector('.lh-category-header');
|
|
285
|
-
const value = categoryDOM.querySelector('.lh-gauge__percentage');
|
|
286
|
-
const title = score.querySelector('.lh-gauge__label');
|
|
287
|
-
const description = score.querySelector('.lh-category-header__description');
|
|
288
|
-
|
|
289
|
-
assert.deepEqual(score, score.firstElementChild, 'first child is a score');
|
|
290
|
-
const scoreInDom = Number(value.textContent);
|
|
291
|
-
assert.ok(Number.isInteger(scoreInDom) && scoreInDom > 10, 'score is rounded out of 100');
|
|
292
|
-
assert.equal(title.textContent, category.title, 'title is set');
|
|
293
|
-
assert.ok(description.querySelector('a'), 'description contains converted markdown links');
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('renders the category header with fraction', () => {
|
|
297
|
-
const categoryDOM = renderer.render(
|
|
298
|
-
category,
|
|
299
|
-
sampleResults.categoryGroups,
|
|
300
|
-
{gatherMode: 'snapshot'}
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
const gauge = categoryDOM.querySelector('.lh-fraction__content');
|
|
304
|
-
assert.equal(gauge.textContent.trim(), '12/17', 'fraction is included');
|
|
305
|
-
|
|
306
|
-
const score = categoryDOM.querySelector('.lh-category-header');
|
|
307
|
-
const title = score.querySelector('.lh-fraction__label');
|
|
308
|
-
const description = score.querySelector('.lh-category-header__description');
|
|
309
|
-
|
|
310
|
-
assert.deepEqual(score, score.firstElementChild, 'first child is a score');
|
|
311
|
-
assert.equal(title.textContent, category.title, 'title is set');
|
|
312
|
-
assert.ok(description.querySelector('a'), 'description contains converted markdown links');
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it('renders the failed audits grouped by group', () => {
|
|
316
|
-
// Fail all the audits.
|
|
317
|
-
const categoryClone = JSON.parse(JSON.stringify(category));
|
|
318
|
-
const auditRefs = categoryClone.auditRefs;
|
|
319
|
-
auditRefs.forEach(ref => {
|
|
320
|
-
ref.result.score = 0;
|
|
321
|
-
ref.result.scoreDisplayMode = 'binary';
|
|
322
|
-
});
|
|
323
|
-
const categoryDOM = renderer.render(categoryClone, sampleResults.categoryGroups);
|
|
324
|
-
|
|
325
|
-
// All the group names in the config.
|
|
326
|
-
const groupNames = Array.from(new Set(auditRefs.map(ref => ref.group))).filter(Boolean);
|
|
327
|
-
assert.ok(groupNames.length > 5, `not enough groups found in category for test`);
|
|
328
|
-
|
|
329
|
-
// All the group roots in the DOM.
|
|
330
|
-
const failedGroupElems = Array.from(
|
|
331
|
-
categoryDOM.querySelectorAll('.lh-clump--failed > .lh-audit-group'));
|
|
332
|
-
|
|
333
|
-
assert.strictEqual(failedGroupElems.length, groupNames.length);
|
|
334
|
-
|
|
335
|
-
for (const groupName of groupNames) {
|
|
336
|
-
const groupAuditRefs = auditRefs.filter(ref => ref.group === groupName);
|
|
337
|
-
assert.ok(groupAuditRefs.length > 0, `no auditRefs found with group '${groupName}'`);
|
|
338
|
-
|
|
339
|
-
const className = `lh-audit-group--${groupName}`;
|
|
340
|
-
const groupElem = failedGroupElems.find(el => el.classList.contains(className));
|
|
341
|
-
const groupAuditElems = groupElem.querySelectorAll('.lh-audit');
|
|
342
|
-
|
|
343
|
-
assert.strictEqual(groupAuditElems.length, groupAuditRefs.length);
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it('renders the passed audits ungrouped', () => {
|
|
348
|
-
const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
|
|
349
|
-
const passedAudits = category.auditRefs.filter(audit =>
|
|
350
|
-
audit.result.scoreDisplayMode !== 'notApplicable' && audit.result.score === 1);
|
|
351
|
-
|
|
352
|
-
const passedAuditGroups = categoryDOM.querySelectorAll('.lh-clump--passed .lh-audit-group');
|
|
353
|
-
const passedAuditsElems = categoryDOM.querySelectorAll('.lh-clump--passed .lh-audit');
|
|
354
|
-
|
|
355
|
-
assert.equal(passedAuditGroups.length, 0);
|
|
356
|
-
assert.equal(passedAuditsElems.length, passedAudits.length);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it('renders all the audits', () => {
|
|
360
|
-
const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
|
|
361
|
-
const auditsElements = categoryDOM.querySelectorAll('.lh-audit');
|
|
362
|
-
assert.equal(auditsElements.length, category.auditRefs.length);
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
it('renders audits without a group before grouped ones', () => {
|
|
366
|
-
const categoryClone = JSON.parse(JSON.stringify(category));
|
|
367
|
-
|
|
368
|
-
// Remove groups from some audits.
|
|
369
|
-
const ungroupedAudits = ['image-alt', 'link-name'];
|
|
370
|
-
for (const auditRef of categoryClone.auditRefs) {
|
|
371
|
-
if (ungroupedAudits.includes(auditRef.id)) {
|
|
372
|
-
assert.ok(auditRef.group); // Make sure this will change something.
|
|
373
|
-
delete auditRef.group;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const elem = renderer.render(categoryClone, sampleResults.categoryGroups);
|
|
378
|
-
|
|
379
|
-
// Check that the first audits found are the ungrouped ones.
|
|
380
|
-
const auditElems = Array.from(elem.querySelectorAll('.lh-audit'));
|
|
381
|
-
const firstAuditElems = auditElems.slice(0, ungroupedAudits.length);
|
|
382
|
-
for (const auditElem of firstAuditElems) {
|
|
383
|
-
const auditId = auditElem.id;
|
|
384
|
-
assert.ok(ungroupedAudits.includes(auditId), auditId);
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it('gives each group a selectable class', () => {
|
|
389
|
-
const categoryClone = JSON.parse(JSON.stringify(category));
|
|
390
|
-
// Force all results to be Failed for accurate counting of groups.
|
|
391
|
-
categoryClone.auditRefs.forEach(ref => {
|
|
392
|
-
ref.result.score = 0;
|
|
393
|
-
ref.result.scoreDisplayMode = 'binary';
|
|
394
|
-
});
|
|
395
|
-
const categoryGroupIds = new Set(category.auditRefs.filter(a => a.group).map(a => a.group));
|
|
396
|
-
assert.ok(categoryGroupIds.size > 6); // Ensure there's something to test.
|
|
397
|
-
|
|
398
|
-
const categoryElem = renderer.render(categoryClone, sampleResults.categoryGroups);
|
|
399
|
-
|
|
400
|
-
categoryGroupIds.forEach(groupId => {
|
|
401
|
-
const selector = `.lh-audit-group--${groupId}`;
|
|
402
|
-
assert.equal(categoryElem.querySelectorAll(selector).length, 1,
|
|
403
|
-
`could not find '${selector}'`);
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
describe('clumping passed/failed/warning/manual', () => {
|
|
409
|
-
it('separates audits in the DOM', () => {
|
|
410
|
-
const category = sampleResults.categories.pwa;
|
|
411
|
-
const categoryClone = JSON.parse(JSON.stringify(category));
|
|
412
|
-
// Give the first two passing grades warnings
|
|
413
|
-
const passingRefs = categoryClone.auditRefs.filter(ref => ref.result.score === 1);
|
|
414
|
-
passingRefs[0].result.warnings = ['Some warning'];
|
|
415
|
-
passingRefs[1].result.warnings = ['Some warning'];
|
|
416
|
-
// Make one audit n/a
|
|
417
|
-
const audit = categoryClone.auditRefs.find(ref => ref.id === 'themed-omnibox');
|
|
418
|
-
audit.result.scoreDisplayMode = 'notApplicable';
|
|
419
|
-
audit.result.score = null;
|
|
420
|
-
|
|
421
|
-
const elem = renderer.render(categoryClone, sampleResults.categoryGroups);
|
|
422
|
-
const passedAudits = elem.querySelectorAll('.lh-clump--passed .lh-audit');
|
|
423
|
-
const failedAudits = elem.querySelectorAll('.lh-clump--failed .lh-audit');
|
|
424
|
-
const warningAudits = elem.querySelectorAll('.lh-clump--warning .lh-audit');
|
|
425
|
-
const manualAudits = elem.querySelectorAll('.lh-clump--manual .lh-audit');
|
|
426
|
-
const naAudits = elem.querySelectorAll('.lh-clump--notapplicable .lh-audit');
|
|
427
|
-
|
|
428
|
-
assert.equal(passedAudits.length, 0);
|
|
429
|
-
assert.equal(failedAudits.length, 4);
|
|
430
|
-
assert.equal(warningAudits.length, 2);
|
|
431
|
-
assert.equal(manualAudits.length, 3);
|
|
432
|
-
assert.equal(naAudits.length, 1);
|
|
433
|
-
|
|
434
|
-
const allAudits = elem.querySelectorAll('.lh-audit');
|
|
435
|
-
// No unaccounted audits
|
|
436
|
-
assert.equal(allAudits.length - passedAudits.length - failedAudits.length -
|
|
437
|
-
warningAudits.length - manualAudits.length - naAudits.length, 0);
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
it('doesnt create a passed section if there were 0 passed', () => {
|
|
441
|
-
const origCategory = sampleResults.categories.pwa;
|
|
442
|
-
const category = JSON.parse(JSON.stringify(origCategory));
|
|
443
|
-
category.auditRefs.forEach(audit => audit.result.score = 0);
|
|
444
|
-
const elem = renderer.render(category, sampleResults.categoryGroups);
|
|
445
|
-
const passedAudits = elem.querySelectorAll('.lh-clump--passed .lh-audit');
|
|
446
|
-
const failedAudits = elem.querySelectorAll('.lh-clump--failed .lh-audit');
|
|
447
|
-
|
|
448
|
-
assert.equal(passedAudits.length, 0);
|
|
449
|
-
assert.equal(failedAudits.length, 7);
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
it('expands warning audit group', () => {
|
|
453
|
-
const category = sampleResults.categories.pwa;
|
|
454
|
-
const categoryClone = JSON.parse(JSON.stringify(category));
|
|
455
|
-
const failingAudit = categoryClone.auditRefs.find(ref => ref.id === 'content-width');
|
|
456
|
-
failingAudit.result.warnings = ['Some warning'];
|
|
457
|
-
|
|
458
|
-
const auditDOM = renderer.render(categoryClone, sampleResults.categoryGroups);
|
|
459
|
-
const warningClumpEl = auditDOM.querySelector('.lh-clump--warning');
|
|
460
|
-
const isExpanded = warningClumpEl.hasAttribute('open');
|
|
461
|
-
assert.ok(isExpanded, 'Warning audit group should be expanded by default');
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
it('only passing audits with warnings show in warnings section', () => {
|
|
465
|
-
const failingWarning = 'Failed and warned';
|
|
466
|
-
const passingWarning = 'A passing warning';
|
|
467
|
-
const category = {
|
|
468
|
-
id: 'test',
|
|
469
|
-
title: 'Test',
|
|
470
|
-
score: 0,
|
|
471
|
-
auditRefs: [{
|
|
472
|
-
id: 'failing',
|
|
473
|
-
result: {
|
|
474
|
-
id: 'failing',
|
|
475
|
-
title: 'Failing with warning',
|
|
476
|
-
description: '',
|
|
477
|
-
scoreDisplayMode: 'numeric',
|
|
478
|
-
score: 0,
|
|
479
|
-
warnings: [failingWarning],
|
|
480
|
-
},
|
|
481
|
-
}, {
|
|
482
|
-
id: 'passing',
|
|
483
|
-
result: {
|
|
484
|
-
id: 'passing',
|
|
485
|
-
title: 'Passing with warning',
|
|
486
|
-
description: '',
|
|
487
|
-
scoreDisplayMode: 'numeric',
|
|
488
|
-
score: 1,
|
|
489
|
-
warnings: [passingWarning],
|
|
490
|
-
},
|
|
491
|
-
}],
|
|
492
|
-
};
|
|
493
|
-
const categoryDOM = renderer.render(category);
|
|
494
|
-
|
|
495
|
-
const shouldBeFailed = categoryDOM.querySelectorAll('.lh-clump--failed .lh-audit');
|
|
496
|
-
assert.strictEqual(shouldBeFailed.length, 1);
|
|
497
|
-
assert.strictEqual(shouldBeFailed[0].id, 'failing');
|
|
498
|
-
assert.ok(shouldBeFailed[0].textContent.includes(failingWarning));
|
|
499
|
-
|
|
500
|
-
const shouldBeWarning = categoryDOM.querySelectorAll('.lh-clump--warning .lh-audit');
|
|
501
|
-
assert.strictEqual(shouldBeWarning.length, 1);
|
|
502
|
-
assert.strictEqual(shouldBeWarning[0].id, 'passing');
|
|
503
|
-
assert.ok(shouldBeWarning[0].textContent.includes(passingWarning));
|
|
504
|
-
});
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
describe('renderCategoryScore', () => {
|
|
508
|
-
it('removes label if omitLabel is true', () => {
|
|
509
|
-
const options = {omitLabel: true};
|
|
510
|
-
const categoryScore = renderer.renderCategoryScore(
|
|
511
|
-
sampleResults.categories.performance,
|
|
512
|
-
{},
|
|
513
|
-
options
|
|
514
|
-
);
|
|
515
|
-
const label = categoryScore.querySelector('.lh-gauge__label,.lh-fraction__label');
|
|
516
|
-
assert.ok(!label);
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
it('uses custom callback if present', () => {
|
|
520
|
-
const options = {
|
|
521
|
-
onPageAnchorRendered: link => {
|
|
522
|
-
link.href = '#index=0&anchor=performance';
|
|
523
|
-
},
|
|
524
|
-
};
|
|
525
|
-
const categoryScore = renderer.renderCategoryScore(
|
|
526
|
-
sampleResults.categories.performance,
|
|
527
|
-
{},
|
|
528
|
-
options
|
|
529
|
-
);
|
|
530
|
-
const link = categoryScore.querySelector('a');
|
|
531
|
-
assert.equal(link.hash, '#index=0&anchor=performance');
|
|
532
|
-
});
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
it('renders audits by weight', () => {
|
|
536
|
-
const defaultAuditRef = {
|
|
537
|
-
title: '',
|
|
538
|
-
description: '',
|
|
539
|
-
scoreDisplayMode: 'numeric',
|
|
540
|
-
score: 0,
|
|
541
|
-
warnings: [],
|
|
542
|
-
};
|
|
543
|
-
const category = {
|
|
544
|
-
id: 'test',
|
|
545
|
-
title: 'Test',
|
|
546
|
-
score: 0,
|
|
547
|
-
auditRefs: [{
|
|
548
|
-
id: 'audit-1',
|
|
549
|
-
weight: 0,
|
|
550
|
-
result: {
|
|
551
|
-
id: 'audit-1',
|
|
552
|
-
...defaultAuditRef,
|
|
553
|
-
},
|
|
554
|
-
}, {
|
|
555
|
-
id: 'audit-2',
|
|
556
|
-
weight: 1,
|
|
557
|
-
result: {
|
|
558
|
-
id: 'audit-2',
|
|
559
|
-
...defaultAuditRef,
|
|
560
|
-
},
|
|
561
|
-
}, {
|
|
562
|
-
id: 'audit-3',
|
|
563
|
-
weight: 0.5,
|
|
564
|
-
result: {
|
|
565
|
-
id: 'audit-3',
|
|
566
|
-
...defaultAuditRef,
|
|
567
|
-
},
|
|
568
|
-
}],
|
|
569
|
-
};
|
|
570
|
-
const categoryDOM = renderer.render(category);
|
|
571
|
-
|
|
572
|
-
const auditEls = [...categoryDOM.querySelectorAll('.lh-audit')];
|
|
573
|
-
expect(auditEls.map(el => el.id)).toEqual(['audit-2', 'audit-3', 'audit-1']);
|
|
574
|
-
});
|
|
575
|
-
});
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
-
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
-
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import fs from 'fs';
|
|
8
|
-
|
|
9
|
-
import jsdom from 'jsdom';
|
|
10
|
-
|
|
11
|
-
import {DOM} from '../../renderer/dom.js';
|
|
12
|
-
import {LH_ROOT} from '../../../root.js';
|
|
13
|
-
import {normalizeTextNodeText} from '../../../build/build-report-components.js';
|
|
14
|
-
|
|
15
|
-
const html = fs.readFileSync(LH_ROOT + '/report/assets/templates.html', 'utf-8');
|
|
16
|
-
const {window} = new jsdom.JSDOM(html);
|
|
17
|
-
const tmplEls = window.document.querySelectorAll('template');
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @param {HTMLTemplateElement} tmplEl
|
|
21
|
-
*/
|
|
22
|
-
async function assertDOMTreeMatches(tmplEl) {
|
|
23
|
-
global.document = window.document;
|
|
24
|
-
global.Node = window.Node;
|
|
25
|
-
global.DocumentFragment = window.DocumentFragment;
|
|
26
|
-
|
|
27
|
-
const dom = new DOM(window.document);
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @param {HTMLElement} parentEl
|
|
31
|
-
*/
|
|
32
|
-
function normalizeNodes(parentEl) {
|
|
33
|
-
for (const child of Array.from(parentEl.childNodes)) {
|
|
34
|
-
if (child.nodeType === window.Node.TEXT_NODE) {
|
|
35
|
-
const text = normalizeTextNodeText(child);
|
|
36
|
-
if (!text) parentEl.removeChild(child);
|
|
37
|
-
else child.textContent = text;
|
|
38
|
-
} else if (child.nodeType === window.Node.COMMENT_NODE) {
|
|
39
|
-
parentEl.removeChild(child);
|
|
40
|
-
} else if (child.nodeType === window.Node.ELEMENT_NODE) {
|
|
41
|
-
normalizeNodes(child);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @param {HTMLElement} rootEl
|
|
48
|
-
*/
|
|
49
|
-
function normalizeAttributes(rootEl) {
|
|
50
|
-
for (const el of rootEl.querySelectorAll('*')) {
|
|
51
|
-
const clonedAttrNodes = Array.from(el.attributes);
|
|
52
|
-
// Clear existing.
|
|
53
|
-
clonedAttrNodes.forEach(attr => el.removeAttribute(attr.localName));
|
|
54
|
-
// Apply class first, then the rest.
|
|
55
|
-
const classAttr = clonedAttrNodes.find(attr => attr.localName === 'class');
|
|
56
|
-
if (classAttr) {
|
|
57
|
-
el.setAttributeNode(classAttr);
|
|
58
|
-
}
|
|
59
|
-
clonedAttrNodes.forEach(attr => {
|
|
60
|
-
if (attr !== classAttr) el.setAttributeNode(attr);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** @type {DocumentFragment} */
|
|
66
|
-
const generatedFragment = dom.createComponent(tmplEl.id);
|
|
67
|
-
const originalFragment = tmplEl.content.cloneNode(true);
|
|
68
|
-
|
|
69
|
-
// We rely on innerHTML to do a comparison, so we must normalize some things about the
|
|
70
|
-
// original element for two reasons:
|
|
71
|
-
// 1) Some nodes are purposefully not created in components.js; or the text content is trimmed.
|
|
72
|
-
normalizeNodes(originalFragment);
|
|
73
|
-
// 2) jsdom reorders the class attribute in unexpected ways. We always author it as the first attribute, but
|
|
74
|
-
// sometimes jsdom moves it around.
|
|
75
|
-
normalizeAttributes(originalFragment);
|
|
76
|
-
|
|
77
|
-
expect(generatedFragment.childNodes.length).toEqual(originalFragment.childNodes.length);
|
|
78
|
-
for (let i = 0; i < generatedFragment.childNodes.length; i++) {
|
|
79
|
-
expect(generatedFragment.childNodes[i].innerHTML)
|
|
80
|
-
.toEqual(originalFragment.childNodes[i].innerHTML);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
describe('Components', () => {
|
|
85
|
-
const originalCreateElement = DOM.prototype.createElement;
|
|
86
|
-
const originalCreateElementNS = DOM.prototype.createElementNS;
|
|
87
|
-
|
|
88
|
-
before(() => {
|
|
89
|
-
/**
|
|
90
|
-
* @param {string} classNames
|
|
91
|
-
*/
|
|
92
|
-
function checkPrefix(classNames) {
|
|
93
|
-
if (!classNames) return;
|
|
94
|
-
|
|
95
|
-
for (const className of classNames.split(' ')) {
|
|
96
|
-
if (!className.startsWith('lh-')) {
|
|
97
|
-
throw new Error(`expected classname to start with lh-, got: ${className}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
DOM.prototype.createElement = function(...args) {
|
|
103
|
-
const classNames = args[1];
|
|
104
|
-
checkPrefix(classNames);
|
|
105
|
-
return originalCreateElement.call(this, ...args);
|
|
106
|
-
};
|
|
107
|
-
DOM.prototype.createElementNS = function(...args) {
|
|
108
|
-
const classNames = args[2];
|
|
109
|
-
checkPrefix(classNames);
|
|
110
|
-
return originalCreateElementNS.call(this, ...args);
|
|
111
|
-
};
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
after(() => {
|
|
115
|
-
DOM.prototype.createElement = originalCreateElement;
|
|
116
|
-
DOM.prototype.createElementNS = originalCreateElementNS;
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
for (const tmpEl of tmplEls) {
|
|
120
|
-
it(`${tmpEl.id} component matches HTML source`, async () => {
|
|
121
|
-
await assertDOMTreeMatches(tmpEl);
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
});
|