lighthouse 8.5.1-dev.20211007 → 8.5.1-dev.20211011
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/flow-report/assets/styles.css +1 -1
- package/flow-report/src/common.tsx +43 -1
- package/flow-report/src/summary/category.tsx +19 -3
- package/flow-report/src/summary/summary.tsx +2 -4
- package/flow-report/src/util.ts +15 -0
- package/flow-report/test/common-test.tsx +89 -0
- package/flow-report/test/summary/category-test.tsx +37 -12
- package/lighthouse-cli/test/smokehouse/report-assert.js +20 -4
- package/lighthouse-core/util-commonjs.js +20 -4
- package/package.json +2 -1
- package/report/renderer/category-renderer.js +3 -3
- package/report/renderer/performance-category-renderer.js +22 -20
- package/report/renderer/util.js +20 -4
- package/report/test/renderer/category-renderer-test.js +1 -1
- package/report/test/renderer/performance-category-renderer-test.js +12 -0
- package/report/test/renderer/util-test.js +68 -8
|
@@ -373,7 +373,7 @@
|
|
|
373
373
|
width: max-content;
|
|
374
374
|
background-color: var(--report-background-color);
|
|
375
375
|
border: 1px solid var(--color-gray-900);
|
|
376
|
-
border-radius:
|
|
376
|
+
border-radius: 3px;
|
|
377
377
|
padding: var(--base-spacing);
|
|
378
378
|
right: 0;
|
|
379
379
|
box-shadow: 0px 4px 4px var(--summary-tooltip-box-shadow-color);
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {FunctionComponent} from 'preact';
|
|
8
|
+
import {useEffect, useState} from 'preact/hooks';
|
|
8
9
|
|
|
9
10
|
import {NavigationIcon, SnapshotIcon, TimespanIcon} from './icons';
|
|
10
|
-
import {getScreenDimensions, getScreenshot} from './util';
|
|
11
|
+
import {getFilmstripFrames, getScreenDimensions, getScreenshot} from './util';
|
|
12
|
+
|
|
13
|
+
const ANIMATION_FRAME_DURATION_MS = 250;
|
|
11
14
|
|
|
12
15
|
export const Separator: FunctionComponent = () => {
|
|
13
16
|
return <div className="Separator" role="separator"></div>;
|
|
@@ -39,12 +42,42 @@ export const FlowSegment: FunctionComponent<{mode?: LH.Result.GatherMode}> = ({m
|
|
|
39
42
|
);
|
|
40
43
|
};
|
|
41
44
|
|
|
45
|
+
const FlowStepAnimatedThumbnail: FunctionComponent<{
|
|
46
|
+
frames: Array<{data: string}>,
|
|
47
|
+
width: number,
|
|
48
|
+
height: number,
|
|
49
|
+
}> = ({frames, width, height}) => {
|
|
50
|
+
const [frameIndex, setFrameIndex] = useState(0);
|
|
51
|
+
// Handle a frame array of a different length being set.
|
|
52
|
+
const effectiveFrameIndex = frameIndex % frames.length;
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const interval = setInterval(
|
|
56
|
+
() => setFrameIndex(i => (i + 1) % frames.length),
|
|
57
|
+
ANIMATION_FRAME_DURATION_MS
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return () => clearInterval(interval);
|
|
61
|
+
}, [frames.length]);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<img
|
|
65
|
+
className="FlowStepThumbnail"
|
|
66
|
+
data-testid="FlowStepAnimatedThumbnail"
|
|
67
|
+
src={frames[effectiveFrameIndex].data}
|
|
68
|
+
style={{width, height}}
|
|
69
|
+
alt="Animated screenshots of a page tested by Lighthouse"
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
42
74
|
export const FlowStepThumbnail: FunctionComponent<{
|
|
43
75
|
reportResult: LH.ReportResult,
|
|
44
76
|
width?: number,
|
|
45
77
|
height?: number,
|
|
46
78
|
}> = ({reportResult, width, height}) => {
|
|
47
79
|
const screenshot = getScreenshot(reportResult);
|
|
80
|
+
const frames = getFilmstripFrames(reportResult);
|
|
48
81
|
|
|
49
82
|
// Resize the image to fit the viewport aspect ratio.
|
|
50
83
|
const dimensions = getScreenDimensions(reportResult);
|
|
@@ -54,6 +87,15 @@ export const FlowStepThumbnail: FunctionComponent<{
|
|
|
54
87
|
width = dimensions.width * height / dimensions.height;
|
|
55
88
|
}
|
|
56
89
|
|
|
90
|
+
if (!width || !height) {
|
|
91
|
+
console.warn(new Error('FlowStepThumbnail requested without any dimensions').stack);
|
|
92
|
+
return <></>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (reportResult.gatherMode === 'timespan' && frames && frames.length) {
|
|
96
|
+
return <FlowStepAnimatedThumbnail frames={frames} width={width} height={height} />;
|
|
97
|
+
}
|
|
98
|
+
|
|
57
99
|
return <>
|
|
58
100
|
{
|
|
59
101
|
screenshot &&
|
|
@@ -35,11 +35,16 @@ export const SummaryTooltip: FunctionComponent<{
|
|
|
35
35
|
gatherMode: LH.Result.GatherMode
|
|
36
36
|
}> = ({category, gatherMode}) => {
|
|
37
37
|
const strings = useUIStrings();
|
|
38
|
-
const {
|
|
38
|
+
const {
|
|
39
|
+
numPassed,
|
|
40
|
+
numPassableAudits,
|
|
41
|
+
numInformative,
|
|
42
|
+
totalWeight,
|
|
43
|
+
} = Util.calculateCategoryFraction(category);
|
|
39
44
|
|
|
40
45
|
const displayAsFraction = Util.shouldDisplayAsFraction(gatherMode);
|
|
41
46
|
const rating = displayAsFraction ?
|
|
42
|
-
Util.calculateRating(numPassed /
|
|
47
|
+
Util.calculateRating(numPassed / numPassableAudits) :
|
|
43
48
|
Util.calculateRating(category.score);
|
|
44
49
|
|
|
45
50
|
return (
|
|
@@ -64,8 +69,19 @@ export const SummaryTooltip: FunctionComponent<{
|
|
|
64
69
|
}
|
|
65
70
|
</div>
|
|
66
71
|
<div className="SummaryTooltip__fraction">
|
|
67
|
-
{
|
|
72
|
+
{
|
|
73
|
+
// TODO(FLOW-I18N): Placeholder format.
|
|
74
|
+
`${numPassed} audits passed / ${numPassableAudits} passable audits`
|
|
75
|
+
}
|
|
68
76
|
</div>
|
|
77
|
+
{
|
|
78
|
+
// TODO(FLOW-I18N): Placeholder format.
|
|
79
|
+
numInformative ?
|
|
80
|
+
<div className="SummaryTooltip__informative">
|
|
81
|
+
{`${numInformative} informative audits`}
|
|
82
|
+
</div> :
|
|
83
|
+
null
|
|
84
|
+
}
|
|
69
85
|
</div>
|
|
70
86
|
);
|
|
71
87
|
};
|
|
@@ -63,10 +63,7 @@ export const SummaryFlowStep: FunctionComponent<{
|
|
|
63
63
|
<Separator/>
|
|
64
64
|
</div>
|
|
65
65
|
}
|
|
66
|
-
{
|
|
67
|
-
lhr.gatherMode !== 'timespan' &&
|
|
68
|
-
<FlowStepThumbnail reportResult={reportResult} width={THUMBNAIL_WIDTH}/>
|
|
69
|
-
}
|
|
66
|
+
<FlowStepThumbnail reportResult={reportResult} width={THUMBNAIL_WIDTH}/>
|
|
70
67
|
<FlowSegment mode={lhr.gatherMode}/>
|
|
71
68
|
<div className="SummaryFlowStep__label">
|
|
72
69
|
<div className="SummaryFlowStep__mode">{modeDescription}</div>
|
|
@@ -129,6 +126,7 @@ export const SummaryHeader: FunctionComponent = () => {
|
|
|
129
126
|
}
|
|
130
127
|
}
|
|
131
128
|
|
|
129
|
+
// TODO(FLOW-I18N): Placeholder format.
|
|
132
130
|
const subtitleCounts = [];
|
|
133
131
|
if (numNavigation) subtitleCounts.push(`${numNavigation} navigation reports`);
|
|
134
132
|
if (numTimespan) subtitleCounts.push(`${numTimespan} timespan reports`);
|
package/flow-report/src/util.ts
CHANGED
|
@@ -43,6 +43,7 @@ export function getScreenDimensions(reportResult: LH.ReportResult) {
|
|
|
43
43
|
export function getScreenshot(reportResult: LH.ReportResult) {
|
|
44
44
|
const fullPageScreenshotAudit = reportResult.audits['full-page-screenshot'];
|
|
45
45
|
const fullPageScreenshot =
|
|
46
|
+
fullPageScreenshotAudit &&
|
|
46
47
|
fullPageScreenshotAudit.details &&
|
|
47
48
|
fullPageScreenshotAudit.details.type === 'full-page-screenshot' &&
|
|
48
49
|
fullPageScreenshotAudit.details.screenshot.data;
|
|
@@ -50,6 +51,20 @@ export function getScreenshot(reportResult: LH.ReportResult) {
|
|
|
50
51
|
return fullPageScreenshot || null;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
export function getFilmstripFrames(
|
|
55
|
+
reportResult: LH.ReportResult
|
|
56
|
+
): Array<{data: string}> | undefined {
|
|
57
|
+
const filmstripAudit = reportResult.audits['screenshot-thumbnails'];
|
|
58
|
+
if (!filmstripAudit) return undefined;
|
|
59
|
+
|
|
60
|
+
const frameItems =
|
|
61
|
+
filmstripAudit.details &&
|
|
62
|
+
filmstripAudit.details.type === 'filmstrip' &&
|
|
63
|
+
filmstripAudit.details.items;
|
|
64
|
+
|
|
65
|
+
return frameItems || undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
export function getModeDescription(mode: LH.Result.GatherMode, strings: UIStringsType) {
|
|
54
69
|
switch (mode) {
|
|
55
70
|
case 'navigation': return strings.navigationDescription;
|
|
@@ -0,0 +1,89 @@
|
|
|
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 {jest} from '@jest/globals';
|
|
8
|
+
import {act, render} from '@testing-library/preact';
|
|
9
|
+
|
|
10
|
+
import {FlowStepThumbnail} from '../src/common';
|
|
11
|
+
|
|
12
|
+
let lhr: LH.ReportResult;
|
|
13
|
+
|
|
14
|
+
jest.useFakeTimers();
|
|
15
|
+
|
|
16
|
+
describe('FlowStepThumbnail', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
global.console.warn = jest.fn();
|
|
19
|
+
|
|
20
|
+
lhr = {
|
|
21
|
+
gatherMode: 'navigation',
|
|
22
|
+
configSettings: {screenEmulation: {width: 400, height: 600}},
|
|
23
|
+
audits: {
|
|
24
|
+
'full-page-screenshot': {
|
|
25
|
+
details: {
|
|
26
|
+
type: 'full-page-screenshot',
|
|
27
|
+
screenshot: {data: 'baef01', width: 400, height: 600},
|
|
28
|
+
nodes: {},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
} as any;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders a thumbnail', () => {
|
|
36
|
+
const root = render(<FlowStepThumbnail reportResult={lhr} width={200} height={200} />);
|
|
37
|
+
|
|
38
|
+
const thumbnail = root.getByAltText(/Screenshot/);
|
|
39
|
+
expect(thumbnail.style.width).toEqual('200px');
|
|
40
|
+
expect(thumbnail.style.height).toEqual('200px');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('renders nothing without dimensions', () => {
|
|
44
|
+
const root = render(<FlowStepThumbnail reportResult={lhr} />);
|
|
45
|
+
|
|
46
|
+
expect(() => root.getByAltText(/Screenshot/)).toThrow();
|
|
47
|
+
expect(global.console.warn).toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('interpolates height', () => {
|
|
51
|
+
const root = render(<FlowStepThumbnail reportResult={lhr} width={200} />);
|
|
52
|
+
|
|
53
|
+
const thumbnail = root.getByAltText(/Screenshot/);
|
|
54
|
+
expect(thumbnail.style.width).toEqual('200px');
|
|
55
|
+
expect(thumbnail.style.height).toEqual('300px');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('interpolates width', () => {
|
|
59
|
+
const root = render(<FlowStepThumbnail reportResult={lhr} height={150} />);
|
|
60
|
+
|
|
61
|
+
const thumbnail = root.getByAltText(/Screenshot/);
|
|
62
|
+
expect(thumbnail.style.width).toEqual('100px');
|
|
63
|
+
expect(thumbnail.style.height).toEqual('150px');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('renders animated thumbnail for timespan', async () => {
|
|
67
|
+
lhr.gatherMode = 'timespan';
|
|
68
|
+
lhr.audits['screenshot-thumbnails'] = {
|
|
69
|
+
details: {
|
|
70
|
+
type: 'filmstrip',
|
|
71
|
+
items: [
|
|
72
|
+
{data: 'frame1'},
|
|
73
|
+
{data: 'frame2'},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
} as any;
|
|
77
|
+
const root = render(<FlowStepThumbnail reportResult={lhr} height={150} />);
|
|
78
|
+
|
|
79
|
+
const thumbnail = root.getByAltText(/Animated/) as HTMLImageElement;
|
|
80
|
+
expect(thumbnail.style.width).toEqual('100px');
|
|
81
|
+
expect(thumbnail.style.height).toEqual('150px');
|
|
82
|
+
|
|
83
|
+
expect(thumbnail.src).toContain('frame1');
|
|
84
|
+
await act(() => {
|
|
85
|
+
jest.advanceTimersByTime(251);
|
|
86
|
+
});
|
|
87
|
+
expect(thumbnail.src).toContain('frame2');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -28,11 +28,12 @@ beforeEach(() => {
|
|
|
28
28
|
describe('SummaryTooltip', () => {
|
|
29
29
|
it('renders tooltip with rating', async () => {
|
|
30
30
|
const category: any = {
|
|
31
|
+
id: 'performance',
|
|
31
32
|
score: 1,
|
|
32
33
|
auditRefs: [
|
|
33
|
-
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1},
|
|
34
|
-
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1},
|
|
35
|
-
{result: {score: 0, scoreDisplayMode: 'binary'}, weight: 1},
|
|
34
|
+
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
|
|
35
|
+
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
|
|
36
|
+
{result: {score: 0, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
|
|
36
37
|
],
|
|
37
38
|
};
|
|
38
39
|
|
|
@@ -43,16 +44,17 @@ describe('SummaryTooltip', () => {
|
|
|
43
44
|
|
|
44
45
|
expect(root.getByText('Average')).toBeTruthy();
|
|
45
46
|
expect(() => root.getByText(/^[0-9]+$/)).toThrow();
|
|
46
|
-
expect(root.getByText('2 audits passed / 3 audits
|
|
47
|
+
expect(root.getByText('2 audits passed / 3 passable audits')).toBeTruthy();
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
it('renders tooltip without rating', async () => {
|
|
50
51
|
const category: any = {
|
|
52
|
+
id: 'performance',
|
|
51
53
|
score: 1,
|
|
52
54
|
auditRefs: [
|
|
53
|
-
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 0},
|
|
54
|
-
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 0},
|
|
55
|
-
{result: {score: 0, scoreDisplayMode: 'binary'}, weight: 0},
|
|
55
|
+
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 0, group: 'diagnostics'},
|
|
56
|
+
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 0, group: 'diagnostics'},
|
|
57
|
+
{result: {score: 0, scoreDisplayMode: 'binary'}, weight: 0, group: 'diagnostics'},
|
|
56
58
|
],
|
|
57
59
|
};
|
|
58
60
|
|
|
@@ -63,16 +65,17 @@ describe('SummaryTooltip', () => {
|
|
|
63
65
|
|
|
64
66
|
expect(() => root.getByText(/^(Average|Good|Poor)$/)).toThrow();
|
|
65
67
|
expect(() => root.getByText(/^[0-9]+$/)).toThrow();
|
|
66
|
-
expect(root.getByText('2 audits passed / 3 audits
|
|
68
|
+
expect(root.getByText('2 audits passed / 3 passable audits')).toBeTruthy();
|
|
67
69
|
});
|
|
68
70
|
|
|
69
71
|
it('renders scored category tooltip with score', async () => {
|
|
70
72
|
const category: any = {
|
|
73
|
+
id: 'performance',
|
|
71
74
|
score: 1,
|
|
72
75
|
auditRefs: [
|
|
73
|
-
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1},
|
|
74
|
-
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1},
|
|
75
|
-
{result: {score: 0, scoreDisplayMode: 'binary'}, weight: 1},
|
|
76
|
+
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
|
|
77
|
+
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
|
|
78
|
+
{result: {score: 0, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
|
|
76
79
|
],
|
|
77
80
|
};
|
|
78
81
|
|
|
@@ -83,6 +86,28 @@ describe('SummaryTooltip', () => {
|
|
|
83
86
|
|
|
84
87
|
expect(root.getByText('Good')).toBeTruthy();
|
|
85
88
|
expect(root.getByText('100')).toBeTruthy();
|
|
86
|
-
expect(root.getByText('2 audits passed / 3 audits
|
|
89
|
+
expect(root.getByText('2 audits passed / 3 passable audits')).toBeTruthy();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('renders informative audit count if any', async () => {
|
|
93
|
+
const category: any = {
|
|
94
|
+
id: 'performance',
|
|
95
|
+
score: 1,
|
|
96
|
+
auditRefs: [
|
|
97
|
+
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
|
|
98
|
+
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
|
|
99
|
+
{result: {score: 0, scoreDisplayMode: 'informative'}, weight: 1, group: 'diagnostics'},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const root = render(
|
|
104
|
+
<SummaryTooltip category={category} gatherMode="navigation"/>,
|
|
105
|
+
{wrapper}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(root.getByText('Good')).toBeTruthy();
|
|
109
|
+
expect(root.getByText('100')).toBeTruthy();
|
|
110
|
+
expect(root.getByText('2 audits passed / 2 passable audits')).toBeTruthy();
|
|
111
|
+
expect(root.getByText('1 informative audits')).toBeTruthy();
|
|
87
112
|
});
|
|
88
113
|
});
|
|
@@ -200,26 +200,42 @@ function pruneExpectations(localConsole, lhr, expected, reportOptions) {
|
|
|
200
200
|
JSON.stringify(value, null, 2),
|
|
201
201
|
`Actual Chromium version: ${getChromeVersion()}`,
|
|
202
202
|
].join(' '));
|
|
203
|
-
|
|
203
|
+
if (Array.isArray(obj)) {
|
|
204
|
+
obj.splice(Number(key), 1);
|
|
205
|
+
} else {
|
|
206
|
+
delete obj[key];
|
|
207
|
+
}
|
|
204
208
|
} else if (value._legacyOnly && isFraggleRock) {
|
|
205
209
|
localConsole.log([
|
|
206
210
|
`[${key}] marked legacy only but run is Fraggle Rock, pruning expectation:`,
|
|
207
211
|
JSON.stringify(value, null, 2),
|
|
208
212
|
].join(' '));
|
|
209
|
-
|
|
213
|
+
if (Array.isArray(obj)) {
|
|
214
|
+
obj.splice(Number(key), 1);
|
|
215
|
+
} else {
|
|
216
|
+
delete obj[key];
|
|
217
|
+
}
|
|
210
218
|
} else if (value._fraggleRockOnly && !isFraggleRock) {
|
|
211
219
|
localConsole.log([
|
|
212
220
|
`[${key}] marked Fraggle Rock only but run is legacy, pruning expectation:`,
|
|
213
221
|
JSON.stringify(value, null, 2),
|
|
214
222
|
`Actual channel: ${lhr.configSettings.channel}`,
|
|
215
223
|
].join(' '));
|
|
216
|
-
|
|
224
|
+
if (Array.isArray(obj)) {
|
|
225
|
+
obj.splice(Number(key), 1);
|
|
226
|
+
} else {
|
|
227
|
+
delete obj[key];
|
|
228
|
+
}
|
|
217
229
|
} else if (value._skipInBundled && !isBundled) {
|
|
218
230
|
localConsole.log([
|
|
219
231
|
`[${key}] marked as skip in bundled and runner is bundled, pruning expectation:`,
|
|
220
232
|
JSON.stringify(value, null, 2),
|
|
221
233
|
].join(' '));
|
|
222
|
-
|
|
234
|
+
if (Array.isArray(obj)) {
|
|
235
|
+
obj.splice(Number(key), 1);
|
|
236
|
+
} else {
|
|
237
|
+
delete obj[key];
|
|
238
|
+
}
|
|
223
239
|
} else {
|
|
224
240
|
pruneRecursively(value);
|
|
225
241
|
}
|
|
@@ -521,15 +521,31 @@ class Util {
|
|
|
521
521
|
* @param {LH.ReportResult.Category} category
|
|
522
522
|
*/
|
|
523
523
|
static calculateCategoryFraction(category) {
|
|
524
|
-
|
|
525
|
-
|
|
524
|
+
let numPassableAudits = 0;
|
|
526
525
|
let numPassed = 0;
|
|
526
|
+
let numInformative = 0;
|
|
527
527
|
let totalWeight = 0;
|
|
528
528
|
for (const auditRef of category.auditRefs) {
|
|
529
|
+
const auditPassed = Util.showAsPassed(auditRef.result);
|
|
530
|
+
const notDisplayed = !auditRef.group && category.id === 'performance';
|
|
531
|
+
|
|
532
|
+
// Don't count the audit if it's manual, N/A, or isn't displayed.
|
|
533
|
+
if (notDisplayed ||
|
|
534
|
+
auditRef.result.scoreDisplayMode === 'manual' ||
|
|
535
|
+
auditRef.result.scoreDisplayMode === 'notApplicable') {
|
|
536
|
+
continue;
|
|
537
|
+
} else if (auditRef.result.scoreDisplayMode === 'informative') {
|
|
538
|
+
if (!auditPassed) {
|
|
539
|
+
++numInformative;
|
|
540
|
+
}
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
++numPassableAudits;
|
|
529
545
|
totalWeight += auditRef.weight;
|
|
530
|
-
if (
|
|
546
|
+
if (auditPassed) numPassed++;
|
|
531
547
|
}
|
|
532
|
-
return {numPassed,
|
|
548
|
+
return {numPassed, numPassableAudits, numInformative, totalWeight};
|
|
533
549
|
}
|
|
534
550
|
}
|
|
535
551
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
|
-
"version": "8.5.1-dev.
|
|
3
|
+
"version": "8.5.1-dev.20211011",
|
|
4
4
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
5
5
|
"main": "./lighthouse-core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -176,6 +176,7 @@
|
|
|
176
176
|
"ts-jest": "^27.0.4",
|
|
177
177
|
"typed-query-selector": "^2.4.0",
|
|
178
178
|
"typescript": "4.4.3",
|
|
179
|
+
"wait-for-expect": "^3.0.2",
|
|
179
180
|
"webtreemap-cdt": "^3.2.1"
|
|
180
181
|
},
|
|
181
182
|
"dependencies": {
|
|
@@ -375,12 +375,12 @@ export class CategoryRenderer {
|
|
|
375
375
|
const wrapper = this.dom.find('a.lh-fraction__wrapper', tmpl);
|
|
376
376
|
this.dom.safelySetHref(wrapper, `#${category.id}`);
|
|
377
377
|
|
|
378
|
-
const {numPassed,
|
|
378
|
+
const {numPassed, numPassableAudits, totalWeight} = Util.calculateCategoryFraction(category);
|
|
379
379
|
|
|
380
|
-
const fraction = numPassed /
|
|
380
|
+
const fraction = numPassed / numPassableAudits;
|
|
381
381
|
const content = this.dom.find('.lh-fraction__content', tmpl);
|
|
382
382
|
const text = this.dom.createElement('span');
|
|
383
|
-
text.textContent = `${numPassed}/${
|
|
383
|
+
text.textContent = `${numPassed}/${numPassableAudits}`;
|
|
384
384
|
content.appendChild(text);
|
|
385
385
|
|
|
386
386
|
let rating = Util.calculateRating(fraction);
|
|
@@ -172,33 +172,35 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// Metrics.
|
|
175
|
-
const
|
|
175
|
+
const metricAudits = category.auditRefs.filter(audit => audit.group === 'metrics');
|
|
176
|
+
if (metricAudits.length) {
|
|
177
|
+
const metricAuditsEl = this.renderAuditGroup(groups.metrics);
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
// Metric descriptions toggle.
|
|
180
|
+
const toggleTmpl = this.dom.createComponent('metricsToggle');
|
|
181
|
+
const _toggleEl = this.dom.find('.lh-metrics-toggle', toggleTmpl);
|
|
182
|
+
metricAuditsEl.append(..._toggleEl.childNodes);
|
|
181
183
|
|
|
182
|
-
|
|
183
|
-
const metricsBoxesEl = this.dom.createChildOf(metricAuditsEl, 'div', 'lh-metrics-container');
|
|
184
|
+
const metricsBoxesEl = this.dom.createChildOf(metricAuditsEl, 'div', 'lh-metrics-container');
|
|
184
185
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
metricAudits.forEach(item => {
|
|
187
|
+
metricsBoxesEl.appendChild(this._renderMetric(item));
|
|
188
|
+
});
|
|
188
189
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
const estValuesEl = this.dom.createChildOf(metricAuditsEl, 'div', 'lh-metrics__disclaimer');
|
|
191
|
+
const disclaimerEl = this.dom.convertMarkdownLinkSnippets(strings.varianceDisclaimer);
|
|
192
|
+
estValuesEl.appendChild(disclaimerEl);
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
// Add link to score calculator.
|
|
195
|
+
const calculatorLink = this.dom.createChildOf(estValuesEl, 'a', 'lh-calclink');
|
|
196
|
+
calculatorLink.target = '_blank';
|
|
197
|
+
calculatorLink.textContent = strings.calculatorLink;
|
|
198
|
+
this.dom.safelySetHref(calculatorLink, this._getScoringCalculatorHref(category.auditRefs));
|
|
198
199
|
|
|
199
200
|
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
metricAuditsEl.classList.add('lh-audit-group--metrics');
|
|
202
|
+
element.appendChild(metricAuditsEl);
|
|
203
|
+
}
|
|
202
204
|
|
|
203
205
|
// Filmstrip
|
|
204
206
|
const timelineEl = this.dom.createChildOf(element, 'div', 'lh-filmstrip-container');
|
package/report/renderer/util.js
CHANGED
|
@@ -518,15 +518,31 @@ export class Util {
|
|
|
518
518
|
* @param {LH.ReportResult.Category} category
|
|
519
519
|
*/
|
|
520
520
|
static calculateCategoryFraction(category) {
|
|
521
|
-
|
|
522
|
-
|
|
521
|
+
let numPassableAudits = 0;
|
|
523
522
|
let numPassed = 0;
|
|
523
|
+
let numInformative = 0;
|
|
524
524
|
let totalWeight = 0;
|
|
525
525
|
for (const auditRef of category.auditRefs) {
|
|
526
|
+
const auditPassed = Util.showAsPassed(auditRef.result);
|
|
527
|
+
const notDisplayed = !auditRef.group && category.id === 'performance';
|
|
528
|
+
|
|
529
|
+
// Don't count the audit if it's manual, N/A, or isn't displayed.
|
|
530
|
+
if (notDisplayed ||
|
|
531
|
+
auditRef.result.scoreDisplayMode === 'manual' ||
|
|
532
|
+
auditRef.result.scoreDisplayMode === 'notApplicable') {
|
|
533
|
+
continue;
|
|
534
|
+
} else if (auditRef.result.scoreDisplayMode === 'informative') {
|
|
535
|
+
if (!auditPassed) {
|
|
536
|
+
++numInformative;
|
|
537
|
+
}
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
++numPassableAudits;
|
|
526
542
|
totalWeight += auditRef.weight;
|
|
527
|
-
if (
|
|
543
|
+
if (auditPassed) numPassed++;
|
|
528
544
|
}
|
|
529
|
-
return {numPassed,
|
|
545
|
+
return {numPassed, numPassableAudits, numInformative, totalWeight};
|
|
530
546
|
}
|
|
531
547
|
}
|
|
532
548
|
|
|
@@ -297,7 +297,7 @@ describe('CategoryRenderer', () => {
|
|
|
297
297
|
);
|
|
298
298
|
|
|
299
299
|
const gauge = categoryDOM.querySelector('.lh-fraction__content');
|
|
300
|
-
assert.equal(gauge.textContent.trim(), '
|
|
300
|
+
assert.equal(gauge.textContent.trim(), '13/18', 'fraction is included');
|
|
301
301
|
|
|
302
302
|
const score = categoryDOM.querySelector('.lh-category-header');
|
|
303
303
|
const title = score.querySelector('.lh-fraction__label');
|
|
@@ -69,6 +69,18 @@ describe('PerfCategoryRenderer', () => {
|
|
|
69
69
|
assert.equal(timelineElements.length + nontimelineElements.length, metricAudits.length);
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
it('does not render metrics section if no metric group audits', () => {
|
|
73
|
+
// Remove metrics from category
|
|
74
|
+
const newCategory = JSON.parse(JSON.stringify(category));
|
|
75
|
+
newCategory.auditRefs = category.auditRefs.filter(audit => audit.group !== 'metrics');
|
|
76
|
+
|
|
77
|
+
const categoryDOM = renderer.render(newCategory, sampleResults.categoryGroups);
|
|
78
|
+
const sections = categoryDOM.querySelectorAll('.lh-category > .lh-audit-group');
|
|
79
|
+
const metricSection = categoryDOM.querySelector('.lh-audit-group--metrics');
|
|
80
|
+
assert.ok(!metricSection);
|
|
81
|
+
assert.equal(sections.length, 4);
|
|
82
|
+
});
|
|
83
|
+
|
|
72
84
|
it('renders the metrics variance disclaimer as markdown', () => {
|
|
73
85
|
const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
|
|
74
86
|
const disclaimerEl =
|
|
@@ -399,17 +399,77 @@ describe('util helpers', () => {
|
|
|
399
399
|
describe('#calculateCategoryFraction', () => {
|
|
400
400
|
it('returns passed audits and total audits', () => {
|
|
401
401
|
const category = {
|
|
402
|
+
id: 'performance',
|
|
402
403
|
auditRefs: [
|
|
403
|
-
{weight: 3, result: {score: 1, scoreDisplayMode: 'binary'}},
|
|
404
|
-
{weight: 2, result: {score: 1, scoreDisplayMode: 'binary'}},
|
|
405
|
-
{weight: 0, result: {score: 1, scoreDisplayMode: 'binary'}},
|
|
406
|
-
{weight: 1, result: {score: 0, scoreDisplayMode: 'binary'}},
|
|
404
|
+
{weight: 3, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
|
|
405
|
+
{weight: 2, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
|
|
406
|
+
{weight: 0, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
|
|
407
|
+
{weight: 1, result: {score: 0, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
|
|
407
408
|
],
|
|
408
409
|
};
|
|
409
|
-
const
|
|
410
|
-
expect(
|
|
411
|
-
|
|
412
|
-
|
|
410
|
+
const fraction = Util.calculateCategoryFraction(category);
|
|
411
|
+
expect(fraction).toEqual({
|
|
412
|
+
numPassableAudits: 4,
|
|
413
|
+
numPassed: 3,
|
|
414
|
+
numInformative: 0,
|
|
415
|
+
totalWeight: 6,
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('ignores manual audits, N/A audits, and performance audits with no group', () => {
|
|
420
|
+
const category = {
|
|
421
|
+
id: 'performance',
|
|
422
|
+
auditRefs: [
|
|
423
|
+
{weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
|
|
424
|
+
{weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}},
|
|
425
|
+
{weight: 1, result: {score: 0, scoreDisplayMode: 'manual'}, group: 'diagnostics'},
|
|
426
|
+
{weight: 1, result: {score: 0, scoreDisplayMode: 'notApplicable'}, group: 'diagnostics'},
|
|
427
|
+
],
|
|
428
|
+
};
|
|
429
|
+
const fraction = Util.calculateCategoryFraction(category);
|
|
430
|
+
expect(fraction).toEqual({
|
|
431
|
+
numPassableAudits: 1,
|
|
432
|
+
numPassed: 1,
|
|
433
|
+
numInformative: 0,
|
|
434
|
+
totalWeight: 1,
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('does not ignore audits with no group in non-performance category', () => {
|
|
439
|
+
const category = {
|
|
440
|
+
id: 'seo',
|
|
441
|
+
auditRefs: [
|
|
442
|
+
{weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
|
|
443
|
+
{weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}},
|
|
444
|
+
{weight: 1, result: {score: 0, scoreDisplayMode: 'manual'}, group: 'diagnostics'},
|
|
445
|
+
],
|
|
446
|
+
};
|
|
447
|
+
const fraction = Util.calculateCategoryFraction(category);
|
|
448
|
+
expect(fraction).toEqual({
|
|
449
|
+
numPassableAudits: 2,
|
|
450
|
+
numPassed: 2,
|
|
451
|
+
numInformative: 0,
|
|
452
|
+
totalWeight: 2,
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('tracks informative audits separately', () => {
|
|
457
|
+
const category = {
|
|
458
|
+
id: 'performance',
|
|
459
|
+
auditRefs: [
|
|
460
|
+
{weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
|
|
461
|
+
{weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
|
|
462
|
+
{weight: 0, result: {score: 1, scoreDisplayMode: 'informative'}, group: 'diagnostics'},
|
|
463
|
+
{weight: 1, result: {score: 0, scoreDisplayMode: 'informative'}, group: 'diagnostics'},
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
const fraction = Util.calculateCategoryFraction(category);
|
|
467
|
+
expect(fraction).toEqual({
|
|
468
|
+
numPassableAudits: 2,
|
|
469
|
+
numPassed: 2,
|
|
470
|
+
numInformative: 2,
|
|
471
|
+
totalWeight: 2,
|
|
472
|
+
});
|
|
413
473
|
});
|
|
414
474
|
});
|
|
415
475
|
});
|