UNPKG

tape

Version:

tap-producing test harness for node and browsers

235 lines (194 loc) • 6.74 kB
'use strict'; var defined = require('defined'); var through = require('@ljharb/through'); var createDefaultStream = require('./lib/default_stream'); var Test = require('./lib/test'); var Results = require('./lib/results'); var canEmitExit = typeof process !== 'undefined' && process && typeof process.on === 'function' && /** @type {{ browser?: boolean }} */ (process).browser !== true; var canExit = typeof process !== 'undefined' && process && typeof process.exit === 'function'; /** * @import Tape, { * createHarness as CreateHarness, * CreateStream, * Harness, * HarnessConfig, * HarnessEventHandler, * TestOptions, * } from '.' */ /** @import { Result } from './lib/results' */ var tape = (function () { var wait = false; /** @type {undefined | Harness} */ var harness; /** @type {(opts?: HarnessConfig) => Harness} */ function getHarness(opts) { // this override is here since tests fail via nyc if createHarness is moved upwards if (!harness) { // eslint-disable-next-line no-use-before-define harness = createExitHarness(opts || {}, wait); } return harness; } /** @type {(this: Harness, ...args: Parameters<typeof Tape>) => ReturnType<typeof Tape>} */ function lazyLoad() { // eslint-disable-next-line no-invalid-this return getHarness().apply(this, arguments); } lazyLoad.wait = function () { wait = true; }; lazyLoad.run = function () { var run = getHarness().run; if (run) { run(); } }; lazyLoad.only = function () { return getHarness().only.apply(this, arguments); }; /** @type {CreateStream} */ lazyLoad.createStream = function (opts) { var options = opts || {}; if (!harness) { var output = through(); getHarness({ stream: output, objectMode: options.objectMode }); return output; } return harness.createStream(options); }; /** @type {typeof Tape.onFinish} */ lazyLoad.onFinish = function () { return getHarness().onFinish.apply(this, arguments); }; /** @type {typeof Tape.onFailure} */ lazyLoad.onFailure = function () { return getHarness().onFailure.apply(this, arguments); }; lazyLoad.getHarness = getHarness; return lazyLoad; }()); /** @type {typeof CreateHarness} */ function createHarness(conf_) { var results = new Results({ todoIsOK: !!(process.env.TODO_IS_OK === '1') }); if (!conf_ || conf_.autoclose !== false) { results.once('done', function () { results.close(); }); } /** @type {(name: string, conf: TestOptions, cb: Test.TestCase) => Test} */ function test(name, conf, cb) { var t = new Test(name, conf, cb); test._tests.push(t); (function inspectCode(st) { st.on('test', /** @param {Test} st_ */ function sub(st_) { inspectCode(st_); }); st.on('result', /** @param {Result} r */ function (r) { if (!r.todo && !r.ok && typeof r !== 'string') { test._exitCode = 1; } }); }(t)); results.push(t); return t; } test._results = results; /** @type {Test[]} */ test._tests = []; /** @type {CreateStream} */ test.createStream = function (opts) { return results.createStream(opts); }; /** @type {HarnessEventHandler} */ test.onFinish = function (cb) { results.on('done', cb); }; /** @type {HarnessEventHandler} */ test.onFailure = function (cb) { results.on('fail', cb); }; var only = false; /** @returns {Test} */ test.only = function () { if (only) { throw new Error('there can only be one only test'); } if (conf_ && conf_.noOnly) { throw new Error('`only` tests are prohibited'); } only = true; var t = /** @type {Test} */ (test.apply(null, arguments)); results.only(t); return t; }; test._exitCode = 0; test.close = function () { results.close(); }; // @ts-expect-error TODO FIXME: why is `test` not assignable to `Harness`??? return test; } /** @type {(conf: Omit<HarnessConfig, 'autoclose'>, wait?: boolean) => Harness} */ function createExitHarness(config, wait) { var noOnly = config.noOnly; var objectMode = config.objectMode; var cStream = config.stream; var exit = config.exit; var harness = createHarness({ autoclose: !canEmitExit, noOnly: defined(noOnly, defined(process.env.NODE_TAPE_NO_ONLY_TEST, false)) }); var running = false; var ended = false; function run() { if (running) { return; } running = true; var stream = harness.createStream({ objectMode: objectMode }); var es = stream.pipe(/** @type {NodeJS.WritableStream} */ (cStream || createDefaultStream())); if (canEmitExit && es) { // in node v0.4, `es` is `undefined` // eslint-disable-next-line no-unused-vars es.on('error', function (_) { harness._exitCode = 1; }); } stream.on('end', function () { ended = true; }); } if (wait) { harness.run = run; } else { run(); } if (exit === false) { return harness; } if (!canEmitExit || !canExit) { return harness; } process.on('exit', /** @param {number | undefined} code */ function (code) { // let the process exit cleanly. if (typeof code === 'number' && code !== 0) { return; } if (!ended) { var only = harness._results._only; for (var i = 0; i < harness._tests.length; i++) { var t = harness._tests[i]; if (!only || t === only) { t._exit(); } } } harness.close(); process.removeAllListeners('exit'); // necessary for node v0.6 process.exit(code || harness._exitCode); // eslint-disable-line no-process-exit }); return harness; } // `cjs-module-lexer` - used by node's ESM interop and by `attw` - only detects named exports // from literal `module.exports.x =` statements, so attaching these to `tape` before the export // assignment, as TS 7 wants, would break `import { test } from 'tape'`. see microsoft/typescript-go#1685. // @ts-ignore TODO FIXME TS2309 module.exports = tape; // @ts-ignore TODO FIXME TS2551: attw errors without this line module.exports.createHarness = createHarness; // @ts-ignore TODO FIXME TS2339: attw errors without this line module.exports.Test = Test; // @ts-ignore TODO FIXME TS2339: attw errors without this line module.exports.test = tape; // tap compat // @ts-ignore TODO FIXME TS2339: attw errors without this line module.exports.skip = Test.skip; // the casts are load-bearing: without them TS 6 reports TS2303, a circular import alias module.exports.createStream = /** @type {typeof Tape.createStream} */ (tape.createStream); module.exports.only = /** @type {() => any} */ (tape.only); module.exports.getHarness = /** @type {typeof Tape.getHarness} */ (tape.getHarness); module.exports.run = /** @type {typeof Tape.run} */ (tape.run); module.exports.wait = /** @type {typeof Tape.wait} */ (tape.wait); module.exports.onFinish = /** @type {typeof Tape.onFinish} */ (tape.onFinish); module.exports.onFailure = /** @type {typeof Tape.onFailure} */ (tape.onFailure);