Skip to content

Commit b12f2ea

Browse files
authored
Adjust reportRenderer for devtools (#2002)
* introduce setTemplateContext * reportRenderer compiles! * adds tests
1 parent ba01e2a commit b12f2ea

6 files changed

Lines changed: 138 additions & 50 deletions

File tree

lighthouse-core/report/v2/renderer/details-renderer.js

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
'use strict';
1717

18+
/* globals self */
19+
1820
class DetailsRenderer {
1921
/**
2022
* @param {!DOM} dom
@@ -34,7 +36,7 @@ class DetailsRenderer {
3436
case 'block':
3537
return this._renderBlock(details);
3638
case 'cards':
37-
return this._renderCards(details);
39+
return this._renderCards(/** @type {!DetailsRenderer.CardsDetailsJSON} */ (details));
3840
case 'list':
3941
return this._renderList(details);
4042
default:
@@ -43,7 +45,7 @@ class DetailsRenderer {
4345
}
4446

4547
/**
46-
* @param {!DetailsJSON} text
48+
* @param {!DetailsRenderer.DetailsJSON} text
4749
* @return {!Element}
4850
*/
4951
_renderText(text) {
@@ -53,19 +55,20 @@ class DetailsRenderer {
5355
}
5456

5557
/**
56-
* @param {!DetailsJSON} block
58+
* @param {!DetailsRenderer.DetailsJSON} block
5759
* @return {!Element}
5860
*/
5961
_renderBlock(block) {
6062
const element = this._dom.createElement('div', 'lh-block');
61-
for (const item of block.items) {
63+
const items = block.items || [];
64+
for (const item of items) {
6265
element.appendChild(this.render(item));
6366
}
6467
return element;
6568
}
6669

6770
/**
68-
* @param {!DetailsJSON} list
71+
* @param {!DetailsRenderer.DetailsJSON} list
6972
* @return {!Element}
7073
*/
7174
_renderList(list) {
@@ -76,16 +79,17 @@ class DetailsRenderer {
7679
element.appendChild(summary);
7780
}
7881

79-
const items = this._dom.createElement('div', 'lh-list__items');
80-
for (const item of list.items) {
81-
items.appendChild(this.render(item));
82+
const itemsElem = this._dom.createElement('div', 'lh-list__items');
83+
const items = list.items || [];
84+
for (const item of items) {
85+
itemsElem.appendChild(this.render(item));
8286
}
83-
element.appendChild(items);
87+
element.appendChild(itemsElem);
8488
return element;
8589
}
8690

8791
/**
88-
* @param {!CardsDetailsJSON} details
92+
* @param {!DetailsRenderer.CardsDetailsJSON} details
8993
* @return {!Element}
9094
*/
9195
_renderCards(details) {
@@ -117,11 +121,25 @@ class DetailsRenderer {
117121

118122
if (typeof module !== 'undefined' && module.exports) {
119123
module.exports = DetailsRenderer;
124+
} else {
125+
self.DetailsRenderer = DetailsRenderer;
120126
}
121127

122-
/** @typedef {{type: string, text: string|undefined, header: DetailsJSON|undefined, items: Array<DetailsJSON>|undefined}} */
128+
/**
129+
* @typedef {{
130+
* type: string,
131+
* text: (string|undefined),
132+
* header: (!DetailsRenderer.DetailsJSON|undefined),
133+
* items: (!Array<!DetailsRenderer.DetailsJSON>|undefined)
134+
* }}
135+
*/
123136
DetailsRenderer.DetailsJSON; // eslint-disable-line no-unused-expressions
124137

125-
126-
/** @typedef {{type: string, text: string, header: DetailsJSON, items: Array<{title: string, value: string, snippet: string|undefined, target: string}>}} */
138+
/** @typedef {{
139+
* type: string,
140+
* text: string,
141+
* header: !DetailsRenderer.DetailsJSON,
142+
* items: !Array<{title: string, value: string, snippet: (string|undefined), target: string}>
143+
* }}
144+
*/
127145
DetailsRenderer.CardsDetailsJSON; // eslint-disable-line no-unused-expressions

lighthouse-core/report/v2/renderer/dom.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
'use strict';
1717

18-
/* globals URL */
18+
/* globals URL self */
1919

2020
class DOM {
2121
/**
@@ -33,35 +33,39 @@ class DOM {
3333
* set the attribute on the node.
3434
* @return {!Element}
3535
*/
36-
createElement(name, className, attrs = {}) {
36+
createElement(name, className, attrs) {
37+
// TODO(all): adopt `attrs` default arg when https://codereview.chromium.org/2821773002/ lands
38+
attrs = attrs || {};
3739
const element = this._document.createElement(name);
3840
if (className) {
3941
element.className = className;
4042
}
4143
Object.keys(attrs).forEach(key => {
42-
if (attrs[key] !== undefined) {
43-
element.setAttribute(key, attrs[key]);
44+
const value = attrs[key];
45+
if (typeof value !== 'undefined') {
46+
element.setAttribute(key, value);
4447
}
4548
});
4649
return element;
4750
}
4851

4952
/**
5053
* @param {string} selector
54+
* @param {!Document|!Element} context
5155
* @return {!DocumentFragment} A clone of the template content.
5256
* @throws {Error}
5357
*/
54-
cloneTemplate(selector) {
55-
const template = this._document.querySelector(selector);
58+
cloneTemplate(selector, context) {
59+
const template = context.querySelector(selector);
5660
if (!template) {
5761
throw new Error(`Template not found: template${selector}`);
5862
}
59-
return this._document.importNode(template.content, true);
63+
return /** @type {!DocumentFragment} */ (this._document.importNode(template.content, true));
6064
}
6165

6266
/**
6367
* @param {string} text
64-
* @return {!HTMLSpanElement}
68+
* @return {!Element}
6569
*/
6670
createSpanFromMarkdown(text) {
6771
const element = this.createElement('span');
@@ -87,8 +91,17 @@ class DOM {
8791

8892
return element;
8993
}
94+
95+
/**
96+
* @return {!Document}
97+
*/
98+
document() {
99+
return this._document;
100+
}
90101
}
91102

92103
if (typeof module !== 'undefined' && module.exports) {
93104
module.exports = DOM;
105+
} else {
106+
self.DOM = DOM;
94107
}

lighthouse-core/report/v2/renderer/report-renderer.js

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* Dummy text for ensuring report robustness: </script> pre$`post %%LIGHTHOUSE_JSON%%
2323
*/
2424

25-
/* globals DOM, DetailsRenderer */
25+
/* globals self */
2626

2727
const RATINGS = {
2828
PASS: {label: 'pass', minScore: 75},
@@ -56,15 +56,18 @@ function formatNumber(number) {
5656

5757
class ReportRenderer {
5858
/**
59-
* @param {!Document} document
59+
* @param {!DOM} dom
60+
* @param {!DetailsRenderer} detailsRenderer
6061
*/
61-
constructor(document) {
62-
this._dom = new DOM(document);
63-
this._detailsRenderer = new DetailsRenderer(this._dom);
62+
constructor(dom, detailsRenderer) {
63+
this._dom = dom;
64+
this._detailsRenderer = detailsRenderer;
65+
66+
this._templateContext = this._dom.document();
6467
}
6568

6669
/**
67-
* @param {!ReportJSON} report
70+
* @param {!ReportRenderer.ReportJSON} report
6871
* @return {!Element}
6972
*/
7073
renderReport(report) {
@@ -94,15 +97,24 @@ class ReportRenderer {
9497
element.querySelector('.lh-score__description')
9598
.appendChild(this._dom.createSpanFromMarkdown(description));
9699

97-
return element;
100+
return /** @type {!Element} **/ (element);
101+
}
102+
103+
/**
104+
* Define a custom element for <templates> to be extracted from. For example:
105+
* this.setTemplateContext(new DOMParser().parseFromString(htmlStr, 'text/html'))
106+
* @param {!Document|!Element} context
107+
*/
108+
setTemplateContext(context) {
109+
this._templateContext = context;
98110
}
99111

100112
/**
101-
* @param {!AuditJSON} audit
113+
* @param {!ReportRenderer.AuditJSON} audit
102114
* @return {!Element}
103115
*/
104116
_renderAuditScore(audit) {
105-
const tmpl = this._dom.cloneTemplate('#tmpl-lh-audit-score');
117+
const tmpl = this._dom.cloneTemplate('#tmpl-lh-audit-score', this._templateContext);
106118

107119
const scoringMode = audit.result.scoringMode;
108120
const description = audit.result.helpText;
@@ -126,11 +138,11 @@ class ReportRenderer {
126138
}
127139

128140
/**
129-
* @param {!CategoryJSON} category
141+
* @param {!ReportRenderer.CategoryJSON} category
130142
* @return {!Element}
131143
*/
132144
_renderCategoryScore(category) {
133-
const tmpl = this._dom.cloneTemplate('#tmpl-lh-category-score');
145+
const tmpl = this._dom.cloneTemplate('#tmpl-lh-category-score', this._templateContext);
134146
const score = Math.round(category.score);
135147
return this._populateScore(tmpl, score, 'numeric', category.name, category.description);
136148
}
@@ -146,7 +158,7 @@ class ReportRenderer {
146158
}
147159

148160
/**
149-
* @param {!ReportJSON} report
161+
* @param {!ReportRenderer.ReportJSON} report
150162
* @return {!Element}
151163
*/
152164
_renderReport(report) {
@@ -158,7 +170,7 @@ class ReportRenderer {
158170
}
159171

160172
/**
161-
* @param {!CategoryJSON} category
173+
* @param {!ReportRenderer.CategoryJSON} category
162174
* @return {!Element}
163175
*/
164176
_renderCategory(category) {
@@ -185,7 +197,7 @@ class ReportRenderer {
185197
}
186198

187199
/**
188-
* @param {!AuditJSON} audit
200+
* @param {!ReportRenderer.AuditJSON} audit
189201
* @return {!Element}
190202
*/
191203
_renderAudit(audit) {
@@ -197,13 +209,45 @@ class ReportRenderer {
197209

198210
if (typeof module !== 'undefined' && module.exports) {
199211
module.exports = ReportRenderer;
212+
} else {
213+
self.ReportRenderer = ReportRenderer;
200214
}
201215

202-
/** @typedef {{id: string, weight: number, score: number, result: {description: string, displayValue: string, helpText: string, score: number|boolean, details: DetailsRenderer.DetailsJSON|DetailsRenderer.CardsDetailsJSON|undefined}}} */
203-
let AuditJSON; // eslint-disable-line no-unused-vars
216+
/**
217+
* @typedef {{
218+
* id: string, weight:
219+
* number, score: number,
220+
* result: {
221+
* description: string,
222+
* displayValue: string,
223+
* helpText: string,
224+
* score: (number|boolean),
225+
* scoringMode: string,
226+
* details: (!DetailsRenderer.DetailsJSON|!DetailsRenderer.CardsDetailsJSON|undefined)
227+
* }
228+
* }}
229+
*/
230+
ReportRenderer.AuditJSON; // eslint-disable-line no-unused-expressions
204231

205-
/** @typedef {{name: string, weight: number, score: number, description: string, audits: Array<AuditJSON>}} */
206-
let CategoryJSON; // eslint-disable-line no-unused-vars
232+
/**
233+
* @typedef {{
234+
* name: string,
235+
* weight: number,
236+
* score: number,
237+
* description: string,
238+
* audits: !Array<!ReportRenderer.AuditJSON>
239+
* }}
240+
*/
241+
ReportRenderer.CategoryJSON; // eslint-disable-line no-unused-expressions
207242

208-
/** @typedef {{reportCategories: Array<CategoryJSON>}} */
209-
let ReportJSON; // eslint-disable-line no-unused-vars
243+
/**
244+
* @typedef {{
245+
* lighthouseVersion: !string,
246+
* generatedTime: !string,
247+
* initialUrl: !string,
248+
* url: !string,
249+
* audits: ?Object,
250+
* reportCategories: !Array<!ReportRenderer.CategoryJSON>
251+
* }}
252+
*/
253+
ReportRenderer.ReportJSON; // eslint-disable-line no-unused-expressions

lighthouse-core/report/v2/report-template.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@
3030
<script>%%LIGHTHOUSE_JAVASCRIPT%%</script>
3131
<script>window.__LIGHTHOUSE_JSON__ = %%LIGHTHOUSE_JSON%%;</script>
3232
<script>
33-
const renderer = new ReportRenderer(document);
34-
document.body.appendChild(renderer.renderReport(window.__LIGHTHOUSE_JSON__));
33+
const dom = new DOM(document);
34+
const detailsRenderer = new DetailsRenderer(dom);
35+
const renderer = new ReportRenderer(dom, detailsRenderer);
36+
const reportElem = renderer.renderReport(window.__LIGHTHOUSE_JSON__);
37+
document.body.appendChild(reportElem);
3538
</script>
3639
</body>
3740
</html>

lighthouse-core/test/report/v2/renderer/dom-test.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,16 @@ describe('DOM', () => {
5858

5959
describe('cloneTemplate', () => {
6060
it('should clone a template', () => {
61-
const clone = dom.cloneTemplate('#tmpl-lh-audit-score');
61+
const clone = dom.cloneTemplate('#tmpl-lh-audit-score', dom.document());
6262
assert.ok(clone.querySelector('.lh-score'));
6363
});
6464

6565
it('fails when template cannot be found', () => {
66-
assert.throws(() => dom.cloneTemplate('#unknown-selector'));
66+
assert.throws(() => dom.cloneTemplate('#unknown-selector', dom.document()));
67+
});
68+
69+
it('fails when a template context isn\'t provided', () => {
70+
assert.throws(() => dom.cloneTemplate('#tmpl-lh-audit-score'));
6771
});
6872
});
6973

lighthouse-core/test/report/v2/renderer/report-renderer-test.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,14 @@ describe('ReportRenderer V2', () => {
3333

3434
before(() => {
3535
global.URL = URL;
36-
global.DOM = DOM;
37-
global.DetailsRenderer = DetailsRenderer;
3836
const document = jsdom.jsdom(TEMPLATE_FILE);
39-
renderer = new ReportRenderer(document);
37+
const dom = new DOM(document);
38+
const detailsRenderer = new DetailsRenderer(dom);
39+
renderer = new ReportRenderer(dom, detailsRenderer);
4040
});
4141

4242
after(() => {
4343
global.URL = undefined;
44-
global.DOM = undefined;
45-
global.DetailsRenderer = undefined;
4644
});
4745

4846
describe('renderReport', () => {
@@ -96,4 +94,12 @@ describe('ReportRenderer V2', () => {
9694
assert.equal(audits.length, category.audits.length, 'renders correct number of audits');
9795
});
9896
});
97+
98+
it('can set a custom templateContext', () => {
99+
assert.equal(renderer._templateContext, renderer._dom.document());
100+
101+
const otherDocument = jsdom.jsdom(TEMPLATE_FILE);
102+
renderer.setTemplateContext(otherDocument);
103+
assert.equal(renderer._templateContext, otherDocument);
104+
});
99105
});

0 commit comments

Comments
 (0)