qunitx 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,154 @@
1
+ const hasOwn = Object.prototype.hasOwnProperty;
2
+ export function objectType(obj) {
3
+ if (typeof obj === 'undefined') {
4
+ return 'undefined';
5
+ }
6
+ // Consider: typeof null === object
7
+ if (obj === null) {
8
+ return 'null';
9
+ }
10
+ // slice(8, -1) extracts the type name from "[object Foo]" without a regex
11
+ const type = Object.prototype.toString.call(obj).slice(8, -1);
12
+ switch (type) {
13
+ case 'Number':
14
+ if (isNaN(obj)) {
15
+ return 'nan';
16
+ }
17
+ return 'number';
18
+ case 'String':
19
+ case 'Boolean':
20
+ case 'Array':
21
+ case 'Set':
22
+ case 'Map':
23
+ case 'Date':
24
+ case 'RegExp':
25
+ case 'Function':
26
+ case 'Symbol':
27
+ return type.toLowerCase();
28
+ default:
29
+ return typeof obj;
30
+ }
31
+ }
32
+ function is(type, obj) {
33
+ return objectType(obj) === type;
34
+ }
35
+ export function objectValues(obj, allowArray = true) {
36
+ const vals = allowArray && is('array', obj) ? [] : {};
37
+ for (const key in obj) {
38
+ if (hasOwn.call(obj, key)) {
39
+ const val = obj[key];
40
+ vals[key] = val === Object(val) ? objectValues(val, allowArray) : val;
41
+ }
42
+ }
43
+ return vals;
44
+ }
45
+ /**
46
+ *
47
+ * Recursively clone an object into a plain object, taking only the
48
+ * subset of own enumerable properties that exist a given model.
49
+ *
50
+ * @param {any} obj
51
+ * @param {any} model
52
+ * @return {Object}
53
+ */
54
+ export function objectValuesSubset(obj, model) {
55
+ // Return primitive values unchanged to avoid false positives or confusing
56
+ // results from assert.propContains().
57
+ // E.g. an actual null or false wrongly equaling an empty object,
58
+ // or an actual string being reported as object not matching a partial object.
59
+ if (obj !== Object(obj)) {
60
+ return obj;
61
+ }
62
+ // Unlike objectValues(), subset arrays to a plain objects as well.
63
+ // This enables subsetting [20, 30] with {1: 30}.
64
+ const subset = {};
65
+ for (const key in model) {
66
+ if (hasOwn.call(model, key) && hasOwn.call(obj, key)) {
67
+ subset[key] = objectValuesSubset(obj[key], model[key]);
68
+ }
69
+ }
70
+ return subset;
71
+ }
72
+ export function validateExpectedExceptionArgs(expected, message, assertionMethod) {
73
+ const expectedType = objectType(expected);
74
+ // 'expected' is optional unless doing string comparison
75
+ if (expectedType === 'string') {
76
+ if (message === undefined) {
77
+ message = expected;
78
+ expected = undefined;
79
+ return [expected, message];
80
+ }
81
+ else {
82
+ throw new Error('assert.' + assertionMethod + ' does not accept a string value for the expected argument.\n' + 'Use a non-string object value (e.g. RegExp or validator function) ' + 'instead if necessary.');
83
+ }
84
+ }
85
+ const valid = !expected ||
86
+ // TODO: be more explicit here
87
+ expectedType === 'regexp' || expectedType === 'function' || expectedType === 'object';
88
+ if (!valid) {
89
+ throw new Error('Invalid expected value type (' + expectedType + ') ' + 'provided to assert.' + assertionMethod + '.');
90
+ }
91
+ return [expected, message];
92
+ }
93
+ export function validateException(actual, expected, message) {
94
+ let result = false;
95
+ const expectedType = objectType(expected);
96
+ // These branches should be exhaustive, based on validation done in validateExpectedException
97
+ // We don't want to validate
98
+ if (!expected) {
99
+ result = true;
100
+ // Expected is a regexp
101
+ }
102
+ else if (expectedType === 'regexp') {
103
+ result = expected.test(errorString(actual));
104
+ // Log the string form of the regexp
105
+ expected = String(expected);
106
+ // Expected is a constructor, maybe an Error constructor.
107
+ // Note the extra check on its prototype - this is an implicit
108
+ // requirement of "instanceof", else it will throw a TypeError.
109
+ }
110
+ else if (expectedType === 'function' &&
111
+ expected.prototype !== undefined &&
112
+ actual instanceof expected) {
113
+ result = true;
114
+ // Expected is an Error object
115
+ }
116
+ else if (expectedType === 'object') {
117
+ result =
118
+ actual instanceof expected.constructor &&
119
+ actual.name === expected.name &&
120
+ actual.message === expected.message;
121
+ // Log the string form of the Error object
122
+ expected = errorString(expected);
123
+ // Expected is a validation function which returns true if validation passed
124
+ }
125
+ else if (expectedType === 'function') {
126
+ // protect against accidental semantics which could hard error in the test
127
+ try {
128
+ result = expected.call({}, actual) === true;
129
+ expected = null;
130
+ }
131
+ catch (e) {
132
+ // assign the "expected" to a nice error string to communicate the local failure to the user
133
+ expected = errorString(e);
134
+ }
135
+ }
136
+ return [result, expected, message];
137
+ }
138
+ function errorString(error) {
139
+ // Use String() instead of toString() to handle non-object values like undefined or null.
140
+ const resultErrorString = String(error);
141
+ // If the error wasn't a subclass of Error but something like
142
+ // an object literal with name and message properties...
143
+ if (resultErrorString.slice(0, 7) === '[object') {
144
+ // Based on https://es5.github.io/#x15.11.4.4
145
+ return ((error.name || 'Error') +
146
+ (error.message
147
+ ? ': '.concat(error.message)
148
+ : ''));
149
+ }
150
+ else {
151
+ return resultErrorString;
152
+ }
153
+ }
154
+ export default { objectValues, objectValuesSubset, validateExpectedExceptionArgs, validateException };
@@ -0,0 +1,17 @@
1
+ import type Assert from './assert.ts';
2
+ import type { HookFn } from '../types.ts';
3
+ import TestContext from './test-context.ts';
4
+ export default class ModuleContext {
5
+ static Assert: typeof Assert;
6
+ static currentModuleChain: ModuleContext[];
7
+ static get lastModule(): ModuleContext;
8
+ name: string;
9
+ assert: Assert;
10
+ userContext: object;
11
+ context: TestContext;
12
+ moduleChain: ModuleContext[];
13
+ beforeEachHooks: HookFn<Assert>[];
14
+ afterEachHooks: HookFn<Assert>[];
15
+ tests: TestContext[];
16
+ constructor(name: string);
17
+ }
@@ -0,0 +1,28 @@
1
+ import TestContext from "./test-context.js";
2
+ export default class ModuleContext {
3
+ static Assert;
4
+ static currentModuleChain = [];
5
+ static get lastModule() {
6
+ return this.currentModuleChain[this.currentModuleChain.length - 1];
7
+ }
8
+ name;
9
+ assert;
10
+ userContext;
11
+ // Internal fallback assert for modules with no direct tests
12
+ context = new TestContext();
13
+ moduleChain = [];
14
+ beforeEachHooks = [];
15
+ afterEachHooks = [];
16
+ tests = [];
17
+ constructor(name) {
18
+ const parentModule = ModuleContext.currentModuleChain[ModuleContext.currentModuleChain.length - 1];
19
+ ModuleContext.currentModuleChain.push(this);
20
+ this.moduleChain = ModuleContext.currentModuleChain.slice(0);
21
+ this.name = parentModule ? `${parentModule.name} > ${name}` : name;
22
+ this.assert = new ModuleContext.Assert(this);
23
+ // User context uses prototype chain from parent module for proper QUnit-style inheritance:
24
+ // parent before() sets props on parent userContext, child tests inherit via prototype.
25
+ this.userContext = parentModule ? Object.create(parentModule.userContext) : Object.create(null);
26
+ return Object.freeze(this);
27
+ }
28
+ }
@@ -0,0 +1,25 @@
1
+ import type Assert from './assert.ts';
2
+ import type ModuleContext from './module-context.ts';
3
+ export default class TestContext {
4
+ #private;
5
+ static Assert: typeof Assert;
6
+ get name(): string | undefined;
7
+ set name(value: string | undefined);
8
+ get module(): ModuleContext | undefined;
9
+ set module(value: ModuleContext | undefined);
10
+ get asyncOps(): Promise<void>[];
11
+ set asyncOps(value: Promise<void>[]);
12
+ get assert(): Assert | undefined;
13
+ set assert(value: Assert | undefined);
14
+ get timeout(): number | undefined;
15
+ set timeout(value: number | undefined);
16
+ get steps(): string[];
17
+ set steps(value: string[]);
18
+ get expectedAssertionCount(): number | undefined;
19
+ set expectedAssertionCount(value: number | undefined);
20
+ get totalExecutedAssertions(): number;
21
+ set totalExecutedAssertions(value: number);
22
+ userContext: object;
23
+ constructor(name?: string, moduleContext?: ModuleContext);
24
+ finish(): void;
25
+ }
@@ -0,0 +1,94 @@
1
+ export default class TestContext {
2
+ static Assert;
3
+ #name;
4
+ get name() {
5
+ return this.#name;
6
+ }
7
+ set name(value) {
8
+ this.#name = value;
9
+ }
10
+ #module;
11
+ get module() {
12
+ return this.#module;
13
+ }
14
+ set module(value) {
15
+ this.#module = value;
16
+ }
17
+ #asyncOps = [];
18
+ get asyncOps() {
19
+ return this.#asyncOps;
20
+ }
21
+ set asyncOps(value) {
22
+ this.#asyncOps = value;
23
+ }
24
+ #assert;
25
+ get assert() {
26
+ return this.#assert;
27
+ }
28
+ set assert(value) {
29
+ this.#assert = value;
30
+ }
31
+ #timeout;
32
+ get timeout() {
33
+ return this.#timeout;
34
+ }
35
+ set timeout(value) {
36
+ this.#timeout = value;
37
+ }
38
+ #steps = [];
39
+ get steps() {
40
+ return this.#steps;
41
+ }
42
+ set steps(value) {
43
+ this.#steps = value;
44
+ }
45
+ #expectedAssertionCount;
46
+ get expectedAssertionCount() {
47
+ return this.#expectedAssertionCount;
48
+ }
49
+ set expectedAssertionCount(value) {
50
+ this.#expectedAssertionCount = value;
51
+ }
52
+ #totalExecutedAssertions = 0;
53
+ get totalExecutedAssertions() {
54
+ return this.#totalExecutedAssertions;
55
+ }
56
+ set totalExecutedAssertions(value) {
57
+ this.#totalExecutedAssertions = value;
58
+ }
59
+ userContext = {};
60
+ constructor(name, moduleContext) {
61
+ if (moduleContext) {
62
+ this.name = `${moduleContext.name} | ${name}`;
63
+ this.module = moduleContext;
64
+ this.module.tests.push(this);
65
+ this.assert = new TestContext.Assert(moduleContext, this);
66
+ }
67
+ }
68
+ finish() {
69
+ if (this.totalExecutedAssertions === 0) {
70
+ this.assert.pushResult({
71
+ result: false,
72
+ actual: this.totalExecutedAssertions,
73
+ expected: '> 0',
74
+ message: `Expected at least one assertion to be run for test: ${this.name}`,
75
+ });
76
+ }
77
+ else if (this.steps.length > 0) {
78
+ this.assert.pushResult({
79
+ result: false,
80
+ actual: this.steps,
81
+ expected: [],
82
+ message: `Expected assert.verifySteps() to be called before end of test after using assert.step(). Unverified steps: ${this.steps.join(', ')}`,
83
+ });
84
+ }
85
+ else if (this.expectedAssertionCount && this.expectedAssertionCount !== this.totalExecutedAssertions) {
86
+ this.assert.pushResult({
87
+ result: false,
88
+ actual: this.totalExecutedAssertions,
89
+ expected: this.expectedAssertionCount,
90
+ message: `Expected ${this.expectedAssertionCount} assertions, but ${this.totalExecutedAssertions} were run for test: ${this.name}`,
91
+ });
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,64 @@
1
+ export interface AssertionErrorOptions {
2
+ message?: string;
3
+ actual?: unknown;
4
+ expected?: unknown;
5
+ operator?: string;
6
+ stackStartFn?: (...args: never[]) => unknown;
7
+ }
8
+ export type InspectFn = (value: unknown, options?: {
9
+ depth?: number;
10
+ colors?: boolean;
11
+ compact?: boolean | number;
12
+ }) => string;
13
+ export interface AssertionErrorConstructor {
14
+ new (options: AssertionErrorOptions): Error;
15
+ }
16
+ /** Result object passed to {@linkcode Assert.prototype.pushResult}. */
17
+ export interface PushResultInfo {
18
+ /** Whether the assertion passed (`true`) or failed (`false`). */
19
+ result?: boolean;
20
+ /** The actual value produced by the code under test. */
21
+ actual?: unknown;
22
+ /** The expected value. */
23
+ expected?: unknown;
24
+ /** Human-readable description of the assertion. */
25
+ message?: string;
26
+ }
27
+ export interface QUnitObject {
28
+ equiv(a: unknown, b: unknown): boolean;
29
+ config: Record<string, unknown>;
30
+ [key: string]: unknown;
31
+ }
32
+ /** Minimal assertion-tracking state that Assert reads/writes per test. */
33
+ export interface TestState {
34
+ totalExecutedAssertions: number;
35
+ steps: string[];
36
+ asyncOps: Promise<void>[];
37
+ timeout?: number;
38
+ expectedAssertionCount?: number;
39
+ userContext?: object;
40
+ module?: {
41
+ name: string;
42
+ };
43
+ }
44
+ /** Minimal module shape that Assert needs to resolve a fallback test context. */
45
+ export interface ModuleState {
46
+ context: TestState;
47
+ }
48
+ /** A lifecycle hook callback that receives an {@linkcode Assert} instance. */
49
+ export type HookFn<A = unknown> = (assert: A) => void | Promise<void>;
50
+ export type TestFn<A = unknown> = (assert: A, meta: {
51
+ testName: string;
52
+ options: unknown;
53
+ }) => void | Promise<unknown>;
54
+ /** Lifecycle hooks available inside a `module()` callback. */
55
+ export interface HooksObject<A = unknown> {
56
+ /** Runs once before the first test in the module. */
57
+ before(fn: HookFn<A>): void;
58
+ /** Runs before each test in the module. */
59
+ beforeEach(fn: HookFn<A>): void;
60
+ /** Runs after each test in the module. */
61
+ afterEach(fn: HookFn<A>): void;
62
+ /** Runs once after the last test in the module. */
63
+ after(fn: HookFn<A>): void;
64
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qunitx",
3
3
  "type": "module",
4
- "version": "1.0.3",
4
+ "version": "1.1.0",
5
5
  "description": "A universal test framework for testing any js file on node.js, browser or deno with QUnit API",
6
6
  "author": "Izel Nakri",
7
7
  "license": "MIT",
@@ -48,51 +48,62 @@
48
48
  },
49
49
  "imports": {
50
50
  "qunitx": {
51
- "node": "./shims/node/index.js",
52
- "deno": "./shims/deno/index.js",
53
- "default": "./shims/browser/index.js"
51
+ "types": "./dist/node/index.d.ts",
52
+ "node": "./dist/node/index.js",
53
+ "deno": "./shims/deno/index.ts",
54
+ "default": "./dist/browser/index.js"
54
55
  }
55
56
  },
56
57
  "exports": {
57
- "node": "./shims/node/index.js",
58
- "deno": "./shims/deno/index.js",
59
- "default": "./shims/browser/index.js"
58
+ "types": "./dist/node/index.d.ts",
59
+ "node": "./dist/node/index.js",
60
+ "deno": "./shims/deno/index.ts",
61
+ "default": "./dist/browser/index.js"
60
62
  },
63
+ "files": [
64
+ "dist/",
65
+ "shims/deno/",
66
+ "shims/shared/",
67
+ "shims/types.ts",
68
+ "vendor/"
69
+ ],
61
70
  "repository": {
62
71
  "type": "git",
63
72
  "url": "git+https://github.com/izelnakri/qunitx.git"
64
73
  },
65
74
  "scripts": {
66
- "format": "prettier --check \"test/**/*.js\" \"*.js\" \"package.json\"",
67
- "format:fix": "prettier --write \"test/**/*.js\" \"*.js\" \"package.json\"",
75
+ "format": "prettier --check \"test/**/*.ts\" \"*.ts\" \"package.json\"",
76
+ "format:fix": "prettier --write \"test/**/*.ts\" \"*.ts\" \"package.json\"",
68
77
  "lint": "deno lint shims/",
69
- "lint:docs": "deno doc --lint shims/deno/module.js shims/deno/test.js",
70
- "build": "node build.js",
78
+ "lint:docs": "deno doc --lint shims/deno/module.ts shims/deno/test.ts",
79
+ "build": "node --experimental-strip-types build.ts",
80
+ "pretest": "node --experimental-strip-types build.ts",
71
81
  "run:all": "npm run run:node && npm run run:deno",
72
- "run:node": "node --test test/helpers/passing-tests.js && node --test test/helpers/failing-tests.js",
73
- "run:deno": "deno test test/helpers/passing-tests.js && deno test test/helpers/failing-tests.js",
82
+ "run:node": "node --experimental-strip-types --test test/helpers/passing-tests.ts && node --experimental-strip-types --test test/helpers/failing-tests.ts",
83
+ "run:deno": "deno test test/helpers/passing-tests.ts && deno test test/helpers/failing-tests.ts",
74
84
  "changelog:unreleased": "git-cliff --unreleased --strip all",
75
85
  "changelog:preview": "git-cliff",
76
86
  "changelog:update": "git-cliff --output CHANGELOG.md",
77
- "prepack": "npm run build",
87
+ "prepare": "npm run build",
78
88
  "test": "npm run test:browser && npm run test:node && npm run test:deno",
79
89
  "test:dev": "npm run test | tee test-output.log",
80
- "test:browser": "qunitx test/index.js --debug",
81
- "test:deno": "deno test --allow-read --allow-env --allow-run test/index.js",
82
- "test:doctests": "deno test --doc --allow-env --allow-read shims/deno/module.js shims/deno/test.js",
83
- "test:node": "node --test test/index.js",
84
- "coverage": "deno test --coverage=tmp/coverage --allow-read --allow-env --allow-run test/index.js && deno coverage --lcov --output=tmp/coverage/lcov.info --include='shims/' tmp/coverage && node scripts/check-coverage.js",
90
+ "test:browser": "qunitx test/index.ts --debug",
91
+ "test:deno": "deno test --allow-read --allow-env --allow-run test/index.ts",
92
+ "test:doctests": "deno test --doc --allow-env --allow-read shims/deno/module.ts shims/deno/test.ts",
93
+ "test:node": "node --experimental-strip-types --test test/index.ts",
94
+ "coverage": "deno test --coverage=tmp/coverage --allow-read --allow-env --allow-run test/index.ts && deno coverage --lcov --output=tmp/coverage/lcov.info --include='shims/' tmp/coverage && node --experimental-strip-types scripts/check-coverage.ts",
85
95
  "coverage:report": "npm run coverage && deno coverage --html --include='shims/' tmp/coverage",
86
96
  "bench": "deno task bench",
87
97
  "bench:check": "deno task bench:check",
88
98
  "bench:update": "deno task bench:update",
89
- "docs": "deno doc --html --name=\"QUnitX\" --output=docs/src shims/deno/index.js"
99
+ "docs": "deno doc --html --name=\"QUnitX\" --output=docs/src shims/deno/index.ts"
90
100
  },
91
101
  "devDependencies": {
92
102
  "prettier": "^3.8.1",
93
103
  "qunit": "^2.25.0",
94
- "qunitx": "^0.9.3",
95
- "qunitx-cli": "^0.5.0"
104
+ "qunitx": "^1.0.3",
105
+ "qunitx-cli": "^0.5.3",
106
+ "typescript": "^6.0.2"
96
107
  },
97
108
  "volta": {
98
109
  "node": "24.14.0"
@@ -25,12 +25,13 @@
25
25
  * @module
26
26
  */
27
27
  import { AssertionError as DenoAssertionError } from "jsr:@std/assert";
28
+ import type { AssertionErrorOptions } from "../types.ts";
28
29
  import '../../vendor/qunit.js';
29
- import Assert from '../shared/assert.js';
30
- import ModuleContext from '../shared/module-context.js';
31
- import TestContext from '../shared/test-context.js';
32
- import Module from './module.js';
33
- import Test from './test.js';
30
+ import Assert from '../shared/assert.ts';
31
+ import ModuleContext from '../shared/module-context.ts';
32
+ import TestContext from '../shared/test-context.ts';
33
+ import Module from './module.ts';
34
+ import Test from './test.ts';
34
35
 
35
36
  /**
36
37
  * Thrown when an assertion fails. Extends Deno's built-in `AssertionError`
@@ -51,13 +52,14 @@ import Test from './test.js';
51
52
  * ```
52
53
  */
53
54
  export class AssertionError extends DenoAssertionError {
54
- constructor(object) {
55
- super(object.message);
55
+ constructor(object: AssertionErrorOptions) {
56
+ super(object.message ?? 'Assertion failed');
56
57
  }
57
58
  }
58
59
 
59
- Assert.QUnit = globalThis.QUnit;
60
+ Assert.QUnit = (globalThis as typeof globalThis & { QUnit: typeof Assert.QUnit }).QUnit;
60
61
  Assert.AssertionError = AssertionError;
62
+ Assert.inspect = Deno.inspect;
61
63
  ModuleContext.Assert = Assert;
62
64
  TestContext.Assert = Assert;
63
65
 
@@ -0,0 +1,93 @@
1
+ import { describe, beforeAll, afterAll } from "jsr:@std/testing/bdd";
2
+ import type Assert from '../shared/assert.ts';
3
+ import type { HooksObject } from '../types.ts';
4
+ import ModuleContext from '../shared/module-context.ts';
5
+ export type { Assert };
6
+ export type { HookFn, HooksObject, PushResultInfo } from '../types.ts';
7
+
8
+ /**
9
+ * Defines a test module (suite) for Deno's BDD test runner.
10
+ *
11
+ * Wraps `describe()` from `@std/testing/bdd` and sets up the QUnit lifecycle
12
+ * (before/beforeEach/afterEach/after hooks, assertion counting, steps tracking).
13
+ *
14
+ * @param {string} moduleName - Name of the test suite
15
+ * @param {object} [runtimeOptions] - Optional Deno BDD options forwarded to `describe()`
16
+ * (e.g. `{ concurrency: false }`, `{ permissions: { read: true } }`)
17
+ * @param {function} moduleContent - Callback that defines tests and hooks via `hooks.before`,
18
+ * `hooks.beforeEach`, `hooks.afterEach`, `hooks.after`
19
+ * @returns {void}
20
+ * @example
21
+ * ```js ignore
22
+ * import { module, test } from "qunitx";
23
+ *
24
+ * module("Math", (hooks) => {
25
+ * hooks.before((assert) => {
26
+ * assert.step("before hook ran");
27
+ * });
28
+ *
29
+ * test("addition", (assert) => {
30
+ * assert.equal(2 + 2, 4);
31
+ * });
32
+ * });
33
+ * ```
34
+ */
35
+ export default function module(
36
+ moduleName: string,
37
+ runtimeOptions: object | ((hooks: HooksObject<Assert>) => void),
38
+ moduleContent?: (hooks: HooksObject<Assert>, meta: { moduleName: string; options: unknown }) => void,
39
+ ): void {
40
+ const targetRuntimeOptions = moduleContent ? runtimeOptions as object : {};
41
+ const targetModuleContent = (moduleContent ? moduleContent : runtimeOptions) as (hooks: HooksObject<Assert>, meta: { moduleName: string; options: unknown }) => void;
42
+ const moduleContext = new ModuleContext(moduleName);
43
+
44
+ describe(moduleName, { ...targetRuntimeOptions }, function () {
45
+ const beforeHooks: ((assert: Assert) => void | Promise<void>)[] = [];
46
+ const afterHooks: ((assert: Assert) => void | Promise<void>)[] = [];
47
+
48
+ beforeAll(async function () {
49
+ // before() assertions are attributed to the first direct test only (matching QUnit's model).
50
+ // Tests inherit parent context via prototype chain, so no Object.assign needed.
51
+ const firstTest = moduleContext.tests[0];
52
+ const beforeAssert = firstTest ? firstTest.assert! : moduleContext.assert!;
53
+
54
+ for (const hook of beforeHooks) {
55
+ await hook.call(moduleContext.userContext, beforeAssert);
56
+ }
57
+ });
58
+
59
+ afterAll(async () => {
60
+ for (const testContext of moduleContext.tests) {
61
+ await testContext.assert!.waitForAsyncOps();
62
+ }
63
+
64
+ const lastTest = moduleContext.tests[moduleContext.tests.length - 1];
65
+ if (lastTest) {
66
+ for (let j = afterHooks.length - 1; j >= 0; j--) {
67
+ await afterHooks[j]!.call(lastTest.userContext, lastTest.assert!);
68
+ }
69
+ }
70
+
71
+ for (let i = 0, len = moduleContext.tests.length; i < len; i++) {
72
+ moduleContext.tests[i].finish();
73
+ }
74
+ });
75
+
76
+ targetModuleContent.call(moduleContext.userContext, {
77
+ before(beforeFn) {
78
+ beforeHooks.push(beforeFn);
79
+ },
80
+ beforeEach(beforeEachFn) {
81
+ moduleContext.beforeEachHooks.push(beforeEachFn);
82
+ },
83
+ afterEach(afterEachFn) {
84
+ moduleContext.afterEachHooks.push(afterEachFn);
85
+ },
86
+ after(afterFn) {
87
+ afterHooks.push(afterFn);
88
+ }
89
+ }, { moduleName, options: runtimeOptions });
90
+
91
+ ModuleContext.currentModuleChain.pop();
92
+ });
93
+ }