@pipobscure/demitasse
Version:
Miniscule BDD Testing Framework
560 lines (559 loc) • 23.8 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var output = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return console.log.apply(console, args);
};
var logout = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return console.error.apply(console, args);
};
var reporter = function () { return new TAPReporter(); };
var autorun = true;
var itemtimeout = 2500;
var print = false;
var summary = false;
var success = true;
var Cancelable = /** @class */ (function (_super) {
__extends(Cancelable, _super);
function Cancelable() {
return _super !== null && _super.apply(this, arguments) || this;
}
Cancelable.prototype.cancel = function () { };
return Cancelable;
}(Promise));
var sleep = function (ms) {
var later = {};
later.promise = new Promise(function (resolve) {
later.resolve = resolve;
});
later.timer = setTimeout(function () {
later.timer = null;
later.resolve && later.resolve();
}, ms);
later.timer.unref();
later.cancel = function () {
later.timer && clearTimeout(later.timer);
later.timer = null;
later.resolve && later.resolve();
};
later.promise.cancel = later.cancel;
return later.promise;
};
var AssertionError = /** @class */ (function (_super) {
__extends(AssertionError, _super);
function AssertionError() {
return _super !== null && _super.apply(this, arguments) || this;
}
return AssertionError;
}(Error));
exports.AssertionError = AssertionError;
var TAPReporter = /** @class */ (function () {
function TAPReporter() {
this.stack = [];
this.summary = { passed: 0, failed: 0, skipped: 0 };
this.bailed = false;
this.indent = '';
this.indent = '';
output('TAP version 13');
}
TAPReporter.prototype.case = function (item) { };
TAPReporter.prototype.report = function (item, status, error) {
var indent = this.indent + new Array(Math.max(0, this.stack.length - 1)).fill(' ').join('');
if (!this.bailed && this.stack.length && !!~this.stack[0].items.indexOf(item)) {
switch (status) {
case 'ok':
this.summary.passed += 1;
break;
case 'not ok':
this.summary.failed += 1;
break;
case 'skipped':
this.summary.skipped += 1;
break;
}
var cur = this.stack[0];
cur.count += 1;
output("" + indent + (status === 'skipped' ? 'ok' : status) + " " + cur.count + " - " + item.label + (status === 'skipped' ? ' #skip' : '') + (error && error.code === 'ERR_TIMEOUT' ? ' #timeout' : ''));
}
if (error) {
switch (true) {
case error.code === 'ERR_TIMEOUT':
break;
case error.code === 'ERR_ASSERTION': {
output(indent + " ---");
output(indent + " message: " + error.message);
output(indent + " expected: " + JSON.stringify(error.expected));
output(indent + " actual: " + JSON.stringify(error.actual));
output(indent + " ...");
break;
}
case !!error.stack: {
for (var _i = 0, _a = (error.stack || '').split(/\r?\n/).slice(0, 4); _i < _a.length; _i++) {
var line = _a[_i];
if (/^\s+at\s+TestCase.execute\s+\(\S+?index.js\:\d+:\d+\)$/.test(line))
break;
output(indent + "# " + line);
}
break;
}
default: {
output(indent + " ---");
for (var _b = 0, _c = Object.keys(error); _b < _c.length; _b++) {
var prop = _c[_b];
//@ts-ignore
output(indent + " " + prop + ": " + JSON.stringify(error[prop]));
}
output(indent + " ...");
}
}
}
};
TAPReporter.prototype.enter = function (item) {
var items = Array.from(item);
var indent = this.indent + new Array(Math.max(0, this.stack.length - 1)).fill(' ').join('');
output(indent + "# " + item.label);
this.stack.unshift({ item: item, count: 0, items: items });
indent = this.indent + new Array(this.stack.length - 1).fill(' ').join('');
output(indent + "1.." + items.length);
};
TAPReporter.prototype.exit = function (item) {
if (this.stack[0].item !== item)
throw new Error("invalid invokation. non matching item \"" + item.label + "\" !== \"" + this.stack[0].item.label + "\"");
this.stack.shift();
if (!this.stack.length) {
if (this.bailed) {
output('Bail Out!');
if ('undefined' !== typeof process) {
process.exit(3);
}
else {
throw new Error('Bail Out!');
}
}
if (summary && !this.indent.length) {
var total = this.summary.passed + this.summary.failed + this.summary.skipped;
output("# Tests: " + total);
output("# Skipped: " + this.summary.skipped);
output("# Passed: " + this.summary.passed);
output("# Failed: " + this.summary.failed);
}
}
};
return TAPReporter;
}());
exports.TAPReporter = TAPReporter;
var TestCase = /** @class */ (function () {
function TestCase(label, runner) {
this.skipped = false;
this.label = label;
this.run = runner;
this.skipped = false;
this.timeout = itemtimeout;
}
TestCase.prototype.execute = function (context) {
return __awaiter(this, void 0, void 0, function () {
var timer, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (context.bailed)
return [2 /*return*/, false];
if (this.skipped) {
context.report(this, 'skipped');
return [2 /*return*/, true];
}
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
timer = sleep(this.timeout);
return [4 /*yield*/, Promise.race([
this.run(),
timer.then(function () {
var error = new Error('TIMEOUT');
//@ts-ignore
error.code = 'ERR_TIMEOUT';
throw error;
})
])];
case 2:
_a.sent();
timer.cancel();
return [3 /*break*/, 4];
case 3:
error_1 = _a.sent();
context.report(this, 'not ok', error_1);
return [2 /*return*/, false];
case 4:
context.report(this, 'ok');
return [2 /*return*/, true];
}
});
});
};
TestCase.prototype.propagateSkip = function (skipped) {
this.skipped = this.skipped || skipped;
};
TestCase.prototype.print = function (indent) {
if (indent === void 0) { indent = ''; }
output("" + indent + this.label + " " + (this.skipped ? ' # skip' : ' # run'));
};
return TestCase;
}());
var TestSuite = /** @class */ (function (_super) {
__extends(TestSuite, _super);
function TestSuite(label) {
var _this = _super.call(this, label, function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
return [2 /*return*/];
}); }); }) || this;
_this._before = [];
_this._beforeEach = [];
_this._test = [];
_this._afterEach = [];
_this._after = [];
_this._only = null;
return _this;
}
TestSuite.prototype.before = function (item) {
this._before.push(item);
};
TestSuite.prototype.beforeEach = function (item) {
this._beforeEach.push(item);
};
TestSuite.prototype.test = function (item) {
this._test.push(item);
item.skipped = item.skipped || !!(this._only && this._only !== item);
};
TestSuite.prototype.afterEach = function (item) {
this._afterEach.push(item);
};
TestSuite.prototype.after = function (item) {
this._after.push(item);
};
TestSuite.prototype.only = function (item) {
if (this._only)
throw new Error("invalid invocation: only already used for \"" + this._only.label + "\"");
if (!this._test.includes(item))
throw new Error("invalid invocation: only not added \"" + item.label + "\"");
this._only = item;
for (var _i = 0, _a = this._test; _i < _a.length; _i++) {
var item_1 = _a[_i];
item_1.skipped = item_1.skipped || !!(this._only && this._only !== item_1);
}
};
TestSuite.prototype.propagateSkip = function (skipped) {
this.skipped = this.skipped || skipped;
for (var _i = 0, _a = this._test; _i < _a.length; _i++) {
var item = _a[_i];
item.propagateSkip(this.skipped);
}
};
TestSuite.prototype.executeList = function (context, list) {
return __awaiter(this, void 0, void 0, function () {
var _i, list_1, item;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_i = 0, list_1 = list;
_a.label = 1;
case 1:
if (!(_i < list_1.length)) return [3 /*break*/, 4];
item = list_1[_i];
return [4 /*yield*/, item.execute(context)];
case 2:
if (!(_a.sent())) {
return [2 /*return*/, false];
}
_a.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4: return [2 /*return*/, true];
}
});
});
};
TestSuite.prototype.execute = function (context) {
return __awaiter(this, void 0, void 0, function () {
var state, _a, _i, _b, item, _c, _d;
return __generator(this, function (_e) {
switch (_e.label) {
case 0:
if (context.bailed)
return [2 /*return*/, false];
state = true;
context.enter(this);
_a = !this.skipped;
if (!_a) return [3 /*break*/, 2];
return [4 /*yield*/, this.executeList(context, this._before)];
case 1:
_a = !(_e.sent());
_e.label = 2;
case 2:
if (_a) {
context.bailed = true;
state = false;
}
if (!!context.bailed) return [3 /*break*/, 10];
_i = 0, _b = this._test;
_e.label = 3;
case 3:
if (!(_i < _b.length)) return [3 /*break*/, 10];
item = _b[_i];
_c = !item.skipped;
if (!_c) return [3 /*break*/, 5];
return [4 /*yield*/, this.executeList(context, this._beforeEach)];
case 4:
_c = !(_e.sent());
_e.label = 5;
case 5:
if (_c) {
context.bailed = true;
return [3 /*break*/, 9];
}
return [4 /*yield*/, item.execute(context)];
case 6:
if (!(_e.sent())) {
state = false;
}
_d = !item.skipped;
if (!_d) return [3 /*break*/, 8];
return [4 /*yield*/, this.executeList(context, this._afterEach)];
case 7:
_d = !(_e.sent());
_e.label = 8;
case 8:
if (_d) {
context.bailed = true;
return [3 /*break*/, 9];
}
_e.label = 9;
case 9:
_i++;
return [3 /*break*/, 3];
case 10:
if (!(!this.skipped && !context.bailed)) return [3 /*break*/, 12];
return [4 /*yield*/, this.executeList(context, this._after)];
case 11:
if (!(_e.sent())) {
context.bailed = true;
state = false;
}
_e.label = 12;
case 12:
context.exit(this);
if (this.skipped) {
context.report(this, 'skipped');
return [2 /*return*/, true];
}
else {
context.report(this, state ? 'ok' : 'not ok');
return [2 /*return*/, state];
}
return [2 /*return*/];
}
});
});
};
TestSuite.prototype.print = function (indent) {
if (indent === void 0) { indent = ''; }
output("" + indent + this.label + " " + (this.skipped ? ' # skip' : ' # run'));
for (var _i = 0, _a = this._test; _i < _a.length; _i++) {
var item = _a[_i];
item.print(indent + " ");
}
};
TestSuite.prototype[Symbol.iterator] = function () {
return this._test[Symbol.iterator]();
};
return TestSuite;
}(TestCase));
var root = [];
var stack = null;
function configure(options) {
if (options === void 0) { options = {}; }
autorun = options.autorun !== undefined ? !!options.autorun : autorun;
output = options.output !== undefined ? options.output : output;
logout = options.logout !== undefined ? options.logout : logout;
reporter = options.reporter !== undefined ? options.reporter : reporter;
itemtimeout = options.timeout !== undefined ? options.timeout : itemtimeout;
print = options.print !== undefined ? !!options.print : print;
summary = options.summary !== undefined ? !!options.summary : summary;
sleep = options.sleep !== undefined ? options.sleep : sleep;
}
exports.configure = configure;
function describe(label, runner) {
if (autorun && stack && !stack.length)
throw new Error('only 1 top-level describe allowed');
var item = new TestSuite(label);
if (!stack || !stack.length)
root.push(item);
stack = stack || [];
var cur = stack[0];
cur && cur.test(item);
stack.unshift(item);
runner();
stack.shift();
if (autorun && !stack.length) {
item.propagateSkip(false);
if (print) {
item.print();
}
else {
var context_1 = reporter();
item
.execute(context_1)
.then(function (result) {
success = !!(!context_1.bailed && success && result);
})
.catch(function (error) {
logout(error.stack);
});
}
}
var result = {
skip: function () { return ((item.skipped = true), result); },
todo: function () { return ((item.skipped = true), (item.label += ' # todo'), result); },
only: function () { return (cur && cur.only(item), result); }
};
return result;
}
exports.describe = describe;
describe.skip = function (label, runner) { return describe(label, runner).skip(); };
describe.todo = function (label, runner) { return describe(label + " # todo", runner).skip(); };
describe.only = function (label, runner) { return describe(label, runner).only(); };
function before(runner) {
if (!stack || !stack.length)
throw new TypeError('invalid invokation. missing context');
stack[0].before(new TestCase('before', runner));
}
exports.before = before;
function beforeEach(runner) {
if (!stack || !stack.length)
throw new TypeError('invalid invokation. missing context');
stack[0].beforeEach(new TestCase('beforeEach', runner));
}
exports.beforeEach = beforeEach;
function it(label, runner) {
var cur = stack && stack[0];
if (!cur)
throw new TypeError('invalid invokation. missing context');
var item = new TestCase(label, runner);
cur.test(item);
var result = {
skip: function () { return ((item.skipped = true), result); },
todo: function () { return ((item.skipped = true), (item.label += ' # todo'), result); },
only: function () { return (cur.only(item), result); },
timeout: function (ms) { return ((item.timeout = ms), result); }
};
return result;
}
exports.it = it;
it.skip = function (label, runner) { return it(label, runner).skip(); };
it.todo = function (label, runner) { return it(label + " # todo", runner).skip(); };
it.only = function (label, runner) { return it(label, runner).only(); };
exports.timeout = (it.timeout = function (label, ms, runner) { return it(label, runner).timeout(ms); });
function afterEach(runner) {
if (!stack || !stack.length)
throw new TypeError('invalid invokation. missing context');
stack[0].afterEach(new TestCase('afterEach', runner));
}
exports.afterEach = afterEach;
function after(runner) {
if (!stack || !stack.length)
throw new TypeError('invalid invokation. missing context');
stack[0].after(new TestCase('after', runner));
}
exports.after = after;
function main() {
if (autorun)
throw new Error('this test is meant to autorun');
if (!root.length)
throw new Error('no tests defined');
var suite = new TestSuite('');
root.forEach(function (item) { return suite.test(item); });
var context = reporter();
suite.propagateSkip(false);
return suite
.execute(context)
.then(function (result) {
return (success = !!(!context.bailed && success && result));
})
.catch(function (error) {
logout(error.stack);
return false;
});
}
exports.main = main;
function skeleton(walker, items) {
if (items === void 0) { items = root; }
for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
var item = items_1[_i];
if (item instanceof TestSuite) {
walker.enter(item);
skeleton(walker, Array.from(item));
walker.exit(item);
}
else {
walker.case(item);
}
}
}
exports.skeleton = skeleton;
if ('undefined' !== typeof process)
process.on('beforeExit', function () { return process.exit(success ? 0 : 1); });