diff --git a/packages/jest-matchers/src/__tests__/__snapshots__/spy_matchers.test.js.snap b/packages/jest-matchers/src/__tests__/__snapshots__/spy_matchers.test.js.snap index 3359d0eba015..548fea9d77a3 100644 --- a/packages/jest-matchers/src/__tests__/__snapshots__/spy_matchers.test.js.snap +++ b/packages/jest-matchers/src/__tests__/__snapshots__/spy_matchers.test.js.snap @@ -16,6 +16,45 @@ Expected mock function to have been last called with: But it was not called." `; +exports[`lastCalledWith works with Immutable.js objects 1`] = ` +"expect(jest.fn()).not.lastCalledWith(expected) + +Expected mock function to not have been last called with: + [Immutable.Map {a: {\\"b\\": \\"c\\"}}, Immutable.Map {a: {\\"b\\": \\"c\\"}}]" +`; + +exports[`lastCalledWith works with Map 1`] = ` +"expect(jest.fn()).not.lastCalledWith(expected) + +Expected mock function to not have been last called with: + [Map {1 => 2, 2 => 1}]" +`; + +exports[`lastCalledWith works with Map 2`] = ` +"expect(jest.fn()).lastCalledWith(expected) + +Expected mock function to have been last called with: + [Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}] +But it was last called with: + [Map {1 => 2, 2 => 1}]" +`; + +exports[`lastCalledWith works with Set 1`] = ` +"expect(jest.fn()).not.lastCalledWith(expected) + +Expected mock function to not have been last called with: + [Set {1, 2}]" +`; + +exports[`lastCalledWith works with Set 2`] = ` +"expect(jest.fn()).lastCalledWith(expected) + +Expected mock function to have been last called with: + [Set {3, 4}] +But it was last called with: + [Set {1, 2}]" +`; + exports[`lastCalledWith works with arguments that don't match 1`] = ` "expect(jest.fn()).lastCalledWith(expected) @@ -205,6 +244,45 @@ Expected mock function to have been called with: But it was not called." `; +exports[`toHaveBeenCalledWith works with Immutable.js objects 1`] = ` +"expect(jest.fn()).not.toHaveBeenCalledWith(expected) + +Expected mock function not to have been called with: + [Immutable.Map {a: {\\"b\\": \\"c\\"}}, Immutable.Map {a: {\\"b\\": \\"c\\"}}]" +`; + +exports[`toHaveBeenCalledWith works with Map 1`] = ` +"expect(jest.fn()).not.toHaveBeenCalledWith(expected) + +Expected mock function not to have been called with: + [Map {1 => 2, 2 => 1}]" +`; + +exports[`toHaveBeenCalledWith works with Map 2`] = ` +"expect(jest.fn()).toHaveBeenCalledWith(expected) + +Expected mock function to have been called with: + [Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}] +But it was called with: + [Map {1 => 2, 2 => 1}]" +`; + +exports[`toHaveBeenCalledWith works with Set 1`] = ` +"expect(jest.fn()).not.toHaveBeenCalledWith(expected) + +Expected mock function not to have been called with: + [Set {1, 2}]" +`; + +exports[`toHaveBeenCalledWith works with Set 2`] = ` +"expect(jest.fn()).toHaveBeenCalledWith(expected) + +Expected mock function to have been called with: + [Set {3, 4}] +But it was called with: + [Set {1, 2}]" +`; + exports[`toHaveBeenCalledWith works with arguments that don't match 1`] = ` "expect(jest.fn()).toHaveBeenCalledWith(expected) @@ -253,6 +331,45 @@ Expected mock function to have been last called with: But it was not called." `; +exports[`toHaveBeenLastCalledWith works with Immutable.js objects 1`] = ` +"expect(jest.fn()).not.toHaveBeenLastCalledWith(expected) + +Expected mock function to not have been last called with: + [Immutable.Map {a: {\\"b\\": \\"c\\"}}, Immutable.Map {a: {\\"b\\": \\"c\\"}}]" +`; + +exports[`toHaveBeenLastCalledWith works with Map 1`] = ` +"expect(jest.fn()).not.toHaveBeenLastCalledWith(expected) + +Expected mock function to not have been last called with: + [Map {1 => 2, 2 => 1}]" +`; + +exports[`toHaveBeenLastCalledWith works with Map 2`] = ` +"expect(jest.fn()).toHaveBeenLastCalledWith(expected) + +Expected mock function to have been last called with: + [Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}] +But it was last called with: + [Map {1 => 2, 2 => 1}]" +`; + +exports[`toHaveBeenLastCalledWith works with Set 1`] = ` +"expect(jest.fn()).not.toHaveBeenLastCalledWith(expected) + +Expected mock function to not have been last called with: + [Set {1, 2}]" +`; + +exports[`toHaveBeenLastCalledWith works with Set 2`] = ` +"expect(jest.fn()).toHaveBeenLastCalledWith(expected) + +Expected mock function to have been last called with: + [Set {3, 4}] +But it was last called with: + [Set {1, 2}]" +`; + exports[`toHaveBeenLastCalledWith works with arguments that don't match 1`] = ` "expect(jest.fn()).toHaveBeenLastCalledWith(expected) diff --git a/packages/jest-matchers/src/__tests__/spy_matchers.test.js b/packages/jest-matchers/src/__tests__/spy_matchers.test.js index 8dd5da1ab50f..ac7adf91f5ec 100644 --- a/packages/jest-matchers/src/__tests__/spy_matchers.test.js +++ b/packages/jest-matchers/src/__tests__/spy_matchers.test.js @@ -8,6 +8,7 @@ * @emails oncall+jsinfra */ +const Immutable = require('immutable'); const jestExpect = require('../'); ['toHaveBeenCalled', 'toBeCalled'].forEach(called => { @@ -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(); + }); }); diff --git a/packages/jest-matchers/src/matchers.js b/packages/jest-matchers/src/matchers.js index 4116c0e453ba..3b8caa1e8e4f 100644 --- a/packages/jest-matchers/src/matchers.js +++ b/packages/jest-matchers/src/matchers.js @@ -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 = @@ -33,36 +38,6 @@ type ContainIterable = | DOMTokenList | HTMLCollection; -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' && diff --git a/packages/jest-matchers/src/spy_matchers.js b/packages/jest-matchers/src/spy_matchers.js index c1a68be217fc..f03247fa0753 100644 --- a/packages/jest-matchers/src/spy_matchers.js +++ b/packages/jest-matchers/src/spy_matchers.js @@ -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()', @@ -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 ? () => @@ -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 ? () => diff --git a/packages/jest-matchers/src/utils.js b/packages/jest-matchers/src/utils.js index ca09e73944eb..90fef8fb4168 100644 --- a/packages/jest-matchers/src/utils.js +++ b/packages/jest-matchers/src/utils.js @@ -8,6 +8,8 @@ * @flow */ +import {equals} from './jasmine_utils'; + type GetPath = { hasEndProp?: boolean, lastTraversedObject: ?Object, @@ -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, };