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();
|
const seen = new Map();
|
||||||
|
|
||||||
return (function compare(a: unknown, b: unknown): boolean {
|
return (function compare(a: unknown, b: unknown): boolean {
|
||||||
|
const asymmetric = asymmetricEqual(a, b);
|
||||||
|
if (asymmetric !== undefined) {
|
||||||
|
return asymmetric;
|
||||||
|
}
|
||||||
|
|
||||||
if (customTesters?.length) {
|
if (customTesters?.length) {
|
||||||
for (const customTester of customTesters) {
|
for (const customTester of customTesters) {
|
||||||
const testContext = {
|
const testContext = {
|
||||||
@ -67,11 +72,6 @@ export function equal(c: unknown, d: unknown, options?: EqualOptions): boolean {
|
|||||||
return String(a) === String(b);
|
return String(a) === String(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
const asymmetric = asymmetricEqual(a, b);
|
|
||||||
if (asymmetric !== undefined) {
|
|
||||||
return asymmetric;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a instanceof Date && b instanceof Date) {
|
if (a instanceof Date && b instanceof Date) {
|
||||||
const aTime = a.getTime();
|
const aTime = a.getTime();
|
||||||
const bTime = b.getTime();
|
const bTime = b.getTime();
|
||||||
@ -119,6 +119,10 @@ export function equal(c: unknown, d: unknown, options?: EqualOptions): boolean {
|
|||||||
let aLen = aKeys.length;
|
let aLen = aKeys.length;
|
||||||
let bLen = bKeys.length;
|
let bLen = bKeys.length;
|
||||||
|
|
||||||
|
if (strictCheck && aLen !== bLen) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!strictCheck) {
|
if (!strictCheck) {
|
||||||
if (aLen > 0) {
|
if (aLen > 0) {
|
||||||
for (let i = 0; i < aKeys.length; i += 1) {
|
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);
|
seen.set(a, b);
|
||||||
if (isKeyedCollection(a) && isKeyedCollection(b)) {
|
if (isKeyedCollection(a) && isKeyedCollection(b)) {
|
||||||
if (a.size !== b.size) {
|
if (a.size !== b.size) {
|
||||||
|
@ -6,7 +6,6 @@ import { assertInstanceOf } from "@std/assert/instance-of";
|
|||||||
import { assertIsError } from "@std/assert/is-error";
|
import { assertIsError } from "@std/assert/is-error";
|
||||||
import { assertNotInstanceOf } from "@std/assert/not-instance-of";
|
import { assertNotInstanceOf } from "@std/assert/not-instance-of";
|
||||||
import { assertMatch } from "@std/assert/match";
|
import { assertMatch } from "@std/assert/match";
|
||||||
import { assertObjectMatch } from "@std/assert/object-match";
|
|
||||||
import { assertNotMatch } from "@std/assert/not-match";
|
import { assertNotMatch } from "@std/assert/not-match";
|
||||||
import { AssertionError } from "@std/assert/assertion-error";
|
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 type { AnyConstructor, MatcherContext, MatchResult } from "./_types.ts";
|
||||||
import { getMockCalls } from "./_mock_util.ts";
|
import { getMockCalls } from "./_mock_util.ts";
|
||||||
import { inspectArg, inspectArgs } from "./_inspect_args.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 {
|
export function toBe(context: MatcherContext, expect: unknown): MatchResult {
|
||||||
if (context.isNot) {
|
if (context.isNot) {
|
||||||
@ -462,34 +465,35 @@ export function toMatchObject(
|
|||||||
context: MatcherContext,
|
context: MatcherContext,
|
||||||
expected: Record<PropertyKey, unknown> | Record<PropertyKey, unknown>[],
|
expected: Record<PropertyKey, unknown> | Record<PropertyKey, unknown>[],
|
||||||
): MatchResult {
|
): MatchResult {
|
||||||
if (context.isNot) {
|
const received = context.value;
|
||||||
let objectMatch = false;
|
|
||||||
try {
|
if (typeof received !== "object" || received === null) {
|
||||||
assertObjectMatch(
|
throw new AssertionError("Received value must be an object");
|
||||||
// deno-lint-ignore no-explicit-any
|
}
|
||||||
context.value as Record<PropertyKey, any>,
|
|
||||||
expected as Record<PropertyKey, unknown>,
|
if (typeof expected !== "object" || expected === null) {
|
||||||
context.customMessage,
|
throw new AssertionError("Received value must be an object");
|
||||||
);
|
}
|
||||||
objectMatch = true;
|
|
||||||
const actualString = format(context.value);
|
const pass = equal(context.value, expected, {
|
||||||
const expectedString = format(expected);
|
strictCheck: false,
|
||||||
throw new AssertionError(
|
customTesters: [
|
||||||
`Expected ${actualString} to NOT match ${expectedString}`,
|
...context.customTesters,
|
||||||
);
|
iterableEquality,
|
||||||
} catch (e) {
|
subsetEquality,
|
||||||
if (objectMatch) {
|
],
|
||||||
throw e;
|
});
|
||||||
}
|
|
||||||
return;
|
const triggerError = () => {
|
||||||
}
|
const actualString = format(context.value);
|
||||||
} else {
|
const expectedString = format(expected);
|
||||||
assertObjectMatch(
|
throw new AssertionError(
|
||||||
// deno-lint-ignore no-explicit-any
|
`Expected ${actualString} to NOT match ${expectedString}`,
|
||||||
context.value as Record<PropertyKey, any>,
|
|
||||||
expected as Record<PropertyKey, unknown>,
|
|
||||||
context.customMessage,
|
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (context.isNot && pass || !context.isNot && !pass) {
|
||||||
|
triggerError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,3 +50,21 @@ Deno.test("expect().toMatchObject()", () => {
|
|||||||
expect([house0]).not.toMatchObject([desiredHouse]);
|
expect([house0]).not.toMatchObject([desiredHouse]);
|
||||||
}, AssertionError);
|
}, 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";
|
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
|
// deno-lint-ignore no-explicit-any
|
||||||
function entries(obj: any) {
|
function entries(obj: any) {
|
||||||
if (!isObject(obj)) return [];
|
if (!isObject(obj)) return [];
|
||||||
@ -199,3 +233,51 @@ export function iterableEquality(
|
|||||||
bStack.pop();
|
bStack.pop();
|
||||||
return true;
|
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