@unrift/core 0.3.0 → 0.5.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.
Files changed (64) hide show
  1. package/bin/unrift.mjs +2 -1
  2. package/dist/esm/cli.js +19 -1
  3. package/dist/esm/expect.d.ts +14 -0
  4. package/dist/esm/expect.d.ts.map +1 -1
  5. package/dist/esm/index.d.ts +3 -1
  6. package/dist/esm/index.d.ts.map +1 -1
  7. package/dist/esm/index.js +2 -0
  8. package/dist/esm/interface.d.ts +7 -3
  9. package/dist/esm/interface.d.ts.map +1 -1
  10. package/dist/esm/interface.js +9 -5
  11. package/dist/esm/matchers.d.ts.map +1 -1
  12. package/dist/esm/matchers.js +195 -1
  13. package/dist/esm/mock/auto.d.ts +27 -0
  14. package/dist/esm/mock/auto.d.ts.map +1 -0
  15. package/dist/esm/mock/auto.js +108 -0
  16. package/dist/esm/mock/fetch.d.ts +47 -0
  17. package/dist/esm/mock/fetch.d.ts.map +1 -0
  18. package/dist/esm/mock/fetch.js +149 -0
  19. package/dist/esm/mock/fs.d.ts +54 -0
  20. package/dist/esm/mock/fs.d.ts.map +1 -0
  21. package/dist/esm/mock/fs.js +125 -0
  22. package/dist/esm/mock/index.d.ts +70 -0
  23. package/dist/esm/mock/index.d.ts.map +1 -0
  24. package/dist/esm/mock/index.js +70 -0
  25. package/dist/esm/mock/module-loader.d.ts +66 -0
  26. package/dist/esm/mock/module-loader.d.ts.map +1 -0
  27. package/dist/esm/mock/module-loader.js +123 -0
  28. package/dist/esm/mock/module.d.ts +99 -0
  29. package/dist/esm/mock/module.d.ts.map +1 -0
  30. package/dist/esm/mock/module.js +198 -0
  31. package/dist/esm/mock/register-loader.d.ts +17 -0
  32. package/dist/esm/mock/register-loader.d.ts.map +1 -0
  33. package/dist/esm/mock/register-loader.js +45 -0
  34. package/dist/esm/mock/spy.d.ts +73 -0
  35. package/dist/esm/mock/spy.d.ts.map +1 -0
  36. package/dist/esm/mock/spy.js +307 -0
  37. package/dist/esm/mock/stub.d.ts +26 -0
  38. package/dist/esm/mock/stub.d.ts.map +1 -0
  39. package/dist/esm/mock/stub.js +82 -0
  40. package/dist/esm/mock/timers.d.ts +51 -0
  41. package/dist/esm/mock/timers.d.ts.map +1 -0
  42. package/dist/esm/mock/timers.js +581 -0
  43. package/dist/esm/run.d.ts +4 -7
  44. package/dist/esm/run.d.ts.map +1 -1
  45. package/dist/esm/run.js +6 -2
  46. package/dist/esm/runner.d.ts +1 -0
  47. package/dist/esm/runner.d.ts.map +1 -1
  48. package/dist/esm/runner.js +21 -10
  49. package/dist/esm/suite.d.ts +24 -6
  50. package/dist/esm/suite.d.ts.map +1 -1
  51. package/dist/esm/suite.js +114 -20
  52. package/dist/esm/test.d.ts +5 -1
  53. package/dist/esm/test.d.ts.map +1 -1
  54. package/dist/esm/test.js +20 -6
  55. package/dist/esm/utils/compareEngine.js +3 -3
  56. package/dist/esm/utils/defineConfig.d.ts +1 -0
  57. package/dist/esm/utils/defineConfig.d.ts.map +1 -1
  58. package/dist/esm/utils/discoverTestFiles.js +1 -1
  59. package/dist/esm/utils/helpers.d.ts +12 -0
  60. package/dist/esm/utils/helpers.d.ts.map +1 -1
  61. package/dist/esm/utils/helpers.js +20 -0
  62. package/dist/esm/utils/toThrow.d.ts.map +1 -1
  63. package/dist/esm/utils/toThrow.js +2 -1
  64. package/package.json +4 -3
package/bin/unrift.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- // Run the real CLI
4
+ // Run the real CLI. The CLI installs the module-mock loader itself
5
+ // (see src/mock/register-loader.ts), so this bin stays a thin entry point.
5
6
  await import("../dist/esm/cli.js");
package/dist/esm/cli.js CHANGED
@@ -2,8 +2,13 @@
2
2
  import { readFileSync } from "node:fs";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { registerModuleLoader } from "./mock/register-loader.js";
5
6
  import { runTestsCLI, watchTestsCLI } from "./runner.js";
6
7
  import { safeRegExp } from "./utils/helpers.js";
8
+ // Install the module-mock loader before any test file is imported. Both the
9
+ // core bin and the unrift wrapper bin run this CLI, so this is the single
10
+ // place module mocking is bootstrapped.
11
+ registerModuleLoader();
7
12
  const __cliDir = dirname(fileURLToPath(import.meta.url));
8
13
  function readVersion() {
9
14
  // Walk up from current file to find package.json (works from both src/ and dist/esm/)
@@ -31,6 +36,7 @@ Usage:
31
36
 
32
37
  Options:
33
38
  -c, --config <path> Path to config file
39
+ -g, --grep <pattern> Only run tests whose full name matches (regex)
34
40
  -b, --bail Stop on first test failure
35
41
  -t, --timeout <ms> Default test timeout in milliseconds
36
42
  -w, --watch Re-run tests on file changes
@@ -44,6 +50,7 @@ Options:
44
50
  Examples:
45
51
  unrift
46
52
  unrift math
53
+ unrift --grep "adds numbers"
47
54
  unrift -c test/config.ts --bail
48
55
  unrift --timeout 10000
49
56
  unrift --watch
@@ -52,6 +59,7 @@ Examples:
52
59
  function parseArgs(argv) {
53
60
  let configPath;
54
61
  let pattern;
62
+ let grep;
55
63
  let timeoutMs;
56
64
  let bail;
57
65
  let debug = false;
@@ -80,6 +88,14 @@ function parseArgs(argv) {
80
88
  i++;
81
89
  continue;
82
90
  }
91
+ if (a === "--grep" || a === "-g") {
92
+ const next = argv[i + 1];
93
+ if (!next)
94
+ throw new Error(`${a} requires a pattern`);
95
+ grep = safeRegExp(next);
96
+ i++;
97
+ continue;
98
+ }
83
99
  if (a === "--bail" || a === "-b") {
84
100
  bail = true;
85
101
  continue;
@@ -122,6 +138,7 @@ function parseArgs(argv) {
122
138
  return {
123
139
  configPath,
124
140
  pattern,
141
+ grep,
125
142
  timeoutMs,
126
143
  bail,
127
144
  debug,
@@ -133,7 +150,7 @@ function parseArgs(argv) {
133
150
  showVersion,
134
151
  };
135
152
  }
136
- const { configPath, pattern, timeoutMs, bail, debug, list, json, cacheClean, watch, help, showVersion, } = parseArgs(process.argv.slice(2));
153
+ const { configPath, pattern, grep, timeoutMs, bail, debug, list, json, cacheClean, watch, help, showVersion, } = parseArgs(process.argv.slice(2));
137
154
  if (showVersion) {
138
155
  console.log(version);
139
156
  process.exit(0);
@@ -144,6 +161,7 @@ if (help) {
144
161
  }
145
162
  const runOpts = {
146
163
  pattern,
164
+ grep,
147
165
  configPath,
148
166
  timeoutMs,
149
167
  bail,
@@ -17,6 +17,7 @@ export interface Matchers<T> {
17
17
  prototype: unknown;
18
18
  }): void | Promise<void>;
19
19
  toContain(expected: unknown): void | Promise<void>;
20
+ toContainEqual(expected: unknown): void | Promise<void>;
20
21
  toEqual(expected: unknown): void | Promise<void>;
21
22
  toHaveLength(expected: number): void | Promise<void>;
22
23
  toMatch(expected: RegExp | string): void | Promise<void>;
@@ -24,10 +25,23 @@ export interface Matchers<T> {
24
25
  toThrow(expected?: unknown): void | Promise<void>;
25
26
  toBeGreaterThanOrEqual(expected: number): void | Promise<void>;
26
27
  toBeLessThanOrEqual(expected: number): void | Promise<void>;
28
+ toBeCloseTo(expected: number, precision?: number): void | Promise<void>;
27
29
  toBeNaN(): void | Promise<void>;
28
30
  toBeFinite(): void | Promise<void>;
29
31
  toMatchObject(expected: Record<string, unknown>): void | Promise<void>;
30
32
  toHaveProperty(path: string | readonly string[], value?: unknown): void | Promise<void>;
33
+ toHaveBeenCalled(): void | Promise<void>;
34
+ toHaveBeenCalledTimes(expected: number): void | Promise<void>;
35
+ toHaveBeenCalledWith(...args: unknown[]): void | Promise<void>;
36
+ toHaveBeenLastCalledWith(...args: unknown[]): void | Promise<void>;
37
+ toHaveBeenNthCalledWith(n: number, ...args: unknown[]): void | Promise<void>;
38
+ toHaveReturned(): void | Promise<void>;
39
+ toHaveReturnedTimes(expected: number): void | Promise<void>;
40
+ toHaveReturnedWith(expected: unknown): void | Promise<void>;
41
+ toHaveLastReturnedWith(expected: unknown): void | Promise<void>;
42
+ toHaveNthReturnedWith(n: number, expected: unknown): void | Promise<void>;
43
+ toHaveFetched(urlOrMatcher: string | RegExp | ((req: Request) => boolean)): void | Promise<void>;
44
+ toHaveFetchedTimes(expected: number): void | Promise<void>;
31
45
  readonly not: Matchers<T>;
32
46
  readonly resolves: Matchers<Awaited<T>>;
33
47
  readonly rejects: Matchers<unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/expect.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/expect.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,11 @@
1
1
  export { beforeEach, afterEach, beforeAll, afterAll } from "./hooks";
2
2
  export { defineConfig, type UnriftConfigOptions } from "./utils/defineConfig";
3
- export { describe, it, test } from "./interface";
3
+ export { describe, it, test, type TestOptions } from "./interface";
4
4
  export { expect, extendMatchers } from "./expect";
5
5
  export type { Matchers, ExpectInterface } from "./expect";
6
6
  export type { MatcherContext, MatcherFn, MatcherMap, Promisable, } from "./types";
7
7
  export { runEngine } from "./run";
8
8
  export type { RunEngineResult } from "./run";
9
+ export { mock, spy, spyOn, isSpy, mockObject, mockClass, stub, mockGlobal, mockFetch, mockFetchOnce, useFakeTimers, useRealTimers, advanceTimersByTime, advanceTimersByTimeAsync, runAllTimers, runAllTimersAsync, runOnlyPendingTimers, runOnlyPendingTimersAsync, getTimerCount, setSystemTime, getRealSystemTime, doMock, unmock, listMockedSpecs, fromModule, mockFs, clearAllMocks, resetAllMocks, restoreAllMocks, } from "./mock/index";
10
+ export type { Spy, MockState, MockResult, MockFetch, FetchMatcher, MockResponseInit, MockFetchResponse, UseFakeTimersOptions, FakeableApi, LoaderMessage, FakeFsHandle, FakeFsState, } from "./mock/index";
9
11
  //# 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,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,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,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAElD,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC1D,YAAY,EACV,cAAc,EACd,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,YAAY,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAG7C,OAAO,EACL,IAAI,EACJ,GAAG,EACH,KAAK,EACL,KAAK,EACL,UAAU,EACV,SAAS,EACT,IAAI,EACJ,UAAU,EACV,SAAS,EACT,aAAa,EACb,aAAa,EACb,aAAa,EACb,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACpB,yBAAyB,EACzB,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,MAAM,EACN,MAAM,EACN,eAAe,EACf,UAAU,EACV,MAAM,EACN,aAAa,EACb,aAAa,EACb,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,GAAG,EACH,SAAS,EACT,UAAU,EACV,SAAS,EACT,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,WAAW,GACZ,MAAM,cAAc,CAAC"}
package/dist/esm/index.js CHANGED
@@ -3,3 +3,5 @@ export { defineConfig } from "./utils/defineConfig.js";
3
3
  export { describe, it, test } from "./interface.js";
4
4
  export { expect, extendMatchers } from "./expect.js";
5
5
  export { runEngine } from "./run.js";
6
+ // Mocking suite
7
+ export { mock, spy, spyOn, isSpy, mockObject, mockClass, stub, mockGlobal, mockFetch, mockFetchOnce, useFakeTimers, useRealTimers, advanceTimersByTime, advanceTimersByTimeAsync, runAllTimers, runAllTimersAsync, runOnlyPendingTimers, runOnlyPendingTimersAsync, getTimerCount, setSystemTime, getRealSystemTime, doMock, unmock, listMockedSpecs, fromModule, mockFs, clearAllMocks, resetAllMocks, restoreAllMocks, } from "./mock/index.js";
@@ -8,10 +8,14 @@ type DescribeFn = {
8
8
  skip: (description: string, fn: () => void) => void;
9
9
  todo: (description: string) => void;
10
10
  };
11
+ export interface TestOptions {
12
+ timeout?: number;
13
+ retries?: number;
14
+ }
11
15
  type ItFn = {
12
- (description: string, fn: PromisableFn<void>, timeoutMs?: number): void;
13
- only: (description: string, fn: PromisableFn<void>, timeoutMs?: number) => void;
14
- skip: (description: string, fn: PromisableFn<void>, timeoutMs?: number) => void;
16
+ (description: string, fn: PromisableFn<void>, options?: number | TestOptions): void;
17
+ only: (description: string, fn: PromisableFn<void>, options?: number | TestOptions) => void;
18
+ skip: (description: string, fn: PromisableFn<void>, options?: number | TestOptions) => void;
15
19
  todo: (description: string) => void;
16
20
  };
17
21
  declare const describe: DescribeFn;
@@ -1 +1 @@
1
- {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/interface.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,YAAY,EAAY,MAAM,SAAS,CAAC;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"}
1
+ {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/interface.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,YAAY,EAAY,MAAM,SAAS,CAAC;AAGtD;;GAEG;AAEH,KAAK,UAAU,GAAG;IAChB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC5C,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACpD,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACpD,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,IAAI,GAAG;IACV,CACE,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EACtB,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,GAC7B,IAAI,CAAC;IACR,IAAI,EAAE,CACJ,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EACtB,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,KAC3B,IAAI,CAAC;IACV,IAAI,EAAE,CACJ,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EACtB,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,KAC3B,IAAI,CAAC;IACV,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AA6BF,QAAA,MAAM,QAAQ,EAAE,UAC8B,CAAC;AAyB/C,QAAA,MAAM,EAAE,EAAE,IACuC,CAAC;AAOlD,QAAA,MAAM,IAAI,EAAE,IAAS,CAAC;AAEtB,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC"}
@@ -2,6 +2,7 @@ import { assertRegisterState, unriftGlobalContext } from "./context.js";
2
2
  import { getCurrentSuite, Suite } from "./suite.js";
3
3
  import { Test } from "./test.js";
4
4
  import { TaskMode } from "./types.js";
5
+ import { assertValidRetries } from "./utils/helpers.js";
5
6
  function _describe(description, fn, mode) {
6
7
  assertRegisterState("describe");
7
8
  const parent = getCurrentSuite();
@@ -24,14 +25,17 @@ const describe = (description, fn) => _describe(description, fn, TaskMode.Defaul
24
25
  describe.only = (description, fn) => _describe(description, fn, TaskMode.Only);
25
26
  describe.skip = (description, fn) => _describe(description, fn, TaskMode.Skip);
26
27
  describe.todo = (description) => _describe(description, () => { }, TaskMode.Todo);
27
- function _it(description, fn, mode, timeoutMs) {
28
+ function _it(description, fn, mode, options) {
28
29
  assertRegisterState("it");
29
- const test = new Test(description, fn, mode, timeoutMs);
30
+ // A bare number is the timeout, kept for backwards compatibility.
31
+ const { timeout, retries } = typeof options === "number" ? { timeout: options } : (options ?? {});
32
+ assertValidRetries(retries, `it("${description}")`);
33
+ const test = new Test(description, fn, mode, timeout, retries);
30
34
  getCurrentSuite().addTest(test);
31
35
  }
32
- const it = (description, fn, 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);
36
+ const it = (description, fn, options) => _it(description, fn, TaskMode.Default, options);
37
+ it.only = (description, fn, options) => _it(description, fn, TaskMode.Only, options);
38
+ it.skip = (description, fn, options) => _it(description, fn, TaskMode.Skip, options);
35
39
  it.todo = (description) => _it(description, () => { }, TaskMode.Todo);
36
40
  const test = it;
37
41
  export { describe, it, test };
@@ -1 +1 @@
1
- {"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/matchers.ts"],"names":[],"mappings":"AA8DA,wBAAgB,sBAAsB,SAMrC;AAED,wBAAgB,aAAa,SAE5B"}
1
+ {"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/matchers.ts"],"names":[],"mappings":"AAyGA,wBAAgB,sBAAsB,SAMrC;AAED,wBAAgB,aAAa,SAE5B"}
@@ -1,8 +1,41 @@
1
1
  import { expect } from "./expect.js";
2
+ import { isSpy } from "./mock/spy.js";
3
+ import { isFetchMock } from "./mock/fetch.js";
2
4
  import { deepEqual } from "./utils/deepEqual.js";
3
5
  import { looseEqual } from "./utils/looseEqual.js";
6
+ import { regexTest } from "./utils/helpers.js";
4
7
  import { toThrow } from "./utils/toThrow.js";
5
8
  let matchersRegistered = false;
9
+ function ensureSpy(received, matcherName) {
10
+ if (!isSpy(received)) {
11
+ throw new Error(`${matcherName}() requires a spy or mock function (created via spy() or spyOn()), ` +
12
+ `but received ${typeof received === "object" ? Object.prototype.toString.call(received) : typeof received}`);
13
+ }
14
+ }
15
+ function ensureFetchMock(received, matcherName) {
16
+ if (!isFetchMock(received)) {
17
+ throw new Error(`${matcherName}() requires \`mock.fetch\` as the received value, ` +
18
+ `but received ${typeof received === "object" ? Object.prototype.toString.call(received) : typeof received}`);
19
+ }
20
+ }
21
+ function matchesFetchTarget(req, target) {
22
+ if (typeof target === "string")
23
+ return req.url === target;
24
+ if (target instanceof RegExp)
25
+ return regexTest(target, req.url);
26
+ if (typeof target === "function")
27
+ return Boolean(target(req));
28
+ return false;
29
+ }
30
+ function argsEqual(a, b) {
31
+ if (a.length !== b.length)
32
+ return false;
33
+ for (let i = 0; i < a.length; i++) {
34
+ if (!looseEqual(a[i], b[i]))
35
+ return false;
36
+ }
37
+ return true;
38
+ }
6
39
  function partialMatch(received, expected) {
7
40
  if (expected === null || typeof expected !== "object") {
8
41
  return looseEqual(received, expected);
@@ -132,6 +165,22 @@ function registerCoreMatchers() {
132
165
  throw new Error(this.diff(received, expected));
133
166
  }
134
167
  },
168
+ toContainEqual(received, expected) {
169
+ if (!Array.isArray(received)) {
170
+ throw new Error(this.diff(received, "expected an array for toContainEqual()"));
171
+ }
172
+ // for...of visits sparse holes as undefined; Array.some would skip them.
173
+ let pass = false;
174
+ for (const item of received) {
175
+ if (looseEqual(item, expected)) {
176
+ pass = true;
177
+ break;
178
+ }
179
+ }
180
+ if (this.isNot ? pass : !pass) {
181
+ throw new Error(this.diff(received, expected));
182
+ }
183
+ },
135
184
  toEqual(received, expected) {
136
185
  const pass = looseEqual(received, expected);
137
186
  if (this.isNot ? pass : !pass) {
@@ -163,7 +212,7 @@ function registerCoreMatchers() {
163
212
  }
164
213
  const pass = typeof expected === "string"
165
214
  ? received.includes(expected)
166
- : expected.test(received);
215
+ : regexTest(expected, received);
167
216
  if (this.isNot ? pass : !pass)
168
217
  throw new Error(this.diff(received, expected));
169
218
  },
@@ -196,6 +245,24 @@ function registerCoreMatchers() {
196
245
  throw new Error(this.diff(received, `<= ${expected}`));
197
246
  }
198
247
  },
248
+ toBeCloseTo(received, expected, precision = 2) {
249
+ if (typeof received !== "number") {
250
+ throw new Error(this.diff(received, "expected a number"));
251
+ }
252
+ if (typeof expected !== "number") {
253
+ throw new Error(this.diff(expected, "a number for expected"));
254
+ }
255
+ if (!Number.isFinite(precision)) {
256
+ throw new Error(this.diff(precision, "a finite number for precision"));
257
+ }
258
+ // Same-signed infinities are equal; the subtraction below would be NaN.
259
+ // Finite values always go through the tolerance check.
260
+ const pass = (!Number.isFinite(received) && received === expected) ||
261
+ Math.abs(received - expected) < 10 ** -precision / 2;
262
+ if (this.isNot ? pass : !pass) {
263
+ throw new Error(this.diff(received, `${expected} (precision ${precision})`));
264
+ }
265
+ },
199
266
  toBeNaN(received) {
200
267
  const pass = Number.isNaN(received);
201
268
  if (this.isNot ? pass : !pass) {
@@ -219,6 +286,133 @@ function registerCoreMatchers() {
219
286
  throw new Error(this.diff(received, expected));
220
287
  }
221
288
  },
289
+ toHaveBeenCalled(received) {
290
+ ensureSpy(received, "toHaveBeenCalled");
291
+ const pass = received.mock.calls.length > 0;
292
+ if (this.isNot ? pass : !pass) {
293
+ throw new Error(this.diff(`${received.getMockName()} called ${received.mock.calls.length} time(s)`, "to have been called"));
294
+ }
295
+ },
296
+ toHaveBeenCalledTimes(received, expected) {
297
+ ensureSpy(received, "toHaveBeenCalledTimes");
298
+ if (typeof expected !== "number" || !Number.isFinite(expected)) {
299
+ throw new Error(this.diff(expected, "toHaveBeenCalledTimes() requires a finite number"));
300
+ }
301
+ const actual = received.mock.calls.length;
302
+ const pass = actual === expected;
303
+ if (this.isNot ? pass : !pass) {
304
+ throw new Error(this.diff(actual, expected));
305
+ }
306
+ },
307
+ toHaveBeenCalledWith(received, ...expectedArgs) {
308
+ ensureSpy(received, "toHaveBeenCalledWith");
309
+ const pass = received.mock.calls.some((call) => argsEqual(call, expectedArgs));
310
+ if (this.isNot ? pass : !pass) {
311
+ throw new Error(this.diff(received.mock.calls, expectedArgs));
312
+ }
313
+ },
314
+ toHaveBeenLastCalledWith(received, ...expectedArgs) {
315
+ ensureSpy(received, "toHaveBeenLastCalledWith");
316
+ const last = received.mock.lastCall;
317
+ const pass = last !== undefined && argsEqual(last, expectedArgs);
318
+ if (this.isNot ? pass : !pass) {
319
+ throw new Error(this.diff(last ?? "no calls", expectedArgs));
320
+ }
321
+ },
322
+ toHaveBeenNthCalledWith(received, n, ...expectedArgs) {
323
+ ensureSpy(received, "toHaveBeenNthCalledWith");
324
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
325
+ throw new Error(this.diff(n, "toHaveBeenNthCalledWith() requires a 1-indexed integer"));
326
+ }
327
+ const call = received.mock.calls[n - 1];
328
+ const pass = call !== undefined && argsEqual(call, expectedArgs);
329
+ if (this.isNot ? pass : !pass) {
330
+ throw new Error(this.diff(call ?? `call #${n} not found`, expectedArgs));
331
+ }
332
+ },
333
+ toHaveReturned(received) {
334
+ ensureSpy(received, "toHaveReturned");
335
+ const pass = received.mock.results.some((r) => r.type === "return");
336
+ if (this.isNot ? pass : !pass) {
337
+ throw new Error(this.diff(`${received.mock.results.length} result(s)`, "at least one successful return"));
338
+ }
339
+ },
340
+ toHaveReturnedTimes(received, expected) {
341
+ ensureSpy(received, "toHaveReturnedTimes");
342
+ if (typeof expected !== "number" || !Number.isFinite(expected)) {
343
+ throw new Error(this.diff(expected, "toHaveReturnedTimes() requires a finite number"));
344
+ }
345
+ const actual = received.mock.results.filter((r) => r.type === "return").length;
346
+ const pass = actual === expected;
347
+ if (this.isNot ? pass : !pass) {
348
+ throw new Error(this.diff(actual, expected));
349
+ }
350
+ },
351
+ toHaveReturnedWith(received, expected) {
352
+ ensureSpy(received, "toHaveReturnedWith");
353
+ const pass = received.mock.results.some((r) => r.type === "return" && looseEqual(r.value, expected));
354
+ if (this.isNot ? pass : !pass) {
355
+ const returnValues = received.mock.results
356
+ .filter((r) => r.type === "return")
357
+ .map((r) => r.value);
358
+ throw new Error(this.diff(returnValues, expected));
359
+ }
360
+ },
361
+ toHaveLastReturnedWith(received, expected) {
362
+ ensureSpy(received, "toHaveLastReturnedWith");
363
+ const results = received.mock.results;
364
+ const last = results[results.length - 1];
365
+ const pass = last !== undefined &&
366
+ last.type === "return" &&
367
+ looseEqual(last.value, expected);
368
+ if (this.isNot ? pass : !pass) {
369
+ if (last === undefined) {
370
+ throw new Error(this.diff("no calls", expected));
371
+ }
372
+ if (last.type === "throw") {
373
+ throw new Error(this.diff("last call threw", expected));
374
+ }
375
+ throw new Error(this.diff(last.value, expected));
376
+ }
377
+ },
378
+ toHaveNthReturnedWith(received, n, expected) {
379
+ ensureSpy(received, "toHaveNthReturnedWith");
380
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
381
+ throw new Error(this.diff(n, "toHaveNthReturnedWith() requires a 1-indexed integer"));
382
+ }
383
+ const result = received.mock.results[n - 1];
384
+ const pass = result !== undefined &&
385
+ result.type === "return" &&
386
+ looseEqual(result.value, expected);
387
+ if (this.isNot ? pass : !pass) {
388
+ if (result === undefined) {
389
+ throw new Error(this.diff(`call #${n} not found`, expected));
390
+ }
391
+ if (result.type === "throw") {
392
+ throw new Error(this.diff(`call #${n} threw`, expected));
393
+ }
394
+ throw new Error(this.diff(result.value, expected));
395
+ }
396
+ },
397
+ toHaveFetched(received, target) {
398
+ ensureFetchMock(received, "toHaveFetched");
399
+ const pass = received.calls.some((req) => matchesFetchTarget(req, target));
400
+ if (this.isNot ? pass : !pass) {
401
+ const urls = received.calls.map((r) => `${r.method} ${r.url}`);
402
+ throw new Error(this.diff(urls, target));
403
+ }
404
+ },
405
+ toHaveFetchedTimes(received, expected) {
406
+ ensureFetchMock(received, "toHaveFetchedTimes");
407
+ if (typeof expected !== "number" || !Number.isFinite(expected)) {
408
+ throw new Error(this.diff(expected, "toHaveFetchedTimes() requires a finite number"));
409
+ }
410
+ const actual = received.calls.length;
411
+ const pass = actual === expected;
412
+ if (this.isNot ? pass : !pass) {
413
+ throw new Error(this.diff(actual, expected));
414
+ }
415
+ },
222
416
  toHaveProperty(received, path, ...rest) {
223
417
  if (received === null || typeof received !== "object") {
224
418
  throw new Error(this.diff(received, "expected an object for toHaveProperty()"));
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Auto-mocking helpers: `mock.object` and `mock.class`.
3
+ *
4
+ * Both walk own + prototype methods and replace each function with a spy.
5
+ * Restoration uses the same registry as `spyOn`, so `mock.restoreAll()`
6
+ * cleans up auto mocks too.
7
+ */
8
+ /**
9
+ * Replace every method on `obj` (own + inherited, excluding Object.prototype)
10
+ * with a spy. Returns the same reference, mutated in place.
11
+ *
12
+ * Cleanup happens via `mock.restoreAll()` - auto-mock spies share the same
13
+ * restorer registry as `spyOn`.
14
+ */
15
+ export declare function mockObject<T extends object>(obj: T): T;
16
+ /**
17
+ * Wrap a constructor so every instance is auto-mocked, and so static
18
+ * methods on the constructor itself are spied too. Returns a Proxy that
19
+ * is `instanceof`-compatible with the original.
20
+ *
21
+ * Instance methods are spied as OWN properties on each instance (shadowing
22
+ * the prototype), NOT by mutating the shared prototype. This keeps call
23
+ * state per-instance (a.run and b.run are separate spies) and leaves the
24
+ * class prototype untouched - nothing to restore, nothing to leak.
25
+ */
26
+ export declare function mockClass<T extends new (...args: any[]) => any>(Ctor: T): T;
27
+ //# sourceMappingURL=auto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto.d.ts","sourceRoot":"","sources":["../../../src/mock/auto.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA+CH;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAatD;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAEvB,CAAC,SAAS,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EACrC,IAAI,EAAE,CAAC,GAAG,CAAC,CA8BZ"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Auto-mocking helpers: `mock.object` and `mock.class`.
3
+ *
4
+ * Both walk own + prototype methods and replace each function with a spy.
5
+ * Restoration uses the same registry as `spyOn`, so `mock.restoreAll()`
6
+ * cleans up auto mocks too.
7
+ */
8
+ import { spy, spyOn } from "./spy.js";
9
+ /**
10
+ * Walks the chain from `target` up to (but not including) the built in
11
+ * `Object.prototype` and `Function.prototype`, yielding every own method
12
+ * key encountered. Spying on built in prototypes (`apply`, `call`, etc.)
13
+ * would recurse into the spy machinery itself.
14
+ */
15
+ function* enumerableMethodKeys(target) {
16
+ const seen = new Set();
17
+ let current = target;
18
+ while (current &&
19
+ current !== Object.prototype &&
20
+ current !== Function.prototype) {
21
+ for (const key of Reflect.ownKeys(current)) {
22
+ if (seen.has(key))
23
+ continue;
24
+ // Skip the `constructor` property, replacing it would break
25
+ // `instance.constructor` checks and is rarely what callers want.
26
+ if (key === "constructor")
27
+ continue;
28
+ // Built in `Function` properties on a constructor we don't want to
29
+ // wrap (length / name are non-writable; prototype is structural).
30
+ if (key === "length" || key === "name" || key === "prototype")
31
+ continue;
32
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
33
+ if (!descriptor)
34
+ continue;
35
+ // Skip getters/setters: spyOn would invoke the getter to read the
36
+ // current value, which can have side effects. Track separately.
37
+ if (descriptor.get || descriptor.set)
38
+ continue;
39
+ if (typeof descriptor.value !== "function")
40
+ continue;
41
+ if (descriptor.configurable === false)
42
+ continue;
43
+ seen.add(key);
44
+ yield { host: current, key };
45
+ }
46
+ current = Object.getPrototypeOf(current);
47
+ }
48
+ }
49
+ /**
50
+ * Replace every method on `obj` (own + inherited, excluding Object.prototype)
51
+ * with a spy. Returns the same reference, mutated in place.
52
+ *
53
+ * Cleanup happens via `mock.restoreAll()` - auto-mock spies share the same
54
+ * restorer registry as `spyOn`.
55
+ */
56
+ export function mockObject(obj) {
57
+ for (const { host, key } of enumerableMethodKeys(obj)) {
58
+ try {
59
+ spyOn(host, key);
60
+ }
61
+ catch {
62
+ /** Non-configurable properties (e.g. some built ins), skip silently.
63
+ * Test code targeting these would fail clearly when the spy methods
64
+ * aren't found. We don't want auto mock to throw on the first such key.
65
+ */
66
+ }
67
+ }
68
+ return obj;
69
+ }
70
+ /**
71
+ * Wrap a constructor so every instance is auto-mocked, and so static
72
+ * methods on the constructor itself are spied too. Returns a Proxy that
73
+ * is `instanceof`-compatible with the original.
74
+ *
75
+ * Instance methods are spied as OWN properties on each instance (shadowing
76
+ * the prototype), NOT by mutating the shared prototype. This keeps call
77
+ * state per-instance (a.run and b.run are separate spies) and leaves the
78
+ * class prototype untouched - nothing to restore, nothing to leak.
79
+ */
80
+ export function mockClass(Ctor) {
81
+ // Spy static methods on the constructor itself (mutates Ctor in place;
82
+ // statics are own properties of Ctor, so spyOn restores them normally).
83
+ mockObject(Ctor);
84
+ return new Proxy(Ctor, {
85
+ construct(target, args, newTarget) {
86
+ const instance = Reflect.construct(target, args, newTarget);
87
+ for (const { host, key } of enumerableMethodKeys(instance)) {
88
+ if (host === instance) {
89
+ // Own method (assigned in the constructor): spy in place.
90
+ spyOn(instance, key);
91
+ }
92
+ else {
93
+ // Inherited method: shadow it with an own-property spy that passes
94
+ // through to the prototype implementation. The prototype itself is
95
+ // never modified.
96
+ const protoMethod = host[key];
97
+ Object.defineProperty(instance, key, {
98
+ value: spy(protoMethod),
99
+ writable: true,
100
+ configurable: true,
101
+ enumerable: false,
102
+ });
103
+ }
104
+ }
105
+ return instance;
106
+ },
107
+ });
108
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * `mock.fetch` - stub `globalThis.fetch` for HTTP testing.
3
+ *
4
+ * Handlers are matched against requests in registration order; the first
5
+ * matching handler wins. `mock.fetchOnce()` registers a single-use handler
6
+ * that's consumed after its first match.
7
+ *
8
+ * Restoration:
9
+ * - `mock.fetch.restore()` un-patches global fetch and clears handlers
10
+ * - `mock.restoreAll()` does the same as part of bulk cleanup
11
+ */
12
+ /** Brand applied to `mock.fetch` so matchers can verify their received value. */
13
+ export declare const FETCH_MOCK_BRAND: unique symbol;
14
+ /**
15
+ * Derive WebFetch helper types from the runtime constructors so we don't
16
+ * have to widen the project's `lib` config to include `dom` types. These
17
+ * match what the platform actually accepts.
18
+ */
19
+ type HeadersInitLike = ConstructorParameters<typeof Headers>[0];
20
+ type BodyInitLike = ConstructorParameters<typeof Response>[0];
21
+ /** What can be passed as the first argument to `mock.fetch(matcher, ...)`. */
22
+ export type FetchMatcher = string | RegExp | ((req: Request) => boolean | Promise<boolean>);
23
+ export interface MockResponseInit {
24
+ status?: number;
25
+ statusText?: string;
26
+ headers?: HeadersInitLike;
27
+ body?: BodyInitLike | null;
28
+ json?: unknown;
29
+ }
30
+ export type MockFetchResponse = Response | MockResponseInit | ((req: Request) => Response | MockResponseInit | Promise<Response | MockResponseInit>);
31
+ /**
32
+ * Public surface for `mock.fetch`. Callable to register a handler, with
33
+ * attached helpers for inspection and cleanup.
34
+ */
35
+ export interface MockFetch {
36
+ (matcher: FetchMatcher, response: MockFetchResponse): void;
37
+ once(matcher: FetchMatcher, response: MockFetchResponse): void;
38
+ readonly calls: Request[];
39
+ reset(): void;
40
+ restore(): void;
41
+ readonly [FETCH_MOCK_BRAND]: true;
42
+ }
43
+ export declare function isFetchMock(value: unknown): value is MockFetch;
44
+ export declare const mockFetch: MockFetch;
45
+ export declare function mockFetchOnce(matcher: FetchMatcher, response: MockFetchResponse): void;
46
+ export {};
47
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../src/mock/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,EAAE,OAAO,MAAuC,CAAC;AAE9E;;;;GAIG;AACH,KAAK,eAAe,GAAG,qBAAqB,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AAChE,KAAK,YAAY,GAAG,qBAAqB,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAG9D,8EAA8E;AAC9E,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,MAAM,GACN,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;AAGnD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,IAAI,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAGD,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,gBAAgB,GAChB,CAAC,CACC,GAAG,EAAE,OAAO,KACT,QAAQ,GAAG,gBAAgB,GAAG,OAAO,CAAC,QAAQ,GAAG,gBAAgB,CAAC,CAAC,CAAC;AAuI7E;;;GAGG;AACH,MAAM,WAAW,SAAS;IAExB,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAE3D,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAE/D,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;IAE1B,KAAK,IAAI,IAAI,CAAC;IAEd,OAAO,IAAI,IAAI,CAAC;IAEhB,QAAQ,CAAC,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAC;CACnC;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAK9D;AA2BD,eAAO,MAAM,SAAS,EAAE,SAAyB,CAAC;AAElD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,iBAAiB,GAC1B,IAAI,CAEN"}