Skip to content

Commit

Permalink
typescript: type this as IWorld in user functions (cucumber#1690)
Browse files Browse the repository at this point in the history
* type this as any in user fns, add test

* update changelog

* setWorldConstructor for completeness

* use generics to do it right

* Update CHANGELOG.md

* use a clearer generic type name
  • Loading branch information
davidjgoss committed Jun 4, 2021
1 parent 9e89686 commit 8a0d5e8
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO

### Fixed

* `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))
* Rerun functionality will now run nothing if the rerun file is empty from the previous run ([#1302](https://github.com/cucumber/cucumber-js/issues/1302) [#1568](https://github.com/cucumber/cucumber-js/pull/1568))
Expand Down
1 change: 1 addition & 0 deletions dependency-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ requiredModules:
dev:
- '{compatibility,features,scripts,test}/**/*'
- '**/*_spec.ts'
- 'test-d/**/*.ts'
- 'example/index.ts'
- '**/test_helpers.ts'
ignore:
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
36 changes: 24 additions & 12 deletions src/support_code_library_builder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,19 @@ export class SupportCodeLibraryBuilder {

defineTestCaseHook(
getCollection: () => ITestCaseHookDefinitionConfig[]
): (
options: string | IDefineTestCaseHookOptions | TestCaseHookFunction,
code?: TestCaseHookFunction
): <WorldType>(
options:
| string
| IDefineTestCaseHookOptions
| TestCaseHookFunction<WorldType>,
code?: TestCaseHookFunction<WorldType>
) => void {
return (
options: string | IDefineTestCaseHookOptions | TestCaseHookFunction,
code?: TestCaseHookFunction
return <WorldType>(
options:
| string
| IDefineTestCaseHookOptions
| TestCaseHookFunction<WorldType>,
code?: TestCaseHookFunction<WorldType>
) => {
if (typeof options === 'string') {
options = { tags: options }
Expand All @@ -180,13 +186,19 @@ export class SupportCodeLibraryBuilder {

defineTestStepHook(
getCollection: () => ITestStepHookDefinitionConfig[]
): (
options: string | IDefineTestStepHookOptions | TestStepHookFunction,
code?: TestStepHookFunction
): <WorldType>(
options:
| string
| IDefineTestStepHookOptions
| TestStepHookFunction<WorldType>,
code?: TestStepHookFunction<WorldType>
) => void {
return (
options: string | IDefineTestStepHookOptions | TestStepHookFunction,
code?: TestStepHookFunction
return <WorldType>(
options:
| string
| IDefineTestStepHookOptions
| TestStepHookFunction<WorldType>,
code?: TestStepHookFunction<WorldType>
) => {
if (typeof options === 'string') {
options = { tags: options }
Expand Down
114 changes: 78 additions & 36 deletions src/support_code_library_builder/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -22,21 +23,20 @@ export interface ITestStepHookParameter {
testStepId: string
}

export type TestCaseHookFunctionWithoutParameter = () => any | Promise<any>
export type TestCaseHookFunctionWithParameter = (
arg: ITestCaseHookParameter
export type TestCaseHookFunction<WorldType> = (
this: WorldType,
arg?: ITestCaseHookParameter
) => any | Promise<any>
export type TestCaseHookFunction =
| TestCaseHookFunctionWithoutParameter
| TestCaseHookFunctionWithParameter

export type TestStepHookFunctionWithoutParameter = () => void
export type TestStepHookFunctionWithParameter = (
arg: ITestStepHookParameter
export type TestStepHookFunction<WorldType> = (
this: WorldType,
arg?: ITestStepHookParameter
) => void
export type TestStepHookFunction =
| TestStepHookFunctionWithoutParameter
| TestStepHookFunctionWithParameter

export type TestStepFunction<WorldType> = (
this: WorldType,
...args: any[]
) => any | Promise<any>

export interface IDefineStepOptions {
timeout?: number
Expand Down Expand Up @@ -67,48 +67,90 @@ export interface IParameterTypeDefinition<T> {

export interface IDefineSupportCodeMethods {
defineParameterType: (options: IParameterTypeDefinition<any>) => void
defineStep: ((pattern: DefineStepPattern, code: Function) => void) &
((
defineStep: (<WorldType = IWorld>(
pattern: DefineStepPattern,
code: TestStepFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
pattern: DefineStepPattern,
options: IDefineStepOptions,
code: Function
code: TestStepFunction<WorldType>
) => 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: (<WorldType = IWorld>(code: TestCaseHookFunction<WorldType>) => void) &
(<WorldType = IWorld>(
tags: string,
code: TestCaseHookFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
options: IDefineTestCaseHookOptions,
code: TestCaseHookFunction<WorldType>
) => void)
AfterStep: (<WorldType = IWorld>(
code: TestStepHookFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
tags: string,
code: TestStepHookFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
options: IDefineTestStepHookOptions,
code: TestStepHookFunction<WorldType>
) => 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: (<WorldType = IWorld>(
code: TestCaseHookFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
tags: string,
code: TestCaseHookFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
options: IDefineTestCaseHookOptions,
code: TestCaseHookFunction<WorldType>
) => void)
BeforeStep: (<WorldType = IWorld>(
code: TestStepHookFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
tags: string,
code: TestStepHookFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
options: IDefineTestStepHookOptions,
code: TestStepHookFunction<WorldType>
) => void)
BeforeAll: ((code: Function) => void) &
((options: IDefineTestRunHookOptions, code: Function) => void)
Given: ((pattern: DefineStepPattern, code: Function) => void) &
((
Given: (<WorldType = IWorld>(
pattern: DefineStepPattern,
code: TestStepFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
pattern: DefineStepPattern,
options: IDefineStepOptions,
code: Function
code: TestStepFunction<WorldType>
) => void)
Then: ((pattern: DefineStepPattern, code: Function) => void) &
((
Then: (<WorldType = IWorld>(
pattern: DefineStepPattern,
code: TestStepFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
pattern: DefineStepPattern,
options: IDefineStepOptions,
code: Function
code: TestStepFunction<WorldType>
) => void)
When: ((pattern: DefineStepPattern, code: Function) => void) &
((
When: (<WorldType = IWorld>(
pattern: DefineStepPattern,
code: TestStepFunction<WorldType>
) => void) &
(<WorldType = IWorld>(
pattern: DefineStepPattern,
options: IDefineStepOptions,
code: Function
code: TestStepFunction<WorldType>
) => void)
}

Expand Down
9 changes: 8 additions & 1 deletion src/support_code_library_builder/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions test-d/world.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Before, setWorldConstructor, When, World } from '../'
import { expectError } from 'tsd'

// 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 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'
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'
}
}
setWorldConstructor(CustomWorld)
Before(async function (this: CustomWorld) {
this.doThing()
})
When('stuff happens', async function (this: CustomWorld) {
this.doThing()
})

0 comments on commit 8a0d5e8

Please sign in to comment.