This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+9
View File
@@ -0,0 +1,9 @@
# The MIT License
Copyright 2021 Harminder Virk, contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+26
View File
@@ -0,0 +1,26 @@
# @japa/core
> The core of the Japa tests runners
[![github-actions-image]][github-actions-url] [![npm-image]][npm-url] [![license-image]][license-url] [![typescript-image]][typescript-url]
This repo contains the code for the core of the japa tests runner. You can use it create your tests runner, just like `@japa/runner`.
You can view the documentation on [https://japa.dev](https://japa.dev)
[github-actions-image]: https://img.shields.io/github/actions/workflow/status/japa/core/test.yml?style=for-the-badge
[github-actions-url]: https://github.com/japa/core/actions/workflows/test.yml "github-actions"
[npm-image]: https://img.shields.io/npm/v/@japa/core.svg?style=for-the-badge&logo=npm
[npm-url]: https://npmjs.org/package/@japa/core "npm"
[license-image]: https://img.shields.io/npm/l/@japa/core?color=blueviolet&style=for-the-badge
[license-url]: LICENSE.md "license"
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
[typescript-url]: "typescript"
<br />
<hr>
![](https://raw.githubusercontent.com/thetutlage/static/main/sponsorkit/sponsors.png)
+9
View File
@@ -0,0 +1,9 @@
export * from './src/types';
export { Runner } from './src/runner';
export { Test } from './src/test/main';
export { Emitter } from './src/emitter';
export { Refiner } from './src/refiner';
export { Tracker } from './src/tracker';
export { Suite } from './src/suite/main';
export { Group } from './src/group/main';
export { TestContext } from './src/test_context';
+42
View File
@@ -0,0 +1,42 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestContext = exports.Group = exports.Suite = exports.Tracker = exports.Refiner = exports.Emitter = exports.Test = exports.Runner = void 0;
__exportStar(require("./src/types"), exports);
var runner_1 = require("./src/runner");
Object.defineProperty(exports, "Runner", { enumerable: true, get: function () { return runner_1.Runner; } });
var main_1 = require("./src/test/main");
Object.defineProperty(exports, "Test", { enumerable: true, get: function () { return main_1.Test; } });
var emitter_1 = require("./src/emitter");
Object.defineProperty(exports, "Emitter", { enumerable: true, get: function () { return emitter_1.Emitter; } });
var refiner_1 = require("./src/refiner");
Object.defineProperty(exports, "Refiner", { enumerable: true, get: function () { return refiner_1.Refiner; } });
var tracker_1 = require("./src/tracker");
Object.defineProperty(exports, "Tracker", { enumerable: true, get: function () { return tracker_1.Tracker; } });
var main_2 = require("./src/suite/main");
Object.defineProperty(exports, "Suite", { enumerable: true, get: function () { return main_2.Suite; } });
var main_3 = require("./src/group/main");
Object.defineProperty(exports, "Group", { enumerable: true, get: function () { return main_3.Group; } });
var test_context_1 = require("./src/test_context");
Object.defineProperty(exports, "TestContext", { enumerable: true, get: function () { return test_context_1.TestContext; } });
+3
View File
@@ -0,0 +1,3 @@
/// <reference types="node" />
declare const _default: import("util").DebugLogger;
export default _default;
+12
View File
@@ -0,0 +1,12 @@
"use strict";
/*
* @japa/core
*
* (c) Japa.dev
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("util");
exports.default = (0, util_1.debuglog)('japa:core');
+16
View File
@@ -0,0 +1,16 @@
import Emittery from 'emittery';
import { RunnerEvents } from './types';
/**
* Runner emitter
*/
export declare class Emitter extends Emittery<RunnerEvents> {
private errorHandler?;
/**
* Define onError handler invoked when `emit` fails
*/
onError(errorHandler: (error: any) => void | Promise<void>): void;
/**
* Emit event
*/
emit<Name extends keyof RunnerEvents>(eventName: Name, eventData?: RunnerEvents[Name], allowMetaEvents?: boolean): Promise<void>;
}
+43
View File
@@ -0,0 +1,43 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Emitter = void 0;
const emittery_1 = __importDefault(require("emittery"));
/**
* Runner emitter
*/
class Emitter extends emittery_1.default {
/**
* Define onError handler invoked when `emit` fails
*/
onError(errorHandler) {
this.errorHandler = errorHandler;
}
/**
* Emit event
*/
async emit(eventName, eventData, allowMetaEvents) {
try {
await super.emit(eventName, eventData, allowMetaEvents);
}
catch (error) {
if (this.errorHandler) {
await this.errorHandler(error);
}
else {
throw error;
}
}
}
}
exports.Emitter = Emitter;
+77
View File
@@ -0,0 +1,77 @@
import { Macroable } from 'macroable';
import { Test } from '../test/main';
import { Emitter } from '../emitter';
import { Refiner } from '../refiner';
import { GroupHooksHandler, TestHooksHandler, GroupOptions } from '../types';
/**
* Group class exposes an API to group multiple tests together
* and bulk configure them.
*
* NOTE: Nested groups are not supported on purpose.
*
* @example
* const group = new Group('addition', emitter, refiner)
* const test = new Test('2 + 2 = 4', emitter, refiner)
*
* group.add(test)
* await group.exec()
*/
export declare class Group<Context extends Record<any, any>> extends Macroable {
title: string;
private emitter;
private refiner;
static macros: {};
static getters: {};
/**
* Reference to registered hooks
*/
private hooks;
/**
* Callbacks to invoke on each test
*/
private tapsCallbacks;
/**
* Properties to configure on every test
*/
private testsTimeout?;
private testsRetries?;
private testSetupHooks;
private testTeardownHooks;
options: GroupOptions;
/**
* An array of tests registered under the given group
*/
tests: Test<Context, any>[];
/**
* Shortcut methods to configure tests
*/
each: {
setup: (handler: TestHooksHandler<Context>) => void;
teardown: (handler: TestHooksHandler<Context>) => void;
timeout: (timeout: number) => void;
retry: (retries: number) => void;
disableTimeout: () => void;
};
constructor(title: string, emitter: Emitter, refiner: Refiner);
/**
* Add a test to the group. Adding a test to the group
* mutates the test properties
*/
add(test: Test<Context, any>): this;
/**
* Tap into each test and configure it
*/
tap(callback: (test: Test<Context, any>) => void): this;
/**
* Define setup hook for the group
*/
setup(handler: GroupHooksHandler<Context>): this;
/**
* Define teardown hook for the group
*/
teardown(handler: GroupHooksHandler<Context>): this;
/**
* Execute group hooks and tests
*/
exec(): Promise<void>;
}
+156
View File
@@ -0,0 +1,156 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Group = void 0;
const macroable_1 = require("macroable");
const hooks_1 = require("@poppinss/hooks");
const debug_1 = __importDefault(require("../debug"));
const runner_1 = require("./runner");
/**
* Group class exposes an API to group multiple tests together
* and bulk configure them.
*
* NOTE: Nested groups are not supported on purpose.
*
* @example
* const group = new Group('addition', emitter, refiner)
* const test = new Test('2 + 2 = 4', emitter, refiner)
*
* group.add(test)
* await group.exec()
*/
class Group extends macroable_1.Macroable {
constructor(title, emitter, refiner) {
super();
this.title = title;
this.emitter = emitter;
this.refiner = refiner;
/**
* Reference to registered hooks
*/
this.hooks = new hooks_1.Hooks();
/**
* Callbacks to invoke on each test
*/
this.tapsCallbacks = [];
this.testSetupHooks = [];
this.testTeardownHooks = [];
this.options = {
title: this.title,
meta: {},
};
/**
* An array of tests registered under the given group
*/
this.tests = [];
/**
* Shortcut methods to configure tests
*/
this.each = {
/**
* Define setup hook for all tests inside the group
*/
setup: (handler) => {
this.testSetupHooks.push(handler);
},
/**
* Define teardown hook for all tests inside the group
*/
teardown: (handler) => {
this.testTeardownHooks.push(handler);
},
/**
* Define timeout for all tests inside the group
*/
timeout: (timeout) => {
this.testsTimeout = timeout;
},
/**
* Disable timeout for all tests inside the group
*/
disableTimeout: () => {
this.testsTimeout = 0;
},
/**
* Define retries for all tests inside the group
*/
retry: (retries) => {
this.testsRetries = retries;
},
};
}
/**
* Add a test to the group. Adding a test to the group
* mutates the test properties
*/
add(test) {
(0, debug_1.default)('adding "%s" test to "%s" group', test.title, this.title);
/**
* Bulk configure
*/
if (this.testsTimeout !== undefined) {
test.timeout(this.testsTimeout);
}
if (this.testsRetries !== undefined) {
test.retry(this.testsRetries);
}
if (this.testSetupHooks.length) {
this.testSetupHooks.forEach((handler) => test.setup(handler));
}
if (this.testTeardownHooks.length) {
this.testTeardownHooks.forEach((handler) => test.teardown(handler));
}
/**
* Invoke tap callback passing test to each callback
*/
this.tapsCallbacks.forEach((callback) => callback(test));
this.tests.push(test);
return this;
}
/**
* Tap into each test and configure it
*/
tap(callback) {
this.tapsCallbacks.push(callback);
return this;
}
/**
* Define setup hook for the group
*/
setup(handler) {
(0, debug_1.default)('registering "%s" group setup hook %s', this.title, handler);
this.hooks.add('setup', handler);
return this;
}
/**
* Define teardown hook for the group
*/
teardown(handler) {
(0, debug_1.default)('registering "%s" group teardown hook %s', this.title, handler);
this.hooks.add('teardown', handler);
return this;
}
/**
* Execute group hooks and tests
*/
async exec() {
if (!this.refiner.allows(this)) {
(0, debug_1.default)('group skipped by refined %s', this.title);
return;
}
await new runner_1.GroupRunner(this, this.hooks, this.emitter).run();
}
}
exports.Group = Group;
Group.macros = {};
Group.getters = {};
+56
View File
@@ -0,0 +1,56 @@
import { Hooks } from '@poppinss/hooks';
import { Group } from './main';
import { Emitter } from '../emitter';
/**
* Run all tests for a given group
*/
export declare class GroupRunner {
private group;
private hooks;
private emitter;
/**
* Reference to the startup runner
*/
private setupRunner;
/**
* Reference to the cleanup runner
*/
private teardownRunner;
/**
* Test errors
*/
private errors;
/**
* Track if test has any errors
*/
private hasError;
constructor(group: Group<any>, hooks: Hooks, emitter: Emitter);
/**
* Notify the reporter about the group start
*/
private notifyStart;
/**
* Notify the reporter about the group end
*/
private notifyEnd;
/**
* Running setup hooks
*/
private runSetupHooks;
/**
* Running teardown hooks
*/
private runTeardownHooks;
/**
* Running setup cleanup functions
*/
private runSetupCleanupFunctions;
/**
* Running teardown cleanup functions
*/
private runTeardownCleanupFunctions;
/**
* Run the test
*/
run(): Promise<void>;
}
+152
View File
@@ -0,0 +1,152 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GroupRunner = void 0;
const debug_1 = __importDefault(require("../debug"));
/**
* Run all tests for a given group
*/
class GroupRunner {
constructor(group, hooks, emitter) {
this.group = group;
this.hooks = hooks;
this.emitter = emitter;
/**
* Reference to the startup runner
*/
this.setupRunner = this.hooks.runner('setup');
/**
* Reference to the cleanup runner
*/
this.teardownRunner = this.hooks.runner('teardown');
/**
* Test errors
*/
this.errors = [];
/**
* Track if test has any errors
*/
this.hasError = false;
}
/**
* Notify the reporter about the group start
*/
notifyStart() {
const startOptions = { ...this.group.options };
this.emitter.emit('group:start', startOptions);
}
/**
* Notify the reporter about the group end
*/
notifyEnd() {
const endOptions = {
...this.group.options,
hasError: this.hasError,
errors: this.errors,
};
this.emitter.emit('group:end', endOptions);
}
/**
* Running setup hooks
*/
async runSetupHooks() {
try {
(0, debug_1.default)('running "%s" group setup hooks', this.group.title);
await this.setupRunner.run(this.group);
}
catch (error) {
(0, debug_1.default)('group setup hooks failed, group: %s, error: %O', this.group.title, error);
this.hasError = true;
this.errors.push({ phase: 'setup', error });
}
}
/**
* Running teardown hooks
*/
async runTeardownHooks() {
try {
(0, debug_1.default)('running "%s" group teardown hooks', this.group.title);
await this.teardownRunner.run(this.group);
}
catch (error) {
(0, debug_1.default)('group teardown hooks failed, group: %s, error: %O', this.group.title, error);
this.hasError = true;
this.errors.push({ phase: 'teardown', error });
}
}
/**
* Running setup cleanup functions
*/
async runSetupCleanupFunctions() {
try {
(0, debug_1.default)('running "%s" group setup cleanup functions', this.group.title);
await this.setupRunner.cleanup(this.hasError, this.group);
}
catch (error) {
(0, debug_1.default)('group setup cleanup function failed, group: %s, error: %O', this.group.title, error);
this.hasError = true;
this.errors.push({ phase: 'setup:cleanup', error });
}
}
/**
* Running teardown cleanup functions
*/
async runTeardownCleanupFunctions() {
try {
(0, debug_1.default)('running "%s" group teardown cleanup functions', this.group.title);
await this.teardownRunner.cleanup(this.hasError, this.group);
}
catch (error) {
(0, debug_1.default)('group teardown cleanup function failed, group: %s, error: %O', this.group.title, error);
this.hasError = true;
this.errors.push({ phase: 'teardown:cleanup', error });
}
}
/**
* Run the test
*/
async run() {
(0, debug_1.default)('starting to run "%s" group', this.group.title);
this.notifyStart();
/**
* Run setup hooks and exit early when one of the hooks
* fails
*/
await this.runSetupHooks();
if (this.hasError) {
await this.runSetupCleanupFunctions();
this.notifyEnd();
return;
}
/**
* Run the test executor
*/
for (let test of this.group.tests) {
await test.exec();
}
/**
* Cleanup setup hooks
*/
await this.runSetupCleanupFunctions();
/**
* Run + cleanup teardown hooks
*/
await this.runTeardownHooks();
await this.runTeardownCleanupFunctions();
/**
* Notify test end
*/
this.notifyEnd();
}
}
exports.GroupRunner = GroupRunner;
+7
View File
@@ -0,0 +1,7 @@
/**
* A simple function interpolate values inside curly braces.
*
* @example
* interpolate('hello { username }', { username: 'virk' })
*/
export declare function interpolate(input: string, data: any, index: number): string;
+53
View File
@@ -0,0 +1,53 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.interpolate = void 0;
function uncurryThis(fn) {
return function (...args) {
return Function.call.apply(fn, args);
};
}
const hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
/**
* Parses prop
*/
function parseProp(data, key) {
const tokens = key.split('.');
while (tokens.length) {
if (data === null || typeof data !== 'object') {
return;
}
const token = tokens.shift();
data = hasOwnProperty(data, token) ? data[token] : undefined;
}
return data;
}
/**
* A simple function interpolate values inside curly braces.
*
* @example
* interpolate('hello { username }', { username: 'virk' })
*/
function interpolate(input, data, index) {
return input.replace(/(\\)?{(.*?)}/g, (_, escapeChar, key) => {
if (escapeChar) {
return `{${key}}`;
}
key = key.trim();
if (key === '$i') {
return index;
}
if (key === '$self') {
return data;
}
return parseProp(data, key);
});
}
exports.interpolate = interpolate;
+63
View File
@@ -0,0 +1,63 @@
import { Test } from './test/main';
import { Group } from './group/main';
import { FilteringOptions } from './types';
/**
* Exposes the API to refine unwanted tests based upon applied
* filters.
*
* @example
* const refiner = new Refiner({ tags: ['@slow'] })
* refiner.allows('tags', ['@slow']) // true
* refiner.allows('tags', ['@regression']) // false
*
* const refiner = new Refiner({ tags: [] })
* refiner.allows('tags', ['@slow']) // true
* refiner.allows('tags', ['@regression']) // true
*/
export declare class Refiner {
/**
* A set of pinned tests
*/
private pinnedTests;
/**
* Available filters
*/
private filters;
constructor(filters?: FilteringOptions);
/**
* Find if the group is allowed to execute its tests.
*/
private isGroupAllowed;
/**
* Find if the test is allowed to be executed by checking
* for the test title filter
*/
private isTestTitleAllowed;
/**
* Find if test is allowed by the negated tags filter
*/
private allowedByNegatedTags;
/**
* Test if the test is allowed by the tags filter
*/
private allowedByTags;
private areTestTagsAllowed;
private isAllowedByPinnedTest;
/**
* Pin a test to be executed.
*/
pinTest(test: Test<any, any>): void;
/**
* Find if a test is pinned
*/
isPinned(test: Test<any, any>): boolean;
/**
* Add a filter
*/
add(layer: 'tests' | 'tags' | 'groups', values: string[]): void;
/**
* Check if refiner allows a specific test or group to run by looking
* at the applied filters
*/
allows(testOrGroup: Test<any, any> | Group<any>): boolean;
}
+204
View File
@@ -0,0 +1,204 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Refiner = void 0;
const main_1 = require("./group/main");
/**
* Exposes the API to refine unwanted tests based upon applied
* filters.
*
* @example
* const refiner = new Refiner({ tags: ['@slow'] })
* refiner.allows('tags', ['@slow']) // true
* refiner.allows('tags', ['@regression']) // false
*
* const refiner = new Refiner({ tags: [] })
* refiner.allows('tags', ['@slow']) // true
* refiner.allows('tags', ['@regression']) // true
*/
class Refiner {
constructor(filters = {}) {
/**
* A set of pinned tests
*/
this.pinnedTests = new Set();
/**
* Available filters
*/
this.filters = {
tags: [],
tests: [],
groups: [],
negateTags: [],
};
if (filters.tags) {
this.add('tags', filters.tags);
}
if (filters.tests) {
this.add('tests', filters.tests);
}
if (filters.groups) {
this.add('groups', filters.groups);
}
}
/**
* Find if the group is allowed to execute its tests.
*/
isGroupAllowed(group) {
const groupFilters = this.filters.groups;
/**
* Group filters exists and group title is not within the filters
* list, then return false right away
*/
if (groupFilters.length && !groupFilters.includes(group.title)) {
return false;
}
/**
* By default the group is not allowed to be executed. However,
* we go through all the tests within that group and if
* one or more tests are allowed to run, then we will
* allow the group to run as well.
*
* Basically, we are checking the children to find if the group
* should run or not.
*/
let allowGroup = false;
for (let test of group.tests) {
allowGroup = this.allows(test);
if (allowGroup) {
break;
}
}
return allowGroup;
}
/**
* Find if the test is allowed to be executed by checking
* for the test title filter
*/
isTestTitleAllowed(test) {
/**
* All tests are allowed, when no filters are applied
* on the test title
*/
if (!this.filters.tests.length) {
return true;
}
return this.filters.tests.includes(test.title);
}
/**
* Find if test is allowed by the negated tags filter
*/
allowedByNegatedTags(test) {
if (!this.filters.negateTags.length) {
return true;
}
/**
* There should be zero matching negated tags
*/
return this.filters.negateTags.every((tag) => !test.options.tags.includes(tag));
}
/**
* Test if the test is allowed by the tags filter
*/
allowedByTags(test) {
if (!this.filters.tags.length) {
return true;
}
/**
* Find one or more matching tags
*/
return this.filters.tags.some((tag) => test.options.tags.includes(tag));
}
/*
* Find if the test is allowed to be executed by checking
* for the test tags
*/
areTestTagsAllowed(test) {
return this.allowedByTags(test) && this.allowedByNegatedTags(test);
}
/*
* Find if the test is allowed to be executed by checking
* for the pinned tests
*/
isAllowedByPinnedTest(test) {
/**
* All tests are allowed, when no tests are pinned
*/
if (!this.pinnedTests.size) {
return true;
}
return this.pinnedTests.has(test);
}
/**
* Pin a test to be executed.
*/
pinTest(test) {
this.pinnedTests.add(test);
}
/**
* Find if a test is pinned
*/
isPinned(test) {
return this.pinnedTests.has(test);
}
/**
* Add a filter
*/
add(layer, values) {
if (layer === 'tags') {
values.forEach((tag) => {
if (tag.startsWith('!')) {
this.filters.negateTags.push(tag.slice(1));
}
else {
this.filters.tags.push(tag);
}
});
}
else {
this.filters[layer].push(...values);
}
}
/**
* Check if refiner allows a specific test or group to run by looking
* at the applied filters
*/
allows(testOrGroup) {
if (testOrGroup instanceof main_1.Group) {
return this.isGroupAllowed(testOrGroup);
}
/**
* Do not run lone tests when group filter is applied. It is responsibility
* of the runner to attach groups to tests.
*/
if (this.filters.groups.length && !testOrGroup.parent) {
return false;
}
/**
* Layer 1
*/
const isTestTitleAllowed = this.isTestTitleAllowed(testOrGroup);
if (!isTestTitleAllowed) {
return false;
}
/**
* Layer 2
*/
const areTestTagsAllowed = this.areTestTagsAllowed(testOrGroup);
if (!areTestTagsAllowed) {
return false;
}
/**
* Layer 3
*/
return this.isAllowedByPinnedTest(testOrGroup);
}
}
exports.Refiner = Refiner;
+88
View File
@@ -0,0 +1,88 @@
import { Macroable } from 'macroable';
import { Suite } from './suite/main';
import { Emitter } from './emitter';
import { ReporterContract, RunnerSummary } from './types';
/**
* The Runner class exposes the API to register test suites and execute
* them sequentially.
*
* @example
* const runner = new Runner(emitter)
* const suite = new Suite('unit', emitter)
*
* runner.add(suite)
* runner.registerReporter(reporters.list)
*
* await runner.exec()
*/
export declare class Runner<Context extends Record<any, any>> extends Macroable {
private emitter;
static macros: {};
static getters: {};
/**
* Callbacks to invoke on every suite
*/
private configureSuiteCallbacks;
/**
* Reference to tests tracker
*/
private tracker;
/**
* Handler to listen for uncaughtException
*/
private uncaughtExceptionHandler?;
/**
* A collection of suites
*/
suites: Suite<Context>[];
/**
* Registered tests reporter
*/
reporters: Set<ReporterContract>;
constructor(emitter: Emitter);
/**
* Notify the reporter about the runner start
*/
private notifyStart;
/**
* Notify the reporter about the runner end
*/
private notifyEnd;
/**
* Boot the runner
*/
private boot;
/**
* Add a suite to the runner
*/
add(suite: Suite<Context>): this;
/**
* Tap into each suite and configure it
*/
onSuite(callback: (suite: Suite<Context>) => void): this;
/**
* Register a tests reporter
*/
registerReporter(reporter: ReporterContract): this;
/**
* Manage unhandled exceptions occurred during tests
*/
manageUnHandledExceptions(): this;
/**
* Get tests summary
*/
getSummary(): RunnerSummary;
/**
* Start the test runner process. The method emits
* "runner:start" event
*/
start(): Promise<void>;
/**
* Execute runner suites
*/
exec(): Promise<void>;
/**
* End the runner process. Emits "runner:end" event
*/
end(): Promise<void>;
}
+148
View File
@@ -0,0 +1,148 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Runner = void 0;
const macroable_1 = require("macroable");
const debug_1 = __importDefault(require("./debug"));
const tracker_1 = require("./tracker");
/**
* The Runner class exposes the API to register test suites and execute
* them sequentially.
*
* @example
* const runner = new Runner(emitter)
* const suite = new Suite('unit', emitter)
*
* runner.add(suite)
* runner.registerReporter(reporters.list)
*
* await runner.exec()
*/
class Runner extends macroable_1.Macroable {
constructor(emitter) {
super();
this.emitter = emitter;
/**
* Callbacks to invoke on every suite
*/
this.configureSuiteCallbacks = [];
/**
* A collection of suites
*/
this.suites = [];
/**
* Registered tests reporter
*/
this.reporters = new Set();
}
/**
* Notify the reporter about the runner start
*/
notifyStart() {
return this.emitter.emit('runner:start', {});
}
/**
* Notify the reporter about the runner end
*/
notifyEnd() {
return this.emitter.emit('runner:end', {});
}
/**
* Boot the runner
*/
boot() {
this.tracker = new tracker_1.Tracker();
this.emitter.on('uncaught:exception', (payload) => this.tracker.processEvent('uncaught:exception', payload));
this.emitter.on('runner:start', (payload) => this.tracker.processEvent('runner:start', payload));
this.emitter.on('runner:end', (payload) => this.tracker.processEvent('runner:end', payload));
this.emitter.on('suite:start', (payload) => this.tracker.processEvent('suite:start', payload));
this.emitter.on('suite:end', (payload) => this.tracker.processEvent('suite:end', payload));
this.emitter.on('group:start', (payload) => this.tracker.processEvent('group:start', payload));
this.emitter.on('group:end', (payload) => this.tracker.processEvent('group:end', payload));
this.emitter.on('test:start', (payload) => this.tracker.processEvent('test:start', payload));
this.emitter.on('test:end', (payload) => this.tracker.processEvent('test:end', payload));
}
/**
* Add a suite to the runner
*/
add(suite) {
this.configureSuiteCallbacks.forEach((callback) => callback(suite));
this.suites.push(suite);
(0, debug_1.default)('registering suite %s', suite.name);
return this;
}
/**
* Tap into each suite and configure it
*/
onSuite(callback) {
this.configureSuiteCallbacks.push(callback);
return this;
}
/**
* Register a tests reporter
*/
registerReporter(reporter) {
this.reporters.add(reporter);
return this;
}
/**
* Manage unhandled exceptions occurred during tests
*/
manageUnHandledExceptions() {
if (!this.uncaughtExceptionHandler) {
this.uncaughtExceptionHandler = (error) => this.emitter.emit('uncaught:exception', error);
process.on('uncaughtException', this.uncaughtExceptionHandler);
}
return this;
}
/**
* Get tests summary
*/
getSummary() {
return this.tracker.getSummary();
}
/**
* Start the test runner process. The method emits
* "runner:start" event
*/
async start() {
this.boot();
(0, debug_1.default)('starting to run tests');
for (let reporter of this.reporters) {
if (typeof reporter === 'function') {
await reporter(this, this.emitter);
}
else {
await reporter.handler(this, this.emitter);
}
}
await this.notifyStart();
}
/**
* Execute runner suites
*/
async exec() {
for (let suite of this.suites) {
await suite.exec();
}
}
/**
* End the runner process. Emits "runner:end" event
*/
async end() {
await this.notifyEnd();
}
}
exports.Runner = Runner;
Runner.macros = {};
Runner.getters = {};
+71
View File
@@ -0,0 +1,71 @@
import { Macroable } from 'macroable';
import { Emitter } from '../emitter';
import { Test } from '../test/main';
import { Refiner } from '../refiner';
import { Group } from '../group/main';
import { SuiteHooksHandler } from '../types';
/**
* The Suite class exposes the API to run a group of tests
* or independent tests together as part of a suite.
*
* You can think of suites as
* - unit tests suite
* - e2e tests suites
* - and so on
*
* @example
* const suite = new Suite('unit', emitter)
* const group = new Group('addition', emitter, refiner)
* const test = new Test('2 + 2 = 4', emitter, refiner)
*
* suite.add(group)
* group.add(test)
*
* // Runs all the tests inside the registered group
* await suite.exec()
*/
export declare class Suite<Context extends Record<any, any>> extends Macroable {
name: string;
private emitter;
private refiner;
static macros: {};
static getters: {};
/**
* Reference to registered hooks
*/
private hooks;
/**
* Callbacks to invoke on each test and group
*/
private configureTestCallbacks;
private configureGroupCallbacks;
/**
* A collection of tests and groups both
*/
stack: (Test<Context, any> | Group<Context>)[];
constructor(name: string, emitter: Emitter, refiner: Refiner);
/**
* Add a test or a group to the execution stack
*/
add(testOrGroup: Test<Context, any> | Group<Context>): this;
/**
* Tap into each test and configure it
*/
onTest(callback: (test: Test<Context, any>) => void): this;
/**
* Tap into each group and configure it
*/
onGroup(callback: (group: Group<Context>) => void): this;
/**
* Register a test setup function
*/
setup(handler: SuiteHooksHandler<Context>): this;
/**
* Register a test teardown function
*/
teardown(handler: SuiteHooksHandler<Context>): this;
/**
* Execute suite groups, tests and hooks
*/
exec(): Promise<void>;
}
+133
View File
@@ -0,0 +1,133 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Suite = void 0;
const macroable_1 = require("macroable");
const hooks_1 = require("@poppinss/hooks");
const debug_1 = __importDefault(require("../debug"));
const main_1 = require("../test/main");
const main_2 = require("../group/main");
const runner_1 = require("./runner");
/**
* The Suite class exposes the API to run a group of tests
* or independent tests together as part of a suite.
*
* You can think of suites as
* - unit tests suite
* - e2e tests suites
* - and so on
*
* @example
* const suite = new Suite('unit', emitter)
* const group = new Group('addition', emitter, refiner)
* const test = new Test('2 + 2 = 4', emitter, refiner)
*
* suite.add(group)
* group.add(test)
*
* // Runs all the tests inside the registered group
* await suite.exec()
*/
class Suite extends macroable_1.Macroable {
constructor(name, emitter, refiner) {
super();
this.name = name;
this.emitter = emitter;
this.refiner = refiner;
/**
* Reference to registered hooks
*/
this.hooks = new hooks_1.Hooks();
/**
* Callbacks to invoke on each test and group
*/
this.configureTestCallbacks = [];
this.configureGroupCallbacks = [];
/**
* A collection of tests and groups both
*/
this.stack = [];
}
/**
* Add a test or a group to the execution stack
*/
add(testOrGroup) {
if (testOrGroup instanceof main_2.Group) {
this.configureGroupCallbacks.forEach((callback) => callback(testOrGroup));
}
if (testOrGroup instanceof main_1.Test) {
this.configureTestCallbacks.forEach((callback) => callback(testOrGroup));
}
this.stack.push(testOrGroup);
return this;
}
/**
* Tap into each test and configure it
*/
onTest(callback) {
this.configureTestCallbacks.push(callback);
return this;
}
/**
* Tap into each group and configure it
*/
onGroup(callback) {
this.configureGroupCallbacks.push(callback);
return this;
}
/**
* Register a test setup function
*/
setup(handler) {
(0, debug_1.default)('registering suite setup hook %s', handler);
this.hooks.add('setup', handler);
return this;
}
/**
* Register a test teardown function
*/
teardown(handler) {
(0, debug_1.default)('registering suite teardown hook %s', handler);
this.hooks.add('teardown', handler);
return this;
}
/**
* Execute suite groups, tests and hooks
*/
async exec() {
/**
* By default a suite is not allowed to be executed. However, we go
* through all the tests/ groups within the suite and if one
* or more tests/groups are allowed to run, then we will
* allow the suite to run as well.
*
* Basically, we are checking the children to find if the suite
* should run or not.
*/
let allowSuite = false;
for (let item of this.stack) {
allowSuite = this.refiner.allows(item);
if (allowSuite) {
break;
}
}
if (!allowSuite) {
(0, debug_1.default)('suite disabled by refiner %s', this.name);
return;
}
await new runner_1.SuiteRunner(this, this.hooks, this.emitter).run();
}
}
exports.Suite = Suite;
Suite.macros = {};
Suite.getters = {};
+56
View File
@@ -0,0 +1,56 @@
import { Hooks } from '@poppinss/hooks';
import { Suite } from './main';
import { Emitter } from '../emitter';
/**
* Run all groups or tests inside the suite stack
*/
export declare class SuiteRunner {
private suite;
private hooks;
private emitter;
/**
* Reference to the startup runner
*/
private setupRunner;
/**
* Reference to the cleanup runner
*/
private teardownRunner;
/**
* Test errors
*/
private errors;
/**
* Track if test has any errors
*/
private hasError;
constructor(suite: Suite<any>, hooks: Hooks, emitter: Emitter);
/**
* Notify the reporter about the suite start
*/
private notifyStart;
/**
* Notify the reporter about the suite end
*/
private notifyEnd;
/**
* Running setup hooks
*/
private runSetupHooks;
/**
* Running teardown hooks
*/
private runTeardownHooks;
/**
* Running setup cleanup functions
*/
private runSetupCleanupFunctions;
/**
* Running teardown cleanup functions
*/
private runTeardownCleanupFunctions;
/**
* Run the test
*/
run(): Promise<void>;
}
+152
View File
@@ -0,0 +1,152 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SuiteRunner = void 0;
const debug_1 = __importDefault(require("../debug"));
/**
* Run all groups or tests inside the suite stack
*/
class SuiteRunner {
constructor(suite, hooks, emitter) {
this.suite = suite;
this.hooks = hooks;
this.emitter = emitter;
/**
* Reference to the startup runner
*/
this.setupRunner = this.hooks.runner('setup');
/**
* Reference to the cleanup runner
*/
this.teardownRunner = this.hooks.runner('teardown');
/**
* Test errors
*/
this.errors = [];
/**
* Track if test has any errors
*/
this.hasError = false;
}
/**
* Notify the reporter about the suite start
*/
notifyStart() {
const startOptions = { name: this.suite.name };
this.emitter.emit('suite:start', startOptions);
}
/**
* Notify the reporter about the suite end
*/
notifyEnd() {
const endOptions = {
name: this.suite.name,
hasError: this.hasError,
errors: this.errors,
};
this.emitter.emit('suite:end', endOptions);
}
/**
* Running setup hooks
*/
async runSetupHooks() {
(0, debug_1.default)('running "%s" suite setup hooks', this.suite.name);
try {
await this.setupRunner.run(this.suite);
}
catch (error) {
(0, debug_1.default)('suite setup hooks failed, suite: %s, error: %O', this.suite.name, error);
this.hasError = true;
this.errors.push({ phase: 'setup', error });
}
}
/**
* Running teardown hooks
*/
async runTeardownHooks() {
(0, debug_1.default)('running "%s" suite teardown hooks', this.suite.name);
try {
await this.teardownRunner.run(this.suite);
}
catch (error) {
(0, debug_1.default)('suite teardown hooks failed, suite: %s, error: %O', this.suite.name, error);
this.hasError = true;
this.errors.push({ phase: 'teardown', error });
}
}
/**
* Running setup cleanup functions
*/
async runSetupCleanupFunctions() {
(0, debug_1.default)('running "%s" suite setup cleanup functions', this.suite.name);
try {
await this.setupRunner.cleanup(this.hasError, this.suite);
}
catch (error) {
(0, debug_1.default)('suite setup cleanup functions failed, suite: %s, error: %O', this.suite.name, error);
this.hasError = true;
this.errors.push({ phase: 'setup:cleanup', error });
}
}
/**
* Running teardown cleanup functions
*/
async runTeardownCleanupFunctions() {
(0, debug_1.default)('running "%s" suite teardown cleanup functions', this.suite.name);
try {
await this.teardownRunner.cleanup(this.hasError, this.suite);
}
catch (error) {
(0, debug_1.default)('suite teardown cleanup functions failed, suite: %s, error: %O', this.suite.name, error);
this.hasError = true;
this.errors.push({ phase: 'teardown:cleanup', error });
}
}
/**
* Run the test
*/
async run() {
(0, debug_1.default)('starting to run "%s" suite', this.suite.name);
this.notifyStart();
/**
* Run setup hooks and exit early when one of the hooks
* fails
*/
await this.runSetupHooks();
if (this.hasError) {
await this.runSetupCleanupFunctions();
this.notifyEnd();
return;
}
/**
* Run the test executor
*/
for (let groupOrTest of this.suite.stack) {
await groupOrTest.exec();
}
/**
* Cleanup setup hooks
*/
await this.runSetupCleanupFunctions();
/**
* Run + cleanup teardown hooks
*/
await this.runTeardownHooks();
await this.runTeardownCleanupFunctions();
/**
* Notify test end
*/
this.notifyEnd();
}
}
exports.SuiteRunner = SuiteRunner;
+150
View File
@@ -0,0 +1,150 @@
import { Macroable } from 'macroable';
import { Group } from '../group/main';
import { Emitter } from '../emitter';
import { Refiner } from '../refiner';
import { DataSetNode, TestEndNode, TestOptions, TestExecutor, TestHooksHandler, TestHooksCleanupHandler } from '../types';
/**
* Test class exposes a self contained API to configure and run
* tests along with its hooks.
*
* @example
* const test = new Test('2 + 2 = 4', emitter, refiner)
*
* test.run(async ({ assert }) => {
* assert.equal(2 + 2 , 4)
* })
*/
export declare class Test<Context extends Record<any, any>, TestData extends DataSetNode = undefined> extends Macroable {
title: string;
private emitter;
private refiner;
parent?: Group<Context> | undefined;
static macros: {};
static getters: {};
/**
* Methods to call before disposing the test
*/
static disposeCallbacks: ((test: Test<any, any>, hasError: boolean, errors: TestEndNode['errors']) => void)[];
/**
* Find if the test has already been executed
*/
private executed;
/**
* Reference to registered hooks
*/
private hooks;
/**
* Test options
*/
options: TestOptions;
/**
* Reference to the test dataset
*/
dataset?: any[];
/**
* Reference to the test context. Available at the time
* of running the test
*/
context: Context;
/**
* Find if the test is pinned
*/
get isPinned(): boolean;
/**
* The function for creating the test context
*/
private contextAccumlator?;
/**
* The function for computing if test should
* be skipped or not
*/
private skipAccumulator?;
/**
* The function that returns the test data set
*/
private datasetAccumlator?;
constructor(title: string, context: Context | ((test: Test<Context, TestData>) => Context | Promise<Context>), emitter: Emitter, refiner: Refiner, parent?: Group<Context> | undefined);
/**
* Find if test should be skipped
*/
private computeShouldSkip;
/**
* Find if test is a todo
*/
private computeisTodo;
/**
* Returns the dataset array or undefined
*/
private computeDataset;
/**
* Get context instance for the test
*/
private computeContext;
/**
* Skip the test conditionally
*/
skip(skip?: boolean | (() => Promise<boolean> | boolean), skipReason?: string): this;
/**
* Expect the test to fail. Helpful in creating test cases
* to showcase bugs
*/
fails(failReason?: string): this;
/**
* Define custom timeout for the test
*/
timeout(timeout: number): this;
/**
* Disable test timeout. It is same as calling `test.timeout(0)`
*/
disableTimeout(): this;
/**
* Assign tags to the test. Later you can use the tags to run
* specific tests
*/
tags(tags: string[], strategy?: 'replace' | 'append' | 'prepend'): this;
/**
* Configure the number of times this test should be retried
* when failing.
*/
retry(retries: number): this;
/**
* Wait for the test executor to call done method
*/
waitForDone(): this;
/**
* Pin current test. Pinning a test will only run the
* pinned tests.
*/
pin(): this;
/**
* Define a dispose callback.
*
* Do note: Async methods are not allowed
*/
static dispose(callback: (test: Test<any, any>, hasError: boolean, errors: TestEndNode['errors']) => void): void;
/**
* Define the dataset for the test. The test executor will be invoked
* for all the items inside the dataset array
*/
with<Dataset extends DataSetNode>(dataset: Dataset): Test<Context, Dataset>;
/**
* Define the test executor function
*/
run(executor: TestExecutor<Context, TestData>): this;
/**
* Register a test setup function
*/
setup(handler: TestHooksHandler<Context>): this;
/**
* Register a test teardown function
*/
teardown(handler: TestHooksHandler<Context>): this;
/**
* Register a cleanup hook from within the test
*/
cleanup(handler: TestHooksCleanupHandler<Context>): this;
/**
* Execute test
*/
exec(): Promise<void>;
}
+300
View File
@@ -0,0 +1,300 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Test = void 0;
const macroable_1 = require("macroable");
const hooks_1 = require("@poppinss/hooks");
const debug_1 = __importDefault(require("../debug"));
const runner_1 = require("./runner");
/**
* Test class exposes a self contained API to configure and run
* tests along with its hooks.
*
* @example
* const test = new Test('2 + 2 = 4', emitter, refiner)
*
* test.run(async ({ assert }) => {
* assert.equal(2 + 2 , 4)
* })
*/
class Test extends macroable_1.Macroable {
/**
* Find if the test is pinned
*/
get isPinned() {
return this.refiner.isPinned(this);
}
constructor(title, context, emitter, refiner, parent) {
super();
this.title = title;
this.emitter = emitter;
this.refiner = refiner;
this.parent = parent;
/**
* Find if the test has already been executed
*/
this.executed = false;
/**
* Reference to registered hooks
*/
this.hooks = new hooks_1.Hooks();
/**
* Test options
*/
this.options = {
title: this.title,
tags: [],
timeout: 2000,
meta: {},
};
/**
* Make sure the instantiated class has its own property "disposeCalls"
*/
if (!this.constructor.hasOwnProperty('disposeCallbacks')) {
throw new Error(`Define static property "disposeCallbacks = []" on ${this.constructor.name} class`);
}
if (typeof context === 'function') {
this.contextAccumlator = context;
}
else {
this.context = context;
}
}
/**
* Find if test should be skipped
*/
async computeShouldSkip() {
if (this.skipAccumulator) {
this.options.isSkipped = await this.skipAccumulator();
}
}
/**
* Find if test is a todo
*/
computeisTodo() {
this.options.isTodo = !this.options.executor;
}
/**
* Returns the dataset array or undefined
*/
async computeDataset() {
if (typeof this.datasetAccumlator === 'function') {
this.dataset = await this.datasetAccumlator();
}
return this.dataset;
}
/**
* Get context instance for the test
*/
async computeContext() {
if (typeof this.contextAccumlator === 'function') {
this.context = await this.contextAccumlator(this);
}
return this.context;
}
/**
* Skip the test conditionally
*/
skip(skip = true, skipReason) {
if (typeof skip === 'function') {
this.skipAccumulator = skip;
}
else {
this.options.isSkipped = skip;
}
this.options.skipReason = skipReason;
return this;
}
/**
* Expect the test to fail. Helpful in creating test cases
* to showcase bugs
*/
fails(failReason) {
this.options.isFailing = true;
this.options.failReason = failReason;
return this;
}
/**
* Define custom timeout for the test
*/
timeout(timeout) {
this.options.timeout = timeout;
return this;
}
/**
* Disable test timeout. It is same as calling `test.timeout(0)`
*/
disableTimeout() {
return this.timeout(0);
}
/**
* Assign tags to the test. Later you can use the tags to run
* specific tests
*/
tags(tags, strategy = 'replace') {
if (strategy === 'replace') {
this.options.tags = tags;
return this;
}
if (strategy === 'prepend') {
this.options.tags = tags.concat(this.options.tags);
return this;
}
this.options.tags = this.options.tags.concat(tags);
return this;
}
/**
* Configure the number of times this test should be retried
* when failing.
*/
retry(retries) {
this.options.retries = retries;
return this;
}
/**
* Wait for the test executor to call done method
*/
waitForDone() {
this.options.waitsForDone = true;
return this;
}
/**
* Pin current test. Pinning a test will only run the
* pinned tests.
*/
pin() {
this.refiner.pinTest(this);
return this;
}
/**
* Define a dispose callback.
*
* Do note: Async methods are not allowed
*/
static dispose(callback) {
this.disposeCallbacks.push(callback);
}
/**
* Define the dataset for the test. The test executor will be invoked
* for all the items inside the dataset array
*/
with(dataset) {
if (Array.isArray(dataset)) {
this.dataset = dataset;
return this;
}
if (typeof dataset === 'function') {
this.datasetAccumlator = dataset;
return this;
}
throw new Error('dataset must be an array or a function that returns an array');
}
/**
* Define the test executor function
*/
run(executor) {
this.options.executor = executor;
return this;
}
/**
* Register a test setup function
*/
setup(handler) {
(0, debug_1.default)('registering "%s" test setup hook %s', this.title, handler);
this.hooks.add('setup', handler);
return this;
}
/**
* Register a test teardown function
*/
teardown(handler) {
(0, debug_1.default)('registering "%s" test teardown hook %s', this.title, handler);
this.hooks.add('teardown', handler);
return this;
}
/**
* Register a cleanup hook from within the test
*/
cleanup(handler) {
(0, debug_1.default)('registering "%s" test cleanup function %s', this.title, handler);
this.hooks.add('cleanup', handler);
return this;
}
/**
* Execute test
*/
async exec() {
/**
* Return early, if there are pinned test and the current test is not
* pinned.
*
* However, the pinned test check is only applied when there
* is no filter on the test title.
*/
if (!this.refiner.allows(this)) {
(0, debug_1.default)('test "%s" skipped by refiner', this.title);
return;
}
/**
* Avoid re-running the same test multiple times
*/
if (this.executed) {
return;
}
this.executed = true;
/**
* Do not run tests without executor function
*/
this.computeisTodo();
if (this.options.isTodo) {
(0, debug_1.default)('skipping todo test "%s"', this.title);
new runner_1.DummyRunner(this, this.emitter).run();
return;
}
/**
* Do not run test meant to be skipped
*/
await this.computeShouldSkip();
if (this.options.isSkipped) {
(0, debug_1.default)('skipping test "%s", reason (%s)', this.title, this.options.skipReason || 'Skipped using .skip method');
new runner_1.DummyRunner(this, this.emitter).run();
return;
}
/**
* Run for each row inside dataset
*/
await this.computeDataset();
if (Array.isArray(this.dataset) && this.dataset.length) {
let index = 0;
// eslint-disable-next-line @typescript-eslint/naming-convention
for (let _ of this.dataset) {
await this.computeContext();
await new runner_1.TestRunner(this, this.hooks, this.emitter, this.constructor.disposeCallbacks, index).run();
index++;
}
return;
}
/**
* Run when no dataset is used
*/
await this.computeContext();
await new runner_1.TestRunner(this, this.hooks, this.emitter, this.constructor.disposeCallbacks).run();
}
}
exports.Test = Test;
Test.macros = {};
Test.getters = {};
/**
* Methods to call before disposing the test
*/
Test.disposeCallbacks = [];
+115
View File
@@ -0,0 +1,115 @@
import { Hooks } from '@poppinss/hooks';
import { Test } from './main';
import { Emitter } from '../emitter';
import { TestEndNode } from '../types';
/**
* Dummy test runner that just emits the required events
*/
export declare class DummyRunner {
private test;
private emitter;
constructor(test: Test<any, any>, emitter: Emitter);
/**
* Notify the reporter about the test start
*/
private notifyStart;
/**
* Notify the reporter about the test start
*/
private notifyEnd;
/**
* Run test
*/
run(): void;
}
/**
* Run an instance of test
*/
export declare class TestRunner {
private test;
private hooks;
private emitter;
private disposeCalls;
private datasetCurrentIndex?;
/**
* Time tracker to find test duration
*/
private timeTracker;
/**
* Reference to the startup runner
*/
private setupRunner;
/**
* Reference to the cleanup runner
*/
private teardownRunner;
/**
* Test errors
*/
private errors;
/**
* Track if test has any errors
*/
private hasError;
private uncaughtExceptionHandler?;
constructor(test: Test<any, any>, hooks: Hooks, emitter: Emitter, disposeCalls: ((test: Test<any, any>, hasError: boolean, errors: TestEndNode['errors']) => void)[], datasetCurrentIndex?: number | undefined);
/**
* Returns the dataset node for the test events
*/
private getDatasetNode;
/**
* Get the title node for the test
*/
private getTitle;
/**
* Notify the reporter about the test start
*/
private notifyStart;
/**
* Notify the reporter about the test start
*/
private notifyEnd;
/**
* Running setup hooks
*/
private runSetupHooks;
/**
* Running teardown hooks
*/
private runTeardownHooks;
/**
* Running test cleanup functions
*/
private runTestCleanupFunctions;
/**
* Running setup cleanup functions
*/
private runSetupCleanupFunctions;
/**
* Running teardown cleanup functions
*/
private runTeardownCleanupFunctions;
/**
* Run the test executor. The method takes care of passing
* dataset row to the test method
*/
private runTest;
/**
* Run the test executor that relies on the done method. The test will
* timeout if done isn't called.
*/
private runTestWithDone;
/**
* Run the test executor and make sure it times out after the configured
* timeout.
*/
private wrapTestInTimeout;
/**
* Runs the test with retries in place
*/
private wrapTestInRetries;
/**
* Run the test
*/
run(): Promise<void>;
}
+367
View File
@@ -0,0 +1,367 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestRunner = exports.DummyRunner = void 0;
const async_retry_1 = __importDefault(require("async-retry"));
const time_span_1 = __importDefault(require("time-span"));
const debug_1 = __importDefault(require("../debug"));
const interpolate_1 = require("../interpolate");
/**
* Dummy test runner that just emits the required events
*/
class DummyRunner {
constructor(test, emitter) {
this.test = test;
this.emitter = emitter;
}
/**
* Notify the reporter about the test start
*/
notifyStart() {
const startOptions = {
...this.test.options,
title: {
original: this.test.options.title,
expanded: this.test.options.title,
toString() {
return this.original;
},
},
isPinned: this.test.isPinned,
};
this.emitter.emit('test:start', startOptions);
}
/**
* Notify the reporter about the test start
*/
notifyEnd() {
const endOptions = {
...this.test.options,
title: {
original: this.test.options.title,
expanded: this.test.options.title,
toString() {
return this.original;
},
},
isPinned: this.test.isPinned,
hasError: false,
duration: 0,
errors: [],
};
this.emitter.emit('test:end', endOptions);
}
/**
* Run test
*/
run() {
this.notifyStart();
this.notifyEnd();
}
}
exports.DummyRunner = DummyRunner;
/**
* Run an instance of test
*/
class TestRunner {
constructor(test, hooks, emitter, disposeCalls, datasetCurrentIndex) {
this.test = test;
this.hooks = hooks;
this.emitter = emitter;
this.disposeCalls = disposeCalls;
this.datasetCurrentIndex = datasetCurrentIndex;
/**
* Reference to the startup runner
*/
this.setupRunner = this.hooks.runner('setup');
/**
* Reference to the cleanup runner
*/
this.teardownRunner = this.hooks.runner('teardown');
/**
* Test errors
*/
this.errors = [];
/**
* Track if test has any errors
*/
this.hasError = false;
}
/**
* Returns the dataset node for the test events
*/
getDatasetNode() {
if (this.datasetCurrentIndex !== undefined && this.test.dataset) {
return {
dataset: {
row: this.test.dataset[this.datasetCurrentIndex],
index: this.datasetCurrentIndex,
size: this.test.dataset.length,
},
};
}
}
/**
* Get the title node for the test
*/
getTitle(dataset) {
const title = this.test.options.title;
return {
original: title,
expanded: dataset ? (0, interpolate_1.interpolate)(title, dataset.row, dataset.index + 1) : title,
toString() {
return this.original;
},
};
}
/**
* Notify the reporter about the test start
*/
notifyStart() {
this.timeTracker = (0, time_span_1.default)();
const dataset = this.getDatasetNode();
const startOptions = {
...this.test.options,
...dataset,
isPinned: this.test.isPinned,
title: this.getTitle(dataset ? dataset.dataset : undefined),
};
this.emitter.emit('test:start', startOptions);
}
/**
* Notify the reporter about the test start
*/
notifyEnd() {
const dataset = this.getDatasetNode();
const endOptions = {
...this.test.options,
...dataset,
isPinned: this.test.isPinned,
title: this.getTitle(dataset ? dataset.dataset : undefined),
hasError: this.hasError,
errors: this.errors,
retryAttempt: this.test.options.retryAttempt,
duration: this.timeTracker.rounded(),
};
this.emitter.emit('test:end', endOptions);
}
/**
* Running setup hooks
*/
async runSetupHooks() {
try {
(0, debug_1.default)('running "%s" test setup hooks', this.test.title);
await this.setupRunner.run(this.test);
}
catch (error) {
(0, debug_1.default)('test setup hooks failed, test: %s, error: %O', this.test.title, error);
this.hasError = true;
this.errors.push({ phase: 'setup', error });
}
}
/**
* Running teardown hooks
*/
async runTeardownHooks() {
try {
(0, debug_1.default)('running "%s" test teardown hooks', this.test.title);
await this.teardownRunner.run(this.test);
}
catch (error) {
(0, debug_1.default)('test teardown hooks failed, test: %s, error: %O', this.test.title, error);
this.hasError = true;
this.errors.push({ phase: 'teardown', error });
}
}
/**
* Running test cleanup functions
*/
async runTestCleanupFunctions() {
try {
(0, debug_1.default)('running "%s" test cleanup functions', this.test.title);
await this.hooks.runner('cleanup').run(this.errors.length > 0, this.test);
}
catch (error) {
(0, debug_1.default)('test cleanup functions failed, test: %s, error: %O', this.test.title, error);
this.hasError = true;
this.errors.push({ phase: 'test:cleanup', error });
}
}
/**
* Running setup cleanup functions
*/
async runSetupCleanupFunctions() {
try {
(0, debug_1.default)('running "%s" test setup cleanup functions', this.test.title);
await this.setupRunner.cleanup(this.errors.length > 0, this.test);
}
catch (error) {
(0, debug_1.default)('test setup cleanup functions failed, test: %s, error: %O', this.test.title, error);
this.hasError = true;
this.errors.push({ phase: 'setup:cleanup', error });
}
}
/**
* Running teardown cleanup functions
*/
async runTeardownCleanupFunctions() {
try {
(0, debug_1.default)('running "%s" test teardown cleanup functions', this.test.title);
await this.teardownRunner.cleanup(this.errors.length > 0, this.test);
}
catch (error) {
(0, debug_1.default)('test teardown cleanup functions failed, test: %s, error: %O', this.test.title, error);
this.hasError = true;
this.errors.push({ phase: 'teardown:cleanup', error });
}
}
/**
* Run the test executor. The method takes care of passing
* dataset row to the test method
*/
async runTest(done) {
const datasetRow = this.datasetCurrentIndex !== undefined && this.test.dataset
? this.test.dataset[this.datasetCurrentIndex]
: undefined;
return datasetRow !== undefined
? this.test.options.executor(this.test.context, datasetRow, done)
: this.test.options.executor(this.test.context, done);
}
/**
* Run the test executor that relies on the done method. The test will
* timeout if done isn't called.
*/
runTestWithDone() {
return new Promise((resolve, reject) => {
const done = (error) => {
if (error) {
reject(error);
}
else {
resolve();
}
};
/**
* Done style tests the primary source of uncaught exceptions. Hence
* we make an extra efforts to related uncaught exceptions with
* them
*/
if (!this.uncaughtExceptionHandler) {
this.uncaughtExceptionHandler = (error) => {
reject(error);
};
process.on('uncaughtException', this.uncaughtExceptionHandler);
}
(0, debug_1.default)('running test "%s" and waiting for done method call', this.test.title);
this.runTest(done).catch(reject);
});
}
/**
* Run the test executor and make sure it times out after the configured
* timeout.
*/
async wrapTestInTimeout() {
if (!this.test.options.timeout) {
return this.test.options.waitsForDone ? this.runTestWithDone() : this.runTest();
}
let timeoutTimer = null;
const timeout = () => {
return new Promise((_, reject) => {
timeoutTimer = setTimeout(() => reject(new Error('Test timeout')), this.test.options.timeout);
});
};
try {
await Promise.race([
this.test.options.waitsForDone ? this.runTestWithDone() : this.runTest(),
timeout(),
]);
}
finally {
if (timeoutTimer) {
clearTimeout(timeoutTimer);
}
}
}
/**
* Runs the test with retries in place
*/
wrapTestInRetries() {
if (!this.test.options.retries) {
return this.wrapTestInTimeout();
}
return (0, async_retry_1.default)((_, attempt) => {
this.test.options.retryAttempt = attempt;
return this.wrapTestInTimeout();
}, { retries: this.test.options.retries, factor: 1 });
}
/**
* Run the test
*/
async run() {
(0, debug_1.default)('starting to run "%s" test', this.test.title);
this.notifyStart();
/**
* Run setup hooks and exit early when one of the hooks
* fails
*/
await this.runSetupHooks();
if (this.hasError) {
await this.runSetupCleanupFunctions();
this.notifyEnd();
return;
}
/**
* Run the test executor
*/
try {
await this.wrapTestInRetries();
}
catch (error) {
this.hasError = true;
this.errors.push({ phase: 'test', error });
}
finally {
if (this.uncaughtExceptionHandler) {
process.removeListener('uncaughtException', this.uncaughtExceptionHandler);
}
}
/**
* Run dispose callbacks
*/
try {
this.disposeCalls.forEach((callback) => callback(this.test, this.hasError, this.errors));
}
catch (error) {
this.hasError = true;
this.errors.push({ phase: 'test', error });
}
/**
* Run test cleanup hooks
*/
await this.runTestCleanupFunctions();
/**
* Cleanup setup hooks
*/
await this.runSetupCleanupFunctions();
/**
* Run + cleanup teardown hooks
*/
await this.runTeardownHooks();
await this.runTeardownCleanupFunctions();
/**
* Notify test end
*/
this.notifyEnd();
}
}
exports.TestRunner = TestRunner;
+11
View File
@@ -0,0 +1,11 @@
/// <reference types="node" />
import { inspect } from 'util';
import { Macroable } from 'macroable';
/**
* A fresh copy of test context is shared with all the tests
*/
export declare class TestContext extends Macroable {
static macros: {};
static getters: {};
[inspect.custom](): string;
}
+24
View File
@@ -0,0 +1,24 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestContext = void 0;
const util_1 = require("util");
const macroable_1 = require("macroable");
/**
* A fresh copy of test context is shared with all the tests
*/
class TestContext extends macroable_1.Macroable {
[util_1.inspect.custom]() {
return (0, util_1.inspect)(this, { showHidden: false, depth: 1, colors: true, customInspect: false });
}
}
exports.TestContext = TestContext;
TestContext.macros = {};
TestContext.getters = {};
+81
View File
@@ -0,0 +1,81 @@
import { RunnerEvents, RunnerSummary } from './types';
/**
* Tracks the tests events to generate a summary report. Failing tests are further tracked
* for complete hierarchy
*/
export declare class Tracker {
/**
* Time tracker to find runner duration
*/
private timeTracker;
/**
* Currently active suite
*/
private currentSuite?;
/**
* Currently active group
*/
private currentGroup?;
/**
* If the entire run cycle has one or more errors
*/
private hasError;
/**
* Storing state if current suite and group has errors. These
* errors are not directly from the suite and groups, but
* instead from their children.
*
* For example: If a test fails, it marks both current group
* and suite has errors.
*/
private currentSuiteHasError;
private currentGroupHasError;
private aggregates;
private duration;
/**
* A tree of suites/groups and tests that have failed. They are always nested inside
* other unless the test groups where used, then suites contains a list of tests
* directly.
*/
private failureTree;
private failedTestsTitles;
/**
* Set reference for the current suite
*/
private onSuiteStart;
/**
* Move suite to the failure tree when the suite
* has errors
*/
private onSuiteEnd;
/**
* Set reference for the current group
*/
private onGroupStart;
/**
* Move suite to the failure tree when the suite
* has errors
*/
private onGroupEnd;
/**
* In case of failure, track the test inside the current group
* or the current suite.
*/
private onTestEnd;
/**
* Mark test as failed
*/
private markTestAsFailed;
/**
* Increment the count of uncaught exceptions
*/
private onUncaughtException;
/**
* Process the tests events
*/
processEvent<Event extends keyof RunnerEvents>(event: keyof RunnerEvents, payload: RunnerEvents[Event]): void;
/**
* Returns the tests runner summary
*/
getSummary(): RunnerSummary;
}
+240
View File
@@ -0,0 +1,240 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tracker = void 0;
const time_span_1 = __importDefault(require("time-span"));
/**
* Tracks the tests events to generate a summary report. Failing tests are further tracked
* for complete hierarchy
*/
class Tracker {
constructor() {
/**
* If the entire run cycle has one or more errors
*/
this.hasError = false;
/**
* Storing state if current suite and group has errors. These
* errors are not directly from the suite and groups, but
* instead from their children.
*
* For example: If a test fails, it marks both current group
* and suite has errors.
*/
this.currentSuiteHasError = false;
this.currentGroupHasError = false;
this.aggregates = {
total: 0,
failed: 0,
passed: 0,
regression: 0,
skipped: 0,
todo: 0,
uncaughtExceptions: 0,
};
this.duration = 0;
/**
* A tree of suites/groups and tests that have failed. They are always nested inside
* other unless the test groups where used, then suites contains a list of tests
* directly.
*/
this.failureTree = [];
this.failedTestsTitles = [];
}
/**
* Set reference for the current suite
*/
onSuiteStart(payload) {
this.currentSuiteHasError = false;
this.currentSuite = {
name: payload.name,
type: 'suite',
errors: [],
children: [],
};
}
/**
* Move suite to the failure tree when the suite
* has errors
*/
onSuiteEnd(payload) {
if (payload.hasError) {
this.hasError = true;
this.currentSuiteHasError = true;
this.currentSuite.errors = payload.errors;
}
if (this.currentSuiteHasError) {
this.failureTree.push(this.currentSuite);
}
}
/**
* Set reference for the current group
*/
onGroupStart(payload) {
this.currentGroupHasError = false;
this.currentGroup = {
name: payload.title,
type: 'group',
errors: [],
children: [],
};
}
/**
* Move suite to the failure tree when the suite
* has errors
*/
onGroupEnd(payload) {
if (payload.hasError) {
this.hasError = true;
this.currentGroupHasError = true;
this.currentGroup.errors = payload.errors;
}
if (this.currentGroupHasError) {
this.currentSuiteHasError = true;
this.currentSuite.children.push(this.currentGroup);
}
}
/**
* In case of failure, track the test inside the current group
* or the current suite.
*/
onTestEnd(payload) {
/**
* Bumping aggregates
*/
this.aggregates.total++;
/**
* Test was skipped
*/
if (payload.isSkipped) {
this.aggregates.skipped++;
return;
}
/**
* Test was a todo
*/
if (payload.isTodo) {
this.aggregates.todo++;
return;
}
/**
* Regression test. Mark test as failed, when there is no error
* Because, we expect regression tests to have errors.
*
* However, there is no need to move anything to the failure
* tree, since there is no real error
*/
if (payload.isFailing) {
if (!payload.hasError) {
this.aggregates.failed++;
this.hasError = true;
}
else {
this.aggregates.regression++;
}
return;
}
/**
* Test completed successfully
*/
if (!payload.hasError) {
this.aggregates.passed++;
return;
}
this.markTestAsFailed(payload);
}
/**
* Mark test as failed
*/
markTestAsFailed(payload) {
/**
* Bump failed count
*/
this.aggregates.failed++;
this.hasError = true;
/**
* Test payload
*/
const testPayload = {
type: 'test',
title: payload.title.expanded,
errors: payload.errors,
};
/**
* Track test inside the current group or suite
*/
if (this.currentGroup) {
this.currentGroupHasError = true;
this.currentGroup.children.push(testPayload);
}
else if (this.currentSuite) {
this.currentSuiteHasError = true;
this.currentSuite.children.push(testPayload);
}
/**
* Push title to the failedTestsTitles array
*/
this.failedTestsTitles.push(payload.title.original);
}
/**
* Increment the count of uncaught exceptions
*/
onUncaughtException() {
this.aggregates.uncaughtExceptions++;
this.hasError = true;
}
/**
* Process the tests events
*/
processEvent(event, payload) {
switch (event) {
case 'uncaught:exception':
this.onUncaughtException();
break;
case 'suite:start':
this.onSuiteStart(payload);
break;
case 'suite:end':
this.onSuiteEnd(payload);
break;
case 'group:start':
this.onGroupStart(payload);
break;
case 'group:end':
this.onGroupEnd(payload);
break;
case 'test:end':
this.onTestEnd(payload);
break;
case 'runner:start':
this.timeTracker = (0, time_span_1.default)();
break;
case 'runner:end':
this.duration = this.timeTracker.rounded();
break;
}
}
/**
* Returns the tests runner summary
*/
getSummary() {
return {
aggregates: this.aggregates,
hasError: this.hasError,
duration: this.duration,
failureTree: this.failureTree,
failedTestsTitles: this.failedTestsTitles,
};
}
}
exports.Tracker = Tracker;
+222
View File
@@ -0,0 +1,222 @@
import type { Runner } from './runner';
import type { Test } from './test/main';
import type { Emitter } from './emitter';
import type { Group } from './group/main';
import type { Suite } from './suite/main';
/**
* Shape of test data set. Should be an array of a function that
* returns an array
*/
export type DataSetNode = undefined | any[] | (() => any[] | Promise<any[]>);
/**
* The cleanup function for test hooks
*/
export type TestHooksCleanupHandler<Context extends Record<any, any>> = (error: null | any, test: Test<Context, any>) => Promise<any> | any;
/**
* The function that can be registered as a test hook
*/
export type TestHooksHandler<Context extends Record<any, any>> = (test: Test<Context, any>) => Promise<any> | any | TestHooksCleanupHandler<Context> | Promise<TestHooksCleanupHandler<Context>>;
/**
* The cleanup function for group hooks
*/
export type GroupHooksCleanupHandler<Context extends Record<any, any>> = (error: null | any, group: Group<Context>) => Promise<any> | any;
/**
* The function that can be registered as a group hook
*/
export type GroupHooksHandler<Context extends Record<any, any>> = (group: Group<Context>) => Promise<any> | any | GroupHooksCleanupHandler<Context> | Promise<GroupHooksCleanupHandler<Context>>;
/**
* The cleanup function for suite hooks
*/
export type SuiteHooksCleanupHandler<Context extends Record<any, any>> = (error: null | any, suite: Suite<Context>) => Promise<any> | any;
/**
* The function that can be registered as a suite hook
*/
export type SuiteHooksHandler<Context extends Record<any, any>> = (suite: Suite<Context>) => Promise<any> | any | SuiteHooksCleanupHandler<Context> | Promise<SuiteHooksCleanupHandler<Context>>;
/**
* The function to execute the test
*/
export type TestExecutor<Context, DataSet> = DataSet extends any[] ? (context: Context, value: DataSet[number], done: (error?: any) => void) => void | Promise<void> : DataSet extends () => infer A ? (context: Context, value: Awaited<A> extends any[] ? Awaited<A>[number] : Awaited<A>, done?: (error?: any) => void) => void | Promise<void> : (context: Context, done: (error?: any) => void) => void | Promise<void>;
/**
* Test configuration options.
*/
export type TestOptions = {
title: string;
tags: string[];
timeout: number;
waitsForDone?: boolean;
executor?: TestExecutor<any, any>;
isTodo?: boolean;
isSkipped?: boolean;
isFailing?: boolean;
skipReason?: string;
failReason?: string;
retries?: number;
retryAttempt?: number;
meta: Record<string, any>;
};
/**
* Data shared during "test:start" event
*/
export type TestStartNode = Omit<TestOptions, 'title'> & {
title: {
original: string;
expanded: string;
toString(): string;
};
isPinned: boolean;
dataset?: {
size: number;
index: number;
row: any;
};
};
/**
* Data shared during "test:end" event
*/
export type TestEndNode = Omit<TestOptions, 'title'> & {
title: {
original: string;
expanded: string;
toString(): string;
};
isPinned: boolean;
duration: number;
hasError: boolean;
errors: {
phase: 'setup' | 'test' | 'setup:cleanup' | 'teardown' | 'teardown:cleanup' | 'test:cleanup';
error: Error;
}[];
retryAttempt?: number;
dataset?: {
size: number;
index: number;
row: any;
};
};
/**
* Group options
*/
export type GroupOptions = {
title: string;
meta: Record<string, any>;
};
/**
* Data shared with "group:start" event
*/
export type GroupStartNode = GroupOptions;
/**
* Data shared with "group:end" event
*/
export type GroupEndNode = GroupOptions & {
hasError: boolean;
errors: {
phase: 'setup' | 'setup:cleanup' | 'teardown' | 'teardown:cleanup';
error: Error;
}[];
};
/**
* Data shared with "suite:start" event
*/
export type SuiteStartNode = {
name: string;
};
/**
* Data shared with "suite:end" event
*/
export type SuiteEndNode = {
name: string;
hasError: boolean;
errors: {
phase: 'setup' | 'setup:cleanup' | 'teardown' | 'teardown:cleanup';
error: Error;
}[];
};
/**
* Data shared with "runner:start" event
*/
export type RunnerStartNode = {};
/**
* Data shared with "runner:end" event
*/
export type RunnerEndNode = {};
/**
* Events emitted by the runner emitter. These can be extended as well
*/
export interface RunnerEvents {
'test:start': TestStartNode;
'test:end': TestEndNode;
'group:start': GroupStartNode;
'group:end': GroupEndNode;
'suite:start': SuiteStartNode;
'suite:end': SuiteEndNode;
'runner:start': RunnerStartNode;
'runner:end': RunnerEndNode;
'uncaught:exception': Error;
}
/**
* Options for filtering and running on selected tests
*/
export type FilteringOptions = {
tags?: string[];
groups?: string[];
tests?: string[];
};
/**
* Type for the reporter handler function
*/
export type ReporterHandlerContract = (runner: Runner<any>, emitter: Emitter) => void | Promise<void>;
/**
* Type for a named reporter object.
*/
export type NamedReporterContract = {
readonly name: string;
handler: ReporterHandlerContract;
};
/**
* Test reporters must adhere to the following contract
*/
export type ReporterContract = ReporterHandlerContract | NamedReporterContract;
/**
* The test node inside the failure tree
*/
export type FailureTreeTestNode = {
title: string;
type: 'test';
errors: TestEndNode['errors'];
};
/**
* The group node inside the failure tree
*/
export type FailureTreeGroupNode = {
name: string;
type: 'group';
errors: GroupEndNode['errors'];
children: FailureTreeTestNode[];
};
/**
* The suite node inside the failure tree
*/
export type FailureTreeSuiteNode = {
name: string;
type: 'suite';
errors: SuiteEndNode['errors'];
children: (FailureTreeTestNode | FailureTreeGroupNode)[];
};
/**
* Runner summary properties
*/
export type RunnerSummary = {
aggregates: {
total: number;
failed: number;
passed: number;
regression: number;
skipped: number;
todo: number;
uncaughtExceptions: number;
};
duration: number;
hasError: boolean;
failureTree: FailureTreeSuiteNode[];
failedTestsTitles: string[];
};
+10
View File
@@ -0,0 +1,10 @@
"use strict";
/*
* @japa/core
*
* (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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
+134
View File
@@ -0,0 +1,134 @@
{
"name": "@japa/core",
"version": "7.3.3",
"description": "The core of Japa testing framework",
"main": "build/index.js",
"files": [
"build/src",
"build/index.d.ts",
"build/index.js"
],
"exports": {
".": "./build/index.js",
"./types": "./build/src/types.js"
},
"scripts": {
"mrm": "mrm --preset=@adonisjs/mrm-preset",
"pretest": "npm run lint",
"test": "cross-env NODE_DEBUG=japa:core nyc --reporter html node .bin/test.js",
"clean": "del build",
"compile": "npm run lint && npm run clean && tsc",
"build": "npm run compile",
"prepublishOnly": "npm run build",
"lint": "eslint . --ext=.ts",
"format": "prettier --write .",
"commit": "git-cz",
"release": "np --message=\"chore(release): %s\"",
"version": "npm run build",
"sync-labels": "npx github-label-sync --labels ./node_modules/@adonisjs/mrm-preset/gh-labels.json thetutlage/japa"
},
"repository": {
"type": "git",
"url": "git+https://github.com/thetutlage/japa.git"
},
"keywords": [
"japa",
"testing",
"tests"
],
"author": "virk,japa",
"license": "MIT",
"bugs": {
"url": "https://github.com/thetutlage/japa/issues"
},
"homepage": "https://github.com/thetutlage/japa#readme",
"devDependencies": {
"@adonisjs/mrm-preset": "^5.0.3",
"@adonisjs/require-ts": "^2.0.13",
"@types/node": "^18.14.5",
"commitizen": "^4.3.0",
"cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
"del-cli": "^5.0.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-adonis": "^2.1.1",
"eslint-plugin-prettier": "^4.2.1",
"husky": "^8.0.3",
"japa": "^4.0.0",
"mrm": "^4.1.13",
"np": "^7.6.3",
"nyc": "^15.1.0",
"prettier": "^2.8.4",
"typescript": "^4.9.5"
},
"mrmConfig": {
"core": false,
"license": "MIT",
"services": [
"github-actions"
],
"minNodeVersion": "16.13.1",
"probotApps": [
"stale",
"lock"
],
"runGhActionsOnWindows": true
},
"eslintConfig": {
"extends": [
"plugin:adonis/typescriptPackage",
"prettier"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
]
}
},
"eslintIgnore": [
"build"
],
"prettier": {
"trailingComma": "es5",
"semi": false,
"singleQuote": true,
"useTabs": false,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 100
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"np": {
"contents": ".",
"anyBranch": false
},
"dependencies": {
"@poppinss/hooks": "^6.0.2-0",
"async-retry": "^1.3.3",
"emittery": "^0.13.1",
"macroable": "^7.0.2",
"time-span": "^4.0.0"
},
"nyc": {
"exclude": [
"test",
"test-helpers"
]
},
"publishConfig": {
"access": "public",
"tag": "latest"
}
}