UNPKG

@academyjs/rover

Version:

Rover allows you to learn programming interactively.

604 lines 20.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.constants = void 0; const events_1 = require("events"); const hook_1 = __importDefault(require("./hook")); const utils_1 = require("./utils"); const ms_1 = __importDefault(require("ms")); const errors = __importStar(require("./errors")); const { MOCHA_ID_PROP_NAME } = utils_1.constants; const constants = (0, utils_1.defineConstants)({ /** * Event emitted after a test file has been loaded Not emitted in browser. */ EVENT_FILE_POST_REQUIRE: "post-require", /** * Event emitted before a test file has been loaded. In browser, this is emitted once an interface has been selected. */ EVENT_FILE_PRE_REQUIRE: "pre-require", /** * Event emitted immediately after a test file has been loaded. Not emitted in browser. */ EVENT_FILE_REQUIRE: "require", /** * Event emitted when `global.run()` is called (use with `delay` option) */ EVENT_ROOT_SUITE_RUN: "run", /** * Namespace for collection of a `Suite`'s "after all" hooks */ HOOK_TYPE_AFTER_ALL: "afterAll", /** * Namespace for collection of a `Suite`'s "after each" hooks */ HOOK_TYPE_AFTER_EACH: "afterEach", /** * Namespace for collection of a `Suite`'s "before all" hooks */ HOOK_TYPE_BEFORE_ALL: "beforeAll", /** * Namespace for collection of a `Suite`'s "before all" hooks */ HOOK_TYPE_BEFORE_EACH: "beforeEach", // the following events are all deprecated /** * Emitted after an "after all" `Hook` has been added to a `Suite`. Deprecated */ EVENT_SUITE_ADD_HOOK_AFTER_ALL: "afterAll", /** * Emitted after an "after each" `Hook` has been added to a `Suite` Deprecated */ EVENT_SUITE_ADD_HOOK_AFTER_EACH: "afterEach", /** * Emitted after an "before all" `Hook` has been added to a `Suite` Deprecated */ EVENT_SUITE_ADD_HOOK_BEFORE_ALL: "beforeAll", /** * Emitted after an "before each" `Hook` has been added to a `Suite` Deprecated */ EVENT_SUITE_ADD_HOOK_BEFORE_EACH: "beforeEach", /** * Emitted after a child `Suite` has been added to a `Suite`. Deprecated */ EVENT_SUITE_ADD_SUITE: "suite", /** * Emitted after a `Test` has been added to a `Suite`. Deprecated */ EVENT_SUITE_ADD_TEST: "test", }); exports.constants = constants; class Suite extends events_1.EventEmitter { /** * Constructs a new `Suite` instance with the given `title`, `ctx`, and `isRoot`. * * @public * @class * @extends EventEmitter * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter|EventEmitter} * @param {string} title - Suite title. * @param {Context} parentContext - Parent context instance. * @param {boolean} [isRoot=false] - Whether this is the root suite. */ constructor(title, parentContext, isRoot) { super(); /** * Resets the state initially or for a next run. */ this.reset = function () { this.delayed = false; function doReset(thingToReset) { thingToReset.reset(); } this.suites.forEach(doReset); this.tests.forEach(doReset); this._beforeEach.forEach(doReset); this._afterEach.forEach(doReset); this._beforeAll.forEach(doReset); this._afterAll.forEach(doReset); }; /** * Return a clone of this `Suite`. * * @private * @return {Suite} */ this.clone = function () { var suite = new Suite(this.title); suite.ctx = this.ctx; suite.root = this.root; suite.timeout(this.timeout()); suite.retries(this.retries()); suite.slow(this.slow()); suite.bail(this.bail()); return suite; }; /** * Set or get timeout `ms` or short-hand such as "2s". * * @private * @todo Do not attempt to set value if `ms` is undefined * @param {number|string} ms * @return {Suite|number} for chaining */ this.timeout = function (ms) { if (!arguments.length) { return this._timeout; } if (typeof ms === "string") { ms = (0, ms_1.default)(ms); } // Clamp to range var INT_MAX = Math.pow(2, 31) - 1; var range = [0, INT_MAX]; ms = (0, utils_1.clamp)(ms, range); this._timeout = parseInt(ms, 10); return this; }; /** * Set or get number of times to retry a failed test. * * @private * @param {number|string} n * @return {Suite|number} for chaining */ this.retries = function (n) { if (!arguments.length) { return this._retries; } this._retries = parseInt(n, 10) || 0; return this; }; /** * Set or get slow `ms` or short-hand such as "2s". * * @private * @param {number|string} ms * @return {Suite|number} for chaining */ this.slow = function (ms) { if (!arguments.length) { return this._slow; } if (typeof ms === "string") { ms = (0, ms_1.default)(ms); } this._slow = ms; return this; }; /** * Set or get whether to bail after first error. * * @private * @param {boolean} bail * @return {Suite|number} for chaining */ this.bail = function (bail) { if (!arguments.length) { return this._bail; } this._bail = bail; return this; }; /** * Check if this suite or its parent suite is marked as pending. * * @private */ this.isPending = function () { return this.pending || (this.parent && this.parent.isPending()); }; /** * Generic hook-creator. * @private * @param {string} title - Title of hook * @param {Function} fn - Hook callback * @returns {Hook} A new hook */ this._createHook = function (title, fn) { var hook = new hook_1.default(title, fn); hook.parent = this; hook.timeout(this.timeout()); hook.retries(this.retries()); hook.slow(this.slow()); hook.ctx = this.ctx; hook.file = this.file; return hook; }; /** * Run `fn(test[, done])` before running tests. * * @private * @param {string} title * @param {Function} fn * @return {Suite} for chaining */ this.beforeAll = function (title, fn) { if (this.isPending()) { return this; } if (typeof title === "function") { fn = title; title = fn.name; } title = '"before all" hook' + (title ? ": " + title : ""); var hook = this._createHook(title, fn); this._beforeAll.push(hook); this.emit(constants.EVENT_SUITE_ADD_HOOK_BEFORE_ALL, hook); return this; }; /** * Run `fn(test[, done])` after running tests. * * @private * @param {string} title * @param {Function} fn * @return {Suite} for chaining */ this.afterAll = function (title, fn) { if (this.isPending()) { return this; } if (typeof title === "function") { fn = title; title = fn.name; } title = '"after all" hook' + (title ? ": " + title : ""); var hook = this._createHook(title, fn); this._afterAll.push(hook); this.emit(constants.EVENT_SUITE_ADD_HOOK_AFTER_ALL, hook); return this; }; /** * Run `fn(test[, done])` before each test case. * * @private * @param {string} title * @param {Function} fn * @return {Suite} for chaining */ this.beforeEach = function (title, fn) { if (this.isPending()) { return this; } if (typeof title === "function") { fn = title; title = fn.name; } title = '"before each" hook' + (title ? ": " + title : ""); var hook = this._createHook(title, fn); this._beforeEach.push(hook); this.emit(constants.EVENT_SUITE_ADD_HOOK_BEFORE_EACH, hook); return this; }; /** * Run `fn(test[, done])` after each test case. * * @private * @param {string} title * @param {Function} fn * @return {Suite} for chaining */ this.afterEach = function (title, fn) { if (this.isPending()) { return this; } if (typeof title === "function") { fn = title; title = fn.name; } title = '"after each" hook' + (title ? ": " + title : ""); var hook = this._createHook(title, fn); this._afterEach.push(hook); this.emit(constants.EVENT_SUITE_ADD_HOOK_AFTER_EACH, hook); return this; }; /** * Add a test `suite`. * * @private * @param {Suite} suite * @return {Suite} for chaining */ this.addSuite = function (suite) { suite.parent = this; suite.root = false; suite.timeout(this.timeout()); suite.retries(this.retries()); suite.slow(this.slow()); suite.bail(this.bail()); this.suites.push(suite); this.emit(constants.EVENT_SUITE_ADD_SUITE, suite); return this; }; /** * Add a `test` to this suite. * * @private * @param {Test} test * @return {Suite} for chaining */ this.addTest = function (test) { test.parent = this; test.timeout(this.timeout()); test.retries(this.retries()); test.slow(this.slow()); test.ctx = this.ctx; this.tests.push(test); this.emit(constants.EVENT_SUITE_ADD_TEST, test); return this; }; /** * Return the full title generated by recursively concatenating the parent's * full title. * * @memberof Suite * @public * @return {string} */ this.fullTitle = function () { return this.titlePath().join(" "); }; /** * Return the title path generated by recursively concatenating the parent's * title path. * * @memberof Suite * @public * @return {string} */ this.titlePath = function () { var result = []; if (this.parent) { result = result.concat(this.parent.titlePath()); } if (!this.root) { result.push(this.title); } return result; }; /** * Return the total number of tests. * * @memberof Suite * @public * @return {number} */ this.total = function () { return (this.suites.reduce(function (sum, suite) { return sum + suite.total(); }, 0) + this.tests.length); }; /** * Iterates through each suite recursively to find all tests. Applies a * function in the format `fn(test)`. * * @private * @param {Function} fn * @return {Suite} */ this.eachTest = function (fn) { this.tests.forEach(fn); this.suites.forEach(function (suite) { suite.eachTest(fn); }); return this; }; /** * This will run the root suite if we happen to be running in delayed mode. * @private */ this.run = function run() { if (this.root) { this.emit(constants.EVENT_ROOT_SUITE_RUN); } }; /** * Determines whether a suite has an `only` test or suite as a descendant. * * @private * @returns {Boolean} */ this.hasOnly = function hasOnly() { return (this._onlyTests.length > 0 || this._onlySuites.length > 0 || this.suites.some(function (suite) { return suite.hasOnly(); })); }; /** * Filter suites based on `isOnly` logic. * * @private * @returns {Boolean} */ this.filterOnly = function filterOnly() { if (this._onlyTests.length) { // If the suite contains `only` tests, run those and ignore any nested suites. this.tests = this._onlyTests; this.suites = []; } else { // Otherwise, do not run any of the tests in this suite. this.tests = []; this._onlySuites.forEach(function (onlySuite) { // If there are other `only` tests/suites nested in the current `only` suite, then filter that `only` suite. // Otherwise, all of the tests on this `only` suite should be run, so don't filter it. if (onlySuite.hasOnly()) { onlySuite.filterOnly(); } }); // Run the `only` suites, as well as any other suites that have `only` tests/suites as descendants. var onlySuites = this._onlySuites; this.suites = this.suites.filter(function (childSuite) { return (onlySuites.indexOf(childSuite) !== -1 || childSuite.filterOnly()); }); } // Keep the suite only if there is something to run return this.tests.length > 0 || this.suites.length > 0; }; /** * Adds a suite to the list of subsuites marked `only`. * * @private * @param {Suite} suite */ this.appendOnlySuite = function (suite) { this._onlySuites.push(suite); }; /** * Marks a suite to be `only`. * * @private */ this.markOnly = function () { this.parent && this.parent.appendOnlySuite(this); }; /** * Adds a test to the list of tests marked `only`. * * @private * @param {Test} test */ this.appendOnlyTest = function (test) { this._onlyTests.push(test); }; /** * Returns the array of hooks by hook name; see `HOOK_TYPE_*` constants. * @private */ this.getHooks = function getHooks(name) { return this["_" + name]; }; /** * cleans all references from this suite and all child suites. */ this.dispose = function () { this.suites.forEach(function (suite) { suite.dispose(); }); this.cleanReferences(); }; /** * Cleans up the references to all the deferred functions * (before/after/beforeEach/afterEach) and tests of a Suite. * These must be deleted otherwise a memory leak can happen, * as those functions may reference variables from closures, * thus those variables can never be garbage collected as long * as the deferred functions exist. * * @private */ this.cleanReferences = function cleanReferences() { function cleanArrReferences(arr) { for (var i = 0; i < arr.length; i++) { delete arr[i].fn; } } if (Array.isArray(this._beforeAll)) { cleanArrReferences(this._beforeAll); } if (Array.isArray(this._beforeEach)) { cleanArrReferences(this._beforeEach); } if (Array.isArray(this._afterAll)) { cleanArrReferences(this._afterAll); } if (Array.isArray(this._afterEach)) { cleanArrReferences(this._afterEach); } for (var i = 0; i < this.tests.length; i++) { delete this.tests[i].fn; } }; /** * Returns an object suitable for IPC. * Functions are represented by keys beginning with `$$`. * @private * @returns {Object} */ this.serialize = function serialize() { return { _bail: this._bail, $$fullTitle: this.fullTitle(), $$isPending: Boolean(this.isPending()), root: this.root, title: this.title, [MOCHA_ID_PROP_NAME]: this.id, parent: this.parent ? { [MOCHA_ID_PROP_NAME]: this.parent.id } : null, }; }; if (!(0, utils_1.isString)(title)) { throw errors.createInvalidArgumentTypeError('Suite argument "title" must be a string. Received type "' + typeof title + '"', "title", "string"); } this.title = title; function Context() { } Context.prototype = parentContext; this.ctx = new Context(); this.suites = []; this.tests = []; this.root = isRoot === true; this.pending = false; this._retries = -1; this._beforeEach = []; this._beforeAll = []; this._afterEach = []; this._afterAll = []; this._timeout = 2000; this._slow = 75; this._bail = false; this._onlyTests = []; this._onlySuites = []; (0, utils_1.assignNewMochaID)(this); Object.defineProperty(this, "id", { get() { return (0, utils_1.getMochaID)(this); }, }); this.reset(); } } /** * Create a new `Suite` with the given `title` and parent `Suite`. * * @public * @param {Suite} parent - Parent suite (required!) * @param {string} title - Title * @return {Suite} */ Suite.create = function (parent, title) { var suite = new Suite(title, parent.ctx); suite.parent = parent; title = suite.fullTitle(); parent.addSuite(suite); return suite; }; Suite.constants = constants; exports.default = Suite; //# sourceMappingURL=suite.js.map