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

Use iterableEquality in spy matchers #3651

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,45 @@ Expected mock function to have been last called with:
But it was <red>not called</>."
`;

exports[`lastCalledWith works with Immutable.js objects 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).not.lastCalledWith(<green>expected</><dim>)

Expected mock function to not have been last called with:
<green>[Immutable.Map {a: {\\"b\\": \\"c\\"}}, Immutable.Map {a: {\\"b\\": \\"c\\"}}]</>"
`;

exports[`lastCalledWith works with Map 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).not.lastCalledWith(<green>expected</><dim>)

Expected mock function to not have been last called with:
<green>[Map {1 => 2, 2 => 1}]</>"
`;

exports[`lastCalledWith works with Map 2`] = `
"<dim>expect(<red>jest.fn()</><dim>).lastCalledWith(<green>expected</><dim>)

Expected mock function to have been last called with:
<green>[Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}]</>
But it was last called with:
<red>[Map {1 => 2, 2 => 1}]</>"
`;

exports[`lastCalledWith works with Set 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).not.lastCalledWith(<green>expected</><dim>)

Expected mock function to not have been last called with:
<green>[Set {1, 2}]</>"
`;

exports[`lastCalledWith works with Set 2`] = `
"<dim>expect(<red>jest.fn()</><dim>).lastCalledWith(<green>expected</><dim>)

Expected mock function to have been last called with:
<green>[Set {3, 4}]</>
But it was last called with:
<red>[Set {1, 2}]</>"
`;

exports[`lastCalledWith works with arguments that don't match 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).lastCalledWith(<green>expected</><dim>)

Expand Down Expand Up @@ -205,6 +244,45 @@ Expected mock function to have been called with:
But it was <red>not called</>."
`;

exports[`toHaveBeenCalledWith works with Immutable.js objects 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).not.toHaveBeenCalledWith(<green>expected</><dim>)

Expected mock function not to have been called with:
<green>[Immutable.Map {a: {\\"b\\": \\"c\\"}}, Immutable.Map {a: {\\"b\\": \\"c\\"}}]</>"
`;

exports[`toHaveBeenCalledWith works with Map 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).not.toHaveBeenCalledWith(<green>expected</><dim>)

Expected mock function not to have been called with:
<green>[Map {1 => 2, 2 => 1}]</>"
`;

exports[`toHaveBeenCalledWith works with Map 2`] = `
"<dim>expect(<red>jest.fn()</><dim>).toHaveBeenCalledWith(<green>expected</><dim>)

Expected mock function to have been called with:
<green>[Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}]</>
But it was called with:
<red>[Map {1 => 2, 2 => 1}]</>"
`;

exports[`toHaveBeenCalledWith works with Set 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).not.toHaveBeenCalledWith(<green>expected</><dim>)

Expected mock function not to have been called with:
<green>[Set {1, 2}]</>"
`;

exports[`toHaveBeenCalledWith works with Set 2`] = `
"<dim>expect(<red>jest.fn()</><dim>).toHaveBeenCalledWith(<green>expected</><dim>)

Expected mock function to have been called with:
<green>[Set {3, 4}]</>
But it was called with:
<red>[Set {1, 2}]</>"
`;

exports[`toHaveBeenCalledWith works with arguments that don't match 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).toHaveBeenCalledWith(<green>expected</><dim>)

Expand Down Expand Up @@ -253,6 +331,45 @@ Expected mock function to have been last called with:
But it was <red>not called</>."
`;

exports[`toHaveBeenLastCalledWith works with Immutable.js objects 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).not.toHaveBeenLastCalledWith(<green>expected</><dim>)

Expected mock function to not have been last called with:
<green>[Immutable.Map {a: {\\"b\\": \\"c\\"}}, Immutable.Map {a: {\\"b\\": \\"c\\"}}]</>"
`;

exports[`toHaveBeenLastCalledWith works with Map 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).not.toHaveBeenLastCalledWith(<green>expected</><dim>)

Expected mock function to not have been last called with:
<green>[Map {1 => 2, 2 => 1}]</>"
`;

exports[`toHaveBeenLastCalledWith works with Map 2`] = `
"<dim>expect(<red>jest.fn()</><dim>).toHaveBeenLastCalledWith(<green>expected</><dim>)

Expected mock function to have been last called with:
<green>[Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}]</>
But it was last called with:
<red>[Map {1 => 2, 2 => 1}]</>"
`;

exports[`toHaveBeenLastCalledWith works with Set 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).not.toHaveBeenLastCalledWith(<green>expected</><dim>)

Expected mock function to not have been last called with:
<green>[Set {1, 2}]</>"
`;

exports[`toHaveBeenLastCalledWith works with Set 2`] = `
"<dim>expect(<red>jest.fn()</><dim>).toHaveBeenLastCalledWith(<green>expected</><dim>)

Expected mock function to have been last called with:
<green>[Set {3, 4}]</>
But it was last called with:
<red>[Set {1, 2}]</>"
`;

exports[`toHaveBeenLastCalledWith works with arguments that don't match 1`] = `
"<dim>expect(<red>jest.fn()</><dim>).toHaveBeenLastCalledWith(<green>expected</><dim>)

Expand Down
50 changes: 50 additions & 0 deletions packages/jest-matchers/src/__tests__/spy_matchers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @emails oncall+jsinfra
*/

const Immutable = require('immutable');
const jestExpect = require('../');

['toHaveBeenCalled', 'toBeCalled'].forEach(called => {
Expand Down Expand Up @@ -167,4 +168,53 @@ describe('toHaveBeenCalledTimes', () => {
jestExpect(fn).not[calledWith]('foo', 'bar'),
).toThrowErrorMatchingSnapshot();
});

test(`${calledWith} works with Map`, () => {
const fn = jest.fn();

const m1 = new Map([[1, 2], [2, 1]]);
const m2 = new Map([[1, 2], [2, 1]]);
const m3 = new Map([['a', 'b'], ['b', 'a']]);

fn(m1);

jestExpect(fn)[calledWith](m2);
jestExpect(fn).not[calledWith](m3);

expect(() =>
jestExpect(fn).not[calledWith](m2),
).toThrowErrorMatchingSnapshot();
expect(() => jestExpect(fn)[calledWith](m3)).toThrowErrorMatchingSnapshot();
});

test(`${calledWith} works with Set`, () => {
const fn = jest.fn();

const s1 = new Set([1, 2]);
const s2 = new Set([1, 2]);
const s3 = new Set([3, 4]);

fn(s1);

jestExpect(fn)[calledWith](s2);
jestExpect(fn).not[calledWith](s3);

expect(() =>
jestExpect(fn).not[calledWith](s2),
).toThrowErrorMatchingSnapshot();
expect(() => jestExpect(fn)[calledWith](s3)).toThrowErrorMatchingSnapshot();
});

test(`${calledWith} works with Immutable.js objects`, () => {
const fn = jest.fn();
const directlyCreated = new Immutable.Map([['a', {b: 'c'}]]);
const indirectlyCreated = new Immutable.Map().set('a', {b: 'c'});
fn(directlyCreated, indirectlyCreated);

jestExpect(fn)[calledWith](indirectlyCreated, directlyCreated);

expect(() =>
jestExpect(fn).not[calledWith](indirectlyCreated, directlyCreated),
).toThrowErrorMatchingSnapshot();
});
});
37 changes: 6 additions & 31 deletions packages/jest-matchers/src/matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ import {
printExpected,
printWithType,
} from 'jest-matcher-utils';
import {getObjectSubset, getPath, hasOwnProperty} from './utils';
import {
getObjectSubset,
getPath,
hasOwnProperty,
iterableEquality,
} from './utils';
import {equals} from './jasmine_utils';

type ContainIterable =
Expand All @@ -33,36 +38,6 @@ type ContainIterable =
| DOMTokenList
| HTMLCollection<any>;

const IteratorSymbol = Symbol.iterator;

const hasIterator = object => !!(object != null && object[IteratorSymbol]);
const iterableEquality = (a, b) => {
if (
typeof a !== 'object' ||
typeof b !== 'object' ||
Array.isArray(a) ||
Array.isArray(b) ||
!hasIterator(a) ||
!hasIterator(b)
) {
return undefined;
}
if (a.constructor !== b.constructor) {
return false;
}
const bIterator = b[IteratorSymbol]();

for (const aValue of a) {
const nextB = bIterator.next();
if (nextB.done || !equals(aValue, nextB.value, [iterableEquality])) {
return false;
}
}
if (!bIterator.next().done) {
return false;
}
return true;
};
const isObjectWithKeys = a =>
a !== null &&
typeof a === 'object' &&
Expand Down
5 changes: 3 additions & 2 deletions packages/jest-matchers/src/spy_matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
RECEIVED_COLOR,
} from 'jest-matcher-utils';
import {equals} from './jasmine_utils';
import {iterableEquality} from './utils';

const RECEIVED_NAME = {
'mock function': 'jest.fn()',
Expand Down Expand Up @@ -68,7 +69,7 @@ const createToBeCalledWithMatcher = matcherName => (
const calls = receivedIsSpy
? received.calls.all().map(x => x.args)
: received.mock.calls;
const pass = calls.some(call => equals(call, expected));
const pass = calls.some(call => equals(call, expected, [iterableEquality]));

const message = pass
? () =>
Expand Down Expand Up @@ -97,7 +98,7 @@ const createLastCalledWithMatcher = matcherName => (
const calls = receivedIsSpy
? received.calls.all().map(x => x.args)
: received.mock.calls;
const pass = equals(calls[calls.length - 1], expected);
const pass = equals(calls[calls.length - 1], expected, [iterableEquality]);

const message = pass
? () =>
Expand Down
34 changes: 34 additions & 0 deletions packages/jest-matchers/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* @flow
*/

import {equals} from './jasmine_utils';

type GetPath = {
hasEndProp?: boolean,
lastTraversedObject: ?Object,
Expand Down Expand Up @@ -91,8 +93,40 @@ const getObjectSubset = (object: Object, subset: Object) => {
return object;
};

const IteratorSymbol = Symbol.iterator;

const hasIterator = object => !!(object != null && object[IteratorSymbol]);
const iterableEquality = (a: any, b: any) => {
if (
typeof a !== 'object' ||
typeof b !== 'object' ||
Array.isArray(a) ||
Array.isArray(b) ||
!hasIterator(a) ||
!hasIterator(b)
) {
return undefined;
}
if (a.constructor !== b.constructor) {
return false;
}
const bIterator = b[IteratorSymbol]();

for (const aValue of a) {
const nextB = bIterator.next();
if (nextB.done || !equals(aValue, nextB.value, [iterableEquality])) {
return false;
}
}
if (!bIterator.next().done) {
return false;
}
return true;
};

module.exports = {
getObjectSubset,
getPath,
hasOwnProperty,
iterableEquality,
};