qunitx 1.2.5 → 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 +21 -20
- package/dist/browser/index.js +55 -52
- package/dist/deno/index.d.ts +20 -19
- package/dist/deno/index.js +135 -153
- package/dist/deno/module.d.ts +3 -2
- package/dist/deno/module.js +15 -12
- package/dist/deno/test.d.ts +2 -2
- package/dist/deno/test.js +26 -16
- package/dist/node/index.d.ts +3 -2
- package/dist/node/index.js +1 -1
- package/dist/node/module.d.ts +1 -0
- package/dist/node/module.js +15 -12
- package/dist/node/test.js +25 -15
- package/dist/shared/assert.d.ts +2 -2
- package/dist/shared/assert.js +27 -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 +11 -16
- package/dist/shared/test-context.js +38 -65
- package/dist/types.d.ts +2 -1
- package/package.json +2 -2
- package/shims/deno/index.ts +21 -21
- package/shims/deno/module.ts +37 -15
- package/shims/deno/test.ts +32 -19
- package/shims/shared/assert.ts +38 -33
- package/shims/shared/index.ts +14 -27
- package/shims/shared/module-context.ts +5 -5
- package/shims/shared/test-context.ts +41 -74
- package/shims/types.ts +2 -1
|
@@ -1,62 +1,17 @@
|
|
|
1
1
|
class TestContext {
|
|
2
2
|
static Assert;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
}
|
|
3
|
+
name;
|
|
4
|
+
module;
|
|
5
|
+
asyncOps = [];
|
|
6
|
+
assert;
|
|
7
|
+
timeout;
|
|
8
|
+
steps = [];
|
|
9
|
+
expectedAssertionCount;
|
|
10
|
+
totalExecutedAssertions = 0;
|
|
59
11
|
userContext = {};
|
|
12
|
+
rejectTimeout;
|
|
13
|
+
#timeoutHandle;
|
|
14
|
+
#timedOut = false;
|
|
60
15
|
constructor(name, moduleContext) {
|
|
61
16
|
if (moduleContext) {
|
|
62
17
|
this.name = `${moduleContext.name} | ${name}`;
|
|
@@ -65,28 +20,46 @@ class TestContext {
|
|
|
65
20
|
this.assert = new TestContext.Assert(moduleContext, this);
|
|
66
21
|
}
|
|
67
22
|
}
|
|
23
|
+
setTimeoutDuration(ms) {
|
|
24
|
+
if (this.#timeoutHandle !== void 0) {
|
|
25
|
+
clearTimeout(this.#timeoutHandle);
|
|
26
|
+
this.#timeoutHandle = void 0;
|
|
27
|
+
}
|
|
28
|
+
if (!this.rejectTimeout) return;
|
|
29
|
+
this.#timeoutHandle = setTimeout(() => {
|
|
30
|
+
this.#timedOut = true;
|
|
31
|
+
this.rejectTimeout(new Error(`Test timed out after ${ms}ms`));
|
|
32
|
+
}, ms);
|
|
33
|
+
}
|
|
34
|
+
clearTimeoutHandle() {
|
|
35
|
+
if (this.#timeoutHandle !== void 0) {
|
|
36
|
+
clearTimeout(this.#timeoutHandle);
|
|
37
|
+
this.#timeoutHandle = void 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
68
40
|
finish() {
|
|
69
|
-
if (this
|
|
70
|
-
|
|
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
|
-
} else if (this.steps.length > 0) {
|
|
41
|
+
if (this.#timedOut) return;
|
|
42
|
+
if (this.steps.length > 0) {
|
|
77
43
|
this.assert.pushResult({
|
|
78
44
|
result: false,
|
|
79
45
|
actual: this.steps,
|
|
80
46
|
expected: [],
|
|
81
47
|
message: `Expected assert.verifySteps() to be called before end of test after using assert.step(). Unverified steps: ${this.steps.join(", ")}`
|
|
82
48
|
});
|
|
83
|
-
} else if (this.expectedAssertionCount && this.expectedAssertionCount !== this.totalExecutedAssertions) {
|
|
49
|
+
} else if (this.expectedAssertionCount !== void 0 && this.expectedAssertionCount !== this.totalExecutedAssertions) {
|
|
84
50
|
this.assert.pushResult({
|
|
85
51
|
result: false,
|
|
86
52
|
actual: this.totalExecutedAssertions,
|
|
87
53
|
expected: this.expectedAssertionCount,
|
|
88
54
|
message: `Expected ${this.expectedAssertionCount} assertions, but ${this.totalExecutedAssertions} were run for test: ${this.name}`
|
|
89
55
|
});
|
|
56
|
+
} else if (this.expectedAssertionCount === void 0 && this.totalExecutedAssertions === 0) {
|
|
57
|
+
this.assert.pushResult({
|
|
58
|
+
result: false,
|
|
59
|
+
actual: this.totalExecutedAssertions,
|
|
60
|
+
expected: "> 0",
|
|
61
|
+
message: `Expected at least one assertion to be run for test: ${this.name}`
|
|
62
|
+
});
|
|
90
63
|
}
|
|
91
64
|
}
|
|
92
65
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -40,10 +40,11 @@ export interface TestState {
|
|
|
40
40
|
module?: {
|
|
41
41
|
name: string;
|
|
42
42
|
};
|
|
43
|
+
setTimeoutDuration(ms: number): void;
|
|
43
44
|
}
|
|
44
45
|
/** Minimal module shape that Assert needs to resolve a fallback test context. */
|
|
45
46
|
export interface ModuleState {
|
|
46
|
-
|
|
47
|
+
testContext: TestState;
|
|
47
48
|
}
|
|
48
49
|
/** A lifecycle hook callback that receives an {@linkcode Assert} instance and a meta object with the shared context. */
|
|
49
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
|
});
|
|
@@ -127,3 +124,28 @@ export default function module(
|
|
|
127
124
|
module.skip = function skipModule(moduleName: string, _moduleContent?: unknown): void {
|
|
128
125
|
describe(moduleName, { ignore: true }, function () {});
|
|
129
126
|
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Marks all tests inside a module as todo. Equivalent to `QUnit.module.todo`.
|
|
130
|
+
* The module is registered as ignored by Deno's runner; no test bodies run.
|
|
131
|
+
* Deno has no native "todo module" concept, so this maps to ignore.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} moduleName - Name of the module to mark as todo.
|
|
134
|
+
* @param {function} [_moduleContent] - Optional body (ignored — no tests run).
|
|
135
|
+
* @example
|
|
136
|
+
* ```js ignore
|
|
137
|
+
* import { module, test } from "qunitx";
|
|
138
|
+
*
|
|
139
|
+
* module.todo("Math — not yet implemented", () => {
|
|
140
|
+
* test("addition", (assert) => {
|
|
141
|
+
* assert.equal(1 + 1, 2);
|
|
142
|
+
* });
|
|
143
|
+
* });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
module.todo = function todoModule(moduleName: string, _moduleContent?: unknown): void {
|
|
147
|
+
describe(moduleName, { ignore: true }, function () {});
|
|
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.
|
|
@@ -68,27 +66,39 @@ export default function test(
|
|
|
68
66
|
const userContext = Object.create(moduleContext.userContext);
|
|
69
67
|
context.userContext = userContext;
|
|
70
68
|
|
|
71
|
-
it(testName, { ...targetRuntimeOptions },
|
|
69
|
+
it(testName, { ...targetRuntimeOptions }, function () {
|
|
70
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
71
|
+
context.rejectTimeout = reject;
|
|
72
|
+
|
|
72
73
|
const hookMeta = { context: userContext };
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await targetTestContent.call(userContext, context.assert!, { testName, options: targetRuntimeOptions, context: userContext });
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
await context.assert!.waitForAsyncOps();
|
|
81
86
|
|
|
82
|
-
|
|
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);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
83
92
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
resolve();
|
|
94
|
+
} catch (err) {
|
|
95
|
+
reject(err);
|
|
96
|
+
} finally {
|
|
97
|
+
context.clearTimeoutHandle();
|
|
88
98
|
}
|
|
89
|
-
}
|
|
99
|
+
})();
|
|
90
100
|
|
|
91
|
-
return
|
|
101
|
+
return promise;
|
|
92
102
|
});
|
|
93
103
|
}
|
|
94
104
|
|
|
@@ -134,3 +144,6 @@ test.skip = function skipTest(testName: string, _testContent?: unknown): void {
|
|
|
134
144
|
test.todo = function todoTest(testName: string, _testContent?: unknown): void {
|
|
135
145
|
it(testName, { ignore: true }, async function () {});
|
|
136
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
|
|
|
@@ -61,6 +64,7 @@ export default class Assert {
|
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
this.test.timeout = number;
|
|
67
|
+
this.test.setTimeoutDuration(number);
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
/**
|
|
@@ -79,22 +83,16 @@ export default class Assert {
|
|
|
79
83
|
* ```
|
|
80
84
|
*/
|
|
81
85
|
step(message: string): void {
|
|
82
|
-
let assertionMessage = message;
|
|
83
|
-
let result = !!message;
|
|
84
|
-
|
|
85
86
|
this.test.steps.push(message);
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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';
|
|
93
94
|
|
|
94
|
-
this.pushResult({
|
|
95
|
-
result,
|
|
96
|
-
message: assertionMessage
|
|
97
|
-
});
|
|
95
|
+
this.pushResult({ result, message: assertionMessage });
|
|
98
96
|
}
|
|
99
97
|
|
|
100
98
|
/**
|
|
@@ -158,12 +156,9 @@ export default class Assert {
|
|
|
158
156
|
* ```
|
|
159
157
|
*/
|
|
160
158
|
async(): () => void {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
this.test.asyncOps.push(done);
|
|
165
|
-
|
|
166
|
-
return () => { resolveFn(); };
|
|
159
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
160
|
+
this.test.asyncOps.push(promise);
|
|
161
|
+
return resolve;
|
|
167
162
|
}
|
|
168
163
|
|
|
169
164
|
/** @internal Used by the test runner to wait for all async operations to complete. */
|
|
@@ -579,7 +574,7 @@ export default class Assert {
|
|
|
579
574
|
* ```
|
|
580
575
|
*/
|
|
581
576
|
throws(blockFn: unknown, expectedInput?: unknown, assertionMessage?: string): void {
|
|
582
|
-
this
|
|
577
|
+
this._incrementAssertionCount();
|
|
583
578
|
const [expected, message] = validateExpectedExceptionArgs(expectedInput, assertionMessage, 'throws');
|
|
584
579
|
if (typeof blockFn !== 'function') {
|
|
585
580
|
throw new Assert.AssertionError({
|
|
@@ -590,8 +585,9 @@ export default class Assert {
|
|
|
590
585
|
});
|
|
591
586
|
}
|
|
592
587
|
|
|
588
|
+
let returnValue: unknown;
|
|
593
589
|
try {
|
|
594
|
-
blockFn();
|
|
590
|
+
returnValue = blockFn();
|
|
595
591
|
} catch (error) {
|
|
596
592
|
const [result, validatedExpected, validatedMessage] = validateException(error, expected, message);
|
|
597
593
|
if (result === false) {
|
|
@@ -606,6 +602,15 @@ export default class Assert {
|
|
|
606
602
|
return;
|
|
607
603
|
}
|
|
608
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
|
+
|
|
609
614
|
throw new Assert.AssertionError({
|
|
610
615
|
actual: blockFn,
|
|
611
616
|
expected: expected,
|
|
@@ -642,16 +647,14 @@ export default class Assert {
|
|
|
642
647
|
});
|
|
643
648
|
}
|
|
644
649
|
|
|
645
|
-
let
|
|
646
|
-
let rejectionError;
|
|
650
|
+
let caught: unknown = PENDING;
|
|
647
651
|
try {
|
|
648
652
|
await promise;
|
|
649
653
|
} catch (error) {
|
|
650
|
-
|
|
651
|
-
rejectionError = error;
|
|
654
|
+
caught = error;
|
|
652
655
|
}
|
|
653
656
|
|
|
654
|
-
if (
|
|
657
|
+
if (caught === PENDING) {
|
|
655
658
|
throw new Assert.AssertionError({
|
|
656
659
|
actual: promise,
|
|
657
660
|
expected: expected,
|
|
@@ -660,7 +663,7 @@ export default class Assert {
|
|
|
660
663
|
});
|
|
661
664
|
}
|
|
662
665
|
|
|
663
|
-
const [result, validatedExpected, validatedMessage] = validateException(
|
|
666
|
+
const [result, validatedExpected, validatedMessage] = validateException(caught, expected, message);
|
|
664
667
|
if (result === false) {
|
|
665
668
|
throw new Assert.AssertionError({
|
|
666
669
|
actual: result,
|
|
@@ -670,7 +673,7 @@ export default class Assert {
|
|
|
670
673
|
});
|
|
671
674
|
}
|
|
672
675
|
}
|
|
673
|
-
}
|
|
676
|
+
}
|
|
674
677
|
|
|
675
678
|
function defaultMessage(actual: unknown, description: string, expected: unknown): string {
|
|
676
679
|
return `
|
|
@@ -682,6 +685,8 @@ ${description}
|
|
|
682
685
|
${inspect(expected)}`
|
|
683
686
|
}
|
|
684
687
|
|
|
688
|
+
const INSPECT_OPTIONS = { depth: 10, colors: true, compact: false as const };
|
|
689
|
+
|
|
685
690
|
function inspect(value: unknown): string {
|
|
686
|
-
return Assert.inspect(value,
|
|
691
|
+
return Assert.inspect(value, INSPECT_OPTIONS);
|
|
687
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
|
|