fancy-test
Version:
extendable utilities for testing
140 lines (139 loc) • 5.02 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const _ = require("lodash");
const context = {
test: it,
plugins: {},
chain: [],
};
function assignWithProps(target, ...sources) {
sources.forEach(source => {
if (!source)
return;
const descriptors = Object.keys(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
return descriptors;
}, {});
// by default, Object.assign copies enumerable Symbols too
Object.getOwnPropertySymbols(source).forEach(sym => {
const descriptor = Object.getOwnPropertyDescriptor(source, sym);
if (descriptor.enumerable) {
descriptors[sym] = descriptor;
}
});
Object.defineProperties(target, descriptors);
});
return target;
}
const base = (context) => {
const end = (arg1, cb) => {
const originalContext = context;
if (_.isFunction(arg1)) {
cb = arg1;
arg1 = undefined;
}
if (!arg1)
arg1 = context.expectation || 'test';
async function run(done) {
context = assignWithProps({}, originalContext);
if (context.retries)
this.retries(context.retries);
if (cb) {
context.chain = [...context.chain, {
run: async (input) => {
await cb.call(this, input, done);
},
}];
}
for (let i = 0; i < context.chain.length; i++) {
const handleError = async (err) => {
context.error = err;
i++;
const handler = context.chain[i];
if (!handler || !handler.catch)
return false;
try {
await handler.catch(context);
delete context.error;
return true;
}
catch (error) {
return handleError(error);
}
};
const next = context.chain[i];
try {
// eslint-disable-next-line no-await-in-loop
if (next.run)
await next.run(context);
}
catch (error) {
// eslint-disable-next-line no-await-in-loop
if (!await handleError(error))
break;
}
}
for (const p of context.chain.reverse()) {
// eslint-disable-next-line no-await-in-loop
if (p.finally)
await p.finally(context);
}
if (context.error)
throw context.error;
}
return context.test(arg1, (cb && cb.length === 2) ? function (done) {
if (context.timeout)
this.timeout(context.timeout);
run.call(this, done).catch(done);
} : function () {
if (context.timeout)
this.timeout(context.timeout);
return run.call(this);
});
};
return Object.assign(Object.assign({}, Object.entries(context.plugins)
.reduce((plugins, [k, v]) => {
plugins[k] = (...args) => {
const plugin = v(...args);
// clone context first
const c = Object.assign({}, context);
if (plugin.init)
plugin.init(c);
return base(Object.assign(Object.assign({}, c), { chain: [...c.chain, plugin] }));
};
return plugins;
}, {})), { register(k, v) {
return base(Object.assign(Object.assign({}, context), { plugins: Object.assign(Object.assign({}, context.plugins), { [k]: v }) }));
},
do(cb) {
return base(Object.assign(Object.assign({}, context), { chain: [...context.chain, { run: (input) => cb(input) }] }));
},
finally(cb) {
return base(Object.assign(Object.assign({}, context), { chain: [...context.chain, { finally: (input) => cb(input) }] }));
},
add(key, v) {
return base(Object.assign(Object.assign({}, context), { chain: [...context.chain, {
run: async (ctx) => {
// eslint-disable-next-line require-atomic-updates
ctx[key] = await (_.isFunction(v) ? v(ctx) : v);
},
}] }));
},
end, it: end });
};
exports.default = base(context)
.register('skip', () => ({
init: ctx => {
ctx.test = it.skip;
},
}))
.register('only', () => ({
init: ctx => {
ctx.test = it.only;
},
}))
.register('retries', (count) => ({
init: ctx => {
ctx.retries = count;
},
}));
;