jest-html-reporter 2.6.2 → 2.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,602 @@
1
+ 'use strict';
2
+
3
+ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
4
+
5
+ var dateformat = _interopDefault(require('dateformat'));
6
+ var fs = _interopDefault(require('fs'));
7
+ var mkdirp = _interopDefault(require('mkdirp'));
8
+ var path = _interopDefault(require('path'));
9
+ var stripAnsi = _interopDefault(require('strip-ansi'));
10
+ var xmlbuilder = _interopDefault(require('xmlbuilder'));
11
+
12
+ var sorting = ((testResults, sortType) => {
13
+ const sortTypeLowercase = sortType && sortType.toLowerCase();
14
+
15
+ switch (sortTypeLowercase) {
16
+ case "status":
17
+ return sortByStatus(testResults);
18
+
19
+ case "executiondesc":
20
+ return sortByExecutionDesc(testResults);
21
+
22
+ case "executionasc":
23
+ return sortByExecutionAsc(testResults);
24
+
25
+ case "titledesc":
26
+ return sortByTitleDesc(testResults);
27
+
28
+ case "titleasc":
29
+ return sortByTitleAsc(testResults);
30
+
31
+ default:
32
+ return testResults;
33
+ }
34
+ });
35
+ /**
36
+ * Splits test suites apart based on individual test status and sorts by that status:
37
+ * 1. Pending
38
+ * 2. Failed
39
+ * 3. Passed
40
+ */
41
+
42
+ const sortByStatus = testResults => {
43
+ const pendingSuites = [];
44
+ const failingSuites = [];
45
+ const passingSuites = [];
46
+ testResults.forEach(result => {
47
+ const pending = [];
48
+ const failed = [];
49
+ const passed = [];
50
+ result.testResults.forEach(x => {
51
+ if (x.status === "pending") {
52
+ pending.push(x);
53
+ } else if (x.status === "failed") {
54
+ failed.push(x);
55
+ } else {
56
+ passed.push(x);
57
+ }
58
+ });
59
+
60
+ if (pending.length > 0) {
61
+ pendingSuites.push({ ...result,
62
+ testResults: pending
63
+ });
64
+ }
65
+
66
+ if (failed.length > 0) {
67
+ failingSuites.push({ ...result,
68
+ testResults: failed
69
+ });
70
+ }
71
+
72
+ if (passed.length > 0) {
73
+ passingSuites.push({ ...result,
74
+ testResults: passed
75
+ });
76
+ }
77
+ });
78
+ return [].concat(pendingSuites, failingSuites, passingSuites);
79
+ };
80
+ /**
81
+ * Sorts by Execution Time | Descending
82
+ */
83
+
84
+
85
+ const sortByExecutionDesc = testResults => {
86
+ if (testResults) {
87
+ testResults.sort((a, b) => b.perfStats.end - b.perfStats.start - (a.perfStats.end - a.perfStats.start));
88
+ }
89
+
90
+ return testResults;
91
+ };
92
+ /**
93
+ * Sorts by Execution Time | Ascending
94
+ */
95
+
96
+
97
+ const sortByExecutionAsc = testResults => {
98
+ if (testResults) {
99
+ testResults.sort((a, b) => a.perfStats.end - a.perfStats.start - (b.perfStats.end - b.perfStats.start));
100
+ }
101
+
102
+ return testResults;
103
+ };
104
+ /**
105
+ * Sorts by Suite filename and Test name | Descending
106
+ */
107
+
108
+
109
+ const sortByTitleDesc = testResults => {
110
+ if (testResults) {
111
+ // Sort Suites
112
+ const sorted = testResults.sort((a, b) => sortAlphabetically(a.testFilePath, b.testFilePath, true)); // Sort Suite testResults
113
+
114
+ sorted.forEach(suite => {
115
+ suite.testResults.sort((a, b) => sortAlphabetically(a.ancestorTitles.join(" "), b.ancestorTitles.join(" "), true));
116
+ });
117
+ return sorted;
118
+ }
119
+
120
+ return testResults;
121
+ };
122
+ /**
123
+ * Sorts by Suite filename and Test name | Ascending
124
+ */
125
+
126
+
127
+ const sortByTitleAsc = testResults => {
128
+ if (testResults) {
129
+ // Sort Suites
130
+ const sorted = testResults.sort((a, b) => sortAlphabetically(a.testFilePath, b.testFilePath)); // Sort Suite testResults
131
+
132
+ sorted.forEach(suite => {
133
+ suite.testResults.sort((a, b) => sortAlphabetically(a.ancestorTitles.join(" "), b.ancestorTitles.join(" ")));
134
+ });
135
+ return sorted;
136
+ }
137
+
138
+ return testResults;
139
+ };
140
+ /**
141
+ * Helper sorting method
142
+ */
143
+
144
+
145
+ const sortAlphabetically = (a, b, reversed = false) => {
146
+ if (!reversed && a < b || reversed && a > b) {
147
+ return -1;
148
+ } else if (!reversed && a > b || reversed && a < b) {
149
+ return 1;
150
+ }
151
+
152
+ return 0;
153
+ };
154
+
155
+ class HTMLReporter {
156
+ constructor(testData, options, consoleLogs) {
157
+ this.testData = testData;
158
+ this.consoleLogList = consoleLogs;
159
+ this.setupConfig(options);
160
+ }
161
+
162
+ async generate() {
163
+ try {
164
+ const report = await this.renderTestReport();
165
+ const outputPath = this.getConfigValue("outputPath");
166
+ await mkdirp(path.dirname(outputPath));
167
+
168
+ if (this.getConfigValue("append")) {
169
+ await fs.appendFileSync(outputPath, report);
170
+ } else {
171
+ await fs.writeFileSync(outputPath, report);
172
+ }
173
+
174
+ this.logMessage("success", `Report generated (${outputPath})`);
175
+ return report;
176
+ } catch (e) {
177
+ this.logMessage("error", e);
178
+ }
179
+ }
180
+
181
+ async renderTestReport() {
182
+ // Generate the content of the test report
183
+ const reportBody = await this.renderTestReportBody(); // --
184
+ // Boilerplate Option
185
+
186
+ if (!!this.getConfigValue("boilerplate")) {
187
+ const boilerplateContent = await fs.readFileSync(this.getConfigValue("boilerplate"), "utf8");
188
+ return boilerplateContent.replace("{jesthtmlreporter-content}", reportBody.toString());
189
+ } // --
190
+ // Create HTML and apply reporter content
191
+
192
+
193
+ const HTMLBase = {
194
+ html: {
195
+ head: {
196
+ meta: {
197
+ "@charset": "utf-8"
198
+ },
199
+ title: {
200
+ "#text": this.getConfigValue("pageTitle")
201
+ },
202
+ style: undefined,
203
+ link: undefined
204
+ }
205
+ }
206
+ }; // Default to the currently set theme
207
+
208
+ let stylesheetFilePath = path.join(__dirname, `../style/${this.getConfigValue("theme")}.css`); // Overriding stylesheet
209
+
210
+ if (this.getConfigValue("styleOverridePath")) {
211
+ stylesheetFilePath = this.getConfigValue("styleOverridePath");
212
+ } // Decide whether to inline the CSS or not
213
+
214
+
215
+ const inlineCSS = !this.getConfigValue("useCssFile") && !!!this.getConfigValue("styleOverridePath");
216
+
217
+ if (inlineCSS) {
218
+ const stylesheetContent = await fs.readFileSync(stylesheetFilePath, "utf8");
219
+ HTMLBase.html.head.style = {
220
+ "@type": "text/css",
221
+ "#text": stylesheetContent
222
+ };
223
+ } else {
224
+ HTMLBase.html.head.link = {
225
+ "@rel": "stylesheet",
226
+ "@type": "text/css",
227
+ "@href": stylesheetFilePath
228
+ };
229
+ }
230
+
231
+ const report = xmlbuilder.create(HTMLBase);
232
+ report.ele("body").raw(reportBody.toString());
233
+ return report;
234
+ }
235
+
236
+ async renderTestReportBody() {
237
+ try {
238
+ if (!this.testData || Object.entries(this.testData).length === 0) {
239
+ throw Error("No test data provided");
240
+ } // HTML Body
241
+
242
+
243
+ const reportBody = xmlbuilder.begin().element("div", {
244
+ id: "jesthtml-content"
245
+ });
246
+ /**
247
+ * Page Header
248
+ */
249
+
250
+ const header = reportBody.ele("header"); // Page Title
251
+
252
+ header.ele("h1", {
253
+ id: "title"
254
+ }, this.getConfigValue("pageTitle")); // Logo
255
+
256
+ const logo = this.getConfigValue("logo");
257
+
258
+ if (logo) {
259
+ header.ele("img", {
260
+ id: "logo",
261
+ src: logo
262
+ });
263
+ }
264
+ /**
265
+ * Meta-Data
266
+ */
267
+
268
+
269
+ const metaDataContainer = reportBody.ele("div", {
270
+ id: "metadata-container"
271
+ }); // Timestamp
272
+
273
+ const timestamp = new Date(this.testData.startTime);
274
+ metaDataContainer.ele("div", {
275
+ id: "timestamp"
276
+ }, `Start: ${dateformat(timestamp.toDateString(), this.getConfigValue("dateFormat"))}`); // Test Summary
277
+
278
+ metaDataContainer.ele("div", {
279
+ id: "summary"
280
+ }, `${this.testData.numTotalTests} tests -- ${this.testData.numPassedTests} passed / ${this.testData.numFailedTests} failed / ${this.testData.numPendingTests} pending`);
281
+ /**
282
+ * Apply any given sorting method to the test results
283
+ */
284
+
285
+ const sortedTestResults = sorting(this.testData.testResults, this.getConfigValue("sort"));
286
+ /**
287
+ * Setup ignored test result statuses
288
+ */
289
+
290
+ const statusIgnoreFilter = this.getConfigValue("statusIgnoreFilter");
291
+ let ignoredStatuses = [];
292
+
293
+ if (statusIgnoreFilter) {
294
+ ignoredStatuses = statusIgnoreFilter.replace(/\s/g, "").toLowerCase().split(",");
295
+ }
296
+ /**
297
+ * Test Suites
298
+ */
299
+
300
+
301
+ sortedTestResults.map(suite => {
302
+ // Ignore this suite if there are no results
303
+ if (!suite.testResults || suite.testResults.length <= 0) {
304
+ return;
305
+ } // Suite Information
306
+
307
+
308
+ const suiteInfo = reportBody.ele("div", {
309
+ class: "suite-info"
310
+ }); // Suite Path
311
+
312
+ suiteInfo.ele("div", {
313
+ class: "suite-path"
314
+ }, suite.testFilePath); // Suite execution time
315
+
316
+ const executionTime = (suite.perfStats.end - suite.perfStats.start) / 1000;
317
+ suiteInfo.ele("div", {
318
+ class: `suite-time${executionTime > 5 ? " warn" : ""}`
319
+ }, `${executionTime}s`); // Suite Test Table
320
+
321
+ const suiteTable = reportBody.ele("table", {
322
+ class: "suite-table",
323
+ cellspacing: "0",
324
+ cellpadding: "0"
325
+ }); // Test Results
326
+
327
+ suite.testResults // Filter out the test results with statuses that equals the statusIgnoreFilter
328
+ .filter(s => !ignoredStatuses.includes(s.status)).forEach(test => {
329
+ const testTr = suiteTable.ele("tr", {
330
+ class: test.status
331
+ }); // Suite Name(s)
332
+
333
+ testTr.ele("td", {
334
+ class: "suite"
335
+ }, test.ancestorTitles.join(" > ")); // Test name
336
+
337
+ const testTitleTd = testTr.ele("td", {
338
+ class: "test"
339
+ }, test.title); // Test Failure Messages
340
+
341
+ if (test.failureMessages && this.getConfigValue("includeFailureMsg")) {
342
+ const failureMsgDiv = testTitleTd.ele("div", {
343
+ class: "failureMessages"
344
+ });
345
+ test.failureMessages.forEach(failureMsg => {
346
+ failureMsgDiv.ele("pre", {
347
+ class: "failureMsg"
348
+ }, stripAnsi(failureMsg));
349
+ });
350
+ } // Append data to <tr>
351
+
352
+
353
+ testTr.ele("td", {
354
+ class: "result"
355
+ }, test.status === "passed" ? `${test.status} in ${test.duration / 1000}s` : test.status);
356
+ }); // All console.logs caught during the test run
357
+
358
+ if (this.consoleLogList && this.consoleLogList.length > 0 && this.getConfigValue("includeConsoleLog")) {
359
+ // Filter out the logs for this test file path
360
+ const filteredConsoleLogs = this.consoleLogList.find(logs => logs.filePath === suite.testFilePath);
361
+
362
+ if (filteredConsoleLogs && filteredConsoleLogs.logs.length > 0) {
363
+ // Console Log Container
364
+ const consoleLogContainer = reportBody.ele("div", {
365
+ class: "suite-consolelog"
366
+ }); // Console Log Header
367
+
368
+ consoleLogContainer.ele("div", {
369
+ class: "suite-consolelog-header"
370
+ }, "Console Log"); // Apply the logs to the body
371
+
372
+ filteredConsoleLogs.logs.forEach(log => {
373
+ const logElement = consoleLogContainer.ele("div", {
374
+ class: "suite-consolelog-item"
375
+ });
376
+ logElement.ele("pre", {
377
+ class: "suite-consolelog-item-origin"
378
+ }, stripAnsi(log.origin));
379
+ logElement.ele("pre", {
380
+ class: "suite-consolelog-item-message"
381
+ }, stripAnsi(log.message));
382
+ });
383
+ }
384
+ }
385
+ });
386
+ return reportBody;
387
+ } catch (e) {
388
+ this.logMessage("error", e);
389
+ }
390
+ }
391
+ /**
392
+ * Fetch and setup configuration
393
+ */
394
+
395
+
396
+ setupConfig(options) {
397
+ this.config = {
398
+ append: {
399
+ defaultValue: false,
400
+ environmentVariable: "JEST_HTML_REPORTER_APPEND",
401
+ configValue: options.append
402
+ },
403
+ boilerplate: {
404
+ defaultValue: null,
405
+ environmentVariable: "JEST_HTML_REPORTER_BOILERPLATE",
406
+ configValue: options.boilerplate
407
+ },
408
+ customScriptPath: {
409
+ defaultValue: null,
410
+ environmentVariable: "JEST_HTML_REPORTER_CUSTOM_SCRIPT_PATH",
411
+ configValue: options.customScriptPath
412
+ },
413
+ dateFormat: {
414
+ defaultValue: "yyyy-mm-dd HH:MM:ss",
415
+ environmentVariable: "JEST_HTML_REPORTER_DATE_FORMAT",
416
+ configValue: options.dateFormat
417
+ },
418
+ executionTimeWarningThreshold: {
419
+ defaultValue: 5,
420
+ environmentVariable: "JEST_HTML_REPORTER_EXECUTION_TIME_WARNING_THRESHOLD",
421
+ configValue: options.executionTimeWarningThreshold
422
+ },
423
+ logo: {
424
+ defaultValue: null,
425
+ environmentVariable: "JEST_HTML_REPORTER_LOGO",
426
+ configValue: options.logo
427
+ },
428
+ includeFailureMsg: {
429
+ defaultValue: false,
430
+ environmentVariable: "JEST_HTML_REPORTER_INCLUDE_FAILURE_MSG",
431
+ configValue: options.includeFailureMsg
432
+ },
433
+ includeConsoleLog: {
434
+ defaultValue: false,
435
+ environmentVariable: "JEST_HTML_REPORTER_INCLUDE_CONSOLE_LOG",
436
+ configValue: options.includeConsoleLog
437
+ },
438
+ outputPath: {
439
+ defaultValue: path.join(process.cwd(), "test-report.html"),
440
+ environmentVariable: "JEST_HTML_REPORTER_OUTPUT_PATH",
441
+ configValue: options.outputPath
442
+ },
443
+ pageTitle: {
444
+ defaultValue: "Test Report",
445
+ environmentVariable: "JEST_HTML_REPORTER_PAGE_TITLE",
446
+ configValue: options.pageTitle
447
+ },
448
+ theme: {
449
+ defaultValue: "defaultTheme",
450
+ environmentVariable: "JEST_HTML_REPORTER_THEME",
451
+ configValue: options.theme
452
+ },
453
+ sort: {
454
+ defaultValue: null,
455
+ environmentVariable: "JEST_HTML_REPORTER_SORT",
456
+ configValue: options.sort
457
+ },
458
+ statusIgnoreFilter: {
459
+ defaultValue: null,
460
+ environmentVariable: "JEST_HTML_REPORTER_STATUS_FILTER",
461
+ configValue: options.statusIgnoreFilter
462
+ },
463
+ styleOverridePath: {
464
+ defaultValue: null,
465
+ environmentVariable: "JEST_HTML_REPORTER_STYLE_OVERRIDE_PATH",
466
+ configValue: options.styleOverridePath
467
+ },
468
+ useCssFile: {
469
+ defaultValue: false,
470
+ environmentVariable: "JEST_HTML_REPORTER_USE_CSS_FILE",
471
+ configValue: options.useCssFile
472
+ }
473
+ }; // Attempt to collect and assign config settings from jesthtmlreporter.config.json
474
+
475
+ try {
476
+ const jesthtmlreporterconfig = fs.readFileSync(path.join(process.cwd(), "jesthtmlreporter.config.json"), "utf8");
477
+
478
+ if (jesthtmlreporterconfig) {
479
+ const parsedConfig = JSON.parse(jesthtmlreporterconfig);
480
+
481
+ for (const key of Object.keys(parsedConfig)) {
482
+ if (this.config[key]) {
483
+ this.config[key].configValue = parsedConfig[key];
484
+ }
485
+ }
486
+
487
+ return;
488
+ }
489
+ } catch (e) {}
490
+ /** do nothing */
491
+ // If above method did not work we attempt to check package.json
492
+
493
+
494
+ try {
495
+ const packageJson = fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8");
496
+
497
+ if (packageJson) {
498
+ const parsedConfig = JSON.parse(packageJson)["jest-html-reporter"];
499
+
500
+ for (const key of Object.keys(parsedConfig)) {
501
+ if (this.config[key]) {
502
+ this.config[key].configValue = parsedConfig[key];
503
+ }
504
+ }
505
+ }
506
+ } catch (e) {
507
+ /** do nothing */
508
+ }
509
+ }
510
+ /**
511
+ * Returns the configurated value from the config in the following priority order:
512
+ * Environment Variable > JSON configured value > Default value
513
+ * @param key
514
+ */
515
+
516
+
517
+ getConfigValue(key) {
518
+ const option = this.config[key];
519
+
520
+ if (!option) {
521
+ return;
522
+ }
523
+
524
+ if (process.env[option.environmentVariable]) {
525
+ return process.env[option.environmentVariable];
526
+ }
527
+
528
+ return option.configValue || option.defaultValue;
529
+ }
530
+ /**
531
+ * Method for logging to the terminal
532
+ * @param type
533
+ * @param message
534
+ * @param ignoreConsole
535
+ */
536
+
537
+
538
+ logMessage(type = "default", message, ignoreConsole) {
539
+ const logTypes = {
540
+ default: "\x1b[37m%s\x1b[0m",
541
+ success: "\x1b[32m%s\x1b[0m",
542
+ error: "\x1b[31m%s\x1b[0m"
543
+ };
544
+ const logColor = !logTypes[type] ? logTypes.default : logTypes[type];
545
+ const logMsg = `jest-html-reporter >> ${message}`;
546
+
547
+ if (!ignoreConsole) {
548
+ console.log(logColor, logMsg);
549
+ }
550
+
551
+ return {
552
+ logColor,
553
+ logMsg
554
+ }; // Return for testing purposes
555
+ }
556
+
557
+ }
558
+
559
+ function JestHtmlReporter(globalConfig, options) {
560
+ const consoleLogs = [];
561
+ /**
562
+ * If the first parameter has a property named 'testResults',
563
+ * the script is being run as a 'testResultsProcessor'.
564
+ * We then need to return the test results as they were received from Jest
565
+ * https://facebook.github.io/jest/docs/en/configuration.html#testresultsprocessor-string
566
+ */
567
+
568
+ if (Object.prototype.hasOwnProperty.call(globalConfig, "testResults")) {
569
+ // @ts-ignore
570
+ setupAndRun(globalConfig.testResults, options); // Return the results as required by Jest
571
+
572
+ return globalConfig;
573
+ }
574
+ /**
575
+ * The default behaviour - run as Custom Reporter, in parallel with Jest.
576
+ * https://facebook.github.io/jest/docs/en/configuration.html#reporters-array-modulename-modulename-options
577
+ */
578
+
579
+
580
+ this.onTestResult = (data, result) => {
581
+ // Catch console logs per test
582
+ if (result.console) {
583
+ consoleLogs.push({
584
+ filePath: result.testFilePath,
585
+ logs: result.console
586
+ });
587
+ }
588
+ };
589
+
590
+ this.onRunComplete = (contexts, testResult) => setupAndRun(testResult, options, consoleLogs);
591
+ }
592
+ /**
593
+ * Setup Jest HTML Reporter and generate a report with the given data
594
+ */
595
+
596
+
597
+ const setupAndRun = (testResults, options, logs) => {
598
+ const reporter = new HTMLReporter(testResults, options, logs);
599
+ return reporter.generate();
600
+ };
601
+
602
+ module.exports = JestHtmlReporter;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAGA,kEAA0C;AAG1C,SAAS,gBAAgB,CACvB,YAAiC,EACjC,OAA8B;IAE9B,oDAAoD;IACpD,MAAM,WAAW,GAA+B,EAAE,CAAC;IAEnD;;;;;OAKG;IACH,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,YAAY,EAAE,aAAa,CAAC,EAAE;QACrE,kBAAkB;QAClB,MAAM,QAAQ,GAAG,IAAI,sBAAY;QAC/B,aAAa;QACb,YAAY,CAAC,WAAW,EACxB,OAAmC,CACpC,CAAC;QACF,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACpB,yCAAyC;QACzC,OAAO,YAAY,CAAC;KACrB;IAED;;;;OAIG;IACH,IAAI,CAAC,UAAU,GAAG,YAAY,CAAC;IAC/B,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;IAE3B,IAAI,CAAC,YAAY,GAAG,CAAC,IAAS,EAAE,MAAkB,EAAE,EAAE;QACpD,8BAA8B;QAC9B,IAAI,MAAM,CAAC,OAAO,EAAE;YAClB,WAAW,CAAC,IAAI,CAAC;gBACf,QAAQ,EAAE,MAAM,CAAC,YAAY;gBAC7B,IAAI,EAAE,MAAM,CAAC,OAAO;aACrB,CAAC,CAAC;SACJ;IACH,CAAC,CAAC;IAEF,IAAI,CAAC,aAAa,GAAG,CAAC,QAAa,EAAE,UAA4B,EAAE,EAAE;QACnE,MAAM,QAAQ,GAAG,IAAI,sBAAY,CAC/B,UAAU,EACV,OAAmC,CACpC,CAAC;QACF,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACpB,2CAA2C;IAC7C,CAAC,CAAC;AACJ,CAAC;AAED,kBAAe,gBAAgB,CAAC"}