Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber
## [Unreleased]
### Added
- Support named BeforeAll/AfterAll hooks ([#2661](https://github.com/cucumber/cucumber-js/pull/2661))
- Emit messages for suggestions ([#2703](https://github.com/cucumber/cucumber-js/pull/2703))

### Changed
- Render a more test case-centric HTML report ([react-components/#396](https://github.com/cucumber/react-components/pull/396))
Expand Down
6 changes: 0 additions & 6 deletions compatibility/cck_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,6 @@ const UNSUPPORTED = [
'global-hooks-afterall-error',
// not a test sample
'test-run-exception',
// suggestions not implemented yet
'examples-tables-undefined',
'hooks-undefined',
'retry-undefined',
'undefined',
'unknown-parameter-type',
]

config.truncateThreshold = 100
Expand Down
21 changes: 21 additions & 0 deletions features/step_definition_snippets_custom_syntax.feature
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,19 @@ Feature: step definition snippets custom syntax
"""
Feature: a feature
Scenario: a scenario
Given a step

Scenario: another scenario
Given an undefined step
"""
And a file named "features/steps.js" with:
"""
const {Given} = require('@cucumber/cucumber')

Given('a step', function() {
// noop
})
"""
And a file named "coffeescript_syntax.js" with:
"""
function CoffeeScriptSyntax(snippetInterface) {
Expand Down Expand Up @@ -61,3 +72,13 @@ Feature: step definition snippets custom syntax
| promise | -> | 'pending' |
| async-await | -> | 'pending' |
| synchronous | -> | 'pending' |

Scenario: Custom snippet syntax works in parallel runtime
When I run cucumber-js with `--parallel 2 --format-options '{"snippetInterface": "async-await", "snippetSyntax": "./coffeescript_syntax.js"}'`
Then it fails
And the output contains the text:
"""
@Given 'an undefined step', ->
# Write code here that turns the phrase above into concrete actions
'pending'
"""
3 changes: 3 additions & 0 deletions features/support/formatter_output_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,7 @@ export const ignorableKeys = [
// errors
'message',
'stackTrace',
// snippets
'language',
'code',
]
1 change: 1 addition & 0 deletions src/api/run_cucumber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ Running from: ${__dirname}
newId,
supportCodeLibrary,
options: options.runtime,
snippetOptions: options.formats.options,
})
const success = await runtime.run()
await pluginManager.cleanup()
Expand Down
17 changes: 17 additions & 0 deletions src/formatter/step_definition_snippet_builder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,23 @@ export default class StepDefinitionSnippetBuilder {
})
}

buildMultiple({ keywordType, pickleStep }: IBuildRequest): string[] {
const comment =
'Write code here that turns the phrase above into concrete actions'
const functionName = this.getFunctionName(keywordType)
const generatedExpressions =
this.cucumberExpressionGenerator.generateExpressions(pickleStep.text)
const stepParameterNames = this.getStepParameterNames(pickleStep)
return generatedExpressions.map((generatedExpression) => {
return this.snippetSyntax.build({
comment,
functionName,
generatedExpressions: [generatedExpression],
stepParameterNames,
})
})
}

getFunctionName(keywordType: KeywordType): string {
switch (keywordType) {
case KeywordType.Event:
Expand Down
75 changes: 55 additions & 20 deletions src/runtime/make_runtime.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { EventEmitter } from 'node:events'
import { IdGenerator } from '@cucumber/messages'
import { IRunOptionsRuntime } from '../api'
import { ILogger } from '../environment'
import { ILogger, IRunEnvironment } from '../environment'
import { SourcedPickle } from '../assemble'
import { SupportCodeLibrary } from '../support_code_library_builder/types'
import { IRunEnvironment } from '../environment'
import { Runtime, RuntimeAdapter } from './types'
import FormatterBuilder from '../formatter/builder'
import { FormatOptions } from '../formatter'
import { Runtime } from './types'
import { ChildProcessAdapter } from './parallel/adapter'
import { InProcessAdapter } from './serial/adapter'
import { Coordinator } from './coordinator'
Expand All @@ -18,6 +19,7 @@ export async function makeRuntime({
newId,
supportCodeLibrary,
options,
snippetOptions,
}: {
environment: IRunEnvironment
logger: ILogger
Expand All @@ -26,25 +28,19 @@ export async function makeRuntime({
sourcedPickles: ReadonlyArray<SourcedPickle>
supportCodeLibrary: SupportCodeLibrary
options: IRunOptionsRuntime
snippetOptions: Pick<FormatOptions, 'snippetInterface' | 'snippetSyntax'>
}): Promise<Runtime> {
const testRunStartedId = newId()
const adapter: RuntimeAdapter =
options.parallel > 0
? new ChildProcessAdapter(
testRunStartedId,
environment,
logger,
eventBroadcaster,
options,
supportCodeLibrary
)
: new InProcessAdapter(
testRunStartedId,
eventBroadcaster,
newId,
options,
supportCodeLibrary
)
const adapter = await makeAdapter(
options,
snippetOptions,
testRunStartedId,
environment,
logger,
eventBroadcaster,
supportCodeLibrary,
newId
)
return new Coordinator(
testRunStartedId,
eventBroadcaster,
Expand All @@ -54,3 +50,42 @@ export async function makeRuntime({
adapter
)
}

async function makeAdapter(
options: IRunOptionsRuntime,
snippetOptions: Pick<FormatOptions, 'snippetInterface' | 'snippetSyntax'>,
testRunStartedId: string,
environment: IRunEnvironment,
logger: ILogger,
eventBroadcaster: EventEmitter,
supportCodeLibrary: SupportCodeLibrary,
newId: () => string
) {
if (options.parallel > 0) {
return new ChildProcessAdapter(
testRunStartedId,
environment,
logger,
eventBroadcaster,
options,
snippetOptions,
supportCodeLibrary
)
}
const snippetBuilder = await FormatterBuilder.getStepDefinitionSnippetBuilder(
{
cwd: environment.cwd,
snippetInterface: snippetOptions.snippetInterface,
snippetSyntax: snippetOptions.snippetSyntax,
supportCodeLibrary,
}
)
return new InProcessAdapter(
testRunStartedId,
eventBroadcaster,
newId,
options,
supportCodeLibrary,
snippetBuilder
)
}
44 changes: 44 additions & 0 deletions src/runtime/make_suggestion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
IdGenerator,
PickleStep,
PickleStepType,
Suggestion,
} from '@cucumber/messages'
import StepDefinitionSnippetBuilder from '../formatter/step_definition_snippet_builder'
import { KeywordType } from '../formatter/helpers'

function mapPickleStepTypeToKeywordType(type?: PickleStepType): KeywordType {
switch (type) {
case PickleStepType.CONTEXT:
return KeywordType.Precondition
case PickleStepType.ACTION:
return KeywordType.Event
case PickleStepType.OUTCOME:
return KeywordType.Outcome
default:
return KeywordType.Precondition
}
}

export function makeSuggestion({
newId,
snippetBuilder,
pickleStep,
}: {
newId: IdGenerator.NewId
snippetBuilder: StepDefinitionSnippetBuilder
pickleStep: PickleStep
}): Suggestion {
const keywordType = mapPickleStepTypeToKeywordType(pickleStep.type)
const codes = snippetBuilder.buildMultiple({ keywordType, pickleStep })
const snippets = codes.map((code) => ({
code,
language: 'javascript',
}))

return {
id: newId(),
pickleStepId: pickleStep.id,
snippets,
}
}
46 changes: 46 additions & 0 deletions src/runtime/make_suggestion_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, it } from 'mocha'
import { expect } from 'chai'
import { IdGenerator } from '@cucumber/messages'
import * as messages from '@cucumber/messages'
import { buildSupportCodeLibrary } from '../../test/runtime_helpers'
import FormatterBuilder from '../formatter/builder'
import { makeSuggestion } from './make_suggestion'

describe('makeSuggestion', () => {
it('generates multiple snippets for expressions with numeric parameters', async () => {
const supportCodeLibrary = buildSupportCodeLibrary()
const snippetBuilder =
await FormatterBuilder.getStepDefinitionSnippetBuilder({
cwd: process.cwd(),
supportCodeLibrary,
})
const newId = IdGenerator.incrementing()
const pickleStep: messages.PickleStep = {
id: '1',
text: 'I have 5 apples',
type: messages.PickleStepType.CONTEXT,
astNodeIds: [],
}

const suggestion = makeSuggestion({
newId,
snippetBuilder,
pickleStep,
})

expect(suggestion).to.deep.equal({
id: '0',
pickleStepId: '1',
snippets: [
{
code: "Given('I have {int} apples', function (int) {\n // Write code here that turns the phrase above into concrete actions\n return 'pending';\n});",
language: 'javascript',
},
{
code: "Given('I have {float} apples', function (float) {\n // Write code here that turns the phrase above into concrete actions\n return 'pending';\n});",
language: 'javascript',
},
],
})
})
})
6 changes: 6 additions & 0 deletions src/runtime/parallel/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AssembledTestCase } from '../../assemble'
import { ILogger, IRunEnvironment } from '../../environment'
import { RuntimeAdapter } from '../types'
import { IRunOptionsRuntime } from '../../api'
import { FormatOptions } from '../../formatter'
import {
FinalizeCommand,
InitializeCommand,
Expand Down Expand Up @@ -47,6 +48,10 @@ export class ChildProcessAdapter implements RuntimeAdapter {
private readonly logger: ILogger,
private readonly eventBroadcaster: EventEmitter,
private readonly options: IRunOptionsRuntime,
private readonly snippetOptions: Pick<
FormatOptions,
'snippetInterface' | 'snippetSyntax'
>,
private readonly supportCodeLibrary: SupportCodeLibrary
) {}

Expand Down Expand Up @@ -131,6 +136,7 @@ export class ChildProcessAdapter implements RuntimeAdapter {
this.supportCodeLibrary.afterTestRunHookDefinitions.map((h) => h.id),
},
options: this.options,
snippetOptions: this.snippetOptions,
} satisfies InitializeCommand)
}

Expand Down
2 changes: 2 additions & 0 deletions src/runtime/parallel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RuntimeOptions } from '../index'
import { ISupportCodeCoordinates } from '../../api'
import { AssembledTestCase } from '../../assemble'
import { CanonicalSupportCodeIds } from '../../support_code_library_builder/types'
import { FormatOptions } from '../../formatter'

// Messages from Coordinator to Worker

Expand All @@ -17,6 +18,7 @@ export interface InitializeCommand {
supportCodeCoordinates: ISupportCodeCoordinates
supportCodeIds: CanonicalSupportCodeIds
options: RuntimeOptions
snippetOptions: Pick<FormatOptions, 'snippetInterface' | 'snippetSyntax'>
}

export interface RunCommand {
Expand Down
14 changes: 13 additions & 1 deletion src/runtime/parallel/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SupportCodeLibrary } from '../../support_code_library_builder/types'
import tryRequire from '../../try_require'
import { Worker } from '../worker'
import { RuntimeOptions } from '../index'
import FormatterBuilder from '../../formatter/builder'
import {
WorkerToCoordinatorEvent,
CoordinatorToWorkerCommand,
Expand Down Expand Up @@ -58,6 +59,7 @@ export class ChildProcessWorker {
supportCodeCoordinates,
supportCodeIds,
options,
snippetOptions,
}: InitializeCommand): Promise<void> {
supportCodeLibraryBuilder.reset(
this.cwd,
Expand All @@ -75,13 +77,23 @@ export class ChildProcessWorker {
this.supportCodeLibrary = supportCodeLibraryBuilder.finalize(supportCodeIds)

this.options = options

const snippetBuilder =
await FormatterBuilder.getStepDefinitionSnippetBuilder({
cwd: this.cwd,
snippetInterface: snippetOptions.snippetInterface,
snippetSyntax: snippetOptions.snippetSyntax,
supportCodeLibrary: this.supportCodeLibrary,
})

this.worker = new Worker(
testRunStartedId,
this.id,
this.eventBroadcaster,
this.newId,
this.options,
this.supportCodeLibrary
this.supportCodeLibrary,
snippetBuilder
)
await this.worker.runBeforeAllHooks()
this.sendMessage({ type: 'READY' })
Expand Down
Loading