UNPKG

japa

Version:

Lean test runner for Node.js

347 lines (346 loc) 9.56 kB
"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 = {}));