UNPKG

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