mirror of
https://github.com/denoland/std.git
synced 2024-11-21 12:40:03 +00:00
fix(expect): re-align expect.toMatchObject
api (#6160)
This commit is contained in:
parent
689fb69c14
commit
32b4fb62d1
@ -44,6 +44,11 @@ export function equal(c: unknown, d: unknown, options?: EqualOptions): boolean {
|
||||
const seen = new Map();
|
||||
|
||||
return (function compare(a: unknown, b: unknown): boolean {
|
||||
const asymmetric = asymmetricEqual(a, b);
|
||||
if (asymmetric !== undefined) {
|
||||
return asymmetric;
|
||||
}
|
||||
|
||||
if (customTesters?.length) {
|
||||
for (const customTester of customTesters) {
|
||||
const testContext = {
|
||||
@ -67,11 +72,6 @@ export function equal(c: unknown, d: unknown, options?: EqualOptions): boolean {
|
||||
return String(a) === String(b);
|
||||
}
|
||||
|
||||
const asymmetric = asymmetricEqual(a, b);
|
||||
if (asymmetric !== undefined) {
|
||||
return asymmetric;
|
||||
}
|
||||
|
||||
if (a instanceof Date && b instanceof Date) {
|
||||
const aTime = a.getTime();
|
||||
const bTime = b.getTime();
|
||||
@ -119,6 +119,10 @@ export function equal(c: unknown, d: unknown, options?: EqualOptions): boolean {
|
||||
let aLen = aKeys.length;
|
||||
let bLen = bKeys.length;
|
||||
|
||||
if (strictCheck && aLen !== bLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!strictCheck) {
|
||||
if (aLen > 0) {
|
||||
for (let i = 0; i < aKeys.length; i += 1) {
|
||||
@ -145,9 +149,6 @@ export function equal(c: unknown, d: unknown, options?: EqualOptions): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
if (aLen !== bLen) {
|
||||
return false;
|
||||
}
|
||||
seen.set(a, b);
|
||||
if (isKeyedCollection(a) && isKeyedCollection(b)) {
|
||||
if (a.size !== b.size) {
|
||||
|
@ -6,7 +6,6 @@ import { assertInstanceOf } from "@std/assert/instance-of";
|
||||
import { assertIsError } from "@std/assert/is-error";
|
||||
import { assertNotInstanceOf } from "@std/assert/not-instance-of";
|
||||
import { assertMatch } from "@std/assert/match";
|
||||
import { assertObjectMatch } from "@std/assert/object-match";
|
||||
import { assertNotMatch } from "@std/assert/not-match";
|
||||
import { AssertionError } from "@std/assert/assertion-error";
|
||||
|
||||
@ -17,7 +16,11 @@ import { format } from "@std/internal/format";
|
||||
import type { AnyConstructor, MatcherContext, MatchResult } from "./_types.ts";
|
||||
import { getMockCalls } from "./_mock_util.ts";
|
||||
import { inspectArg, inspectArgs } from "./_inspect_args.ts";
|
||||
import { buildEqualOptions, iterableEquality } from "./_utils.ts";
|
||||
import {
|
||||
buildEqualOptions,
|
||||
iterableEquality,
|
||||
subsetEquality,
|
||||
} from "./_utils.ts";
|
||||
|
||||
export function toBe(context: MatcherContext, expect: unknown): MatchResult {
|
||||
if (context.isNot) {
|
||||
@ -462,34 +465,35 @@ export function toMatchObject(
|
||||
context: MatcherContext,
|
||||
expected: Record<PropertyKey, unknown> | Record<PropertyKey, unknown>[],
|
||||
): MatchResult {
|
||||
if (context.isNot) {
|
||||
let objectMatch = false;
|
||||
try {
|
||||
assertObjectMatch(
|
||||
// deno-lint-ignore no-explicit-any
|
||||
context.value as Record<PropertyKey, any>,
|
||||
expected as Record<PropertyKey, unknown>,
|
||||
context.customMessage,
|
||||
);
|
||||
objectMatch = true;
|
||||
const actualString = format(context.value);
|
||||
const expectedString = format(expected);
|
||||
throw new AssertionError(
|
||||
`Expected ${actualString} to NOT match ${expectedString}`,
|
||||
);
|
||||
} catch (e) {
|
||||
if (objectMatch) {
|
||||
throw e;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
assertObjectMatch(
|
||||
// deno-lint-ignore no-explicit-any
|
||||
context.value as Record<PropertyKey, any>,
|
||||
expected as Record<PropertyKey, unknown>,
|
||||
context.customMessage,
|
||||
const received = context.value;
|
||||
|
||||
if (typeof received !== "object" || received === null) {
|
||||
throw new AssertionError("Received value must be an object");
|
||||
}
|
||||
|
||||
if (typeof expected !== "object" || expected === null) {
|
||||
throw new AssertionError("Received value must be an object");
|
||||
}
|
||||
|
||||
const pass = equal(context.value, expected, {
|
||||
strictCheck: false,
|
||||
customTesters: [
|
||||
...context.customTesters,
|
||||
iterableEquality,
|
||||
subsetEquality,
|
||||
],
|
||||
});
|
||||
|
||||
const triggerError = () => {
|
||||
const actualString = format(context.value);
|
||||
const expectedString = format(expected);
|
||||
throw new AssertionError(
|
||||
`Expected ${actualString} to NOT match ${expectedString}`,
|
||||
);
|
||||
};
|
||||
|
||||
if (context.isNot && pass || !context.isNot && !pass) {
|
||||
triggerError();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,3 +50,21 @@ Deno.test("expect().toMatchObject()", () => {
|
||||
expect([house0]).not.toMatchObject([desiredHouse]);
|
||||
}, AssertionError);
|
||||
});
|
||||
|
||||
Deno.test("expect().toMatchObject() with array", () => {
|
||||
const fixedPriorityQueue = Array.from({ length: 5 });
|
||||
fixedPriorityQueue[0] = { data: 1, priority: 0 };
|
||||
|
||||
expect(fixedPriorityQueue).toMatchObject([
|
||||
{ data: 1, priority: 0 },
|
||||
]);
|
||||
});
|
||||
|
||||
Deno.test("expect(),toMatchObject() with asyAsymmetric matcher", () => {
|
||||
expect({ position: { x: 0, y: 0 } }).toMatchObject({
|
||||
position: {
|
||||
x: expect.any(Number),
|
||||
y: expect.any(Number),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -40,6 +40,40 @@ function isObject(a: unknown) {
|
||||
return a !== null && typeof a === "object";
|
||||
}
|
||||
|
||||
function isObjectWithKeys(a: unknown) {
|
||||
return (
|
||||
isObject(a) &&
|
||||
!(a instanceof Error) &&
|
||||
!Array.isArray(a) &&
|
||||
!(a instanceof Date) &&
|
||||
!(a instanceof Set) &&
|
||||
!(a instanceof Map)
|
||||
);
|
||||
}
|
||||
|
||||
function getObjectKeys(object: object): Array<string | symbol> {
|
||||
return [
|
||||
...Object.keys(object),
|
||||
...Object.getOwnPropertySymbols(object).filter(
|
||||
(s) => Object.getOwnPropertyDescriptor(object, s)?.enumerable,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function hasPropertyInObject(object: object, key: string | symbol): boolean {
|
||||
const shouldTerminate = !object || typeof object !== "object" ||
|
||||
object === Object.prototype;
|
||||
|
||||
if (shouldTerminate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
Object.prototype.hasOwnProperty.call(object, key) ||
|
||||
hasPropertyInObject(Object.getPrototypeOf(object), key)
|
||||
);
|
||||
}
|
||||
|
||||
// deno-lint-ignore no-explicit-any
|
||||
function entries(obj: any) {
|
||||
if (!isObject(obj)) return [];
|
||||
@ -199,3 +233,51 @@ export function iterableEquality(
|
||||
bStack.pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ported from https://github.com/jestjs/jest/blob/442c7f692e3a92f14a2fb56c1737b26fc663a0ef/packages/expect-utils/src/utils.ts#L341
|
||||
export function subsetEquality(
|
||||
object: unknown,
|
||||
subset: unknown,
|
||||
customTesters: Tester[] = [],
|
||||
): boolean | undefined {
|
||||
const filteredCustomTesters = customTesters.filter((t) =>
|
||||
t !== subsetEquality
|
||||
);
|
||||
|
||||
const subsetEqualityWithContext =
|
||||
(seenReferences: WeakMap<object, boolean> = new WeakMap()) =>
|
||||
// deno-lint-ignore no-explicit-any
|
||||
(object: any, subset: any): boolean | undefined => {
|
||||
if (!isObjectWithKeys(subset)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (seenReferences.has(subset)) return undefined;
|
||||
seenReferences.set(subset, true);
|
||||
|
||||
const matchResult = getObjectKeys(subset).every((key) => {
|
||||
if (isObjectWithKeys(subset[key])) {
|
||||
if (seenReferences.has(subset[key])) {
|
||||
return equal(object[key], subset[key], {
|
||||
customTesters: filteredCustomTesters,
|
||||
});
|
||||
}
|
||||
}
|
||||
const result = object != null &&
|
||||
hasPropertyInObject(object, key) &&
|
||||
equal(object[key], subset[key], {
|
||||
customTesters: [
|
||||
...filteredCustomTesters,
|
||||
subsetEqualityWithContext(seenReferences),
|
||||
],
|
||||
});
|
||||
seenReferences.delete(subset[key]);
|
||||
return result;
|
||||
});
|
||||
seenReferences.delete(subset);
|
||||
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
return subsetEqualityWithContext()(object, subset);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user