fix(expect): support expect.objectContaining (#6065)

Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
This commit is contained in:
eryue0220 2024-10-21 22:12:53 +08:00 committed by GitHub
parent a541fb468c
commit ae7048f9ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 99 additions and 1 deletions

View File

@ -1,6 +1,8 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// deno-lint-ignore-file no-explicit-any // deno-lint-ignore-file no-explicit-any
import { equal } from "./_equal.ts";
export abstract class AsymmetricMatcher<T> { export abstract class AsymmetricMatcher<T> {
constructor( constructor(
protected value: T, protected value: T,
@ -145,3 +147,31 @@ export class StringMatching extends AsymmetricMatcher<RegExp> {
export function stringMatching(pattern: string | RegExp): StringMatching { export function stringMatching(pattern: string | RegExp): StringMatching {
return new StringMatching(pattern); return new StringMatching(pattern);
} }
export class ObjectContaining
extends AsymmetricMatcher<Record<string, unknown>> {
constructor(obj: Record<string, unknown>) {
super(obj);
}
equals(other: Record<string, unknown>): boolean {
const keys = Object.keys(this.value);
for (const key of keys) {
if (
!Object.hasOwn(other, key) ||
!equal(this.value[key], other[key])
) {
return false;
}
}
return true;
}
}
export function objectContaining(
obj: Record<string, unknown>,
): ObjectContaining {
return new ObjectContaining(obj);
}

View File

@ -0,0 +1,50 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { Buffer } from "node:buffer";
import { Buffer as DenoBuffer } from "@std/io/buffer";
import { expect } from "./expect.ts";
Deno.test("expect.objectContaining()", () => {
expect({ bar: "baz" }).toEqual(expect.objectContaining({ bar: "baz" }));
expect({ foo: undefined }).toEqual(
expect.objectContaining({ foo: undefined }),
);
expect({ bar: "baz" }).not.toEqual(expect.objectContaining({ foo: "bar" }));
});
Deno.test("expect.objectContaining() with nested objects", () => {
expect({ foo: { bar: "baz" } }).toEqual(
expect.objectContaining({ foo: { bar: "baz" } }),
);
expect({ foo: { bar: "baz" } }).not.toEqual(
expect.objectContaining({ foo: { bar: "bar" } }),
);
});
Deno.test("expect.objectContaining() with symbols", () => {
const foo = Symbol("foo");
expect({ [foo]: { bar: "baz" } }).toEqual(
expect.objectContaining({ [foo]: { bar: "baz" } }),
);
});
Deno.test("expect.objectContaining() with nested arrays", () => {
expect({ foo: ["bar", "baz"] }).toEqual(
expect.objectContaining({ foo: ["bar", "baz"] }),
);
expect({ foo: ["bar", "baz"] }).not.toEqual(
expect.objectContaining({ foo: ["bar", "bar"] }),
);
});
Deno.test("expect.objectContaining() with Node Buffer", () => {
expect({ foo: Buffer.from("foo") }).toEqual(
expect.objectContaining({ foo: Buffer.from("foo") }),
);
});
Deno.test("expect.objectContaining() with Deno Buffer", () => {
expect({ foo: new DenoBuffer([1, 2, 3]) }).toEqual(
expect.objectContaining({ foo: new DenoBuffer([1, 2, 3]) }),
);
});

View File

@ -509,3 +509,21 @@ expect.stringMatching = asymmetricMatchers.stringMatching as (
* ``` * ```
*/ */
expect.hasAssertions = hasAssertions as () => void; expect.hasAssertions = hasAssertions as () => void;
/**
* `expect.objectContaining(object)` matches any received object that recursively matches the expected properties.
* That is, the expected object is not a subset of the received object. Therefore, it matches a received object
* which contains properties that are not in the expected object.
*
* @example
* ```ts
* import { expect } from "@std/expect";
*
* Deno.test("example", () => {
* expect({ bar: 'baz' }).toEqual(expect.objectContaining({ bar: 'bar'}));
* expect({ bar: 'baz' }).not.toEqual(expect.objectContaining({ foo: 'bar'}));
* });
* ```
*/
expect.objectContaining = asymmetricMatchers.objectContaining as (
obj: Record<string, unknown>,
) => ReturnType<typeof asymmetricMatchers.objectContaining>;

View File

@ -54,6 +54,7 @@
* - {@linkcode expect.anything} * - {@linkcode expect.anything}
* - {@linkcode expect.any} * - {@linkcode expect.any}
* - {@linkcode expect.arrayContaining} * - {@linkcode expect.arrayContaining}
* - {@linkcode expect.objectContaining}
* - {@linkcode expect.closeTo} * - {@linkcode expect.closeTo}
* - {@linkcode expect.stringContaining} * - {@linkcode expect.stringContaining}
* - {@linkcode expect.stringMatching} * - {@linkcode expect.stringMatching}
@ -69,7 +70,6 @@
* - `toThrowErrorMatchingSnapshot` * - `toThrowErrorMatchingSnapshot`
* - `toThrowErrorMatchingInlineSnapshot` * - `toThrowErrorMatchingInlineSnapshot`
* - Asymmetric matchers: * - Asymmetric matchers:
* - `expect.objectContaining`
* - `expect.not.objectContaining` * - `expect.not.objectContaining`
* - Utilities: * - Utilities:
* - `expect.assertions` * - `expect.assertions`