Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support: record step keyword, deprecate defineStep #2044

Merged
merged 14 commits into from Jun 10, 2022
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,9 +8,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CONTRIBUTING.md) on how to contribute to Cucumber.

## [Unreleased]

### Added
- Add `willBeRetried` to the parameter passed to `After` hook functions ([#2045](https://github.com/cucumber/cucumber-js/pull/2045))

### Changed
- `defineStep` is now deprecated and will eventually be removed; use the appropriate Given/When/Then keyword to define your step ([#2044](https://github.com/cucumber/cucumber-js/pull/2044))

## [8.2.2] - 2022-05-27
### Changed
- Use latest HTML formatter with better handling for scenario outlines
Expand Down
16 changes: 5 additions & 11 deletions docs/support_files/api_reference.md
Expand Up @@ -104,11 +104,11 @@ Multiple `BeforeStep` hooks are executed in the order that they are defined.

---

#### `defineStep(pattern[, options], fn)`
#### `Given(pattern[, options], fn)`

Defines a step.
Define a "Given" step.

Aliases: `Given`, `When`, `Then`.
Aliases: `defineStep` (deprecated and will be removed in a future release; use the appropriate Given/When/Then keyword to define your step).

* `pattern`: A regex or string pattern to match against a gherkin step.
* `options`: An object with the following keys:
Expand All @@ -121,12 +121,6 @@ Aliases: `Given`, `When`, `Then`.

---

#### `Given(pattern[, options], fn)`

Alias of `defineStep`.

---

#### `setDefaultTimeout(milliseconds)`

Set the default timeout for asynchronous steps. Defaults to `5000` milliseconds.
Expand Down Expand Up @@ -199,10 +193,10 @@ function World({attach, parameters}) {

#### `Then(pattern[, options], fn)`

Alias of `defineStep`.
Define a "Then" step. Same interface as `Given`

---

#### `When(pattern[, options], fn)`

Alias of `defineStep`.
Define a "When" step. Same interface as `Given`
2 changes: 2 additions & 0 deletions src/cli/helpers_spec.ts
Expand Up @@ -145,6 +145,7 @@ describe('helpers', () => {
line: 9,
options: {},
uri: 'features/support/cukes.js',
keyword: 'Given',
pattern: 'I have {int} cukes in my belly',
expression: new CucumberExpression(
'I have {int} cukes in my belly',
Expand Down Expand Up @@ -183,6 +184,7 @@ describe('helpers', () => {
line: 9,
options: {},
uri: 'features/support/cukes.js',
keyword: 'Given',
pattern: /I have (\d+) cukes in my belly/,
expression: new RegularExpression(
/I have (\d+) cukes in my belly/,
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Expand Up @@ -38,6 +38,9 @@ export const Before = methods.Before
export const BeforeAll = methods.BeforeAll
export const BeforeStep = methods.BeforeStep
export const defineParameterType = methods.defineParameterType
/**
* @deprecated use `Given`, `When` or `Then` instead; see <https://github.com/cucumber/cucumber-js/issues/2043>
*/
davidjgoss marked this conversation as resolved.
Show resolved Hide resolved
export const defineStep = methods.defineStep
export const Given = methods.Given
export const setDefaultTimeout = methods.setDefaultTimeout
Expand Down
2 changes: 2 additions & 0 deletions src/models/definition.ts
@@ -1,6 +1,7 @@
import * as messages from '@cucumber/messages'
import { ITestCaseHookParameter } from '../support_code_library_builder/types'
import { Expression } from '@cucumber/cucumber-expressions'
import { GherkinStepKeyword } from './gherkin_step_keyword'

export interface IGetInvocationDataRequest {
hookParameter: ITestCaseHookParameter
Expand Down Expand Up @@ -35,6 +36,7 @@ export interface IDefinitionParameters<T extends IDefinitionOptions> {

export interface IStepDefinitionParameters
extends IDefinitionParameters<IDefinitionOptions> {
keyword: GherkinStepKeyword
pattern: string | RegExp
expression: Expression
}
Expand Down
1 change: 1 addition & 0 deletions src/models/gherkin_step_keyword.ts
@@ -0,0 +1 @@
export type GherkinStepKeyword = 'Unknown' | 'Given' | 'When' | 'Then'
3 changes: 3 additions & 0 deletions src/models/step_definition.ts
Expand Up @@ -8,13 +8,16 @@ import Definition, {
import { parseStepArgument } from '../step_arguments'
import { Expression } from '@cucumber/cucumber-expressions'
import { doesHaveValue } from '../value_checker'
import { GherkinStepKeyword } from './gherkin_step_keyword'

export default class StepDefinition extends Definition implements IDefinition {
public readonly keyword: GherkinStepKeyword
public readonly pattern: string | RegExp
public readonly expression: Expression

constructor(data: IStepDefinitionParameters) {
super(data)
this.keyword = data.keyword
this.pattern = data.pattern
this.expression = data.expression
}
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/helpers_spec.ts
Expand Up @@ -16,6 +16,7 @@ describe('Helpers', () => {
id: '',
options: undefined,
line: 3,
keyword: 'Given',
pattern: 'pattern1',
uri: 'steps1.js',
}),
Expand All @@ -25,6 +26,7 @@ describe('Helpers', () => {
id: '',
options: undefined,
line: 4,
keyword: 'Given',
pattern: 'longer pattern2',
uri: 'steps2.js',
}),
Expand Down
65 changes: 39 additions & 26 deletions src/support_code_library_builder/index.ts
Expand Up @@ -8,6 +8,7 @@ import TestRunHookDefinition from '../models/test_run_hook_definition'
import StepDefinition from '../models/step_definition'
import { formatLocation } from '../formatter/helpers'
import validateArguments from './validate_arguments'
import { deprecate } from 'util'
import arity from 'util-arity'

import {
Expand All @@ -29,14 +30,17 @@ import {
TestStepHookFunction,
ParallelAssignmentValidator,
ISupportCodeCoordinates,
IDefineStep,
} from './types'
import World from './world'
import { ICanonicalSupportCodeIds } from '../runtime/parallel/command_types'
import { GherkinStepKeyword } from '../models/gherkin_step_keyword'

interface IStepDefinitionConfig {
code: any
line: number
options: any
keyword: GherkinStepKeyword
pattern: string | RegExp
uri: string
}
Expand Down Expand Up @@ -96,7 +100,6 @@ export class SupportCodeLibraryBuilder {
private parallelCanAssign: ParallelAssignmentValidator

constructor() {
const defineStep = this.defineStep.bind(this)
this.methods = {
After: this.defineTestCaseHook(
() => this.afterTestCaseHookDefinitionConfigs
Expand All @@ -117,8 +120,11 @@ export class SupportCodeLibraryBuilder {
() => this.beforeTestStepHookDefinitionConfigs
),
defineParameterType: this.defineParameterType.bind(this),
defineStep,
Given: defineStep,
defineStep: deprecate(
this.defineStep('Unknown', () => this.stepDefinitionConfigs),
'`defineStep` is deprecated, use `Given`, `When` or `Then` instead; see https://github.com/cucumber/cucumber-js/issues/2043'
),
Given: this.defineStep('Given', () => this.stepDefinitionConfigs),
setDefaultTimeout: (milliseconds) => {
this.defaultTimeout = milliseconds
},
Expand All @@ -131,8 +137,8 @@ export class SupportCodeLibraryBuilder {
setParallelCanAssign: (fn: ParallelAssignmentValidator): void => {
this.parallelCanAssign = fn
},
Then: defineStep,
When: defineStep,
Then: this.defineStep('Then', () => this.stepDefinitionConfigs),
When: this.defineStep('When', () => this.stepDefinitionConfigs),
}
}

Expand All @@ -142,27 +148,33 @@ export class SupportCodeLibraryBuilder {
}

defineStep(
pattern: DefineStepPattern,
options: IDefineStepOptions | Function,
code?: Function
): void {
if (typeof options === 'function') {
code = options
options = {}
keyword: GherkinStepKeyword,
getCollection: () => IStepDefinitionConfig[]
): IDefineStep {
return (
pattern: DefineStepPattern,
options: IDefineStepOptions | Function,
code?: Function
) => {
if (typeof options === 'function') {
code = options
options = {}
}
const { line, uri } = getDefinitionLineAndUri(this.cwd)
validateArguments({
args: { code, pattern, options },
fnName: 'defineStep',
location: formatLocation({ line, uri }),
})
getCollection().push({
code,
line,
options,
keyword,
pattern,
uri,
})
}
const { line, uri } = getDefinitionLineAndUri(this.cwd)
validateArguments({
args: { code, pattern, options },
fnName: 'defineStep',
location: formatLocation({ line, uri }),
})
this.stepDefinitionConfigs.push({
code,
line,
options,
pattern,
uri,
})
}

defineTestCaseHook(
Expand Down Expand Up @@ -345,7 +357,7 @@ export class SupportCodeLibraryBuilder {
const stepDefinitions: StepDefinition[] = []
const undefinedParameterTypes: messages.UndefinedParameterType[] = []
this.stepDefinitionConfigs.forEach(
({ code, line, options, pattern, uri }, index) => {
({ code, line, options, keyword, pattern, uri }, index) => {
let expression
if (typeof pattern === 'string') {
try {
Expand Down Expand Up @@ -381,6 +393,7 @@ export class SupportCodeLibraryBuilder {
id: canonicalIds ? canonicalIds[index] : this.newId(),
line,
options,
keyword,
pattern,
unwrappedCode: code,
uri,
Expand Down
36 changes: 36 additions & 0 deletions src/support_code_library_builder/index_spec.ts
Expand Up @@ -99,6 +99,42 @@ describe('supportCodeLibraryBuilder', () => {
expect(stepDefinition.unwrappedCode).to.eql(step)
})
})

describe('keyword retention', () => {
const step = function (): void {} // eslint-disable-line @typescript-eslint/no-empty-function

beforeEach(() =>
supportCodeLibraryBuilder.reset('path/to/project', uuid())
)

it('should record correctly for Given', () => {
supportCodeLibraryBuilder.methods.Given('a thing', step)
expect(
supportCodeLibraryBuilder.finalize().stepDefinitions[0].keyword
).to.eq('Given')
})

it('should record correctly for When', () => {
supportCodeLibraryBuilder.methods.When('a thing', step)
expect(
supportCodeLibraryBuilder.finalize().stepDefinitions[0].keyword
).to.eq('When')
})

it('should record correctly for Then', () => {
supportCodeLibraryBuilder.methods.Then('a thing', step)
expect(
supportCodeLibraryBuilder.finalize().stepDefinitions[0].keyword
).to.eq('Then')
})

it('should record correctly for defineStep', () => {
supportCodeLibraryBuilder.methods.defineStep('a thing', step)
expect(
supportCodeLibraryBuilder.finalize().stepDefinitions[0].keyword
).to.eq('Unknown')
})
})
})

describe('After', () => {
Expand Down
50 changes: 14 additions & 36 deletions src/support_code_library_builder/types.ts
Expand Up @@ -71,17 +71,19 @@ export interface IParameterTypeDefinition<T> {
preferForRegexpMatch?: boolean
}

export interface IDefineSupportCodeMethods {
defineParameterType: (options: IParameterTypeDefinition<any>) => void
defineStep: (<WorldType = IWorld>(
export type IDefineStep = (<WorldType = IWorld>(
pattern: DefineStepPattern,
code: TestStepFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
pattern: DefineStepPattern,
options: IDefineStepOptions,
code: TestStepFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
pattern: DefineStepPattern,
options: IDefineStepOptions,
code: TestStepFunction<WorldType>
) => void)
) => void)

export interface IDefineSupportCodeMethods {
defineParameterType: (options: IParameterTypeDefinition<any>) => void
defineStep: IDefineStep
setDefaultTimeout: (milliseconds: number) => void
setDefinitionFunctionWrapper: (fn: Function) => void
setParallelCanAssign: (fn: ParallelAssignmentValidator) => void
Expand Down Expand Up @@ -132,33 +134,9 @@ export interface IDefineSupportCodeMethods {
) => void)
BeforeAll: ((code: Function) => void) &
((options: IDefineTestRunHookOptions, code: Function) => void)
Given: (<WorldType = IWorld>(
pattern: DefineStepPattern,
code: TestStepFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
pattern: DefineStepPattern,
options: IDefineStepOptions,
code: TestStepFunction<WorldType>
) => void)
Then: (<WorldType = IWorld>(
pattern: DefineStepPattern,
code: TestStepFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
pattern: DefineStepPattern,
options: IDefineStepOptions,
code: TestStepFunction<WorldType>
) => void)
When: (<WorldType = IWorld>(
pattern: DefineStepPattern,
code: TestStepFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
pattern: DefineStepPattern,
options: IDefineStepOptions,
code: TestStepFunction<WorldType>
) => void)
Given: IDefineStep
Then: IDefineStep
When: IDefineStep
}

export interface ISupportCodeCoordinates {
Expand Down
3 changes: 3 additions & 0 deletions src/wrapper.mjs
Expand Up @@ -28,6 +28,9 @@ export const Before = cucumber.Before
export const BeforeAll = cucumber.BeforeAll
export const BeforeStep = cucumber.BeforeStep
export const defineParameterType = cucumber.defineParameterType
/**
* @deprecated use `Given`, `When` or `Then` instead; see <https://github.com/cucumber/cucumber-js/issues/2043>
*/
export const defineStep = cucumber.defineStep
export const Given = cucumber.Given
export const setDefaultTimeout = cucumber.setDefaultTimeout
Expand Down