From e7790f5f431b7bcb1e7bb5e0ebd642ecf74ef1e2 Mon Sep 17 00:00:00 2001 From: Jose Luis Leon Date: Thu, 6 Aug 2026 22:55:57 -0500 Subject: [PATCH] fix(core): Array matcher missing deep equality --- packages/core/src/lib/ArrayAssertion.ts | 7 ++--- packages/core/src/lib/helpers/predicates.ts | 27 +++++++++++++++++++ packages/core/src/lib/helpers/types.ts | 8 ++++++ packages/core/test/lib/ArrayAssertion.test.ts | 24 ++++++++--------- 4 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 packages/core/src/lib/helpers/predicates.ts diff --git a/packages/core/src/lib/ArrayAssertion.ts b/packages/core/src/lib/ArrayAssertion.ts index b6cdd7c..b2a62c4 100644 --- a/packages/core/src/lib/ArrayAssertion.ts +++ b/packages/core/src/lib/ArrayAssertion.ts @@ -5,6 +5,7 @@ import isDeepEqual from "fast-deep-equal/es6"; import { Assertion } from "./Assertion"; import { UnsupportedOperationError } from "./errors/UnsupportedOperationError"; import { prettify } from "./helpers/messages"; +import { deepEquals } from "./helpers/predicates"; import type { Expect } from "./expect"; import type { TypeFactory } from "./helpers/TypeFactories"; @@ -233,7 +234,7 @@ export class ArrayAssertion extends Assertion { return this.execute({ assertWhen: this.actual.length === expected.length - && this.actual.every(value => expected.includes(value)), + && this.actual.every(item => expected.some(deepEquals(item))), error, invertedError, }); @@ -262,7 +263,7 @@ export class ArrayAssertion extends Assertion { }); return this.execute({ - assertWhen: values.every(value => this.actual.includes(value)), + assertWhen: values.every(value => this.actual.some(deepEquals(value))), error, invertedError, }); @@ -291,7 +292,7 @@ export class ArrayAssertion extends Assertion { }); return this.execute({ - assertWhen: values.some(value => this.actual.includes(value)), + assertWhen: values.some(value => this.actual.some(deepEquals(value))), error, invertedError, }); diff --git a/packages/core/src/lib/helpers/predicates.ts b/packages/core/src/lib/helpers/predicates.ts new file mode 100644 index 0000000..5da8b0e --- /dev/null +++ b/packages/core/src/lib/helpers/predicates.ts @@ -0,0 +1,27 @@ +import isDeepEqual from "fast-deep-equal/es6"; + +import type { Predicate } from "./types"; + +/** + * Creates a predicate function that checks if a given value is deeply equal + * to the provided `second` value. + * + * @param second - the value to compare against + * @returns a predicate function that takes a `first` value and returns `true` + * if it's deeply equal to `second`, `false` otherwise + */ +export function deepEquals(second: B): Predicate { + return first => isDeepEqual(first, second); +} + +/** + * Creates a predicate function that checks if a given value is NOT deeply + * equal to the provided `second` value. + * + * @param second - the value to compare against + * @returns a predicate function that takes a `first` value and returns `true` + * if it's NOT deeply equal to `second`, `false` otherwise + */ +export function notDeepEquals(second: B): Predicate { + return first => !isDeepEqual(first, second); +} diff --git a/packages/core/src/lib/helpers/types.ts b/packages/core/src/lib/helpers/types.ts index 0ee9db1..e268eb7 100644 --- a/packages/core/src/lib/helpers/types.ts +++ b/packages/core/src/lib/helpers/types.ts @@ -10,3 +10,11 @@ export type Struct = Record; * @param T an structured object which extends {@link Struct} */ export type Entry = { [K in keyof T]: [K, T[K]]; }[keyof T]; + +/** + * Utility type that represents a predicate function, i.e. a function that + * takes a value of type `` and returns a boolean. + * + * @param T the type of the value to be evaluated by the predicate + */ +export type Predicate = (value: T) => boolean; diff --git a/packages/core/test/lib/ArrayAssertion.test.ts b/packages/core/test/lib/ArrayAssertion.test.ts index 93ec7af..a3604bb 100644 --- a/packages/core/test/lib/ArrayAssertion.test.ts +++ b/packages/core/test/lib/ArrayAssertion.test.ts @@ -180,11 +180,11 @@ describe("[Unit] ArrayAssertion.test.ts", () => { describe(".toHaveSameMembers", () => { context("when the array has the same members as the passed array", () => { it("returns the assertion instance", () => { - const test = new ArrayAssertion([1, 2, 3]); + const test = new ArrayAssertion([1, { x: 2 }, "3"]); - assert.deepStrictEqual(test.toHaveSameMembers([2, 3, 1]), test); - assert.throws(() => test.not.toHaveSameMembers([2, 3, 1]), { - message: "Expected array NOT to have the same members as <[2,3,1]>", + assert.deepStrictEqual(test.toHaveSameMembers([{ x: 2 }, "3", 1]), test); + assert.throws(() => test.not.toHaveSameMembers([{ x: 2 }, "3", 1]), { + message: 'Expected array NOT to have the same members as <[{"x":2},"3",1]>', name: AssertionError.name, }); }); @@ -215,11 +215,11 @@ describe("[Unit] ArrayAssertion.test.ts", () => { describe(".toContainAll", () => { context("when the array contains the expected values", () => { it("returns the assertion instance", () => { - const test = new ArrayAssertion([1, 2, 3, 4, 5]); + const test = new ArrayAssertion([1, 2, { x: 3 }, 4, "5"]); - assert.deepStrictEqual(test.toContainAll(1, 3, 5), test); - assert.throws(() => test.not.toContainAll(1, 3, 5), { - message: "Expected array NOT to contain all the values <1, 3, 5>", + assert.deepStrictEqual(test.toContainAll(1, { x: 3 }, "5"), test); + assert.throws(() => test.not.toContainAll(1, { x: 3 }, "5"), { + message: 'Expected array NOT to contain all the values <1, {"x":3}, "5">', name: AssertionError.name, }); }); @@ -241,11 +241,11 @@ describe("[Unit] ArrayAssertion.test.ts", () => { describe(".toContainAny", () => { context("when the array contains one of the expected values", () => { it("returns the assertion instance", () => { - const test = new ArrayAssertion([1, 2, 3, 4, 5]); + const test = new ArrayAssertion([1, 2, { x: 3 }, 4, 5]); - assert.deepStrictEqual(test.toContainAny(7, 3, 8), test); - assert.throws(() => test.not.toContainAny(7, 3, 8), { - message: "Expected array NOT to contain any of the values <7, 3, 8>", + assert.deepStrictEqual(test.toContainAny(2, 7, { x: 3 }, 8), test); + assert.throws(() => test.not.toContainAny(2, 7, { x: 3 }, 8), { + message: 'Expected array NOT to contain any of the values <2, 7, {"x":3}, 8>', name: AssertionError.name, }); });