@unrift/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/cli.js +14 -13
- package/dist/esm/context.d.ts +1 -2
- package/dist/esm/context.d.ts.map +1 -1
- package/dist/esm/expect.d.ts +9 -1
- package/dist/esm/expect.d.ts.map +1 -1
- package/dist/esm/expect.js +11 -5
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/interface.d.ts +3 -3
- package/dist/esm/interface.d.ts.map +1 -1
- package/dist/esm/interface.js +11 -6
- package/dist/esm/matchers.d.ts.map +1 -1
- package/dist/esm/matchers.js +100 -0
- package/dist/esm/run.d.ts +1 -1
- package/dist/esm/run.d.ts.map +1 -1
- package/dist/esm/runner.d.ts +1 -0
- package/dist/esm/runner.d.ts.map +1 -1
- package/dist/esm/runner.js +126 -21
- package/dist/esm/test.d.ts +3 -2
- package/dist/esm/test.d.ts.map +1 -1
- package/dist/esm/test.js +5 -2
- package/dist/esm/types.d.ts +0 -1
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/types.js +0 -1
- package/dist/esm/utils/compareEngine.d.ts.map +1 -1
- package/dist/esm/utils/compareEngine.js +31 -0
- package/dist/esm/utils/defineConfig.d.ts +0 -1
- package/dist/esm/utils/defineConfig.d.ts.map +1 -1
- package/dist/esm/utils/diff.d.ts.map +1 -1
- package/dist/esm/utils/diff.js +31 -0
- package/dist/esm/utils/helpers.d.ts +11 -0
- package/dist/esm/utils/helpers.d.ts.map +1 -1
- package/dist/esm/utils/helpers.js +84 -0
- package/dist/esm/utils/loadConfig.d.ts.map +1 -1
- package/dist/esm/utils/loadConfig.js +2 -2
- package/dist/esm/utils/toThrow.d.ts.map +1 -1
- package/dist/esm/utils/toThrow.js +12 -1
- package/package.json +7 -3
package/dist/esm/cli.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { runTestsCLI } from "./runner.js";
|
|
5
|
+
import { runTestsCLI, watchTestsCLI } from "./runner.js";
|
|
6
|
+
import { safeRegExp } from "./utils/helpers.js";
|
|
6
7
|
const __cliDir = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
function readVersion() {
|
|
8
9
|
// Walk up from current file to find package.json (works from both src/ and dist/esm/)
|
|
@@ -21,15 +22,6 @@ function readVersion() {
|
|
|
21
22
|
return "0.0.0";
|
|
22
23
|
}
|
|
23
24
|
const version = readVersion();
|
|
24
|
-
function safeRegExp(source) {
|
|
25
|
-
try {
|
|
26
|
-
return new RegExp(source);
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
30
|
-
throw new Error(`Invalid pattern regex "${source}": ${msg}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
25
|
function printHelp() {
|
|
34
26
|
console.log(`
|
|
35
27
|
Unrift Test Runner v${version}
|
|
@@ -41,6 +33,7 @@ Options:
|
|
|
41
33
|
-c, --config <path> Path to config file
|
|
42
34
|
-b, --bail Stop on first test failure
|
|
43
35
|
-t, --timeout <ms> Default test timeout in milliseconds
|
|
36
|
+
-w, --watch Re-run tests on file changes
|
|
44
37
|
-d, --debug Enable debug logging
|
|
45
38
|
-l, --list List discovered test files
|
|
46
39
|
-j, --json Output test results as JSON
|
|
@@ -53,6 +46,7 @@ Examples:
|
|
|
53
46
|
unrift math
|
|
54
47
|
unrift -c test/config.ts --bail
|
|
55
48
|
unrift --timeout 10000
|
|
49
|
+
unrift --watch
|
|
56
50
|
`);
|
|
57
51
|
}
|
|
58
52
|
function parseArgs(argv) {
|
|
@@ -66,6 +60,7 @@ function parseArgs(argv) {
|
|
|
66
60
|
let list = false;
|
|
67
61
|
let json = false;
|
|
68
62
|
let cacheClean = false;
|
|
63
|
+
let watch = false;
|
|
69
64
|
const rest = [];
|
|
70
65
|
for (let i = 0; i < argv.length; i++) {
|
|
71
66
|
const a = argv[i];
|
|
@@ -111,6 +106,10 @@ function parseArgs(argv) {
|
|
|
111
106
|
i++;
|
|
112
107
|
continue;
|
|
113
108
|
}
|
|
109
|
+
if (a === "--watch" || a === "-w") {
|
|
110
|
+
watch = true;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
114
113
|
if (a === "--cache-clean" || a === "--clear-cache") {
|
|
115
114
|
cacheClean = true;
|
|
116
115
|
continue;
|
|
@@ -129,11 +128,12 @@ function parseArgs(argv) {
|
|
|
129
128
|
list,
|
|
130
129
|
json,
|
|
131
130
|
cacheClean,
|
|
131
|
+
watch,
|
|
132
132
|
help,
|
|
133
133
|
showVersion,
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
|
-
const { configPath, pattern, timeoutMs, bail, debug, list, json, cacheClean, help, showVersion, } = parseArgs(process.argv.slice(2));
|
|
136
|
+
const { configPath, pattern, timeoutMs, bail, debug, list, json, cacheClean, watch, help, showVersion, } = parseArgs(process.argv.slice(2));
|
|
137
137
|
if (showVersion) {
|
|
138
138
|
console.log(version);
|
|
139
139
|
process.exit(0);
|
|
@@ -142,7 +142,7 @@ if (help) {
|
|
|
142
142
|
printHelp();
|
|
143
143
|
process.exit(0);
|
|
144
144
|
}
|
|
145
|
-
|
|
145
|
+
const runOpts = {
|
|
146
146
|
pattern,
|
|
147
147
|
configPath,
|
|
148
148
|
timeoutMs,
|
|
@@ -151,7 +151,8 @@ runTestsCLI({
|
|
|
151
151
|
list,
|
|
152
152
|
json,
|
|
153
153
|
cacheClean,
|
|
154
|
-
}
|
|
154
|
+
};
|
|
155
|
+
(watch ? watchTestsCLI(runOpts) : runTestsCLI(runOpts)).catch((err) => {
|
|
155
156
|
console.error(err);
|
|
156
157
|
process.exit(1);
|
|
157
158
|
});
|
package/dist/esm/context.d.ts
CHANGED
|
@@ -4,10 +4,9 @@ export declare enum RunState {
|
|
|
4
4
|
Run = "run",
|
|
5
5
|
Idle = "idle"
|
|
6
6
|
}
|
|
7
|
-
type UnriftState = RunState;
|
|
8
7
|
interface UnriftGlobalContext {
|
|
9
8
|
currentSuite: Suite | null;
|
|
10
|
-
state:
|
|
9
|
+
state: RunState;
|
|
11
10
|
}
|
|
12
11
|
export declare const unriftGlobalContext: UnriftGlobalContext;
|
|
13
12
|
export declare function assertRegisterState(apiName: string): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,oBAAY,QAAQ;IAClB,QAAQ,aAAa;IACrB,GAAG,QAAQ;IACX,IAAI,SAAS;CACd;AAED,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,oBAAY,QAAQ;IAClB,QAAQ,aAAa;IACrB,GAAG,QAAQ;IACX,IAAI,SAAS;CACd;AAED,UAAU,mBAAmB;IAC3B,YAAY,EAAE,KAAK,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,QAAQ,CAAC;CACjB;AAED,eAAO,MAAM,mBAAmB,EAAE,mBAGjC,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,QAOlD"}
|
package/dist/esm/expect.d.ts
CHANGED
|
@@ -13,13 +13,21 @@ export interface Matchers<T> {
|
|
|
13
13
|
toBeUndefined(): void | Promise<void>;
|
|
14
14
|
toBeGreaterThan(expected: number): void | Promise<void>;
|
|
15
15
|
toBeLessThan(expected: number): void | Promise<void>;
|
|
16
|
-
toBeInstanceOf(expected:
|
|
16
|
+
toBeInstanceOf(expected: {
|
|
17
|
+
prototype: unknown;
|
|
18
|
+
}): void | Promise<void>;
|
|
17
19
|
toContain(expected: unknown): void | Promise<void>;
|
|
18
20
|
toEqual(expected: unknown): void | Promise<void>;
|
|
19
21
|
toHaveLength(expected: number): void | Promise<void>;
|
|
20
22
|
toMatch(expected: RegExp | string): void | Promise<void>;
|
|
21
23
|
toStrictEqual(expected: unknown): void | Promise<void>;
|
|
22
24
|
toThrow(expected?: unknown): void | Promise<void>;
|
|
25
|
+
toBeGreaterThanOrEqual(expected: number): void | Promise<void>;
|
|
26
|
+
toBeLessThanOrEqual(expected: number): void | Promise<void>;
|
|
27
|
+
toBeNaN(): void | Promise<void>;
|
|
28
|
+
toBeFinite(): void | Promise<void>;
|
|
29
|
+
toMatchObject(expected: Record<string, unknown>): void | Promise<void>;
|
|
30
|
+
toHaveProperty(path: string | readonly string[], value?: unknown): void | Promise<void>;
|
|
23
31
|
readonly not: Matchers<T>;
|
|
24
32
|
readonly resolves: Matchers<Awaited<T>>;
|
|
25
33
|
readonly rejects: Matchers<unknown>;
|
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;AAK1D,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,
|
|
1
|
+
{"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/expect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,UAAU,EAAE,MAAM,SAAS,CAAC;AAK1D,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,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,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;IAExB,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,eAAO,MAAM,MAAM,EAAE,eAsGjB,CAAC"}
|
package/dist/esm/expect.js
CHANGED
|
@@ -18,19 +18,25 @@ export const expect = (() => {
|
|
|
18
18
|
const fn = matcherRegistry[name];
|
|
19
19
|
matcherFns[name] = (...args) => fn.call(ctx, received, ...args);
|
|
20
20
|
}
|
|
21
|
-
function makeAsyncProxy(getActual) {
|
|
21
|
+
function makeAsyncProxy(getActual, asyncIsNot) {
|
|
22
|
+
const asyncCtx = {
|
|
23
|
+
isNot: asyncIsNot,
|
|
24
|
+
diff(a, b) {
|
|
25
|
+
return formatDiff(a, b);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
22
28
|
const asyncFns = {};
|
|
23
29
|
for (const name in matcherRegistry) {
|
|
24
30
|
const fn = matcherRegistry[name];
|
|
25
31
|
asyncFns[name] = async (...args) => {
|
|
26
32
|
const actual = await getActual();
|
|
27
|
-
return fn.call(
|
|
33
|
+
return fn.call(asyncCtx, actual, ...args);
|
|
28
34
|
};
|
|
29
35
|
}
|
|
30
36
|
return new Proxy(asyncFns, {
|
|
31
37
|
get(target, prop) {
|
|
32
38
|
if (prop === "not") {
|
|
33
|
-
return
|
|
39
|
+
return makeAsyncProxy(getActual, !asyncIsNot);
|
|
34
40
|
}
|
|
35
41
|
if (typeof prop === "string" && !(prop in target)) {
|
|
36
42
|
throw new Error(`Unknown matcher: ${prop}`);
|
|
@@ -51,7 +57,7 @@ export const expect = (() => {
|
|
|
51
57
|
catch (err) {
|
|
52
58
|
throw new Error(`Expected promise to resolve, but it rejected with: ${err instanceof Error ? err.message : String(err)}`);
|
|
53
59
|
}
|
|
54
|
-
});
|
|
60
|
+
}, isNot);
|
|
55
61
|
}
|
|
56
62
|
if (prop === "rejects") {
|
|
57
63
|
return makeAsyncProxy(async () => {
|
|
@@ -66,7 +72,7 @@ export const expect = (() => {
|
|
|
66
72
|
}
|
|
67
73
|
return err;
|
|
68
74
|
}
|
|
69
|
-
});
|
|
75
|
+
}, isNot);
|
|
70
76
|
}
|
|
71
77
|
if (typeof prop === "string" && !(prop in target)) {
|
|
72
78
|
throw new Error(`Unknown matcher: ${prop}`);
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -4,4 +4,6 @@ export { describe, it, test } 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";
|
|
7
|
+
export { runEngine } from "./run";
|
|
8
|
+
export type { RunEngineResult } from "./run";
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
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;AACjD,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"}
|
|
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;AACjD,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"}
|
package/dist/esm/index.js
CHANGED
|
@@ -2,3 +2,4 @@ export { beforeEach, afterEach, beforeAll, afterAll } from "./hooks.js";
|
|
|
2
2
|
export { defineConfig } from "./utils/defineConfig.js";
|
|
3
3
|
export { describe, it, test } from "./interface.js";
|
|
4
4
|
export { expect, extendMatchers } from "./expect.js";
|
|
5
|
+
export { runEngine } from "./run.js";
|
package/dist/esm/interface.d.ts
CHANGED
|
@@ -9,9 +9,9 @@ type DescribeFn = {
|
|
|
9
9
|
todo: (description: string) => void;
|
|
10
10
|
};
|
|
11
11
|
type ItFn = {
|
|
12
|
-
(description: string, fn: PromisableFn<void
|
|
13
|
-
only: (description: string, fn: PromisableFn<void
|
|
14
|
-
skip: (description: string, fn: PromisableFn<void
|
|
12
|
+
(description: string, fn: PromisableFn<void>, timeoutMs?: number): void;
|
|
13
|
+
only: (description: string, fn: PromisableFn<void>, timeoutMs?: number) => void;
|
|
14
|
+
skip: (description: string, fn: PromisableFn<void>, timeoutMs?: number) => void;
|
|
15
15
|
todo: (description: string) => void;
|
|
16
16
|
};
|
|
17
17
|
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;AAEtD;;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,KAAK,IAAI,GAAG;IACV,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/interface.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,YAAY,EAAY,MAAM,SAAS,CAAC;AAEtD;;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,KAAK,IAAI,GAAG;IACV,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxE,IAAI,EAAE,CACJ,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EACtB,SAAS,CAAC,EAAE,MAAM,KACf,IAAI,CAAC;IACV,IAAI,EAAE,CACJ,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EACtB,SAAS,CAAC,EAAE,MAAM,KACf,IAAI,CAAC;IACV,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AA6BF,QAAA,MAAM,QAAQ,EAAE,UAC8B,CAAC;AAmB/C,QAAA,MAAM,EAAE,EAAE,IACyC,CAAC;AAOpD,QAAA,MAAM,IAAI,EAAE,IAAS,CAAC;AAEtB,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC"}
|
package/dist/esm/interface.js
CHANGED
|
@@ -9,7 +9,12 @@ function _describe(description, fn, mode) {
|
|
|
9
9
|
parent.addSuite(suite);
|
|
10
10
|
unriftGlobalContext.currentSuite = suite;
|
|
11
11
|
try {
|
|
12
|
-
fn();
|
|
12
|
+
const result = fn();
|
|
13
|
+
if (result != null &&
|
|
14
|
+
typeof result.then === "function") {
|
|
15
|
+
throw new Error(`describe() callbacks must be synchronous. ` +
|
|
16
|
+
`Move async setup into beforeAll() or beforeEach().`);
|
|
17
|
+
}
|
|
13
18
|
}
|
|
14
19
|
finally {
|
|
15
20
|
unriftGlobalContext.currentSuite = parent;
|
|
@@ -19,14 +24,14 @@ const describe = (description, fn) => _describe(description, fn, TaskMode.Defaul
|
|
|
19
24
|
describe.only = (description, fn) => _describe(description, fn, TaskMode.Only);
|
|
20
25
|
describe.skip = (description, fn) => _describe(description, fn, TaskMode.Skip);
|
|
21
26
|
describe.todo = (description) => _describe(description, () => { }, TaskMode.Todo);
|
|
22
|
-
function _it(description, fn, mode) {
|
|
27
|
+
function _it(description, fn, mode, timeoutMs) {
|
|
23
28
|
assertRegisterState("it");
|
|
24
|
-
const test = new Test(description, fn, mode);
|
|
29
|
+
const test = new Test(description, fn, mode, timeoutMs);
|
|
25
30
|
getCurrentSuite().addTest(test);
|
|
26
31
|
}
|
|
27
|
-
const it = (description, fn) => _it(description, fn, TaskMode.Default);
|
|
28
|
-
it.only = (description, fn) => _it(description, fn, TaskMode.Only);
|
|
29
|
-
it.skip = (description, fn) => _it(description, fn, TaskMode.Skip);
|
|
32
|
+
const it = (description, fn, timeoutMs) => _it(description, fn, TaskMode.Default, timeoutMs);
|
|
33
|
+
it.only = (description, fn, timeoutMs) => _it(description, fn, TaskMode.Only, timeoutMs);
|
|
34
|
+
it.skip = (description, fn, timeoutMs) => _it(description, fn, TaskMode.Skip, timeoutMs);
|
|
30
35
|
it.todo = (description) => _it(description, () => { }, TaskMode.Todo);
|
|
31
36
|
const test = it;
|
|
32
37
|
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":"AA8DA,wBAAgB,sBAAsB,SAMrC;AAED,wBAAgB,aAAa,SAE5B"}
|
package/dist/esm/matchers.js
CHANGED
|
@@ -3,6 +3,46 @@ import { deepEqual } from "./utils/deepEqual.js";
|
|
|
3
3
|
import { looseEqual } from "./utils/looseEqual.js";
|
|
4
4
|
import { toThrow } from "./utils/toThrow.js";
|
|
5
5
|
let matchersRegistered = false;
|
|
6
|
+
function partialMatch(received, expected) {
|
|
7
|
+
if (expected === null || typeof expected !== "object") {
|
|
8
|
+
return looseEqual(received, expected);
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(expected)) {
|
|
11
|
+
if (!Array.isArray(received))
|
|
12
|
+
return false;
|
|
13
|
+
if (received.length !== expected.length)
|
|
14
|
+
return false;
|
|
15
|
+
return expected.every((e, i) => partialMatch(received[i], e));
|
|
16
|
+
}
|
|
17
|
+
if (received === null ||
|
|
18
|
+
typeof received !== "object" ||
|
|
19
|
+
Array.isArray(received)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const recv = received;
|
|
23
|
+
const exp = expected;
|
|
24
|
+
for (const key of Object.keys(exp)) {
|
|
25
|
+
if (!Object.hasOwn(recv, key))
|
|
26
|
+
return false;
|
|
27
|
+
if (!partialMatch(recv[key], exp[key]))
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
function getNestedValue(obj, path) {
|
|
33
|
+
const keys = Array.isArray(path) ? path : path.split(".");
|
|
34
|
+
let current = obj;
|
|
35
|
+
for (const key of keys) {
|
|
36
|
+
if (current === null || typeof current !== "object") {
|
|
37
|
+
return { found: false, value: undefined };
|
|
38
|
+
}
|
|
39
|
+
const record = current;
|
|
40
|
+
if (!Object.hasOwn(record, key))
|
|
41
|
+
return { found: false, value: undefined };
|
|
42
|
+
current = record[key];
|
|
43
|
+
}
|
|
44
|
+
return { found: true, value: current };
|
|
45
|
+
}
|
|
6
46
|
export function ensureInternalMatchers() {
|
|
7
47
|
if (matchersRegistered)
|
|
8
48
|
return;
|
|
@@ -138,5 +178,65 @@ function registerCoreMatchers() {
|
|
|
138
178
|
if (!pass)
|
|
139
179
|
throw new Error(message());
|
|
140
180
|
},
|
|
181
|
+
toBeGreaterThanOrEqual(received, expected) {
|
|
182
|
+
if (typeof received !== "number") {
|
|
183
|
+
throw new Error(this.diff(received, "expected a number"));
|
|
184
|
+
}
|
|
185
|
+
const pass = received >= expected;
|
|
186
|
+
if (this.isNot ? pass : !pass) {
|
|
187
|
+
throw new Error(this.diff(received, `>= ${expected}`));
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
toBeLessThanOrEqual(received, expected) {
|
|
191
|
+
if (typeof received !== "number") {
|
|
192
|
+
throw new Error(this.diff(received, "expected a number"));
|
|
193
|
+
}
|
|
194
|
+
const pass = received <= expected;
|
|
195
|
+
if (this.isNot ? pass : !pass) {
|
|
196
|
+
throw new Error(this.diff(received, `<= ${expected}`));
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
toBeNaN(received) {
|
|
200
|
+
const pass = Number.isNaN(received);
|
|
201
|
+
if (this.isNot ? pass : !pass) {
|
|
202
|
+
throw new Error(this.diff(received, "NaN"));
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
toBeFinite(received) {
|
|
206
|
+
const pass = Number.isFinite(received);
|
|
207
|
+
if (this.isNot ? pass : !pass) {
|
|
208
|
+
throw new Error(this.diff(received, "finite number"));
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
toMatchObject(received, expected) {
|
|
212
|
+
if (received === null ||
|
|
213
|
+
typeof received !== "object" ||
|
|
214
|
+
Array.isArray(received)) {
|
|
215
|
+
throw new Error(this.diff(received, "expected a non-array object for toMatchObject()"));
|
|
216
|
+
}
|
|
217
|
+
const pass = partialMatch(received, expected);
|
|
218
|
+
if (this.isNot ? pass : !pass) {
|
|
219
|
+
throw new Error(this.diff(received, expected));
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
toHaveProperty(received, path, ...rest) {
|
|
223
|
+
if (received === null || typeof received !== "object") {
|
|
224
|
+
throw new Error(this.diff(received, "expected an object for toHaveProperty()"));
|
|
225
|
+
}
|
|
226
|
+
const normalizedPath = path;
|
|
227
|
+
const { found, value } = getNestedValue(received, normalizedPath);
|
|
228
|
+
const hasValue = rest.length > 0;
|
|
229
|
+
const expectedValue = rest[0];
|
|
230
|
+
const pass = hasValue ? found && looseEqual(value, expectedValue) : found;
|
|
231
|
+
if (this.isNot ? pass : !pass) {
|
|
232
|
+
const pathStr = Array.isArray(normalizedPath)
|
|
233
|
+
? normalizedPath.join(".")
|
|
234
|
+
: normalizedPath;
|
|
235
|
+
if (!found) {
|
|
236
|
+
throw new Error(this.diff(Object.keys(received), `property "${pathStr}"`));
|
|
237
|
+
}
|
|
238
|
+
throw new Error(this.diff(value, expectedValue));
|
|
239
|
+
}
|
|
240
|
+
},
|
|
141
241
|
});
|
|
142
242
|
}
|
package/dist/esm/run.d.ts
CHANGED
package/dist/esm/run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAIA,OAAO,EAAY,UAAU,EAAE,MAAM,SAAS,CAAC;AAI/C,KAAK,gBAAgB,GAAG;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAIA,OAAO,EAAY,UAAU,EAAE,MAAM,SAAS,CAAC;AAI/C,KAAK,gBAAgB,GAAG;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,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,KAAK,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,UAAU,CAAC;QACnB,KAAK,CAAC,EAAE,KAAK,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ,CAAC;AAQF,wBAAsB,SAAS,CAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CA2D1B"}
|
package/dist/esm/runner.d.ts
CHANGED
|
@@ -10,5 +10,6 @@ interface RunnerOptions {
|
|
|
10
10
|
cacheClean?: boolean;
|
|
11
11
|
}
|
|
12
12
|
export declare function runTestsCLI(options?: RunnerOptions): Promise<void>;
|
|
13
|
+
export declare function watchTestsCLI(options?: RunnerOptions): Promise<void>;
|
|
13
14
|
export {};
|
|
14
15
|
//# sourceMappingURL=runner.d.ts.map
|
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":"AAkBA,UAAU,aAAa;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,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;AA0ND,wBAAsB,WAAW,CAAC,OAAO,GAAE,aAAkB,iBA8T5D;AA+DD,wBAAsB,aAAa,CAAC,OAAO,GAAE,aAAkB,iBAyD9D"}
|
package/dist/esm/runner.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { watch as fsWatch, readdirSync } from "node:fs";
|
|
2
|
+
import { relative, resolve, join } from "node:path";
|
|
2
3
|
import { loadConfig, loadConfigFromPath, } from "./utils/loadConfig.js";
|
|
3
4
|
import { cleanUnriftCaches } from "./utils/transform.js";
|
|
4
5
|
import { colors } from "./utils/colors.js";
|
|
5
6
|
import { discoverTestFiles } from "./utils/discoverTestFiles.js";
|
|
6
7
|
import { runEngine } from "./run.js";
|
|
7
8
|
import { normalizePath } from "./utils/normalizePath.js";
|
|
9
|
+
import { safeRegExp, isGlobPattern, globToRegex } from "./utils/helpers.js";
|
|
8
10
|
import { TaskStatus } from "./types.js";
|
|
9
11
|
function debugLog(enabled, json, ...args) {
|
|
10
12
|
// Keep stdout clean for --json mode; use stderr for debug.
|
|
@@ -12,27 +14,15 @@ function debugLog(enabled, json, ...args) {
|
|
|
12
14
|
return;
|
|
13
15
|
(json ? console.error : console.log)(...args);
|
|
14
16
|
}
|
|
15
|
-
function safeRegExp(source) {
|
|
16
|
-
try {
|
|
17
|
-
return new RegExp(source);
|
|
18
|
-
}
|
|
19
|
-
catch (err) {
|
|
20
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
21
|
-
throw new Error(`Invalid regex "${source}": ${msg}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
17
|
const regExpCache = new Map();
|
|
25
18
|
function getCachedRegExp(pattern) {
|
|
26
19
|
let re = regExpCache.get(pattern);
|
|
27
20
|
if (!re) {
|
|
28
|
-
re = safeRegExp(pattern);
|
|
21
|
+
re = isGlobPattern(pattern) ? globToRegex(pattern) : safeRegExp(pattern);
|
|
29
22
|
regExpCache.set(pattern, re);
|
|
30
23
|
}
|
|
31
24
|
return re;
|
|
32
25
|
}
|
|
33
|
-
/**
|
|
34
|
-
* @TODO Add glob support
|
|
35
|
-
*/
|
|
36
26
|
function matchesAnyPattern(fileAbs, patterns, baseDir) {
|
|
37
27
|
const absolutePath = normalizePath(fileAbs);
|
|
38
28
|
const relativePath = normalizePath(relative(baseDir, fileAbs));
|
|
@@ -51,6 +41,32 @@ function filterByIncludesExcludes(files, baseDir, includes, excludes) {
|
|
|
51
41
|
}
|
|
52
42
|
return filtered;
|
|
53
43
|
}
|
|
44
|
+
function filterStack(stack) {
|
|
45
|
+
const lines = stack.split("\n");
|
|
46
|
+
const message = [];
|
|
47
|
+
const frames = [];
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
if (line.trimStart().startsWith("at ")) {
|
|
50
|
+
frames.push(line);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
message.push(line);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const filtered = frames.filter((line) => {
|
|
57
|
+
if (line.includes("node:"))
|
|
58
|
+
return false;
|
|
59
|
+
if (line.includes("/@unrift/core/dist/"))
|
|
60
|
+
return false;
|
|
61
|
+
if (line.includes("/unrift/dist/"))
|
|
62
|
+
return false;
|
|
63
|
+
if (line.includes("/dist/esm/"))
|
|
64
|
+
return false;
|
|
65
|
+
return true;
|
|
66
|
+
});
|
|
67
|
+
const kept = filtered.length > 0 ? filtered : frames;
|
|
68
|
+
return [...message, ...kept].join("\n");
|
|
69
|
+
}
|
|
54
70
|
function formatDuration(ms) {
|
|
55
71
|
if (ms < 1000)
|
|
56
72
|
return `${ms.toFixed(0)}ms`;
|
|
@@ -127,6 +143,7 @@ function extractTestName(description) {
|
|
|
127
143
|
return description.replace(/^.*?›\s*/, "");
|
|
128
144
|
}
|
|
129
145
|
export async function runTestsCLI(options = {}) {
|
|
146
|
+
regExpCache.clear();
|
|
130
147
|
const runStart = performance.now();
|
|
131
148
|
// Cache clean mode
|
|
132
149
|
if (options.cacheClean) {
|
|
@@ -162,8 +179,10 @@ export async function runTestsCLI(options = {}) {
|
|
|
162
179
|
// Test dir is relative to config dir if config is loaded
|
|
163
180
|
const testDir = resolve(configDir, config?.testDir ?? options.testDir ?? "test");
|
|
164
181
|
let files = discoverTestFiles(testDir);
|
|
165
|
-
|
|
166
|
-
|
|
182
|
+
const pattern = options.pattern ??
|
|
183
|
+
(config?.pattern ? safeRegExp(config.pattern) : undefined);
|
|
184
|
+
if (pattern) {
|
|
185
|
+
files = files.filter((f) => pattern.test(normalizePath(f)));
|
|
167
186
|
}
|
|
168
187
|
files = filterByIncludesExcludes(files, testDir, config?.includes, config?.excludes).sort((a, b) => normalizePath(a).localeCompare(normalizePath(b)));
|
|
169
188
|
debugLog(options.debug, options.json, "Discovered test files:", files);
|
|
@@ -296,14 +315,13 @@ export async function runTestsCLI(options = {}) {
|
|
|
296
315
|
const parts = testName.split(" › ");
|
|
297
316
|
const leaf = parts[parts.length - 1];
|
|
298
317
|
const suitePath = parts.slice(0, -1).join(" › ");
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
318
|
+
const depth = parts.length - 1;
|
|
319
|
+
// Print suite name when it changes (depth > 0 guards against top-level it() tests)
|
|
320
|
+
if (depth > 0 && suitePath && suitePath !== lastSuitePath) {
|
|
302
321
|
const suiteIndent = " " + " ".repeat(Math.max(0, depth - 1));
|
|
303
322
|
console.log(`${suiteIndent}${colors.dim("›")} ${colors.bold(parts[depth - 1])}`);
|
|
304
323
|
lastSuitePath = suitePath;
|
|
305
324
|
}
|
|
306
|
-
const depth = parts.length - 1;
|
|
307
325
|
const indent = " " + " ".repeat(depth);
|
|
308
326
|
if (result.status === TaskStatus.Pass) {
|
|
309
327
|
const dur = colors.dim(formatDuration(result.durationMs ?? 0));
|
|
@@ -332,7 +350,7 @@ export async function runTestsCLI(options = {}) {
|
|
|
332
350
|
const testName = extractTestName(r.description);
|
|
333
351
|
console.log(colors.boldRed(` ${i + 1}) ${testName}`));
|
|
334
352
|
if (r.error) {
|
|
335
|
-
const errText = r.error.stack ?? r.error.message;
|
|
353
|
+
const errText = filterStack(r.error.stack ?? r.error.message);
|
|
336
354
|
const lines = errText.split("\n");
|
|
337
355
|
for (const line of lines) {
|
|
338
356
|
// Highlight the "Received/Expected" lines differently
|
|
@@ -373,3 +391,90 @@ export async function runTestsCLI(options = {}) {
|
|
|
373
391
|
if (!ok)
|
|
374
392
|
process.exitCode = 1;
|
|
375
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Watch a directory tree for changes, calling `onChange` after a debounce.
|
|
396
|
+
* Uses individual `fs.watch()` calls per directory so it works on all
|
|
397
|
+
* platforms (Linux `fs.watch` does not support `recursive: true` before Node 22).
|
|
398
|
+
* Returns a cleanup function to stop watching.
|
|
399
|
+
*/
|
|
400
|
+
function watchRecursive(dir, onChange, debounceMs = 150) {
|
|
401
|
+
let timer = null;
|
|
402
|
+
let lastFile = "";
|
|
403
|
+
function trigger(filename) {
|
|
404
|
+
lastFile = filename;
|
|
405
|
+
if (timer)
|
|
406
|
+
clearTimeout(timer);
|
|
407
|
+
timer = setTimeout(() => onChange(lastFile), debounceMs);
|
|
408
|
+
}
|
|
409
|
+
const watchers = [];
|
|
410
|
+
function watchDir(d) {
|
|
411
|
+
try {
|
|
412
|
+
watchers.push(fsWatch(d, (_event, filename) => trigger(filename ?? d)));
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
let entries;
|
|
418
|
+
try {
|
|
419
|
+
entries = readdirSync(d, { withFileTypes: true });
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
for (const entry of entries) {
|
|
425
|
+
if (entry.isDirectory() &&
|
|
426
|
+
!entry.name.startsWith(".") &&
|
|
427
|
+
entry.name !== "node_modules") {
|
|
428
|
+
watchDir(join(d, entry.name));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
watchDir(dir);
|
|
433
|
+
return () => {
|
|
434
|
+
if (timer)
|
|
435
|
+
clearTimeout(timer);
|
|
436
|
+
for (const w of watchers) {
|
|
437
|
+
try {
|
|
438
|
+
w.close();
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
// ignore
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
export async function watchTestsCLI(options = {}) {
|
|
447
|
+
// Run once immediately
|
|
448
|
+
await runTestsCLI(options);
|
|
449
|
+
// Resolve the directory to watch (same logic as runTestsCLI)
|
|
450
|
+
const loaded = options.configPath
|
|
451
|
+
? await import("./utils/loadConfig").then((m) => m.loadConfigFromPath(options.configPath))
|
|
452
|
+
: await import("./utils/loadConfig").then((m) => m.loadConfig(process.cwd()));
|
|
453
|
+
const config = loaded?.config ?? null;
|
|
454
|
+
const configDir = loaded?.configDir ?? process.cwd();
|
|
455
|
+
const testDir = resolve(configDir, config?.testDir ?? options.testDir ?? "test");
|
|
456
|
+
console.log(`\n ${colors.dim("↺")} ${colors.dim("Watching")} ${colors.bold(shortenPath(testDir))} ${colors.dim("for changes… Ctrl+C to quit")}\n`);
|
|
457
|
+
let running = false;
|
|
458
|
+
const stop = watchRecursive(testDir, async (filename) => {
|
|
459
|
+
if (running)
|
|
460
|
+
return;
|
|
461
|
+
running = true;
|
|
462
|
+
const shortFile = shortenPath(filename);
|
|
463
|
+
console.log(`\n${colors.dim("─".repeat(Math.min(process.stdout.columns || 80, 80)))}`);
|
|
464
|
+
console.log(` ${colors.dim("↺")} ${colors.dim(`${shortFile} changed — re-running…`)}\n`);
|
|
465
|
+
// Reset exit code so each run is evaluated independently
|
|
466
|
+
process.exitCode = 0;
|
|
467
|
+
try {
|
|
468
|
+
await runTestsCLI(options);
|
|
469
|
+
}
|
|
470
|
+
finally {
|
|
471
|
+
running = false;
|
|
472
|
+
}
|
|
473
|
+
console.log(` ${colors.dim("↺")} ${colors.dim("Watching for changes… Ctrl+C to quit")}\n`);
|
|
474
|
+
});
|
|
475
|
+
// Keep process alive and clean up on exit
|
|
476
|
+
process.on("SIGINT", () => {
|
|
477
|
+
stop();
|
|
478
|
+
process.exit(0);
|
|
479
|
+
});
|
|
480
|
+
}
|
package/dist/esm/test.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ export declare class Test {
|
|
|
6
6
|
fn: PromisableFn<void>;
|
|
7
7
|
mode: TaskMode;
|
|
8
8
|
status: TaskStatus;
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
constructor(description: string, fn: PromisableFn<void>, mode?: TaskMode, timeoutMs?: number);
|
|
11
|
+
run(globalTimeoutMs?: number): Promise<void>;
|
|
11
12
|
}
|
|
12
13
|
//# 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;AAGlE,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;
|
|
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;AAGlE,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;gBAGjB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EACtB,IAAI,GAAE,QAA2B,EACjC,SAAS,CAAC,EAAE,MAAM;IAQd,GAAG,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAuDnD"}
|
package/dist/esm/test.js
CHANGED
|
@@ -7,12 +7,15 @@ export class Test {
|
|
|
7
7
|
fn;
|
|
8
8
|
mode;
|
|
9
9
|
status = TaskStatus.Pending;
|
|
10
|
-
|
|
10
|
+
timeoutMs;
|
|
11
|
+
constructor(description, fn, mode = TaskMode.Default, timeoutMs) {
|
|
11
12
|
this.description = description;
|
|
12
13
|
this.fn = fn;
|
|
13
14
|
this.mode = mode;
|
|
15
|
+
this.timeoutMs = timeoutMs;
|
|
14
16
|
}
|
|
15
|
-
async run(
|
|
17
|
+
async run(globalTimeoutMs) {
|
|
18
|
+
const timeoutMs = this.timeoutMs ?? globalTimeoutMs;
|
|
16
19
|
if (this.mode === TaskMode.Skip) {
|
|
17
20
|
this.status = TaskStatus.Skipped;
|
|
18
21
|
this.durationMs = 0;
|
package/dist/esm/types.d.ts
CHANGED
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,
|
|
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"}
|
package/dist/esm/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compareEngine.d.ts","sourceRoot":"","sources":["../../../src/utils/compareEngine.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,cAAc,EAAE,OAAO,CAAC;IACxB,0EAA0E;IAC1E,sBAAsB,EAAE,OAAO,CAAC;IAChC,yDAAyD;IACzD,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAID,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,cAAc,EACvB,eAAe,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,EACxC,eAAe,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,GACxC,OAAO,
|
|
1
|
+
{"version":3,"file":"compareEngine.d.ts","sourceRoot":"","sources":["../../../src/utils/compareEngine.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,cAAc,EAAE,OAAO,CAAC;IACxB,0EAA0E;IAC1E,sBAAsB,EAAE,OAAO,CAAC;IAChC,yDAAyD;IACzD,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAID,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,cAAc,EACvB,eAAe,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,EACxC,eAAe,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,GACxC,OAAO,CA4TT"}
|
|
@@ -142,6 +142,21 @@ export function compareInternal(left, right, options, leftToRightSeen, rightToLe
|
|
|
142
142
|
const rightSet = currentRight;
|
|
143
143
|
if (leftSet.size !== rightSet.size)
|
|
144
144
|
return false;
|
|
145
|
+
// Fast path: primitive-only sets can use has() directly — O(n) vs O(n²)
|
|
146
|
+
let allPrimitive = true;
|
|
147
|
+
for (const v of leftSet) {
|
|
148
|
+
if (!isPrimitive(v)) {
|
|
149
|
+
allPrimitive = false;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (allPrimitive) {
|
|
154
|
+
for (const v of leftSet) {
|
|
155
|
+
if (!rightSet.has(v))
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
145
160
|
const remaining = new Set(rightSet);
|
|
146
161
|
outer: for (const leftValue of leftSet) {
|
|
147
162
|
for (const rightValue of remaining) {
|
|
@@ -164,6 +179,22 @@ export function compareInternal(left, right, options, leftToRightSeen, rightToLe
|
|
|
164
179
|
const rightMap = currentRight;
|
|
165
180
|
if (leftMap.size !== rightMap.size)
|
|
166
181
|
return false;
|
|
182
|
+
// Fast path: primitive-only keys can use get() directly — O(n) vs O(n²)
|
|
183
|
+
let allKeysPrimitive = true;
|
|
184
|
+
for (const k of leftMap.keys()) {
|
|
185
|
+
if (!isPrimitive(k)) {
|
|
186
|
+
allKeysPrimitive = false;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (allKeysPrimitive) {
|
|
191
|
+
for (const [leftKey, leftValue] of leftMap.entries()) {
|
|
192
|
+
if (!rightMap.has(leftKey))
|
|
193
|
+
return false;
|
|
194
|
+
worklist.push([leftValue, rightMap.get(leftKey)]);
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
167
198
|
const remainingEntries = new Set(rightMap.entries());
|
|
168
199
|
outer: for (const [leftKey, leftValue] of leftMap.entries()) {
|
|
169
200
|
for (const candidate of remainingEntries) {
|
|
@@ -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,
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/utils/diff.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/utils/diff.ts"],"names":[],"mappings":"AAuGA,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAsCvE"}
|
package/dist/esm/utils/diff.js
CHANGED
|
@@ -37,6 +37,32 @@ function formatValue(value, indent = 2) {
|
|
|
37
37
|
}
|
|
38
38
|
return String(value);
|
|
39
39
|
}
|
|
40
|
+
function diffArrays(received, expected) {
|
|
41
|
+
const len = Math.max(received.length, expected.length);
|
|
42
|
+
const lines = [];
|
|
43
|
+
for (let i = 0; i < len; i++) {
|
|
44
|
+
const inReceived = i < received.length;
|
|
45
|
+
const inExpected = i < expected.length;
|
|
46
|
+
if (inReceived && inExpected) {
|
|
47
|
+
const rv = formatValue(received[i]);
|
|
48
|
+
const ev = formatValue(expected[i]);
|
|
49
|
+
if (rv !== ev) {
|
|
50
|
+
lines.push(colors.red(` - [${i}]: ${rv}`));
|
|
51
|
+
lines.push(colors.green(` + [${i}]: ${ev}`));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
lines.push(` [${i}]: ${rv}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (inReceived) {
|
|
58
|
+
lines.push(colors.red(` - [${i}]: ${formatValue(received[i])}`));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
lines.push(colors.green(` + [${i}]: ${formatValue(expected[i])}`));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
40
66
|
function diffObjects(received, expected) {
|
|
41
67
|
const allKeys = new Set([...Object.keys(received), ...Object.keys(expected)]);
|
|
42
68
|
const lines = [];
|
|
@@ -64,6 +90,11 @@ function diffObjects(received, expected) {
|
|
|
64
90
|
return lines.join("\n");
|
|
65
91
|
}
|
|
66
92
|
export function formatDiff(received, expected) {
|
|
93
|
+
// For arrays, provide per-element diffing
|
|
94
|
+
if (Array.isArray(received) && Array.isArray(expected)) {
|
|
95
|
+
const diff = diffArrays(received, expected);
|
|
96
|
+
return (`\n${colors.red("- Received")} / ${colors.green("+ Expected")}\n\n` + diff);
|
|
97
|
+
}
|
|
67
98
|
// For objects, provide a structural diff
|
|
68
99
|
if (received !== null &&
|
|
69
100
|
expected !== null &&
|
|
@@ -3,6 +3,17 @@ export type TypedArray = Uint8Array | Int8Array | Uint16Array | Int16Array | Uin
|
|
|
3
3
|
export declare function isPrimitive(value: unknown): value is Primitive;
|
|
4
4
|
export declare function isObjectLike(value: unknown): value is object;
|
|
5
5
|
export declare function isTypedArray(value: unknown): value is TypedArray;
|
|
6
|
+
/**
|
|
7
|
+
* Returns true if the string looks like a glob pattern (contains * ? [ { }).
|
|
8
|
+
* Patterns without these characters are treated as regex strings for backward compatibility.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isGlobPattern(pattern: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Converts a glob pattern to a RegExp.
|
|
13
|
+
* Supports: ** (any path), * (any segment), ? (single char), {a,b} (alternation), [abc] (char class).
|
|
14
|
+
*/
|
|
15
|
+
export declare function globToRegex(glob: string): RegExp;
|
|
16
|
+
export declare function safeRegExp(source: string): RegExp;
|
|
6
17
|
/**
|
|
7
18
|
* SameValueZero-ish semantics:
|
|
8
19
|
* - NaN equals NaN
|
|
@@ -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;;;;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;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CASzD"}
|
|
@@ -14,6 +14,90 @@ export function isObjectLike(value) {
|
|
|
14
14
|
export function isTypedArray(value) {
|
|
15
15
|
return ArrayBuffer.isView(value) && !(value instanceof DataView);
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Returns true if the string looks like a glob pattern (contains * ? [ { }).
|
|
19
|
+
* Patterns without these characters are treated as regex strings for backward compatibility.
|
|
20
|
+
*/
|
|
21
|
+
export function isGlobPattern(pattern) {
|
|
22
|
+
return /[*?[\]{]/.test(pattern);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Converts a glob pattern to a RegExp.
|
|
26
|
+
* Supports: ** (any path), * (any segment), ? (single char), {a,b} (alternation), [abc] (char class).
|
|
27
|
+
*/
|
|
28
|
+
export function globToRegex(glob) {
|
|
29
|
+
let src = "";
|
|
30
|
+
let i = 0;
|
|
31
|
+
while (i < glob.length) {
|
|
32
|
+
const c = glob[i];
|
|
33
|
+
if (c === "*") {
|
|
34
|
+
if (glob[i + 1] === "*") {
|
|
35
|
+
// ** matches anything including path separators
|
|
36
|
+
src += ".*";
|
|
37
|
+
i += 2;
|
|
38
|
+
// consume optional trailing slash so "fixtures/**" works
|
|
39
|
+
if (glob[i] === "/")
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// * matches anything within a single path segment
|
|
44
|
+
src += "[^/]*";
|
|
45
|
+
i++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else if (c === "?") {
|
|
49
|
+
src += "[^/]";
|
|
50
|
+
i++;
|
|
51
|
+
}
|
|
52
|
+
else if (c === "[") {
|
|
53
|
+
// Pass character classes through as-is
|
|
54
|
+
const close = glob.indexOf("]", i);
|
|
55
|
+
if (close !== -1) {
|
|
56
|
+
src += glob.slice(i, close + 1);
|
|
57
|
+
i = close + 1;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
src += "\\[";
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (c === "{") {
|
|
65
|
+
// {a,b,c} → (a|b|c)
|
|
66
|
+
const close = glob.indexOf("}", i);
|
|
67
|
+
if (close !== -1) {
|
|
68
|
+
const parts = glob
|
|
69
|
+
.slice(i + 1, close)
|
|
70
|
+
.split(",")
|
|
71
|
+
.map((p) => p.replace(/[.+^${}()|[\]\\]/g, "\\$&"));
|
|
72
|
+
src += `(${parts.join("|")})`;
|
|
73
|
+
i = close + 1;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
src += "\\{";
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (/[.+^$|()\\]/.test(c)) {
|
|
81
|
+
// Escape regex special characters
|
|
82
|
+
src += "\\" + c;
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
src += c;
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return new RegExp(src);
|
|
91
|
+
}
|
|
92
|
+
export function safeRegExp(source) {
|
|
93
|
+
try {
|
|
94
|
+
return new RegExp(source);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
throw new Error(`Invalid regex "${source}": ${msg}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
17
101
|
/**
|
|
18
102
|
* SameValueZero-ish semantics:
|
|
19
103
|
* - NaN equals NaN
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadConfig.d.ts","sourceRoot":"","sources":["../../../src/utils/loadConfig.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loadConfig.d.ts","sourceRoot":"","sources":["../../../src/utils/loadConfig.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAU1D,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,mBAAmB,CAAC;IAE5B,UAAU,EAAE,MAAM,CAAC;IAEnB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAuCF,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,CAevB;AAqCD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAc9B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { toImportUrl } from "./transform.js";
|
|
2
|
+
import { toError } from "./toError.js";
|
|
2
3
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
3
4
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
5
|
/**
|
|
@@ -18,8 +19,7 @@ async function importConfigFile(configPath) {
|
|
|
18
19
|
return JSON.parse(raw);
|
|
19
20
|
}
|
|
20
21
|
catch (err) {
|
|
21
|
-
|
|
22
|
-
throw new Error(`Failed to parse JSON config at ${configPath}: ${msg}`);
|
|
22
|
+
throw new Error(`Failed to parse JSON config at ${configPath}: ${toError(err).message}`);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
const module = await import(toImportUrl(configPath, "bundle-config"));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toThrow.d.ts","sourceRoot":"","sources":["../../../src/utils/toThrow.ts"],"names":[],"mappings":"AAAA,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;AAgED,wBAAgB,OAAO,CACrB,QAAQ,EAAE,OAAO,EACjB,QAAQ,CAAC,EAAE,eAAe,EAC1B,KAAK,UAAQ,GACZ,WAAW,
|
|
1
|
+
{"version":3,"file":"toThrow.d.ts","sourceRoot":"","sources":["../../../src/utils/toThrow.ts"],"names":[],"mappings":"AAAA,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;AAgED,wBAAgB,OAAO,CACrB,QAAQ,EAAE,OAAO,EACjB,QAAQ,CAAC,EAAE,eAAe,EAC1B,KAAK,UAAQ,GACZ,WAAW,CAgEb"}
|
|
@@ -60,13 +60,24 @@ export function toThrow(received, expected, isNot = false) {
|
|
|
60
60
|
}
|
|
61
61
|
let threw = false;
|
|
62
62
|
let thrown;
|
|
63
|
+
let result;
|
|
63
64
|
try {
|
|
64
|
-
received();
|
|
65
|
+
result = received();
|
|
65
66
|
}
|
|
66
67
|
catch (e) {
|
|
67
68
|
threw = true;
|
|
68
69
|
thrown = e;
|
|
69
70
|
}
|
|
71
|
+
if (!threw &&
|
|
72
|
+
result != null &&
|
|
73
|
+
typeof result.then === "function") {
|
|
74
|
+
// Suppress the unhandled rejection from the returned promise
|
|
75
|
+
result.catch(() => { });
|
|
76
|
+
return {
|
|
77
|
+
pass: false,
|
|
78
|
+
message: () => "toThrow() received an async function. Use expect(fn).rejects.toThrow() for async errors.",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
70
81
|
const matched = threw && matchThrown(thrown, expected);
|
|
71
82
|
const pass = isNot ? !matched : matched;
|
|
72
83
|
return {
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unrift/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Core engine for the Unrift TypeScript test framework — runner, matchers, CLI, and programmatic API",
|
|
5
5
|
"private": false,
|
|
6
|
+
"sideEffects": false,
|
|
6
7
|
"main": "./dist/cjs/index.cjs",
|
|
7
8
|
"module": "./dist/esm/index.js",
|
|
8
9
|
"types": "./dist/esm/index.d.ts",
|
|
@@ -51,8 +52,11 @@
|
|
|
51
52
|
"bugs": {
|
|
52
53
|
"url": "https://github.com/elijahkotyluk/unrift/issues"
|
|
53
54
|
},
|
|
54
|
-
"homepage": "https://
|
|
55
|
+
"homepage": "https://unrift.pages.dev",
|
|
55
56
|
"license": "MIT",
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
59
|
+
},
|
|
56
60
|
"dependencies": {
|
|
57
61
|
"esbuild": "^0.27.2"
|
|
58
62
|
},
|