@unrift/core 0.3.0 → 0.4.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/bin/unrift.mjs +2 -1
- package/dist/esm/cli.js +5 -0
- package/dist/esm/expect.d.ts +12 -0
- package/dist/esm/expect.d.ts.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/matchers.d.ts.map +1 -1
- package/dist/esm/matchers.js +161 -1
- package/dist/esm/mock/auto.d.ts +27 -0
- package/dist/esm/mock/auto.d.ts.map +1 -0
- package/dist/esm/mock/auto.js +108 -0
- package/dist/esm/mock/fetch.d.ts +47 -0
- package/dist/esm/mock/fetch.d.ts.map +1 -0
- package/dist/esm/mock/fetch.js +149 -0
- package/dist/esm/mock/fs.d.ts +54 -0
- package/dist/esm/mock/fs.d.ts.map +1 -0
- package/dist/esm/mock/fs.js +125 -0
- package/dist/esm/mock/index.d.ts +70 -0
- package/dist/esm/mock/index.d.ts.map +1 -0
- package/dist/esm/mock/index.js +70 -0
- package/dist/esm/mock/module-loader.d.ts +66 -0
- package/dist/esm/mock/module-loader.d.ts.map +1 -0
- package/dist/esm/mock/module-loader.js +123 -0
- package/dist/esm/mock/module.d.ts +99 -0
- package/dist/esm/mock/module.d.ts.map +1 -0
- package/dist/esm/mock/module.js +198 -0
- package/dist/esm/mock/register-loader.d.ts +17 -0
- package/dist/esm/mock/register-loader.d.ts.map +1 -0
- package/dist/esm/mock/register-loader.js +45 -0
- package/dist/esm/mock/spy.d.ts +73 -0
- package/dist/esm/mock/spy.d.ts.map +1 -0
- package/dist/esm/mock/spy.js +307 -0
- package/dist/esm/mock/stub.d.ts +26 -0
- package/dist/esm/mock/stub.d.ts.map +1 -0
- package/dist/esm/mock/stub.js +82 -0
- package/dist/esm/mock/timers.d.ts +51 -0
- package/dist/esm/mock/timers.d.ts.map +1 -0
- package/dist/esm/mock/timers.js +581 -0
- package/dist/esm/runner.d.ts.map +1 -1
- package/dist/esm/runner.js +13 -8
- package/dist/esm/test.d.ts.map +1 -1
- package/dist/esm/test.js +12 -5
- package/dist/esm/utils/compareEngine.js +3 -3
- package/dist/esm/utils/discoverTestFiles.js +1 -1
- package/dist/esm/utils/helpers.d.ts +10 -0
- package/dist/esm/utils/helpers.d.ts.map +1 -1
- package/dist/esm/utils/helpers.js +13 -0
- package/dist/esm/utils/toThrow.d.ts.map +1 -1
- package/dist/esm/utils/toThrow.js +2 -1
- package/package.json +4 -3
package/bin/unrift.mjs
CHANGED
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/)
|
package/dist/esm/expect.d.ts
CHANGED
|
@@ -28,6 +28,18 @@ export interface Matchers<T> {
|
|
|
28
28
|
toBeFinite(): void | Promise<void>;
|
|
29
29
|
toMatchObject(expected: Record<string, unknown>): void | Promise<void>;
|
|
30
30
|
toHaveProperty(path: string | readonly string[], value?: unknown): void | Promise<void>;
|
|
31
|
+
toHaveBeenCalled(): void | Promise<void>;
|
|
32
|
+
toHaveBeenCalledTimes(expected: number): void | Promise<void>;
|
|
33
|
+
toHaveBeenCalledWith(...args: unknown[]): void | Promise<void>;
|
|
34
|
+
toHaveBeenLastCalledWith(...args: unknown[]): void | Promise<void>;
|
|
35
|
+
toHaveBeenNthCalledWith(n: number, ...args: unknown[]): void | Promise<void>;
|
|
36
|
+
toHaveReturned(): void | Promise<void>;
|
|
37
|
+
toHaveReturnedTimes(expected: number): void | Promise<void>;
|
|
38
|
+
toHaveReturnedWith(expected: unknown): void | Promise<void>;
|
|
39
|
+
toHaveLastReturnedWith(expected: unknown): void | Promise<void>;
|
|
40
|
+
toHaveNthReturnedWith(n: number, expected: unknown): void | Promise<void>;
|
|
41
|
+
toHaveFetched(urlOrMatcher: string | RegExp | ((req: Request) => boolean)): void | Promise<void>;
|
|
42
|
+
toHaveFetchedTimes(expected: number): void | Promise<void>;
|
|
31
43
|
readonly not: Matchers<T>;
|
|
32
44
|
readonly resolves: Matchers<Awaited<T>>;
|
|
33
45
|
readonly rejects: Matchers<unknown>;
|
package/dist/esm/expect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/expect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,UAAU,EAAE,MAAM,SAAS,CAAC;AAK1D,wBAAgB,cAAc,CAAC,WAAW,EAAE,UAAU,QAErD;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,WAAW,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,SAAS,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,QAAQ,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,UAAU,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,aAAa,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,cAAc,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;
|
|
1
|
+
{"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/expect.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -6,4 +6,6 @@ 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
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAElD,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC1D,YAAY,EACV,cAAc,EACd,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,SAAS,CAAC;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,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;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";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/matchers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/matchers.ts"],"names":[],"mappings":"AAyGA,wBAAgB,sBAAsB,SAMrC;AAED,wBAAgB,aAAa,SAE5B"}
|
package/dist/esm/matchers.js
CHANGED
|
@@ -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);
|
|
@@ -163,7 +196,7 @@ function registerCoreMatchers() {
|
|
|
163
196
|
}
|
|
164
197
|
const pass = typeof expected === "string"
|
|
165
198
|
? received.includes(expected)
|
|
166
|
-
: expected
|
|
199
|
+
: regexTest(expected, received);
|
|
167
200
|
if (this.isNot ? pass : !pass)
|
|
168
201
|
throw new Error(this.diff(received, expected));
|
|
169
202
|
},
|
|
@@ -219,6 +252,133 @@ function registerCoreMatchers() {
|
|
|
219
252
|
throw new Error(this.diff(received, expected));
|
|
220
253
|
}
|
|
221
254
|
},
|
|
255
|
+
toHaveBeenCalled(received) {
|
|
256
|
+
ensureSpy(received, "toHaveBeenCalled");
|
|
257
|
+
const pass = received.mock.calls.length > 0;
|
|
258
|
+
if (this.isNot ? pass : !pass) {
|
|
259
|
+
throw new Error(this.diff(`${received.getMockName()} called ${received.mock.calls.length} time(s)`, "to have been called"));
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
toHaveBeenCalledTimes(received, expected) {
|
|
263
|
+
ensureSpy(received, "toHaveBeenCalledTimes");
|
|
264
|
+
if (typeof expected !== "number" || !Number.isFinite(expected)) {
|
|
265
|
+
throw new Error(this.diff(expected, "toHaveBeenCalledTimes() requires a finite number"));
|
|
266
|
+
}
|
|
267
|
+
const actual = received.mock.calls.length;
|
|
268
|
+
const pass = actual === expected;
|
|
269
|
+
if (this.isNot ? pass : !pass) {
|
|
270
|
+
throw new Error(this.diff(actual, expected));
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
toHaveBeenCalledWith(received, ...expectedArgs) {
|
|
274
|
+
ensureSpy(received, "toHaveBeenCalledWith");
|
|
275
|
+
const pass = received.mock.calls.some((call) => argsEqual(call, expectedArgs));
|
|
276
|
+
if (this.isNot ? pass : !pass) {
|
|
277
|
+
throw new Error(this.diff(received.mock.calls, expectedArgs));
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
toHaveBeenLastCalledWith(received, ...expectedArgs) {
|
|
281
|
+
ensureSpy(received, "toHaveBeenLastCalledWith");
|
|
282
|
+
const last = received.mock.lastCall;
|
|
283
|
+
const pass = last !== undefined && argsEqual(last, expectedArgs);
|
|
284
|
+
if (this.isNot ? pass : !pass) {
|
|
285
|
+
throw new Error(this.diff(last ?? "no calls", expectedArgs));
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
toHaveBeenNthCalledWith(received, n, ...expectedArgs) {
|
|
289
|
+
ensureSpy(received, "toHaveBeenNthCalledWith");
|
|
290
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
|
|
291
|
+
throw new Error(this.diff(n, "toHaveBeenNthCalledWith() requires a 1-indexed integer"));
|
|
292
|
+
}
|
|
293
|
+
const call = received.mock.calls[n - 1];
|
|
294
|
+
const pass = call !== undefined && argsEqual(call, expectedArgs);
|
|
295
|
+
if (this.isNot ? pass : !pass) {
|
|
296
|
+
throw new Error(this.diff(call ?? `call #${n} not found`, expectedArgs));
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
toHaveReturned(received) {
|
|
300
|
+
ensureSpy(received, "toHaveReturned");
|
|
301
|
+
const pass = received.mock.results.some((r) => r.type === "return");
|
|
302
|
+
if (this.isNot ? pass : !pass) {
|
|
303
|
+
throw new Error(this.diff(`${received.mock.results.length} result(s)`, "at least one successful return"));
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
toHaveReturnedTimes(received, expected) {
|
|
307
|
+
ensureSpy(received, "toHaveReturnedTimes");
|
|
308
|
+
if (typeof expected !== "number" || !Number.isFinite(expected)) {
|
|
309
|
+
throw new Error(this.diff(expected, "toHaveReturnedTimes() requires a finite number"));
|
|
310
|
+
}
|
|
311
|
+
const actual = received.mock.results.filter((r) => r.type === "return").length;
|
|
312
|
+
const pass = actual === expected;
|
|
313
|
+
if (this.isNot ? pass : !pass) {
|
|
314
|
+
throw new Error(this.diff(actual, expected));
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
toHaveReturnedWith(received, expected) {
|
|
318
|
+
ensureSpy(received, "toHaveReturnedWith");
|
|
319
|
+
const pass = received.mock.results.some((r) => r.type === "return" && looseEqual(r.value, expected));
|
|
320
|
+
if (this.isNot ? pass : !pass) {
|
|
321
|
+
const returnValues = received.mock.results
|
|
322
|
+
.filter((r) => r.type === "return")
|
|
323
|
+
.map((r) => r.value);
|
|
324
|
+
throw new Error(this.diff(returnValues, expected));
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
toHaveLastReturnedWith(received, expected) {
|
|
328
|
+
ensureSpy(received, "toHaveLastReturnedWith");
|
|
329
|
+
const results = received.mock.results;
|
|
330
|
+
const last = results[results.length - 1];
|
|
331
|
+
const pass = last !== undefined &&
|
|
332
|
+
last.type === "return" &&
|
|
333
|
+
looseEqual(last.value, expected);
|
|
334
|
+
if (this.isNot ? pass : !pass) {
|
|
335
|
+
if (last === undefined) {
|
|
336
|
+
throw new Error(this.diff("no calls", expected));
|
|
337
|
+
}
|
|
338
|
+
if (last.type === "throw") {
|
|
339
|
+
throw new Error(this.diff("last call threw", expected));
|
|
340
|
+
}
|
|
341
|
+
throw new Error(this.diff(last.value, expected));
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
toHaveNthReturnedWith(received, n, expected) {
|
|
345
|
+
ensureSpy(received, "toHaveNthReturnedWith");
|
|
346
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
|
|
347
|
+
throw new Error(this.diff(n, "toHaveNthReturnedWith() requires a 1-indexed integer"));
|
|
348
|
+
}
|
|
349
|
+
const result = received.mock.results[n - 1];
|
|
350
|
+
const pass = result !== undefined &&
|
|
351
|
+
result.type === "return" &&
|
|
352
|
+
looseEqual(result.value, expected);
|
|
353
|
+
if (this.isNot ? pass : !pass) {
|
|
354
|
+
if (result === undefined) {
|
|
355
|
+
throw new Error(this.diff(`call #${n} not found`, expected));
|
|
356
|
+
}
|
|
357
|
+
if (result.type === "throw") {
|
|
358
|
+
throw new Error(this.diff(`call #${n} threw`, expected));
|
|
359
|
+
}
|
|
360
|
+
throw new Error(this.diff(result.value, expected));
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
toHaveFetched(received, target) {
|
|
364
|
+
ensureFetchMock(received, "toHaveFetched");
|
|
365
|
+
const pass = received.calls.some((req) => matchesFetchTarget(req, target));
|
|
366
|
+
if (this.isNot ? pass : !pass) {
|
|
367
|
+
const urls = received.calls.map((r) => `${r.method} ${r.url}`);
|
|
368
|
+
throw new Error(this.diff(urls, target));
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
toHaveFetchedTimes(received, expected) {
|
|
372
|
+
ensureFetchMock(received, "toHaveFetchedTimes");
|
|
373
|
+
if (typeof expected !== "number" || !Number.isFinite(expected)) {
|
|
374
|
+
throw new Error(this.diff(expected, "toHaveFetchedTimes() requires a finite number"));
|
|
375
|
+
}
|
|
376
|
+
const actual = received.calls.length;
|
|
377
|
+
const pass = actual === expected;
|
|
378
|
+
if (this.isNot ? pass : !pass) {
|
|
379
|
+
throw new Error(this.diff(actual, expected));
|
|
380
|
+
}
|
|
381
|
+
},
|
|
222
382
|
toHaveProperty(received, path, ...rest) {
|
|
223
383
|
if (received === null || typeof received !== "object") {
|
|
224
384
|
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"}
|
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
import { activeRestorers } from "./spy.js";
|
|
13
|
+
import { regexTest } from "../utils/helpers.js";
|
|
14
|
+
/** Brand applied to `mock.fetch` so matchers can verify their received value. */
|
|
15
|
+
export const FETCH_MOCK_BRAND = Symbol.for("unrift.fetchMock");
|
|
16
|
+
// Process-global singleton state. Correct because unrift runs spec files
|
|
17
|
+
// sequentially in one process and afterEach(restoreAll) resets between tests.
|
|
18
|
+
// Parallel per-file execution (see ISSUES.md) would need per-worker isolation.
|
|
19
|
+
const handlers = [];
|
|
20
|
+
const calls = [];
|
|
21
|
+
let originalFetch;
|
|
22
|
+
// Whether globalThis had a `fetch` at install time. If it didn't (older Node,
|
|
23
|
+
// custom runtimes), restore must DELETE our patch rather than leave it - the
|
|
24
|
+
// original state was "fetch is absent", and `originalFetch` being undefined
|
|
25
|
+
// must not be mistaken for "nothing to restore".
|
|
26
|
+
let fetchWasDefined = false;
|
|
27
|
+
let installed = false;
|
|
28
|
+
function urlOf(req) {
|
|
29
|
+
return req.url;
|
|
30
|
+
}
|
|
31
|
+
async function matches(handler, req) {
|
|
32
|
+
if (handler.consumed)
|
|
33
|
+
return false;
|
|
34
|
+
const m = handler.matcher;
|
|
35
|
+
if (typeof m === "string")
|
|
36
|
+
return urlOf(req) === m;
|
|
37
|
+
if (m instanceof RegExp)
|
|
38
|
+
return regexTest(m, urlOf(req));
|
|
39
|
+
if (typeof m === "function")
|
|
40
|
+
return Boolean(await m(req));
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
async function buildResponse(spec, req) {
|
|
44
|
+
let resolved;
|
|
45
|
+
if (typeof spec === "function") {
|
|
46
|
+
resolved = await spec(req);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
resolved = spec;
|
|
50
|
+
}
|
|
51
|
+
if (resolved instanceof Response)
|
|
52
|
+
return resolved;
|
|
53
|
+
const init = resolved;
|
|
54
|
+
let body = init.body;
|
|
55
|
+
const headers = new Headers(init.headers);
|
|
56
|
+
if (init.json !== undefined) {
|
|
57
|
+
body = JSON.stringify(init.json);
|
|
58
|
+
if (!headers.has("Content-Type")) {
|
|
59
|
+
headers.set("Content-Type", "application/json");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return new Response(body ?? null, {
|
|
63
|
+
status: init.status ?? 200,
|
|
64
|
+
statusText: init.statusText,
|
|
65
|
+
headers,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function patchedFetch(input, init) {
|
|
69
|
+
/**
|
|
70
|
+
* Normalize all call shapes - `fetch(url)`, `fetch(url, init)`,
|
|
71
|
+
* `fetch(new Request(...))`, `fetch(new Request(...), init)` into a
|
|
72
|
+
* single Request object that handlers can introspect.
|
|
73
|
+
*/
|
|
74
|
+
const req = input instanceof Request
|
|
75
|
+
? new Request(input, init)
|
|
76
|
+
: new Request(input, init);
|
|
77
|
+
calls.push(req);
|
|
78
|
+
return (async () => {
|
|
79
|
+
for (const handler of handlers) {
|
|
80
|
+
if (await matches(handler, req)) {
|
|
81
|
+
if (handler.once)
|
|
82
|
+
handler.consumed = true;
|
|
83
|
+
return buildResponse(handler.response, req);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
throw new Error(`No mock.fetch handler matched: ${req.method} ${req.url}`);
|
|
87
|
+
})();
|
|
88
|
+
}
|
|
89
|
+
function install() {
|
|
90
|
+
if (installed)
|
|
91
|
+
return;
|
|
92
|
+
installed = true;
|
|
93
|
+
// `in` checks presence without triggering Node's lazy fetch getter.
|
|
94
|
+
fetchWasDefined = "fetch" in globalThis;
|
|
95
|
+
originalFetch = globalThis.fetch;
|
|
96
|
+
globalThis.fetch = patchedFetch;
|
|
97
|
+
activeRestorers.add(restoreFetch);
|
|
98
|
+
}
|
|
99
|
+
function restoreFetch() {
|
|
100
|
+
if (!installed)
|
|
101
|
+
return;
|
|
102
|
+
installed = false;
|
|
103
|
+
if (fetchWasDefined) {
|
|
104
|
+
globalThis.fetch = originalFetch;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// fetch didn't exist before we patched it - remove our patch so the
|
|
108
|
+
// environment reads `undefined` again, rather than leaking the stale mock.
|
|
109
|
+
delete globalThis.fetch;
|
|
110
|
+
}
|
|
111
|
+
originalFetch = undefined;
|
|
112
|
+
fetchWasDefined = false;
|
|
113
|
+
handlers.length = 0;
|
|
114
|
+
calls.length = 0;
|
|
115
|
+
activeRestorers.delete(restoreFetch);
|
|
116
|
+
}
|
|
117
|
+
function register(matcher, response, once) {
|
|
118
|
+
install();
|
|
119
|
+
handlers.push({ matcher, response, once, consumed: false });
|
|
120
|
+
}
|
|
121
|
+
// Type guard used by toHaveFetched / toHaveFetchedTimes matchers.
|
|
122
|
+
export function isFetchMock(value) {
|
|
123
|
+
return (typeof value === "function" &&
|
|
124
|
+
value[FETCH_MOCK_BRAND] === true);
|
|
125
|
+
}
|
|
126
|
+
const mockFetchImpl = ((matcher, response) => register(matcher, response, false));
|
|
127
|
+
mockFetchImpl.once = (matcher, response) => register(matcher, response, true);
|
|
128
|
+
mockFetchImpl.reset = () => {
|
|
129
|
+
handlers.length = 0;
|
|
130
|
+
calls.length = 0;
|
|
131
|
+
};
|
|
132
|
+
mockFetchImpl.restore = restoreFetch;
|
|
133
|
+
Object.defineProperty(mockFetchImpl, "calls", {
|
|
134
|
+
// Return a snapshot copy, not the internal array, so callers can't mutate
|
|
135
|
+
// recorded state (e.g. push/splice) and corrupt later assertions.
|
|
136
|
+
get: () => calls.slice(),
|
|
137
|
+
enumerable: true,
|
|
138
|
+
configurable: false,
|
|
139
|
+
});
|
|
140
|
+
Object.defineProperty(mockFetchImpl, FETCH_MOCK_BRAND, {
|
|
141
|
+
value: true,
|
|
142
|
+
enumerable: false,
|
|
143
|
+
configurable: false,
|
|
144
|
+
writable: false,
|
|
145
|
+
});
|
|
146
|
+
export const mockFetch = mockFetchImpl;
|
|
147
|
+
export function mockFetchOnce(matcher, response) {
|
|
148
|
+
mockFetch.once(matcher, response);
|
|
149
|
+
}
|