import { equal } from 'assert' ; import { StepDefinitions } from 'cucumber' ; import createContext , { Context } from './context' ; import renderComps , { createFnVar , createValueVar , MapCreateComponent , Render , Var } from './render' ; export interface StepArgs { ctx : Context ; defs : StepDefinitions ; render : Render ; } export interface Options { mapCreateComponent ? : MapCreateComponent ; } export const createArgs = ( defs , comps , options ) : StepArgs => { const ctx = createContext ( ( ) => ( { vars : { $onChange : createFnVar ( null ) } } ) ) ; const render = renderComps ( ( ) => ctx . get ( 'vars' ) , comps , options . mapCreateComponent ) ; return { ctx , defs , render } ; } ; const getVar = ( ctx , name ) => ctx . get ( 'vars' ) [ name ] ; const setVar = ( ctx , name , v ) => ctx . get ( 'vars' ) [ name ] = v ; const findTarget = ( ctx , selector , index ) => { if ( ! selector ) return ctx . get ( 'lastTarget' ) ; const idx = index ? parseInt ( index ) - 1 : 0 ; const comp = ctx . get ( 'comp' ) ; const target = comp . findByQuery ( selector ) [ idx ] ; equal ( ! ! target , true , `Cannot find '${ selector }'.

    Here is a dump of the rendered component that should help you
    troubleshoot this error: \n\n${ comp . dump ( ) }\n
  ` ) ; ctx . set ( 'lastTarget' , target ) ; return target ; } ; const findTargetProp = ( ctx , selector , index , key ) => { const target = findTarget ( ctx , selector , index ) ; const value = target . props [ key ] ; equal ( ! ! value , true , `Cannot find ${ key } in ${ selector }'s props` ) ; return value ; } ; const rendered = ( ctx , render ) => ( mode , type , attrs ) => { ctx . set ( 'comp' , render ( mode , type , attrs ) ) ; } ; const renderedText = ( ctx , render ) => ( mode , comp ) => { const re = /[\r\n]+/g ; const matches = comp . replace ( re , '' ) . trim ( ) . match ( /<(\w+)(\s+.*)\/>/ ) ; if ( matches ) { const [ , type , attrs ] = matches ; ctx . set ( 'comp' , render ( mode , type , attrs ) ) ; } else { equal ( false , true , `The following does not look like a component spec:

        ${ comp }

      The expected component pattern is:

        ${ re }

      ` ) ; } } ; const noOp = ( ) => { ; } ; const event = { preventDefault : noOp , stopPropagation : noOp } ; export const givenRenderedComponent = ( { ctx , defs , render } ) => { defs . Given ( /^a (?:(shallow|full) )?rendered <(\w+)(\s+.*)\/>$/ , rendered ( ctx , render ) ) ; defs . Given ( /^a (?:(shallow|full) )?rendered$/ , renderedText ( ctx , render ) ) ; } ; export const givenFunction = ( { ctx , defs } ) => { const fn = ( name , returns ) => { const retVal = returns ? JSON . parse ( returns ) : undefined ; setVar ( ctx , name , createFnVar ( retVal ) ) ; } ; defs . Given ( /^a function (\$\w+)(?: that returns (.*))?$/ , fn ) ; defs . Given ( /^a function (\$\w+) that returns$/ , fn ) ; } ; export const givenVariable = ( { ctx , defs } ) => { const fn = ( name , value ) => { setVar ( ctx , name , createValueVar ( JSON . parse ( value ) ) ) ; } ; defs . Given ( /^that (\$\w+) is (.*)$/ , fn ) ; defs . Given ( /^that (\$\w+) is$/ , fn ) ; } ; export const whenRenderingComponent = ( { ctx , defs , render } ) => { defs . When ( /^(?:(shallow|full) )?rendering <(\w+)(\s+.*)\/>$/ , rendered ( ctx , render ) ) ; defs . When ( /^(?:(shallow|full) )?rendering$/ , renderedText ( ctx , render ) ) ; } ; export const whenTheSelectorCallsProp = ( { ctx , defs } ) => { const fn = ( index , selector , key , withEvent , arg ) => { const fn = findTargetProp ( ctx , selector , index , key ) ; const fnArgs = ! arg ? undefined : arg [ 0 ] == '[' ? JSON . parse ( arg ) : [ JSON . parse ( arg ) ] ; const args = withEvent ? [ { ... event , ... fnArgs [ 0 ] } , ... fnArgs . slice ( 1 ) ] : fnArgs ; fn ( ... args ) ; } ; defs . When ( /^(?:the (?:(\d+).. )?(.*) )?calls props\.(\w+)(?: with( event)? (.*))?$/ , fn ) ; defs . When ( /^(?:the (?:(\d+).. )?(.*) )?calls props\.(\w+) with( event)?$/ , fn ) ; } ; export const whenTheSelectorChanges = ( { ctx , defs } ) => { const fn = ( index , selector , value ) => { const fn = findTargetProp ( ctx , selector , index , 'onChange' ) ; fn ( { target : { value : JSON . parse ( value ) } } ) ; } ; defs . When ( /^(?:the (?:(\d+).. )?(.*) )?changes to (.*)$/ , fn ) ; defs . When ( /^(?:the (?:(\d+).. )?(.*) )?changes$/ , fn ) ; } ; export const thenDumpComponent = ( { ctx , defs } ) => defs . Then ( /^dump the rendered component$/ , ( ) => console . log ( '\n\nComponent\n---------\n\n' , ctx . get ( 'comp' ) . dump ( ) ) ) ; export const thenDumpContext = ( { ctx , defs } ) => defs . Then ( /^dump the test context$/ , ( ) => ctx . dump ( ) ) ; export const thenTheSelectorHasProp = ( { ctx , defs } ) => { const fn = ( index , selector , key , op , value ) => { const target = findTarget ( ctx , selector , index ) ; const actual = key === 'text' ? target . text : target . props [ key . replace ( /^props\./ , '' ) ] ; if ( op === 'equal to' ) { equal ( JSON . stringify ( actual ) , JSON . stringify ( JSON . parse ( value ) ) ) ; } else { const actualText = JSON . stringify ( actual ) ; equal ( ! ! actualText . match ( new RegExp ( value ) ) , true , `The value for ${ selector }[${ index }].${ key } does not match:

          ${ value }

        The actual value is:

          ${ actualText }
        ` ) ; } } ; defs . Then ( /^(?:the (?:(\d+).. )?(.*) has )?(props\.\w+|text) (equal to|matching) (.*)$/ , fn ) ; defs . Then ( /^(?:the (?:(\d+).. )?(.*) has )?(props\.\w+|text) (equal to|matching)$/ , fn ) ; } ; export const thenThereAreSelectorElements = ( { ctx , defs } ) => defs . Then ( /^there (?:is|are) (.+) (.+) elements?$/ , ( count , selector ) => equal ( ctx . get ( 'comp' ) . findByQuery ( selector ) . length , parseInt ( count ) , `Number of '${ selector }' mismatch` ) ) ; export const thenTheFunctionWasCalled = ( { ctx , defs } ) => defs . Then ( /^the function (\$\w+) was called (?:(\d+) times|once)?$/ , ( name , times ) => { const fn = getVar ( ctx , name ) ; equal ( fn . calls . length , parseInt ( times || '1' ) ) ; } ) ; export const thenTheComponentChanged = ( { ctx , defs } ) => { const fn = ( value ) => { const index = 0 ; const fn = getVar ( ctx , '$onChange' ) ; equal ( JSON . stringify ( fn . calls [ index ] [ 0 ] . target . value ) , JSON . stringify ( JSON . parse ( value ) ) ) ; } ; defs . Then ( /^the component changed to (.*)$/ , fn ) ; defs . Then ( /^the component changed to$/ , fn ) ; } ; export const thenTheComponentDidNotChange = ( { ctx , defs } ) => defs . Then ( /^the component (?:did not|didn't) change$/ , ( ) => equal ( getVar ( ctx , '$onChange' ) . calls , 0 , 'The component\'s onChange function was called at least once' ) ) ; export const thenTheFunctionCallWas = ( { ctx , defs } ) => { const fn = ( index , name , args ) => { const idx = index ? parseInt ( index ) - 1 : 0 ; const fn = getVar ( ctx , name ) ; equal ( JSON . stringify ( fn . calls [ idx ] ) , JSON . stringify ( JSON . parse ( args ) ) ) ; } ; defs . Then ( /^the (?:(\d+).. )?call to (\$\w+) had arguments (.*)$/ , fn ) ; defs . Then ( /^the (?:(\d+).. )?call to (\$\w+) had arguments$/ , fn ) ; } ; export default [ givenRenderedComponent , givenFunction , givenVariable , whenRenderingComponent , whenTheSelectorCallsProp , whenTheSelectorChanges , thenDumpComponent , thenDumpContext , thenTheSelectorHasProp , thenThereAreSelectorElements , thenTheFunctionWasCalled , thenTheFunctionCallWas , thenTheComponentChanged , thenTheComponentDidNotChange ] ;