diff --git a/CHANGELOG.md b/CHANGELOG.md index 64dc287a..604fe37f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Provide source maps for pickles ([#149](https://github.com/cucumber/cucumber-node/pull/149)) - Support returning "skipped" or "pending" from user code ([#155](https://github.com/cucumber/cucumber-node/pull/155)) +### Changed +- BREAKING CHANGE: Prepend context to parameter transformers ([#160](https://github.com/cucumber/cucumber-node/pull/160)) + ## [0.5.0] - 2025-11-17 ### Added - Support Node.js 24 ([#67](https://github.com/cucumber/cucumber-node/pull/67)) diff --git a/cucumber-node.api.md b/cucumber-node.api.md index 18c1eb98..e87d927f 100644 --- a/cucumber-node.api.md +++ b/cucumber-node.api.md @@ -48,7 +48,7 @@ export function ParameterType(options: ParameterTypeOptions): void; export type ParameterTypeOptions = { name: string; regexp: RegExp | string | readonly RegExp[] | readonly string[]; - transformer?: (this: World, ...match: string[]) => unknown; + transformer?: TransformerFunction; useForSnippets?: boolean; preferForRegexpMatch?: boolean; }; @@ -71,6 +71,9 @@ export type TestCaseContext = { // @public export function Then(pattern: string | RegExp, fn: StepFunction): void; +// @public +export type TransformerFunction = (this: World, context: TestCaseContext, ...match: string[]) => unknown; + // @public export function When(pattern: string | RegExp, fn: StepFunction): void; diff --git a/package-lock.json b/package-lock.json index f2d98e72..216778eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@babel/generator": "^7.28.5", "@babel/types": "^7.28.5", "@cucumber/ci-environment": "12.0.0", - "@cucumber/core": "0.7.0", + "@cucumber/core": "0.8.0", "@cucumber/cucumber-expressions": "18.0.1", "@cucumber/gherkin": "37.0.0", "@cucumber/html-formatter": "22.1.0", @@ -139,9 +139,9 @@ "license": "MIT" }, "node_modules/@cucumber/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cucumber/core/-/core-0.7.0.tgz", - "integrity": "sha512-dEjZuQj6LmXkOvn7fphxf+t9nUpNT+KNLsmXnmmC4L/emonniNhb59FpuYEwNOhUZhJn7Irha2t4WSKATKGDyw==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cucumber/core/-/core-0.8.0.tgz", + "integrity": "sha512-BY/GE96ZijyVlz47Xt8vE3XRSrYSUewLjJlQVVsX3SiPRfsWaO8lTl6PyaH2Pid+l0GeVVNII+ynXHD7l5npJA==", "license": "MIT", "peerDependencies": { "@cucumber/cucumber-expressions": "*", diff --git a/package.json b/package.json index ec236f47..32e9b92d 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "@babel/generator": "^7.28.5", "@babel/types": "^7.28.5", "@cucumber/ci-environment": "12.0.0", - "@cucumber/core": "0.7.0", + "@cucumber/core": "0.8.0", "@cucumber/cucumber-expressions": "18.0.1", "@cucumber/gherkin": "37.0.0", "@cucumber/html-formatter": "22.1.0", diff --git a/src/index.ts b/src/index.ts index e0476361..dc4d00e6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import { Promisable } from 'type-fest' import { makeSourceReference } from './makeSourceReference.js' import { coreBuilder, extraBuilder } from './runner/state.js' import { HookFunction, HookOptions, ParameterTypeOptions, StepFunction, World } from './types.js' +import { wrapTransformer } from './wrapTransformer.js' export * from './types.js' export { DataTable } from '@cucumber/core' @@ -37,14 +38,13 @@ export function WorldCreator( * @example ParameterType(\{ * name: 'flight', * regexp: /([A-Z]\{3\})-([A-Z]\{3\})/, - * transformer(from, to) \{ - * return new Flight(from, to) - * \}, + * transformer: (t, from, to) => new Flight(from, to) * \}) */ export function ParameterType(options: ParameterTypeOptions) { coreBuilder.parameterType({ ...options, + transformer: wrapTransformer(options.transformer), sourceReference: makeSourceReference(), }) } diff --git a/src/runner/ExecutableTestStep.ts b/src/runner/ExecutableTestStep.ts index c7a69a35..9f840507 100644 --- a/src/runner/ExecutableTestStep.ts +++ b/src/runner/ExecutableTestStep.ts @@ -1,7 +1,7 @@ import { TestContext } from 'node:test' import { styleText } from 'node:util' -import { AssembledTestStep } from '@cucumber/core' +import { AssembledTestStep, DataTable } from '@cucumber/core' import { TestStepResult } from '@cucumber/messages' import { makeTimestamp } from '../makeTimestamp.js' @@ -39,9 +39,15 @@ export class ExecutableTestStep { async execute(nodeTestContext: TestContext) { let success = false try { - const { fn, args } = this.assembledStep.prepare(this.parent.world) + const { fn, args, dataTable, docString } = this.assembledStep.prepare() const context = this.makeContext(nodeTestContext) - const returned = await fn(context, ...args) + const fnArgs: Array = [context, ...args.map((arg) => arg.getValue(context))] + if (dataTable) { + fnArgs.push(DataTable.from(dataTable)) + } else if (docString) { + fnArgs.push(docString.content) + } + const returned = await fn.apply(context.world, fnArgs) if (returned === 'skipped') { context.skip() } else if (returned === 'pending') { diff --git a/src/types.ts b/src/types.ts index 7e8ae9a8..d9bdb661 100644 --- a/src/types.ts +++ b/src/types.ts @@ -103,11 +103,10 @@ export type ParameterTypeOptions = { /** * A function for transforming the matched values to another object before passing to * the step function - * @param match - matched values from the regular expression * @remarks * If not provided, the raw matched value(s) will be passed to the step function. */ - transformer?: (this: World, ...match: string[]) => unknown + transformer?: TransformerFunction /** * Whether this parameter type should be used when suggesting snippets for missing step * definitions @@ -138,6 +137,16 @@ export type HookOptions = { tags?: string } +/** + * A function to transform a raw parameter value into a user-defined type + * @public + */ +export type TransformerFunction = ( + this: World, + context: TestCaseContext, + ...match: string[] +) => unknown + /** * A function to be executed as a hook * @public diff --git a/src/wrapTransformer.spec.ts b/src/wrapTransformer.spec.ts new file mode 100644 index 00000000..d3e90604 --- /dev/null +++ b/src/wrapTransformer.spec.ts @@ -0,0 +1,39 @@ +import { expect } from 'chai' + +import { TestCaseContext } from './types.js' +import { wrapTransformer } from './wrapTransformer.js' + +describe('wrapTransformer', () => { + it('returns undefined when transformer is undefined', () => { + const result = wrapTransformer(undefined) + + expect(result).to.eq(undefined) + }) + + it('prepends context to args and uses world as this', () => { + const transformer = function ( + this: any, // eslint-disable-line @typescript-eslint/no-explicit-any + context: TestCaseContext, + match1: string, + match2: string + ) { + return { + fromThis: this.foo, + fromContext: context.world.foo, + match1, + match2, + } + } + + const wrapped = wrapTransformer(transformer)! + const testContext = { world: { foo: 'bar' } } as TestCaseContext + const result = wrapped.call(testContext, 'a', 'b') + + expect(result).to.deep.eq({ + fromThis: 'bar', + fromContext: 'bar', + match1: 'a', + match2: 'b', + }) + }) +}) diff --git a/src/wrapTransformer.ts b/src/wrapTransformer.ts new file mode 100644 index 00000000..3310fb6c --- /dev/null +++ b/src/wrapTransformer.ts @@ -0,0 +1,21 @@ +import { NewParameterType } from '@cucumber/core' + +import { TestCaseContext, TransformerFunction } from './types.js' + +/* + * Turn the user-supplied transformer function into one we can provide to + * the ParameterTypeRegistry - which only supports a custom `this` for + * modifying behaviour - while maintaining our signature of prepending + * the context as the first arg, and setting the world as `this` for + * continuity when coming from cucumber-js + */ +export function wrapTransformer( + transformer?: TransformerFunction +): NewParameterType['transformer'] { + if (transformer) { + return function (this: TestCaseContext, ...match: string[]) { + return transformer.apply(this.world, [this, ...match]) + } + } + return undefined +} diff --git a/test/cck/parameter-types/support.mjs b/test/cck/parameter-types/support.mjs index faee4ae6..67559731 100644 --- a/test/cck/parameter-types/support.mjs +++ b/test/cck/parameter-types/support.mjs @@ -14,9 +14,7 @@ class Flight { ParameterType({ name: 'flight', regexp: /([A-Z]{3})-([A-Z]{3})/, - transformer(from, to) { - return new Flight(from, to) - }, + transformer: (t, from, to) => new Flight(from, to), }) Given('{flight} has been delayed', (t, flight) => {