report: add PWA category badge gauge (#6526)
This commit is contained in:
Родитель
130c5e0126
Коммит
7f55977b2a
|
@ -46,14 +46,53 @@ class PwaCategoryRenderer extends CategoryRenderer {
|
|||
return categoryElem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LH.ReportResult.Category} category
|
||||
* @return {DocumentFragment}
|
||||
*/
|
||||
renderScoreGauge(category) {
|
||||
// Defer to parent-gauge style if category error.
|
||||
if (category.score === null) {
|
||||
return super.renderScoreGauge(category);
|
||||
}
|
||||
|
||||
const tmpl = this.dom.cloneTemplate('#tmpl-lh-gauge--pwa', this.templateContext);
|
||||
const wrapper = /** @type {HTMLAnchorElement} */ (this.dom.find('.lh-gauge--pwa__wrapper',
|
||||
tmpl));
|
||||
wrapper.href = `#${category.id}`;
|
||||
|
||||
const allGroups = this._getGroupIds(category.auditRefs);
|
||||
const passingGroupIds = this._getPassingGroupIds(category.auditRefs);
|
||||
|
||||
if (passingGroupIds.size === allGroups.size) {
|
||||
wrapper.classList.add('lh-badged--all');
|
||||
} else {
|
||||
for (const passingGroupId of passingGroupIds) {
|
||||
wrapper.classList.add(`lh-badged--${passingGroupId}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.dom.find('.lh-gauge__label', tmpl).textContent = category.title;
|
||||
return tmpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the group IDs found in auditRefs.
|
||||
* @param {Array<LH.ReportResult.AuditRef>} auditRefs
|
||||
* @return {Set<string>}
|
||||
*/
|
||||
_getGroupIds(auditRefs) {
|
||||
const groupIds = auditRefs.map(ref => ref.group).filter(/** @return {g is string} */ g => !!g);
|
||||
return new Set(groupIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the group IDs whose audits are all considered passing.
|
||||
* @param {Array<LH.ReportResult.AuditRef>} auditRefs
|
||||
* @return {Set<string>}
|
||||
*/
|
||||
_getPassingGroupIds(auditRefs) {
|
||||
const groupIds = auditRefs.map(ref => ref.group).filter(/** @return {g is string} */ g => !!g);
|
||||
const uniqueGroupIds = new Set(groupIds);
|
||||
const uniqueGroupIds = this._getGroupIds(auditRefs);
|
||||
|
||||
// Remove any that have a failing audit.
|
||||
for (const auditRef of auditRefs) {
|
||||
|
|
|
@ -201,15 +201,26 @@ class ReportRenderer {
|
|||
const categories = reportSection.appendChild(this._dom.createElement('div', 'lh-categories'));
|
||||
|
||||
for (const category of report.reportCategories) {
|
||||
if (scoreHeader) {
|
||||
scoreHeader.appendChild(categoryRenderer.renderScoreGauge(category));
|
||||
}
|
||||
|
||||
const renderer = specificCategoryRenderers[category.id] || categoryRenderer;
|
||||
categories.appendChild(renderer.render(category, report.categoryGroups));
|
||||
}
|
||||
|
||||
if (scoreHeader) {
|
||||
const defaultGauges = [];
|
||||
const customGauges = [];
|
||||
for (const category of report.reportCategories) {
|
||||
const renderer = specificCategoryRenderers[category.id] || categoryRenderer;
|
||||
const categoryGauge = renderer.renderScoreGauge(category);
|
||||
|
||||
// Group gauges that aren't default at the end of the header
|
||||
if (renderer.renderScoreGauge === categoryRenderer.renderScoreGauge) {
|
||||
defaultGauges.push(categoryGauge);
|
||||
} else {
|
||||
customGauges.push(categoryGauge);
|
||||
}
|
||||
}
|
||||
scoreHeader.append(...defaultGauges, ...customGauges);
|
||||
|
||||
const scoreScale = this._dom.cloneTemplate('#tmpl-lh-scorescale', this._templateContext);
|
||||
this._dom.find('.lh-scorescale-label', scoreScale).textContent =
|
||||
Util.UIStrings.scorescaleLabel;
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
--lh-audit-group-vpadding: 8px;
|
||||
--lh-section-vpadding: 12px;
|
||||
--chevron-size: 12px;
|
||||
--circle-size: calc(3 * var(--header-font-size));
|
||||
|
||||
--pass-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>check</title><path fill="%23178239" d="M24 4C12.95 4 4 12.95 4 24c0 11.04 8.95 20 20 20 11.04 0 20-8.96 20-20 0-11.05-8.96-20-20-20zm-4 30L10 24l2.83-2.83L20 28.34l15.17-15.17L38 16 20 34z"/></svg>');
|
||||
--average-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>info</title><path fill="%23E67700" d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm2 30h-4V22h4v12zm0-16h-4v-4h4v4z"/></svg>');
|
||||
|
@ -785,10 +786,15 @@ details, summary {
|
|||
border: 0;
|
||||
}
|
||||
|
||||
.lh-scores-header .lh-gauge__wrapper {
|
||||
.lh-scores-header .lh-gauge__wrapper,
|
||||
.lh-scores-header .lh-gauge--pwa__wrapper, {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.lh-scores-header .lh-gauge--pwa__wrapper {
|
||||
border-left: 1px solid var(--report-secondary-border-color)
|
||||
}
|
||||
|
||||
.lh-scorescale {
|
||||
color: var(--medium-75-gray);
|
||||
padding: 0 calc(var(--section-indent) * 1.5) 0;
|
||||
|
@ -835,7 +841,6 @@ details, summary {
|
|||
}
|
||||
|
||||
.lh-category {
|
||||
--circle-size: calc(2.5 * var(--header-font-size));
|
||||
padding: var(--section-padding);
|
||||
}
|
||||
|
||||
|
|
|
@ -477,7 +477,6 @@ limitations under the License.
|
|||
<template id="tmpl-lh-gauge">
|
||||
<style>
|
||||
.lh-gauge__wrapper {
|
||||
--circle-size: calc(3 * var(--header-font-size));
|
||||
--circle-size-half: calc(var(--circle-size) / 2);
|
||||
--circle-background: hsl(216, 12%, 92%);
|
||||
--circle-border-width: 9;
|
||||
|
@ -573,6 +572,143 @@ limitations under the License.
|
|||
</a>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Lighthouse PWA badge gauge -->
|
||||
<template id="tmpl-lh-gauge--pwa">
|
||||
<style>
|
||||
.lh-gauge--pwa__wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
text-decoration: none;
|
||||
flex: 1;
|
||||
min-width: auto;
|
||||
position: relative;
|
||||
}
|
||||
.lh-gauge--pwa {
|
||||
width: var(--circle-size);
|
||||
height: var(--circle-size);
|
||||
}
|
||||
.lh-gauge--pwa .lh-gauge--pwa__component {
|
||||
display: none;
|
||||
}
|
||||
.lh-gauge--pwa__wrapper:not(.lh-badged--all) .lh-gauge--pwa__logo > path {
|
||||
/* Gray logo unless everything is passing. */
|
||||
fill: #B0B0B0;
|
||||
}
|
||||
|
||||
/* No passing groups. */
|
||||
.lh-gauge--pwa__wrapper:not([class*='lh-badged--']) .lh-gauge--pwa__na-line {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Just fast and reliable. */
|
||||
.lh-gauge--pwa__wrapper.lh-badged--pwa-fast-reliable:not(.lh-badged--pwa-installable) .lh-gauge--pwa__fast-reliable-badge {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Just installable. */
|
||||
.lh-gauge--pwa__wrapper.lh-badged--pwa-installable:not(.lh-badged--pwa-fast-reliable) .lh-gauge--pwa__installable-badge {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Fast and reliable and installable. */
|
||||
.lh-gauge--pwa__wrapper.lh-badged--pwa-fast-reliable.lh-badged--pwa-installable .lh-gauge--pwa__fast-reliable-installable-badges {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* All passing groups. */
|
||||
.lh-gauge--pwa__wrapper.lh-badged--all .lh-gauge--pwa__check-circle {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.lh-gauge__label {
|
||||
font-size: var(--body-font-size);
|
||||
line-height: var(--body-line-height);
|
||||
margin-top: calc(0.5 * var(--body-line-height));
|
||||
text-align: center;
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<a href="#" class="lh-gauge--pwa__wrapper">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" class="lh-gauge--pwa">
|
||||
<defs>
|
||||
<linearGradient id="lh-gauge--pwa__bg-disk__gradient" x1="50%" y1="2.15%" x2="50%" y2="97.645%">
|
||||
<stop stop-color="#F1F3F4" offset="0%"></stop>
|
||||
<stop stop-color="#DEE6EA" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="lh-gauge--pwa__check-circle__gradient" x1="50%" y1="0%" x2="50%" y2="100%">
|
||||
<stop stop-color="#00C852" offset="0%"></stop>
|
||||
<stop stop-color="#009688" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="lh-gauge--pwa__installable__shadow-gradient" x1="76.056%" x2="24.111%" y1="82.995%" y2="24.735%">
|
||||
<stop stop-color="#A5D6A7" offset="0%"></stop>
|
||||
<stop stop-color="#80CBC4" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="lh-gauge--pwa__fast-reliable__shadow-gradient" x1="76.056%" y1="82.995%" x2="25.678%" y2="26.493%">
|
||||
<stop stop-color="#64B5F6" offset="0%"></stop>
|
||||
<stop stop-color="#2979FF" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
|
||||
<g id="lh-gauge--pwa__fast-reliable-badge">
|
||||
<circle fill="#FFFFFF" cx="10" cy="10" r="10"></circle>
|
||||
<path fill="#304FFE" d="M10 3.58l5.25 2.34v3.5c0 3.23-2.24 6.26-5.25 7-3.01-.74-5.25-3.77-5.25-7v-3.5L10 3.58zm-.47 10.74l2.76-4.83.03-.07c.04-.08 0-.24-.22-.24h-1.64l.47-3.26h-.47l-2.7 4.77c-.02.01.05-.1-.04.05-.09.16-.1.31.18.31h1.63l-.47 3.27h.47z"/>
|
||||
</g>
|
||||
<g id="lh-gauge--pwa__installable-badge">
|
||||
<circle fill="#FFFFFF" cx="10" cy="10" r="10"></circle>
|
||||
<path fill="#009688" d="M10 4.167A5.835 5.835 0 0 0 4.167 10 5.835 5.835 0 0 0 10 15.833 5.835 5.835 0 0 0 15.833 10 5.835 5.835 0 0 0 10 4.167zm2.917 6.416h-2.334v2.334H9.417v-2.334H7.083V9.417h2.334V7.083h1.166v2.334h2.334v1.166z"/>
|
||||
</g>
|
||||
</defs>
|
||||
|
||||
<g stroke="none" fill-rule="nonzero">
|
||||
<!-- Background and PWA logo (color by default) -->
|
||||
<circle fill="url(#lh-gauge--pwa__bg-disk__gradient)" cx="30" cy="30" r="30"></circle>
|
||||
<g class="lh-gauge--pwa__logo">
|
||||
<path fill="#3D3D3D" d="M35.66 19.39l.7-1.75h2L37.4 15 38.6 12l3.4 9h-2.51l-.58-1.61z"/>
|
||||
<path fill="#304FFE" d="M33.52 21l3.65-9h-2.42l-2.5 5.82L30.5 12h-1.86l-1.9 5.82-1.35-2.65-1.21 3.72L25.4 21h2.38l1.72-5.2 1.64 5.2z"/>
|
||||
<path fill="#3D3D3D" fill-rule="nonzero" d="M20.3 17.91h1.48c.45 0 .85-.05 1.2-.15l.39-1.18 1.07-3.3a2.64 2.64 0 0 0-.28-.37c-.55-.6-1.36-.91-2.42-.91H18v9h2.3V17.9zm1.96-3.84c.22.22.33.5.33.87 0 .36-.1.65-.29.87-.2.23-.59.35-1.15.35h-.86v-2.41h.87c.52 0 .89.1 1.1.32z"/>
|
||||
</g>
|
||||
|
||||
<!-- No badges. -->
|
||||
<rect class="lh-gauge--pwa__component lh-gauge--pwa__na-line" fill="#FFFFFF" x="20" y="32" width="20" height="4" rx="2"></rect>
|
||||
|
||||
<!-- Just fast and reliable. -->
|
||||
<g class="lh-gauge--pwa__component lh-gauge--pwa__fast-reliable-badge" transform="translate(20, 29)">
|
||||
<path fill="url(#lh-gauge--pwa__fast-reliable__shadow-gradient)" d="M33.63 19.49A30 30 0 0 1 16.2 30.36L3 17.14 17.14 3l16.49 16.49z"/>
|
||||
<use xlink:href="#lh-gauge--pwa__fast-reliable-badge" />
|
||||
</g>
|
||||
|
||||
<!-- Just installable. -->
|
||||
<g class="lh-gauge--pwa__component lh-gauge--pwa__installable-badge" transform="translate(20, 29)">
|
||||
<path fill="url(#lh-gauge--pwa__installable__shadow-gradient)" d="M33.629 19.487c-4.272 5.453-10.391 9.39-17.415 10.869L3 17.142 17.142 3 33.63 19.487z"/>
|
||||
<use xlink:href="#lh-gauge--pwa__installable-badge" />
|
||||
</g>
|
||||
|
||||
<!-- Fast and reliable and installable. -->
|
||||
<g class="lh-gauge--pwa__component lh-gauge--pwa__fast-reliable-installable-badges">
|
||||
<g transform="translate(8, 29)"> <!-- fast and reliable -->
|
||||
<path fill="url(#lh-gauge--pwa__fast-reliable__shadow-gradient)" d="M16.321 30.463L3 17.143 17.142 3l22.365 22.365A29.864 29.864 0 0 1 22 31c-1.942 0-3.84-.184-5.679-.537z"/>
|
||||
<use xlink:href="#lh-gauge--pwa__fast-reliable-badge" />
|
||||
</g>
|
||||
<g transform="translate(32, 29)"> <!-- installable -->
|
||||
<path fill="url(#lh-gauge--pwa__installable__shadow-gradient)" d="M25.982 11.84a30.107 30.107 0 0 1-13.08 15.203L3 17.143 17.142 3l8.84 8.84z"/>
|
||||
<use xlink:href="#lh-gauge--pwa__installable-badge" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Full PWA. -->
|
||||
<g class="lh-gauge--pwa__component lh-gauge--pwa__check-circle" transform="translate(18, 28)">
|
||||
<circle fill="#FFFFFF" cx="12" cy="12" r="12"></circle>
|
||||
<path fill="url(#lh-gauge--pwa__check-circle__gradient)" d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<div class="lh-gauge__label"></div>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<!-- Lighthouse crtiical request chains component -->
|
||||
<template id="tmpl-lh-crc">
|
||||
<div class="lh-crc-container">
|
||||
|
|
|
@ -131,10 +131,17 @@ describe('PwaCategoryRenderer', () => {
|
|||
|
||||
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
||||
const badgedElems = categoryElem.querySelectorAll(`.lh-badged`);
|
||||
const badgedScoreGauge =
|
||||
categoryElem.querySelector('.lh-gauge--pwa__wrapper[class*="lh-badged--"]');
|
||||
|
||||
// Only expect a badge on last permutation (all bits are set).
|
||||
const expectedBadgeCount = i === totalPermutations - 1 ? 1 : 0;
|
||||
assert.strictEqual(badgedElems.length, expectedBadgeCount);
|
||||
// Only expect a badge (and badged gauge) on last permutation (all bits are set).
|
||||
if (i !== totalPermutations - 1) {
|
||||
assert.strictEqual(badgedElems.length, 0);
|
||||
assert.strictEqual(badgedScoreGauge, null);
|
||||
} else {
|
||||
assert.strictEqual(badgedElems.length, 1);
|
||||
assert.ok(badgedScoreGauge.classList.contains(`lh-badged--${targetGroupId}`));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -145,6 +152,7 @@ describe('PwaCategoryRenderer', () => {
|
|||
|
||||
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
||||
assert.strictEqual(categoryElem.querySelectorAll('.lh-badged').length, groupIds.length);
|
||||
assert.ok(categoryElem.querySelector('.lh-gauge--pwa__wrapper.lh-badged--all'));
|
||||
});
|
||||
|
||||
it('renders no badges when no audit groups are passing', () => {
|
||||
|
@ -154,6 +162,8 @@ describe('PwaCategoryRenderer', () => {
|
|||
|
||||
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
||||
assert.strictEqual(categoryElem.querySelectorAll('.lh-badged').length, 0);
|
||||
assert.strictEqual(categoryElem.querySelector('.lh-gauge--pwa__wrapper[class*="lh-badged-"]'),
|
||||
null);
|
||||
});
|
||||
|
||||
it('renders all but one badge when all groups but one are passing', () => {
|
||||
|
@ -164,13 +174,30 @@ describe('PwaCategoryRenderer', () => {
|
|||
const failingGroupId = auditRefs[0].group;
|
||||
|
||||
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
||||
const gaugeElem = categoryElem.querySelector('.lh-gauge--pwa__wrapper');
|
||||
|
||||
for (const groupId of groupIds) {
|
||||
const expectedCount = groupId === failingGroupId ? 0 : 1;
|
||||
|
||||
const groupElems = categoryElem.querySelectorAll(`.lh-audit-group--${groupId}.lh-badged`);
|
||||
assert.strictEqual(groupElems.length, expectedCount);
|
||||
|
||||
gaugeElem.classList.contains(`lh-badged--${groupId}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('#renderScoreGauge', () => {
|
||||
it('renders an error score gauge in case of category error', () => {
|
||||
category.score = null;
|
||||
const badgeGauge = pwaRenderer.renderScoreGauge(category);
|
||||
|
||||
// Not a PWA gauge.
|
||||
assert.strictEqual(badgeGauge.querySelector('.lh-gauge--pwa__wrapper'), null);
|
||||
|
||||
const percentageElem = badgeGauge.querySelector('.lh-gauge__percentage');
|
||||
assert.strictEqual(percentageElem.textContent, '?');
|
||||
assert.strictEqual(percentageElem.title, Util.UIStrings.errorLabel);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -79,7 +79,7 @@ describe('ReportRenderer', () => {
|
|||
const output = renderer.renderReport(sampleResults, container);
|
||||
assert.ok(output.querySelector('.lh-header-sticky'), 'has a header');
|
||||
assert.ok(output.querySelector('.lh-report'), 'has report body');
|
||||
assert.equal(output.querySelectorAll('.lh-gauge').length,
|
||||
assert.equal(output.querySelectorAll('.lh-gauge__wrapper, .lh-gauge--pwa__wrapper').length,
|
||||
sampleResults.reportCategories.length * 2, 'renders category gauges');
|
||||
});
|
||||
|
||||
|
@ -104,6 +104,31 @@ describe('ReportRenderer', () => {
|
|||
assert.equal(url.href, sampleResults.finalUrl);
|
||||
});
|
||||
|
||||
it('renders special score gauges after the mainstream ones', () => {
|
||||
const container = renderer._dom._document.body;
|
||||
const output = renderer.renderReport(sampleResults, container);
|
||||
|
||||
const allGaugeCount = output
|
||||
.querySelectorAll('.lh-scores-header > a[class*="lh-gauge"]').length;
|
||||
const regularGaugeCount = output
|
||||
.querySelectorAll('.lh-scores-header > .lh-gauge__wrapper').length;
|
||||
|
||||
// Not all gauges are regular.
|
||||
assert.ok(regularGaugeCount < allGaugeCount);
|
||||
|
||||
const scoresHeaderElem = output.querySelector('.lh-scores-header');
|
||||
for (let i = 0; i < scoresHeaderElem.children.length; i++) {
|
||||
const gauge = scoresHeaderElem.children[i];
|
||||
|
||||
if (i < regularGaugeCount) {
|
||||
assert.ok(gauge.classList.contains('lh-gauge__wrapper'));
|
||||
} else {
|
||||
assert.ok(!gauge.classList.contains('lh-gauge__wrapper'));
|
||||
assert.ok(gauge.classList.contains('lh-gauge--pwa__wrapper'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should not mutate a report object', () => {
|
||||
const container = renderer._dom._document.body;
|
||||
const originalResults = JSON.parse(JSON.stringify(sampleResults));
|
||||
|
|
Загрузка…
Ссылка в новой задаче