@unrift/core 0.4.0 → 0.5.1
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/dist/esm/cli.js +14 -1
- package/dist/esm/expect.d.ts +6 -1
- package/dist/esm/expect.d.ts.map +1 -1
- package/dist/esm/expect.js +57 -26
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/interface.d.ts +7 -3
- package/dist/esm/interface.d.ts.map +1 -1
- package/dist/esm/interface.js +9 -5
- package/dist/esm/matchers.d.ts.map +1 -1
- package/dist/esm/matchers.js +39 -2
- package/dist/esm/run.d.ts +4 -7
- package/dist/esm/run.d.ts.map +1 -1
- package/dist/esm/run.js +6 -2
- package/dist/esm/runner.d.ts +1 -0
- package/dist/esm/runner.d.ts.map +1 -1
- package/dist/esm/runner.js +8 -2
- package/dist/esm/suite.d.ts +24 -6
- package/dist/esm/suite.d.ts.map +1 -1
- package/dist/esm/suite.js +114 -20
- package/dist/esm/test.d.ts +5 -1
- package/dist/esm/test.d.ts.map +1 -1
- package/dist/esm/test.js +8 -1
- package/dist/esm/types.d.ts +2 -0
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/utils/defineConfig.d.ts +1 -0
- package/dist/esm/utils/defineConfig.d.ts.map +1 -1
- package/dist/esm/utils/helpers.d.ts +9 -0
- package/dist/esm/utils/helpers.d.ts.map +1 -1
- package/dist/esm/utils/helpers.js +37 -0
- package/dist/esm/utils/toThrow.d.ts +5 -0
- package/dist/esm/utils/toThrow.d.ts.map +1 -1
- package/dist/esm/utils/toThrow.js +44 -15
- package/package.json +1 -1
package/dist/esm/cli.js
CHANGED
|
@@ -36,6 +36,7 @@ Usage:
|
|
|
36
36
|
|
|
37
37
|
Options:
|
|
38
38
|
-c, --config <path> Path to config file
|
|
39
|
+
-g, --grep <pattern> Only run tests whose full name matches (regex)
|
|
39
40
|
-b, --bail Stop on first test failure
|
|
40
41
|
-t, --timeout <ms> Default test timeout in milliseconds
|
|
41
42
|
-w, --watch Re-run tests on file changes
|
|
@@ -49,6 +50,7 @@ Options:
|
|
|
49
50
|
Examples:
|
|
50
51
|
unrift
|
|
51
52
|
unrift math
|
|
53
|
+
unrift --grep "adds numbers"
|
|
52
54
|
unrift -c test/config.ts --bail
|
|
53
55
|
unrift --timeout 10000
|
|
54
56
|
unrift --watch
|
|
@@ -57,6 +59,7 @@ Examples:
|
|
|
57
59
|
function parseArgs(argv) {
|
|
58
60
|
let configPath;
|
|
59
61
|
let pattern;
|
|
62
|
+
let grep;
|
|
60
63
|
let timeoutMs;
|
|
61
64
|
let bail;
|
|
62
65
|
let debug = false;
|
|
@@ -85,6 +88,14 @@ function parseArgs(argv) {
|
|
|
85
88
|
i++;
|
|
86
89
|
continue;
|
|
87
90
|
}
|
|
91
|
+
if (a === "--grep" || a === "-g") {
|
|
92
|
+
const next = argv[i + 1];
|
|
93
|
+
if (!next)
|
|
94
|
+
throw new Error(`${a} requires a pattern`);
|
|
95
|
+
grep = safeRegExp(next);
|
|
96
|
+
i++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
88
99
|
if (a === "--bail" || a === "-b") {
|
|
89
100
|
bail = true;
|
|
90
101
|
continue;
|
|
@@ -127,6 +138,7 @@ function parseArgs(argv) {
|
|
|
127
138
|
return {
|
|
128
139
|
configPath,
|
|
129
140
|
pattern,
|
|
141
|
+
grep,
|
|
130
142
|
timeoutMs,
|
|
131
143
|
bail,
|
|
132
144
|
debug,
|
|
@@ -138,7 +150,7 @@ function parseArgs(argv) {
|
|
|
138
150
|
showVersion,
|
|
139
151
|
};
|
|
140
152
|
}
|
|
141
|
-
const { configPath, pattern, timeoutMs, bail, debug, list, json, cacheClean, watch, help, showVersion, } = parseArgs(process.argv.slice(2));
|
|
153
|
+
const { configPath, pattern, grep, timeoutMs, bail, debug, list, json, cacheClean, watch, help, showVersion, } = parseArgs(process.argv.slice(2));
|
|
142
154
|
if (showVersion) {
|
|
143
155
|
console.log(version);
|
|
144
156
|
process.exit(0);
|
|
@@ -149,6 +161,7 @@ if (help) {
|
|
|
149
161
|
}
|
|
150
162
|
const runOpts = {
|
|
151
163
|
pattern,
|
|
164
|
+
grep,
|
|
152
165
|
configPath,
|
|
153
166
|
timeoutMs,
|
|
154
167
|
bail,
|
package/dist/esm/expect.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface Matchers<T> {
|
|
|
17
17
|
prototype: unknown;
|
|
18
18
|
}): void | Promise<void>;
|
|
19
19
|
toContain(expected: unknown): void | Promise<void>;
|
|
20
|
+
toContainEqual(expected: unknown): void | Promise<void>;
|
|
20
21
|
toEqual(expected: unknown): void | Promise<void>;
|
|
21
22
|
toHaveLength(expected: number): void | Promise<void>;
|
|
22
23
|
toMatch(expected: RegExp | string): void | Promise<void>;
|
|
@@ -24,6 +25,7 @@ export interface Matchers<T> {
|
|
|
24
25
|
toThrow(expected?: unknown): void | Promise<void>;
|
|
25
26
|
toBeGreaterThanOrEqual(expected: number): void | Promise<void>;
|
|
26
27
|
toBeLessThanOrEqual(expected: number): void | Promise<void>;
|
|
28
|
+
toBeCloseTo(expected: number, precision?: number): void | Promise<void>;
|
|
27
29
|
toBeNaN(): void | Promise<void>;
|
|
28
30
|
toBeFinite(): void | Promise<void>;
|
|
29
31
|
toMatchObject(expected: Record<string, unknown>): void | Promise<void>;
|
|
@@ -41,7 +43,7 @@ export interface Matchers<T> {
|
|
|
41
43
|
toHaveFetched(urlOrMatcher: string | RegExp | ((req: Request) => boolean)): void | Promise<void>;
|
|
42
44
|
toHaveFetchedTimes(expected: number): void | Promise<void>;
|
|
43
45
|
readonly not: Matchers<T>;
|
|
44
|
-
readonly resolves: Matchers<
|
|
46
|
+
readonly resolves: Matchers<Settled<T>>;
|
|
45
47
|
readonly rejects: Matchers<unknown>;
|
|
46
48
|
}
|
|
47
49
|
export interface Matchers<T> {
|
|
@@ -51,5 +53,8 @@ export interface ExpectInterface {
|
|
|
51
53
|
extend<M extends MatcherMap>(m: M): void;
|
|
52
54
|
readonly matchers: Readonly<MatcherMap>;
|
|
53
55
|
}
|
|
56
|
+
/** What `.resolves` sees: a function's returned promise, or the promise itself. */
|
|
57
|
+
type Settled<T> = Awaited<T extends (...args: never[]) => infer R ? R : T>;
|
|
54
58
|
export declare const expect: ExpectInterface;
|
|
59
|
+
export {};
|
|
55
60
|
//# sourceMappingURL=expect.d.ts.map
|
package/dist/esm/expect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/expect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/expect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,UAAU,EAAE,MAAM,SAAS,CAAC;AAM1D,wBAAgB,cAAc,CAAC,WAAW,EAAE,UAAU,QAErD;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,WAAW,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,SAAS,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,QAAQ,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,UAAU,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,aAAa,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,cAAc,CAAC,QAAQ,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,cAAc,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,aAAa,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,OAAO,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,UAAU,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,cAAc,CACZ,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EAChC,KAAK,CAAC,EAAE,OAAO,GACd,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAGxB,gBAAgB,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,oBAAoB,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,wBAAwB,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,uBAAuB,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,cAAc,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,sBAAsB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG1E,aAAa,CACX,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,GAC1D,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;CACrC;AAID,MAAM,WAAW,QAAQ,CAAC,CAAC;CAAI;AAE/B,MAAM,WAAW,eAAe;IAC9B,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;CACzC;AAED,mFAAmF;AACnF,KAAK,OAAO,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAc3E,eAAO,MAAM,MAAM,EAAE,eAiJjB,CAAC"}
|
package/dist/esm/expect.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { formatDiff } from "./utils/diff.js";
|
|
2
|
+
import { isErrorLike, safeString } from "./utils/helpers.js";
|
|
2
3
|
const matcherRegistry = Object.create(null);
|
|
3
4
|
export function extendMatchers(newMatchers) {
|
|
4
5
|
for (const key in newMatchers)
|
|
5
6
|
matcherRegistry[key] = newMatchers[key];
|
|
6
7
|
}
|
|
8
|
+
function describeError(value) {
|
|
9
|
+
return isErrorLike(value) ? value.message : safeString(value);
|
|
10
|
+
}
|
|
11
|
+
function isThenable(value) {
|
|
12
|
+
return (value != null &&
|
|
13
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
14
|
+
typeof value.then === "function");
|
|
15
|
+
}
|
|
7
16
|
export const expect = (() => {
|
|
8
17
|
function expectFn(received) {
|
|
9
18
|
function makeExpectation(isNot) {
|
|
10
19
|
const ctx = {
|
|
11
20
|
isNot,
|
|
21
|
+
promise: "",
|
|
12
22
|
diff(a, b) {
|
|
13
23
|
return formatDiff(a, b);
|
|
14
24
|
},
|
|
@@ -18,9 +28,10 @@ export const expect = (() => {
|
|
|
18
28
|
const fn = matcherRegistry[name];
|
|
19
29
|
matcherFns[name] = (...args) => fn.call(ctx, received, ...args);
|
|
20
30
|
}
|
|
21
|
-
function makeAsyncProxy(getActual, asyncIsNot) {
|
|
31
|
+
function makeAsyncProxy(getActual, asyncIsNot, promise) {
|
|
22
32
|
const asyncCtx = {
|
|
23
33
|
isNot: asyncIsNot,
|
|
34
|
+
promise,
|
|
24
35
|
diff(a, b) {
|
|
25
36
|
return formatDiff(a, b);
|
|
26
37
|
},
|
|
@@ -28,15 +39,14 @@ export const expect = (() => {
|
|
|
28
39
|
const asyncFns = {};
|
|
29
40
|
for (const name in matcherRegistry) {
|
|
30
41
|
const fn = matcherRegistry[name];
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
42
|
+
// Not async: getActual() runs now, so a bad received value (a sync
|
|
43
|
+
// throw, a non-promise) throws immediately like Jest, even unawaited.
|
|
44
|
+
asyncFns[name] = (...args) => getActual().then((actual) => fn.call(asyncCtx, actual, ...args));
|
|
35
45
|
}
|
|
36
46
|
return new Proxy(asyncFns, {
|
|
37
47
|
get(target, prop) {
|
|
38
48
|
if (prop === "not") {
|
|
39
|
-
return makeAsyncProxy(getActual, !asyncIsNot);
|
|
49
|
+
return makeAsyncProxy(getActual, !asyncIsNot, promise);
|
|
40
50
|
}
|
|
41
51
|
if (typeof prop === "string" && !(prop in target)) {
|
|
42
52
|
throw new Error(`Unknown matcher: ${prop}`);
|
|
@@ -49,30 +59,51 @@ export const expect = (() => {
|
|
|
49
59
|
get(target, prop) {
|
|
50
60
|
if (prop === "not")
|
|
51
61
|
return makeExpectation(!isNot);
|
|
62
|
+
// Same as Jest: a function is called and must return a promise. A
|
|
63
|
+
// sync throw from it isn't a rejection, it fails the assertion.
|
|
64
|
+
const toPromise = (modifier) => {
|
|
65
|
+
const value = typeof received === "function"
|
|
66
|
+
? received()
|
|
67
|
+
: received;
|
|
68
|
+
if (!isThenable(value)) {
|
|
69
|
+
throw new TypeError(`.${modifier}: received value must be a promise or a function returning a promise, got ${describeError(value)}`);
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
};
|
|
52
73
|
if (prop === "resolves") {
|
|
53
|
-
return makeAsyncProxy(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
return makeAsyncProxy(() => {
|
|
75
|
+
const pending = toPromise("resolves");
|
|
76
|
+
return (async () => {
|
|
77
|
+
try {
|
|
78
|
+
return await pending;
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
throw new Error(`Expected promise to resolve, but it rejected with: ${describeError(err)}`);
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
84
|
+
}, isNot, "resolves");
|
|
61
85
|
}
|
|
62
86
|
if (prop === "rejects") {
|
|
63
|
-
return makeAsyncProxy(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
return makeAsyncProxy(() => {
|
|
88
|
+
const pending = toPromise("rejects");
|
|
89
|
+
return (async () => {
|
|
90
|
+
// Tracked with a flag, not by error message, so a promise that
|
|
91
|
+
// rejects with that same text is still treated as a rejection.
|
|
92
|
+
let resolved = false;
|
|
93
|
+
let reason;
|
|
94
|
+
try {
|
|
95
|
+
await pending;
|
|
96
|
+
resolved = true;
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
reason = err;
|
|
100
|
+
}
|
|
101
|
+
if (resolved) {
|
|
102
|
+
throw new Error("Expected promise to reject, but it resolved");
|
|
72
103
|
}
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
}, isNot);
|
|
104
|
+
return reason;
|
|
105
|
+
})();
|
|
106
|
+
}, isNot, "rejects");
|
|
76
107
|
}
|
|
77
108
|
if (typeof prop === "string" && !(prop in target)) {
|
|
78
109
|
throw new Error(`Unknown matcher: ${prop}`);
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { beforeEach, afterEach, beforeAll, afterAll } from "./hooks";
|
|
2
2
|
export { defineConfig, type UnriftConfigOptions } from "./utils/defineConfig";
|
|
3
|
-
export { describe, it, test } from "./interface";
|
|
3
|
+
export { describe, it, test, type TestOptions } from "./interface";
|
|
4
4
|
export { expect, extendMatchers } from "./expect";
|
|
5
5
|
export type { Matchers, ExpectInterface } from "./expect";
|
|
6
6
|
export type { MatcherContext, MatcherFn, MatcherMap, Promisable, } from "./types";
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAElD,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC1D,YAAY,EACV,cAAc,EACd,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,YAAY,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAG7C,OAAO,EACL,IAAI,EACJ,GAAG,EACH,KAAK,EACL,KAAK,EACL,UAAU,EACV,SAAS,EACT,IAAI,EACJ,UAAU,EACV,SAAS,EACT,aAAa,EACb,aAAa,EACb,aAAa,EACb,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACpB,yBAAyB,EACzB,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,MAAM,EACN,MAAM,EACN,eAAe,EACf,UAAU,EACV,MAAM,EACN,aAAa,EACb,aAAa,EACb,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,GAAG,EACH,SAAS,EACT,UAAU,EACV,SAAS,EACT,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,WAAW,GACZ,MAAM,cAAc,CAAC"}
|
package/dist/esm/interface.d.ts
CHANGED
|
@@ -8,10 +8,14 @@ type DescribeFn = {
|
|
|
8
8
|
skip: (description: string, fn: () => void) => void;
|
|
9
9
|
todo: (description: string) => void;
|
|
10
10
|
};
|
|
11
|
+
export interface TestOptions {
|
|
12
|
+
timeout?: number;
|
|
13
|
+
retries?: number;
|
|
14
|
+
}
|
|
11
15
|
type ItFn = {
|
|
12
|
-
(description: string, fn: PromisableFn<void>,
|
|
13
|
-
only: (description: string, fn: PromisableFn<void>,
|
|
14
|
-
skip: (description: string, fn: PromisableFn<void>,
|
|
16
|
+
(description: string, fn: PromisableFn<void>, options?: number | TestOptions): void;
|
|
17
|
+
only: (description: string, fn: PromisableFn<void>, options?: number | TestOptions) => void;
|
|
18
|
+
skip: (description: string, fn: PromisableFn<void>, options?: number | TestOptions) => void;
|
|
15
19
|
todo: (description: string) => void;
|
|
16
20
|
};
|
|
17
21
|
declare const describe: DescribeFn;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/interface.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,YAAY,EAAY,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/interface.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,YAAY,EAAY,MAAM,SAAS,CAAC;AAGtD;;GAEG;AAEH,KAAK,UAAU,GAAG;IAChB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC5C,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACpD,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACpD,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,IAAI,GAAG;IACV,CACE,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EACtB,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,GAC7B,IAAI,CAAC;IACR,IAAI,EAAE,CACJ,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EACtB,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,KAC3B,IAAI,CAAC;IACV,IAAI,EAAE,CACJ,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EACtB,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,KAC3B,IAAI,CAAC;IACV,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AA6BF,QAAA,MAAM,QAAQ,EAAE,UAC8B,CAAC;AAyB/C,QAAA,MAAM,EAAE,EAAE,IACuC,CAAC;AAOlD,QAAA,MAAM,IAAI,EAAE,IAAS,CAAC;AAEtB,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC"}
|
package/dist/esm/interface.js
CHANGED
|
@@ -2,6 +2,7 @@ import { assertRegisterState, unriftGlobalContext } from "./context.js";
|
|
|
2
2
|
import { getCurrentSuite, Suite } from "./suite.js";
|
|
3
3
|
import { Test } from "./test.js";
|
|
4
4
|
import { TaskMode } from "./types.js";
|
|
5
|
+
import { assertValidRetries } from "./utils/helpers.js";
|
|
5
6
|
function _describe(description, fn, mode) {
|
|
6
7
|
assertRegisterState("describe");
|
|
7
8
|
const parent = getCurrentSuite();
|
|
@@ -24,14 +25,17 @@ const describe = (description, fn) => _describe(description, fn, TaskMode.Defaul
|
|
|
24
25
|
describe.only = (description, fn) => _describe(description, fn, TaskMode.Only);
|
|
25
26
|
describe.skip = (description, fn) => _describe(description, fn, TaskMode.Skip);
|
|
26
27
|
describe.todo = (description) => _describe(description, () => { }, TaskMode.Todo);
|
|
27
|
-
function _it(description, fn, mode,
|
|
28
|
+
function _it(description, fn, mode, options) {
|
|
28
29
|
assertRegisterState("it");
|
|
29
|
-
|
|
30
|
+
// A bare number is the timeout, kept for backwards compatibility.
|
|
31
|
+
const { timeout, retries } = typeof options === "number" ? { timeout: options } : (options ?? {});
|
|
32
|
+
assertValidRetries(retries, `it("${description}")`);
|
|
33
|
+
const test = new Test(description, fn, mode, timeout, retries);
|
|
30
34
|
getCurrentSuite().addTest(test);
|
|
31
35
|
}
|
|
32
|
-
const it = (description, fn,
|
|
33
|
-
it.only = (description, fn,
|
|
34
|
-
it.skip = (description, fn,
|
|
36
|
+
const it = (description, fn, options) => _it(description, fn, TaskMode.Default, options);
|
|
37
|
+
it.only = (description, fn, options) => _it(description, fn, TaskMode.Only, options);
|
|
38
|
+
it.skip = (description, fn, options) => _it(description, fn, TaskMode.Skip, options);
|
|
35
39
|
it.todo = (description) => _it(description, () => { }, TaskMode.Todo);
|
|
36
40
|
const test = it;
|
|
37
41
|
export { describe, it, test };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/matchers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/matchers.ts"],"names":[],"mappings":"AA6GA,wBAAgB,sBAAsB,SAMrC;AAED,wBAAgB,aAAa,SAE5B"}
|
package/dist/esm/matchers.js
CHANGED
|
@@ -4,7 +4,7 @@ import { isFetchMock } from "./mock/fetch.js";
|
|
|
4
4
|
import { deepEqual } from "./utils/deepEqual.js";
|
|
5
5
|
import { looseEqual } from "./utils/looseEqual.js";
|
|
6
6
|
import { regexTest } from "./utils/helpers.js";
|
|
7
|
-
import { toThrow } from "./utils/toThrow.js";
|
|
7
|
+
import { toThrow, toThrowFromPromise, } from "./utils/toThrow.js";
|
|
8
8
|
let matchersRegistered = false;
|
|
9
9
|
function ensureSpy(received, matcherName) {
|
|
10
10
|
if (!isSpy(received)) {
|
|
@@ -165,6 +165,22 @@ function registerCoreMatchers() {
|
|
|
165
165
|
throw new Error(this.diff(received, expected));
|
|
166
166
|
}
|
|
167
167
|
},
|
|
168
|
+
toContainEqual(received, expected) {
|
|
169
|
+
if (!Array.isArray(received)) {
|
|
170
|
+
throw new Error(this.diff(received, "expected an array for toContainEqual()"));
|
|
171
|
+
}
|
|
172
|
+
// for...of visits sparse holes as undefined; Array.some would skip them.
|
|
173
|
+
let pass = false;
|
|
174
|
+
for (const item of received) {
|
|
175
|
+
if (looseEqual(item, expected)) {
|
|
176
|
+
pass = true;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (this.isNot ? pass : !pass) {
|
|
181
|
+
throw new Error(this.diff(received, expected));
|
|
182
|
+
}
|
|
183
|
+
},
|
|
168
184
|
toEqual(received, expected) {
|
|
169
185
|
const pass = looseEqual(received, expected);
|
|
170
186
|
if (this.isNot ? pass : !pass) {
|
|
@@ -207,7 +223,10 @@ function registerCoreMatchers() {
|
|
|
207
223
|
}
|
|
208
224
|
},
|
|
209
225
|
toThrow(received, expected) {
|
|
210
|
-
|
|
226
|
+
// Under .resolves/.rejects, `received` is the settled value itself.
|
|
227
|
+
const { pass, message } = this.promise
|
|
228
|
+
? toThrowFromPromise(received, expected, this.isNot, this.promise)
|
|
229
|
+
: toThrow(received, expected, this.isNot);
|
|
211
230
|
if (!pass)
|
|
212
231
|
throw new Error(message());
|
|
213
232
|
},
|
|
@@ -229,6 +248,24 @@ function registerCoreMatchers() {
|
|
|
229
248
|
throw new Error(this.diff(received, `<= ${expected}`));
|
|
230
249
|
}
|
|
231
250
|
},
|
|
251
|
+
toBeCloseTo(received, expected, precision = 2) {
|
|
252
|
+
if (typeof received !== "number") {
|
|
253
|
+
throw new Error(this.diff(received, "expected a number"));
|
|
254
|
+
}
|
|
255
|
+
if (typeof expected !== "number") {
|
|
256
|
+
throw new Error(this.diff(expected, "a number for expected"));
|
|
257
|
+
}
|
|
258
|
+
if (!Number.isFinite(precision)) {
|
|
259
|
+
throw new Error(this.diff(precision, "a finite number for precision"));
|
|
260
|
+
}
|
|
261
|
+
// Same-signed infinities are equal; the subtraction below would be NaN.
|
|
262
|
+
// Finite values always go through the tolerance check.
|
|
263
|
+
const pass = (!Number.isFinite(received) && received === expected) ||
|
|
264
|
+
Math.abs(received - expected) < 10 ** -precision / 2;
|
|
265
|
+
if (this.isNot ? pass : !pass) {
|
|
266
|
+
throw new Error(this.diff(received, `${expected} (precision ${precision})`));
|
|
267
|
+
}
|
|
268
|
+
},
|
|
232
269
|
toBeNaN(received) {
|
|
233
270
|
const pass = Number.isNaN(received);
|
|
234
271
|
if (this.isNot ? pass : !pass) {
|
package/dist/esm/run.d.ts
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type TestResult } from "./suite";
|
|
2
2
|
type RunEngineOptions = {
|
|
3
3
|
files: string[];
|
|
4
4
|
timeoutMs?: number;
|
|
5
5
|
bail?: boolean;
|
|
6
|
+
grep?: RegExp;
|
|
7
|
+
retries?: number;
|
|
6
8
|
matchers?: string[];
|
|
7
9
|
};
|
|
8
10
|
export type RunEngineResult = {
|
|
9
11
|
isOnly: boolean;
|
|
10
12
|
timeoutMs?: number;
|
|
11
13
|
bail: boolean;
|
|
12
|
-
results:
|
|
13
|
-
description: string;
|
|
14
|
-
status: TaskStatus;
|
|
15
|
-
error?: Error;
|
|
16
|
-
durationMs: number;
|
|
17
|
-
}>;
|
|
14
|
+
results: TestResult[];
|
|
18
15
|
};
|
|
19
16
|
export declare function runEngine(options: RunEngineOptions): Promise<RunEngineResult>;
|
|
20
17
|
export {};
|
package/dist/esm/run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,SAAS,CAAC;AASjB,KAAK,gBAAgB,GAAG;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB,CAAC;AAQF,wBAAsB,SAAS,CAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAoE1B"}
|
package/dist/esm/run.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { clearContext, computeOnlyFlags, rootSuite, Suite } from "./suite.js";
|
|
1
|
+
import { clearContext, computeOnlyFlags, rootSuite, Suite, } from "./suite.js";
|
|
2
2
|
import { ensureInternalMatchers } from "./matchers.js";
|
|
3
3
|
import { RunState, unriftGlobalContext } from "./context.js";
|
|
4
4
|
import { normalizePath } from "./utils/normalizePath.js";
|
|
5
5
|
import { TaskMode } from "./types.js";
|
|
6
6
|
import { toError } from "./utils/toError.js";
|
|
7
7
|
import { toImportUrl } from "./utils/transform.js";
|
|
8
|
+
import { assertValidRetries } from "./utils/helpers.js";
|
|
8
9
|
async function loadMatcherModules(specifiers) {
|
|
9
10
|
for (const spec of specifiers ?? []) {
|
|
10
11
|
await import(spec);
|
|
@@ -12,6 +13,9 @@ async function loadMatcherModules(specifiers) {
|
|
|
12
13
|
}
|
|
13
14
|
export async function runEngine(options) {
|
|
14
15
|
const { files } = options;
|
|
16
|
+
// Validate before touching global state. A bad value (e.g. "3" from a JSON
|
|
17
|
+
// config) would otherwise turn into "31" attempts or zero.
|
|
18
|
+
assertValidRetries(options.retries, "runEngine");
|
|
15
19
|
clearContext();
|
|
16
20
|
ensureInternalMatchers();
|
|
17
21
|
await loadMatcherModules(options.matchers);
|
|
@@ -40,7 +44,7 @@ export async function runEngine(options) {
|
|
|
40
44
|
}
|
|
41
45
|
const isOnly = computeOnlyFlags(rootSuite);
|
|
42
46
|
unriftGlobalContext.state = RunState.Run;
|
|
43
|
-
await rootSuite.run({ timeoutMs, bail }, { bailed: false }, isOnly, false);
|
|
47
|
+
await rootSuite.run({ timeoutMs, bail, grep: options.grep, retries: options.retries }, { bailed: false }, isOnly, false);
|
|
44
48
|
const results = rootSuite.getResults();
|
|
45
49
|
return {
|
|
46
50
|
isOnly,
|
package/dist/esm/runner.d.ts
CHANGED
package/dist/esm/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/runner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/runner.ts"],"names":[],"mappings":"AAyBA,UAAU,aAAa;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA+MD,wBAAsB,WAAW,CAAC,OAAO,GAAE,aAAkB,iBAwU5D;AA+DD,wBAAsB,aAAa,CAAC,OAAO,GAAE,aAAkB,iBAyD9D"}
|
package/dist/esm/runner.js
CHANGED
|
@@ -237,6 +237,8 @@ export async function runTestsCLI(options = {}) {
|
|
|
237
237
|
files,
|
|
238
238
|
timeoutMs,
|
|
239
239
|
bail,
|
|
240
|
+
grep: options.grep,
|
|
241
|
+
retries: config?.retries,
|
|
240
242
|
matchers: config?.matchers,
|
|
241
243
|
});
|
|
242
244
|
debugLog(options.debug, options.json, "Only mode:", engine.isOnly);
|
|
@@ -264,6 +266,7 @@ export async function runTestsCLI(options = {}) {
|
|
|
264
266
|
description: result.description,
|
|
265
267
|
status: result.status,
|
|
266
268
|
durationMs: result.durationMs ?? 0,
|
|
269
|
+
attempts: result.attempts && result.attempts > 1 ? result.attempts : undefined,
|
|
267
270
|
error: result.error
|
|
268
271
|
? { message: result.error.message, stack: result.error.stack }
|
|
269
272
|
: undefined,
|
|
@@ -328,14 +331,17 @@ export async function runTestsCLI(options = {}) {
|
|
|
328
331
|
lastSuitePath = suitePath;
|
|
329
332
|
}
|
|
330
333
|
const indent = " " + " ".repeat(depth);
|
|
334
|
+
const retried = result.attempts && result.attempts > 1
|
|
335
|
+
? colors.yellow(` (${result.status === TaskStatus.Fail ? "failed" : "passed"} after ${result.attempts} attempts)`)
|
|
336
|
+
: "";
|
|
331
337
|
if (result.status === TaskStatus.Pass) {
|
|
332
338
|
const dur = colors.dim(formatDuration(result.durationMs ?? 0));
|
|
333
|
-
console.log(`${indent}${colors.green("✔")} ${colors.dim(leaf)} ${dur}`);
|
|
339
|
+
console.log(`${indent}${colors.green("✔")} ${colors.dim(leaf)}${retried} ${dur}`);
|
|
334
340
|
continue;
|
|
335
341
|
}
|
|
336
342
|
if (result.status === TaskStatus.Fail) {
|
|
337
343
|
const dur = colors.dim(formatDuration(result.durationMs ?? 0));
|
|
338
|
-
console.log(`${indent}${colors.red("✘")} ${colors.boldRed(leaf)} ${dur}`);
|
|
344
|
+
console.log(`${indent}${colors.red("✘")} ${colors.boldRed(leaf)}${retried} ${dur}`);
|
|
339
345
|
continue;
|
|
340
346
|
}
|
|
341
347
|
if (result.status === TaskStatus.Skipped) {
|
package/dist/esm/suite.d.ts
CHANGED
|
@@ -3,10 +3,20 @@ import { type PromisableFn, TaskMode, TaskStatus } from "./types";
|
|
|
3
3
|
interface RunOptions {
|
|
4
4
|
timeoutMs?: number;
|
|
5
5
|
bail?: boolean;
|
|
6
|
+
grep?: RegExp;
|
|
7
|
+
retries?: number;
|
|
6
8
|
}
|
|
7
9
|
interface SuiteRunState {
|
|
8
10
|
bailed: boolean;
|
|
9
11
|
}
|
|
12
|
+
export interface TestResult {
|
|
13
|
+
description: string;
|
|
14
|
+
status: TaskStatus;
|
|
15
|
+
error?: Error;
|
|
16
|
+
durationMs: number;
|
|
17
|
+
/** How many times the test ran. Above 1 means it was retried. */
|
|
18
|
+
attempts?: number;
|
|
19
|
+
}
|
|
10
20
|
export declare class Suite {
|
|
11
21
|
description: string;
|
|
12
22
|
mode: TaskMode;
|
|
@@ -27,20 +37,28 @@ export declare class Suite {
|
|
|
27
37
|
afterEachHooks: PromisableFn<void>[];
|
|
28
38
|
constructor(description: string, parent?: Suite, mode?: TaskMode);
|
|
29
39
|
markSubtreeSkipped(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Used when --grep prunes a suite. Matching todos stay todo so their status
|
|
42
|
+
* doesn't depend on whether a runnable sibling matched; everything else is
|
|
43
|
+
* skipped.
|
|
44
|
+
*/
|
|
45
|
+
private markSubtreeFilteredOut;
|
|
30
46
|
markSubtreeTodo(): void;
|
|
31
47
|
addSuite(suite: Suite): void;
|
|
32
48
|
addTest(test: Test): void;
|
|
33
49
|
private collectBeforeEachHooks;
|
|
34
50
|
private collectAfterEachHooks;
|
|
35
51
|
run(options?: RunOptions, state?: SuiteRunState, isOnly?: boolean, ancestorOnly?: boolean): Promise<void>;
|
|
52
|
+
private testMatchesGrep;
|
|
53
|
+
/**
|
|
54
|
+
* True if a test below this suite matches grep AND would actually run, using
|
|
55
|
+
* the same skip/todo/only rules as run(). A matching test that won't run
|
|
56
|
+
* shouldn't cause this suite's hooks to fire.
|
|
57
|
+
*/
|
|
58
|
+
private hasRunnableMatch;
|
|
36
59
|
getFullDescription(): string;
|
|
37
60
|
reset(): void;
|
|
38
|
-
getResults():
|
|
39
|
-
description: string;
|
|
40
|
-
status: TaskStatus;
|
|
41
|
-
error?: Error;
|
|
42
|
-
durationMs: number;
|
|
43
|
-
}>;
|
|
61
|
+
getResults(): TestResult[];
|
|
44
62
|
}
|
|
45
63
|
export declare function computeOnlyFlags(suite: Suite): boolean;
|
|
46
64
|
export declare const rootSuite: Suite;
|
package/dist/esm/suite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suite.d.ts","sourceRoot":"","sources":["../../src/suite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE9B,OAAO,EAAE,KAAK,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"suite.d.ts","sourceRoot":"","sources":["../../src/suite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE9B,OAAO,EAAE,KAAK,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAIlE,UAAU,UAAU;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,aAAa;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB;AAsBD,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,KAAK;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,KAAK,CAAC;IAEf,cAAc,EAAE,OAAO,CAAS;IAEhC,IAAI,EAAE,OAAO,CAAS;IACtB,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAM;IACpD,MAAM,EAAE,KAAK,EAAE,CAAM;IACrB,KAAK,EAAE,IAAI,EAAE,CAAM;IAEnB,aAAa,CAAC,EAAE,KAAK,CAAC;IACtB,cAAc,CAAC,EAAE,KAAK,CAAC;IAEvB,cAAc,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAM;IAC1C,aAAa,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAM;IACzC,eAAe,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAM;IAC3C,cAAc,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAM;gBAGxC,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,KAAK,EACd,IAAI,GAAE,QAA2B;IAO5B,kBAAkB;IASzB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IA8BvB,eAAe;IAStB,QAAQ,CAAC,KAAK,EAAE,KAAK;IAIrB,OAAO,CAAC,IAAI,EAAE,IAAI;IAIlB,OAAO,CAAC,sBAAsB;IAiB9B,OAAO,CAAC,qBAAqB;IAYvB,GAAG,CACP,OAAO,CAAC,EAAE,UAAU,EACpB,KAAK,GAAE,aAAiC,EACxC,MAAM,GAAE,OAAe,EACvB,YAAY,GAAE,OAAe,GAC5B,OAAO,CAAC,IAAI,CAAC;IAmNhB,OAAO,CAAC,eAAe;IAOvB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IA4BxB,kBAAkB,IAAI,MAAM;IAK5B,KAAK;IAaL,UAAU,IAAI,UAAU,EAAE;CA8C3B;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAiBtD;AAKD,eAAO,MAAM,SAAS,OAAoB,CAAC;AAG3C,wBAAgB,eAAe,UAE9B;AAED,wBAAgB,YAAY,SAK3B"}
|
package/dist/esm/suite.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { unriftGlobalContext } from "./context.js";
|
|
2
2
|
import { resetMatchers } from "./matchers.js";
|
|
3
3
|
import { TaskMode, TaskStatus } from "./types.js";
|
|
4
|
+
import { regexTest } from "./utils/helpers.js";
|
|
4
5
|
import { toError } from "./utils/toError.js";
|
|
5
6
|
function appendHookFailure(existing, hookName, hookError) {
|
|
6
7
|
if (!existing)
|
|
@@ -42,6 +43,34 @@ export class Suite {
|
|
|
42
43
|
for (const suite of this.suites)
|
|
43
44
|
suite.markSubtreeSkipped();
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Used when --grep prunes a suite. Matching todos stay todo so their status
|
|
48
|
+
* doesn't depend on whether a runnable sibling matched; everything else is
|
|
49
|
+
* skipped.
|
|
50
|
+
*/
|
|
51
|
+
markSubtreeFilteredOut(grep, isOnly, inOnlyContext) {
|
|
52
|
+
for (const test of this.tests) {
|
|
53
|
+
if (test.status !== TaskStatus.Pending)
|
|
54
|
+
continue;
|
|
55
|
+
test.status =
|
|
56
|
+
test.mode === TaskMode.Todo && this.testMatchesGrep(test, grep)
|
|
57
|
+
? TaskStatus.Todo
|
|
58
|
+
: TaskStatus.Skipped;
|
|
59
|
+
test.durationMs = 0;
|
|
60
|
+
}
|
|
61
|
+
// Same skip and .only rules as run(), so a suite excluded by .only stays
|
|
62
|
+
// skipped no matter which pattern pruned its parent.
|
|
63
|
+
for (const suite of this.suites) {
|
|
64
|
+
const childOnly = inOnlyContext || suite.mode === TaskMode.Only;
|
|
65
|
+
if (suite.mode === TaskMode.Skip ||
|
|
66
|
+
(isOnly && !suite.subtreeHasOnly && !childOnly)) {
|
|
67
|
+
suite.markSubtreeSkipped();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
suite.markSubtreeFilteredOut(grep, isOnly, childOnly);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
45
74
|
markSubtreeTodo() {
|
|
46
75
|
for (const test of this.tests) {
|
|
47
76
|
if (test.status === TaskStatus.Pending)
|
|
@@ -100,6 +129,12 @@ export class Suite {
|
|
|
100
129
|
this.markSubtreeSkipped();
|
|
101
130
|
return;
|
|
102
131
|
}
|
|
132
|
+
// Same for --grep: nothing below can run, so don't run hooks either
|
|
133
|
+
if (options?.grep &&
|
|
134
|
+
!this.hasRunnableMatch(options.grep, isOnly, inOnlyContext)) {
|
|
135
|
+
this.markSubtreeFilteredOut(options.grep, isOnly, inOnlyContext);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
103
138
|
let entered = false;
|
|
104
139
|
try {
|
|
105
140
|
// beforeAll
|
|
@@ -122,6 +157,12 @@ export class Suite {
|
|
|
122
157
|
const test = this.tests[i];
|
|
123
158
|
if (state.bailed)
|
|
124
159
|
break;
|
|
160
|
+
// Checked before skip/todo so a non-matching todo reports as skipped
|
|
161
|
+
if (options?.grep && !this.testMatchesGrep(test, options.grep)) {
|
|
162
|
+
test.status = TaskStatus.Skipped;
|
|
163
|
+
test.durationMs = 0;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
125
166
|
if (test.mode === TaskMode.Skip) {
|
|
126
167
|
test.status = TaskStatus.Skipped;
|
|
127
168
|
test.durationMs = 0;
|
|
@@ -139,32 +180,59 @@ export class Suite {
|
|
|
139
180
|
test.durationMs = 0;
|
|
140
181
|
continue;
|
|
141
182
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
183
|
+
// Each attempt gets its own beforeEach/afterEach so state resets between retries.
|
|
184
|
+
const maxAttempts = (test.retries ?? options?.retries ?? 0) + 1;
|
|
185
|
+
let totalMs = 0;
|
|
186
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
187
|
+
test.attempts = attempt;
|
|
188
|
+
test.error = undefined;
|
|
189
|
+
// Reset so an attempt whose beforeEach throws doesn't report the
|
|
190
|
+
// previous attempt's time.
|
|
191
|
+
test.durationMs = 0;
|
|
192
|
+
try {
|
|
193
|
+
for (const hook of beforeEachHooks) {
|
|
194
|
+
try {
|
|
195
|
+
await hook();
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
throw appendHookFailure(undefined, "beforeEach", toError(error));
|
|
199
|
+
}
|
|
149
200
|
}
|
|
201
|
+
await test.run(options?.timeoutMs);
|
|
150
202
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
203
|
+
catch (error) {
|
|
204
|
+
test.status = TaskStatus.Fail;
|
|
205
|
+
test.error = toError(error);
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
for (const hook of afterEachHooks) {
|
|
209
|
+
try {
|
|
210
|
+
await hook();
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
test.status = TaskStatus.Fail;
|
|
214
|
+
test.error = appendHookFailure(test.error, "afterEach", toError(error));
|
|
215
|
+
}
|
|
161
216
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
217
|
+
}
|
|
218
|
+
totalMs += test.durationMs;
|
|
219
|
+
if (test.status !== TaskStatus.Fail)
|
|
220
|
+
break;
|
|
221
|
+
// A timed-out body can't be cancelled and may still be running, so
|
|
222
|
+
// another attempt could race with it. Stop here instead.
|
|
223
|
+
if (test.timedOut) {
|
|
224
|
+
if (attempt < maxAttempts && test.error) {
|
|
225
|
+
const note = "\n\n[retries] not retried: the timed-out attempt may still be running";
|
|
226
|
+
// Read the stack before touching the message: V8 builds it lazily,
|
|
227
|
+
// and an afterEach failure may have already combined it.
|
|
228
|
+
const stack = test.error.stack ?? test.error.message;
|
|
229
|
+
test.error.message += note;
|
|
230
|
+
test.error.stack = stack + note;
|
|
165
231
|
}
|
|
232
|
+
break;
|
|
166
233
|
}
|
|
167
234
|
}
|
|
235
|
+
test.durationMs = totalMs;
|
|
168
236
|
// Mark everything that hasn't run as skipped on bail
|
|
169
237
|
if (options?.bail && test.status === TaskStatus.Fail) {
|
|
170
238
|
state.bailed = true;
|
|
@@ -212,6 +280,31 @@ export class Suite {
|
|
|
212
280
|
}
|
|
213
281
|
}
|
|
214
282
|
}
|
|
283
|
+
testMatchesGrep(test, grep) {
|
|
284
|
+
return regexTest(grep, `${this.getFullDescription()} › ${test.description}`);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* True if a test below this suite matches grep AND would actually run, using
|
|
288
|
+
* the same skip/todo/only rules as run(). A matching test that won't run
|
|
289
|
+
* shouldn't cause this suite's hooks to fire.
|
|
290
|
+
*/
|
|
291
|
+
hasRunnableMatch(grep, isOnly, inOnlyContext) {
|
|
292
|
+
const runnable = this.tests.some((test) => test.mode !== TaskMode.Skip &&
|
|
293
|
+
test.mode !== TaskMode.Todo &&
|
|
294
|
+
(!isOnly || inOnlyContext || test.mode === TaskMode.Only) &&
|
|
295
|
+
this.testMatchesGrep(test, grep));
|
|
296
|
+
if (runnable)
|
|
297
|
+
return true;
|
|
298
|
+
return this.suites.some((suite) => {
|
|
299
|
+
if (suite.mode === TaskMode.Skip || suite.mode === TaskMode.Todo) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
const childOnly = inOnlyContext || suite.mode === TaskMode.Only;
|
|
303
|
+
if (isOnly && !suite.subtreeHasOnly && !childOnly)
|
|
304
|
+
return false;
|
|
305
|
+
return suite.hasRunnableMatch(grep, isOnly, childOnly);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
215
308
|
getFullDescription() {
|
|
216
309
|
if (!this.parent || this.parent.root)
|
|
217
310
|
return this.description;
|
|
@@ -261,6 +354,7 @@ export class Suite {
|
|
|
261
354
|
status: test.status,
|
|
262
355
|
error: test.error,
|
|
263
356
|
durationMs: test.durationMs ?? 0,
|
|
357
|
+
attempts: test.attempts,
|
|
264
358
|
});
|
|
265
359
|
}
|
|
266
360
|
for (const child of this.suites) {
|
package/dist/esm/test.d.ts
CHANGED
|
@@ -7,7 +7,11 @@ export declare class Test {
|
|
|
7
7
|
mode: TaskMode;
|
|
8
8
|
status: TaskStatus;
|
|
9
9
|
timeoutMs?: number;
|
|
10
|
-
|
|
10
|
+
retries?: number;
|
|
11
|
+
attempts: number;
|
|
12
|
+
/** True if the last run timed out. Its body may still be running. */
|
|
13
|
+
timedOut: boolean;
|
|
14
|
+
constructor(description: string, fn: PromisableFn<void>, mode?: TaskMode, timeoutMs?: number, retries?: number);
|
|
11
15
|
run(globalTimeoutMs?: number): Promise<void>;
|
|
12
16
|
}
|
|
13
17
|
//# sourceMappingURL=test.d.ts.map
|
package/dist/esm/test.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AASlE,qBAAa,IAAI;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAK;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;IAEd,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,EAAE,QAAQ,CAAC;IAEf,MAAM,EAAE,UAAU,CAAsB;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AASlE,qBAAa,IAAI;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAK;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;IAEd,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,EAAE,QAAQ,CAAC;IAEf,MAAM,EAAE,UAAU,CAAsB;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAK;IACrB,qEAAqE;IACrE,QAAQ,EAAE,OAAO,CAAS;gBAGxB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EACtB,IAAI,GAAE,QAA2B,EACjC,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM;IASZ,GAAG,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA2DnD"}
|
package/dist/esm/test.js
CHANGED
|
@@ -13,11 +13,16 @@ export class Test {
|
|
|
13
13
|
mode;
|
|
14
14
|
status = TaskStatus.Pending;
|
|
15
15
|
timeoutMs;
|
|
16
|
-
|
|
16
|
+
retries;
|
|
17
|
+
attempts = 0;
|
|
18
|
+
/** True if the last run timed out. Its body may still be running. */
|
|
19
|
+
timedOut = false;
|
|
20
|
+
constructor(description, fn, mode = TaskMode.Default, timeoutMs, retries) {
|
|
17
21
|
this.description = description;
|
|
18
22
|
this.fn = fn;
|
|
19
23
|
this.mode = mode;
|
|
20
24
|
this.timeoutMs = timeoutMs;
|
|
25
|
+
this.retries = retries;
|
|
21
26
|
}
|
|
22
27
|
async run(globalTimeoutMs) {
|
|
23
28
|
const timeoutMs = this.timeoutMs ?? globalTimeoutMs;
|
|
@@ -33,12 +38,14 @@ export class Test {
|
|
|
33
38
|
}
|
|
34
39
|
const start = realPerformanceNow();
|
|
35
40
|
this.status = TaskStatus.Running;
|
|
41
|
+
this.timedOut = false;
|
|
36
42
|
const exec = Promise.resolve().then(() => this.fn());
|
|
37
43
|
try {
|
|
38
44
|
if (timeoutMs !== undefined) {
|
|
39
45
|
let timeoutId = null;
|
|
40
46
|
const timeoutFn = new Promise((_, reject) => {
|
|
41
47
|
timeoutId = setTimeout(() => {
|
|
48
|
+
this.timedOut = true;
|
|
42
49
|
reject(new Error(`Test "${this.description}" timed out after ${timeoutMs} ms`));
|
|
43
50
|
}, timeoutMs);
|
|
44
51
|
});
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export declare enum TaskMode {
|
|
|
16
16
|
}
|
|
17
17
|
export interface MatcherContext {
|
|
18
18
|
isNot: boolean;
|
|
19
|
+
/** "resolves" or "rejects" under those modifiers, otherwise "" (same as Jest). */
|
|
20
|
+
promise: "" | "resolves" | "rejects";
|
|
19
21
|
diff(a: unknown, b: unknown): string;
|
|
20
22
|
}
|
|
21
23
|
export type MatcherFn = (this: MatcherContext, received: unknown, ...args: readonly unknown[]) => void | Promise<void>;
|
package/dist/esm/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAE3C,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,MAAM,UAAU,CAAC,CAAC,CAAC,CAAC;AAElD,oBAAY,UAAU;IACpB,OAAO,YAAY;IACnB,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,IAAI,SAAS;CACd;AAED,oBAAY,QAAQ;IAClB,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,IAAI,SAAS;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACtC;AAED,MAAM,MAAM,SAAS,GAAG,CACtB,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,OAAO,EACjB,GAAG,IAAI,EAAE,SAAS,OAAO,EAAE,KACxB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAE3C,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,MAAM,UAAU,CAAC,CAAC,CAAC,CAAC;AAElD,oBAAY,UAAU;IACpB,OAAO,YAAY;IACnB,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,IAAI,SAAS;CACd;AAED,oBAAY,QAAQ;IAClB,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,IAAI,SAAS;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,kFAAkF;IAClF,OAAO,EAAE,EAAE,GAAG,UAAU,GAAG,SAAS,CAAC;IACrC,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACtC;AAED,MAAM,MAAM,SAAS,GAAG,CACtB,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,OAAO,EACjB,GAAG,IAAI,EAAE,SAAS,OAAO,EAAE,KACxB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defineConfig.d.ts","sourceRoot":"","sources":["../../../src/utils/defineConfig.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,mBAAmB,GAC3B,mBAAmB,CAErB"}
|
|
1
|
+
{"version":3,"file":"defineConfig.d.ts","sourceRoot":"","sources":["../../../src/utils/defineConfig.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,mBAAmB,GAC3B,mBAAmB,CAErB"}
|
|
@@ -30,5 +30,14 @@ export declare function regexTest(re: RegExp, value: string): boolean;
|
|
|
30
30
|
* - +0 and -0 are equal
|
|
31
31
|
*/
|
|
32
32
|
export declare function sameValue(a: unknown, b: unknown): boolean;
|
|
33
|
+
/** Throws unless `retries` is undefined or a non-negative integer. */
|
|
34
|
+
export declare function assertValidRetries(retries: unknown, where: string): void;
|
|
35
|
+
/** String() throws for e.g. null-prototype objects; fall back to the tag. */
|
|
36
|
+
export declare function safeString(value: unknown): string;
|
|
37
|
+
/**
|
|
38
|
+
* Error check that also works across realms (vm, workers, jsdom), where
|
|
39
|
+
* `instanceof Error` is false. Same test Jest uses.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isErrorLike(value: unknown): value is Error;
|
|
33
42
|
export {};
|
|
34
43
|
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/helpers.ts"],"names":[],"mappings":"AAAA,KAAK,SAAS,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhF,MAAM,MAAM,UAAU,GAClB,UAAU,GACV,SAAS,GACT,WAAW,GACX,UAAU,GACV,WAAW,GACX,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,aAAa,GACb,cAAc,CAAC;AAEnB,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAY9D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE5D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAEhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyDhD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CASzD"}
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/helpers.ts"],"names":[],"mappings":"AAAA,KAAK,SAAS,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhF,MAAM,MAAM,UAAU,GAClB,UAAU,GACV,SAAS,GACT,WAAW,GACX,UAAU,GACV,WAAW,GACX,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,aAAa,GACb,cAAc,CAAC;AAEnB,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAY9D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE5D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAEhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyDhD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CASzD;AAED,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CASxE;AAED,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAWjD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAW1D"}
|
|
@@ -124,3 +124,40 @@ export function sameValue(a, b) {
|
|
|
124
124
|
Number.isNaN(a) &&
|
|
125
125
|
Number.isNaN(b));
|
|
126
126
|
}
|
|
127
|
+
/** Throws unless `retries` is undefined or a non-negative integer. */
|
|
128
|
+
export function assertValidRetries(retries, where) {
|
|
129
|
+
if (retries !== undefined &&
|
|
130
|
+
!(typeof retries === "number" && Number.isInteger(retries) && retries >= 0)) {
|
|
131
|
+
throw new TypeError(`${where}: retries must be a non-negative integer, got ${typeof retries === "string" ? JSON.stringify(retries) : String(retries)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** String() throws for e.g. null-prototype objects; fall back to the tag. */
|
|
135
|
+
export function safeString(value) {
|
|
136
|
+
try {
|
|
137
|
+
return String(value);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// The tag lookup can throw too (a throwing Symbol.toStringTag getter).
|
|
141
|
+
try {
|
|
142
|
+
return Object.prototype.toString.call(value);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return "[unprintable value]";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Error check that also works across realms (vm, workers, jsdom), where
|
|
151
|
+
* `instanceof Error` is false. Same test Jest uses.
|
|
152
|
+
*/
|
|
153
|
+
export function isErrorLike(value) {
|
|
154
|
+
// Both checks can throw (Proxy traps, a throwing Symbol.toStringTag getter).
|
|
155
|
+
// A value that can't be inspected isn't treated as an Error.
|
|
156
|
+
try {
|
|
157
|
+
return (value instanceof Error ||
|
|
158
|
+
Object.prototype.toString.call(value) === "[object Error]");
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -7,5 +7,10 @@ interface MatchResult {
|
|
|
7
7
|
message: () => string;
|
|
8
8
|
}
|
|
9
9
|
export declare function toThrow(received: unknown, expected?: ToThrowExpected, isNot?: boolean): MatchResult;
|
|
10
|
+
/**
|
|
11
|
+
* toThrow under `.resolves` / `.rejects`. `received` is the settled value, not
|
|
12
|
+
* a function. Same as Jest: it only counts as thrown if it's an Error.
|
|
13
|
+
*/
|
|
14
|
+
export declare function toThrowFromPromise(received: unknown, expected: ToThrowExpected, isNot: boolean, promise: "resolves" | "rejects"): MatchResult;
|
|
10
15
|
export {};
|
|
11
16
|
//# sourceMappingURL=toThrow.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toThrow.d.ts","sourceRoot":"","sources":["../../../src/utils/toThrow.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,MAAM,GACN,MAAM,GACN,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,KAAK,CAAC,GACnC;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,UAAU,WAAW;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,MAAM,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"toThrow.d.ts","sourceRoot":"","sources":["../../../src/utils/toThrow.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,MAAM,GACN,MAAM,GACN,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,KAAK,CAAC,GACnC;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,UAAU,WAAW;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,MAAM,CAAC;CACvB;AA4ED,wBAAgB,OAAO,CACrB,QAAQ,EAAE,OAAO,EACjB,QAAQ,CAAC,EAAE,eAAe,EAC1B,KAAK,UAAQ,GACZ,WAAW,CAwDb;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,OAAO,EACjB,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,UAAU,GAAG,SAAS,GAC9B,WAAW,CAyBb"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { regexTest } from "./helpers.js";
|
|
1
|
+
import { isErrorLike, regexTest, safeString } from "./helpers.js";
|
|
2
2
|
function isErrorConstructor(err) {
|
|
3
3
|
return typeof err === "function";
|
|
4
4
|
}
|
|
5
5
|
function formatThrown(thrown) {
|
|
6
|
-
if (thrown
|
|
6
|
+
if (isErrorLike(thrown)) {
|
|
7
7
|
const name = thrown.name || "Error";
|
|
8
8
|
const msg = thrown.message ?? "";
|
|
9
9
|
return msg ? `${name}: ${msg}` : name;
|
|
@@ -12,13 +12,22 @@ function formatThrown(thrown) {
|
|
|
12
12
|
return typeof thrown === "string" ? thrown : JSON.stringify(thrown);
|
|
13
13
|
}
|
|
14
14
|
catch {
|
|
15
|
-
return
|
|
15
|
+
return safeString(thrown);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
function formatExpected(expected) {
|
|
19
|
+
return typeof expected === "string"
|
|
20
|
+
? `"${expected}"`
|
|
21
|
+
: expected instanceof RegExp
|
|
22
|
+
? expected.toString()
|
|
23
|
+
: isErrorConstructor(expected)
|
|
24
|
+
? expected.name || "[AnonymousErrorConstructor]"
|
|
25
|
+
: JSON.stringify(expected, (_key, value) => value instanceof RegExp ? value.toString() : value);
|
|
26
|
+
}
|
|
18
27
|
function matchThrown(thrown, expected) {
|
|
19
28
|
if (expected === undefined)
|
|
20
29
|
return true;
|
|
21
|
-
const msg = thrown
|
|
30
|
+
const msg = isErrorLike(thrown) ? thrown.message : safeString(thrown);
|
|
22
31
|
// toThrow("substring")
|
|
23
32
|
if (typeof expected === "string") {
|
|
24
33
|
return msg.includes(expected);
|
|
@@ -34,7 +43,7 @@ function matchThrown(thrown, expected) {
|
|
|
34
43
|
// toThrow({ name?, message? })
|
|
35
44
|
if (expected && typeof expected === "object") {
|
|
36
45
|
if (expected.name) {
|
|
37
|
-
const thrownName = thrown
|
|
46
|
+
const thrownName = isErrorLike(thrown) ? thrown.name : undefined;
|
|
38
47
|
if (thrownName !== expected.name)
|
|
39
48
|
return false;
|
|
40
49
|
}
|
|
@@ -44,7 +53,7 @@ function matchThrown(thrown, expected) {
|
|
|
44
53
|
return false;
|
|
45
54
|
}
|
|
46
55
|
else {
|
|
47
|
-
if (!expected.message
|
|
56
|
+
if (!regexTest(expected.message, msg))
|
|
48
57
|
return false;
|
|
49
58
|
}
|
|
50
59
|
}
|
|
@@ -73,7 +82,8 @@ export function toThrow(received, expected, isNot = false) {
|
|
|
73
82
|
result != null &&
|
|
74
83
|
typeof result.then === "function") {
|
|
75
84
|
// Suppress the unhandled rejection from the returned promise
|
|
76
|
-
|
|
85
|
+
// Promise.resolve so plain thenables without .catch work too
|
|
86
|
+
Promise.resolve(result).catch(() => { });
|
|
77
87
|
return {
|
|
78
88
|
pass: false,
|
|
79
89
|
message: () => "toThrow() received an async function. Use expect(fn).rejects.toThrow() for async errors.",
|
|
@@ -92,14 +102,33 @@ export function toThrow(received, expected, isNot = false) {
|
|
|
92
102
|
if (expected === undefined) {
|
|
93
103
|
return `Expected function ${expectation}, but it threw: ${thrownStr}`;
|
|
94
104
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
return `Expected function ${expectation} ${formatExpected(expected)}, but it threw: ${thrownStr}`;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* toThrow under `.resolves` / `.rejects`. `received` is the settled value, not
|
|
111
|
+
* a function. Same as Jest: it only counts as thrown if it's an Error.
|
|
112
|
+
*/
|
|
113
|
+
export function toThrowFromPromise(received, expected, isNot, promise) {
|
|
114
|
+
// Same as Jest: a non-Error function is called and treated like a sync
|
|
115
|
+
// toThrow, so `resolves.toThrow()` works on a promise of a function.
|
|
116
|
+
if (typeof received === "function" && !isErrorLike(received)) {
|
|
117
|
+
return toThrow(received, expected, isNot);
|
|
118
|
+
}
|
|
119
|
+
const isError = isErrorLike(received);
|
|
120
|
+
const matched = isError && matchThrown(received, expected);
|
|
121
|
+
const pass = isNot ? !matched : matched;
|
|
122
|
+
return {
|
|
123
|
+
pass,
|
|
124
|
+
message: () => {
|
|
125
|
+
const verb = promise === "rejects" ? "reject" : "resolve";
|
|
126
|
+
const past = promise === "rejects" ? "rejected" : "resolved";
|
|
127
|
+
const target = expected === undefined ? "an Error" : formatExpected(expected);
|
|
128
|
+
const got = isError
|
|
129
|
+
? formatThrown(received)
|
|
130
|
+
: `a non-Error value: ${formatThrown(received)}`;
|
|
131
|
+
return `Expected promise ${isNot ? "not " : ""}to ${verb} with ${target}, but it ${past} with ${got}`;
|
|
103
132
|
},
|
|
104
133
|
};
|
|
105
134
|
}
|