Skip to content

Commit

Permalink
feat: add support for custom parameter types
Browse files Browse the repository at this point in the history
  • Loading branch information
dnotes committed Nov 27, 2024
1 parent 62b41cb commit 2611f1d
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 8 deletions.
34 changes: 34 additions & 0 deletions .changeset/slow-walls-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
"quickpickle": minor
---

Added support for [custom parameter types], exactly as in CucumberJS.
A simple example:

```ts
// Step definition file
defineParameterType({
name: 'updown',
regexp: /(up|down)/,
})
Given('a number {int}', (world,num) => {
world.number = num
})
When('the number goes {updown} {int}', (world, updown, num) => {
if (updown === 'up') world.number += num
else world.number -= num
})
Then('the number should be {int}', (world, num) => {
expect(world.number).toBe(num)
})
```

```gherkin
Feature: Custom parameter types
Scenario: The number
Given a number 4
When the number goes up 5
Then the number should be 9
```

[custom parameter types]: https://github.com/cucumber/cucumber-js/blob/main/docs/support_files/api_reference.md#defineparametertypename-preferforregexpmatch-regexp-transformer-useforsnippets
6 changes: 1 addition & 5 deletions packages/main/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ by parsing them with the official [Gherkin Parser] and running them as Vitest te
- [x] [named hooks]
- [x] [tagged hooks]
- [x] [skipping in a before hook]: *(use [world.context.skip()])*
- [ ] [defineParameterType] for custom variable types in Cucumber Expressions *(WORKAROUND: use Regex)* **[PLANNED]**
- [x] [defineParameterType] for custom parameter types in Cucumber Expressions
- [x] [setDefaultTimeout] *(use [testTimeout] in Vitest config)*
- [x] [setDefinitionFunctionWrapper] *(use Before and After hooks)*
- [x] [setParallelCanAssign] *(use @concurrent and @sequential [special tags])*
Expand Down Expand Up @@ -448,10 +448,6 @@ come to notice:

#### Planned for feature completeness:

- [defineParameterType] for custom variable types in Cucumber Expressions

Defining custom parameters is not yet supported. As a workaround, one can use Regex for step definitions.

- [snippets] full support, including proper variables

Snippets are currently not well supported, as they don't yet include the proper variables or language formatting.
Expand Down
1 change: 1 addition & 0 deletions packages/main/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { normalizeTags, tagsMatch } from './tags';
export { setWorldConstructor, getWorldConstructor, QuickPickleWorld, QuickPickleWorldInterface } from './world';
export { DocString, DataTable }
export { explodeTags, tagsMatch, normalizeTags, applyHooks }
export { defineParameterType } from './steps'

const featureRegex = /\.feature(?:\.md)?$/;

Expand Down
21 changes: 19 additions & 2 deletions packages/main/src/steps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ExpressionFactory, ParameterTypeRegistry, Expression } from '@cucumber/cucumber-expressions';
import { ExpressionFactory, ParameterTypeRegistry, Expression, ParameterType } from '@cucumber/cucumber-expressions';
import { IParameterTypeDefinition } from '@cucumber/cucumber/lib/support_code_library_builder/types';

interface StepDefinition {
expression: string|RegExp;
Expand Down Expand Up @@ -27,7 +28,23 @@ const typeName: Record<string, string> = {
when: 'When',
};

const expressionFactory = new ExpressionFactory(new ParameterTypeRegistry());
const parameterTypeRegistry = new ParameterTypeRegistry();
const expressionFactory = new ExpressionFactory(parameterTypeRegistry);

const buildParameterType = (type:IParameterTypeDefinition<any>): ParameterType<unknown> => {
return new ParameterType(
type.name,
type.regexp,
null,
type.transformer,
type.useForSnippets ?? true,
type.preferForRegexpMatch ?? false,
)
}

export const defineParameterType = (parameterType: IParameterTypeDefinition<any>): void => {
parameterTypeRegistry.defineParameterType(buildParameterType(parameterType));
};

export const addStepDefinition = (expression: string|RegExp, f: (state: any, ...args: any[]) => any): void => {
const cucumberExpression = expressionFactory.createExpression(expression);
Expand Down
18 changes: 18 additions & 0 deletions packages/main/tests/test.feature
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,21 @@ Feature: Basic Test
And the variable "data.doubleEscapedBacktick" should be 3 characters long
And the variable "data.unescapedError" should be 31 characters long
And the variable "data.escapedError" should be 32 characters long
Rule: Custom parameter types must work
Example: a custom parameter for up or down
Given a number 1
And a number 2
Then the sum should be 3
When I push all the numbers up
Then the sum should be 5
When I push all the numbers down
And I push all the numbers down again
Then the sum should be 1
@fails @soft
Example: a custom parameter only matches its exact regex
Given a number 1
When I push all the numbers right
Then the error 1 should contain "Undefined. Implement with the following snippet:"
16 changes: 15 additions & 1 deletion packages/main/tests/tests.steps.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { expect } from "vitest";
import { Given, Then, When } from "../src";
import { Given, Then, When, defineParameterType } from "../src";
import type { DataTable } from "../src";
import { clone, get, set } from "lodash-es";
import type { DocString } from "../src/models/DocString";

defineParameterType({
name: 'updown',
regexp: /(up|down)/,
})

Given("I run the tests", () => {});

Then("the tests (should )pass", () => {
Expand Down Expand Up @@ -59,6 +64,10 @@ Then('the variable/value/property {string} should be {int} character(s) long', (
expect(value?.toString()?.length).toBe(parseInt(length))
})

Then('(the )error {int} should contain {string}', async (world, idx, expected) => {
let error = world.info.errors[idx-1]
await expect(error.message).toContain(expected)
})
Then('the stack for error {int} should contain {string}', async (world, idx, expected) => {
let stack = world.info.errors[idx-1].stack.split('\n')[0]
await expect(stack).toContain(expected)
Expand All @@ -76,4 +85,9 @@ When('I set a common flag', (world) => {
})
Then('the flag should be set', (world) => {
expect(world.common.flag).toBe(true)
})

// CUSTOM PARAMETER TYPES
When('I push all the numbers {updown}( again)', (world, updown:'up'|'down') => {
world.numbers = world.numbers.map(n => updown === 'up' ? n+1 : n-1)
})

0 comments on commit 2611f1d

Please sign in to comment.