diff --git a/lighthouse-core/report/html/renderer/category-renderer.js b/lighthouse-core/report/html/renderer/category-renderer.js index 66427c07a813..b7d8dc39841f 100644 --- a/lighthouse-core/report/html/renderer/category-renderer.js +++ b/lighthouse-core/report/html/renderer/category-renderer.js @@ -26,25 +26,28 @@ class CategoryRenderer { /** * @param {!ReportRenderer.AuditJSON} audit * @param {number} index + * @param {DocumentFragment=} providedTmpl * @return {!Element} */ - renderAudit(audit, index) { - const tmpl = this.dom.cloneTemplate('#tmpl-lh-audit', this.templateContext); + renderAudit(audit, index, providedTmpl) { + const tmpl = providedTmpl || this.dom.cloneTemplate('#tmpl-lh-audit', this.templateContext); const auditEl = this.dom.find('.lh-audit', tmpl); auditEl.id = audit.result.name; + const displayTextEl = this.dom.find('.lh-audit__display-text', auditEl); const scoreDisplayMode = audit.result.scoreDisplayMode; if (audit.result.displayValue) { const displayValue = Util.formatDisplayValue(audit.result.displayValue); - this.dom.find('.lh-audit__display-text', auditEl).textContent = displayValue; + displayTextEl.textContent = displayValue; } const titleEl = this.dom.find('.lh-audit__title', auditEl); titleEl.appendChild(this.dom.convertMarkdownCodeSnippets(audit.result.description)); - this.dom.find('.lh-audit__description', auditEl) - .appendChild(this.dom.convertMarkdownLinkSnippets(audit.result.helpText)); + if (audit.result.helpText) { + this.dom.find('.lh-audit__description', auditEl) + .appendChild(this.dom.convertMarkdownLinkSnippets(audit.result.helpText)); + } - // Append audit details to header section so the entire audit is within a
. const header = /** @type {!HTMLDetailsElement} */ (this.dom.find('details', auditEl)); if (audit.result.details && audit.result.details.type) { const elem = this.detailsRenderer.render(audit.result.details); @@ -63,7 +66,7 @@ class CategoryRenderer { const textEl = this.dom.find('.lh-audit__display-text', auditEl); textEl.textContent = 'Error!'; textEl.classList.add('tooltip-boundary'); - const tooltip = this.dom.createChildOf(textEl, 'div', 'lh-error-tooltip-content tooltip'); + const tooltip = this.dom.createChildOf(textEl, 'div', 'tooltip lh-debug'); tooltip.textContent = audit.result.errorMessage || 'Report error: no audit information'; } else if (audit.result.explanation) { const explanationEl = this.dom.createChildOf(titleEl, 'div', 'lh-debug'); diff --git a/lighthouse-core/report/html/renderer/performance-category-renderer.js b/lighthouse-core/report/html/renderer/performance-category-renderer.js index 478a0128c107..b765037f35cb 100644 --- a/lighthouse-core/report/html/renderer/performance-category-renderer.js +++ b/lighthouse-core/report/html/renderer/performance-category-renderer.js @@ -31,7 +31,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer { if (audit.result.scoreDisplayMode === 'error') { descriptionEl.textContent = ''; valueEl.textContent = 'Error!'; - const tooltip = this.dom.createChildOf(descriptionEl, 'span', 'lh-error-tooltip-content'); + const tooltip = this.dom.createChildOf(descriptionEl, 'span'); tooltip.textContent = audit.result.errorMessage || 'Report error: no metric information'; } @@ -45,21 +45,11 @@ class PerformanceCategoryRenderer extends CategoryRenderer { * @return {!Element} */ _renderOpportunity(audit, index, scale) { - const tmpl = this.dom.cloneTemplate('#tmpl-lh-opportunity', this.templateContext); - const element = this.dom.find('.lh-load-opportunity', tmpl); - element.classList.add(`lh-load-opportunity--${Util.calculateRating(audit.result.score)}`); + const oppTmpl = this.dom.cloneTemplate('#tmpl-lh-opportunity', this.templateContext); + const element = this.renderAudit(audit, index, oppTmpl); + element.classList.add(`lh-audit--${Util.calculateRating(audit.result.score)}`); element.id = audit.result.name; - const titleEl = this.dom.find('.lh-load-opportunity__title', tmpl); - titleEl.textContent = audit.result.description; - this.dom.find('.lh-audit__index', element).textContent = `${index + 1}`; - - if (audit.result.errorMessage || audit.result.explanation) { - const debugStrEl = this.dom.createChildOf(titleEl, 'div', 'lh-debug'); - debugStrEl.textContent = audit.result.errorMessage || audit.result.explanation; - } - if (audit.result.scoreDisplayMode === 'error') return element; - const details = audit.result.details; const summaryInfo = /** @type {!DetailsRenderer.OpportunitySummary} */ (details && details.summary); @@ -67,21 +57,17 @@ class PerformanceCategoryRenderer extends CategoryRenderer { return element; } - const displayValue = Util.formatDisplayValue(audit.result.displayValue); - const sparklineWidthPct = `${summaryInfo.wastedMs / scale * 100}%`; - const wastedMs = Util.formatSeconds(summaryInfo.wastedMs, 0.01); - const auditDescription = this.dom.convertMarkdownLinkSnippets(audit.result.helpText); - this.dom.find('.lh-load-opportunity__sparkline', tmpl).title = displayValue; - this.dom.find('.lh-load-opportunity__wasted-stat', tmpl).title = displayValue; - this.dom.find('.lh-sparkline__bar', tmpl).style.width = sparklineWidthPct; - this.dom.find('.lh-load-opportunity__wasted-stat', tmpl).textContent = wastedMs; - this.dom.find('.lh-load-opportunity__description', tmpl).appendChild(auditDescription); - - // If there's no `type`, then we only used details for `summary` - if (details.type) { - const detailsElem = this.detailsRenderer.render(details); - detailsElem.classList.add('lh-details'); - element.appendChild(detailsElem); + const wastedEl = this.dom.find('.lh-audit__display-text', element); + if (audit.result.scoreDisplayMode !== 'error') { + const sparklineWidthPct = `${summaryInfo.wastedMs / scale * 100}%`; + this.dom.find('.lh-sparkline__bar', element).style.width = sparklineWidthPct; + wastedEl.textContent = Util.formatSeconds(summaryInfo.wastedMs, 0.01); + } + + if (audit.result.displayValue) { + const displayValue = Util.formatDisplayValue(audit.result.displayValue); + this.dom.find('.lh-load-opportunity__sparkline', element).title = displayValue; + wastedEl.title = displayValue; } return element; diff --git a/lighthouse-core/report/html/renderer/util.js b/lighthouse-core/report/html/renderer/util.js index bd165d23b356..d23d12ffd318 100644 --- a/lighthouse-core/report/html/renderer/util.js +++ b/lighthouse-core/report/html/renderer/util.js @@ -36,9 +36,11 @@ class Util { * @return {string} */ static formatDisplayValue(displayValue) { - if (typeof displayValue === 'undefined') return ''; if (typeof displayValue === 'string') return displayValue; + if (!displayValue) return ''; + // Don't mutate the value + displayValue = [...displayValue]; const replacementRegex = /%([0-9]*(\.[0-9]+)?d|s)/; const template = /** @type {string} */ (displayValue.shift()); if (typeof template !== 'string') { diff --git a/lighthouse-core/report/html/report-styles.css b/lighthouse-core/report/html/report-styles.css index e474659e0e12..7c4dfc576aa1 100644 --- a/lighthouse-core/report/html/report-styles.css +++ b/lighthouse-core/report/html/report-styles.css @@ -137,9 +137,6 @@ color: #0c50c7; } -.lh-root details summary { - cursor: pointer; -} .lh-audit__description, .lh-load-opportunity__description, @@ -255,14 +252,19 @@ margin: calc(var(--default-padding) / 2) 0 var(--default-padding); } -.lh-audit__header > div, -.lh-audit__header > span { + +.lh-audit__index, +.lh-audit__title, +.lh-audit__display-text, +.lh-audit__score-icon, +.lh-load-opportunity__sparkline, +.lh-toggle-arrow { margin: 0 var(--audit-item-gap); } -.lh-audit__header > div:first-child, .lh-audit__header > span:first-child { +.lh-audit__index { margin-left: 0; } -.lh-audit__header > div:last-child, .lh-audit__header > span:last-child { +.lh-toggle-arrow { margin-right: 0; } @@ -292,19 +294,16 @@ } /* Expandable Details (Audit Groups, Audits) */ - -.lh-expandable-details { - -} - -.lh-expandable-details__summary { - cursor: pointer; -} - .lh-audit__header { display: flex; padding: var(--lh-audit-vpadding) var(--text-indent); + cursor: pointer; +} + +.lh-audit--load-opportunity .lh-audit__header { + display: block; } + .lh-audit__header:hover { background-color: #F8F9FA; } @@ -312,7 +311,8 @@ .lh-audit-group[open] > .lh-audit-group__summary > .lh-toggle-arrow, .lh-expandable-details[open] > .lh-expandable-details__summary > .lh-toggle-arrow, -.lh-expandable-details[open] > .lh-expandable-details__summary > div > .lh-toggle-arrow { +.lh-expandable-details[open] > .lh-expandable-details__summary > div > .lh-toggle-arrow, +.lh-expandable-details[open] > .lh-expandable-details__summary > div > div > .lh-toggle-arrow { transform: rotateZ(-90deg); } @@ -424,14 +424,6 @@ /* Perf load opportunity */ -.lh-load-opportunity { - border-bottom: 1px solid var(--report-secondary-border-color); -} - -.lh-load-opportunity:last-of-type { - border-bottom: none; -} - .lh-load-opportunity__cols { display: flex; align-items: flex-start; @@ -445,20 +437,11 @@ line-height: calc(2.3 * var(--body-font-size)); } -.lh-load-opportunity__summary { - padding: var(--lh-audit-vpadding) var(--text-indent); -} -.lh-load-opportunity__summary:hover { - background-color: #F8F9FA; -} - .lh-load-opportunity__col { display: flex; justify-content: space-between; } -.lh-load-opportunity__col > * { - margin: 0 5px; -} + .lh-load-opportunity__col--one { flex: 5; margin-right: 2px; @@ -467,46 +450,9 @@ flex: 4; } -.lh-load-opportunity__title { - font-size: var(--body-font-size); - flex: 10; -} - - -.lh-load-opportunity__wasted-stat { +.lh-audit--load-opportunity .lh-audit__display-text { text-align: right; flex: 0 0 calc(3 * var(--body-font-size)); - font-size: var(--body-font-size); - line-height: var(--body-line-height); -} - -.lh-load-opportunity__description { - color: var(--secondary-text-color); - margin-top: calc(var(--default-padding) / 2); -} - -.lh-load-opportunity--pass .lh-load-opportunity__wasted-stat { - color: var(--pass-color); -} - -.lh-load-opportunity--pass .lh-sparkline__bar { - background: var(--pass-color); -} - -.lh-load-opportunity--average .lh-sparkline__bar { - background: var(--average-color); -} - -.lh-load-opportunity--average .lh-load-opportunity__wasted-stat { - color: var(--average-color); -} - -.lh-load-opportunity--fail .lh-sparkline__bar { - background: var(--fail-color); -} - -.lh-load-opportunity--fail .lh-load-opportunity__wasted-stat { - color: var(--fail-color); } @@ -527,6 +473,19 @@ float: right; } +.lh-audit--pass .lh-sparkline__bar { + background: var(--pass-color); +} + +.lh-audit--average .lh-sparkline__bar { + background: var(--average-color); +} + +.lh-audit--fail .lh-sparkline__bar { + background: var(--fail-color); +} + + /* Filmstrip */ @@ -558,7 +517,6 @@ .lh-audit { border-bottom: 1px solid var(--report-secondary-border-color); - font-size: var(--body-font-size); } .lh-audit:last-child { @@ -660,7 +618,7 @@ font-size: var(--caption-font-size); line-height: var(--caption-line-height); color: var(--fail-color); - margin-top: 3px; + margin-bottom: 3px; } @@ -958,6 +916,12 @@ summary.lh-passed-audits-summary { position: absolute; display: none; /* Don't retain these layers when not needed */ opacity: 0; + + background: #ffffff; + min-width: 23em; + padding: 15px; + border-radius: 5px; + text-align: initial; } .tooltip-boundary:hover { @@ -969,10 +933,6 @@ summary.lh-passed-audits-summary { animation: fadeInTooltip 250ms; animation-fill-mode: forwards; animation-delay: 850ms; - min-width: 23em; - background: #ffffff; - padding: 15px; - border-radius: 5px; bottom: 100%; z-index: 1; will-change: opacity; diff --git a/lighthouse-core/report/html/templates.html b/lighthouse-core/report/html/templates.html index da0695c96989..9393c9af53e9 100644 --- a/lighthouse-core/report/html/templates.html +++ b/lighthouse-core/report/html/templates.html @@ -54,24 +54,26 @@ diff --git a/lighthouse-core/test/report/html/renderer/performance-category-renderer-test.js b/lighthouse-core/test/report/html/renderer/performance-category-renderer-test.js index 49df1c89f011..39c8dfe78f5d 100644 --- a/lighthouse-core/test/report/html/renderer/performance-category-renderer-test.js +++ b/lighthouse-core/test/report/html/renderer/performance-category-renderer-test.js @@ -85,14 +85,14 @@ describe('PerfCategoryRenderer', () => { const oppAudits = category.audits.filter(audit => audit.group === 'load-opportunities' && audit.result.score !== 1); - const oppElements = categoryDOM.querySelectorAll('.lh-load-opportunity'); + const oppElements = categoryDOM.querySelectorAll('.lh-audit--load-opportunity'); assert.equal(oppElements.length, oppAudits.length); const oppElement = oppElements[0]; const oppSparklineBarElement = oppElement.querySelector('.lh-sparkline__bar'); const oppSparklineElement = oppElement.querySelector('.lh-load-opportunity__sparkline'); - const oppTitleElement = oppElement.querySelector('.lh-load-opportunity__title'); - const oppWastedElement = oppElement.querySelector('.lh-load-opportunity__wasted-stat'); + const oppTitleElement = oppElement.querySelector('.lh-audit__title'); + const oppWastedElement = oppElement.querySelector('.lh-audit__display-text'); assert.ok(oppTitleElement.textContent, 'did not render title'); assert.ok(oppSparklineBarElement.style.width, 'did not set sparkline width'); assert.ok(oppWastedElement.textContent, 'did not render stats'); @@ -105,14 +105,33 @@ describe('PerfCategoryRenderer', () => { group: 'load-opportunities', result: { score: null, scoreDisplayMode: 'error', errorMessage: 'Yikes!!', description: 'Bug #2', + details: {summary: {wastedMs: 3223}}, + }, + }; + + const fakeCategory = Object.assign({}, category, {audits: [auditWithDebug]}); + const categoryDOM = renderer.render(fakeCategory, sampleResults.reportGroups); + const debugEl = categoryDOM.querySelector('.lh-audit--load-opportunity .lh-debug'); + assert.ok(debugEl, 'did not render debug'); + assert.ok(/Yikes!!/.test(debugEl.textContent)); + }); + + it('renders errored performance opportunities with a debug string', () => { + const auditWithDebug = { + score: 0, + group: 'load-opportunities', + result: { + score: 0, scoreDisplayMode: 'numeric', + rawValue: 100, explanation: 'Yikes!!', description: 'Bug #2', }, }; const fakeCategory = Object.assign({}, category, {audits: [auditWithDebug]}); const categoryDOM = renderer.render(fakeCategory, sampleResults.reportGroups); - const debugEl = categoryDOM.querySelector('.lh-load-opportunity .lh-debug'); + const debugEl = categoryDOM.querySelector('.lh-audit--load-opportunity .lh-debug'); assert.ok(debugEl, 'did not render debug'); + assert.ok(/Yikes!!/.test(debugEl.textContent)); }); it('renders the failing diagnostics', () => { diff --git a/lighthouse-extension/test/extension-test.js b/lighthouse-extension/test/extension-test.js index 829f2cf3090d..73ecee22e51c 100644 --- a/lighthouse-extension/test/extension-test.js +++ b/lighthouse-extension/test/extension-test.js @@ -117,8 +117,8 @@ describe('Lighthouse chrome extension', function() { const selectors = { - audits: '.lh-audit,.lh-metric,.lh-load-opportunity', - titles: '.lh-audit__title, .lh-load-opportunity__title, .lh-metric__title', + audits: '.lh-audit, .lh-metric', + titles: '.lh-audit__title, .lh-metric__title', }; it('should contain all categories', async () => { diff --git a/lighthouse-viewer/test/viewer-test-pptr.js b/lighthouse-viewer/test/viewer-test-pptr.js index c9982c11b14b..2afde324403e 100644 --- a/lighthouse-viewer/test/viewer-test-pptr.js +++ b/lighthouse-viewer/test/viewer-test-pptr.js @@ -74,8 +74,8 @@ describe('Lighthouse Viewer', function() { const selectors = { - audits: '.lh-audit, .lh-metric, .lh-load-opportunity', - titles: '.lh-audit__title, .lh-load-opportunity__title, .lh-metric__title', + audits: '.lh-audit, .lh-metric', + titles: '.lh-audit__title, .lh-metric__title', }; it('should load with no errors', async () => {