2018-11-07 04:05:38 +03:00
|
|
|
/**
|
|
|
|
* @license
|
2020-03-18 19:21:54 +03:00
|
|
|
* Copyright 2018 The Lighthouse Authors. All Rights Reserved.
|
2018-11-07 04:05:38 +03:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
2021-07-16 01:02:57 +03:00
|
|
|
import {Util} from './util.js';
|
|
|
|
import {CategoryRenderer} from './category-renderer.js';
|
2018-11-07 04:05:38 +03:00
|
|
|
|
2021-07-16 01:02:57 +03:00
|
|
|
export class PwaCategoryRenderer extends CategoryRenderer {
|
2018-11-07 04:05:38 +03:00
|
|
|
/**
|
|
|
|
* @param {LH.ReportResult.Category} category
|
|
|
|
* @param {Object<string, LH.Result.ReportGroup>} [groupDefinitions]
|
|
|
|
* @return {Element}
|
|
|
|
*/
|
|
|
|
render(category, groupDefinitions = {}) {
|
|
|
|
const categoryElem = this.dom.createElement('div', 'lh-category');
|
|
|
|
this.createPermalinkSpan(categoryElem, category.id);
|
2018-12-10 22:52:46 +03:00
|
|
|
categoryElem.appendChild(this.renderCategoryHeader(category, groupDefinitions));
|
2018-11-07 04:05:38 +03:00
|
|
|
|
|
|
|
const auditRefs = category.auditRefs;
|
|
|
|
|
2019-01-05 04:16:38 +03:00
|
|
|
// Regular audits aren't split up into pass/fail/notApplicable clumps, they're
|
2019-03-11 22:24:15 +03:00
|
|
|
// all put in a top-level clump that isn't expandable/collapsible.
|
2018-11-07 04:05:38 +03:00
|
|
|
const regularAuditRefs = auditRefs.filter(ref => ref.result.scoreDisplayMode !== 'manual');
|
2018-11-08 03:47:13 +03:00
|
|
|
const auditsElem = this._renderAudits(regularAuditRefs, groupDefinitions);
|
2018-11-07 04:05:38 +03:00
|
|
|
categoryElem.appendChild(auditsElem);
|
|
|
|
|
|
|
|
// Manual audits are still in a manual clump.
|
|
|
|
const manualAuditRefs = auditRefs.filter(ref => ref.result.scoreDisplayMode === 'manual');
|
|
|
|
const manualElem = this.renderClump('manual',
|
2019-01-11 20:40:14 +03:00
|
|
|
{auditRefs: manualAuditRefs, description: category.manualDescription});
|
2018-11-07 04:05:38 +03:00
|
|
|
categoryElem.appendChild(manualElem);
|
|
|
|
|
|
|
|
return categoryElem;
|
|
|
|
}
|
2018-11-08 03:47:13 +03:00
|
|
|
|
2018-11-17 04:08:37 +03:00
|
|
|
/**
|
|
|
|
* @param {LH.ReportResult.Category} category
|
2018-12-10 22:52:46 +03:00
|
|
|
* @param {Record<string, LH.Result.ReportGroup>} groupDefinitions
|
2018-11-17 04:08:37 +03:00
|
|
|
* @return {DocumentFragment}
|
|
|
|
*/
|
2018-12-10 22:52:46 +03:00
|
|
|
renderScoreGauge(category, groupDefinitions) {
|
2018-11-17 04:08:37 +03:00
|
|
|
// Defer to parent-gauge style if category error.
|
|
|
|
if (category.score === null) {
|
2018-12-10 22:52:46 +03:00
|
|
|
return super.renderScoreGauge(category, groupDefinitions);
|
2018-11-17 04:08:37 +03:00
|
|
|
}
|
|
|
|
|
2021-08-10 00:59:24 +03:00
|
|
|
const tmpl = this.dom.createComponent('gaugePwa');
|
2021-01-22 22:42:27 +03:00
|
|
|
const wrapper = this.dom.find('a.lh-gauge--pwa__wrapper', tmpl);
|
2021-07-21 19:33:14 +03:00
|
|
|
this.dom.safelySetHref(wrapper, `#${category.id}`);
|
2018-11-17 04:08:37 +03:00
|
|
|
|
2019-06-12 01:21:31 +03:00
|
|
|
// Correct IDs in case multiple instances end up in the page.
|
|
|
|
const svgRoot = tmpl.querySelector('svg');
|
|
|
|
if (!svgRoot) throw new Error('no SVG element found in PWA score gauge template');
|
|
|
|
PwaCategoryRenderer._makeSvgReferencesUnique(svgRoot);
|
|
|
|
|
2018-11-17 04:08:37 +03:00
|
|
|
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;
|
2018-12-10 22:52:46 +03:00
|
|
|
wrapper.title = this._getGaugeTooltip(category.auditRefs, groupDefinitions);
|
2018-11-17 04:08:37 +03:00
|
|
|
return tmpl;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the group IDs found in auditRefs.
|
|
|
|
* @param {Array<LH.ReportResult.AuditRef>} auditRefs
|
2020-03-13 06:04:28 +03:00
|
|
|
* @return {!Set<string>}
|
2018-11-17 04:08:37 +03:00
|
|
|
*/
|
|
|
|
_getGroupIds(auditRefs) {
|
|
|
|
const groupIds = auditRefs.map(ref => ref.group).filter(/** @return {g is string} */ g => !!g);
|
|
|
|
return new Set(groupIds);
|
|
|
|
}
|
|
|
|
|
2018-11-08 03:47:13 +03:00
|
|
|
/**
|
|
|
|
* Returns the group IDs whose audits are all considered passing.
|
|
|
|
* @param {Array<LH.ReportResult.AuditRef>} auditRefs
|
|
|
|
* @return {Set<string>}
|
|
|
|
*/
|
|
|
|
_getPassingGroupIds(auditRefs) {
|
2018-11-17 04:08:37 +03:00
|
|
|
const uniqueGroupIds = this._getGroupIds(auditRefs);
|
2018-11-08 03:47:13 +03:00
|
|
|
|
|
|
|
// Remove any that have a failing audit.
|
|
|
|
for (const auditRef of auditRefs) {
|
|
|
|
if (!Util.showAsPassed(auditRef.result) && auditRef.group) {
|
|
|
|
uniqueGroupIds.delete(auditRef.group);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return uniqueGroupIds;
|
|
|
|
}
|
|
|
|
|
2018-12-10 22:52:46 +03:00
|
|
|
/**
|
|
|
|
* Returns a tooltip string summarizing group pass rates.
|
|
|
|
* @param {Array<LH.ReportResult.AuditRef>} auditRefs
|
|
|
|
* @param {Record<string, LH.Result.ReportGroup>} groupDefinitions
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
_getGaugeTooltip(auditRefs, groupDefinitions) {
|
|
|
|
const groupIds = this._getGroupIds(auditRefs);
|
|
|
|
|
|
|
|
const tips = [];
|
|
|
|
for (const groupId of groupIds) {
|
|
|
|
const groupAuditRefs = auditRefs.filter(ref => ref.group === groupId);
|
|
|
|
const auditCount = groupAuditRefs.length;
|
|
|
|
const passedCount = groupAuditRefs.filter(ref => Util.showAsPassed(ref.result)).length;
|
|
|
|
|
|
|
|
const title = groupDefinitions[groupId].title;
|
|
|
|
tips.push(`${title}: ${passedCount}/${auditCount}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return tips.join(', ');
|
|
|
|
}
|
|
|
|
|
2018-11-08 03:47:13 +03:00
|
|
|
/**
|
|
|
|
* Render non-manual audits in groups, giving a badge to any group that has
|
|
|
|
* all passing audits.
|
|
|
|
* @param {Array<LH.ReportResult.AuditRef>} auditRefs
|
|
|
|
* @param {Object<string, LH.Result.ReportGroup>} groupDefinitions
|
|
|
|
* @return {Element}
|
|
|
|
*/
|
|
|
|
_renderAudits(auditRefs, groupDefinitions) {
|
|
|
|
const auditsElem = this.renderUnexpandableClump(auditRefs, groupDefinitions);
|
|
|
|
|
|
|
|
// Add a 'badged' class to group if all audits in that group pass.
|
|
|
|
const passsingGroupIds = this._getPassingGroupIds(auditRefs);
|
|
|
|
for (const groupId of passsingGroupIds) {
|
|
|
|
const groupElem = this.dom.find(`.lh-audit-group--${groupId}`, auditsElem);
|
|
|
|
groupElem.classList.add('lh-badged');
|
|
|
|
}
|
|
|
|
|
|
|
|
return auditsElem;
|
|
|
|
}
|
2019-06-12 01:21:31 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Alters SVG id references so multiple instances of an SVG element can coexist
|
|
|
|
* in a single page. If `svgRoot` has a `<defs>` block, gives all elements defined
|
|
|
|
* in it unique ids, then updates id references (`<use xlink:href="...">`,
|
|
|
|
* `fill="url(#...)"`) to the altered ids in all descendents of `svgRoot`.
|
|
|
|
* @param {SVGElement} svgRoot
|
|
|
|
*/
|
|
|
|
static _makeSvgReferencesUnique(svgRoot) {
|
|
|
|
const defsEl = svgRoot.querySelector('defs');
|
|
|
|
if (!defsEl) return;
|
|
|
|
|
2020-07-20 21:06:55 +03:00
|
|
|
const idSuffix = Util.getUniqueSuffix();
|
2019-06-12 01:21:31 +03:00
|
|
|
const elementsToUpdate = defsEl.querySelectorAll('[id]');
|
|
|
|
for (const el of elementsToUpdate) {
|
|
|
|
const oldId = el.id;
|
|
|
|
const newId = `${oldId}-${idSuffix}`;
|
|
|
|
el.id = newId;
|
|
|
|
|
|
|
|
// Update all <use>s.
|
|
|
|
const useEls = svgRoot.querySelectorAll(`use[href="#${oldId}"]`);
|
|
|
|
for (const useEl of useEls) {
|
|
|
|
useEl.setAttribute('href', `#${newId}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update all fill="url(#...)"s.
|
|
|
|
const fillEls = svgRoot.querySelectorAll(`[fill="url(#${oldId})"]`);
|
|
|
|
for (const fillEl of fillEls) {
|
|
|
|
fillEl.setAttribute('fill', `url(#${newId})`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-11-07 04:05:38 +03:00
|
|
|
}
|