From 8a864d0b4516cd0df0aea74710bba9104701b528 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 11:20:54 +0000 Subject: [PATCH 01/13] change cck sample code --- test/cck/parameter-types/support.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cck/parameter-types/support.mjs b/test/cck/parameter-types/support.mjs index faee4ae6..b1829145 100644 --- a/test/cck/parameter-types/support.mjs +++ b/test/cck/parameter-types/support.mjs @@ -14,7 +14,7 @@ class Flight { ParameterType({ name: 'flight', regexp: /([A-Z]{3})-([A-Z]{3})/, - transformer(from, to) { + transformer(t, from, to) { return new Flight(from, to) }, }) From 04c98a609ba6018260cd34de56c928f3522f93ff Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 13:47:53 +0000 Subject: [PATCH 02/13] redo parameter type interface --- src/index.ts | 4 +++- src/types.ts | 3 ++- src/wrapTransformer.ts | 12 ++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 src/wrapTransformer.ts diff --git a/src/index.ts b/src/index.ts index e0476361..22e284a6 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,7 +38,7 @@ export function WorldCreator( * @example ParameterType(\{ * name: 'flight', * regexp: /([A-Z]\{3\})-([A-Z]\{3\})/, - * transformer(from, to) \{ + * transformer(t, from, to) \{ * return new Flight(from, to) * \}, * \}) @@ -45,6 +46,7 @@ export function WorldCreator( export function ParameterType(options: ParameterTypeOptions) { coreBuilder.parameterType({ ...options, + transformer: wrapTransformer(options.transformer), sourceReference: makeSourceReference(), }) } diff --git a/src/types.ts b/src/types.ts index 7e8ae9a8..69c2d9cc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -103,11 +103,12 @@ export type ParameterTypeOptions = { /** * A function for transforming the matched values to another object before passing to * the step function + * @param context - context object for the current test * @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?: (this: World, context: TestCaseContext, ...match: string[]) => unknown /** * Whether this parameter type should be used when suggesting snippets for missing step * definitions diff --git a/src/wrapTransformer.ts b/src/wrapTransformer.ts new file mode 100644 index 00000000..2744fa9c --- /dev/null +++ b/src/wrapTransformer.ts @@ -0,0 +1,12 @@ +import { NewParameterType } from '@cucumber/core/dist/types.js' + +import { ParameterTypeOptions, TestCaseContext } from './types.js' + +export function wrapTransformer(transformer: ParameterTypeOptions['transformer']): NewParameterType['transformer'] { + if (transformer) { + return function(this: TestCaseContext, ...match: string[]) { + return transformer.apply(this.world, [this, ...match]) + } + } + return undefined +} \ No newline at end of file From fa5c5cfa3a18137c12839c596a4e600a094590ae Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 13:54:14 +0000 Subject: [PATCH 03/13] implement with new core changes --- src/runner/ExecutableTestStep.ts | 12 +++++++++--- src/wrapTransformer.ts | 8 +++++--- 2 files changed, 14 insertions(+), 6 deletions(-) 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/wrapTransformer.ts b/src/wrapTransformer.ts index 2744fa9c..4f4806cc 100644 --- a/src/wrapTransformer.ts +++ b/src/wrapTransformer.ts @@ -2,11 +2,13 @@ import { NewParameterType } from '@cucumber/core/dist/types.js' import { ParameterTypeOptions, TestCaseContext } from './types.js' -export function wrapTransformer(transformer: ParameterTypeOptions['transformer']): NewParameterType['transformer'] { +export function wrapTransformer( + transformer: ParameterTypeOptions['transformer'] +): NewParameterType['transformer'] { if (transformer) { - return function(this: TestCaseContext, ...match: string[]) { + return function (this: TestCaseContext, ...match: string[]) { return transformer.apply(this.world, [this, ...match]) } } return undefined -} \ No newline at end of file +} From 7bd97c3cb2077dd9c297d2d74bf01d7fac0ba227 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 18:09:15 +0000 Subject: [PATCH 04/13] bump core library --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) 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", From 4d3017aefc9eccfb5feb75dd5fd27d8135ea0237 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 18:10:18 +0000 Subject: [PATCH 05/13] regenerate api spec --- cucumber-node.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber-node.api.md b/cucumber-node.api.md index 18c1eb98..bd542729 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?: (this: World, context: TestCaseContext, ...match: string[]) => unknown; useForSnippets?: boolean; preferForRegexpMatch?: boolean; }; From 548bb24b3b380f82469ba53edae63b846ebc609a Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 19:03:51 +0000 Subject: [PATCH 06/13] improve typing and testing --- src/types.ts | 8 +++++++- src/wrapTransformer.spec.ts | 40 +++++++++++++++++++++++++++++++++++++ src/wrapTransformer.ts | 6 +++--- 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 src/wrapTransformer.spec.ts diff --git a/src/types.ts b/src/types.ts index 69c2d9cc..8165f879 100644 --- a/src/types.ts +++ b/src/types.ts @@ -108,7 +108,7 @@ export type ParameterTypeOptions = { * @remarks * If not provided, the raw matched value(s) will be passed to the step function. */ - transformer?: (this: World, context: TestCaseContext, ...match: string[]) => unknown + transformer?: TransformerFunction /** * Whether this parameter type should be used when suggesting snippets for missing step * definitions @@ -139,6 +139,12 @@ export type HookOptions = { tags?: string } +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..b4bcae4f --- /dev/null +++ b/src/wrapTransformer.spec.ts @@ -0,0 +1,40 @@ +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('wrapped transformer handles multiple match arguments', () => { + 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 index 4f4806cc..2105f376 100644 --- a/src/wrapTransformer.ts +++ b/src/wrapTransformer.ts @@ -1,9 +1,9 @@ -import { NewParameterType } from '@cucumber/core/dist/types.js' +import { NewParameterType } from '@cucumber/core' -import { ParameterTypeOptions, TestCaseContext } from './types.js' +import { TestCaseContext, TransformerFunction } from './types.js' export function wrapTransformer( - transformer: ParameterTypeOptions['transformer'] + transformer?: TransformerFunction ): NewParameterType['transformer'] { if (transformer) { return function (this: TestCaseContext, ...match: string[]) { From ce7032df64431fa25f91a360c7c0ada9fcd936f7 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 19:05:47 +0000 Subject: [PATCH 07/13] update api again --- cucumber-node.api.md | 5 ++++- src/types.ts | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cucumber-node.api.md b/cucumber-node.api.md index bd542729..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, context: TestCaseContext, ...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/src/types.ts b/src/types.ts index 8165f879..69652a9b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -139,6 +139,10 @@ 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, From bdee9d9bb1bdbe9dbefc11377c8a39c8117fa3ae Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 19:18:58 +0000 Subject: [PATCH 08/13] commentary --- src/wrapTransformer.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wrapTransformer.ts b/src/wrapTransformer.ts index 2105f376..3310fb6c 100644 --- a/src/wrapTransformer.ts +++ b/src/wrapTransformer.ts @@ -2,6 +2,13 @@ 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'] { From b5428ea5f9f9933108a0f46a136e625ef65d5e1d Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 19:20:43 +0000 Subject: [PATCH 09/13] update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) 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)) From a66760291b16624f44a5f674fe48427987487fa4 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 19:38:13 +0000 Subject: [PATCH 10/13] make cck sample nicer --- test/cck/parameter-types/support.mjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/cck/parameter-types/support.mjs b/test/cck/parameter-types/support.mjs index b1829145..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(t, from, to) { - return new Flight(from, to) - }, + transformer: (t, from, to) => new Flight(from, to), }) Given('{flight} has been delayed', (t, flight) => { From 968659e83f0fd3d01413acfe3ebd92f9b6c8f6cc Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 19:40:56 +0000 Subject: [PATCH 11/13] make doc example nicer too --- src/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 22e284a6..dc4d00e6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,9 +38,7 @@ export function WorldCreator( * @example ParameterType(\{ * name: 'flight', * regexp: /([A-Z]\{3\})-([A-Z]\{3\})/, - * transformer(t, from, to) \{ - * return new Flight(from, to) - * \}, + * transformer: (t, from, to) => new Flight(from, to) * \}) */ export function ParameterType(options: ParameterTypeOptions) { From 393545a11bfe32aecdf6c830250df2f41675eb74 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 19:47:09 +0000 Subject: [PATCH 12/13] trim doc comment --- src/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index 69652a9b..d9bdb661 100644 --- a/src/types.ts +++ b/src/types.ts @@ -103,8 +103,6 @@ export type ParameterTypeOptions = { /** * A function for transforming the matched values to another object before passing to * the step function - * @param context - context object for the current test - * @param match - matched values from the regular expression * @remarks * If not provided, the raw matched value(s) will be passed to the step function. */ From 016ede6c0a98a6341dc5ed7ef8f96a5aabad3ec8 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 25 Nov 2025 19:48:48 +0000 Subject: [PATCH 13/13] clean up test --- src/wrapTransformer.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wrapTransformer.spec.ts b/src/wrapTransformer.spec.ts index b4bcae4f..d3e90604 100644 --- a/src/wrapTransformer.spec.ts +++ b/src/wrapTransformer.spec.ts @@ -10,7 +10,7 @@ describe('wrapTransformer', () => { expect(result).to.eq(undefined) }) - it('wrapped transformer handles multiple match arguments', () => { + 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, @@ -27,7 +27,6 @@ describe('wrapTransformer', () => { const wrapped = wrapTransformer(transformer)! const testContext = { world: { foo: 'bar' } } as TestCaseContext - const result = wrapped.call(testContext, 'a', 'b') expect(result).to.deep.eq({