UNPKG

strong-mock

Version:

Type safe mocking library for TypeScript

1,154 lines (1,102 loc) 32.6 kB
var __defProp = Object.defineProperty; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/errors/unexpected-access.ts import { DIM_COLOR } from "jest-matcher-utils"; // src/print.ts import { EXPECTED_COLOR, RECEIVED_COLOR, stringify } from "jest-matcher-utils"; import cloneDeepWith from "lodash/cloneDeepWith.js"; // src/expectation/expectation.ts var ApplyProp = Symbol("apply"); // src/matchers/matcher.ts var MATCHER_SYMBOL = Symbol("matcher"); function isMatcher(f) { return !!(f && f[MATCHER_SYMBOL]); } var getMatcherDiffs = (matchers, args) => { const matcherDiffs = matchers.map((matcher, i) => matcher.getDiff(args[i])); const actual = matcherDiffs.map((d) => d.actual); const expected = matcherDiffs.map((d) => d.expected); return { actual, expected }; }; var matches = (predicate, options) => { var _a, _b; const toString = (_a = options == null ? void 0 : options.toString) != null ? _a : () => `Matcher(${predicate.toString()})`; const getDiff = (_b = options == null ? void 0 : options.getDiff) != null ? _b : (actual) => ({ actual, expected: toString() }); const matcher = { [MATCHER_SYMBOL]: true, matches: (actual) => predicate(actual), toString, getDiff: (actual) => { if (predicate(actual)) { return { actual, expected: actual }; } return getDiff(actual); } }; return matcher; }; // src/print.ts var printProperty = (property) => { if (property === ApplyProp) { return ""; } if (typeof property === "symbol") { return `[${property.toString()}]`; } return `.${property}`; }; var printValue = (arg) => { if (isMatcher(arg)) { return arg.toString(); } return stringify(arg); }; var deepPrint = (value) => cloneDeepWith(value, (value2) => { if (isMatcher(value2)) { return value2.toString(); } return void 0; }); var printArgs = (args) => args.map((arg) => printValue(arg)).join(", "); var printCall = (mockName, property, args) => { const prettyProperty = printProperty(property); if (args) { const prettyArgs = printArgs(args); return `${mockName}${RECEIVED_COLOR(`${prettyProperty}(${prettyArgs})`)}`; } return `${mockName}${RECEIVED_COLOR(`${prettyProperty}`)}`; }; var printReturns = ({ isError, isPromise, value }, min, max) => { let thenPrefix = ""; if (isPromise) { if (isError) { thenPrefix += "thenReject"; } else { thenPrefix += "thenResolve"; } } else if (isError) { thenPrefix += "thenThrow"; } else { thenPrefix += "thenReturn"; } return `.${thenPrefix}(${RECEIVED_COLOR( printValue(value) )}).between(${min}, ${max})`; }; var printWhen = (mockName, property, args) => { const prettyProperty = printProperty(property); if (args) { return `when(() => ${mockName}${EXPECTED_COLOR( `${prettyProperty}(${printArgs(args)})` )})`; } return `when(() => ${mockName}${EXPECTED_COLOR(`${printProperty(property)}`)})`; }; var printExpectation = (mockName, property, args, returnValue, min, max) => `${printWhen(mockName, property, args)}${printReturns(returnValue, min, max)}`; var printRemainingExpectations = (expectations) => expectations.length ? `Remaining unmet expectations: - ${expectations.map((e) => e.toString()).join("\n - ")}` : "There are no remaining unmet expectations."; // src/errors/unexpected-access.ts var UnexpectedAccess = class extends Error { constructor(mockName, property, expectations) { super( DIM_COLOR(`Didn't expect ${printCall(mockName, property)} to be accessed. If you expect this property to be accessed then you should set an expectation for it with when(). ${printRemainingExpectations(expectations)}`) ); } }; // src/errors/unexpected-call.ts import { DIM_COLOR as DIM_COLOR2 } from "jest-matcher-utils"; // src/errors/diff.ts import { diff as printDiff } from "jest-diff"; import { EXPECTED_COLOR as EXPECTED_COLOR2, RECEIVED_COLOR as RECEIVED_COLOR2 } from "jest-matcher-utils"; var noColor = (s) => s; var printArgsDiff = (expected, actual) => { const diff = printDiff(expected, actual, { omitAnnotationLines: true, aColor: noColor, bColor: noColor, changeColor: noColor, commonColor: noColor, patchColor: noColor }); if (!diff) { return ""; } const diffLines = diff.split("\n"); let relevantDiffLines; if (!expected.length) { relevantDiffLines = diffLines.slice(2, -1); } else if (!actual.length) { relevantDiffLines = diffLines.slice(1, -2); } else { relevantDiffLines = diffLines.slice(1, -1); } const lastLine = relevantDiffLines[relevantDiffLines.length - 1].slice(0, -1); const coloredDiffLines = [...relevantDiffLines.slice(0, -1), lastLine].map( (line) => { const first = line.charAt(0); switch (first) { case "-": return EXPECTED_COLOR2(line); case "+": return RECEIVED_COLOR2(line); default: return line; } } ); return coloredDiffLines.join("\n"); }; var printExpectationDiff = (e, args) => { var _a; if (!((_a = e.args) == null ? void 0 : _a.length)) { return ""; } const { actual, expected } = getMatcherDiffs(e.args, args); return printArgsDiff(expected, actual); }; var printDiffForAllExpectations = (expectations, actual) => expectations.map((e) => { const diff = printExpectationDiff(e, actual); if (diff) { return `${e.toString()} ${EXPECTED_COLOR2("- Expected")} ${RECEIVED_COLOR2("+ Received")} ${diff}`; } return void 0; }).filter((x) => x).join("\n\n"); // src/errors/unexpected-call.ts var UnexpectedCall = class extends Error { constructor(mockName, property, args, expectations) { var _a; const header = `Didn't expect ${printCall(mockName, property, args)} to be called.`; const propertyExpectations = expectations.filter( (e) => e.property === property ); if (propertyExpectations.length) { super( DIM_COLOR2(`${header} Remaining expectations: ${printDiffForAllExpectations(propertyExpectations, args)}`) ); if (propertyExpectations.length === 1 && ((_a = propertyExpectations[0].args) == null ? void 0 : _a.length)) { const { actual, expected } = getMatcherDiffs( propertyExpectations[0].args, args ); this.actual = actual; this.expected = expected; this.matcherResult = { actual, expected }; } } else { super( DIM_COLOR2(`${header} No remaining expectations.`) ); } } }; // src/mock/options.ts var UnexpectedProperty = /* @__PURE__ */ ((UnexpectedProperty2) => { UnexpectedProperty2[UnexpectedProperty2["THROW"] = 0] = "THROW"; UnexpectedProperty2[UnexpectedProperty2["CALL_THROW"] = 1] = "CALL_THROW"; return UnexpectedProperty2; })(UnexpectedProperty || {}); // src/expectation/repository/return-value.ts var unboxReturnValue = ({ isError, isPromise, value }) => { if (isError) { if (value instanceof Error) { if (isPromise) { return Promise.reject(value); } throw value; } if (isPromise) { return Promise.reject(new Error(value)); } throw new Error(value); } if (isPromise) { return Promise.resolve(value); } return value; }; // src/expectation/repository/flexible-repository.ts var FlexibleRepository = class { constructor(mockName, unexpectedProperty = 0 /* THROW */) { this.mockName = mockName; this.unexpectedProperty = unexpectedProperty; this.expectations = /* @__PURE__ */ new Map(); this.expectedCallStats = /* @__PURE__ */ new Map(); this.unexpectedCallStats = /* @__PURE__ */ new Map(); this.apply = (args) => this.get(ApplyProp)(...args); this.handlePropertyWithMatchingExpectations = (property, expectations) => { if (property !== ApplyProp) { this.recordExpected(property, void 0); } const propertyExpectation = expectations.find( (e) => e.expectation.matches(void 0) ); if (propertyExpectation) { this.countAndConsume(propertyExpectation); return unboxReturnValue(propertyExpectation.expectation.returnValue); } return (...args) => { const callExpectation = expectations.find( (e) => e.expectation.matches(args) ); if (callExpectation) { this.recordExpected(property, args); this.countAndConsume(callExpectation); return unboxReturnValue(callExpectation.expectation.returnValue); } return this.getValueForUnexpectedCall(property, args); }; }; this.handlePropertyWithNoExpectations = (property) => { switch (property) { case "toString": return () => this.mockName; case "@@toStringTag": case Symbol.toStringTag: case "name": return this.mockName; // Promise.resolve() tries to see if it's a "thenable". // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables case "then": return void 0; // pretty-format case "$$typeof": case "constructor": case "@@__IMMUTABLE_ITERABLE__@@": case "@@__IMMUTABLE_RECORD__@@": return null; case MATCHER_SYMBOL: return false; case ApplyProp: return (...args) => this.getValueForUnexpectedCall(property, args); default: return this.getValueForUnexpectedAccess(property); } }; } add(expectation) { const { property } = expectation; const expectations = this.expectations.get(property) || []; this.expectations.set(property, [ ...expectations, { expectation, matchCount: 0 } ]); } clear() { this.expectations.clear(); this.expectedCallStats.clear(); this.unexpectedCallStats.clear(); } // TODO: this returns any, but the interface returns unknown // unknown causes errors in apply tests, and any causes bugs in bootstrapped SM get(property) { const expectations = this.expectations.get(property); if (expectations && expectations.length) { return this.handlePropertyWithMatchingExpectations( property, expectations ); } return this.handlePropertyWithNoExpectations(property); } getAllProperties() { return Array.from(this.expectations.keys()); } getCallStats() { return { expected: this.expectedCallStats, unexpected: this.unexpectedCallStats }; } getUnmet() { return [].concat( ...Array.from(this.expectations.values()).map( (expectations) => expectations.filter((e) => e.expectation.min > e.matchCount).map((e) => e.expectation) ) ); } recordExpected(property, args) { const calls = this.expectedCallStats.get(property) || []; this.expectedCallStats.set(property, [...calls, { arguments: args }]); } recordUnexpected(property, args) { const calls = this.unexpectedCallStats.get(property) || []; this.unexpectedCallStats.set(property, [...calls, { arguments: args }]); } countAndConsume(expectation) { expectation.matchCount++; this.consumeExpectation(expectation); } consumeExpectation(expectation) { const { property, max } = expectation.expectation; const expectations = this.expectations.get(property); if (expectation.matchCount === max) { this.expectations.set( property, expectations.filter((e) => e !== expectation) ); } } getValueForUnexpectedCall(property, args) { this.recordUnexpected(property, args); throw new UnexpectedCall(this.mockName, property, args, this.getUnmet()); } getValueForUnexpectedAccess(property) { if (this.unexpectedProperty === 0 /* THROW */) { this.recordUnexpected(property, void 0); throw new UnexpectedAccess(this.mockName, property, this.getUnmet()); } return (...args) => { this.recordUnexpected(property, args); throw new UnexpectedCall(this.mockName, property, args, this.getUnmet()); }; } }; // src/expectation/strong-expectation.ts var StrongExpectation = class { constructor(mockName, property, args, returnValue, exactParams = false) { this.mockName = mockName; this.property = property; this.args = args; this.returnValue = returnValue; this.exactParams = exactParams; this.matched = 0; this.min = 1; this.max = 1; } setInvocationCount(min, max = 1) { this.min = min; this.max = max; } matches(args) { if (!this.matchesArgs(args)) { return false; } this.matched++; return this.max === 0 || this.matched <= this.max; } isUnmet() { return this.matched < this.min; } matchesArgs(received) { if (this.args === void 0) { return !received; } if (!received) { return false; } if (this.exactParams) { if (this.args.length !== received.length) { return false; } } return this.args.every((arg, i) => arg.matches(received[i])); } toString() { return printExpectation( this.mockName, this.property, this.args, this.returnValue, this.min, this.max ); } }; // src/errors/api.ts var UnfinishedExpectation = class extends Error { constructor(mockName, property, args) { super(`There is an unfinished pending expectation: ${printWhen(mockName, property, args)} You should finish it by setting a return value with e.g. thenReturns(), even if that value is undefined.`); } }; var MissingWhen = class extends Error { constructor() { super(`You tried setting a return value without an expectation. Every call to set a return value must be preceded by an expectation.`); } }; var NotAMock = class extends Error { constructor() { super(`We couldn't find the mock. Make sure you're passing in an actual mock.`); } }; var NestedWhen = class extends Error { constructor(parentProp, childProp) { const snippet = ` const parentMock = mock<T1>(); const childMock = mock<T2>(); when(() => childMock${printProperty(childProp)}).thenReturn(...); when(() => parentMock${printProperty(parentProp)}).thenReturn(childMock) `; super( `Setting an expectation on a nested property is not supported. You can return an object directly when the first property is accessed, or you can even return a separate mock: ${snippet}` ); } }; // src/when/expectation-builder.ts var ExpectationBuilderWithFactory = class { constructor(createExpectation, concreteMatcher, mockName, exactParams) { this.createExpectation = createExpectation; this.concreteMatcher = concreteMatcher; this.mockName = mockName; this.exactParams = exactParams; } setProperty(value) { if (this.property) { throw new UnfinishedExpectation(this.mockName, this.property, this.args); } this.property = value; } setArgs(value) { this.args = value; } finish(returnValue) { if (!this.property) { throw new MissingWhen(); } const expectation = this.createExpectation( this.mockName, this.property, this.args, returnValue, this.concreteMatcher, this.exactParams ); this.property = void 0; this.args = void 0; return expectation; } }; // src/matchers/deep-equals.ts import cloneDeep from "lodash/cloneDeep.js"; import cloneDeepWith2 from "lodash/cloneDeepWith.js"; import isEqualWith from "lodash/isEqualWith.js"; import isMap from "lodash/isMap.js"; import isObjectLike from "lodash/isObjectLike.js"; import omitBy from "lodash/omitBy.js"; var removeUndefined = (object) => { if (Array.isArray(object)) { return object.map((x) => removeUndefined(x)); } if (!isObjectLike(object)) { return object; } return omitBy(object, (value) => value === void 0); }; var getKey = (key, object) => { if (key === void 0) { return object; } return isMap(object) ? object.get(key) : object[key]; }; var deepEquals = (expected, { strict = true } = {}) => matches( (actual) => isEqualWith( strict ? actual : removeUndefined(actual), strict ? expected : removeUndefined(expected), (actualValue, expectedValue) => { if (isMatcher(expectedValue)) { return expectedValue.matches(actualValue); } return void 0; } ), { toString: () => printValue(deepPrint(expected)), getDiff: (actual) => { let actualResult = cloneDeep(actual); const expectedResult = cloneDeepWith2(expected, (expectedValue, key) => { const actualValue = getKey(key, actualResult); if (isMatcher(expectedValue)) { if (expectedValue.matches(actualValue)) { return actualValue; } const result = expectedValue.getDiff(actualValue); if (key !== void 0) { if (isMap(actualResult)) { actualResult.set(key, result.actual); } else { actualResult[key] = result.actual; } } else { actualResult = result.actual; } return result.expected; } return void 0; }); return { actual: actualResult, expected: expectedResult }; } } ); // src/mock/defaults.ts var defaults = { name: "mock", concreteMatcher: deepEquals, unexpectedProperty: 1 /* CALL_THROW */, exactParams: false }; var currentDefaults = defaults; var setDefaults = (newDefaults) => { currentDefaults = __spreadValues(__spreadValues({}, defaults), newDefaults); }; // src/mock/map.ts var activeMock; var setActiveMock = (mock2) => { activeMock = mock2; }; var getActiveMock = () => activeMock; var mockMap = /* @__PURE__ */ new Map(); var getMockState = (mock2) => { if (mockMap.has(mock2)) { return mockMap.get(mock2); } throw new NotAMock(); }; var setMockState = (mock2, state) => { mockMap.set(mock2, state); }; var getAllMocks = () => Array.from(mockMap.entries()); // src/mock/mode.ts var currentMode = 1 /* CALL */; var setMode = (mode) => { currentMode = mode; }; var getMode = () => currentMode; // src/proxy.ts var createProxy = (traps) => ( // The Proxy target MUST be a function, otherwise we can't use the `apply` trap: // https://262.ecma-international.org/6.0/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist new Proxy( /* c8 ignore next */ () => { }, { get: (target, prop) => { if (prop === "bind") { return (thisArg, ...args) => (...moreArgs) => traps.apply([...args, ...moreArgs]); } if (prop === "apply") { return (thisArg, args) => traps.apply(args || []); } if (prop === "call") { return (thisArg, ...args) => traps.apply(args); } return traps.property(prop); }, apply: (target, thisArg, args) => traps.apply(args), ownKeys: () => traps.ownKeys(), getOwnPropertyDescriptor(target, prop) { const keys = traps.ownKeys(); if (keys.includes(prop)) { return { configurable: true, enumerable: true }; } return void 0; } } ) ); // src/mock/stub.ts var createStub = (repo, builder, getCurrentMode) => { const stub = createProxy({ property: (property) => { if (getCurrentMode() === 1 /* CALL */) { return repo.get(property); } setActiveMock(stub); builder.setProperty(property); return createProxy({ property: (childProp) => { throw new NestedWhen(property, childProp); }, apply: (args) => { builder.setArgs(args); }, ownKeys: () => { throw new Error("Spreading during an expectation is not supported."); } }); }, apply: (args) => { if (getCurrentMode() === 1 /* CALL */) { return repo.apply(args); } setActiveMock(stub); builder.setProperty(ApplyProp); builder.setArgs(args); return void 0; }, ownKeys: () => { if (getCurrentMode() === 1 /* CALL */) { return repo.getAllProperties(); } throw new Error("Spreading during an expectation is not supported."); } }); return stub; }; // src/mock/mock.ts var strongExpectationFactory = (name, property, args, returnValue, concreteMatcher, exactParams) => new StrongExpectation( name, property, args == null ? void 0 : args.map((arg) => isMatcher(arg) ? arg : concreteMatcher(arg)), returnValue, exactParams ); var mock = ({ name, unexpectedProperty, concreteMatcher, exactParams } = {}) => { const options = { name: name != null ? name : currentDefaults.name, unexpectedProperty: unexpectedProperty != null ? unexpectedProperty : currentDefaults.unexpectedProperty, concreteMatcher: concreteMatcher != null ? concreteMatcher : currentDefaults.concreteMatcher, exactParams: exactParams != null ? exactParams : currentDefaults.exactParams }; const repository = new FlexibleRepository( options.name, options.unexpectedProperty ); const builder = new ExpectationBuilderWithFactory( strongExpectationFactory, options.concreteMatcher, options.name, options.exactParams ); const stub = createStub(repository, builder, getMode); setMockState(stub, { repository, builder, options }); return stub; }; // src/return/invocation-count.ts var createInvocationCount = (expectation) => ({ between(min, max) { expectation.setInvocationCount(min, max); }, /* c8 ignore next 3 */ times(exact) { expectation.setInvocationCount(exact, exact); }, /* c8 ignore next 3 */ anyTimes() { expectation.setInvocationCount(0, 0); }, /* c8 ignore next 3 */ atLeast(min) { expectation.setInvocationCount(min, Infinity); }, /* c8 ignore next 3 */ atMost(max) { expectation.setInvocationCount(0, max); }, /* c8 ignore next 3 */ once() { expectation.setInvocationCount(1, 1); }, /* c8 ignore next 3 */ twice() { expectation.setInvocationCount(2, 2); } }); // src/return/returns.ts var finishExpectation = (returnValue, builder, repo) => { const finishedExpectation = builder.finish(returnValue); repo.add(finishedExpectation); return createInvocationCount(finishedExpectation); }; var getError = (errorOrMessage) => { if (typeof errorOrMessage === "string") { return new Error(errorOrMessage); } if (errorOrMessage instanceof Error) { return errorOrMessage; } return new Error(); }; var createReturns = (builder, repository) => ({ thenReturn: (returnValue) => finishExpectation( // This will handle both thenReturn(23) and thenReturn(Promise.resolve(3)). { value: returnValue, isError: false, isPromise: false }, builder, repository ), thenThrow: (errorOrMessage) => finishExpectation( { value: getError(errorOrMessage), isError: true, isPromise: false }, builder, repository ), thenResolve: (promiseValue) => finishExpectation( { value: promiseValue, isError: false, isPromise: true }, builder, repository ), thenReject: (errorOrMessage) => finishExpectation( { value: getError(errorOrMessage), isError: true, isPromise: true }, builder, repository ) }); // src/when/when.ts var when = (expectation) => { setMode(0 /* EXPECT */); expectation(); setMode(1 /* CALL */); const { builder, repository } = getMockState(getActiveMock()); return createReturns(builder, repository); }; // src/verify/reset.ts var reset = (mock2) => { getMockState(mock2).repository.clear(); }; var resetAll = () => { getAllMocks().forEach(([mock2]) => { reset(mock2); }); }; // src/errors/verify.ts import { DIM_COLOR as DIM_COLOR3 } from "jest-matcher-utils"; var UnmetExpectations = class extends Error { constructor(expectations) { super( DIM_COLOR3(`There are unmet expectations: - ${expectations.map((e) => e.toString()).join("\n - ")}`) ); } }; var mergeCalls = (callMap) => new Map( Array.from(callMap.entries()).map(([property, calls]) => { const hasMethodCalls = calls.some((call) => call.arguments); const hasPropertyAccesses = calls.some((call) => !call.arguments); if (hasMethodCalls && hasPropertyAccesses) { return [property, calls.filter((call) => call.arguments)]; } return [property, calls]; }) ); var UnexpectedCalls = class extends Error { constructor(mockName, unexpectedCalls, expectations) { const printedCalls = Array.from(mergeCalls(unexpectedCalls).entries()).map( ([property, calls]) => calls.map((call) => printCall(mockName, property, call.arguments)).join("\n - ") ).join("\n - "); super( DIM_COLOR3(`The following calls were unexpected: - ${printedCalls} ${printRemainingExpectations(expectations)}`) ); } }; // src/verify/verify.ts var verifyRepo = (repository) => { const unmetExpectations = repository.getUnmet(); if (unmetExpectations.length) { throw new UnmetExpectations(unmetExpectations); } const callStats = repository.getCallStats(); if (callStats.unexpected.size) { throw new UnexpectedCalls( repository.mockName, callStats.unexpected, unmetExpectations ); } }; var verify = (mock2) => { const { repository } = getMockState(mock2); verifyRepo(repository); }; var verifyAll = () => { getAllMocks().forEach(([mock2]) => { verify(mock2); }); }; // src/matchers/it.ts var it_exports = {}; __export(it_exports, { containsObject: () => containsObject, deepEquals: () => deepEquals, is: () => is, isAny: () => isAny, isArray: () => isArray, isNumber: () => isNumber, isPlainObject: () => isPlainObject, isString: () => isString, matches: () => matches, willCapture: () => willCapture }); // src/matchers/is.ts var is = (expected) => matches((actual) => Object.is(actual, expected), { toString: () => `${printValue(expected)}`, getDiff: (actual) => ({ actual, expected }) }); // src/matchers/is-any.ts var isAny = () => matches(() => true, { toString: () => "Matcher<any>" }); // src/matchers/is-array.ts var isArray = (containing) => matches( (actual) => { if (!Array.isArray(actual)) { return false; } if (!containing) { return true; } return containing.every( (x) => actual.find((y) => { if (isMatcher(x)) { return x.matches(y); } return deepEquals(x).matches(y); }) !== void 0 ); }, { toString: () => containing ? `array([${containing.map((v) => printValue(v)).join(", ")}])` : "array", getDiff: (actual) => { if (containing) { return { actual, expected: `Matcher<array>([${containing.map((value) => { if (isMatcher(value)) { return value.toString(); } return value; }).join(", ")}])` }; } return { actual: `${printValue(actual)} (${typeof actual})`, expected: "Matcher<array>" }; } } ); // src/matchers/is-number.ts var isNumber = () => matches((actual) => typeof actual === "number" && !Number.isNaN(actual), { toString: () => "Matcher<number>", getDiff: (actual) => ({ actual: `${printValue(actual)} (${typeof actual})`, expected: "Matcher<number>" }) }); // src/matchers/is-plain-object.ts import isObjectLike2 from "lodash/isObjectLike.js"; import isPlainObjectLodash from "lodash/isPlainObject.js"; var isPlainObject = () => matches((actual) => isPlainObjectLodash(actual), { toString: () => "Matcher<object>", getDiff: (actual) => { const type = isObjectLike2(actual) ? "object-like" : typeof actual; return { actual: `${printValue(actual)} (${type})`, expected: "Matcher<object>" }; } }); // src/matchers/contains-object.ts import isPlainObject2 from "lodash/isPlainObject.js"; var looksLikeObject = (value) => isPlainObject2(value); var getExpectedObjectDiff = (actual, expected) => Object.fromEntries( getKeys(expected).map((key) => { const expectedValue = getKey2(expected, key); const actualValue = getKey2(actual, key); if (isMatcher(expectedValue)) { return [key, expectedValue.getDiff(actualValue).expected]; } if (looksLikeObject(expectedValue)) { return [key, getExpectedObjectDiff(actualValue, expectedValue)]; } return [key, expectedValue]; }) ); var getActualObjectDiff = (actual, expected) => { const actualKeys = getKeys(actual); const expectedKeys = new Set(getKeys(expected)); const commonKeys = actualKeys.filter((key) => expectedKeys.has(key)); if (!commonKeys.length) { return actual; } return Object.fromEntries( commonKeys.map((key) => { const expectedValue = getKey2(expected, key); const actualValue = getKey2(actual, key); if (isMatcher(expectedValue)) { return [key, expectedValue.getDiff(actualValue).actual]; } if (looksLikeObject(expectedValue)) { return [key, getActualObjectDiff(actualValue, expectedValue)]; } return [key, actualValue]; }) ); }; var getKeys = (value) => { if (typeof value === "object" && value !== null) { return Reflect.ownKeys(value); } return []; }; var getKey2 = (value, key) => ( // @ts-expect-error because we're fine with a runtime undefined value value == null ? void 0 : value[key] ); var isMatch = (actual, expected) => { const actualKeys = getKeys(actual); const expectedKeys = getKeys(expected); if (!isArray(expectedKeys).matches(actualKeys)) { return false; } return expectedKeys.every((key) => { const expectedValue = getKey2(expected, key); const actualValue = getKey2(actual, key); if (isMatcher(expectedValue)) { return expectedValue.matches(actualValue); } if (looksLikeObject(expectedValue)) { return isMatch(actualValue, expectedValue); } return deepEquals(expectedValue).matches(actualValue); }); }; var containsObject = (partial) => matches((actual) => isMatch(actual, partial), { toString: () => `Matcher<object>(${printValue(deepPrint(partial))})`, getDiff: (actual) => ({ actual: getActualObjectDiff(actual, partial), expected: getExpectedObjectDiff(actual, partial) }) }); // src/matchers/is-string.ts var isString = (matching) => matches( (actual) => { if (typeof actual !== "string") { return false; } if (!matching) { return true; } if (typeof matching === "string") { return actual.indexOf(matching) !== -1; } return matching.test(actual); }, { toString: () => { if (matching) { return `Matcher<string>(${matching})`; } return "Matcher<string>"; }, getDiff: (actual) => { if (matching) { return { expected: `Matcher<string>(${matching})`, actual }; } return { expected: "Matcher<string>", actual: `${actual} (${typeof actual})` }; } } ); // src/matchers/will-capture.ts var willCapture = (name) => { let capturedValue; const matcher = { [MATCHER_SYMBOL]: true, matches: (actual) => { capturedValue = actual; return true; }, toString: () => name ? `Capture(${name})` : "Capture", getDiff: (actual) => ({ actual, expected: actual }), get value() { return capturedValue; } }; return matcher; }; export { it_exports as It, UnexpectedProperty, mock, reset, resetAll, setDefaults, verify, verifyAll, when }; /* c8 ignore next 3 this is not expected in practice -- @preserve */