mocha-typescript
Version:
TypeScript decorators based wrapper over mocha's interface
654 lines • 24.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const Mocha = require("mocha");
const Common = require("mocha/lib/interfaces/common");
const Test = require("mocha/lib/test");
const globalTestFunctions = {
get describe() { return global.describe; },
get it() { return global.it; },
get before() { return global.before; },
get after() { return global.after; },
get beforeEach() { return global.beforeEach; },
get afterEach() { return global.afterEach; },
};
// key => Symbol("mocha-typescript:" + key)
const nodeSymbol = (key) => "__mts_" + key;
const suiteSymbol = nodeSymbol("suite");
const testNameSymbol = nodeSymbol("test");
const parametersSymbol = nodeSymbol("parametersSymbol");
const nameForParametersSymbol = nodeSymbol("nameForParameters");
const slowSymbol = nodeSymbol("slow");
const timeoutSymbol = nodeSymbol("timout");
const retriesSymbol = nodeSymbol("retries");
const onlySymbol = nodeSymbol("only");
const pendingSymbol = nodeSymbol("pending");
const skipSymbol = nodeSymbol("skip");
const traitsSymbol = nodeSymbol("traits");
const isTraitSymbol = nodeSymbol("isTrait");
const contextSymbol = nodeSymbol("context");
const handled = nodeSymbol("handled");
const noname = (cb) => cb;
function applyDecorators(mocha, ctorOrProto, method, instance) {
const timeoutValue = method[timeoutSymbol];
if (typeof timeoutValue === "number") {
mocha.timeout(timeoutValue);
}
const slowValue = method[slowSymbol];
if (mocha.slow && typeof slowValue === "number") {
mocha.slow(slowValue);
}
const retriesValue = method[retriesSymbol];
if (mocha.retries && typeof retriesValue === "number") {
mocha.retries(retriesValue);
}
const contextProperty = ctorOrProto[contextSymbol];
if (contextProperty) {
instance[contextProperty] = mocha;
}
}
function applyTestTraits(context, instance, method) {
const traits = method[traitsSymbol];
if (traits) {
traits.forEach((trait) => {
trait.call(context, context, instance, method);
});
}
}
function applySuiteTraits(context, target) {
const traits = target[traitsSymbol];
if (traits) {
traits.forEach((trait) => {
trait.call(context, context, target);
});
}
}
function suiteClassCallback(target, context) {
return function () {
applySuiteTraits(this, target);
applyDecorators(this, target, target, target);
let instance;
if (target.before) {
if (target.before.length > 0) {
context.before(function (done) {
applyDecorators(this, target, target.before, target);
return target.before(done);
});
}
else {
context.before(function () {
applyDecorators(this, target, target.before, target);
return target.before();
});
}
}
if (target.after) {
if (target.after.length > 0) {
context.after(function (done) {
applyDecorators(this, target, target.after, target);
return target.after(done);
});
}
else {
context.after(function () {
applyDecorators(this, target, target.after, target);
return target.after();
});
}
}
const prototype = target.prototype;
let beforeEachFunction;
if (prototype.before) {
if (prototype.before.length > 0) {
beforeEachFunction = noname(function (done) {
instance = getInstance(target);
applyDecorators(this, prototype, prototype.before, instance);
return prototype.before.call(instance, done);
});
}
else {
beforeEachFunction = noname(function () {
instance = getInstance(target);
applyDecorators(this, prototype, prototype.before, instance);
return prototype.before.call(instance);
});
}
}
else {
beforeEachFunction = noname(function () {
instance = getInstance(target);
});
}
context.beforeEach(beforeEachFunction);
let afterEachFunction;
if (prototype.after) {
if (prototype.after.length > 0) {
afterEachFunction = noname(function (done) {
try {
applyDecorators(this, prototype, prototype.after, instance);
return prototype.after.call(instance, done);
}
finally {
instance = undefined;
}
});
}
else {
afterEachFunction = noname(function () {
try {
applyDecorators(this, prototype, prototype.after, instance);
return prototype.after.call(instance);
}
finally {
instance = undefined;
}
});
}
}
else {
afterEachFunction = noname(function () {
instance = undefined;
});
}
context.afterEach(afterEachFunction);
function runTest(prototype, method) {
const testName = method[testNameSymbol] || method.name;
const shouldSkip = method[skipSymbol];
const shouldOnly = method[onlySymbol];
const shouldPending = method[pendingSymbol];
const parameters = method[parametersSymbol];
if (testName || shouldOnly || shouldPending || shouldSkip) {
if (shouldPending && !shouldSkip && !shouldOnly) {
context.it.skip(testName);
}
else if (parameters) {
const nameForParameters = method[nameForParametersSymbol];
parameters.forEach((parameterOptions, i) => {
const { mark, name, params } = parameterOptions;
let parametersTestName = `${testName}_${i}`;
if (name) {
parametersTestName = name;
}
else if (nameForParameters) {
parametersTestName = nameForParameters(params);
}
const shouldSkipParam = shouldSkip || (mark === 1 /* skip */);
const shouldOnlyParam = shouldOnly || (mark === 2 /* only */);
const shouldPendingParam = shouldPending || (mark === 3 /* pending */);
if (shouldPendingParam && !shouldSkipParam && !shouldOnlyParam) {
context.it.skip(testName);
}
else {
const testFunc = (shouldSkipParam && context.it.skip)
|| (shouldOnlyParam && context.it.only)
|| context.it;
applyTestFunc(testFunc, parametersTestName, method, [params], method.length <= 1);
}
});
}
else {
const testFunc = (shouldSkip && context.it.skip)
|| (shouldOnly && context.it.only)
|| context.it;
applyTestFunc(testFunc, testName, method, [], method.length === 0);
}
}
}
function applyTestFunc(testFunc, testName, method, callArgs, sync = true) {
if (sync) {
testFunc(testName, noname(function () {
applyDecorators(this, prototype, method, instance);
applyTestTraits(this, instance, method);
return method.call(instance, ...callArgs);
}));
}
else {
testFunc(testName, noname(function (done) {
applyDecorators(this, prototype, method, instance);
applyTestTraits(this, instance, method);
return method.call(instance, ...callArgs, done);
}));
}
}
// collect all tests along the inheritance chain, allow overrides
const collectedTests = {};
let currentPrototype = prototype;
while (currentPrototype !== Object.prototype) {
Object.getOwnPropertyNames(currentPrototype).forEach((key) => {
if (typeof prototype[key] === "function") {
const method = prototype[key];
if (method[testNameSymbol] && !collectedTests[key]) {
collectedTests[key] = [prototype, method];
}
}
});
currentPrototype = Object.getPrototypeOf(currentPrototype);
if (currentPrototype !== Object.prototype && currentPrototype.constructor[suiteSymbol]) {
throw new Error("deriving from other suites is bad practice and thus prohibited");
}
}
// run all collected tests
for (const key in collectedTests) {
const value = collectedTests[key];
runTest(value[0], value[1]);
}
};
}
function suiteOverload(overloads) {
return function () {
if (arguments.length === 2 && typeof arguments[0] === "string" && typeof arguments[1] === "function" && !arguments[1][isTraitSymbol]) {
return overloads.suite.apply(this, arguments);
}
else if (arguments.length === 1 && typeof arguments[0] === "function" && !arguments[0][isTraitSymbol]) {
overloads.suiteCtor.apply(this, arguments);
}
else if (arguments.length >= 1 && typeof arguments[0] === "string") {
return overloads.suiteDecoratorNamed.apply(this, arguments);
}
else {
return overloads.suiteDecorator.apply(this, arguments);
}
};
}
function makeSuiteFunction(suiteFunc, context) {
return suiteOverload({
suite(name, fn) {
return suiteFunc()(name, fn);
},
suiteCtor(ctor) {
ctor[suiteSymbol] = true;
suiteFunc(ctor)(ctor.name, suiteClassCallback(ctor, context));
},
suiteDecorator(...traits) {
return function (ctor) {
ctor[suiteSymbol] = true;
ctor[traitsSymbol] = traits;
suiteFunc(ctor)(ctor.name, suiteClassCallback(ctor, context));
};
},
suiteDecoratorNamed(name, ...traits) {
return function (ctor) {
ctor[suiteSymbol] = true;
ctor[traitsSymbol] = traits;
suiteFunc(ctor)(name, suiteClassCallback(ctor, context));
};
},
});
}
function suiteFuncCheckingDecorators(context) {
return function (ctor) {
if (ctor) {
const shouldSkip = ctor[skipSymbol];
const shouldOnly = ctor[onlySymbol];
const shouldPending = ctor[pendingSymbol];
return (shouldSkip && context.describe.skip)
|| (shouldOnly && context.describe.only)
|| (shouldPending && context.describe.skip)
|| context.describe;
}
else {
return context.describe;
}
};
}
function makeSuiteObject(context) {
return Object.assign(makeSuiteFunction(suiteFuncCheckingDecorators(context), context), {
skip: makeSuiteFunction(() => context.describe.skip, context),
only: makeSuiteFunction(() => context.describe.only, context),
pending: makeSuiteFunction(() => context.describe.skip, context),
});
}
exports.suite = makeSuiteObject(globalTestFunctions);
var Mark;
(function (Mark) {
Mark[Mark["test"] = 0] = "test";
Mark[Mark["skip"] = 1] = "skip";
Mark[Mark["only"] = 2] = "only";
Mark[Mark["pending"] = 3] = "pending";
})(Mark || (Mark = {}));
function makeParamsFunction(mark) {
return (params, name) => {
return (target, propertyKey) => {
target[propertyKey][testNameSymbol] = propertyKey ? propertyKey.toString() : "";
target[propertyKey][parametersSymbol] = target[propertyKey][parametersSymbol] || [];
target[propertyKey][parametersSymbol].push({ mark, name, params });
};
};
}
function makeParamsNameFunction() {
return (nameForParameters) => {
return (target, propertyKey) => {
target[propertyKey][nameForParametersSymbol] = nameForParameters;
};
};
}
function makeParamsObject(context) {
return Object.assign(makeParamsFunction(0 /* test */), {
skip: makeParamsFunction(1 /* skip */),
only: makeParamsFunction(2 /* only */),
pending: makeParamsFunction(3 /* pending */),
naming: makeParamsNameFunction(),
});
}
exports.params = makeParamsObject(globalTestFunctions);
function testOverload(overloads) {
return function () {
if (arguments.length === 2 && typeof arguments[0] === "string" && typeof arguments[1] === "function" && !arguments[1][isTraitSymbol]) {
return overloads.test.apply(this, arguments);
}
else if (arguments.length >= 2 && typeof arguments[0] !== "string" && typeof arguments[0] !== "function") {
overloads.testProperty.apply(this, arguments);
}
else if (arguments.length >= 1 && typeof arguments[0] === "string") {
return overloads.testDecoratorNamed.apply(this, arguments);
}
else {
return overloads.testDecorator.apply(this, arguments);
}
};
}
function makeTestFunction(testFunc, mark) {
return testOverload({
test(name, fn) {
testFunc()(name, fn);
},
testProperty(target, propertyKey, descriptor) {
target[propertyKey][testNameSymbol] = propertyKey ? propertyKey.toString() : "";
if (mark) {
target[propertyKey][mark] = true;
}
},
testDecorator(...traits) {
return function (target, propertyKey, descriptor) {
target[propertyKey][testNameSymbol] = propertyKey ? propertyKey.toString() : "";
target[propertyKey][traitsSymbol] = traits;
if (mark) {
target[propertyKey][mark] = true;
}
};
},
testDecoratorNamed(name, ...traits) {
return function (target, propertyKey, descriptor) {
target[propertyKey][testNameSymbol] = name;
target[propertyKey][traitsSymbol] = traits;
if (mark) {
target[propertyKey][mark] = true;
}
};
},
});
}
function makeTestObject(context) {
return Object.assign(makeTestFunction(() => context.it, null), {
skip: makeTestFunction(() => context.it.skip, skipSymbol),
only: makeTestFunction(() => context.it.only, onlySymbol),
pending: makeTestFunction(() => context.it.skip, pendingSymbol),
});
}
exports.test = makeTestObject(globalTestFunctions);
function trait(arg) {
arg[isTraitSymbol] = true;
return arg;
}
exports.trait = trait;
/**
* Set a test method execution time that is considered slow.
* @param time The time in miliseconds.
*/
function slow(time) {
return trait(function () {
if (arguments.length === 1) {
const target = arguments[0];
target[slowSymbol] = time;
}
else if (arguments.length === 2 && typeof arguments[1] === "string" || typeof arguments[1] === "symbol") {
const target = arguments[0];
const property = arguments[1];
target[property][slowSymbol] = time;
}
else if (arguments.length === 2) {
const context = arguments[0];
const ctor = arguments[1];
context.slow(time);
}
else if (arguments.length === 3) {
if (typeof arguments[2] === "function") {
const context = arguments[0];
const instance = arguments[1];
const method = arguments[2];
context.slow(time);
}
else if (typeof arguments[1] === "string" || typeof arguments[1] === "symbol") {
const proto = arguments[0];
const prop = arguments[1];
const descriptor = arguments[2];
proto[prop][slowSymbol] = time;
}
}
});
}
exports.slow = slow;
/**
* Set a test method or suite timeout time.
* @param time The time in miliseconds.
*/
function timeout(time) {
return trait(function () {
if (arguments.length === 1) {
const target = arguments[0];
target[timeoutSymbol] = time;
}
else if (arguments.length === 2 && typeof arguments[1] === "string" || typeof arguments[1] === "symbol") {
const target = arguments[0];
const property = arguments[1];
target[property][timeoutSymbol] = time;
}
else if (arguments.length === 2) {
const context = arguments[0];
const ctor = arguments[1];
context.timeout(time);
}
else if (arguments.length === 3) {
if (typeof arguments[2] === "function") {
const context = arguments[0];
const instance = arguments[1];
const method = arguments[2];
context.timeout(time);
}
else if (typeof arguments[1] === "string" || typeof arguments[1] === "symbol") {
const proto = arguments[0];
const prop = arguments[1];
const descriptor = arguments[2];
proto[prop][timeoutSymbol] = time;
}
}
});
}
exports.timeout = timeout;
/**
* Set a test method or site retries count.
* @param count The number of retries to attempt when running the test.
*/
function retries(count) {
return trait(function () {
if (arguments.length === 1) {
const target = arguments[0];
target[retriesSymbol] = count;
}
else if (arguments.length === 2 && typeof arguments[1] === "string" || typeof arguments[1] === "symbol") {
const target = arguments[0];
const property = arguments[1];
target[property][retriesSymbol] = count;
}
else if (arguments.length === 2) {
const context = arguments[0];
const ctor = arguments[1];
context.retries(count);
}
else if (arguments.length === 3) {
if (typeof arguments[2] === "function") {
const context = arguments[0];
const instance = arguments[1];
const method = arguments[2];
context.retries(count);
}
else if (typeof arguments[1] === "string" || typeof arguments[1] === "symbol") {
const proto = arguments[0];
const prop = arguments[1];
const descriptor = arguments[2];
proto[prop][retriesSymbol] = count;
}
}
});
}
exports.retries = retries;
exports.skipOnError = trait(function (ctx, ctor) {
ctx.beforeEach(function () {
if (ctor.__skip_all) {
this.skip();
}
});
ctx.afterEach(function () {
if (this.currentTest.state === "failed") {
ctor.__skip_all = true;
}
});
});
/**
* Mart a test or suite as pending.
* - Used as `@suite @pending class` is `describe.skip("name", ...);`.
* - Used as `@test @pending method` is `it("name");`
*/
function pending(target, propertyKey) {
if (arguments.length === 1) {
target[pendingSymbol] = true;
}
else {
target[propertyKey][pendingSymbol] = true;
}
}
exports.pending = pending;
/**
* Mark a test or suite as the only one to execute.
* - Used as `@suite @only class` is `describe.only("name", ...)`.
* - Used as `@test @only method` is `it.only("name", ...)`.
*/
function only(target, propertyKey) {
if (arguments.length === 1) {
target[onlySymbol] = true;
}
else {
target[propertyKey][onlySymbol] = true;
}
}
exports.only = only;
/**
* Mark a test or suite to skip.
* - Used as `@suite @skip class` is `describe.skip("name", ...);`.
* - Used as `@test @skip method` is `it.skip("name")`.
*/
function skip(target, propertyKey) {
if (arguments.length === 1) {
target[onlySymbol] = true;
}
else {
target[propertyKey][skipSymbol] = true;
}
}
exports.skip = skip;
/**
* Mark a method as test. Use the method name as test name.
*/
function context(target, propertyKey) {
target[contextSymbol] = propertyKey;
}
exports.context = context;
/**
* Rip-off the TDD and BDD at: mocha/lib/interfaces/tdd.js and mocha/lib/interfaces/bdd.js
* Augmented the suite and test for the mocha-typescript decorators.
*/
function tsdd(suite) {
const suites = [suite];
suite.on("pre-require", function (context, file, mocha) {
const common = Common(suites, context, mocha);
context.before = common.before;
context.after = common.after;
context.beforeEach = common.beforeEach;
context.afterEach = common.afterEach;
context.run = mocha.options.delay && common.runWithSuite(suite);
// Copy of bdd
context.describe = context.context = function (title, fn) {
return common.suite.create({
title,
file,
fn,
});
};
context.xdescribe = context.xcontext = context.describe.skip = function (title, fn) {
return common.suite.skip({
title,
file,
fn,
});
};
context.describe.only = function (title, fn) {
return common.suite.only({
title,
file,
fn,
});
};
context.it = context.specify = function (title, fn) {
const suite = suites[0];
if (suite.isPending()) {
fn = null;
}
const test = new Test(title, fn);
test.file = file;
suite.addTest(test);
return test;
};
context.it.only = function (title, fn) {
return common.test.only(mocha, context.it(title, fn));
};
context.xit = context.xspecify = context.it.skip = function (title) {
context.it(title);
};
context.it.retries = function (n) {
context.retries(n);
};
context.suite = makeSuiteObject(context);
context.params = makeParamsObject(context);
context.test = makeTestObject(context);
context.test.retries = common.test.retries;
context.timeout = timeout;
context.slow = slow;
context.retries = retries;
context.skipOnError = exports.skipOnError;
});
}
const defaultDependencyInjectionSystem = {
handles() { return true; },
create(cls) {
return new cls();
},
};
const dependencyInjectionSystems = [defaultDependencyInjectionSystem];
function getInstance(testClass) {
const di = dependencyInjectionSystems.find((di) => di.handles(testClass));
return di.create(testClass);
}
/**
* Register a dependency injection system.
*/
function registerDI(instantiator) {
// Maybe check if it is not already added?
if (dependencyInjectionSystems.some((di) => di === instantiator)) {
return false;
}
dependencyInjectionSystems.unshift(instantiator);
return true;
}
exports.registerDI = registerDI;
module.exports = Object.assign(tsdd, exports);
Mocha.interfaces["mocha-typescript"] = tsdd;
//# sourceMappingURL=index.js.map