import { BigNumber } from 'ethers' ; import isEqual from 'lodash.isequal' ; import isEqualWith from 'lodash.isequalwith' ; import { Observable , of } from 'rxjs' ; import { ContractCall } from '../types' ; import { convertStructToPojo , getObjectAndStruct , humanizeTimes } from '../utils' ; export class WatchableFunctionLogic { protected name : string ; protected callHistory : ContractCall [ ] = [ ] ; constructor ( name , calls$ ) { this . name = name ; calls$ . subscribe ( ( call ) => this . callHistory . push ( call ) ) ; } atCall ( index ) { if ( ! this . getCall ( index ) ) throw new Error ( `expected ${ this . name } to have been called ${ humanizeTimes ( index + 1 ) }, but it was called ${ humanizeTimes ( this . getCallCount ( ) ) }` ) ; return new WatchableFunctionLogic ( this . name , of ( this . getCall ( index ) ) ) ; } getCall ( index ) { return this . callHistory [ index ] ; } calledWith ( ... expectedCallArgs ) { return ! ! this . callHistory . find ( ( call ) => this . isDeepEqual ( call . args , expectedCallArgs ) ) ; } alwaysCalledWith ( ... expectedCallArgs ) { const callWithOtherArgs = this . callHistory . find ( ( call ) => ! this . isDeepEqual ( call . args , expectedCallArgs ) ) ; return this . getCalled ( ) && ! callWithOtherArgs ; } calledOnceWith ( ... expectedCallArgs ) { return this . getCalledOnce ( ) && this . calledWith ( ... expectedCallArgs ) ; } calledBefore ( anotherWatchableContract ) { return this . compareWatchableContractNonces ( this , anotherWatchableContract , ( thisNonce , anotherWatchableContractNonce ) => thisNonce < anotherWatchableContractNonce ) ; } alwaysCalledBefore ( anotherWatchableContract ) { return this . callHistory [ this . getCallCount ( ) - 1 ] ?. nonce < anotherWatchableContract . callHistory [ 0 ] ?. nonce ; } calledAfter ( anotherWatchableContract ) { return this . compareWatchableContractNonces ( this , anotherWatchableContract , ( thisNonce , anotherWatchableContractNonce ) => thisNonce > anotherWatchableContractNonce ) ; } alwaysCalledAfter ( anotherWatchableContract ) { return this . callHistory [ 0 ] ?. nonce > anotherWatchableContract . callHistory [ anotherWatchableContract . getCallCount ( ) - 1 ] ?. nonce ; } calledImmediatelyBefore ( anotherWatchableContract ) { return this . compareWatchableContractNonces ( this , anotherWatchableContract , ( thisNonce , anotherWatchableContractNonce ) => thisNonce === anotherWatchableContractNonce - 1 ) ; } alwaysCalledImmediatelyBefore ( anotherWatchableContract ) { if ( this . getCallCount ( ) === 0 || this . getCallCount ( ) != anotherWatchableContract . getCallCount ( ) ) return false ; return ! this . callHistory . find ( ( thisCall , index ) => { return thisCall . nonce !== anotherWatchableContract . callHistory [ index ] . nonce - 1 ; } ) ; } calledImmediatelyAfter ( anotherWatchableContract ) { return this . compareWatchableContractNonces ( this , anotherWatchableContract , ( thisNonce , anotherWatchableContractNonce ) => thisNonce === anotherWatchableContractNonce + 1 ) ; } alwaysCalledImmediatelyAfter ( anotherWatchableContract ) { if ( this . getCallCount ( ) === 0 || this . getCallCount ( ) != anotherWatchableContract . getCallCount ( ) ) return false ; return ! this . callHistory . find ( ( thisCall , index ) => { return thisCall . nonce !== anotherWatchableContract . callHistory [ index ] . nonce + 1 ; } ) ; } getName ( ) { return this . name ; } getCallCount ( ) { return this . callHistory . length ; } getCalled ( ) { return this . getCallCount ( ) > 0 ; } getCalledOnce ( ) { return this . getCallCount ( ) === 1 ; } getCalledTwice ( ) { return this . getCallCount ( ) === 2 ; } getCalledThrice ( ) { return this . getCallCount ( ) === 3 ; } protected reset ( ) { this . callHistory = [ ] ; } private compareWatchableContractNonces ( watchablecontractA , watchablecontractB , comparison ) { return ! ! watchablecontractA . callHistory . find ( ( watchablecontractACall ) => { return watchablecontractB . callHistory . find ( ( watchablecontractBCall ) => { return comparison ( watchablecontractACall . nonce , watchablecontractBCall . nonce ) ; } ) ; } ) ; } protected isDeepEqual ( obj1 , obj2 ) { return isEqualWith ( obj1 , obj2 , this . isEqualCustomizer . bind ( this ) ) ; } param obj1 param obj2 returns private isEqualCustomizer ( obj1 , obj2 ) { if ( BigNumber . isBigNumber ( obj1 ) ) { return obj1 . eq ( obj2 as any ) ; } if ( BigNumber . isBigNumber ( obj2 ) ) { return obj2 . eq ( obj1 as any ) ; } const objectAndStruct = getObjectAndStruct ( obj1 , obj2 ) ; if ( objectAndStruct ) { return isEqual ( objectAndStruct [ 0 ] , convertStructToPojo ( objectAndStruct [ 1 ] ) ) ; } return undefined ; } }