From 5c39fbdc47a48de2251322d4d5a1506e2a7f2f83 Mon Sep 17 00:00:00 2001 From: davidgoss Date: Wed, 2 Jun 2021 07:28:29 +0100 Subject: [PATCH 1/6] type this as any in user fns, add test --- src/support_code_library_builder/types.ts | 36 ++++++++++------------- test-d/world.ts | 32 ++++++++++++++++++++ 2 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 test-d/world.ts diff --git a/src/support_code_library_builder/types.ts b/src/support_code_library_builder/types.ts index 9e22f38e9..3d0789472 100644 --- a/src/support_code_library_builder/types.ts +++ b/src/support_code_library_builder/types.ts @@ -22,21 +22,17 @@ export interface ITestStepHookParameter { testStepId: string } -export type TestCaseHookFunctionWithoutParameter = () => any | Promise -export type TestCaseHookFunctionWithParameter = ( - arg: ITestCaseHookParameter +export type TestCaseHookFunction = ( + this: any, + arg?: ITestCaseHookParameter ) => any | Promise -export type TestCaseHookFunction = - | TestCaseHookFunctionWithoutParameter - | TestCaseHookFunctionWithParameter -export type TestStepHookFunctionWithoutParameter = () => void -export type TestStepHookFunctionWithParameter = ( - arg: ITestStepHookParameter +export type TestStepHookFunction = ( + this: any, + arg?: ITestStepHookParameter ) => void -export type TestStepHookFunction = - | TestStepHookFunctionWithoutParameter - | TestStepHookFunctionWithParameter + +export type TestStepFunction = (this: any, ...args: any[]) => any | Promise export interface IDefineStepOptions { timeout?: number @@ -67,11 +63,11 @@ export interface IParameterTypeDefinition { export interface IDefineSupportCodeMethods { defineParameterType: (options: IParameterTypeDefinition) => void - defineStep: ((pattern: DefineStepPattern, code: Function) => void) & + defineStep: ((pattern: DefineStepPattern, code: TestStepFunction) => void) & (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: Function + code: TestStepFunction ) => void) setDefaultTimeout: (milliseconds: number) => void setDefinitionFunctionWrapper: (fn: Function) => void @@ -92,23 +88,23 @@ export interface IDefineSupportCodeMethods { ((options: IDefineTestStepHookOptions, code: TestStepHookFunction) => void) BeforeAll: ((code: Function) => void) & ((options: IDefineTestRunHookOptions, code: Function) => void) - Given: ((pattern: DefineStepPattern, code: Function) => void) & + Given: ((pattern: DefineStepPattern, code: TestStepFunction) => void) & (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: Function + code: TestStepFunction ) => void) - Then: ((pattern: DefineStepPattern, code: Function) => void) & + Then: ((pattern: DefineStepPattern, code: TestStepFunction) => void) & (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: Function + code: TestStepFunction ) => void) - When: ((pattern: DefineStepPattern, code: Function) => void) & + When: ((pattern: DefineStepPattern, code: TestStepFunction) => void) & (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: Function + code: TestStepFunction ) => void) } diff --git a/test-d/world.ts b/test-d/world.ts new file mode 100644 index 000000000..a23166183 --- /dev/null +++ b/test-d/world.ts @@ -0,0 +1,32 @@ +import { Before, When, World } from '../' + +// should allow us to read parameters and add attachments +Before(async function () { + await this.attach(this.parameters.foo) +}) +When('stuff happens', async function () { + await this.attach(this.parameters.foo) +}) + +// should allow us to set and get arbitrary properties +Before(async function () { + this.bar = 'baz' + await this.log(this.baz) +}) +When('stuff happens', async function () { + this.bar = 'baz' + await this.log(this.baz) +}) + +// should allow us to use a custom world class +class CustomWorld extends World { + doThing(): string { + return 'foo' + } +} +Before(async function (this: CustomWorld) { + this.doThing() +}) +When('stuff happens', async function (this: CustomWorld) { + this.doThing() +}) From 7f6f97b24d9ea63b2f9473ee7eafae45e63b7355 Mon Sep 17 00:00:00 2001 From: davidgoss Date: Wed, 2 Jun 2021 16:30:15 +0100 Subject: [PATCH 2/6] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1860fc347..23eeaf782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO ### Fixed +* `this` now explicitly typed in support code functions ([#1667](https://github.com/cucumber/cucumber-js/issues/1667) [#1690](https://github.com/cucumber/cucumber-js/pull/1690)) * Progress bar formatter now reports total step count correctly ([#1579](https://github.com/cucumber/cucumber-js/issues/1579) [#1669](https://github.com/cucumber/cucumber-js/pull/1669)) * All messages now emitted with project-relative `uri`s From d5791d942f39e969acd0e0962cfc086bee991f8c Mon Sep 17 00:00:00 2001 From: davidgoss Date: Wed, 2 Jun 2021 16:31:42 +0100 Subject: [PATCH 3/6] setWorldConstructor for completeness --- test-d/world.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test-d/world.ts b/test-d/world.ts index a23166183..dd6817af4 100644 --- a/test-d/world.ts +++ b/test-d/world.ts @@ -1,4 +1,4 @@ -import { Before, When, World } from '../' +import { Before, setWorldConstructor, When, World } from '../' // should allow us to read parameters and add attachments Before(async function () { @@ -24,6 +24,7 @@ class CustomWorld extends World { return 'foo' } } +setWorldConstructor(CustomWorld) Before(async function (this: CustomWorld) { this.doThing() }) From ecdb88d8796913e525aef273985bc8cca670d623 Mon Sep 17 00:00:00 2001 From: David Goss Date: Thu, 3 Jun 2021 12:47:17 +0100 Subject: [PATCH 4/6] use generics to do it right --- dependency-lint.yml | 1 + src/index.ts | 1 + src/support_code_library_builder/index.ts | 24 +++---- src/support_code_library_builder/types.ts | 86 +++++++++++++++-------- src/support_code_library_builder/world.ts | 9 ++- test-d/world.ts | 13 ++++ 6 files changed, 92 insertions(+), 42 deletions(-) diff --git a/dependency-lint.yml b/dependency-lint.yml index d85654c33..ffc2979a0 100644 --- a/dependency-lint.yml +++ b/dependency-lint.yml @@ -36,6 +36,7 @@ requiredModules: dev: - '{compatibility,features,scripts,test}/**/*' - '**/*_spec.ts' + - 'test-d/**/*.ts' - 'example/index.ts' - '**/test_helpers.ts' ignore: diff --git a/src/index.ts b/src/index.ts index de2d072c7..5f7926e23 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,6 +40,7 @@ export const Then = methods.Then export const When = methods.When export { default as World, + IWorld, IWorldOptions, } from './support_code_library_builder/world' export const Status = messages.TestStepResultStatus diff --git a/src/support_code_library_builder/index.ts b/src/support_code_library_builder/index.ts index 7fee92afd..bacb180ac 100644 --- a/src/support_code_library_builder/index.ts +++ b/src/support_code_library_builder/index.ts @@ -149,13 +149,13 @@ export class SupportCodeLibraryBuilder { defineTestCaseHook( getCollection: () => ITestCaseHookDefinitionConfig[] - ): ( - options: string | IDefineTestCaseHookOptions | TestCaseHookFunction, - code?: TestCaseHookFunction + ): ( + options: string | IDefineTestCaseHookOptions | TestCaseHookFunction, + code?: TestCaseHookFunction ) => void { - return ( - options: string | IDefineTestCaseHookOptions | TestCaseHookFunction, - code?: TestCaseHookFunction + return ( + options: string | IDefineTestCaseHookOptions | TestCaseHookFunction, + code?: TestCaseHookFunction ) => { if (typeof options === 'string') { options = { tags: options } @@ -180,13 +180,13 @@ export class SupportCodeLibraryBuilder { defineTestStepHook( getCollection: () => ITestStepHookDefinitionConfig[] - ): ( - options: string | IDefineTestStepHookOptions | TestStepHookFunction, - code?: TestStepHookFunction + ): ( + options: string | IDefineTestStepHookOptions | TestStepHookFunction, + code?: TestStepHookFunction ) => void { - return ( - options: string | IDefineTestStepHookOptions | TestStepHookFunction, - code?: TestStepHookFunction + return ( + options: string | IDefineTestStepHookOptions | TestStepHookFunction, + code?: TestStepHookFunction ) => { if (typeof options === 'string') { options = { tags: options } diff --git a/src/support_code_library_builder/types.ts b/src/support_code_library_builder/types.ts index 3d0789472..a2f1b36c2 100644 --- a/src/support_code_library_builder/types.ts +++ b/src/support_code_library_builder/types.ts @@ -4,6 +4,7 @@ import TestStepHookDefinition from '../models/test_step_hook_definition' import TestRunHookDefinition from '../models/test_run_hook_definition' import StepDefinition from '../models/step_definition' import { ParameterTypeRegistry } from '@cucumber/cucumber-expressions' +import { IWorld } from './world' export type DefineStepPattern = string | RegExp @@ -22,17 +23,20 @@ export interface ITestStepHookParameter { testStepId: string } -export type TestCaseHookFunction = ( - this: any, +export type TestCaseHookFunction = ( + this: W, arg?: ITestCaseHookParameter ) => any | Promise -export type TestStepHookFunction = ( - this: any, +export type TestStepHookFunction = ( + this: W, arg?: ITestStepHookParameter ) => void -export type TestStepFunction = (this: any, ...args: any[]) => any | Promise +export type TestStepFunction = ( + this: W, + ...args: any[] +) => any | Promise export interface IDefineStepOptions { timeout?: number @@ -63,48 +67,72 @@ export interface IParameterTypeDefinition { export interface IDefineSupportCodeMethods { defineParameterType: (options: IParameterTypeDefinition) => void - defineStep: ((pattern: DefineStepPattern, code: TestStepFunction) => void) & - (( + defineStep: (( + pattern: DefineStepPattern, + code: TestStepFunction + ) => void) & + (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: TestStepFunction + code: TestStepFunction ) => void) setDefaultTimeout: (milliseconds: number) => void setDefinitionFunctionWrapper: (fn: Function) => void setWorldConstructor: (fn: any) => void - After: ((code: TestCaseHookFunction) => void) & - ((tags: string, code: TestCaseHookFunction) => void) & - ((options: IDefineTestCaseHookOptions, code: TestCaseHookFunction) => void) - AfterStep: ((code: TestStepHookFunction) => void) & - ((tags: string, code: TestStepHookFunction) => void) & - ((options: IDefineTestStepHookOptions, code: TestStepHookFunction) => void) + After: ((code: TestCaseHookFunction) => void) & + ((tags: string, code: TestCaseHookFunction) => void) & + (( + options: IDefineTestCaseHookOptions, + code: TestCaseHookFunction + ) => void) + AfterStep: ((code: TestStepHookFunction) => void) & + ((tags: string, code: TestStepHookFunction) => void) & + (( + options: IDefineTestStepHookOptions, + code: TestStepHookFunction + ) => void) AfterAll: ((code: Function) => void) & ((options: IDefineTestRunHookOptions, code: Function) => void) - Before: ((code: TestCaseHookFunction) => void) & - ((tags: string, code: TestCaseHookFunction) => void) & - ((options: IDefineTestCaseHookOptions, code: TestCaseHookFunction) => void) - BeforeStep: ((code: TestStepHookFunction) => void) & - ((tags: string, code: TestStepHookFunction) => void) & - ((options: IDefineTestStepHookOptions, code: TestStepHookFunction) => void) + Before: ((code: TestCaseHookFunction) => void) & + ((tags: string, code: TestCaseHookFunction) => void) & + (( + options: IDefineTestCaseHookOptions, + code: TestCaseHookFunction + ) => void) + BeforeStep: ((code: TestStepHookFunction) => void) & + ((tags: string, code: TestStepHookFunction) => void) & + (( + options: IDefineTestStepHookOptions, + code: TestStepHookFunction + ) => void) BeforeAll: ((code: Function) => void) & ((options: IDefineTestRunHookOptions, code: Function) => void) - Given: ((pattern: DefineStepPattern, code: TestStepFunction) => void) & - (( + Given: (( + pattern: DefineStepPattern, + code: TestStepFunction + ) => void) & + (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: TestStepFunction + code: TestStepFunction ) => void) - Then: ((pattern: DefineStepPattern, code: TestStepFunction) => void) & - (( + Then: (( + pattern: DefineStepPattern, + code: TestStepFunction + ) => void) & + (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: TestStepFunction + code: TestStepFunction ) => void) - When: ((pattern: DefineStepPattern, code: TestStepFunction) => void) & - (( + When: (( + pattern: DefineStepPattern, + code: TestStepFunction + ) => void) & + (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: TestStepFunction + code: TestStepFunction ) => void) } diff --git a/src/support_code_library_builder/world.ts b/src/support_code_library_builder/world.ts index e918c63fb..1d142c4ac 100644 --- a/src/support_code_library_builder/world.ts +++ b/src/support_code_library_builder/world.ts @@ -6,7 +6,14 @@ export interface IWorldOptions { parameters: any } -export default class World { +export interface IWorld { + readonly attach: ICreateAttachment + readonly log: ICreateLog + readonly parameters: any + [key: string]: any +} + +export default class World implements IWorld { public readonly attach: ICreateAttachment public readonly log: ICreateLog public readonly parameters: any diff --git a/test-d/world.ts b/test-d/world.ts index dd6817af4..45bc75cb6 100644 --- a/test-d/world.ts +++ b/test-d/world.ts @@ -1,4 +1,5 @@ import { Before, setWorldConstructor, When, World } from '../' +import { expectError } from 'tsd' // should allow us to read parameters and add attachments Before(async function () { @@ -8,6 +9,18 @@ When('stuff happens', async function () { await this.attach(this.parameters.foo) }) +// should prevent reassignment of parameters +expectError( + Before(async function () { + this.parameters = null + }) +) +expectError( + When('stuff happens', async function () { + this.parameters = null + }) +) + // should allow us to set and get arbitrary properties Before(async function () { this.bar = 'baz' From f7fbd5b5b4a8e067c7ecfd2b4c22a48d542f456f Mon Sep 17 00:00:00 2001 From: David Goss Date: Thu, 3 Jun 2021 12:54:36 +0100 Subject: [PATCH 5/6] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23eeaf782..3c73ea45f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO ### Fixed -* `this` now explicitly typed in support code functions ([#1667](https://github.com/cucumber/cucumber-js/issues/1667) [#1690](https://github.com/cucumber/cucumber-js/pull/1690)) +* `this` now has correct TypeScript type in support code functions ([#1667](https://github.com/cucumber/cucumber-js/issues/1667) [#1690](https://github.com/cucumber/cucumber-js/pull/1690)) * Progress bar formatter now reports total step count correctly ([#1579](https://github.com/cucumber/cucumber-js/issues/1579) [#1669](https://github.com/cucumber/cucumber-js/pull/1669)) * All messages now emitted with project-relative `uri`s From ac95567e54062cf4357245dd32727ee34bbe5f31 Mon Sep 17 00:00:00 2001 From: David Goss Date: Thu, 3 Jun 2021 16:06:35 +0100 Subject: [PATCH 6/6] use a clearer generic type name --- src/support_code_library_builder/index.ts | 36 ++++++--- src/support_code_library_builder/types.ts | 94 ++++++++++++++--------- 2 files changed, 80 insertions(+), 50 deletions(-) diff --git a/src/support_code_library_builder/index.ts b/src/support_code_library_builder/index.ts index bacb180ac..d9febcd9b 100644 --- a/src/support_code_library_builder/index.ts +++ b/src/support_code_library_builder/index.ts @@ -149,13 +149,19 @@ export class SupportCodeLibraryBuilder { defineTestCaseHook( getCollection: () => ITestCaseHookDefinitionConfig[] - ): ( - options: string | IDefineTestCaseHookOptions | TestCaseHookFunction, - code?: TestCaseHookFunction + ): ( + options: + | string + | IDefineTestCaseHookOptions + | TestCaseHookFunction, + code?: TestCaseHookFunction ) => void { - return ( - options: string | IDefineTestCaseHookOptions | TestCaseHookFunction, - code?: TestCaseHookFunction + return ( + options: + | string + | IDefineTestCaseHookOptions + | TestCaseHookFunction, + code?: TestCaseHookFunction ) => { if (typeof options === 'string') { options = { tags: options } @@ -180,13 +186,19 @@ export class SupportCodeLibraryBuilder { defineTestStepHook( getCollection: () => ITestStepHookDefinitionConfig[] - ): ( - options: string | IDefineTestStepHookOptions | TestStepHookFunction, - code?: TestStepHookFunction + ): ( + options: + | string + | IDefineTestStepHookOptions + | TestStepHookFunction, + code?: TestStepHookFunction ) => void { - return ( - options: string | IDefineTestStepHookOptions | TestStepHookFunction, - code?: TestStepHookFunction + return ( + options: + | string + | IDefineTestStepHookOptions + | TestStepHookFunction, + code?: TestStepHookFunction ) => { if (typeof options === 'string') { options = { tags: options } diff --git a/src/support_code_library_builder/types.ts b/src/support_code_library_builder/types.ts index a2f1b36c2..ef0d8f763 100644 --- a/src/support_code_library_builder/types.ts +++ b/src/support_code_library_builder/types.ts @@ -23,18 +23,18 @@ export interface ITestStepHookParameter { testStepId: string } -export type TestCaseHookFunction = ( - this: W, +export type TestCaseHookFunction = ( + this: WorldType, arg?: ITestCaseHookParameter ) => any | Promise -export type TestStepHookFunction = ( - this: W, +export type TestStepHookFunction = ( + this: WorldType, arg?: ITestStepHookParameter ) => void -export type TestStepFunction = ( - this: W, +export type TestStepFunction = ( + this: WorldType, ...args: any[] ) => any | Promise @@ -67,72 +67,90 @@ export interface IParameterTypeDefinition { export interface IDefineSupportCodeMethods { defineParameterType: (options: IParameterTypeDefinition) => void - defineStep: (( + defineStep: (( pattern: DefineStepPattern, - code: TestStepFunction + code: TestStepFunction ) => void) & - (( + (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: TestStepFunction + code: TestStepFunction ) => void) setDefaultTimeout: (milliseconds: number) => void setDefinitionFunctionWrapper: (fn: Function) => void setWorldConstructor: (fn: any) => void - After: ((code: TestCaseHookFunction) => void) & - ((tags: string, code: TestCaseHookFunction) => void) & - (( + After: ((code: TestCaseHookFunction) => void) & + (( + tags: string, + code: TestCaseHookFunction + ) => void) & + (( options: IDefineTestCaseHookOptions, - code: TestCaseHookFunction + code: TestCaseHookFunction ) => void) - AfterStep: ((code: TestStepHookFunction) => void) & - ((tags: string, code: TestStepHookFunction) => void) & - (( + AfterStep: (( + code: TestStepHookFunction + ) => void) & + (( + tags: string, + code: TestStepHookFunction + ) => void) & + (( options: IDefineTestStepHookOptions, - code: TestStepHookFunction + code: TestStepHookFunction ) => void) AfterAll: ((code: Function) => void) & ((options: IDefineTestRunHookOptions, code: Function) => void) - Before: ((code: TestCaseHookFunction) => void) & - ((tags: string, code: TestCaseHookFunction) => void) & - (( + Before: (( + code: TestCaseHookFunction + ) => void) & + (( + tags: string, + code: TestCaseHookFunction + ) => void) & + (( options: IDefineTestCaseHookOptions, - code: TestCaseHookFunction + code: TestCaseHookFunction ) => void) - BeforeStep: ((code: TestStepHookFunction) => void) & - ((tags: string, code: TestStepHookFunction) => void) & - (( + BeforeStep: (( + code: TestStepHookFunction + ) => void) & + (( + tags: string, + code: TestStepHookFunction + ) => void) & + (( options: IDefineTestStepHookOptions, - code: TestStepHookFunction + code: TestStepHookFunction ) => void) BeforeAll: ((code: Function) => void) & ((options: IDefineTestRunHookOptions, code: Function) => void) - Given: (( + Given: (( pattern: DefineStepPattern, - code: TestStepFunction + code: TestStepFunction ) => void) & - (( + (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: TestStepFunction + code: TestStepFunction ) => void) - Then: (( + Then: (( pattern: DefineStepPattern, - code: TestStepFunction + code: TestStepFunction ) => void) & - (( + (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: TestStepFunction + code: TestStepFunction ) => void) - When: (( + When: (( pattern: DefineStepPattern, - code: TestStepFunction + code: TestStepFunction ) => void) & - (( + (( pattern: DefineStepPattern, options: IDefineStepOptions, - code: TestStepFunction + code: TestStepFunction ) => void) }