source: trip-planner-front/node_modules/istanbul-reports/lib/html/index.js@ 1ad8e64

Last change on this file since 1ad8e64 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 13.8 KB
Line 
1'use strict';
2/*
3 Copyright 2012-2015, Yahoo Inc.
4 Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
5 */
6const fs = require('fs');
7const path = require('path');
8const html = require('html-escaper');
9const { ReportBase } = require('istanbul-lib-report');
10const annotator = require('./annotator');
11
12function htmlHead(details) {
13 return `
14<head>
15 <title>Code coverage report for ${html.escape(details.entity)}</title>
16 <meta charset="utf-8" />
17 <link rel="stylesheet" href="${html.escape(details.prettify.css)}" />
18 <link rel="stylesheet" href="${html.escape(details.base.css)}" />
19 <link rel="shortcut icon" type="image/x-icon" href="${html.escape(
20 details.favicon
21 )}" />
22 <meta name="viewport" content="width=device-width, initial-scale=1" />
23 <style type='text/css'>
24 .coverage-summary .sorter {
25 background-image: url(${html.escape(details.sorter.image)});
26 }
27 </style>
28</head>
29 `;
30}
31
32function headerTemplate(details) {
33 function metricsTemplate({ pct, covered, total }, kind) {
34 return `
35 <div class='fl pad1y space-right2'>
36 <span class="strong">${pct}% </span>
37 <span class="quiet">${kind}</span>
38 <span class='fraction'>${covered}/${total}</span>
39 </div>
40 `;
41 }
42
43 function skipTemplate(metrics) {
44 const statements = metrics.statements.skipped;
45 const branches = metrics.branches.skipped;
46 const functions = metrics.functions.skipped;
47
48 const countLabel = (c, label, plural) =>
49 c === 0 ? [] : `${c} ${label}${c === 1 ? '' : plural}`;
50 const skips = [].concat(
51 countLabel(statements, 'statement', 's'),
52 countLabel(functions, 'function', 's'),
53 countLabel(branches, 'branch', 'es')
54 );
55
56 if (skips.length === 0) {
57 return '';
58 }
59
60 return `
61 <div class='fl pad1y'>
62 <span class="strong">${skips.join(', ')}</span>
63 <span class="quiet">Ignored</span> &nbsp;&nbsp;&nbsp;&nbsp;
64 </div>
65 `;
66 }
67
68 return `
69<!doctype html>
70<html lang="en">
71${htmlHead(details)}
72<body>
73<div class='wrapper'>
74 <div class='pad1'>
75 <h1>${details.pathHtml}</h1>
76 <div class='clearfix'>
77 ${metricsTemplate(details.metrics.statements, 'Statements')}
78 ${metricsTemplate(details.metrics.branches, 'Branches')}
79 ${metricsTemplate(details.metrics.functions, 'Functions')}
80 ${metricsTemplate(details.metrics.lines, 'Lines')}
81 ${skipTemplate(details.metrics)}
82 </div>
83 <p class="quiet">
84 Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
85 </p>
86 </div>
87 <div class='status-line ${details.reportClass}'></div>
88 `;
89}
90
91function footerTemplate(details) {
92 return `
93 <div class='push'></div><!-- for sticky footer -->
94 </div><!-- /wrapper -->
95 <div class='footer quiet pad2 space-top1 center small'>
96 Code coverage generated by
97 <a href="https://istanbul.js.org/" target="_blank">istanbul</a>
98 at ${html.escape(details.datetime)}
99 </div>
100 </div>
101 <script src="${html.escape(details.prettify.js)}"></script>
102 <script>
103 window.onload = function () {
104 prettyPrint();
105 };
106 </script>
107 <script src="${html.escape(details.sorter.js)}"></script>
108 <script src="${html.escape(details.blockNavigation.js)}"></script>
109 </body>
110</html>
111 `;
112}
113
114function detailTemplate(data) {
115 const lineNumbers = new Array(data.maxLines).fill().map((_, i) => i + 1);
116 const lineLink = num =>
117 `<a name='L${num}'></a><a href='#L${num}'>${num}</a>`;
118 const lineCount = line =>
119 `<span class="cline-any cline-${line.covered}">${line.hits}</span>`;
120
121 /* This is rendered in a `<pre>`, need control of all whitespace. */
122 return [
123 '<tr>',
124 `<td class="line-count quiet">${lineNumbers
125 .map(lineLink)
126 .join('\n')}</td>`,
127 `<td class="line-coverage quiet">${data.lineCoverage
128 .map(lineCount)
129 .join('\n')}</td>`,
130 `<td class="text"><pre class="prettyprint lang-js">${data.annotatedCode.join(
131 '\n'
132 )}</pre></td>`,
133 '</tr>'
134 ].join('');
135}
136const summaryTableHeader = [
137 '<div class="pad1">',
138 '<table class="coverage-summary">',
139 '<thead>',
140 '<tr>',
141 ' <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>',
142 ' <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>',
143 ' <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>',
144 ' <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>',
145 ' <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>',
146 ' <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>',
147 ' <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>',
148 ' <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>',
149 ' <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>',
150 ' <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>',
151 '</tr>',
152 '</thead>',
153 '<tbody>'
154].join('\n');
155
156function summaryLineTemplate(details) {
157 const { reportClasses, metrics, file, output } = details;
158 const percentGraph = pct => {
159 if (!isFinite(pct)) {
160 return '';
161 }
162
163 const cls = ['cover-fill'];
164 if (pct === 100) {
165 cls.push('cover-full');
166 }
167
168 pct = Math.floor(pct);
169 return [
170 `<div class="${cls.join(' ')}" style="width: ${pct}%"></div>`,
171 `<div class="cover-empty" style="width: ${100 - pct}%"></div>`
172 ].join('');
173 };
174 const summaryType = (type, showGraph = false) => {
175 const info = metrics[type];
176 const reportClass = reportClasses[type];
177 const result = [
178 `<td data-value="${info.pct}" class="pct ${reportClass}">${info.pct}%</td>`,
179 `<td data-value="${info.total}" class="abs ${reportClass}">${info.covered}/${info.total}</td>`
180 ];
181 if (showGraph) {
182 result.unshift(
183 `<td data-value="${info.pct}" class="pic ${reportClass}">`,
184 `<div class="chart">${percentGraph(info.pct)}</div>`,
185 `</td>`
186 );
187 }
188
189 return result;
190 };
191
192 return []
193 .concat(
194 '<tr>',
195 `<td class="file ${
196 reportClasses.statements
197 }" data-value="${html.escape(file)}"><a href="${html.escape(
198 output
199 )}">${html.escape(file)}</a></td>`,
200 summaryType('statements', true),
201 summaryType('branches'),
202 summaryType('functions'),
203 summaryType('lines'),
204 '</tr>\n'
205 )
206 .join('\n\t');
207}
208
209const summaryTableFooter = ['</tbody>', '</table>', '</div>'].join('\n');
210const emptyClasses = {
211 statements: 'empty',
212 lines: 'empty',
213 functions: 'empty',
214 branches: 'empty'
215};
216
217const standardLinkMapper = {
218 getPath(node) {
219 if (typeof node === 'string') {
220 return node;
221 }
222 let filePath = node.getQualifiedName();
223 if (node.isSummary()) {
224 if (filePath !== '') {
225 filePath += '/index.html';
226 } else {
227 filePath = 'index.html';
228 }
229 } else {
230 filePath += '.html';
231 }
232 return filePath;
233 },
234
235 relativePath(source, target) {
236 const targetPath = this.getPath(target);
237 const sourcePath = path.dirname(this.getPath(source));
238 return path.posix.relative(sourcePath, targetPath);
239 },
240
241 assetPath(node, name) {
242 return this.relativePath(this.getPath(node), name);
243 }
244};
245
246function fixPct(metrics) {
247 Object.keys(emptyClasses).forEach(key => {
248 metrics[key].pct = 0;
249 });
250 return metrics;
251}
252
253class HtmlReport extends ReportBase {
254 constructor(opts) {
255 super();
256
257 this.verbose = opts.verbose;
258 this.linkMapper = opts.linkMapper || standardLinkMapper;
259 this.subdir = opts.subdir || '';
260 this.date = Date();
261 this.skipEmpty = opts.skipEmpty;
262 }
263
264 getBreadcrumbHtml(node) {
265 let parent = node.getParent();
266 const nodePath = [];
267
268 while (parent) {
269 nodePath.push(parent);
270 parent = parent.getParent();
271 }
272
273 const linkPath = nodePath.map(ancestor => {
274 const target = this.linkMapper.relativePath(node, ancestor);
275 const name = ancestor.getRelativeName() || 'All files';
276 return '<a href="' + target + '">' + name + '</a>';
277 });
278
279 linkPath.reverse();
280 return linkPath.length > 0
281 ? linkPath.join(' / ') + ' ' + node.getRelativeName()
282 : 'All files';
283 }
284
285 fillTemplate(node, templateData, context) {
286 const linkMapper = this.linkMapper;
287 const summary = node.getCoverageSummary();
288 templateData.entity = node.getQualifiedName() || 'All files';
289 templateData.metrics = summary;
290 templateData.reportClass = context.classForPercent(
291 'statements',
292 summary.statements.pct
293 );
294 templateData.pathHtml = this.getBreadcrumbHtml(node);
295 templateData.base = {
296 css: linkMapper.assetPath(node, 'base.css')
297 };
298 templateData.sorter = {
299 js: linkMapper.assetPath(node, 'sorter.js'),
300 image: linkMapper.assetPath(node, 'sort-arrow-sprite.png')
301 };
302 templateData.blockNavigation = {
303 js: linkMapper.assetPath(node, 'block-navigation.js')
304 };
305 templateData.prettify = {
306 js: linkMapper.assetPath(node, 'prettify.js'),
307 css: linkMapper.assetPath(node, 'prettify.css')
308 };
309 templateData.favicon = linkMapper.assetPath(node, 'favicon.png');
310 }
311
312 getTemplateData() {
313 return { datetime: this.date };
314 }
315
316 getWriter(context) {
317 if (!this.subdir) {
318 return context.writer;
319 }
320 return context.writer.writerForDir(this.subdir);
321 }
322
323 onStart(root, context) {
324 const assetHeaders = {
325 '.js': '/* eslint-disable */\n'
326 };
327
328 ['.', 'vendor'].forEach(subdir => {
329 const writer = this.getWriter(context);
330 const srcDir = path.resolve(__dirname, 'assets', subdir);
331 fs.readdirSync(srcDir).forEach(f => {
332 const resolvedSource = path.resolve(srcDir, f);
333 const resolvedDestination = '.';
334 const stat = fs.statSync(resolvedSource);
335 let dest;
336
337 if (stat.isFile()) {
338 dest = resolvedDestination + '/' + f;
339 if (this.verbose) {
340 console.log('Write asset: ' + dest);
341 }
342 writer.copyFile(
343 resolvedSource,
344 dest,
345 assetHeaders[path.extname(f)]
346 );
347 }
348 });
349 });
350 }
351
352 onSummary(node, context) {
353 const linkMapper = this.linkMapper;
354 const templateData = this.getTemplateData();
355 const children = node.getChildren();
356 const skipEmpty = this.skipEmpty;
357
358 this.fillTemplate(node, templateData, context);
359 const cw = this.getWriter(context).writeFile(linkMapper.getPath(node));
360 cw.write(headerTemplate(templateData));
361 cw.write(summaryTableHeader);
362 children.forEach(child => {
363 const metrics = child.getCoverageSummary();
364 const isEmpty = metrics.isEmpty();
365 if (skipEmpty && isEmpty) {
366 return;
367 }
368 const reportClasses = isEmpty
369 ? emptyClasses
370 : {
371 statements: context.classForPercent(
372 'statements',
373 metrics.statements.pct
374 ),
375 lines: context.classForPercent(
376 'lines',
377 metrics.lines.pct
378 ),
379 functions: context.classForPercent(
380 'functions',
381 metrics.functions.pct
382 ),
383 branches: context.classForPercent(
384 'branches',
385 metrics.branches.pct
386 )
387 };
388 const data = {
389 metrics: isEmpty ? fixPct(metrics) : metrics,
390 reportClasses,
391 file: child.getRelativeName(),
392 output: linkMapper.relativePath(node, child)
393 };
394 cw.write(summaryLineTemplate(data) + '\n');
395 });
396 cw.write(summaryTableFooter);
397 cw.write(footerTemplate(templateData));
398 cw.close();
399 }
400
401 onDetail(node, context) {
402 const linkMapper = this.linkMapper;
403 const templateData = this.getTemplateData();
404
405 this.fillTemplate(node, templateData, context);
406 const cw = this.getWriter(context).writeFile(linkMapper.getPath(node));
407 cw.write(headerTemplate(templateData));
408 cw.write('<pre><table class="coverage">\n');
409 cw.write(detailTemplate(annotator(node.getFileCoverage(), context)));
410 cw.write('</table></pre>\n');
411 cw.write(footerTemplate(templateData));
412 cw.close();
413 }
414}
415
416module.exports = HtmlReport;
Note: See TracBrowser for help on using the repository browser.