@academyjs/rover
Version:
Rover allows you to learn programming interactively.
604 lines • 20.3 kB
JavaScript
"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