import { After , Before , Given , Then , When , World } from "@cucumber/cucumber" ; import { messages } from "@cucumber/messages" ; import * as _ from "underscore" ; import logger from "./logger" ; import { BindingRegistry , DEFAULT_TAG } from "./binding-registry" ; import { ManagedScenarioContext } from "./managed-scenario-context" ; import { StepBinding , StepBindingFlags } from "./step-binding" ; import { ContextType , StepPattern , TypeDecorator } from "./types" ; interface WritableWorld extends World { [ key ] : any ; } /**
 * The property name of the current scenario context that will be attached to the Cucumber
 * world object.
 */ const SCENARIO_CONTEXT_SLOTNAME = "__SCENARIO_CONTEXT" ; /**
 * A set of step patterns that have been registered with Cucumber.
 *
 * In order to support scoped (or tagged) step definitions, we must ensure that any step binding is
 * only registered with Cucumber once. The binding function for that step pattern then becomes
 * responsible for looking up and execuing the step binding based on the context that is in scope at
 * the point of invocation.
 */ const stepPatternRegistrations = new Map < StepPattern , StepBindingFlags > ( ) ; param requiredContextTypes export function binding ( requiredContextTypes ? ) { return < T > ( target ) => { ensureSystemBindings ( ) ; const bindingRegistry = BindingRegistry . instance ; bindingRegistry . registerContextTypesForTarget ( target . prototype , requiredContextTypes ) ; bindingRegistry . getStepBindingsForTarget ( target . prototype ) . forEach ( stepBinding => { if ( stepBinding . bindingType & StepBindingFlags . StepDefinitions ) { let stepBindingFlags = stepPatternRegistrations . get ( stepBinding . stepPattern . toString ( ) ) ; if ( stepBindingFlags === undefined ) { stepBindingFlags = StepBindingFlags . none ; } if ( stepBindingFlags & stepBinding . bindingType ) { return ; } bindStepDefinition ( stepBinding ) ; stepPatternRegistrations . set ( stepBinding . stepPattern . toString ( ) , stepBindingFlags | stepBinding . bindingType ) ; } else if ( stepBinding . bindingType & StepBindingFlags . Hooks ) { bindHook ( stepBinding ) ; } } ) ; } ; } param cucumber const ensureSystemBindings = _ . once ( ( ) => { Before ( function ( this , scenario ) { logger . trace ( "Setting up scenario context for scenario:" , JSON . stringify ( scenario ) ) ; this [ SCENARIO_CONTEXT_SLOTNAME ] = new ManagedScenarioContext ( scenario . pickle . name ! , _ . map ( scenario . pickle . tags ! , ( tag ) => tag . name ! ) ) ; } ) ; After ( function ( this ) { const scenarioContext = this [ SCENARIO_CONTEXT_SLOTNAME ] as ManagedScenarioContext ; if ( scenarioContext ) { scenarioContext . dispose ( ) ; } } ) ; } ) ; param cucumber param stepBinding function bindStepDefinition ( stepBinding ) { const bindingFunc = function ( this ) : any { const bindingRegistry = BindingRegistry . instance ; const scenarioContext = this [ SCENARIO_CONTEXT_SLOTNAME ] as ManagedScenarioContext ; const matchingStepBindings = bindingRegistry . getStepBindings ( stepBinding . stepPattern . toString ( ) , scenarioContext . scenarioInfo . tags ) ; if ( matchingStepBindings . length > 1 ) { let message = `Ambiguous step definitions for '${ matchingStepBindings [ 0 ] . stepPattern }':\n` ; matchingStepBindings . forEach ( matchingStepBinding => { message = message + `\t\t${ String ( matchingStepBinding . targetPropertyKey ) } (${ matchingStepBinding . callsite . toString ( ) })\n` ; } ) ; throw new Error ( message ) ; } else if ( matchingStepBindings . length === 0 ) { throw new Error ( `Cannot find matched step definition for ${ stepBinding . stepPattern . toString ( ) } with tag ${ scenarioContext . scenarioInfo . tags } in binding registry` ) ; } const contextTypes = bindingRegistry . getContextTypesForTarget ( matchingStepBindings [ 0 ] . targetPrototype ) ; const bindingObject = scenarioContext . getOrActivateBindingClass ( matchingStepBindings [ 0 ] . targetPrototype , contextTypes ) ; bindingObject . _worldObj = this ; return ( bindingObject [ matchingStepBindings [ 0 ] . targetPropertyKey ] as ( ) => void ) . apply ( bindingObject , arguments as any ) ; } ; Object . defineProperty ( bindingFunc , "length" , { value : stepBinding . argsLength } ) ; if ( stepBinding . bindingType & StepBindingFlags . given ) { if ( stepBinding . timeout ) { Given ( stepBinding . stepPattern , { timeout : stepBinding . timeout } , bindingFunc ) ; } else { Given ( stepBinding . stepPattern , bindingFunc ) ; } } else if ( stepBinding . bindingType & StepBindingFlags . when ) { if ( stepBinding . timeout ) { When ( stepBinding . stepPattern , { timeout : stepBinding . timeout } , bindingFunc ) ; } else { When ( stepBinding . stepPattern , bindingFunc ) ; } } else if ( stepBinding . bindingType & StepBindingFlags . then ) { if ( stepBinding . timeout ) { Then ( stepBinding . stepPattern , { timeout : stepBinding . timeout } , bindingFunc ) ; } else { Then ( stepBinding . stepPattern , bindingFunc ) ; } } } param cucumber param stepBinding function bindHook ( stepBinding ) { const bindingFunc = function ( this ) : any { const scenarioContext = this [ SCENARIO_CONTEXT_SLOTNAME ] as ManagedScenarioContext ; const contextTypes = BindingRegistry . instance . getContextTypesForTarget ( stepBinding . targetPrototype ) ; const bindingObject = scenarioContext . getOrActivateBindingClass ( stepBinding . targetPrototype , contextTypes ) ; bindingObject . _worldObj = this ; return ( bindingObject [ stepBinding . targetPropertyKey ] as ( ) => void ) . apply ( bindingObject , arguments as any ) ; } ; Object . defineProperty ( bindingFunc , "length" , { value : stepBinding . argsLength } ) ; if ( stepBinding . bindingType & StepBindingFlags . before ) { if ( stepBinding . tag === DEFAULT_TAG ) { Before ( bindingFunc ) ; } else { Before ( String ( stepBinding . tag ) , bindingFunc ) ; } } else if ( stepBinding . bindingType & StepBindingFlags . after ) { if ( stepBinding . tag === DEFAULT_TAG ) { After ( bindingFunc ) ; } else { After ( String ( stepBinding . tag ) , bindingFunc ) ; } } }