From bd60d6de9237b073f773069957599903124d8efe Mon Sep 17 00:00:00 2001 From: Eric Bidelman Date: Fri, 14 Apr 2017 11:26:59 -0700 Subject: [PATCH] Report v2: port of cards formatter (#1992) --- .../audits/dobetterweb/dom-size.js | 5 ++ .../report/v2/renderer/details-renderer.js | 40 ++++++++++++++- lighthouse-core/report/v2/renderer/dom.js | 8 ++- .../report/v2/renderer/report-renderer.js | 2 +- lighthouse-core/report/v2/report-styles.css | 49 +++++++++++++++++-- .../v2/renderer/details-renderer-test.js | 32 +++++++++++- 6 files changed, 125 insertions(+), 11 deletions(-) diff --git a/lighthouse-core/audits/dobetterweb/dom-size.js b/lighthouse-core/audits/dobetterweb/dom-size.js index f221b5e2a749..f378d6718abf 100644 --- a/lighthouse-core/audits/dobetterweb/dom-size.js +++ b/lighthouse-core/audits/dobetterweb/dom-size.js @@ -114,6 +114,11 @@ class DOMSize extends Audit { extendedInfo: { formatter: Formatter.SUPPORTED_FORMATS.CARDS, value: cards + }, + details: { + type: 'cards', + header: {type: 'text', text: 'View details'}, + items: cards } }; } diff --git a/lighthouse-core/report/v2/renderer/details-renderer.js b/lighthouse-core/report/v2/renderer/details-renderer.js index 295809ea14ee..129538dd2878 100644 --- a/lighthouse-core/report/v2/renderer/details-renderer.js +++ b/lighthouse-core/report/v2/renderer/details-renderer.js @@ -24,7 +24,7 @@ class DetailsRenderer { } /** - * @param {!DetailsJSON} details + * @param {(!DetailsRenderer.DetailsJSON|!DetailsRenderer.CardsDetailsJSON)} details * @return {!Element} */ render(details) { @@ -33,6 +33,8 @@ class DetailsRenderer { return this._renderText(details); case 'block': return this._renderBlock(details); + case 'cards': + return this._renderCards(details); case 'list': return this._renderList(details); default: @@ -67,7 +69,7 @@ class DetailsRenderer { * @return {!Element} */ _renderList(list) { - const element = this._dom.createElement('details', 'lh-list'); + const element = this._dom.createElement('details', 'lh-details'); if (list.header) { const summary = this._dom.createElement('summary', 'lh-list__header'); summary.textContent = list.header.text; @@ -81,6 +83,36 @@ class DetailsRenderer { element.appendChild(items); return element; } + + /** + * @param {!CardsDetailsJSON} details + * @return {!Element} + */ + _renderCards(details) { + const element = this._dom.createElement('details', 'lh-details'); + if (details.header) { + element.appendChild(this._dom.createElement('summary')).textContent = details.header.text; + } + + const cardsParent = this._dom.createElement('div', 'lh-scorecards'); + for (const item of details.items) { + const card = cardsParent.appendChild( + this._dom.createElement('div', 'lh-scorecard', {title: item.snippet})); + const titleEl = this._dom.createElement('div', 'lh-scorecard__title'); + const valueEl = this._dom.createElement('div', 'lh-scorecard__value'); + const targetEl = this._dom.createElement('div', 'lh-scorecard__target'); + + card.appendChild(titleEl).textContent = item.title; + card.appendChild(valueEl).textContent = item.value; + + if (item.target) { + card.appendChild(targetEl).textContent = `target: ${item.target}`; + } + } + + element.appendChild(cardsParent); + return element; + } } if (typeof module !== 'undefined' && module.exports) { @@ -89,3 +121,7 @@ if (typeof module !== 'undefined' && module.exports) { /** @typedef {{type: string, text: string|undefined, header: DetailsJSON|undefined, items: Array|undefined}} */ DetailsRenderer.DetailsJSON; // eslint-disable-line no-unused-expressions + + +/** @typedef {{type: string, text: string, header: DetailsJSON, items: Array<{title: string, value: string, snippet: string|undefined, target: string}>}} */ +DetailsRenderer.CardsDetailsJSON; // eslint-disable-line no-unused-expressions diff --git a/lighthouse-core/report/v2/renderer/dom.js b/lighthouse-core/report/v2/renderer/dom.js index 5eaed54b92e1..4323f6708fe9 100644 --- a/lighthouse-core/report/v2/renderer/dom.js +++ b/lighthouse-core/report/v2/renderer/dom.js @@ -28,7 +28,9 @@ class DOM { /** * @param {string} name * @param {string=} className - * @param {!Object=} attrs Attribute key/val pairs. + * @param {!Object=} attrs Attribute key/val pairs. + * Note: if an attribute key has an undefined value, this method does not + * set the attribute on the node. * @return {!Element} */ createElement(name, className, attrs = {}) { @@ -37,7 +39,9 @@ class DOM { element.className = className; } Object.keys(attrs).forEach(key => { - element.setAttribute(key, attrs[key]); + if (attrs[key] !== undefined) { + element.setAttribute(key, attrs[key]); + } }); return element; } diff --git a/lighthouse-core/report/v2/renderer/report-renderer.js b/lighthouse-core/report/v2/renderer/report-renderer.js index 5a9be3d3463c..58a87d413236 100644 --- a/lighthouse-core/report/v2/renderer/report-renderer.js +++ b/lighthouse-core/report/v2/renderer/report-renderer.js @@ -199,7 +199,7 @@ if (typeof module !== 'undefined' && module.exports) { module.exports = ReportRenderer; } -/** @typedef {{id: string, weight: number, score: number, result: {description: string, displayValue: string, helpText: string, score: number|boolean, details: DetailsRenderer.DetailsJSON|undefined}}} */ +/** @typedef {{id: string, weight: number, score: number, result: {description: string, displayValue: string, helpText: string, score: number|boolean, details: DetailsRenderer.DetailsJSON|DetailsRenderer.CardsDetailsJSON|undefined}}} */ let AuditJSON; // eslint-disable-line no-unused-vars /** @typedef {{name: string, weight: number, score: number, description: string, audits: Array}} */ diff --git a/lighthouse-core/report/v2/report-styles.css b/lighthouse-core/report/v2/report-styles.css index 24fc727b32e8..5967638645c3 100644 --- a/lighthouse-core/report/v2/report-styles.css +++ b/lighthouse-core/report/v2/report-styles.css @@ -50,18 +50,22 @@ body { display: none !important; } -/* List */ -.lh-list { +.lh-details { font-size: smaller; margin-top: var(--default-padding); } -.lh-list__header { +.lh-details summary { cursor: pointer; } +.lh-details[open] summary { + margin-bottom: var(--default-padding); +} + +/* List */ .lh-list__items { - padding-left: 10px; + padding-left: var(--default-padding); } .lh-list__items > * { @@ -69,6 +73,43 @@ body { margin-bottom: 2px; } +/* Card */ +.lh-scorecards { + display: flex; + flex-wrap: wrap; +} +.lh-scorecard { + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 180px; + flex-direction: column; + padding: var(--default-padding); + padding-top: calc(32px + var(--default-padding)); + border-radius: 3px; + margin-right: var(--default-padding); + position: relative; + line-height: inherit; + border: 1px solid #ebebeb; +} +.lh-scorecard__title { + background-color: #eee; + position: absolute; + top: 0; + right: 0; + left: 0; + display: flex; + justify-content: center; + align-items: center; + padding: calc(var(--default-padding) / 2); +} +.lh-scorecard__value { + font-size: 28px; +} +.lh-scorecard__target { + margin-top: calc(var(--default-padding) / 2); +} + /* Score */ .lh-score { diff --git a/lighthouse-core/test/report/v2/renderer/details-renderer-test.js b/lighthouse-core/test/report/v2/renderer/details-renderer-test.js index 62c688bc6065..e973e0ec735a 100644 --- a/lighthouse-core/test/report/v2/renderer/details-renderer-test.js +++ b/lighthouse-core/test/report/v2/renderer/details-renderer-test.js @@ -113,11 +113,39 @@ describe('DetailsRenderer', () => { }); const textChild = el.querySelector('.lh-block > .lh-text'); - const listChild = el.querySelector('.lh-block > .lh-list'); - const textSubChild = el.querySelector('.lh-block .lh-list .lh-text'); + const listChild = el.querySelector('.lh-block > .lh-details'); + const textSubChild = el.querySelector('.lh-block .lh-details .lh-text'); assert.ok(textChild, 'did not render text children'); assert.ok(listChild, 'did not render list child'); assert.ok(textSubChild, 'did not render sub-children'); }); + + it('renders cards', () => { + const list = { + header: {type: 'text', text: 'View details'}, + items: [ + {title: 'Total DOM Nodes', value: 3500, target: '1,500 nodes'}, + {title: 'DOM Depth', value: 10, snippet: 'snippet'}, + {title: 'Maximum Children', value: 20, snippet: 'snippet2', target: 20} + ] + }; + + const details = renderer._renderCards(list); + assert.ok(details.classList.contains('lh-details')); + assert.equal(details.querySelector('summary').textContent, 'View details'); + + const cards = details.querySelectorAll('.lh-scorecards > .lh-scorecard'); + assert.ok(cards.length, list.items.length, `renders ${list.items.length} cards`); + assert.equal(cards[0].hasAttribute('title'), false, + 'does not add title attr if snippet is missing'); + assert.equal(cards[0].querySelector('.lh-scorecard__title').textContent, + 'Total DOM Nodes', 'fills title'); + assert.equal(cards[0].querySelector('.lh-scorecard__value').textContent, + '3500', 'fills value'); + assert.equal(cards[0].querySelector('.lh-scorecard__target').textContent, + 'target: 1,500 nodes', 'fills target'); + assert.equal(cards[1].getAttribute('title'), 'snippet', 'adds title attribute for snippet'); + assert.ok(!cards[1].querySelector('.lh-scorecard__target'), 'handles missing target'); + }); }); });