Skip to content

Commit

Permalink
fix(expect): support expect.objectContaining (denoland#6065)
Browse files Browse the repository at this point in the history
Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
  • Loading branch information
eryue0220 and kt3k authored Oct 21, 2024
1 parent a541fb4 commit ae7048f
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 1 deletion.
30 changes: 30 additions & 0 deletions expect/_asymmetric_matchers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// deno-lint-ignore-file no-explicit-any

import { equal } from "./_equal.ts";

export abstract class AsymmetricMatcher<T> {
constructor(
protected value: T,
Expand Down Expand Up @@ -145,3 +147,31 @@ export class StringMatching extends AsymmetricMatcher<RegExp> {
export function stringMatching(pattern: string | RegExp): StringMatching {
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);
}
50 changes: 50 additions & 0 deletions expect/_object_containing_test.ts
Original file line number Diff line number Diff line change
@@ -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]) }),
);
});
18 changes: 18 additions & 0 deletions expect/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,3 +509,21 @@ expect.stringMatching = asymmetricMatchers.stringMatching as (
* ```
*/
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>;
2 changes: 1 addition & 1 deletion expect/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
* - {@linkcode expect.anything}
* - {@linkcode expect.any}
* - {@linkcode expect.arrayContaining}
* - {@linkcode expect.objectContaining}
* - {@linkcode expect.closeTo}
* - {@linkcode expect.stringContaining}
* - {@linkcode expect.stringMatching}
Expand All @@ -69,7 +70,6 @@
* - `toThrowErrorMatchingSnapshot`
* - `toThrowErrorMatchingInlineSnapshot`
* - Asymmetric matchers:
* - `expect.objectContaining`
* - `expect.not.objectContaining`
* - Utilities:
* - `expect.assertions`
Expand Down

0 comments on commit ae7048f

Please sign in to comment.