@unrift/core 0.2.1 → 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 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
- runTestsCLI({
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
- }).catch((err) => {
154
+ };
155
+ (watch ? watchTestsCLI(runOpts) : runTestsCLI(runOpts)).catch((err) => {
155
156
  console.error(err);
156
157
  process.exit(1);
157
158
  });
@@ -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: UnriftState;
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,KAAK,WAAW,GAAG,QAAQ,CAAC;AAE5B,UAAU,mBAAmB;IAC3B,YAAY,EAAE,KAAK,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,WAAW,CAAC;CACpB;AAED,eAAO,MAAM,mBAAmB,EAAE,mBAGjC,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,QAOlD"}
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"}
@@ -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: new (...args: unknown[]) => unknown): void | Promise<void>;
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>;
@@ -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,CACZ,QAAQ,EAAE,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAC5C,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,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;IAElD,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"}
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"}
@@ -4,5 +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";
7
8
  export type { RunEngineResult } from "./run";
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -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;AACjB,YAAY,EAAE,eAAe,EAAE,MAAM,OAAO,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";
@@ -9,9 +9,9 @@ type DescribeFn = {
9
9
  todo: (description: string) => void;
10
10
  };
11
11
  type ItFn = {
12
- (description: string, fn: PromisableFn<void>): void;
13
- only: (description: string, fn: PromisableFn<void>) => void;
14
- skip: (description: string, fn: PromisableFn<void>) => 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;IACpD,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IAC5D,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IAC5D,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAmBF,QAAA,MAAM,QAAQ,EAAE,UAC8B,CAAC;AAc/C,QAAA,MAAM,EAAE,EAAE,IAAkE,CAAC;AAK7E,QAAA,MAAM,IAAI,EAAE,IAAS,CAAC;AAEtB,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,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"}
@@ -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":"AAUA,wBAAgB,sBAAsB,SAMrC;AAED,wBAAgB,aAAa,SAE5B"}
1
+ {"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/matchers.ts"],"names":[],"mappings":"AA8DA,wBAAgB,sBAAsB,SAMrC;AAED,wBAAgB,aAAa,SAE5B"}
@@ -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
  }
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/runner.ts"],"names":[],"mappings":"AAgBA,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;AA2MD,wBAAsB,WAAW,CAAC,OAAO,GAAE,aAAkB,iBA2T5D"}
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"}
@@ -1,10 +1,12 @@
1
- import { relative, resolve } from "node:path";
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
- if (options.pattern) {
166
- files = files.filter((f) => options.pattern.test(normalizePath(f)));
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
- // Print suite name when it changes
300
- if (suitePath && suitePath !== lastSuitePath) {
301
- const depth = parts.length - 1;
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
+ }
@@ -6,7 +6,8 @@ export declare class Test {
6
6
  fn: PromisableFn<void>;
7
7
  mode: TaskMode;
8
8
  status: TaskStatus;
9
- constructor(description: string, fn: PromisableFn<void>, mode?: TaskMode);
10
- run(timeoutMs?: number): Promise<void>;
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
@@ -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;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"}
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
- constructor(description, fn, mode = TaskMode.Default) {
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(timeoutMs) {
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;
@@ -5,7 +5,6 @@ export declare enum TaskStatus {
5
5
  Running = "running",
6
6
  Pass = "pass",
7
7
  Fail = "fail",
8
- Only = "only",
9
8
  Skipped = "skipped",
10
9
  Todo = "todo"
11
10
  }
@@ -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,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,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
@@ -4,7 +4,6 @@ export var TaskStatus;
4
4
  TaskStatus["Running"] = "running";
5
5
  TaskStatus["Pass"] = "pass";
6
6
  TaskStatus["Fail"] = "fail";
7
- TaskStatus["Only"] = "only";
8
7
  TaskStatus["Skipped"] = "skipped";
9
8
  TaskStatus["Todo"] = "todo";
10
9
  })(TaskStatus || (TaskStatus = {}));
@@ -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,CA6RT"}
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,6 +1,5 @@
1
1
  export interface UnriftConfigOptions {
2
2
  testDir?: string;
3
- rootDir?: string;
4
3
  timeoutMs?: number;
5
4
  bail?: boolean;
6
5
  pattern?: string;
@@ -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,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,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":"AA2EA,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CA6BvE"}
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"}
@@ -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":"AAKA,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
+ {"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
- const msg = err instanceof Error ? err.message : String(err);
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"));
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@unrift/core",
3
- "version": "0.2.1",
4
- "description": "unrift core package",
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",
@@ -53,6 +54,9 @@
53
54
  },
54
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
  },