lighthouse/report/renderer/pwa-category-renderer.js

186 строки
6.5 KiB
JavaScript

/**
* @license
* Copyright 2018 The Lighthouse Authors. All Rights Reserved.
*
* 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.
*/
import {Util} from './util.js';
import {CategoryRenderer} from './category-renderer.js';
export class PwaCategoryRenderer extends CategoryRenderer {
/**
* @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');
categoryElem.id = category.id;
categoryElem.append(this.renderCategoryHeader(category, groupDefinitions));
const auditRefs = category.auditRefs;
// Regular audits aren't split up into pass/fail/notApplicable clumps, they're
// all put in a top-level clump that isn't expandable/collapsible.
const regularAuditRefs = auditRefs.filter(ref => ref.result.scoreDisplayMode !== 'manual');
const auditsElem = this._renderAudits(regularAuditRefs, groupDefinitions);
categoryElem.append(auditsElem);
// Manual audits are still in a manual clump.
const manualAuditRefs = auditRefs.filter(ref => ref.result.scoreDisplayMode === 'manual');
const manualElem = this.renderClump('manual',
{auditRefs: manualAuditRefs, description: category.manualDescription});
categoryElem.append(manualElem);
return categoryElem;
}
/**
* @param {LH.ReportResult.Category} category
* @param {Record<string, LH.Result.ReportGroup>} groupDefinitions
* @return {DocumentFragment}
*/
renderCategoryScore(category, groupDefinitions) {
// Defer to parent-gauge style if category error.
if (category.score === null) {
return super.renderScoreGauge(category, groupDefinitions);
}
const tmpl = this.dom.createComponent('gaugePwa');
const wrapper = this.dom.find('a.lh-gauge--pwa__wrapper', tmpl);
// 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);
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;
wrapper.title = this._getGaugeTooltip(category.auditRefs, groupDefinitions);
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 uniqueGroupIds = this._getGroupIds(auditRefs);
// 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;
}
/**
* 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(', ');
}
/**
* 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;
}
/**
* 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;
const idSuffix = Util.getUniqueSuffix();
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})`);
}
}
}
}