import { WatchableContractFunction } from '../index' ; import { WatchableFunctionLogic } from '../logic/watchable-function-logic' ; import { formatters } from './formatter' ; const TIME_WORDS = [ , 'once' , 'twice' , 'thrice' ] ; export const matchers = ( chai , utils ) => { function timesInWords ( count ) { return TIME_WORDS [ count ] || ( count || 0 ) + ' times' ; } function findWatchableContract ( obj ) { if ( obj instanceof WatchableFunctionLogic ) return obj ; if ( obj ?. _watchable instanceof WatchableFunctionLogic ) return obj . _watchable ; throw new TypeError ( utils . inspect ( obj ) + ' is not a watchablecontract or a call to a watchablecontract!' ) ; } function printf ( watchablecontract , format = '' , ... args ) { return format . replace ( /%(.)/g , function ( match , specifyer ) { if ( typeof formatters [ specifyer ] === 'function' ) { return String ( formatters [ specifyer ] ( watchablecontract , args ) ) ; } else if ( ! isNaN ( parseInt ( specifyer , 10 ) ) ) { return args [ specifyer - 1 ] ; } return `%${ specifyer }` ; } ) ; } function argToString ( arg ) { if ( arg instanceof WatchableFunctionLogic ) { return arg . getName ( ) ; } return arg . toString ( ) ; } function getMessages ( watchablecontract , action , nonNegatedSuffix , always , args = [ ] ) { const verbPhrase = always ? 'always have ' : 'have ' ; nonNegatedSuffix = nonNegatedSuffix || '' ; function printfArray ( array ) { return printf ( watchablecontract , ... array ) ; } return { affirmative : function ( ) { return printfArray ( [ 'expected %n to ' + verbPhrase + action + nonNegatedSuffix ] . concat ( args . map ( argToString ) ) ) ; } , negative : function ( ) { return printfArray ( [ 'expected %n to not ' + verbPhrase + action ] . concat ( args . map ( argToString ) ) ) ; } , } ; } function smockProperty ( name , action , nonNegatedSuffix ) { addChaiProperty ( name , ( assertion ) => { const watchablecontract = findWatchableContract ( assertion . _obj ) ; const messages = getMessages ( watchablecontract , action , nonNegatedSuffix , false ) ; const value = ( watchablecontract as any ) [ getPropertyNameGetter ( name ) ] ( ) ; assertion . assert ( value , messages . affirmative , messages . negative ) ; } ) ; } function getPropertyNameGetter ( name ) { return 'get' + name . charAt ( 0 ) . toUpperCase ( ) + name . slice ( 1 ) ; } function smockPropertyAsBooleanMethod ( name , action , nonNegatedSuffix ) { addChaiMethod ( name , ( assertion , arg ) => { const watchablecontract = findWatchableContract ( assertion . _obj ) ; const messages = getMessages ( watchablecontract , action , nonNegatedSuffix , false , [ timesInWords ( arg ) ] ) ; const value = ( watchablecontract as any ) [ getPropertyNameGetter ( name ) ] ( ) ; assertion . assert ( value === arg , messages . affirmative , messages . negative ) ; } ) ; } function createSmockMethodHandler ( smockName , action , nonNegatedSuffix ) { return ( assertion , ... args ) => { const watchablecontract = findWatchableContract ( assertion . _obj ) ; let methodToCall = smockName ; let shouldBeAlways = false ; if ( utils . flag ( assertion , 'always' ) ) { const alwaysMethod = 'always' + smockName [ 0 ] . toUpperCase ( ) + smockName . substring ( 1 ) ; if ( typeof ( watchablecontract as any ) [ alwaysMethod ] !== 'function' ) { throw Error ( `always flag is not supported for method ${ smockName }` ) ; } methodToCall = alwaysMethod ; shouldBeAlways = true ; } const messages = getMessages ( watchablecontract , action , nonNegatedSuffix , shouldBeAlways , args . slice ( ) ) ; assertion . assert ( ( watchablecontract as any ) [ methodToCall ] ( ... args ) , messages . affirmative , messages . negative ) ; } ; } function smockMethod ( name , action , nonNegatedSuffix = '' ) { const handler = createSmockMethodHandler ( name , action , nonNegatedSuffix ) ; addChaiMethod ( name , handler ) ; } function smockMethodWithWatchableContractArg ( name , action , nonNegatedSuffix = '' ) { const handler = createSmockMethodHandler ( name , action , nonNegatedSuffix ) ; addChaiMethod ( name , ( assertion , arg ) => handler ( assertion , arg . _watchable ) ) ; } function addChaiMethod < T extends any [ ] > ( name , callback ) { utils . addMethod ( chai . Assertion . prototype , name , function ( ... args ) { callback ( this , ... args ) ; } ) ; } function addChaiProperty ( name , callback ) { utils . addProperty ( chai . Assertion . prototype , name , function ( ) { callback ( this ) ; } ) ; } addChaiProperty ( 'always' , ( assertion ) => utils . flag ( assertion , 'always' , true ) ) ; smockProperty ( 'called' , 'been called' , ' at least once, but it was never called' ) ; smockPropertyAsBooleanMethod ( 'callCount' , 'been called exactly %1' , ', but it was called %c%C' ) ; smockProperty ( 'calledOnce' , 'been called exactly once' , ', but it was called %c%C' ) ; smockProperty ( 'calledTwice' , 'been called exactly twice' , ', but it was called %c%C' ) ; smockProperty ( 'calledThrice' , 'been called exactly thrice' , ', but it was called %c%C' ) ; smockMethodWithWatchableContractArg ( 'calledBefore' , 'been called before %1' ) ; smockMethodWithWatchableContractArg ( 'calledAfter' , 'been called after %1' ) ; smockMethodWithWatchableContractArg ( 'calledImmediatelyBefore' , 'been called immediately before %1' ) ; smockMethodWithWatchableContractArg ( 'calledImmediatelyAfter' , 'been called immediately after %1' ) ; smockMethod ( 'calledWith' , 'been called with arguments %*' , '%D' ) ; smockMethod ( 'calledOnceWith' , 'been called exactly once with arguments %*' , '%D' ) ; } ;