UNPKG

mocha-typescript

Version:

TypeScript decorators based wrapper over mocha's interface

654 lines 24.4 kB
"use strict"; 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