Source: src/mocha.js

'use strict';

const
	path = require('path'),
	fs = require('fs-extra'),
	Mocha = require('mocha'),
	output = require('./output.js');

/**
 * @class Mocha_Helper
 * @desc
 * Used for pulling in test files, and launching a Mocha test suite.
 */
class Mocha_Helper {
	/**
	 * Goes through the passed directory, and extracts any tests that match the
	 * regular expression.
	 *
	 * @param {String} dir - The directory to search
	 */
	static collectTests(dir) {
		return new Promise((resolve, reject) => {
			output.debug('Fetching test files');
			// Our container for all the test files to be run
			let tests = [];

			try {
				output.debug(`Searching in ${dir} for tests`);
				// Iterate through each file within the test directory
				fs.readdirSync(dir).forEach(file => {
					// Only use actual test files, ignore everything else
					if (file.match(/.+\.test\.js/g)) {
						let filePath = path.join(dir, file);

						tests.push(filePath);
					}
				});
			} catch (err) {
				reject(err);
			}

			output.debug(`Found ${tests.length} test file(s)`);

			return resolve(tests);
		});
	}

	/**
	 * Runs through the Mocha tests outlined in device_config.js
	 *
	 * @param {Array} testFiles - An array of test files for Mocha to run
	 * @param {String} modRoot - The path to the root of the project being tested
	 * @param {Object}  opts - Optional Arguments
	 * @param {Int} opts.timeout  - Timeout threshold for Mocha tests
	 * @param {Int} opts.slow - Slow threshold for Mocha tests
	 */
	static run(testFiles, modRoot, { timeout = 60000, slow = 30000 } = {}) {
		return new Promise((resolve, reject) => {
			// Have to clear cache so that Mocha will run the same tests twice, solution taken from here https://github.com/mochajs/mocha/issues/995#issuecomment-261752316
			Object.keys(require.cache).forEach((file) => {
				delete require.cache[file];
			});

			// Get the current date time, to label the test run in a unique manner
			const
				moment = require('moment-timezone'),
				time = moment().format('DD-MM-YY_HH꞉mm:ss');

			// Create the labels
			const
				outDir = path.join(modRoot, 'Reports'),
				outFile = path.join(outDir, `${time}.xml`);

			// Make sure our result location exists
			fs.ensureFileSync(outFile);

			output.debug(`Putting Jenkins results in ${time}.xml`);

			// Create the new Mocha instance
			let mocha = new Mocha({
				fullTrace: false,
				useColors: true,
				timeout: timeout,
				slow: slow,
				reporter: 'mocha-jenkins-reporter',
				reporterOptions: {
					junit_report_name: time,
					junit_report_path: outFile,
					junit_report_stack: 1
				}
			});

			// If tests are numbered, ensure they're executed in the correct order
			let collator = new Intl.Collator(undefined, {
				numeric: true,
				sensitivity: 'base'
			});

			testFiles.sort(collator.compare);

			// Add all of the test files one by one
			testFiles.forEach(file => {
				mocha.addFile(file);
			});

			let tests = [];

			output.debug('Handing off to Mocha for testing');

			// Run the supplied test files
			try {
				mocha.run(() => {
					output.debug('Tests complete, exiting Mocha');

					resolve(tests);
				})
					.on('test end', data => {
						try {
							// Attempt to pull the name of the ticket using the file path of the test
							let
								test,
								fileName = path.basename(data.file),
								index = tests.indexOf(tests.find(x => x.fileName === fileName));

							if (data.pending === true) {
								// Check if the ticket is already in the array
								if (index >= 0) {
									// Add the skip message onto the object
									tests[index].errors.push(data.title.replace(/\\/g, '').replace(/"/g, '\'').replace(/\n/, ''));
								} else {
									// If a ticket object isn't already in the array, create it
									test = {
										state: 3,
										name: data.title,
										fileName: fileName,
										errors: [ data.title.replace(/\\/g, '').replace(/"/g, '\'').replace(/\n/, '') ]
									};
								}
							} else if (data.state === 'passed') {
								// Check if the ticket is already in the array
								if (index >= 0) {
									// Change the state of the test to a pass if it was previously skipped
									if (tests[index].state === 3) {
										tests[index].state = 1;
									}
								} else {
									// Create a new ticket object if one doesn't exist
									test = {
										state: 1,
										name: data.title,
										fileName: fileName,
										errors: []
									};
								}
							} else if (data.state === 'failed') {
								// Create a message to be attatched to the ticket
								let failMessage = `[TEST STEP] ${data.title.replace(/"/g, '\'')}\\n[RESULT] ${data.err.message.replace(/\\/g, '').replace(/"/g, '\'').replace(/\n/, '')}`;
								// Check if the ticket is already in the array
								if (index >= 0) {
									// Change the state of the test to a failure
									tests[index].state = 2;
									// Add the error into the array
									tests[index].errors.push(failMessage);
								} else {
									// Create a new ticket object if one doesn't exist
									test = {
										state: 2,
										name: data.title,
										fileName: fileName,
										errors: [ failMessage ]
									};
								}
							}

							if (test) {
								tests.push(test);
							}

						} catch (err) {
							output.debug(err);
						}
					});
			} catch (err) {
				reject(`Issue on Mocha run: ${err}`);
			}
		});
	}
}

module.exports = Mocha_Helper;