japa
Version:
Lean test runner for Node.js
347 lines (346 loc) • 9.56 kB
JavaScript
"use strict";
/**
* @module SlimRunner
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = exports.test = void 0;
/*
* japa
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
const chalk_1 = __importDefault(require("chalk"));
const Group_1 = require("../Group");
const Loader_1 = require("./Loader");
const Runner_1 = require("../Runner");
const Assert_1 = require("../Assert");
const Emitter_1 = require("../Emitter");
const list_1 = __importDefault(require("../Reporter/list"));
const loader = new Loader_1.Loader();
/**
* Returns arguments to be passed to the callback
* of a test
*/
function testArgsFn(done, postRun) {
postRun(function postRunFn(assert) {
assert.evaluate();
});
return [new Assert_1.Assert(), done];
}
/**
* Returns arguments to be passed to the callback of
* a hook
*/
function hookArgsFn(done) {
return [done];
}
/**
* Store of groups
*/
let groups = [];
/**
* The active group, in which all tests must be scoped
*/
let activeGroup = null;
/**
* A flag to track, if `test.only` is used to cherry pick a
* single test. All other tests are ignored from here
* on.
*/
let cherryPickedTest = false;
/**
* Import files instead of require. This does not enable
*/
let experimentalEsmSupport = false;
/**
* Options for the test runner
*/
let runnerOptions = {
bail: false,
timeout: 2000,
};
/**
* Custom reporter function
*/
let reporterFn = list_1.default;
/**
* Reference to runner hooks, to be defined inside configure
* method
*/
let beforeHooks = [];
let afterHooks = [];
/**
* Adds the test to the active group. If there isn't any active
* group, it will be created.
*/
function addTest(title, callback, options) {
if (!activeGroup) {
activeGroup = new Group_1.Group('root', testArgsFn, hookArgsFn, runnerOptions);
groups.push(activeGroup);
}
return activeGroup.test(title, callback, options);
}
/**
* Create a new test
*/
function test(title, callback) {
return addTest(title, callback);
}
exports.test = test;
/**
* Run all the tests using the runner
*/
async function run(exitProcess = true) {
const runner = new Runner_1.Runner([], runnerOptions);
runner.reporter(reporterFn);
/**
* Execute before hooks before loading any files
* from the disk
*/
for (let hook of beforeHooks) {
await hook(runner, Emitter_1.emitter);
}
const loaderFiles = await loader.loadFiles();
if (loaderFiles.length && groups.length) {
console.log(chalk_1.default.bgRed('Calling configure inside test file is not allowed. Create a master file for same'));
process.exit(1);
}
/**
* Load all files from the loader
*/
await Promise.all(loaderFiles.map((file) => {
/**
* Do not require more files, when cherry picking
* tests
*/
if (cherryPickedTest) {
return;
}
/**
* Explicit ESM
*/
if (file.endsWith('.mjs')) {
return import(file);
}
/**
* Explicit CommonJS
*/
if (file.endsWith('.cjs')) {
return require(file);
}
/**
* Opt in ESM
*/
if (experimentalEsmSupport) {
return import(file);
}
/**
* Defaults to CommonJS
*/
return require(file);
}));
let hardException = null;
try {
await runner.useGroups(groups).run();
}
catch (error) {
hardException = error;
}
/**
* Executing after hooks before cleanup
*/
for (let hook of afterHooks) {
await hook(runner, Emitter_1.emitter);
}
if (exitProcess) {
runner.hasErrors || hardException ? process.exit(1) : process.exit(0);
}
groups = [];
activeGroup = null;
}
exports.run = run;
// eslint-disable-next-line no-redeclare
(function (test) {
/**
* Create a new test to group all test together
*/
function group(title, callback) {
/**
* Do not add new groups when already cherry picked a test
*/
if (cherryPickedTest) {
return;
}
activeGroup = new Group_1.Group(title, testArgsFn, hookArgsFn, runnerOptions);
/**
* Track the group
*/
groups.push(activeGroup);
/**
* Pass instance of the group to the callback. This enables defining lifecycle
* hooks
*/
callback(activeGroup);
/**
* Reset group after callback has been executed
*/
activeGroup = null;
}
test.group = group;
/**
* Only run the specified test
*/
function only(title, callback) {
const testInstance = addTest(title, callback, { only: true });
/**
* Empty out existing groups
*/
groups = [];
/**
* Push the current active group
*/
groups.push(activeGroup);
/**
* Turn on the flag
*/
cherryPickedTest = true;
return testInstance;
}
test.only = only;
/**
* Create a test, and mark it as skipped. Skipped functions are
* never executed. However, their hooks are executed
*/
function skip(title, callback) {
return addTest(title, callback, { skip: true });
}
test.skip = skip;
/**
* Create a test, and mark it as skipped only when running in CI. Skipped
* functions are never executed. However, their hooks are executed.
*/
function skipInCI(title, callback) {
return addTest(title, callback, { skipInCI: true });
}
test.skipInCI = skipInCI;
/**
* Create a test and run it only in the CI.
*/
function runInCI(title, callback) {
return addTest(title, callback, { runInCI: true });
}
test.runInCI = runInCI;
/**
* Create regression test
*/
function failing(title, callback) {
return addTest(title, callback, { regression: true });
}
test.failing = failing;
/**
* Configure test runner
*/
function configure(options) {
/**
* Reset runner options before every configure call
*/
runnerOptions = {
bail: false,
timeout: 2000,
};
if (groups.length) {
throw new Error('test.configure must be called before creating any tests');
}
/**
* Hold repoter fn to be passed to the runner
*/
if (options.reporterFn) {
reporterFn = options.reporterFn;
}
/**
* Use bail option if defined by the end user
*/
if (options.bail !== undefined) {
runnerOptions.bail = options.bail;
}
/**
* Use timeout if defined by the end user
*/
if (typeof (options.timeout) === 'number') {
runnerOptions.timeout = options.timeout;
}
/**
* Use files glob if defined
*/
if (options.files !== undefined) {
loader.files(options.files);
}
/**
* Use files filter if defined as function
*/
if (typeof (options.filter) === 'function') {
loader.filter(options.filter);
}
/**
* Set after hooks
*/
if (options.before) {
if (!Array.isArray(options.before)) {
throw new Error('"configure.before" expects an array of functions');
}
options.before.forEach((fn, index) => {
if (typeof (fn) !== 'function') {
throw new Error(`invalid value for "configure.before" at ${index} index`);
}
});
beforeHooks = options.before;
}
/**
* Set before hooks
*/
if (options.after) {
if (!Array.isArray(options.after)) {
throw new Error('"configure.after" expects an array of functions');
}
options.after.forEach((fn, index) => {
if (typeof (fn) !== 'function') {
throw new Error(`invalid value for "configure.after" at ${index} index`);
}
});
afterHooks = options.after;
}
/**
* If grep is defined, then normalize it to regex
*/
if (options.grep) {
runnerOptions.grep = options.grep instanceof RegExp ? options.grep : new RegExp(options.grep);
}
/**
* The test files are written in ESM
*/
if (options.experimentalEsmSupport) {
experimentalEsmSupport = true;
}
}
test.configure = configure;
/**
* Nested only
*/
(function (failing) {
/**
* Only run the specified test
*/
// eslint-disable-next-line @typescript-eslint/no-shadow
function only(title, callback) {
runnerOptions.grep = new RegExp(title);
return addTest(title, callback, { regression: true });
}
failing.only = only;
})(failing = test.failing || (test.failing = {}));
})(test = exports.test || (exports.test = {}));