/**
 * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */ import { getType , isPrimitive } from './get-type' ; import { DIM_COLOR , EXPECTED_COLOR , MatcherHintOptions , RECEIVED_COLOR , diff , ensureExpectedIsNonNegativeInteger , ensureNoExpected , matcherErrorMessage , matcherHint , printExpected , printReceived , printWithType , stringify , } from './matcher-utils' ; import { equals } from './jasmineUtils' ; import type { MatcherState , MatchersObject , SyncExpectationResult , } from './types' ; import { iterableEquality } from './utils' ; const isExpand = ( expand ? ) : boolean => expand !== false ; const PRINT_LIMIT = 3 ; const NO_ARGUMENTS = 'called with 0 arguments' ; const printExpectedArgs = ( expected ) : string => expected . length === 0 ? NO_ARGUMENTS : expected . map ( ( arg ) => printExpected ( arg ) ) . join ( ', ' ) ; const printReceivedArgs = ( received , expected ? ) : string => received . length === 0 ? NO_ARGUMENTS : received . map ( ( arg , i ) => Array . isArray ( expected ) && i < expected . length && isEqualValue ( expected [ i ] , arg ) ? printCommon ( arg ) : printReceived ( arg ) ) . join ( ', ' ) ; const printCommon = ( val ) => DIM_COLOR ( stringify ( val ) ) ; const isEqualValue = ( expected , received ) : boolean => equals ( expected , received , [ iterableEquality ] ) ; const isEqualCall = ( expected , received ) : boolean => isEqualValue ( expected , received ) ; const isEqualReturn = ( expected , result ) : boolean => result . type === 'return' && isEqualValue ( expected , result . value ) ; const countReturns = ( results ) : number => results . reduce ( ( n , result ) => ( result . type === 'return' ? n + 1 : n ) , 0 ) ; const printNumberOfReturns = ( countReturns , countCalls ) : string => `\nNumber of returns: ${ printReceived ( countReturns ) }` + ( countCalls !== countReturns ? `\nNumber of calls:   ${ printReceived ( countCalls ) }` : '' ) ; type PrintLabel = ( string : string , isExpectedCall : boolean ) => string ; const getRightAlignedPrinter = ( label ) : PrintLabel => { const index = label . indexOf ( ':' ) ; const suffix = label . slice ( index ) ; return ( string , isExpectedCall ) => ( isExpectedCall ? '->' + ' ' . repeat ( Math . max ( 0 , index - 2 - string . length ) ) : ' ' . repeat ( Math . max ( index - string . length ) ) ) + string + suffix ; } ; type IndexedCall = [ number , Array < unknown > ] ; const printReceivedCallsNegative = ( expected , indexedCalls , isOnlyCall , iExpectedCall ? ) => { if ( indexedCalls . length === 0 ) { return '' ; } const label = 'Received:     ' ; if ( isOnlyCall ) { return label + printReceivedArgs ( indexedCalls [ 0 ] , expected ) + '\n' ; } const printAligned = getRightAlignedPrinter ( label ) ; return ( 'Received\n' + indexedCalls . reduce ( ( printed , [ i , args ] ) => printed + printAligned ( String ( i + 1 ) , i === iExpectedCall ) + printReceivedArgs ( args , expected ) + '\n' , '' ) ) ; } ; const printExpectedReceivedCallsPositive = ( expected , indexedCalls , expand , isOnlyCall , iExpectedCall ? ) => { const expectedLine = `Expected: ${ printExpectedArgs ( expected ) }\n` ; if ( indexedCalls . length === 0 ) { return expectedLine ; } const label = 'Received: ' ; if ( isOnlyCall && ( iExpectedCall === 0 || iExpectedCall === undefined ) ) { const received = indexedCalls [ 0 ] [ 1 ] ; if ( isLineDiffableCall ( expected , received ) ) { const lines = [ EXPECTED_COLOR ( '- Expected' ) , RECEIVED_COLOR ( '+ Received' ) , '' , ] ; const length = Math . max ( expected . length , received . length ) ; for ( let i = 0 ; i < length ; i += 1 ) { if ( i < expected . length && i < received . length ) { if ( isEqualValue ( expected [ i ] , received [ i ] ) ) { lines . push ( `  ${ printCommon ( received [ i ] ) },` ) ; continue ; } if ( isLineDiffableArg ( expected [ i ] , received [ i ] ) ) { const difference = diff ( expected [ i ] , received [ i ] , { expand , } ) ; if ( typeof difference === 'string' && difference . includes ( '- Expected' ) && difference . includes ( '+ Received' ) ) { lines . push ( difference . split ( '\n' ) . slice ( 3 ) . join ( '\n' ) + ',' ) ; continue ; } } } if ( i < expected . length ) { lines . push ( EXPECTED_COLOR ( '- ' + stringify ( expected [ i ] ) ) + ',' ) ; } if ( i < received . length ) { lines . push ( RECEIVED_COLOR ( '+ ' + stringify ( received [ i ] ) ) + ',' ) ; } } return lines . join ( '\n' ) + '\n' ; } return ( expectedLine + label + printReceivedArgs ( received , expected ) + '\n' ) ; } const printAligned = getRightAlignedPrinter ( label ) ; return ( expectedLine + 'Received\n' + indexedCalls . reduce ( ( printed , [ i , received ] ) => { const aligned = printAligned ( String ( i + 1 ) , i === iExpectedCall ) ; return ( printed + ( ( i === iExpectedCall || iExpectedCall === undefined ) && isLineDiffableCall ( expected , received ) ? aligned . replace ( ': ' , '\n' ) + printDiffCall ( expected , received , expand ) : aligned + printReceivedArgs ( received , expected ) ) + '\n' ) ; } , '' ) ) ; } ; const indentation = 'Received' . replace ( /\w/g , ' ' ) ; const printDiffCall = ( expected , received , expand ) => received . map ( ( arg , i ) => { if ( i < expected . length ) { if ( isEqualValue ( expected [ i ] , arg ) ) { return indentation + '  ' + printCommon ( arg ) + ',' ; } if ( isLineDiffableArg ( expected [ i ] , arg ) ) { const difference = diff ( expected [ i ] , arg , { expand } ) ; if ( typeof difference === 'string' && difference . includes ( '- Expected' ) && difference . includes ( '+ Received' ) ) { return ( difference . split ( '\n' ) . slice ( 3 ) . map ( ( line ) => indentation + line ) . join ( '\n' ) + ',' ) ; } } } return ( indentation + ( i < expected . length ? '  ' + printReceived ( arg ) : RECEIVED_COLOR ( '+ ' + stringify ( arg ) ) ) + ',' ) ; } ) . join ( '\n' ) ; const isLineDiffableCall = ( expected , received ) : boolean => expected . some ( ( arg , i ) => i < received . length && isLineDiffableArg ( arg , received [ i ] ) ) ; const isLineDiffableArg = ( expected , received ) : boolean => { const expectedType = getType ( expected ) ; const receivedType = getType ( received ) ; if ( expectedType !== receivedType ) { return false ; } if ( isPrimitive ( expected ) ) { return false ; } if ( expectedType === 'date' || expectedType === 'function' || expectedType === 'regexp' ) { return false ; } if ( expected instanceof Error && received instanceof Error ) { return false ; } if ( expectedType === 'object' && typeof ( expected as any ) . asymmetricMatch === 'function' ) { return false ; } if ( receivedType === 'object' && typeof ( received as any ) . asymmetricMatch === 'function' ) { return false ; } return true ; } ; const printResult = ( result , expected ) => result . type === 'throw' ? 'function call threw an error' : result . type === 'incomplete' ? 'function call has not returned yet' : isEqualValue ( expected , result . value ) ? printCommon ( result . value ) : printReceived ( result . value ) ; type IndexedResult = [ number , any ] ; const printReceivedResults = ( label , expected , indexedResults , isOnlyCall , iExpectedCall ? ) => { if ( indexedResults . length === 0 ) { return '' ; } if ( isOnlyCall && ( iExpectedCall === 0 || iExpectedCall === undefined ) ) { return label + printResult ( indexedResults [ 0 ] [ 1 ] , expected ) + '\n' ; } const printAligned = getRightAlignedPrinter ( label ) ; return ( label . replace ( ':' , '' ) . trim ( ) + '\n' + indexedResults . reduce ( ( printed , [ i , result ] ) => printed + printAligned ( String ( i + 1 ) , i === iExpectedCall ) + printResult ( result , expected ) + '\n' , '' ) ) ; } ; const createToBeCalledMatcher = ( matcherName ) => function ( this , received , expected ) : SyncExpectationResult { const expectedArgument = '' ; const options = { isNot : this . isNot , promise : this . promise , } ; ensureNoExpected ( expected , matcherName , options ) ; ensureMockOrSpy ( received , matcherName , expectedArgument , options ) ; const receivedIsSpy = isSpy ( received ) ; const receivedName = receivedIsSpy ? 'spy' : received . getMockName ( ) ; const count = receivedIsSpy ? received . calls . count ( ) : received . mock . calls . length ; const calls = receivedIsSpy ? received . calls . all ( ) . map ( ( x ) => x . args ) : received . mock . calls ; const pass = count > 0 ; const message = pass ? ( ) => matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected number of calls: ${ printExpected ( 0 ) }\n` + `Received number of calls: ${ printReceived ( count ) }\n\n` + calls . reduce ( ( lines , args , i ) => { if ( lines . length < PRINT_LIMIT ) { lines . push ( `${ i + 1 }: ${ printReceivedArgs ( args ) }` ) ; } return lines ; } , [ ] ) . join ( '\n' ) : ( ) => matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected number of calls: >= ${ printExpected ( 1 ) }\n` + `Received number of calls:    ${ printReceived ( count ) }` ; return { message , pass } ; } ; const createToReturnMatcher = ( matcherName ) => function ( this , received , expected ) : SyncExpectationResult { const expectedArgument = '' ; const options = { isNot : this . isNot , promise : this . promise , } ; ensureNoExpected ( expected , matcherName , options ) ; ensureMock ( received , matcherName , expectedArgument , options ) ; const receivedName = received . getMockName ( ) ; const count = received . mock . results . reduce ( ( n , result ) => ( result . type === 'return' ? n + 1 : n ) , 0 ) ; const pass = count > 0 ; const message = pass ? ( ) => matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected number of returns: ${ printExpected ( 0 ) }\n` + `Received number of returns: ${ printReceived ( count ) }\n\n` + received . mock . results . reduce ( ( lines , result , i ) => { if ( result . type === 'return' && lines . length < PRINT_LIMIT ) { lines . push ( `${ i + 1 }: ${ printReceived ( result . value ) }` ) ; } return lines ; } , [ ] ) . join ( '\n' ) + ( received . mock . calls . length !== count ? `\n\nReceived number of calls:   ${ printReceived ( received . mock . calls . length ) }` : '' ) : ( ) => matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected number of returns: >= ${ printExpected ( 1 ) }\n` + `Received number of returns:    ${ printReceived ( count ) }` + ( received . mock . calls . length !== count ? `\nReceived number of calls:      ${ printReceived ( received . mock . calls . length ) }` : '' ) ; return { message , pass } ; } ; const createToBeCalledTimesMatcher = ( matcherName ) => function ( this , received , expected ) : SyncExpectationResult { const expectedArgument = 'expected' ; const options = { isNot : this . isNot , promise : this . promise , } ; ensureExpectedIsNonNegativeInteger ( expected , matcherName , options ) ; ensureMockOrSpy ( received , matcherName , expectedArgument , options ) ; const receivedIsSpy = isSpy ( received ) ; const receivedName = receivedIsSpy ? 'spy' : received . getMockName ( ) ; const count = receivedIsSpy ? received . calls . count ( ) : received . mock . calls . length ; const pass = count === expected ; const message = pass ? ( ) => matcherHint ( matcherName , receivedName , expectedArgument , options ) + `\n\n` + `Expected number of calls: not ${ printExpected ( expected ) }` : ( ) => matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected number of calls: ${ printExpected ( expected ) }\n` + `Received number of calls: ${ printReceived ( count ) }` ; return { message , pass } ; } ; const createToReturnTimesMatcher = ( matcherName ) => function ( this , received , expected ) : SyncExpectationResult { const expectedArgument = 'expected' ; const options = { isNot : this . isNot , promise : this . promise , } ; ensureExpectedIsNonNegativeInteger ( expected , matcherName , options ) ; ensureMock ( received , matcherName , expectedArgument , options ) ; const receivedName = received . getMockName ( ) ; const count = received . mock . results . reduce ( ( n , result ) => ( result . type === 'return' ? n + 1 : n ) , 0 ) ; const pass = count === expected ; const message = pass ? ( ) => matcherHint ( matcherName , receivedName , expectedArgument , options ) + `\n\n` + `Expected number of returns: not ${ printExpected ( expected ) }` + ( received . mock . calls . length !== count ? `\n\nReceived number of calls:       ${ printReceived ( received . mock . calls . length ) }` : '' ) : ( ) => matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected number of returns: ${ printExpected ( expected ) }\n` + `Received number of returns: ${ printReceived ( count ) }` + ( received . mock . calls . length !== count ? `\nReceived number of calls:   ${ printReceived ( received . mock . calls . length ) }` : '' ) ; return { message , pass } ; } ; const createToBeCalledWithMatcher = ( matcherName ) => function ( this , received , ... expected ) : SyncExpectationResult { const expectedArgument = '...expected' ; const options = { isNot : this . isNot , promise : this . promise , } ; ensureMockOrSpy ( received , matcherName , expectedArgument , options ) ; const receivedIsSpy = isSpy ( received ) ; const receivedName = receivedIsSpy ? 'spy' : received . getMockName ( ) ; const calls = receivedIsSpy ? received . calls . all ( ) . map ( ( x ) => x . args ) : received . mock . calls ; const pass = calls . some ( ( call ) => isEqualCall ( expected , call ) ) ; const message = pass ? ( ) => { const indexedCalls = [ ] ; let i = 0 ; while ( i < calls . length && indexedCalls . length < PRINT_LIMIT ) { if ( isEqualCall ( expected , calls [ i ] ) ) { indexedCalls . push ( [ i , calls [ i ] ] ) ; } i += 1 ; } return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected: not ${ printExpectedArgs ( expected ) }\n` + ( calls . length === 1 && stringify ( calls [ 0 ] ) === stringify ( expected ) ? '' : printReceivedCallsNegative ( expected , indexedCalls , calls . length === 1 ) ) + `\nNumber of calls: ${ printReceived ( calls . length ) }` ) ; } : ( ) => { const indexedCalls = [ ] ; let i = 0 ; while ( i < calls . length && indexedCalls . length < PRINT_LIMIT ) { indexedCalls . push ( [ i , calls [ i ] ] ) ; i += 1 ; } return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + printExpectedReceivedCallsPositive ( expected , indexedCalls , isExpand ( this . expand ) , calls . length === 1 ) + `\nNumber of calls: ${ printReceived ( calls . length ) }` ) ; } ; return { message , pass } ; } ; const createToReturnWithMatcher = ( matcherName ) => function ( this , received , expected ) : SyncExpectationResult { const expectedArgument = 'expected' ; const options = { isNot : this . isNot , promise : this . promise , } ; ensureMock ( received , matcherName , expectedArgument , options ) ; const receivedName = received . getMockName ( ) ; const { calls , results } = received . mock ; const pass = results . some ( ( result ) => isEqualReturn ( expected , result ) ) ; const message = pass ? ( ) => { const indexedResults = [ ] ; let i = 0 ; while ( i < results . length && indexedResults . length < PRINT_LIMIT ) { if ( isEqualReturn ( expected , results [ i ] ) ) { indexedResults . push ( [ i , results [ i ] ] ) ; } i += 1 ; } return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected: not ${ printExpected ( expected ) }\n` + ( results . length === 1 && results [ 0 ] . type === 'return' && stringify ( results [ 0 ] . value ) === stringify ( expected ) ? '' : printReceivedResults ( 'Received:     ' , expected , indexedResults , results . length === 1 ) ) + printNumberOfReturns ( countReturns ( results ) , calls . length ) ) ; } : ( ) => { const indexedResults = [ ] ; let i = 0 ; while ( i < results . length && indexedResults . length < PRINT_LIMIT ) { indexedResults . push ( [ i , results [ i ] ] ) ; i += 1 ; } return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected: ${ printExpected ( expected ) }\n` + printReceivedResults ( 'Received: ' , expected , indexedResults , results . length === 1 ) + printNumberOfReturns ( countReturns ( results ) , calls . length ) ) ; } ; return { message , pass } ; } ; const createLastCalledWithMatcher = ( matcherName ) => function ( this , received , ... expected ) : SyncExpectationResult { const expectedArgument = '...expected' ; const options = { isNot : this . isNot , promise : this . promise , } ; ensureMockOrSpy ( received , matcherName , expectedArgument , options ) ; const receivedIsSpy = isSpy ( received ) ; const receivedName = receivedIsSpy ? 'spy' : received . getMockName ( ) ; const calls = receivedIsSpy ? received . calls . all ( ) . map ( ( x ) => x . args ) : received . mock . calls ; const iLast = calls . length - 1 ; const pass = iLast >= 0 && isEqualCall ( expected , calls [ iLast ] ) ; const message = pass ? ( ) => { const indexedCalls = [ ] ; if ( iLast > 0 ) { indexedCalls . push ( [ iLast - 1 , calls [ iLast - 1 ] ] ) ; } indexedCalls . push ( [ iLast , calls [ iLast ] ] ) ; return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected: not ${ printExpectedArgs ( expected ) }\n` + ( calls . length === 1 && stringify ( calls [ 0 ] ) === stringify ( expected ) ? '' : printReceivedCallsNegative ( expected , indexedCalls , calls . length === 1 , iLast ) ) + `\nNumber of calls: ${ printReceived ( calls . length ) }` ) ; } : ( ) => { const indexedCalls = [ ] ; if ( iLast >= 0 ) { if ( iLast > 0 ) { let i = iLast - 1 ; while ( i >= 0 && ! isEqualCall ( expected , calls [ i ] ) ) { i -= 1 ; } if ( i < 0 ) { i = iLast - 1 ; } indexedCalls . push ( [ i , calls [ i ] ] ) ; } indexedCalls . push ( [ iLast , calls [ iLast ] ] ) ; } return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + printExpectedReceivedCallsPositive ( expected , indexedCalls , isExpand ( this . expand ) , calls . length === 1 , iLast ) + `\nNumber of calls: ${ printReceived ( calls . length ) }` ) ; } ; return { message , pass } ; } ; const createLastReturnedMatcher = ( matcherName ) => function ( this , received , expected ) : SyncExpectationResult { const expectedArgument = 'expected' ; const options = { isNot : this . isNot , promise : this . promise , } ; ensureMock ( received , matcherName , expectedArgument , options ) ; const receivedName = received . getMockName ( ) ; const { calls , results } = received . mock ; const iLast = results . length - 1 ; const pass = iLast >= 0 && isEqualReturn ( expected , results [ iLast ] ) ; const message = pass ? ( ) => { const indexedResults = [ ] ; if ( iLast > 0 ) { indexedResults . push ( [ iLast - 1 , results [ iLast - 1 ] ] ) ; } indexedResults . push ( [ iLast , results [ iLast ] ] ) ; return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected: not ${ printExpected ( expected ) }\n` + ( results . length === 1 && results [ 0 ] . type === 'return' && stringify ( results [ 0 ] . value ) === stringify ( expected ) ? '' : printReceivedResults ( 'Received:     ' , expected , indexedResults , results . length === 1 , iLast ) ) + printNumberOfReturns ( countReturns ( results ) , calls . length ) ) ; } : ( ) => { const indexedResults = [ ] ; if ( iLast >= 0 ) { if ( iLast > 0 ) { let i = iLast - 1 ; while ( i >= 0 && ! isEqualReturn ( expected , results [ i ] ) ) { i -= 1 ; } if ( i < 0 ) { i = iLast - 1 ; } indexedResults . push ( [ i , results [ i ] ] ) ; } indexedResults . push ( [ iLast , results [ iLast ] ] ) ; } return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `Expected: ${ printExpected ( expected ) }\n` + printReceivedResults ( 'Received: ' , expected , indexedResults , results . length === 1 , iLast ) + printNumberOfReturns ( countReturns ( results ) , calls . length ) ) ; } ; return { message , pass } ; } ; const createNthCalledWithMatcher = ( matcherName ) => function ( this , received , nth , ... expected ) : SyncExpectationResult { const expectedArgument = 'n' ; const options = { expectedColor : ( arg ) => arg , isNot : this . isNot , promise : this . promise , secondArgument : '...expected' , } ; ensureMockOrSpy ( received , matcherName , expectedArgument , options ) ; if ( ! Number . isSafeInteger ( nth ) || nth < 1 ) { throw new Error ( matcherErrorMessage ( matcherHint ( matcherName , undefined , expectedArgument , options ) , `${ expectedArgument } must be a positive integer` , printWithType ( expectedArgument , nth , stringify ) ) ) ; } const receivedIsSpy = isSpy ( received ) ; const receivedName = receivedIsSpy ? 'spy' : received . getMockName ( ) ; const calls = receivedIsSpy ? received . calls . all ( ) . map ( ( x ) => x . args ) : received . mock . calls ; const length = calls . length ; const iNth = nth - 1 ; const pass = iNth < length && isEqualCall ( expected , calls [ iNth ] ) ; const message = pass ? ( ) => { const indexedCalls = [ ] ; if ( iNth - 1 >= 0 ) { indexedCalls . push ( [ iNth - 1 , calls [ iNth - 1 ] ] ) ; } indexedCalls . push ( [ iNth , calls [ iNth ] ] ) ; if ( iNth + 1 < length ) { indexedCalls . push ( [ iNth + 1 , calls [ iNth + 1 ] ] ) ; } return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `n: ${ nth }\n` + `Expected: not ${ printExpectedArgs ( expected ) }\n` + ( calls . length === 1 && stringify ( calls [ 0 ] ) === stringify ( expected ) ? '' : printReceivedCallsNegative ( expected , indexedCalls , calls . length === 1 , iNth ) ) + `\nNumber of calls: ${ printReceived ( calls . length ) }` ) ; } : ( ) => { const indexedCalls = [ ] ; if ( iNth < length ) { if ( iNth - 1 >= 0 ) { let i = iNth - 1 ; while ( i >= 0 && ! isEqualCall ( expected , calls [ i ] ) ) { i -= 1 ; } if ( i < 0 ) { i = iNth - 1 ; } indexedCalls . push ( [ i , calls [ i ] ] ) ; } indexedCalls . push ( [ iNth , calls [ iNth ] ] ) ; if ( iNth + 1 < length ) { let i = iNth + 1 ; while ( i < length && ! isEqualCall ( expected , calls [ i ] ) ) { i += 1 ; } if ( i >= length ) { i = iNth + 1 ; } indexedCalls . push ( [ i , calls [ i ] ] ) ; } } else if ( length > 0 ) { let i = length - 1 ; while ( i >= 0 && ! isEqualCall ( expected , calls [ i ] ) ) { i -= 1 ; } if ( i < 0 ) { i = length - 1 ; } indexedCalls . push ( [ i , calls [ i ] ] ) ; } return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `n: ${ nth }\n` + printExpectedReceivedCallsPositive ( expected , indexedCalls , isExpand ( this . expand ) , calls . length === 1 , iNth ) + `\nNumber of calls: ${ printReceived ( calls . length ) }` ) ; } ; return { message , pass } ; } ; const createNthReturnedWithMatcher = ( matcherName ) => function ( this , received , nth , expected ) : SyncExpectationResult { const expectedArgument = 'n' ; const options = { expectedColor : ( arg ) => arg , isNot : this . isNot , promise : this . promise , secondArgument : 'expected' , } ; ensureMock ( received , matcherName , expectedArgument , options ) ; if ( ! Number . isSafeInteger ( nth ) || nth < 1 ) { throw new Error ( matcherErrorMessage ( matcherHint ( matcherName , undefined , expectedArgument , options ) , `${ expectedArgument } must be a positive integer` , printWithType ( expectedArgument , nth , stringify ) ) ) ; } const receivedName = received . getMockName ( ) ; const { calls , results } = received . mock ; const length = results . length ; const iNth = nth - 1 ; const pass = iNth < length && isEqualReturn ( expected , results [ iNth ] ) ; const message = pass ? ( ) => { const indexedResults = [ ] ; if ( iNth - 1 >= 0 ) { indexedResults . push ( [ iNth - 1 , results [ iNth - 1 ] ] ) ; } indexedResults . push ( [ iNth , results [ iNth ] ] ) ; if ( iNth + 1 < length ) { indexedResults . push ( [ iNth + 1 , results [ iNth + 1 ] ] ) ; } return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `n: ${ nth }\n` + `Expected: not ${ printExpected ( expected ) }\n` + ( results . length === 1 && results [ 0 ] . type === 'return' && stringify ( results [ 0 ] . value ) === stringify ( expected ) ? '' : printReceivedResults ( 'Received:     ' , expected , indexedResults , results . length === 1 , iNth ) ) + printNumberOfReturns ( countReturns ( results ) , calls . length ) ) ; } : ( ) => { const indexedResults = [ ] ; if ( iNth < length ) { if ( iNth - 1 >= 0 ) { let i = iNth - 1 ; while ( i >= 0 && ! isEqualReturn ( expected , results [ i ] ) ) { i -= 1 ; } if ( i < 0 ) { i = iNth - 1 ; } indexedResults . push ( [ i , results [ i ] ] ) ; } indexedResults . push ( [ iNth , results [ iNth ] ] ) ; if ( iNth + 1 < length ) { let i = iNth + 1 ; while ( i < length && ! isEqualReturn ( expected , results [ i ] ) ) { i += 1 ; } if ( i >= length ) { i = iNth + 1 ; } indexedResults . push ( [ i , results [ i ] ] ) ; } } else if ( length > 0 ) { let i = length - 1 ; while ( i >= 0 && ! isEqualReturn ( expected , results [ i ] ) ) { i -= 1 ; } if ( i < 0 ) { i = length - 1 ; } indexedResults . push ( [ i , results [ i ] ] ) ; } return ( matcherHint ( matcherName , receivedName , expectedArgument , options ) + '\n\n' + `n: ${ nth }\n` + `Expected: ${ printExpected ( expected ) }\n` + printReceivedResults ( 'Received: ' , expected , indexedResults , results . length === 1 , iNth ) + printNumberOfReturns ( countReturns ( results ) , calls . length ) ) ; } ; return { message , pass } ; } ; const spyMatchers = { lastCalledWith : createLastCalledWithMatcher ( 'lastCalledWith' ) , lastReturnedWith : createLastReturnedMatcher ( 'lastReturnedWith' ) , nthCalledWith : createNthCalledWithMatcher ( 'nthCalledWith' ) , nthReturnedWith : createNthReturnedWithMatcher ( 'nthReturnedWith' ) , toBeCalled : createToBeCalledMatcher ( 'toBeCalled' ) , toBeCalledTimes : createToBeCalledTimesMatcher ( 'toBeCalledTimes' ) , toBeCalledWith : createToBeCalledWithMatcher ( 'toBeCalledWith' ) , toHaveBeenCalled : createToBeCalledMatcher ( 'toHaveBeenCalled' ) , toHaveBeenCalledTimes : createToBeCalledTimesMatcher ( 'toHaveBeenCalledTimes' ) , toHaveBeenCalledWith : createToBeCalledWithMatcher ( 'toHaveBeenCalledWith' ) , toHaveBeenLastCalledWith : createLastCalledWithMatcher ( 'toHaveBeenLastCalledWith' ) , toHaveBeenNthCalledWith : createNthCalledWithMatcher ( 'toHaveBeenNthCalledWith' ) , toHaveLastReturnedWith : createLastReturnedMatcher ( 'toHaveLastReturnedWith' ) , toHaveNthReturnedWith : createNthReturnedWithMatcher ( 'toHaveNthReturnedWith' ) , toHaveReturned : createToReturnMatcher ( 'toHaveReturned' ) , toHaveReturnedTimes : createToReturnTimesMatcher ( 'toHaveReturnedTimes' ) , toHaveReturnedWith : createToReturnWithMatcher ( 'toHaveReturnedWith' ) , toReturn : createToReturnMatcher ( 'toReturn' ) , toReturnTimes : createToReturnTimesMatcher ( 'toReturnTimes' ) , toReturnWith : createToReturnWithMatcher ( 'toReturnWith' ) , } ; const isMock = ( received ) => received != null && received . _isMockFunction === true ; const isSpy = ( received ) => received != null && received . calls != null && typeof received . calls . all === 'function' && typeof received . calls . count === 'function' ; const ensureMockOrSpy = ( received , matcherName , expectedArgument , options ) => { if ( ! isMock ( received ) && ! isSpy ( received ) ) { throw new Error ( matcherErrorMessage ( matcherHint ( matcherName , undefined , expectedArgument , options ) , `${ RECEIVED_COLOR ( 'received' ) } value must be a mock or spy function` , printWithType ( 'Received' , received , printReceived ) ) ) ; } } ; const ensureMock = ( received , matcherName , expectedArgument , options ) => { if ( ! isMock ( received ) ) { throw new Error ( matcherErrorMessage ( matcherHint ( matcherName , undefined , expectedArgument , options ) , `${ RECEIVED_COLOR ( 'received' ) } value must be a mock function` , printWithType ( 'Received' , received , printReceived ) ) ) ; } } ; export default spyMatchers ;