qunitx 1.2.6 → 1.2.7
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.
- package/README.md +104 -20
- package/dist/.build-hash +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser/index.d.ts +20 -20
- package/dist/browser/index.js +52 -52
- package/dist/deno/index.d.ts +20 -19
- package/dist/deno/index.js +108 -163
- package/dist/deno/module.d.ts +2 -2
- package/dist/deno/module.js +11 -12
- package/dist/deno/test.d.ts +2 -2
- package/dist/deno/test.js +24 -25
- package/dist/node/index.d.ts +3 -2
- package/dist/node/index.js +1 -1
- package/dist/node/module.js +11 -12
- package/dist/node/test.js +23 -24
- package/dist/shared/assert.d.ts +2 -2
- package/dist/shared/assert.js +26 -32
- package/dist/shared/index.d.ts +1 -13
- package/dist/shared/index.js +11 -13
- package/dist/shared/module-context.d.ts +3 -3
- package/dist/shared/module-context.js +4 -4
- package/dist/shared/test-context.d.ts +10 -18
- package/dist/shared/test-context.js +26 -74
- package/dist/types.d.ts +1 -1
- package/package.json +2 -2
- package/shims/deno/index.ts +21 -21
- package/shims/deno/module.ts +15 -15
- package/shims/deno/test.ts +30 -29
- package/shims/shared/assert.ts +37 -33
- package/shims/shared/index.ts +14 -27
- package/shims/shared/module-context.ts +5 -5
- package/shims/shared/test-context.ts +27 -84
- package/shims/types.ts +1 -1
package/dist/types.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export interface TestState {
|
|
|
44
44
|
}
|
|
45
45
|
/** Minimal module shape that Assert needs to resolve a fallback test context. */
|
|
46
46
|
export interface ModuleState {
|
|
47
|
-
|
|
47
|
+
testContext: TestState;
|
|
48
48
|
}
|
|
49
49
|
/** A lifecycle hook callback that receives an {@linkcode Assert} instance and a meta object with the shared context. */
|
|
50
50
|
export type HookFn<A = unknown> = (assert: A, meta: {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qunitx",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.2.
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "1.2.7",
|
|
5
|
+
"description": "Universal test library — run the same test file in Node.js, Deno, and the browser with QUnit's battle-tested assertion API",
|
|
6
6
|
"author": "Izel Nakri",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"keywords": [
|
package/shims/deno/index.ts
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
*
|
|
25
25
|
* @module
|
|
26
26
|
*/
|
|
27
|
-
import { AssertionError as DenoAssertionError } from "jsr:@std/assert";
|
|
28
|
-
import type { AssertionErrorOptions } from "../types.ts";
|
|
29
27
|
import '../../vendor/qunit.js';
|
|
28
|
+
import { AssertionError as DenoAssertionError } from 'jsr:@std/assert';
|
|
30
29
|
import Assert from '../shared/assert.ts';
|
|
31
30
|
import ModuleContext from '../shared/module-context.ts';
|
|
32
31
|
import TestContext from '../shared/test-context.ts';
|
|
32
|
+
import type { AssertionErrorOptions } from '../types.ts';
|
|
33
33
|
import Module from './module.ts';
|
|
34
34
|
import Test from './test.ts';
|
|
35
35
|
|
|
@@ -111,6 +111,25 @@ export { Assert };
|
|
|
111
111
|
*/
|
|
112
112
|
export { default as module } from './module.ts';
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Registers a skipped test. Equivalent to `test.skip`. The test body is never
|
|
116
|
+
* executed and the test is reported as ignored by Deno's runner.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} testName - Name of the test to skip.
|
|
119
|
+
* @param {function} [_testContent] - Optional body (ignored — the test will not run).
|
|
120
|
+
* @example
|
|
121
|
+
* ```js
|
|
122
|
+
* import { module, skip } from "qunitx";
|
|
123
|
+
*
|
|
124
|
+
* module("Math", () => {
|
|
125
|
+
* skip("addition is not yet implemented", (assert) => {
|
|
126
|
+
* assert.equal(1 + 1, 2);
|
|
127
|
+
* });
|
|
128
|
+
* });
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export const skip = Test.skip;
|
|
132
|
+
|
|
114
133
|
/**
|
|
115
134
|
* Defines an individual test. Wraps Deno's `it()` and handles the full QUnit
|
|
116
135
|
* lifecycle: `beforeEach`/`afterEach` hooks, async assertion waiting, and step
|
|
@@ -142,25 +161,6 @@ export { default as module } from './module.ts';
|
|
|
142
161
|
* ```
|
|
143
162
|
*/
|
|
144
163
|
export { default as test } from './test.ts';
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Registers a skipped test. Equivalent to `test.skip`. The test body is never
|
|
148
|
-
* executed and the test is reported as ignored by Deno's runner.
|
|
149
|
-
*
|
|
150
|
-
* @param {string} testName - Name of the test to skip.
|
|
151
|
-
* @param {function} [_testContent] - Optional body (ignored — the test will not run).
|
|
152
|
-
* @example
|
|
153
|
-
* ```js
|
|
154
|
-
* import { module, skip } from "qunitx";
|
|
155
|
-
*
|
|
156
|
-
* module("Math", () => {
|
|
157
|
-
* skip("addition is not yet implemented", (assert) => {
|
|
158
|
-
* assert.equal(1 + 1, 2);
|
|
159
|
-
* });
|
|
160
|
-
* });
|
|
161
|
-
* ```
|
|
162
|
-
*/
|
|
163
|
-
export const skip = Test.skip;
|
|
164
164
|
export const todo = Test.todo;
|
|
165
165
|
|
|
166
166
|
/**
|
package/shims/deno/module.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { afterAll, beforeAll, describe } from 'jsr:@std/testing/bdd';
|
|
2
2
|
import type Assert from '../shared/assert.ts';
|
|
3
|
-
import type { HookFn, HooksObject } from '../types.ts';
|
|
4
3
|
import ModuleContext from '../shared/module-context.ts';
|
|
5
|
-
|
|
6
|
-
export type { HookFn, HooksObject, PushResultInfo } from '../types.ts';
|
|
4
|
+
import type { HookFn, HooksObject } from '../types.ts';
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* Defines a test module (suite) for Deno's BDD test runner.
|
|
@@ -53,14 +51,14 @@ export default function module(
|
|
|
53
51
|
return;
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
const targetModuleContent = (moduleContent
|
|
54
|
+
const targetModuleContent = (moduleContent ?? runtimeOptions) as (hooks: HooksObject<Assert>, meta: { moduleName: string; options: unknown }) => void;
|
|
57
55
|
const moduleContext = new ModuleContext(moduleName);
|
|
58
56
|
|
|
59
57
|
describe(moduleName, { ...targetRuntimeOptions }, function () {
|
|
60
58
|
const beforeHooks: HookFn<Assert>[] = [];
|
|
61
59
|
const afterHooks: HookFn<Assert>[] = [];
|
|
62
60
|
|
|
63
|
-
beforeAll(async
|
|
61
|
+
beforeAll(async () => {
|
|
64
62
|
// before() assertions are attributed to the first direct test only (matching QUnit's model).
|
|
65
63
|
// Tests inherit parent context via prototype chain, so no Object.assign needed.
|
|
66
64
|
const firstTest = moduleContext.tests[0];
|
|
@@ -72,19 +70,18 @@ export default function module(
|
|
|
72
70
|
});
|
|
73
71
|
|
|
74
72
|
afterAll(async () => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
73
|
+
const allAsyncOps = moduleContext.tests.flatMap((t) => t.asyncOps);
|
|
74
|
+
if (allAsyncOps.length > 0) await Promise.all(allAsyncOps);
|
|
78
75
|
|
|
79
|
-
const lastTest = moduleContext.tests
|
|
76
|
+
const lastTest = moduleContext.tests.at(-1);
|
|
80
77
|
if (lastTest) {
|
|
81
|
-
for (
|
|
82
|
-
await
|
|
78
|
+
for (const hook of afterHooks.toReversed()) {
|
|
79
|
+
await hook.call(lastTest.userContext, lastTest.assert!, { context: lastTest.userContext });
|
|
83
80
|
}
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
for (
|
|
87
|
-
|
|
83
|
+
for (const testCtx of moduleContext.tests) {
|
|
84
|
+
testCtx.finish();
|
|
88
85
|
}
|
|
89
86
|
});
|
|
90
87
|
|
|
@@ -101,7 +98,7 @@ export default function module(
|
|
|
101
98
|
after(afterFn) {
|
|
102
99
|
afterHooks.push(afterFn);
|
|
103
100
|
}
|
|
104
|
-
}, { moduleName, options:
|
|
101
|
+
}, { moduleName, options: targetRuntimeOptions, context: moduleContext.userContext });
|
|
105
102
|
|
|
106
103
|
ModuleContext.currentModuleChain.pop();
|
|
107
104
|
});
|
|
@@ -149,3 +146,6 @@ module.skip = function skipModule(moduleName: string, _moduleContent?: unknown):
|
|
|
149
146
|
module.todo = function todoModule(moduleName: string, _moduleContent?: unknown): void {
|
|
150
147
|
describe(moduleName, { ignore: true }, function () {});
|
|
151
148
|
};
|
|
149
|
+
|
|
150
|
+
export type { Assert };
|
|
151
|
+
export type { HookFn, HooksObject, PushResultInfo } from '../types.ts';
|
package/shims/deno/test.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { it } from
|
|
1
|
+
import { it } from 'jsr:@std/testing/bdd';
|
|
2
2
|
import type Assert from '../shared/assert.ts';
|
|
3
|
-
import TestContext from '../shared/test-context.ts';
|
|
4
3
|
import ModuleContext from '../shared/module-context.ts';
|
|
5
|
-
|
|
6
|
-
export type { PushResultInfo } from '../types.ts';
|
|
4
|
+
import TestContext from '../shared/test-context.ts';
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* Defines an individual test within a module for Deno's BDD test runner.
|
|
@@ -59,7 +57,7 @@ export default function test(
|
|
|
59
57
|
return;
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
const targetTestContent = (testContent
|
|
60
|
+
const targetTestContent = (testContent ?? runtimeOptions) as (assert: Assert, meta: { testName: string; options: unknown }) => void | Promise<void>;
|
|
63
61
|
const context = new TestContext(testName, moduleContext);
|
|
64
62
|
|
|
65
63
|
// Each test gets a fresh plain object inheriting from the module's user context.
|
|
@@ -69,38 +67,38 @@ export default function test(
|
|
|
69
67
|
context.userContext = userContext;
|
|
70
68
|
|
|
71
69
|
it(testName, { ...targetRuntimeOptions }, function () {
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
71
|
+
context.rejectTimeout = reject;
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
const hookMeta = { context: userContext };
|
|
73
|
+
const hookMeta = { context: userContext };
|
|
77
74
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
(async () => {
|
|
76
|
+
try {
|
|
77
|
+
for (const mod of context.module!.moduleChain) {
|
|
78
|
+
for (const hook of mod.beforeEachHooks) {
|
|
79
|
+
await hook.call(userContext, context.assert!, hookMeta);
|
|
83
80
|
}
|
|
81
|
+
}
|
|
84
82
|
|
|
85
|
-
|
|
83
|
+
await targetTestContent.call(userContext, context.assert!, { testName, options: targetRuntimeOptions, context: userContext });
|
|
86
84
|
|
|
87
|
-
|
|
85
|
+
await context.assert!.waitForAsyncOps();
|
|
88
86
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
await module.afterEachHooks[j]!.call(userContext, context.assert!, hookMeta);
|
|
93
|
-
}
|
|
87
|
+
for (const mod of context.module!.moduleChain.toReversed()) {
|
|
88
|
+
for (const hook of mod.afterEachHooks.toReversed()) {
|
|
89
|
+
await hook.call(userContext, context.assert!, hookMeta);
|
|
94
90
|
}
|
|
95
|
-
|
|
96
|
-
resolve(result);
|
|
97
|
-
} catch (err) {
|
|
98
|
-
reject(err);
|
|
99
|
-
} finally {
|
|
100
|
-
context.clearTimeoutHandle();
|
|
101
91
|
}
|
|
102
|
-
|
|
103
|
-
|
|
92
|
+
|
|
93
|
+
resolve();
|
|
94
|
+
} catch (err) {
|
|
95
|
+
reject(err);
|
|
96
|
+
} finally {
|
|
97
|
+
context.clearTimeoutHandle();
|
|
98
|
+
}
|
|
99
|
+
})();
|
|
100
|
+
|
|
101
|
+
return promise;
|
|
104
102
|
});
|
|
105
103
|
}
|
|
106
104
|
|
|
@@ -146,3 +144,6 @@ test.skip = function skipTest(testName: string, _testContent?: unknown): void {
|
|
|
146
144
|
test.todo = function todoTest(testName: string, _testContent?: unknown): void {
|
|
147
145
|
it(testName, { ignore: true }, async function () {});
|
|
148
146
|
};
|
|
147
|
+
|
|
148
|
+
export type { Assert };
|
|
149
|
+
export type { PushResultInfo } from '../types.ts';
|
package/shims/shared/assert.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import '../../vendor/qunit.js';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import type { AssertionErrorConstructor, InspectFn, ModuleState, PushResultInfo, QUnitObject, TestState } from '../types.ts';
|
|
3
|
+
import { objectValues, objectValuesSubset, validateException, validateExpectedExceptionArgs } from './index.ts';
|
|
4
|
+
|
|
5
|
+
// Sentinel used by rejects() to distinguish "promise resolved" from "caught undefined".
|
|
6
|
+
const PENDING = Symbol('pending');
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* The assertion object passed to every test callback and lifecycle hook.
|
|
@@ -34,11 +37,11 @@ export default class Assert {
|
|
|
34
37
|
|
|
35
38
|
/** @internal */
|
|
36
39
|
constructor(module: ModuleState | null, test?: TestState) {
|
|
37
|
-
this.test = test || (module as ModuleState).
|
|
40
|
+
this.test = test || (module as ModuleState).testContext;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
/** @internal */
|
|
41
|
-
_incrementAssertionCount() {
|
|
44
|
+
private _incrementAssertionCount() {
|
|
42
45
|
this.test.totalExecutedAssertions++;
|
|
43
46
|
}
|
|
44
47
|
|
|
@@ -80,22 +83,16 @@ export default class Assert {
|
|
|
80
83
|
* ```
|
|
81
84
|
*/
|
|
82
85
|
step(message: string): void {
|
|
83
|
-
let assertionMessage = message;
|
|
84
|
-
let result = !!message;
|
|
85
|
-
|
|
86
86
|
this.test.steps.push(message);
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
const result = typeof message === 'string' && message.length > 0;
|
|
89
|
+
const assertionMessage = result
|
|
90
|
+
? message
|
|
91
|
+
: (message === undefined || message === '')
|
|
92
|
+
? 'You must provide a message to assert.step'
|
|
93
|
+
: 'You must provide a string value to assert.step';
|
|
94
94
|
|
|
95
|
-
this.pushResult({
|
|
96
|
-
result,
|
|
97
|
-
message: assertionMessage
|
|
98
|
-
});
|
|
95
|
+
this.pushResult({ result, message: assertionMessage });
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
/**
|
|
@@ -159,12 +156,9 @@ export default class Assert {
|
|
|
159
156
|
* ```
|
|
160
157
|
*/
|
|
161
158
|
async(): () => void {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
this.test.asyncOps.push(done);
|
|
166
|
-
|
|
167
|
-
return () => { resolveFn(); };
|
|
159
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
160
|
+
this.test.asyncOps.push(promise);
|
|
161
|
+
return resolve;
|
|
168
162
|
}
|
|
169
163
|
|
|
170
164
|
/** @internal Used by the test runner to wait for all async operations to complete. */
|
|
@@ -580,7 +574,7 @@ export default class Assert {
|
|
|
580
574
|
* ```
|
|
581
575
|
*/
|
|
582
576
|
throws(blockFn: unknown, expectedInput?: unknown, assertionMessage?: string): void {
|
|
583
|
-
this
|
|
577
|
+
this._incrementAssertionCount();
|
|
584
578
|
const [expected, message] = validateExpectedExceptionArgs(expectedInput, assertionMessage, 'throws');
|
|
585
579
|
if (typeof blockFn !== 'function') {
|
|
586
580
|
throw new Assert.AssertionError({
|
|
@@ -591,8 +585,9 @@ export default class Assert {
|
|
|
591
585
|
});
|
|
592
586
|
}
|
|
593
587
|
|
|
588
|
+
let returnValue: unknown;
|
|
594
589
|
try {
|
|
595
|
-
blockFn();
|
|
590
|
+
returnValue = blockFn();
|
|
596
591
|
} catch (error) {
|
|
597
592
|
const [result, validatedExpected, validatedMessage] = validateException(error, expected, message);
|
|
598
593
|
if (result === false) {
|
|
@@ -607,6 +602,15 @@ export default class Assert {
|
|
|
607
602
|
return;
|
|
608
603
|
}
|
|
609
604
|
|
|
605
|
+
if (returnValue !== null && typeof returnValue === 'object' && typeof (returnValue as PromiseLike<unknown>).then === 'function') {
|
|
606
|
+
throw new Assert.AssertionError({
|
|
607
|
+
actual: returnValue,
|
|
608
|
+
expected: expected,
|
|
609
|
+
message: 'Function passed to `assert.throws` returned a Promise — did you mean to use `assert.rejects`?',
|
|
610
|
+
stackStartFn: this.throws,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
610
614
|
throw new Assert.AssertionError({
|
|
611
615
|
actual: blockFn,
|
|
612
616
|
expected: expected,
|
|
@@ -643,16 +647,14 @@ export default class Assert {
|
|
|
643
647
|
});
|
|
644
648
|
}
|
|
645
649
|
|
|
646
|
-
let
|
|
647
|
-
let rejectionError;
|
|
650
|
+
let caught: unknown = PENDING;
|
|
648
651
|
try {
|
|
649
652
|
await promise;
|
|
650
653
|
} catch (error) {
|
|
651
|
-
|
|
652
|
-
rejectionError = error;
|
|
654
|
+
caught = error;
|
|
653
655
|
}
|
|
654
656
|
|
|
655
|
-
if (
|
|
657
|
+
if (caught === PENDING) {
|
|
656
658
|
throw new Assert.AssertionError({
|
|
657
659
|
actual: promise,
|
|
658
660
|
expected: expected,
|
|
@@ -661,7 +663,7 @@ export default class Assert {
|
|
|
661
663
|
});
|
|
662
664
|
}
|
|
663
665
|
|
|
664
|
-
const [result, validatedExpected, validatedMessage] = validateException(
|
|
666
|
+
const [result, validatedExpected, validatedMessage] = validateException(caught, expected, message);
|
|
665
667
|
if (result === false) {
|
|
666
668
|
throw new Assert.AssertionError({
|
|
667
669
|
actual: result,
|
|
@@ -671,7 +673,7 @@ export default class Assert {
|
|
|
671
673
|
});
|
|
672
674
|
}
|
|
673
675
|
}
|
|
674
|
-
}
|
|
676
|
+
}
|
|
675
677
|
|
|
676
678
|
function defaultMessage(actual: unknown, description: string, expected: unknown): string {
|
|
677
679
|
return `
|
|
@@ -683,6 +685,8 @@ ${description}
|
|
|
683
685
|
${inspect(expected)}`
|
|
684
686
|
}
|
|
685
687
|
|
|
688
|
+
const INSPECT_OPTIONS = { depth: 10, colors: true, compact: false as const };
|
|
689
|
+
|
|
686
690
|
function inspect(value: unknown): string {
|
|
687
|
-
return Assert.inspect(value,
|
|
691
|
+
return Assert.inspect(value, INSPECT_OPTIONS);
|
|
688
692
|
}
|
package/shims/shared/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const hasOwn = Object.prototype.hasOwnProperty
|
|
1
|
+
const hasOwn = Object.prototype.hasOwnProperty;
|
|
2
2
|
|
|
3
3
|
export function objectType(obj: unknown): string {
|
|
4
4
|
if (typeof obj === 'undefined') {
|
|
@@ -32,12 +32,8 @@ export function objectType(obj: unknown): string {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function is(type: string, obj: unknown): boolean {
|
|
36
|
-
return objectType(obj) === type;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
35
|
export function objectValues(obj: unknown, allowArray = true): unknown {
|
|
40
|
-
const vals: Record<string, unknown> | unknown[] = allowArray &&
|
|
36
|
+
const vals: Record<string, unknown> | unknown[] = allowArray && objectType(obj) === 'array' ? [] : {};
|
|
41
37
|
|
|
42
38
|
for (const key in obj as object) {
|
|
43
39
|
if (hasOwn.call(obj, key)) {
|
|
@@ -50,13 +46,8 @@ export function objectValues(obj: unknown, allowArray = true): unknown {
|
|
|
50
46
|
}
|
|
51
47
|
|
|
52
48
|
/**
|
|
53
|
-
*
|
|
54
49
|
* Recursively clone an object into a plain object, taking only the
|
|
55
|
-
* subset of own enumerable properties that exist a given model.
|
|
56
|
-
*
|
|
57
|
-
* @param {any} obj
|
|
58
|
-
* @param {any} model
|
|
59
|
-
* @return {Object}
|
|
50
|
+
* subset of own enumerable properties that exist in a given model.
|
|
60
51
|
*/
|
|
61
52
|
export function objectValuesSubset(obj: unknown, model: unknown): unknown {
|
|
62
53
|
// Return primitive values unchanged to avoid false positives or confusing
|
|
@@ -95,14 +86,15 @@ export function validateExpectedExceptionArgs(
|
|
|
95
86
|
expected = undefined;
|
|
96
87
|
return [expected, message];
|
|
97
88
|
} else {
|
|
98
|
-
throw new Error(
|
|
89
|
+
throw new Error(
|
|
90
|
+
`assert.${assertionMethod} does not accept a string value for the expected argument.\n` +
|
|
91
|
+
'Use a non-string object value (e.g. RegExp or validator function) instead if necessary.',
|
|
92
|
+
);
|
|
99
93
|
}
|
|
100
94
|
}
|
|
101
|
-
const valid = !expected ||
|
|
102
|
-
// TODO: be more explicit here
|
|
103
|
-
expectedType === 'regexp' || expectedType === 'function' || expectedType === 'object';
|
|
95
|
+
const valid = !expected || expectedType === 'regexp' || expectedType === 'function' || expectedType === 'object';
|
|
104
96
|
if (!valid) {
|
|
105
|
-
throw new Error(
|
|
97
|
+
throw new Error(`Invalid expected value type (${expectedType}) provided to assert.${assertionMethod}.`);
|
|
106
98
|
}
|
|
107
99
|
return [expected, message];
|
|
108
100
|
}
|
|
@@ -170,15 +162,10 @@ function errorString(error: unknown): string {
|
|
|
170
162
|
// an object literal with name and message properties...
|
|
171
163
|
if (resultErrorString.slice(0, 7) === '[object') {
|
|
172
164
|
// Based on https://es5.github.io/#x15.11.4.4
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
? ': '.concat((error as { message: string }).message)
|
|
177
|
-
: '')
|
|
178
|
-
);
|
|
179
|
-
} else {
|
|
180
|
-
return resultErrorString;
|
|
165
|
+
const name = (error as { name?: string }).name || 'Error';
|
|
166
|
+
const msg = (error as { message?: string }).message;
|
|
167
|
+
return msg ? `${name}: ${msg}` : name;
|
|
181
168
|
}
|
|
182
|
-
}
|
|
183
169
|
|
|
184
|
-
|
|
170
|
+
return resultErrorString;
|
|
171
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type Assert from './assert.ts';
|
|
2
1
|
import type { HookFn } from '../types.ts';
|
|
2
|
+
import type Assert from './assert.ts';
|
|
3
3
|
import TestContext from './test-context.ts';
|
|
4
4
|
|
|
5
5
|
export default class ModuleContext {
|
|
@@ -7,7 +7,7 @@ export default class ModuleContext {
|
|
|
7
7
|
static currentModuleChain: ModuleContext[] = [];
|
|
8
8
|
|
|
9
9
|
static get lastModule() {
|
|
10
|
-
return this.currentModuleChain
|
|
10
|
+
return this.currentModuleChain.at(-1);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
name!: string;
|
|
@@ -15,7 +15,7 @@ export default class ModuleContext {
|
|
|
15
15
|
userContext!: Record<string, unknown>;
|
|
16
16
|
|
|
17
17
|
// Internal fallback assert for modules with no direct tests
|
|
18
|
-
|
|
18
|
+
testContext = new TestContext();
|
|
19
19
|
|
|
20
20
|
moduleChain: ModuleContext[] = [];
|
|
21
21
|
beforeEachHooks: HookFn<Assert>[] = [];
|
|
@@ -23,11 +23,11 @@ export default class ModuleContext {
|
|
|
23
23
|
tests: TestContext[] = [];
|
|
24
24
|
|
|
25
25
|
constructor(name: string) {
|
|
26
|
-
const parentModule = ModuleContext.currentModuleChain
|
|
26
|
+
const parentModule = ModuleContext.currentModuleChain.at(-1);
|
|
27
27
|
|
|
28
28
|
ModuleContext.currentModuleChain.push(this);
|
|
29
29
|
|
|
30
|
-
this.moduleChain = ModuleContext.currentModuleChain
|
|
30
|
+
this.moduleChain = [...ModuleContext.currentModuleChain];
|
|
31
31
|
this.name = parentModule ? `${parentModule.name} > ${name}` : name;
|
|
32
32
|
this.assert = new ModuleContext.Assert(this);
|
|
33
33
|
|
|
@@ -4,50 +4,29 @@ import type ModuleContext from './module-context.ts';
|
|
|
4
4
|
export default class TestContext {
|
|
5
5
|
static Assert: typeof Assert;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
get module() {
|
|
17
|
-
return this.#module;
|
|
18
|
-
}
|
|
19
|
-
set module(value) {
|
|
20
|
-
this.#module = value;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
#asyncOps: Promise<void>[] = [];
|
|
24
|
-
get asyncOps() {
|
|
25
|
-
return this.#asyncOps;
|
|
26
|
-
}
|
|
27
|
-
set asyncOps(value) {
|
|
28
|
-
this.#asyncOps = value;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
#assert: Assert | undefined;
|
|
32
|
-
get assert() {
|
|
33
|
-
return this.#assert;
|
|
34
|
-
}
|
|
35
|
-
set assert(value) {
|
|
36
|
-
this.#assert = value;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
#timeout: number | undefined;
|
|
40
|
-
get timeout() {
|
|
41
|
-
return this.#timeout;
|
|
42
|
-
}
|
|
43
|
-
set timeout(value) {
|
|
44
|
-
this.#timeout = value;
|
|
45
|
-
}
|
|
7
|
+
name: string | undefined;
|
|
8
|
+
module: ModuleContext | undefined;
|
|
9
|
+
asyncOps: Promise<void>[] = [];
|
|
10
|
+
assert: Assert | undefined;
|
|
11
|
+
timeout: number | undefined;
|
|
12
|
+
steps: string[] = [];
|
|
13
|
+
expectedAssertionCount: number | undefined;
|
|
14
|
+
totalExecutedAssertions = 0;
|
|
15
|
+
userContext: Record<string, unknown> = {};
|
|
46
16
|
|
|
47
17
|
rejectTimeout: ((err: Error) => void) | undefined;
|
|
48
18
|
#timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
49
19
|
#timedOut = false;
|
|
50
20
|
|
|
21
|
+
constructor(name?: string, moduleContext?: ModuleContext) {
|
|
22
|
+
if (moduleContext) {
|
|
23
|
+
this.name = `${moduleContext.name} | ${name}`;
|
|
24
|
+
this.module = moduleContext;
|
|
25
|
+
this.module.tests.push(this);
|
|
26
|
+
this.assert = new TestContext.Assert(moduleContext, this);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
51
30
|
setTimeoutDuration(ms: number) {
|
|
52
31
|
if (this.#timeoutHandle !== undefined) {
|
|
53
32
|
clearTimeout(this.#timeoutHandle);
|
|
@@ -67,65 +46,29 @@ export default class TestContext {
|
|
|
67
46
|
}
|
|
68
47
|
}
|
|
69
48
|
|
|
70
|
-
#steps: string[] = [];
|
|
71
|
-
get steps() {
|
|
72
|
-
return this.#steps;
|
|
73
|
-
}
|
|
74
|
-
set steps(value) {
|
|
75
|
-
this.#steps = value;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
#expectedAssertionCount: number | undefined;
|
|
79
|
-
get expectedAssertionCount() {
|
|
80
|
-
return this.#expectedAssertionCount;
|
|
81
|
-
}
|
|
82
|
-
set expectedAssertionCount(value) {
|
|
83
|
-
this.#expectedAssertionCount = value;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
#totalExecutedAssertions = 0;
|
|
87
|
-
get totalExecutedAssertions() {
|
|
88
|
-
return this.#totalExecutedAssertions;
|
|
89
|
-
}
|
|
90
|
-
set totalExecutedAssertions(value) {
|
|
91
|
-
this.#totalExecutedAssertions = value;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
userContext: Record<string, unknown> = {};
|
|
95
|
-
|
|
96
|
-
constructor(name?: string, moduleContext?: ModuleContext) {
|
|
97
|
-
if (moduleContext) {
|
|
98
|
-
this.name = `${moduleContext.name} | ${name}`;
|
|
99
|
-
this.module = moduleContext;
|
|
100
|
-
this.module.tests.push(this);
|
|
101
|
-
this.assert = new TestContext.Assert(moduleContext, this);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
49
|
finish() {
|
|
106
50
|
if (this.#timedOut) return;
|
|
107
|
-
if (this.
|
|
108
|
-
this.assert!.pushResult({
|
|
109
|
-
result: false,
|
|
110
|
-
actual: this.totalExecutedAssertions,
|
|
111
|
-
expected: '> 0',
|
|
112
|
-
message: `Expected at least one assertion to be run for test: ${this.name}`,
|
|
113
|
-
});
|
|
114
|
-
} else if (this.steps.length > 0) {
|
|
51
|
+
if (this.steps.length > 0) {
|
|
115
52
|
this.assert!.pushResult({
|
|
116
53
|
result: false,
|
|
117
54
|
actual: this.steps,
|
|
118
55
|
expected: [],
|
|
119
56
|
message: `Expected assert.verifySteps() to be called before end of test after using assert.step(). Unverified steps: ${this.steps.join(', ')}`,
|
|
120
57
|
});
|
|
121
|
-
} else if (this.expectedAssertionCount && this.expectedAssertionCount !== this.totalExecutedAssertions) {
|
|
58
|
+
} else if (this.expectedAssertionCount !== undefined && this.expectedAssertionCount !== this.totalExecutedAssertions) {
|
|
122
59
|
this.assert!.pushResult({
|
|
123
60
|
result: false,
|
|
124
61
|
actual: this.totalExecutedAssertions,
|
|
125
62
|
expected: this.expectedAssertionCount,
|
|
126
63
|
message: `Expected ${this.expectedAssertionCount} assertions, but ${this.totalExecutedAssertions} were run for test: ${this.name}`,
|
|
127
64
|
});
|
|
65
|
+
} else if (this.expectedAssertionCount === undefined && this.totalExecutedAssertions === 0) {
|
|
66
|
+
this.assert!.pushResult({
|
|
67
|
+
result: false,
|
|
68
|
+
actual: this.totalExecutedAssertions,
|
|
69
|
+
expected: '> 0',
|
|
70
|
+
message: `Expected at least one assertion to be run for test: ${this.name}`,
|
|
71
|
+
});
|
|
128
72
|
}
|
|
129
73
|
}
|
|
130
74
|
}
|
|
131
|
-
|
package/shims/types.ts
CHANGED
|
@@ -44,7 +44,7 @@ export interface TestState {
|
|
|
44
44
|
|
|
45
45
|
/** Minimal module shape that Assert needs to resolve a fallback test context. */
|
|
46
46
|
export interface ModuleState {
|
|
47
|
-
|
|
47
|
+
testContext: TestState;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/** A lifecycle hook callback that receives an {@linkcode Assert} instance and a meta object with the shared context. */
|