Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support custom equality testers #705

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/great-pants-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'jest-extended': patch
---

Support custom equality testers
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"eslint-plugin-jest": "^27.0.0",
"eslint-plugin-prettier": "^4.0.0",
"husky": "^8.0.0",
"jest": "^29.0.0",
"jest": "^29.4.0",
"jest-serializer-ansi-escapes": "^2.0.1",
"jest-watch-typeahead": "^2.0.0",
"lint-staged": "~13.2.0",
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/toBeEmpty.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export function toBeEmpty(actual) {
const { printReceived, matcherHint } = this.utils;

const pass = this.equals({}, actual) || isEmptyIterable(actual);
const pass = this.equals({}, actual, this.customTesters) || isEmptyIterable(actual);

return {
pass,
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/toBeOneOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { contains } from '../utils';
export function toBeOneOf(actual, expected) {
const { printReceived, printExpected, matcherHint } = this.utils;

const pass = contains(this.equals, expected, actual);
const pass = contains((a, b) => this.equals(a, b, this.customTesters), expected, actual);

return {
pass,
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/toContainAllEntries.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function toContainAllEntries(actual, expected) {
const pass =
actual.hasOwnProperty &&
expected.length == Object.keys(actual).length &&
expected.every(entry => containsEntry(this.equals, actual, entry));
expected.every(entry => containsEntry((a, b) => this.equals(a, b, this.customTesters), actual, entry));

return {
pass,
Expand Down
4 changes: 3 additions & 1 deletion src/matchers/toContainAllKeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ export function toContainAllKeys(actual, expected) {
const { printExpected, printReceived, matcherHint } = this.utils;

const objectKeys = Object.keys(actual);
const pass = objectKeys.length === expected.length && expected.every(key => contains(this.equals, objectKeys, key));
const pass =
objectKeys.length === expected.length &&
expected.every(key => contains((a, b) => this.equals(a, b, this.customTesters), objectKeys, key));

return {
pass,
Expand Down
3 changes: 2 additions & 1 deletion src/matchers/toContainAllValues.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export function toContainAllValues(actual, expected) {

const values = Object.keys(actual).map(k => actual[k]);
const pass =
values.length === expected.length && values.every(objectValue => contains(this.equals, expected, objectValue));
values.length === expected.length &&
values.every(objectValue => contains((a, b) => this.equals(a, b, this.customTesters), expected, objectValue));

return {
pass,
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/toContainAnyEntries.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function toContainAnyEntries(actual, expected) {
const { printReceived, printExpected, matcherHint } = this.utils;

const entries = Object.keys(actual).map(k => [k, actual[k]]);
const pass = expected.some(entry => contains(this.equals, entries, entry));
const pass = expected.some(entry => contains((a, b) => this.equals(a, b, this.customTesters), entries, entry));

return {
pass,
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/toContainAnyValues.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function toContainAnyValues(actual, expected) {
const { printReceived, printExpected, matcherHint } = this.utils;

const objectValues = Object.keys(actual).map(k => actual[k]);
const pass = expected.some(value => contains(this.equals, objectValues, value));
const pass = expected.some(value => contains((a, b) => this.equals(a, b, this.customTesters), objectValues, value));

return {
pass,
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/toContainEntries.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { containsEntry } from '../utils';
export function toContainEntries(actual, expected) {
const { printReceived, printExpected, matcherHint } = this.utils;

const pass = expected.every(entry => containsEntry(this.equals, actual, entry));
const pass = expected.every(entry => containsEntry((a, b) => this.equals(a, b, this.customTesters), actual, entry));

return {
pass,
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/toContainEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { containsEntry } from '../utils';
export function toContainEntry(actual, expected) {
const { printReceived, printExpected, matcherHint } = this.utils;

const pass = containsEntry(this.equals, actual, expected);
const pass = containsEntry((a, b) => this.equals(a, b, this.customTesters), actual, expected);

return {
pass,
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/toContainValue.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function toContainValue(actual, expected) {
const { printReceived, printExpected, matcherHint } = this.utils;

const values = Object.keys(actual).map(k => actual[k]);
const pass = contains(this.equals, values, expected);
const pass = contains((a, b) => this.equals(a, b, this.customTesters), values, expected);

return {
pass,
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/toContainValues.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function toContainValues(actual, expected) {
const { printReceived, printExpected, matcherHint } = this.utils;

const values = Object.keys(actual).map(k => actual[k]);
const pass = expected.every(value => contains(this.equals, values, value));
const pass = expected.every(value => contains((a, b) => this.equals(a, b, this.customTesters), values, value));

return {
pass,
Expand Down
4 changes: 3 additions & 1 deletion src/matchers/toIncludeAllMembers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ export function toIncludeAllMembers(actual, expected) {
const { printReceived, printExpected, matcherHint } = this.utils;

const pass =
Array.isArray(actual) && Array.isArray(expected) && expected.every(val => contains(this.equals, actual, val));
Array.isArray(actual) &&
Array.isArray(expected) &&
expected.every(val => contains((a, b) => this.equals(a, b, this.customTesters), actual, val));

return {
pass,
Expand Down
6 changes: 5 additions & 1 deletion src/matchers/toIncludeAllPartialMembers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ export function toIncludeAllPartialMembers(actual, expected) {
Array.isArray(actual) &&
Array.isArray(expected) &&
expected.every(partial =>
actual.some(value => Object.entries(partial).every(entry => containsEntry(this.equals, value, entry))),
actual.some(value =>
Object.entries(partial).every(entry =>
containsEntry((a, b) => this.equals(a, b, this.customTesters), value, entry),
),
),
);

return {
Expand Down
4 changes: 3 additions & 1 deletion src/matchers/toIncludeAnyMembers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ export function toIncludeAnyMembers(actual, expected) {
const { printReceived, printExpected, matcherHint } = this.utils;

const pass =
Array.isArray(actual) && Array.isArray(expected) && expected.some(member => contains(this.equals, actual, member));
Array.isArray(actual) &&
Array.isArray(expected) &&
expected.some(member => contains((a, b) => this.equals(a, b, this.customTesters), actual, member));

return {
pass,
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/toIncludeSameMembers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export function toIncludeSameMembers(actual, expected) {
const { printReceived, printExpected, matcherHint } = this.utils;

const pass = predicate(this.equals, actual, expected);
const pass = predicate((a, b) => this.equals(a, b, this.customTesters), actual, expected);

return {
pass,
Expand Down
6 changes: 5 additions & 1 deletion src/matchers/toPartiallyContain.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ export function toPartiallyContain(actual, expected) {
Array.isArray(actual) &&
Array.isArray([expected]) &&
[expected].every(partial =>
actual.some(value => Object.entries(partial).every(entry => containsEntry(this.equals, value, entry))),
actual.some(value =>
Object.entries(partial).every(entry =>
containsEntry((a, b) => this.equals(a, b, this.customTesters), value, entry),
),
),
);

return {
Expand Down
7 changes: 7 additions & 0 deletions test/matchers/__snapshots__/toBeEmpty.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ exports[`.toBeEmpty fails when given non-empty string 1`] = `
Expected value to be empty received:
<red>"string"</color>"
`;

exports[`toBeEmpty with custom equality tester fails when custom equality does not match empty object 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toBeEmpty()</intensity>

Expected value to be empty received:
<red>{}</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toBeOneOf.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ Expected value to be in list:
Received:
<red>4</color>"
`;

exports[`toBeOneOf with custom equality tester fails when custom equality does not match any array element 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toBeOneOf(</intensity><green>expected</color><dim>)</intensity>

Expected value to be in list:
<green>[1, 2, 3]</color>
Received:
<red>1</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toContainAllEntries.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ Expected object to only contain all of the given entries:
Received:
<red>{"a": "foo", "b": "bar", "c": "baz"}</color>"
`;

exports[`toContainAllEntries with custom equality tester fails when custom equality returns false on one of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toContainAllEntries(</intensity><green>expected</color><dim>)</intensity>

Expected object to only contain all of the given entries:
<green>[["a", "foo"], ["b", "bar"], ["c", "baz"]]</color>
Received:
<red>{"a": "foo", "b": "bar", "c": "baz"}</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toContainAllKeys.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ Expected object to contain all keys:
Received:
<red>["a", "b"]</color>"
`;

exports[`toContainAllKeys with custom equality tester fails when custom equality does not match one of the keys 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toContainAllKeys(</intensity><green>expected</color><dim>)</intensity>

Expected object to contain all keys:
<green>["a", "b"]</color>
Received:
<red>["a", "b"]</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toContainAnyEntries.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ Expected object to contain any of the provided entries:
Received:
<red>{"a": "foo", "b": "bar", "c": "baz"}</color>"
`;

exports[`toContainAnyEntries with custom equality tester fails when custom equality does not match any of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toContainAnyEntries(</intensity><green>expected</color><dim>)</intensity>

Expected object to contain any of the provided entries:
<green>[["a", "foo"]]</color>
Received:
<red>{"a": "foo", "b": "bar", "c": "baz"}</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toContainAnyValues.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,12 @@ Expected object to contain any of the following values:
Received:
<red>{"a": "foo", "b": "bar", "c": "baz"}</color>"
`;

exports[`toContainAnyValues with custom equality tester fails when custom equality does not match any of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toContainAnyValues(</intensity><green>expected</color><dim>)</intensity>

Expected object to contain any of the following values:
<green>["bar"]</color>
Received:
<red>{"a": "foo", "b": "bar", "c": "baz"}</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toContainEntries.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ Expected object to contain all of the given entries:
Received:
<red>{"a": "foo", "b": "bar", "c": "baz"}</color>"
`;

exports[`toContainEntries with custom equality tester fails when custom equality does not match any of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toContainEntries(</intensity><green>expected</color><dim>)</intensity>

Expected object to contain all of the given entries:
<green>[["a", "foo"]]</color>
Received:
<red>{"a": "foo", "b": "bar", "c": "baz"}</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toContainEntry.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ Expected object to contain entry:
Received:
<red>{"a": "foo", "b": "bar", "c": "baz"}</color>"
`;

exports[`toContainEntry with custom equality tester fails when custom equality does not match any of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toContainEntry(</intensity><green>expected</color><dim>)</intensity>

Expected object to contain entry:
<green>["a", "foo"]</color>
Received:
<red>{"a": "foo", "b": "bar", "c": "baz"}</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toContainValue.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,12 @@ Expected object to contain value:
Received:
<red>{"hello": "world"}</color>"
`;

exports[`toContainValue with custom equality tester fails when custom equality does not match any of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toContainValue(</intensity><green>expected</color><dim>)</intensity>

Expected object to contain value:
<green>"world"</color>
Received:
<red>{"hello": "world"}</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toContainValues.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,12 @@ Expected object to contain all values:
Received:
<red>{"donald": "duck", "message": {"bar": false, "foo": 0, "hello": "world"}}</color>"
`;

exports[`toContainValues with custom equality tester fails when custom equality does not match any of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toContainValues(</intensity><green>expected</color><dim>)</intensity>

Expected object to contain all values:
<green>["world"]</color>
Received:
<red>{"bar": false, "foo": 0, "hello": "world"}</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toIncludeAllMembers.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ Expected list to have all of the following members:
Received:
<red>2</color>"
`;

exports[`toIncludeAllMembers with custom equality tester fails when custom equality does not match any of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toIncludeAllMembers(</intensity><green>expected</color><dim>)</intensity>

Expected list to have all of the following members:
<green>[1]</color>
Received:
<red>[1]</color>"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ Expected list to have all of the following partial members:
Received:
<red>1</color>"
`;

exports[`toIncludeAllPartialMembers with custom equality tester fails when custom equality does not match any of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toIncludeAllPartialMembers(</intensity><green>expected</color><dim>)</intensity>

Expected list to have all of the following partial members:
<green>[{"foo": "bar"}]</color>
Received:
<red>[{"foo": "bar"}]</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toIncludeAnyMembers.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,12 @@ Expected list to include any of the following members:
Received:
<red>7</color>"
`;

exports[`toIncludeAnyMembers with custom equality tester fails when custom equality does not match any of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toIncludeAnyMembers(</intensity><green>expected</color><dim>)</intensity>

Expected list to include any of the following members:
<green>[1]</color>
Received:
<red>[1]</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toIncludeSameMembers.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ Expected list to have the following members and no more:
Received:
<red>[1, 2]</color>"
`;

exports[`toIncludeSameMembers with custom equality tester fails when custom equality does not match any of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toIncludeSameMembers(</intensity><green>expected</color><dim>)</intensity>

Expected list to have the following members and no more:
<green>[1]</color>
Received:
<red>[1]</color>"
`;
9 changes: 9 additions & 0 deletions test/matchers/__snapshots__/toPartiallyContain.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ Expected array to partially contain:
Received:
<red>[{"a": 1, "b": 2}]</color>"
`;

exports[`.toPartiallyContain with custom equality tester fails when custom equality does not match any of the values 1`] = `
"<dim>expect(</intensity><red>received</color><dim>).toPartiallyContain(</intensity><green>expected</color><dim>)</intensity>

Expected array to partially contain:
<green>{"baz": "qux", "foo": "bar"}</color>
Received:
<red>[{"baz": "qux", "foo": "bar"}]</color>"
`;
20 changes: 20 additions & 0 deletions test/matchers/toBeEmpty.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,23 @@ describe('.not.toBeEmpty', () => {
expect(() => expect('').not.toBeEmpty()).toThrowErrorMatchingSnapshot();
});
});

// Note - custom equality tester must be at the end of the file because once we add it, it cannot be removed
describe('toBeEmpty with custom equality tester', () => {
let mockEqualityTester;
beforeAll(() => {
mockEqualityTester = jest.fn();
expect.addEqualityTesters([mockEqualityTester]);
});
afterEach(() => {
mockEqualityTester.mockReset();
});
test('passes when custom equality matches empty object', () => {
mockEqualityTester.mockReturnValueOnce(true);
expect('a').toBeEmpty();
});
test('fails when custom equality does not match empty object', () => {
mockEqualityTester.mockReturnValueOnce(false);
expect(() => expect({}).toBeEmpty()).toThrowErrorMatchingSnapshot();
});
});
Loading