diff --git a/lighthouse-core/report/html/renderer/category-renderer.js b/lighthouse-core/report/html/renderer/category-renderer.js
index 5b4b7b0c21a2..f455b9b2de46 100644
--- a/lighthouse-core/report/html/renderer/category-renderer.js
+++ b/lighthouse-core/report/html/renderer/category-renderer.js
@@ -159,13 +159,14 @@ class CategoryRenderer {
/**
* @param {LH.ReportResult.Category} category
+ * @param {Record} groupDefinitions
* @return {Element}
*/
- renderCategoryHeader(category) {
+ renderCategoryHeader(category, groupDefinitions) {
const tmpl = this.dom.cloneTemplate('#tmpl-lh-category-header', this.templateContext);
const gaugeContainerEl = this.dom.find('.lh-score__gauge', tmpl);
- const gaugeEl = this.renderScoreGauge(category);
+ const gaugeEl = this.renderScoreGauge(category, groupDefinitions);
gaugeContainerEl.appendChild(gaugeEl);
this.dom.find('.lh-category-header__title', tmpl).appendChild(
@@ -329,9 +330,10 @@ class CategoryRenderer {
/**
* @param {LH.ReportResult.Category} category
+ * @param {Record} groupDefinitions
* @return {DocumentFragment}
*/
- renderScoreGauge(category) {
+ renderScoreGauge(category, groupDefinitions) { // eslint-disable-line no-unused-vars
const tmpl = this.dom.cloneTemplate('#tmpl-lh-gauge', this.templateContext);
const wrapper = /** @type {HTMLAnchorElement} */ (this.dom.find('.lh-gauge__wrapper', tmpl));
wrapper.href = `#${category.id}`;
@@ -387,7 +389,7 @@ class CategoryRenderer {
render(category, groupDefinitions = {}) {
const element = this.dom.createElement('div', 'lh-category');
this.createPermalinkSpan(element, category.id);
- element.appendChild(this.renderCategoryHeader(category));
+ element.appendChild(this.renderCategoryHeader(category, groupDefinitions));
// Top level clumps for audits, in order they will appear in the report.
/** @type {Map>} */
diff --git a/lighthouse-core/report/html/renderer/performance-category-renderer.js b/lighthouse-core/report/html/renderer/performance-category-renderer.js
index 327fa164be60..252b667016bb 100644
--- a/lighthouse-core/report/html/renderer/performance-category-renderer.js
+++ b/lighthouse-core/report/html/renderer/performance-category-renderer.js
@@ -120,11 +120,11 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
const element = this.dom.createElement('div', 'lh-category');
if (environment === 'PSI') {
const gaugeEl = this.dom.createElement('div', 'lh-score__gauge');
- gaugeEl.appendChild(this.renderScoreGauge(category));
+ gaugeEl.appendChild(this.renderScoreGauge(category, groups));
element.appendChild(gaugeEl);
} else {
this.createPermalinkSpan(element, category.id);
- element.appendChild(this.renderCategoryHeader(category));
+ element.appendChild(this.renderCategoryHeader(category, groups));
}
// Metrics
diff --git a/lighthouse-core/report/html/renderer/pwa-category-renderer.js b/lighthouse-core/report/html/renderer/pwa-category-renderer.js
index 7c9be675aaba..2b547b738c9c 100644
--- a/lighthouse-core/report/html/renderer/pwa-category-renderer.js
+++ b/lighthouse-core/report/html/renderer/pwa-category-renderer.js
@@ -27,7 +27,7 @@ class PwaCategoryRenderer extends CategoryRenderer {
render(category, groupDefinitions = {}) {
const categoryElem = this.dom.createElement('div', 'lh-category');
this.createPermalinkSpan(categoryElem, category.id);
- categoryElem.appendChild(this.renderCategoryHeader(category));
+ categoryElem.appendChild(this.renderCategoryHeader(category, groupDefinitions));
const auditRefs = category.auditRefs;
@@ -48,12 +48,13 @@ class PwaCategoryRenderer extends CategoryRenderer {
/**
* @param {LH.ReportResult.Category} category
+ * @param {Record} groupDefinitions
* @return {DocumentFragment}
*/
- renderScoreGauge(category) {
+ renderScoreGauge(category, groupDefinitions) {
// Defer to parent-gauge style if category error.
if (category.score === null) {
- return super.renderScoreGauge(category);
+ return super.renderScoreGauge(category, groupDefinitions);
}
const tmpl = this.dom.cloneTemplate('#tmpl-lh-gauge--pwa', this.templateContext);
@@ -73,6 +74,7 @@ class PwaCategoryRenderer extends CategoryRenderer {
}
this.dom.find('.lh-gauge__label', tmpl).textContent = category.title;
+ wrapper.title = this._getGaugeTooltip(category.auditRefs, groupDefinitions);
return tmpl;
}
@@ -104,6 +106,28 @@ class PwaCategoryRenderer extends CategoryRenderer {
return uniqueGroupIds;
}
+ /**
+ * Returns a tooltip string summarizing group pass rates.
+ * @param {Array} auditRefs
+ * @param {Record} 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.
diff --git a/lighthouse-core/report/html/renderer/report-renderer.js b/lighthouse-core/report/html/renderer/report-renderer.js
index c95d7e2ba274..8b9559fa4814 100644
--- a/lighthouse-core/report/html/renderer/report-renderer.js
+++ b/lighthouse-core/report/html/renderer/report-renderer.js
@@ -211,7 +211,7 @@ class ReportRenderer {
const customGauges = [];
for (const category of report.reportCategories) {
const renderer = specificCategoryRenderers[category.id] || categoryRenderer;
- const categoryGauge = renderer.renderScoreGauge(category);
+ const categoryGauge = renderer.renderScoreGauge(category, report.categoryGroups || {});
// Group gauges that aren't default at the end of the header
if (renderer.renderScoreGauge === categoryRenderer.renderScoreGauge) {
diff --git a/lighthouse-core/test/report/html/renderer/pwa-category-renderer-test.js b/lighthouse-core/test/report/html/renderer/pwa-category-renderer-test.js
index 03043112d248..5698032e9e12 100644
--- a/lighthouse-core/test/report/html/renderer/pwa-category-renderer-test.js
+++ b/lighthouse-core/test/report/html/renderer/pwa-category-renderer-test.js
@@ -119,6 +119,7 @@ describe('PwaCategoryRenderer', () => {
const targetGroupId = groupIds[2];
assert.ok(targetGroupId);
+ const targetGroupTitle = sampleResults.categoryGroups[targetGroupId].title;
const targetAuditRefs = auditRefs.filter(ref => ref.group === targetGroupId);
// Try every permutation of audit scoring.
@@ -134,13 +135,25 @@ describe('PwaCategoryRenderer', () => {
const badgedScoreGauge =
categoryElem.querySelector('.lh-gauge--pwa__wrapper[class*="lh-badged--"]');
+ const tooltip = categoryElem.querySelector('.lh-gauge--pwa__wrapper').title;
+ const targetGroupTip = tooltip.split(', ').find(tip => tip.startsWith(targetGroupTitle));
+ assert.ok(targetGroupTip);
+
// 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);
+
+ // Tooltip ends with passing/total.
+ const passingCount = categoryElem.querySelectorAll(
+ `.lh-audit-group--${targetGroupId} .lh-audit--pass`).length;
+ assert.ok(targetGroupTip.endsWith(`${passingCount}/${targetAuditRefs.length}`));
} else {
assert.strictEqual(badgedElems.length, 1);
assert.ok(badgedScoreGauge.classList.contains(`lh-badged--${targetGroupId}`));
+
+ // Tooltip ends with total/total.
+ assert.ok(targetGroupTip.endsWith(`${targetAuditRefs.length}/${targetAuditRefs.length}`));
}
}
});
@@ -152,7 +165,17 @@ 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'));
+
+ // Score gauge.
+ const gaugeElem = categoryElem.querySelector('.lh-gauge--pwa__wrapper');
+ assert.ok(gaugeElem.classList.contains('lh-badged--all'));
+
+ // All tooltips should have x/x audits passed.
+ const tips = gaugeElem.title.split(', ');
+ assert.strictEqual(tips.length, groupIds.length);
+ for (const tip of tips) {
+ assert.ok(/(\d+)\/\1$/.test(tip));
+ }
});
it('renders no badges when no audit groups are passing', () => {
@@ -162,8 +185,17 @@ 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);
+
+ // Score gauge.
+ const gaugeElem = categoryElem.querySelector('.lh-gauge--pwa__wrapper');
+ assert.ok(!gaugeElem.matches('.lh-gauge--pwa__wrapper[class*="lh-badged-"]'));
+
+ // All tooltips should have 0/x audits passed.
+ const tips = gaugeElem.title.split(', ');
+ assert.strictEqual(tips.length, groupIds.length);
+ for (const tip of tips) {
+ assert.ok(/0\/\d+$/.test(tip));
+ }
});
it('renders all but one badge when all groups but one are passing', () => {
@@ -176,13 +208,33 @@ describe('PwaCategoryRenderer', () => {
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
const gaugeElem = categoryElem.querySelector('.lh-gauge--pwa__wrapper');
+ const tips = gaugeElem.title.split(', ');
+ assert.strictEqual(tips.length, groupIds.length);
+
for (const groupId of groupIds) {
const expectedCount = groupId === failingGroupId ? 0 : 1;
+ // Individual group badges.
const groupElems = categoryElem.querySelectorAll(`.lh-audit-group--${groupId}.lh-badged`);
assert.strictEqual(groupElems.length, expectedCount);
- gaugeElem.classList.contains(`lh-badged--${groupId}`);
+ // Score gauge.
+ if (groupId !== failingGroupId) {
+ assert.ok(gaugeElem.classList.contains(`lh-badged--${groupId}`));
+ }
+
+ // Map back from groupId to groupTitle (used in tooltip).
+ const groupTitle = sampleResults.categoryGroups[groupId].title;
+ const groupTip = tips.find(tip => tip.startsWith(groupTitle));
+ assert.ok(groupTip);
+
+ // All tooltips should be x/x except for failingGroup, which should be (x-1)/x.
+ if (groupId !== failingGroupId) {
+ assert.ok(/(\d+)\/\1$/.test(groupTip));
+ } else {
+ const [, passingCount, totalCount] = /(\d+)\/(\d+)$/.exec(groupTip);
+ assert.strictEqual(Number(passingCount) + 1, Number(totalCount));
+ }
}
});
});