diff --git a/packages/react-native-fantom/runtime/expect.js b/packages/react-native-fantom/runtime/expect.js index 583db433e60..e25cb1080cf 100644 --- a/packages/react-native-fantom/runtime/expect.js +++ b/packages/react-native-fantom/runtime/expect.js @@ -288,6 +288,27 @@ class Expect { } } + nthCalledWith: (index: number, ...args: mixed[]) => void; + toHaveBeenNthCalledWith(index: number, ...args: mixed[]): void { + if (index < 1) { + throw new ErrorWithCustomBlame( + `Expected index to be positive number, got ${index}.`, + ).blameToPreviousFrame(); + } + + const mock = this.#requireMock(); + if (this.#isNot && mock.calls.length < index) { + return; + } + + const pass = deepEqual(mock.calls[index - 1], args, {strict: true}); + if (!this.#isExpectedResult(pass)) { + throw new ErrorWithCustomBlame( + `Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been nth(${index}) called with ${stringify(args)}, but it was called with ${stringify(mock.calls[index - 1])}.`, + ); + } + } + toBeGreaterThan(expected: number): void { if (typeof this.#received !== 'number') { throw new ErrorWithCustomBlame( @@ -489,6 +510,8 @@ Expect.prototype.toBeCalledTimes = Expect.prototype.toHaveBeenCalledTimes; Expect.prototype.toBeCalledWith = Expect.prototype.toHaveBeenCalledWith; // $FlowExpectedError[method-unbinding] Expect.prototype.lastCalledWith = Expect.prototype.toHaveBeenLastCalledWith; +// $FlowExpectedError[method-unbinding] +Expect.prototype.nthCalledWith = Expect.prototype.toHaveBeenNthCalledWith; const expect: mixed => Expect = (received: mixed) => new Expect(received); diff --git a/packages/react-native-fantom/src/__tests__/expect-itest.js b/packages/react-native-fantom/src/__tests__/expect-itest.js index 2c2f55fe8f4..e8e9dd38c5a 100644 --- a/packages/react-native-fantom/src/__tests__/expect-itest.js +++ b/packages/react-native-fantom/src/__tests__/expect-itest.js @@ -405,6 +405,53 @@ describe('expect', () => { }), ); + ['nthCalledWith', 'toHaveBeenNthCalledWith'].map( + toHaveBeenNthCalledWithAlias => + test(toHaveBeenNthCalledWithAlias, () => { + const fn = jest.fn(); + + expect(fn).not[toHaveBeenNthCalledWithAlias](1); + expect(fn).not[toHaveBeenNthCalledWithAlias](1, {}); + + expect(() => { + expect(fn)[toHaveBeenNthCalledWithAlias](0); + }).toThrow(); + + expect(() => { + expect(fn)[toHaveBeenNthCalledWithAlias](1); + }).toThrow(); + + fn('happy'); + fn(); + fn({a: 1}, 2); + + expect(fn)[toHaveBeenNthCalledWithAlias](1, 'happy'); + expect(fn)[toHaveBeenNthCalledWithAlias](2); + expect(fn)[toHaveBeenNthCalledWithAlias](3, {a: 1}, 2); + expect(fn).not[toHaveBeenNthCalledWithAlias](1); + expect(fn).not[toHaveBeenNthCalledWithAlias](3, {a: 1}); + expect(fn).not[toHaveBeenNthCalledWithAlias](3, {a: 2}, 2); + expect(fn).not[toHaveBeenNthCalledWithAlias](3, {a: 1}, 2, undefined); + + expect(() => { + expect(fn).not[toHaveBeenNthCalledWithAlias](3, {a: 1}, 2); + }).toThrow(); + + expect(() => { + expect(fn)[toHaveBeenNthCalledWithAlias](1); + }).toThrow(); + + // Passing functions that aren't mocks should always fail + expect(() => { + expect(() => {})[toHaveBeenNthCalledWithAlias](1); + }).toThrow(); + + expect(() => { + expect(() => {}).not[toHaveBeenNthCalledWithAlias](1); + }).toThrow(); + }), + ); + describe('jest.fn()', () => { it('tracks execution of functions without implementations', () => { const fn = jest.fn();