japa
Version:
Lean test runner for Node.js
312 lines (311 loc) • 9.36 kB
JavaScript
"use strict";
/**
* @module Core
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Group = void 0;
/*
* japa
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
const Hook_1 = require("../Hook");
const Test_1 = require("../Test");
const Emitter_1 = require("../Emitter");
const Contracts_1 = require("../Contracts");
/**
* Group holds `n` number of tests to be executed. Groups also allows
* defining hooks to be called before and after each test and the
* group itself.
*/
class Group {
constructor(title, _resolveTestFn, _resolveHookFn, _options) {
this.title = title;
this._resolveTestFn = _resolveTestFn;
this._resolveHookFn = _resolveHookFn;
this._options = _options;
this._hooks = {
before: [],
after: [],
beforeEach: [],
afterEach: [],
};
/**
* The test error (if any)
*/
this._error = null;
/**
* Has test been executed
*/
this._completed = false;
/**
* An array of tests related to the group. They are mutated by the
* run method to filter and keep only the one's that matches
* the grep filter.
*/
this._tests = [];
/**
* Storing whether the group has any failing tests or
* not.
*/
this._hasFailingTests = false;
/**
* Is there a cherry picked test using the `only` property
* or not?
*/
this._hasCherryPickedTest = false;
}
/**
* Returns a boolean telling if group or any of the tests inside
* the group has errors.
*/
get hasErrors() {
return this._hasFailingTests || !!this._error;
}
/**
* Filter tests if grep value is defined
*/
_filterTests() {
if (!this._options.grep) {
return;
}
const filteredTests = this._tests.filter((test) => this._options.grep.test(test.title));
this._tests = filteredTests;
}
/**
* Run a hook and if it raises error, then we will
* set the completed flag to true, along with the
* error.
*/
async _runHook(fn) {
try {
await fn.run();
}
catch (error) {
this._completed = true;
this._error = error;
}
}
/**
* Runs a single test along side with it's hooks.
*/
async _runTest(test) {
/**
* Run beforeEach hooks
*/
for (let hook of this._hooks.beforeEach) {
if (this._completed) {
break;
}
await this._runHook(hook);
}
/**
* Return early if completed is set to true (happens when any hook throws error)
*/
if (this._completed) {
return;
}
/**
* Otherwise run the test
*/
await test.run();
/**
* Setting flag to true when any one test has failed. This helps
* in telling runner to exit process with the correct status.
*/
const testFailed = test.toJSON().status === Contracts_1.ITestStatus.FAILED;
if (!this._hasFailingTests && testFailed) {
this._hasFailingTests = true;
}
/**
* Mark group as completed when bail is set to true and
* test has failed
*/
if (this._options.bail && testFailed) {
this._completed = true;
return;
}
/**
* Run all after each hooks
*/
for (let hook of this._hooks.afterEach) {
if (this._completed) {
break;
}
await this._runHook(hook);
}
}
/**
* Runs all the tests one by one and also executes
* the beforeEach and afterEach hooks
*/
async _runTests() {
/**
* Run all the tests in sequence. If any hook beforeEach or afterEach
* hook fails, it will set `complete = true` and then we break out
* of the loop, since if hooks are failing, then there is no
* point is running tests.
*/
for (let test of this._tests) {
if (this._completed) {
break;
}
await this._runTest(test);
}
}
/**
* Returns the JSON report for the group. The output of this
* method is emitted as an event.
*/
toJSON() {
let status = Contracts_1.IGroupStatus.PENDING;
if (this._completed && this._error) {
status = Contracts_1.IGroupStatus.FAILED;
}
else if (this._completed) {
status = Contracts_1.IGroupStatus.PASSED;
}
return {
title: this.title,
status: status,
error: this._error,
};
}
/**
* Define timeout for all the tests inside the group. Still
* each test can override it's own timeout.
*/
timeout(duration) {
if (typeof (duration) !== 'number') {
throw new Error('"group.timeout" expects a valid integer');
}
if (this._tests.length) {
throw new Error('group.timeout must be called before defining the tests');
}
this._timeout = duration;
return this;
}
/**
* Create a new test as part of this group.
*/
test(title, callback, testOptions) {
if (!title.trim()) {
throw new Error('test title cannot be empty');
}
testOptions = Object.assign({
regression: false,
skip: false,
skipInCI: false,
runInCI: false,
}, testOptions);
/**
* Using group timeout as a priority over runner timeout
*/
testOptions.timeout = this._timeout !== undefined ? this._timeout : this._options.timeout;
const test = new Test_1.Test(title, this._resolveTestFn, callback, testOptions);
/**
* Do not track test when a test has been cherry picked earlier
*/
if (this._hasCherryPickedTest) {
return test;
}
/**
* Remove all existing tests, when a test has a `.only` property
* set to true.
*/
if (testOptions.only === true) {
this._hasCherryPickedTest = true;
this._tests = [];
}
this._tests.push(test);
return test;
}
/**
* Add before hook to be executed before the group starts
* executing tests.
*/
before(cb) {
if (typeof (cb) !== 'function') {
throw new Error('"group.before" expects callback to be a valid function');
}
this._hooks.before.push(new Hook_1.Hook(this._resolveHookFn, cb, 'before'));
return this;
}
/**
* Add after hook to be executed after the group has executed
* all the tests.
*/
after(cb) {
if (typeof (cb) !== 'function') {
throw new Error('"group.after" expects callback to be a valid function');
}
this._hooks.after.push(new Hook_1.Hook(this._resolveHookFn, cb, 'after'));
return this;
}
/**
* Add before each hook to be execute before each test
*/
beforeEach(cb) {
if (typeof (cb) !== 'function') {
throw new Error('"group.beforeEach" expects callback to be a valid function');
}
this._hooks.beforeEach.push(new Hook_1.Hook(this._resolveHookFn, cb, 'beforeEach'));
return this;
}
/**
* Add after each hook to be execute before each test
*/
afterEach(cb) {
if (typeof (cb) !== 'function') {
throw new Error('"group.afterEach" expects callback to be a valid function');
}
this._hooks.afterEach.push(new Hook_1.Hook(this._resolveHookFn, cb, 'afterEach'));
return this;
}
/**
* Run the group with it's hooks and all tests. Shouldn't be called
* by the end user and Japa itself will call this method
*/
async run() {
this._filterTests();
/**
* Return early when no tests are defined
*/
if (!this._tests.length) {
this._completed = true;
return;
}
Emitter_1.emitter.emit(Contracts_1.IEvents.GROUPSTARTED, this.toJSON());
/**
* Run all before hooks for the group
*/
for (let hook of this._hooks.before) {
if (this._completed) {
break;
}
await this._runHook(hook);
}
/**
* Run the tests, if complete flag is not set to true. It is
* set to true, when any before hooks fail
*/
if (!this._completed) {
await this._runTests();
}
/**
* Run all after hooks
*/
for (let hook of this._hooks.after) {
if (this._completed) {
break;
}
await this._runHook(hook);
}
this._completed = true;
Emitter_1.emitter.emit(Contracts_1.IEvents.GROUPCOMPLETED, this.toJSON());
}
}
exports.Group = Group;