@unrift/core 0.0.0 → 0.2.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 +75 -5
- package/dist/esm/expect.d.ts +8 -0
- package/dist/esm/expect.d.ts.map +1 -1
- package/dist/esm/expect.js +48 -4
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/interface.d.ts +4 -1
- package/dist/esm/interface.d.ts.map +1 -1
- package/dist/esm/interface.js +4 -1
- package/dist/esm/matchers.d.ts +1 -0
- package/dist/esm/matchers.d.ts.map +1 -1
- package/dist/esm/matchers.js +61 -0
- package/dist/esm/run.d.ts.map +1 -1
- package/dist/esm/run.js +3 -4
- package/dist/esm/runner.d.ts.map +1 -1
- package/dist/esm/runner.js +201 -33
- package/dist/esm/suite.d.ts +2 -10
- package/dist/esm/suite.d.ts.map +1 -1
- package/dist/esm/suite.js +29 -10
- package/dist/esm/test.d.ts +1 -7
- package/dist/esm/test.d.ts.map +1 -1
- package/dist/esm/test.js +11 -2
- package/dist/esm/types.d.ts +3 -3
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils/colors.d.ts +21 -0
- package/dist/esm/utils/colors.d.ts.map +1 -1
- package/dist/esm/utils/colors.js +34 -4
- package/dist/esm/utils/compareEngine.d.ts +10 -0
- package/dist/esm/utils/compareEngine.d.ts.map +1 -0
- package/dist/esm/utils/compareEngine.js +222 -0
- package/dist/esm/utils/deepEqual.d.ts.map +1 -1
- package/dist/esm/utils/deepEqual.js +7 -185
- package/dist/esm/utils/diff.d.ts +2 -0
- package/dist/esm/utils/diff.d.ts.map +1 -0
- package/dist/esm/utils/diff.js +84 -0
- package/dist/esm/utils/discoverTestFiles.d.ts.map +1 -1
- package/dist/esm/utils/discoverTestFiles.js +17 -2
- package/dist/esm/utils/looseEqual.d.ts +9 -0
- package/dist/esm/utils/looseEqual.d.ts.map +1 -1
- package/dist/esm/utils/looseEqual.js +16 -197
- package/dist/esm/utils/normalizePath.d.ts +2 -0
- package/dist/esm/utils/normalizePath.d.ts.map +1 -0
- package/dist/esm/utils/normalizePath.js +4 -0
- package/dist/esm/utils/toError.d.ts +2 -0
- package/dist/esm/utils/toError.d.ts.map +1 -0
- package/dist/esm/utils/toError.js +3 -0
- package/dist/esm/utils/transform.d.ts.map +1 -1
- package/dist/esm/utils/transform.js +31 -21
- package/package.json +21 -3
package/dist/esm/runner.js
CHANGED
|
@@ -4,11 +4,8 @@ import { cleanUnriftCaches } from "./utils/transform.js";
|
|
|
4
4
|
import { colors } from "./utils/colors.js";
|
|
5
5
|
import { discoverTestFiles } from "./utils/discoverTestFiles.js";
|
|
6
6
|
import { runEngine } from "./run.js";
|
|
7
|
+
import { normalizePath } from "./utils/normalizePath.js";
|
|
7
8
|
import { TaskStatus } from "./types.js";
|
|
8
|
-
function normalizePath(p) {
|
|
9
|
-
// stable for matching/reporting across platforms
|
|
10
|
-
return p.replace(/\\/g, "/");
|
|
11
|
-
}
|
|
12
9
|
function debugLog(enabled, json, ...args) {
|
|
13
10
|
// Keep stdout clean for --json mode; use stderr for debug.
|
|
14
11
|
if (!enabled)
|
|
@@ -24,6 +21,15 @@ function safeRegExp(source) {
|
|
|
24
21
|
throw new Error(`Invalid regex "${source}": ${msg}`);
|
|
25
22
|
}
|
|
26
23
|
}
|
|
24
|
+
const regExpCache = new Map();
|
|
25
|
+
function getCachedRegExp(pattern) {
|
|
26
|
+
let re = regExpCache.get(pattern);
|
|
27
|
+
if (!re) {
|
|
28
|
+
re = safeRegExp(pattern);
|
|
29
|
+
regExpCache.set(pattern, re);
|
|
30
|
+
}
|
|
31
|
+
return re;
|
|
32
|
+
}
|
|
27
33
|
/**
|
|
28
34
|
* @TODO Add glob support
|
|
29
35
|
*/
|
|
@@ -31,7 +37,7 @@ function matchesAnyPattern(fileAbs, patterns, baseDir) {
|
|
|
31
37
|
const absolutePath = normalizePath(fileAbs);
|
|
32
38
|
const relativePath = normalizePath(relative(baseDir, fileAbs));
|
|
33
39
|
return patterns.some((pattern) => {
|
|
34
|
-
const re =
|
|
40
|
+
const re = getCachedRegExp(pattern);
|
|
35
41
|
return re.test(relativePath) || re.test(absolutePath);
|
|
36
42
|
});
|
|
37
43
|
}
|
|
@@ -50,15 +56,76 @@ function formatDuration(ms) {
|
|
|
50
56
|
return `${ms.toFixed(0)}ms`;
|
|
51
57
|
return `${(ms / 1000).toFixed(2)}s`;
|
|
52
58
|
}
|
|
53
|
-
function padRight(str, width) {
|
|
54
|
-
return str.length >= width ? str : str + " ".repeat(width - str.length);
|
|
55
|
-
}
|
|
56
59
|
function getFileHeadingFromDescription(description) {
|
|
57
60
|
const index = description.indexOf("›");
|
|
58
61
|
if (index === -1)
|
|
59
62
|
return null;
|
|
60
63
|
return description.slice(0, index).trim();
|
|
61
64
|
}
|
|
65
|
+
function shortenPath(absPath) {
|
|
66
|
+
const cwd = normalizePath(process.cwd());
|
|
67
|
+
const norm = normalizePath(absPath);
|
|
68
|
+
if (norm.startsWith(cwd + "/"))
|
|
69
|
+
return norm.slice(cwd.length + 1);
|
|
70
|
+
return norm;
|
|
71
|
+
}
|
|
72
|
+
function horizontalRule(label) {
|
|
73
|
+
const width = Math.min(process.stdout.columns || 80, 80);
|
|
74
|
+
if (!label)
|
|
75
|
+
return colors.dim("─".repeat(width));
|
|
76
|
+
const padding = 2;
|
|
77
|
+
const labelLen = label.length + padding * 2;
|
|
78
|
+
const sideLen = Math.max(1, Math.floor((width - labelLen) / 2));
|
|
79
|
+
const left = "─".repeat(sideLen);
|
|
80
|
+
const right = "─".repeat(Math.max(1, width - sideLen - labelLen));
|
|
81
|
+
return (colors.dim(`${left}${"─".repeat(padding)}`) +
|
|
82
|
+
label +
|
|
83
|
+
colors.dim(`${"─".repeat(padding)}${right}`));
|
|
84
|
+
}
|
|
85
|
+
function groupResultsByFile(results) {
|
|
86
|
+
const groups = [];
|
|
87
|
+
let current = null;
|
|
88
|
+
for (const result of results) {
|
|
89
|
+
const heading = getFileHeadingFromDescription(result.description);
|
|
90
|
+
const key = heading ?? "";
|
|
91
|
+
if (!current || current.heading !== key) {
|
|
92
|
+
current = { heading: key, results: [] };
|
|
93
|
+
groups.push(current);
|
|
94
|
+
}
|
|
95
|
+
current.results.push(result);
|
|
96
|
+
}
|
|
97
|
+
return groups;
|
|
98
|
+
}
|
|
99
|
+
function getFileStats(group) {
|
|
100
|
+
let pass = 0;
|
|
101
|
+
let fail = 0;
|
|
102
|
+
let skip = 0;
|
|
103
|
+
let todoCount = 0;
|
|
104
|
+
let totalDuration = 0;
|
|
105
|
+
for (const r of group.results) {
|
|
106
|
+
if (r.status === TaskStatus.Pass)
|
|
107
|
+
pass++;
|
|
108
|
+
else if (r.status === TaskStatus.Fail)
|
|
109
|
+
fail++;
|
|
110
|
+
else if (r.status === TaskStatus.Skipped)
|
|
111
|
+
skip++;
|
|
112
|
+
else if (r.status === TaskStatus.Todo)
|
|
113
|
+
todoCount++;
|
|
114
|
+
totalDuration += r.durationMs;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
pass,
|
|
118
|
+
fail,
|
|
119
|
+
skip,
|
|
120
|
+
todo: todoCount,
|
|
121
|
+
total: group.results.length,
|
|
122
|
+
totalDuration,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function extractTestName(description) {
|
|
126
|
+
// Remove the file heading and first suite separator, keep nested describe structure
|
|
127
|
+
return description.replace(/^.*?›\s*/, "");
|
|
128
|
+
}
|
|
62
129
|
export async function runTestsCLI(options = {}) {
|
|
63
130
|
const runStart = performance.now();
|
|
64
131
|
// Cache clean mode
|
|
@@ -113,6 +180,33 @@ export async function runTestsCLI(options = {}) {
|
|
|
113
180
|
}
|
|
114
181
|
return;
|
|
115
182
|
}
|
|
183
|
+
// No test files found
|
|
184
|
+
if (files.length === 0) {
|
|
185
|
+
const dir = normalizePath(testDir);
|
|
186
|
+
if (options.json) {
|
|
187
|
+
const report = {
|
|
188
|
+
ok: false,
|
|
189
|
+
failed: 0,
|
|
190
|
+
passed: 0,
|
|
191
|
+
skipped: 0,
|
|
192
|
+
todo: 0,
|
|
193
|
+
total: 0,
|
|
194
|
+
bail: options.bail ?? config?.bail ?? false,
|
|
195
|
+
timeoutMs: options.timeoutMs ?? config?.timeoutMs,
|
|
196
|
+
testDir: dir,
|
|
197
|
+
configPath: resolvedConfigPath,
|
|
198
|
+
files: [],
|
|
199
|
+
durationMs: performance.now() - runStart,
|
|
200
|
+
results: [],
|
|
201
|
+
};
|
|
202
|
+
console.log(JSON.stringify(report, null, 2));
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
console.log(`\n ${colors.boldYellow("!")} No test files found in ${colors.bold(dir)}\n`);
|
|
206
|
+
}
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
116
210
|
const timeoutMs = options.timeoutMs ?? config?.timeoutMs;
|
|
117
211
|
const bail = options.bail ?? config?.bail ?? false;
|
|
118
212
|
const engine = await runEngine({
|
|
@@ -127,6 +221,7 @@ export async function runTestsCLI(options = {}) {
|
|
|
127
221
|
let passed = 0;
|
|
128
222
|
let failed = 0;
|
|
129
223
|
let skipped = 0;
|
|
224
|
+
let todo = 0;
|
|
130
225
|
const failures = [];
|
|
131
226
|
const serializedResults = [];
|
|
132
227
|
for (const result of results) {
|
|
@@ -137,6 +232,8 @@ export async function runTestsCLI(options = {}) {
|
|
|
137
232
|
skipped++;
|
|
138
233
|
else if (result.status === TaskStatus.Fail)
|
|
139
234
|
failed++;
|
|
235
|
+
else if (result.status === TaskStatus.Todo)
|
|
236
|
+
todo++;
|
|
140
237
|
if (result.status === TaskStatus.Fail)
|
|
141
238
|
failures.push(result);
|
|
142
239
|
serializedResults.push({
|
|
@@ -157,6 +254,7 @@ export async function runTestsCLI(options = {}) {
|
|
|
157
254
|
failed,
|
|
158
255
|
passed,
|
|
159
256
|
skipped,
|
|
257
|
+
todo,
|
|
160
258
|
total,
|
|
161
259
|
bail,
|
|
162
260
|
timeoutMs,
|
|
@@ -171,37 +269,107 @@ export async function runTestsCLI(options = {}) {
|
|
|
171
269
|
process.exitCode = 1;
|
|
172
270
|
return;
|
|
173
271
|
}
|
|
174
|
-
//
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
for (const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
272
|
+
// ── Pretty reporting ──────────────────────────────────────────────
|
|
273
|
+
const groups = groupResultsByFile(results);
|
|
274
|
+
// File-by-file results
|
|
275
|
+
for (const group of groups) {
|
|
276
|
+
const stats = getFileStats(group);
|
|
277
|
+
const shortPath = shortenPath(group.heading);
|
|
278
|
+
// File heading with inline summary
|
|
279
|
+
const fileIcon = stats.fail > 0 ? colors.boldRed("✘") : colors.boldGreen("✓");
|
|
280
|
+
const countParts = [];
|
|
281
|
+
if (stats.pass > 0)
|
|
282
|
+
countParts.push(colors.green(`${stats.pass} passed`));
|
|
283
|
+
if (stats.fail > 0)
|
|
284
|
+
countParts.push(colors.red(`${stats.fail} failed`));
|
|
285
|
+
if (stats.skip > 0)
|
|
286
|
+
countParts.push(colors.yellow(`${stats.skip} skipped`));
|
|
287
|
+
if (stats.todo > 0)
|
|
288
|
+
countParts.push(colors.magenta(`${stats.todo} todo`));
|
|
289
|
+
const fileDuration = colors.dim(formatDuration(stats.totalDuration));
|
|
290
|
+
const countSummary = colors.dim("(") + countParts.join(colors.dim(", ")) + colors.dim(")");
|
|
291
|
+
console.log(`\n ${fileIcon} ${colors.bold(shortPath)} ${countSummary} ${fileDuration}`);
|
|
292
|
+
// Individual test results, indented
|
|
293
|
+
let lastSuitePath = "";
|
|
294
|
+
for (const result of group.results) {
|
|
295
|
+
const testName = extractTestName(result.description);
|
|
296
|
+
const parts = testName.split(" › ");
|
|
297
|
+
const leaf = parts[parts.length - 1];
|
|
298
|
+
const suitePath = parts.slice(0, -1).join(" › ");
|
|
299
|
+
// Print suite name when it changes
|
|
300
|
+
if (suitePath && suitePath !== lastSuitePath) {
|
|
301
|
+
const depth = parts.length - 1;
|
|
302
|
+
const suiteIndent = " " + " ".repeat(Math.max(0, depth - 1));
|
|
303
|
+
console.log(`${suiteIndent}${colors.dim("›")} ${colors.bold(parts[depth - 1])}`);
|
|
304
|
+
lastSuitePath = suitePath;
|
|
305
|
+
}
|
|
306
|
+
const depth = parts.length - 1;
|
|
307
|
+
const indent = " " + " ".repeat(depth);
|
|
308
|
+
if (result.status === TaskStatus.Pass) {
|
|
309
|
+
const dur = colors.dim(formatDuration(result.durationMs ?? 0));
|
|
310
|
+
console.log(`${indent}${colors.green("✔")} ${colors.dim(leaf)} ${dur}`);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (result.status === TaskStatus.Fail) {
|
|
314
|
+
const dur = colors.dim(formatDuration(result.durationMs ?? 0));
|
|
315
|
+
console.log(`${indent}${colors.red("✘")} ${colors.boldRed(leaf)} ${dur}`);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (result.status === TaskStatus.Skipped) {
|
|
319
|
+
console.log(`${indent}${colors.yellow("↷")} ${colors.dim(leaf)}`);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (result.status === TaskStatus.Todo) {
|
|
323
|
+
console.log(`${indent}${colors.magenta("○")} ${colors.dim(leaf)}`);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
192
326
|
}
|
|
193
|
-
// Fail
|
|
194
|
-
console.log(colors.red("✘"), durationPadded, result.description.replace(/^.*?›\s*/, ""));
|
|
195
327
|
}
|
|
328
|
+
// ── Failure details ────────────────────────────────────────────────
|
|
196
329
|
if (failures.length > 0) {
|
|
197
|
-
console.log(colors.
|
|
330
|
+
console.log(`\n${horizontalRule(colors.boldRed(" FAILURES "))}\n`);
|
|
198
331
|
failures.forEach((r, i) => {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
332
|
+
const testName = extractTestName(r.description);
|
|
333
|
+
console.log(colors.boldRed(` ${i + 1}) ${testName}`));
|
|
334
|
+
if (r.error) {
|
|
335
|
+
const errText = r.error.stack ?? r.error.message;
|
|
336
|
+
const lines = errText.split("\n");
|
|
337
|
+
for (const line of lines) {
|
|
338
|
+
// Highlight the "Received/Expected" lines differently
|
|
339
|
+
if (line.trimStart().startsWith("Received:")) {
|
|
340
|
+
console.log(colors.red(` ${line.trim()}`));
|
|
341
|
+
}
|
|
342
|
+
else if (line.trimStart().startsWith("Expected:")) {
|
|
343
|
+
console.log(colors.green(` ${line.trim()}`));
|
|
344
|
+
}
|
|
345
|
+
else if (line.trim().startsWith("at ")) {
|
|
346
|
+
console.log(colors.dim(` ${line.trim()}`));
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
console.log(` ${colors.red(line.trim())}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (i < failures.length - 1)
|
|
354
|
+
console.log("");
|
|
202
355
|
});
|
|
356
|
+
console.log(`\n${horizontalRule()}`);
|
|
203
357
|
}
|
|
204
|
-
|
|
358
|
+
// ── Summary ────────────────────────────────────────────────────────
|
|
359
|
+
console.log("");
|
|
360
|
+
const summaryParts = [];
|
|
361
|
+
if (passed > 0)
|
|
362
|
+
summaryParts.push(colors.boldGreen(`${passed} passed`));
|
|
363
|
+
if (failed > 0)
|
|
364
|
+
summaryParts.push(colors.boldRed(`${failed} failed`));
|
|
365
|
+
if (skipped > 0)
|
|
366
|
+
summaryParts.push(colors.boldYellow(`${skipped} skipped`));
|
|
367
|
+
if (todo > 0)
|
|
368
|
+
summaryParts.push(colors.magenta(`${todo} todo`));
|
|
369
|
+
const badge = ok ? colors.badgePass(" PASS ") : colors.badgeFail(" FAIL ");
|
|
370
|
+
console.log(` ${badge} ${summaryParts.join(colors.dim(" · "))} ${colors.dim("of")} ${colors.bold(String(total))} ${colors.dim("tests")} ${colors.dim("in")} ${colors.bold(formatDuration(durationMs))}`);
|
|
371
|
+
console.log(` ${colors.dim(`${files.length} test file${files.length === 1 ? "" : "s"}`)}${bail ? colors.dim(" · bail mode") : ""}`);
|
|
372
|
+
console.log("");
|
|
205
373
|
if (!ok)
|
|
206
374
|
process.exitCode = 1;
|
|
207
375
|
}
|
package/dist/esm/suite.d.ts
CHANGED
|
@@ -7,16 +7,7 @@ interface RunOptions {
|
|
|
7
7
|
interface SuiteRunState {
|
|
8
8
|
bailed: boolean;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
description: string;
|
|
12
|
-
parent?: Suite;
|
|
13
|
-
suites: Suite[];
|
|
14
|
-
tests: Test[];
|
|
15
|
-
addSuite(suite: Suite): void;
|
|
16
|
-
addTest(test: Test): void;
|
|
17
|
-
run(): Promise<void>;
|
|
18
|
-
}
|
|
19
|
-
export declare class Suite implements SuiteTask {
|
|
10
|
+
export declare class Suite {
|
|
20
11
|
description: string;
|
|
21
12
|
mode: TaskMode;
|
|
22
13
|
parent?: Suite;
|
|
@@ -36,6 +27,7 @@ export declare class Suite implements SuiteTask {
|
|
|
36
27
|
afterEachHooks: PromisableFn<void>[];
|
|
37
28
|
constructor(description: string, parent?: Suite, mode?: TaskMode);
|
|
38
29
|
markSubtreeSkipped(): void;
|
|
30
|
+
markSubtreeTodo(): void;
|
|
39
31
|
addSuite(suite: Suite): void;
|
|
40
32
|
addTest(test: Test): void;
|
|
41
33
|
private collectBeforeEachHooks;
|
package/dist/esm/suite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suite.d.ts","sourceRoot":"","sources":["../../src/suite.ts"],"names":[],"mappings":"
|
|
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;AAGlE,UAAU,UAAU;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB;AAsBD,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;IASlB,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;IA4JhB,kBAAkB,IAAI,MAAM;IAK5B,KAAK;IAaL,UAAU,IAAI,KAAK,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,UAAU,CAAC;QACnB,KAAK,CAAC,EAAE,KAAK,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CAkDH;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,5 +1,7 @@
|
|
|
1
1
|
import { unriftGlobalContext } from "./context.js";
|
|
2
|
+
import { resetMatchers } from "./matchers.js";
|
|
2
3
|
import { TaskMode, TaskStatus } from "./types.js";
|
|
4
|
+
import { toError } from "./utils/toError.js";
|
|
3
5
|
function appendHookFailure(existing, hookName, hookError) {
|
|
4
6
|
if (!existing)
|
|
5
7
|
return hookError;
|
|
@@ -40,6 +42,15 @@ export class Suite {
|
|
|
40
42
|
for (const suite of this.suites)
|
|
41
43
|
suite.markSubtreeSkipped();
|
|
42
44
|
}
|
|
45
|
+
markSubtreeTodo() {
|
|
46
|
+
for (const test of this.tests) {
|
|
47
|
+
if (test.status === TaskStatus.Pending)
|
|
48
|
+
test.status = TaskStatus.Todo;
|
|
49
|
+
test.durationMs = 0;
|
|
50
|
+
}
|
|
51
|
+
for (const suite of this.suites)
|
|
52
|
+
suite.markSubtreeTodo();
|
|
53
|
+
}
|
|
43
54
|
addSuite(suite) {
|
|
44
55
|
this.suites.push(suite);
|
|
45
56
|
}
|
|
@@ -78,6 +89,10 @@ export class Suite {
|
|
|
78
89
|
this.markSubtreeSkipped();
|
|
79
90
|
return;
|
|
80
91
|
}
|
|
92
|
+
if (this.mode === TaskMode.Todo) {
|
|
93
|
+
this.markSubtreeTodo();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
81
96
|
const suiteHasOnly = this.subtreeHasOnly;
|
|
82
97
|
const inOnlyContext = ancestorOnly || this.mode === TaskMode.Only;
|
|
83
98
|
// If only is on and this suite has no runnable-only content, skip it entirely
|
|
@@ -94,8 +109,7 @@ export class Suite {
|
|
|
94
109
|
}
|
|
95
110
|
}
|
|
96
111
|
catch (error) {
|
|
97
|
-
this.beforeAllError =
|
|
98
|
-
error instanceof Error ? error : new Error(String(error));
|
|
112
|
+
this.beforeAllError = toError(error);
|
|
99
113
|
// Prevent "pending" tests from showing up if beforeAll fails
|
|
100
114
|
this.markSubtreeSkipped();
|
|
101
115
|
return;
|
|
@@ -113,6 +127,11 @@ export class Suite {
|
|
|
113
127
|
test.durationMs = 0;
|
|
114
128
|
continue;
|
|
115
129
|
}
|
|
130
|
+
if (test.mode === TaskMode.Todo) {
|
|
131
|
+
test.status = TaskStatus.Todo;
|
|
132
|
+
test.durationMs = 0;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
116
135
|
const testIsOnly = inOnlyContext || test.mode === TaskMode.Only;
|
|
117
136
|
// If onlyMode, skip tests that are not in only context
|
|
118
137
|
if (isOnly && !testIsOnly) {
|
|
@@ -126,16 +145,14 @@ export class Suite {
|
|
|
126
145
|
await hook();
|
|
127
146
|
}
|
|
128
147
|
catch (error) {
|
|
129
|
-
|
|
130
|
-
throw appendHookFailure(undefined, "beforeEach", hookError);
|
|
148
|
+
throw appendHookFailure(undefined, "beforeEach", toError(error));
|
|
131
149
|
}
|
|
132
150
|
}
|
|
133
151
|
await test.run(options?.timeoutMs);
|
|
134
152
|
}
|
|
135
153
|
catch (error) {
|
|
136
154
|
test.status = TaskStatus.Fail;
|
|
137
|
-
test.error =
|
|
138
|
-
error instanceof Error ? error : new Error(String(error));
|
|
155
|
+
test.error = toError(error);
|
|
139
156
|
}
|
|
140
157
|
finally {
|
|
141
158
|
for (const hook of afterEachHooks) {
|
|
@@ -143,9 +160,8 @@ export class Suite {
|
|
|
143
160
|
await hook();
|
|
144
161
|
}
|
|
145
162
|
catch (error) {
|
|
146
|
-
const hookError = error instanceof Error ? error : new Error(String(error));
|
|
147
163
|
test.status = TaskStatus.Fail;
|
|
148
|
-
test.error = appendHookFailure(test.error, "afterEach",
|
|
164
|
+
test.error = appendHookFailure(test.error, "afterEach", toError(error));
|
|
149
165
|
}
|
|
150
166
|
}
|
|
151
167
|
}
|
|
@@ -189,8 +205,7 @@ export class Suite {
|
|
|
189
205
|
catch (error) {
|
|
190
206
|
// Record once; keep running remaining afterAll hooks
|
|
191
207
|
if (!this.afterAllError) {
|
|
192
|
-
this.afterAllError =
|
|
193
|
-
error instanceof Error ? error : new Error(String(error));
|
|
208
|
+
this.afterAllError = toError(error);
|
|
194
209
|
}
|
|
195
210
|
}
|
|
196
211
|
}
|
|
@@ -269,6 +284,9 @@ export function computeOnlyFlags(suite) {
|
|
|
269
284
|
suite.subtreeHasOnly = subtreeHasOnly;
|
|
270
285
|
return subtreeHasOnly;
|
|
271
286
|
}
|
|
287
|
+
// KNOWN LIMITATION: rootSuite is a global singleton. This means parallel test
|
|
288
|
+
// execution in the same process is not supported. A future DI/context-container
|
|
289
|
+
// pattern would be needed to support worker-based parallelism.
|
|
272
290
|
export const rootSuite = new Suite("root");
|
|
273
291
|
rootSuite.root = true;
|
|
274
292
|
export function getCurrentSuite() {
|
|
@@ -277,5 +295,6 @@ export function getCurrentSuite() {
|
|
|
277
295
|
export function clearContext() {
|
|
278
296
|
rootSuite.reset();
|
|
279
297
|
rootSuite.subtreeHasOnly = false;
|
|
298
|
+
resetMatchers();
|
|
280
299
|
unriftGlobalContext.currentSuite = rootSuite;
|
|
281
300
|
}
|
package/dist/esm/test.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { TaskMode, TaskStatus, type PromisableFn } from "./types";
|
|
2
|
-
|
|
3
|
-
description: string;
|
|
4
|
-
fn: PromisableFn<void>;
|
|
5
|
-
run(): Promise<void>;
|
|
6
|
-
}
|
|
7
|
-
export declare class Test implements TestTask {
|
|
2
|
+
export declare class Test {
|
|
8
3
|
description: string;
|
|
9
4
|
durationMs: number;
|
|
10
5
|
error?: Error;
|
|
@@ -14,5 +9,4 @@ export declare class Test implements TestTask {
|
|
|
14
9
|
constructor(description: string, fn: PromisableFn<void>, mode?: TaskMode);
|
|
15
10
|
run(timeoutMs?: number): Promise<void>;
|
|
16
11
|
}
|
|
17
|
-
export {};
|
|
18
12
|
//# 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;
|
|
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;gBAGtC,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EACtB,IAAI,GAAE,QAA2B;IAO7B,GAAG,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAsD7C"}
|
package/dist/esm/test.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TaskMode, TaskStatus } from "./types.js";
|
|
2
|
+
import { toError } from "./utils/toError.js";
|
|
2
3
|
export class Test {
|
|
3
4
|
description;
|
|
4
5
|
durationMs = 0;
|
|
@@ -17,6 +18,11 @@ export class Test {
|
|
|
17
18
|
this.durationMs = 0;
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
21
|
+
if (this.mode === TaskMode.Todo) {
|
|
22
|
+
this.status = TaskStatus.Todo;
|
|
23
|
+
this.durationMs = 0;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
20
26
|
const start = performance.now();
|
|
21
27
|
this.status = TaskStatus.Running;
|
|
22
28
|
const exec = Promise.resolve().then(() => this.fn());
|
|
@@ -25,9 +31,12 @@ export class Test {
|
|
|
25
31
|
let timeoutId = null;
|
|
26
32
|
const timeoutFn = new Promise((_, reject) => {
|
|
27
33
|
timeoutId = setTimeout(() => {
|
|
28
|
-
reject(new Error(`Test timed out after ${timeoutMs} ms`));
|
|
34
|
+
reject(new Error(`Test "${this.description}" timed out after ${timeoutMs} ms`));
|
|
29
35
|
}, timeoutMs);
|
|
30
36
|
});
|
|
37
|
+
// NOTE: Promise.race does not cancel the losing promise. A timed-out test
|
|
38
|
+
// function continues running in the background. True cancellation would
|
|
39
|
+
// require AbortController integration, which is a larger future change.
|
|
31
40
|
try {
|
|
32
41
|
await Promise.race([exec, timeoutFn]);
|
|
33
42
|
}
|
|
@@ -42,7 +51,7 @@ export class Test {
|
|
|
42
51
|
this.status = TaskStatus.Pass;
|
|
43
52
|
}
|
|
44
53
|
catch (error) {
|
|
45
|
-
this.error =
|
|
54
|
+
this.error = toError(error);
|
|
46
55
|
this.status = TaskStatus.Fail;
|
|
47
56
|
}
|
|
48
57
|
finally {
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type Promisable<T> = Promise<T> | T;
|
|
1
|
+
export type Promisable<T> = Promise<T> | T;
|
|
2
2
|
export type PromisableFn<T> = () => Promisable<T>;
|
|
3
3
|
export declare enum TaskStatus {
|
|
4
4
|
Pending = "pending",
|
|
@@ -12,7 +12,8 @@ export declare enum TaskStatus {
|
|
|
12
12
|
export declare enum TaskMode {
|
|
13
13
|
Default = "default",
|
|
14
14
|
Only = "only",
|
|
15
|
-
Skip = "skip"
|
|
15
|
+
Skip = "skip",
|
|
16
|
+
Todo = "todo"
|
|
16
17
|
}
|
|
17
18
|
export interface MatcherContext {
|
|
18
19
|
isNot: boolean;
|
|
@@ -20,5 +21,4 @@ export interface MatcherContext {
|
|
|
20
21
|
}
|
|
21
22
|
export type MatcherFn = (this: MatcherContext, received: unknown, ...args: readonly unknown[]) => void | Promise<void>;
|
|
22
23
|
export type MatcherMap = Record<string, MatcherFn>;
|
|
23
|
-
export {};
|
|
24
24
|
//# sourceMappingURL=types.d.ts.map
|
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,
|
|
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,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
|
@@ -2,6 +2,27 @@ export declare const colors: {
|
|
|
2
2
|
green: (text: string) => string;
|
|
3
3
|
red: (text: string) => string;
|
|
4
4
|
yellow: (text: string) => string;
|
|
5
|
+
cyan: (text: string) => string;
|
|
6
|
+
magenta: (text: string) => string;
|
|
7
|
+
white: (text: string) => string;
|
|
5
8
|
bold: (text: string) => string;
|
|
9
|
+
dim: (text: string) => string;
|
|
10
|
+
italic: (text: string) => string;
|
|
11
|
+
underline: (text: string) => string;
|
|
12
|
+
strikethrough: (text: string) => string;
|
|
13
|
+
gray: (text: string) => string;
|
|
14
|
+
bgRed: (text: string) => string;
|
|
15
|
+
bgGreen: (text: string) => string;
|
|
16
|
+
bgYellow: (text: string) => string;
|
|
17
|
+
bgCyan: (text: string) => string;
|
|
18
|
+
boldGreen: (text: string) => string;
|
|
19
|
+
boldRed: (text: string) => string;
|
|
20
|
+
boldYellow: (text: string) => string;
|
|
21
|
+
boldCyan: (text: string) => string;
|
|
22
|
+
boldDim: (text: string) => string;
|
|
23
|
+
badgePass: (text: string) => string;
|
|
24
|
+
badgeFail: (text: string) => string;
|
|
25
|
+
badgeSkip: (text: string) => string;
|
|
26
|
+
badgeTodo: (text: string) => string;
|
|
6
27
|
};
|
|
7
28
|
//# sourceMappingURL=colors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../../src/utils/colors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../../src/utils/colors.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,MAAM;kBARH,MAAM;gBAAN,MAAM;mBAAN,MAAM;iBAAN,MAAM;oBAAN,MAAM;kBAAN,MAAM;iBAAN,MAAM;gBAAN,MAAM;mBAAN,MAAM;sBAAN,MAAM;0BAAN,MAAM;iBAAN,MAAM;kBAAN,MAAM;oBAAN,MAAM;qBAAN,MAAM;mBAAN,MAAM;sBAIN,MAAM;oBAAN,MAAM;uBAAN,MAAM;qBAAN,MAAM;oBAAN,MAAM;sBAAN,MAAM;sBAAN,MAAM;sBAAN,MAAM;sBAAN,MAAM;CAyCrB,CAAC"}
|
package/dist/esm/utils/colors.js
CHANGED
|
@@ -4,9 +4,39 @@ const enabled = process.stdout.isTTY &&
|
|
|
4
4
|
function wrap(code) {
|
|
5
5
|
return (text) => (enabled ? `\x1b[${code}m${text}\x1b[0m` : text);
|
|
6
6
|
}
|
|
7
|
+
function compose(...codes) {
|
|
8
|
+
return (text) => enabled ? `\x1b[${codes.join(";")}m${text}\x1b[0m` : text;
|
|
9
|
+
}
|
|
7
10
|
export const colors = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
// Basic colors
|
|
12
|
+
green: wrap("32"),
|
|
13
|
+
red: wrap("31"),
|
|
14
|
+
yellow: wrap("33"),
|
|
15
|
+
cyan: wrap("36"),
|
|
16
|
+
magenta: wrap("35"),
|
|
17
|
+
white: wrap("37"),
|
|
18
|
+
// Modifiers
|
|
19
|
+
bold: wrap("1"),
|
|
20
|
+
dim: wrap("2"),
|
|
21
|
+
italic: wrap("3"),
|
|
22
|
+
underline: wrap("4"),
|
|
23
|
+
strikethrough: wrap("9"),
|
|
24
|
+
// Bright colors
|
|
25
|
+
gray: wrap("90"),
|
|
26
|
+
// Backgrounds
|
|
27
|
+
bgRed: wrap("41"),
|
|
28
|
+
bgGreen: wrap("42"),
|
|
29
|
+
bgYellow: wrap("43"),
|
|
30
|
+
bgCyan: wrap("46"),
|
|
31
|
+
// Composites
|
|
32
|
+
boldGreen: compose("1", "32"),
|
|
33
|
+
boldRed: compose("1", "31"),
|
|
34
|
+
boldYellow: compose("1", "33"),
|
|
35
|
+
boldCyan: compose("1", "36"),
|
|
36
|
+
boldDim: compose("1", "2"),
|
|
37
|
+
// Badge-style (white on colored background)
|
|
38
|
+
badgePass: compose("1", "30", "42"),
|
|
39
|
+
badgeFail: compose("1", "37", "41"),
|
|
40
|
+
badgeSkip: compose("1", "30", "43"),
|
|
41
|
+
badgeTodo: compose("1", "35"),
|
|
12
42
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface CompareOptions {
|
|
2
|
+
/** true for deepEqual (prototype-sensitive), false for looseEqual */
|
|
3
|
+
checkPrototype: boolean;
|
|
4
|
+
/** true for looseEqual (sparse holes → undefined), false for deepEqual */
|
|
5
|
+
treatSparseAsUndefined: boolean;
|
|
6
|
+
/** true for looseEqual (bidirectional cycle tracking) */
|
|
7
|
+
bijectiveCycleCheck: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function compareInternal(left: unknown, right: unknown, options: CompareOptions, leftToRightSeen: WeakMap<object, object>, rightToLeftSeen?: WeakMap<object, object>): boolean;
|
|
10
|
+
//# sourceMappingURL=compareEngine.d.ts.map
|
|
@@ -0,0 +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,CA6RT"}
|