import { ExpressionFactory, ParameterType, ParameterTypeRegistry, } from '@cucumber/cucumber-expressions' import * as messages from '@cucumber/messages' import { HookType } from '@cucumber/messages' import DateClock from './DateClock' import { MakeErrorMessage, withFullStackTrace } from './ErrorMessageGenerator' import ExpressionStepDefinition from './ExpressionStepDefinition' import Hook from './Hook' import IClock from './IClock' import IParameterTypeDefinition from './IParameterTypeDefinition' import IStopwatch from './IStopwatch' import PerfHooksStopwatch from './PerfHooksStopwatch' import { AnyBody, HookOptions, IHook, IStepDefinition } from './types' function defaultTransformer(...args: string[]) { return args } /** * This class provides an API for defining step definitions and hooks. */ export default class SupportCode { public readonly parameterTypes: Array> = [] public readonly parameterTypeMessages: Array = [] public readonly stepDefinitions: IStepDefinition[] = [] public readonly beforeHooks: IHook[] = [] public readonly afterHooks: IHook[] = [] private readonly parameterTypeRegistry = new ParameterTypeRegistry() private readonly expressionFactory = new ExpressionFactory( this.parameterTypeRegistry ) public readonly undefinedParameterTypeMessages: messages.Envelope[] = [] constructor( public readonly newId: messages.IdGenerator.NewId = messages.IdGenerator.uuid(), public readonly clock: IClock = new DateClock(), public readonly stopwatch: IStopwatch = new PerfHooksStopwatch(), public readonly makeErrorMessage: MakeErrorMessage = withFullStackTrace() ) {} public defineParameterType( sourceReference: messages.SourceReference, parameterTypeDefinition: IParameterTypeDefinition ) { const parameterType = new ParameterType( parameterTypeDefinition.name, parameterTypeDefinition.regexp, parameterTypeDefinition.type, parameterTypeDefinition.transformer || defaultTransformer, parameterTypeDefinition.useForSnippets, parameterTypeDefinition.preferForRegexpMatch ) this.parameterTypeRegistry.defineParameterType(parameterType) this.parameterTypes.push(parameterType) this.parameterTypeMessages.push({ parameterType: { id: this.newId(), sourceReference, name: parameterType.name, regularExpressions: parameterType.regexpStrings.slice(), preferForRegularExpressionMatch: parameterType.preferForRegexpMatch, useForSnippets: parameterType.useForSnippets, }, }) } public defineStepDefinition( sourceReference: messages.SourceReference, expression: string | RegExp, body: AnyBody ): void { try { const expr = this.expressionFactory.createExpression(expression) const stepDefinition = new ExpressionStepDefinition( this.newId(), expr, sourceReference, body ) this.registerStepDefinition(stepDefinition) } catch (e) { if (e.undefinedParameterTypeName) { this.undefinedParameterTypeMessages.push({ undefinedParameterType: { expression: expression.toString(), name: e.undefinedParameterTypeName, }, }) } else { throw e } } } public registerStepDefinition(stepDefinition: IStepDefinition) { this.stepDefinitions.push(stepDefinition) } public defineBeforeHook( sourceReference: messages.SourceReference, tagExpressionOptionsOrBody: string | HookOptions | AnyBody, body?: AnyBody ) { this.registerBeforeHook( this.makeHook( HookType.BEFORE_TEST_CASE, sourceReference, tagExpressionOptionsOrBody, body ) ) } public registerBeforeHook(hook: IHook) { this.beforeHooks.push(hook) } public defineAfterHook( sourceReference: messages.SourceReference, tagExpressionOptionsOrBody: string | HookOptions | AnyBody, body?: AnyBody ) { this.registerAfterHook( this.makeHook( HookType.AFTER_TEST_CASE, sourceReference, tagExpressionOptionsOrBody, body ) ) } public registerAfterHook(hook: IHook) { this.afterHooks.push(hook) } private makeHook( type: HookType, sourceReference: messages.SourceReference, tagExpressionOptionsOrBody: string | HookOptions | AnyBody, body?: AnyBody ) { const name = typeof tagExpressionOptionsOrBody === 'object' ? tagExpressionOptionsOrBody.name : undefined let tagExpression = null if (typeof tagExpressionOptionsOrBody === 'string') { tagExpression = tagExpressionOptionsOrBody } else if (typeof tagExpressionOptionsOrBody === 'object') { tagExpression = tagExpressionOptionsOrBody.tagExpression ?? null } body = typeof tagExpressionOptionsOrBody === 'function' ? tagExpressionOptionsOrBody : body return new Hook( this.newId(), type, tagExpression, sourceReference, body, name ) } }