UNPKG

5.26 kBJavaScriptView Raw
1const Base = require('mocha/lib/reporters/base');
2const mochaPkg = require('mocha/package.json');
3const uuid = require('uuid');
4const marge = require('mochawesome-report-generator');
5const margePkg = require('mochawesome-report-generator/package.json');
6const conf = require('./config');
7const utils = require('./utils');
8const pkg = require('../package.json');
9
10// Import the utility functions
11const {
12 log,
13 mapSuites
14} = utils;
15
16// Track the total number of tests registered
17const totalTestsRegistered = { total: 0 };
18
19/**
20 * Done function gets called before mocha exits
21 *
22 * Creates and saves the report HTML and JSON files
23 *
24 * @param {Object} output Final report object
25 * @param {Object} options Options to pass to report generator
26 * @param {Object} config Reporter config object
27 * @param {Number} failures Number of reported failures
28 * @param {Function} exit
29 *
30 * @return {Promise} Resolves with successful report creation
31 */
32function done(output, options, config, failures, exit) {
33 return marge.create(output, options)
34 .then(([ htmlFile, jsonFile ]) => {
35 if (!htmlFile && !jsonFile) {
36 log('No files were generated', 'warn', config);
37 } else {
38 jsonFile && log(`Report JSON saved to ${jsonFile}`, null, config);
39 htmlFile && log(`Report HTML saved to ${htmlFile}`, null, config);
40 }
41 })
42 .catch(err => {
43 log(err, 'error', config);
44 })
45 .then(() => {
46 exit && exit(failures > 0 ? 1 : 0);
47 });
48}
49
50/**
51 * Get the class of the configured console reporter. This reporter outputs
52 * test results to the console while mocha is running, and before
53 * mochawesome generates its own report.
54 *
55 * Defaults to 'spec'.
56 *
57 * @param {String} reporter Name of reporter to use for console output
58 *
59 * @return {Object} Reporter class object
60 */
61function consoleReporter(reporter) {
62 if (reporter) {
63 try {
64 // eslint-disable-next-line import/no-dynamic-require
65 return require(`mocha/lib/reporters/${reporter}`);
66 } catch (e) {
67 log(`Unknown console reporter '${reporter}', defaulting to spec`);
68 }
69 }
70
71 return require('mocha/lib/reporters/spec');
72}
73
74/**
75 * Initialize a new reporter.
76 *
77 * @param {Runner} runner
78 * @api public
79 */
80function Mochawesome(runner, options) {
81 // Set the config options
82 this.config = conf(options);
83
84 // Reporter options
85 const reporterOptions = Object.assign(
86 {},
87 (options.reporterOptions || {}),
88 {
89 reportFilename: this.config.reportFilename,
90 saveHtml: this.config.saveHtml,
91 saveJson: this.config.saveJson
92 }
93 );
94
95 // Done function will be called before mocha exits
96 // This is where we will save JSON and generate the HTML report
97 this.done = (failures, exit) => done(
98 this.output,
99 reporterOptions,
100 this.config,
101 failures,
102 exit
103 );
104
105 // Reset total tests counter
106 totalTestsRegistered.total = 0;
107
108 // Call the Base mocha reporter
109 Base.call(this, runner);
110
111 const reporterName = reporterOptions.consoleReporter;
112 if (reporterName !== 'none') {
113 const ConsoleReporter = consoleReporter(reporterName);
114 new ConsoleReporter(runner); // eslint-disable-line
115 }
116
117 let endCalled = false;
118
119 // Add a unique identifier to each suite/test/hook
120 [ 'suite', 'test', 'hook', 'pending' ].forEach(type => {
121 runner.on(type, item => {
122 item.uuid = uuid.v4();
123 });
124 });
125
126 // Process the full suite
127 runner.on('end', () => {
128 try {
129 /* istanbul ignore else */
130 if (!endCalled) {
131 // end gets called more than once for some reason
132 // so we ensure the suite is processed only once
133 endCalled = true;
134
135 const rootSuite = mapSuites(this.runner.suite, totalTestsRegistered, this.config);
136
137 const obj = {
138 stats: this.stats,
139 results: [ rootSuite ],
140 meta: {
141 mocha: {
142 version: mochaPkg.version
143 },
144 mochawesome: {
145 options: this.config,
146 version: pkg.version
147 },
148 marge: {
149 options: options.reporterOptions,
150 version: margePkg.version
151 }
152 }
153 };
154
155 obj.stats.testsRegistered = totalTestsRegistered.total;
156
157 const { passes, failures, pending, tests, testsRegistered } = obj.stats;
158 const passPercentage = (passes / (testsRegistered - pending)) * 100;
159 const pendingPercentage = (pending / testsRegistered) * 100;
160
161 obj.stats.passPercent = passPercentage;
162 obj.stats.pendingPercent = pendingPercentage;
163 obj.stats.other = (passes + failures + pending) - tests; // Failed hooks
164 obj.stats.hasOther = obj.stats.other > 0;
165 obj.stats.skipped = testsRegistered - tests;
166 obj.stats.hasSkipped = obj.stats.skipped > 0;
167 obj.stats.failures -= obj.stats.other;
168
169 // Save the final output to be used in the done function
170 this.output = obj;
171 }
172 } catch (e) {
173 // required because thrown errors are not handled directly in the
174 // event emitter pattern and mocha does not have an "on error"
175 /* istanbul ignore next */
176 log(`Problem with mochawesome: ${e.stack}`, 'error');
177 }
178 });
179}
180
181module.exports = Mochawesome;