@g_package/jest-cucumber-fusion 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +444 -38
  2. package/dist/THIRD_PARTY_LICENSES.txt +91 -0
  3. package/dist/index.cjs +9917 -0
  4. package/dist/index.d.cts +145 -0
  5. package/package.json +60 -10
  6. package/scripts/prepare-hooks.js +23 -0
  7. package/src/code-suggestion.js +240 -0
  8. package/src/configuration.js +147 -0
  9. package/src/feature-source.js +322 -0
  10. package/src/index.d.ts +123 -13
  11. package/src/index.js +153 -420
  12. package/src/keywords.js +56 -0
  13. package/src/scenario-name.js +128 -0
  14. package/src/shared-state.js +36 -0
  15. package/src/step-argument.js +56 -0
  16. package/src/step-matching.js +89 -0
  17. package/src/tag-filter.js +90 -0
  18. package/src/test-registration.js +178 -0
  19. package/src/value-description.js +26 -0
  20. package/.prettierignore +0 -16
  21. package/.prettierrc.json +0 -0
  22. package/codecov +0 -0
  23. package/codecov.SHA256SUM +0 -1
  24. package/codecov.SHA256SUM.sig +0 -16
  25. package/docs/AdditionalConfiguration.md +0 -155
  26. package/docs/GherkinTables.md +0 -61
  27. package/docs/Language.md +0 -76
  28. package/docs/ReusingStepDefinitions.md +0 -110
  29. package/docs/RunningTheExamples.md +0 -16
  30. package/docs/ScenarioOutlines.md +0 -43
  31. package/docs/StepDefinitionArguments.md +0 -37
  32. package/docs/product/expectations/fix-l2-outline-regex/outline-regex-binds.md +0 -148
  33. package/docs/product/expectations/fix-l4-escaped-parens-outline/escaped-parens-bind-in-outlines.md +0 -106
  34. package/docs/product/expectations/fix-m3-singleton-reset/clean-slate-per-feature.md +0 -133
  35. package/test/specs/features/basic-scenarios.feature +0 -28
  36. package/test/specs/features/l3-step-argument-delivery.feature +0 -44
  37. package/test/specs/features/language.feature +0 -41
  38. package/test/specs/features/m6-hooks-once-per-test.feature +0 -21
  39. package/test/specs/features/m6-hooks-outline-only.feature +0 -12
  40. package/test/specs/features/reuse-definition.feature +0 -13
  41. package/test/specs/features/scenario-outline2.feature +0 -73
  42. package/test/specs/features/scenario-outlines.feature +0 -88
  43. package/test/specs/features/step-definitions/ambiguous-step-shadowing.steps.js +0 -92
  44. package/test/specs/features/step-definitions/basic-scenarios.steps.js +0 -62
  45. package/test/specs/features/step-definitions/fuzz-properties.steps.js +0 -170
  46. package/test/specs/features/step-definitions/hook-error.steps.js +0 -59
  47. package/test/specs/features/step-definitions/l2-outline-edge-cases.steps.js +0 -327
  48. package/test/specs/features/step-definitions/l3-step-argument-delivery.steps.js +0 -191
  49. package/test/specs/features/step-definitions/l4-escaped-parens-outline.steps.js +0 -256
  50. package/test/specs/features/step-definitions/language.steps.js +0 -86
  51. package/test/specs/features/step-definitions/m1-before-hooks-clobber.steps.js +0 -64
  52. package/test/specs/features/step-definitions/m2-duplicate-matcher.steps.js +0 -60
  53. package/test/specs/features/step-definitions/m3-singleton-reset.steps.js +0 -275
  54. package/test/specs/features/step-definitions/m4-callsite-resolution.steps.js +0 -76
  55. package/test/specs/features/step-definitions/m5-errors-false-silent-skip.steps.js +0 -80
  56. package/test/specs/features/step-definitions/m6-hooks-once-per-test.steps.js +0 -90
  57. package/test/specs/features/step-definitions/missing-feature-file.steps.js +0 -23
  58. package/test/specs/features/step-definitions/reuse-code.js +0 -22
  59. package/test/specs/features/step-definitions/reuse-definition.steps.js +0 -30
  60. package/test/specs/features/step-definitions/scenario-outline2.steps.js +0 -57
  61. package/test/specs/features/step-definitions/scenario-outlines.steps.js +0 -110
  62. package/test/specs/features/step-definitions/undefined-step.steps.js +0 -39
  63. package/test/specs/features/step-definitions/using-dynamic-values.steps.js +0 -70
  64. package/test/specs/features/step-definitions/using-gherkin-tables.steps.js +0 -42
  65. package/test/specs/features/undefined-step.feature +0 -4
  66. package/test/specs/features/using-dynamic-values.feature +0 -34
  67. package/test/specs/features/using-gherkin-tables.feature +0 -16
  68. package/test/src/bank-account.js +0 -17
  69. package/test/src/online-sales.js +0 -33
  70. package/test/src/rocket.js +0 -13
  71. package/test/src/todo-list.js +0 -20
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Jest Cucumber Fusion
2
2
 
3
- Write 'pure' cucumber test in Jest without syntax clutter
3
+ Write Cucumber feature files and run them as Jest tests, without the scaffolding.
4
4
 
5
5
  [![Build Status](https://github.com/gotreasa/jest-cucumber-fusion/workflows/Continuous%20Integration/badge.svg)](https://github.com/gotreasa/jest-cucumber-fusion/actions?query=workflow%3A%22Continuous+Integration%22)
6
6
  [![Codecov](https://codecov.io/gh/gotreasa/jest-cucumber-fusion/branch/master/graph/badge.svg)](https://codecov.io/gh/gotreasa/jest-cucumber-fusion)
@@ -11,16 +11,15 @@ Write 'pure' cucumber test in Jest without syntax clutter
11
11
 
12
12
 
13
13
  ## Overview
14
- Build on top of [Jest-cucumber](https://github.com/bencompton/jest-cucumber), Jest-Cucumber-Fusion handle the writing of the corresponding Jest test steps using an uncluttered cucumber style.
15
- Instead of using `describe` and `it` blocks, you instead write a Jest test for each scenario, and then define `Given`, `When`, and `Then` step definitions inside of your Jest tests.
16
- Jest-Cucumber-Fusion then allows you to link these Cucumber tests to your javascript Cucumber feature steps.
17
- Adding a `Fusion`call, the links between your Feature definition and your Steps definition is handled automatically and the necessary scaffolding for jest-cucumber is build behind the scene.
18
- Now use jest naturally in your project like you would use the native Cucumber library.
14
+ Jest Cucumber Fusion runs your Cucumber feature files as Jest tests. You write the feature file in Gherkin, and a step definition file with a `Given`, `When`, `Then`, `And` or `But` definition for each step. A `Fusion` call at the end of the step definition file links the two: Fusion reads the feature and creates one Jest test per scenario, so you write no `describe` or `it` blocks yourself. Everything else is plain Jest: `expect`, mocks, coverage and reporting.
15
+
16
+ The style of this package began with [Jest-cucumber](https://github.com/bencompton/jest-cucumber), which it was originally built on top of. Since version 3 it no longer depends on that package: it runs on Jest and [@cucumber/gherkin](https://github.com/cucumber/gherkin) directly, and owns the whole test lifecycle itself.
17
+
18
+ This package continues [b-yond-infinite-network/jest-cucumber-fusion](https://github.com/b-yond-infinite-network/jest-cucumber-fusion), published on npm as `jest-cucumber-fusion` until version 0.8.1 in June 2021, and is now maintained and published as `@g_package/jest-cucumber-fusion`.
19
19
 
20
20
  ## Motivation
21
21
 
22
- Jest-cucumber is an amazing project but forces you to write a lot of repetitive scaffolding code to setup the link betwen Jest and Cucumber.
23
- With Jest-Cucumber-Fusion, it really takes only the minimal code possible:
22
+ Jest-cucumber has you write a `defineFeature` block, a `test` for every scenario and a callback for every step to link Jest and Cucumber. With Jest Cucumber Fusion you write only:
24
23
  - a Cucumber Feature file with gherkin sentences
25
24
  - a Cucumber Step definition file with your javascript validation code, ended with the `Fusion` function to link the two
26
25
 
@@ -28,16 +27,68 @@ With Jest-Cucumber-Fusion, it really takes only the minimal code possible:
28
27
 
29
28
  ## Getting Started
30
29
 
31
- ### Install Jest Cucumber Fusion:
30
+ These steps set up a project whose files are ES modules (they use `import` and `export`), which is the recommended way to use Fusion. If your project is CommonJS (it uses `require`), or you need Node 18, follow the same steps with the changes in [Using CommonJS instead](#using-commonjs-instead).
31
+
32
+ For ES modules you need Node 20.11 or newer.
32
33
 
34
+ ### Install Jest and Jest Cucumber Fusion:
35
+
36
+ ```
37
+ npm install --save-dev jest @g_package/jest-cucumber-fusion
33
38
  ```
34
- npm install @g_package/jest-cucumber-fusion --save-dev
39
+
40
+ Coming from `jest-cucumber-fusion` 0.8.x or from version 2 of this package? Your feature and step definition files keep their shape; the import name changes, and several behaviours that used to pass silently now fail with a message saying what to fix. [Migrating to version 3](./docs/Migrating.md) lists each change, observed under both versions.
41
+
42
+ ### Set these keys in your package.json:
43
+
44
+ ```json
45
+ "type": "module",
46
+ "scripts": { "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" },
47
+ "jest": { "testMatch": [ "**/*.steps.js" ] }
48
+ ```
49
+
50
+ Replace any `type` and `scripts.test` that are already there rather than adding a second copy: `npm init -y` writes `"type": "commonjs"` and a placeholder `test` script, and when a key appears twice in `package.json` the last one silently wins.
51
+
52
+ - `"type": "module"` makes Node read your `.js` files as ES modules. If you would rather not set it, name your files `.mjs` instead, and change `testMatch` to `[ "**/*.steps.mjs" ]`.
53
+ - Jest runs ES modules only in its ES module mode, which Node's `--experimental-vm-modules` flag turns on. This form of the `test` script is the one Jest's own documentation gives, and it does not depend on your shell's syntax. Node prints an `ExperimentalWarning` about the flag on each run. That is expected.
54
+ - `testMatch` tells Jest that your step definition files are the test files.
55
+
56
+ ### Lay out your project
57
+
58
+ The examples below use this layout. Any layout works, as long as each `import` path and each `Fusion` path matches where your files are.
59
+
60
+ ```
61
+ your-project/
62
+ ├── package.json
63
+ ├── src/
64
+ │ └── rocket.js the code under test
65
+ └── test/
66
+ └── features/
67
+ ├── rocket-launching.feature
68
+ └── rocket-launching.steps.js
69
+ ```
70
+
71
+ The code under test in these examples is a small class of your own:
72
+
73
+ ```javascript
74
+ //filename: src/rocket.js
75
+ export class Rocket {
76
+ constructor() {
77
+ this.isInSpace = false
78
+ this.boostersLanded = false
79
+ }
80
+
81
+ launch() {
82
+ this.isInSpace = true
83
+ this.boostersLanded = true
84
+ }
85
+ }
35
86
  ```
36
87
 
37
88
  ### Add a Feature file:
38
89
 
39
90
  ```gherkin
40
- ###filename: rocket-launching.feature
91
+ ###filename: test/features/rocket-launching.feature
41
92
  Feature: Rocket Launching
42
93
 
43
94
  Scenario: Launching a SpaceX rocket
@@ -48,38 +99,37 @@ Scenario: Launching a SpaceX rocket
48
99
  And nobody should doubt me ever again
49
100
  ```
50
101
 
51
- ### Add the following to your package.json configuration:
52
-
102
+ ### Add a Cucumber step definition file and load Fusion
53
103
  ```javascript
54
- "jest": { "testMatch": [ "**/*.steps.js" ] }
55
- ```
104
+ //filename: test/features/rocket-launching.steps.js
105
+ import { Given, When, Then, And, Fusion } from '@g_package/jest-cucumber-fusion'
56
106
 
107
+ ```
57
108
 
58
- ### Add a your Cucumber Step definition file and load Fusion
59
- ```javascript
60
- //filename: rocket-launching.steps.js
61
- const { Given, When, Then, And, But, Fusion } = require( '@g_package/jest-cucumber-fusion' )
109
+ Import only the keywords your steps use. `But` is also available.
62
110
 
63
- ```
111
+ **A step binds only to a definition registered with its own keyword.** An `And` step needs an `And(...)` definition, and a `Then(...)` definition with the same text does not serve it. That is why the steps below define `And` for the two `And` lines of the feature. When one definition should serve several keywords, chain it: `Then( And( 'text', fn ) )` registers the same definition under both. A step with no definition makes Fusion refuse the file, naming the step and suggesting the code to add.
64
112
 
65
113
  ### Load any dependency you need to do your test
66
114
 
67
115
  ```javascript
68
- //filename: rocket-launching.steps.js
69
- const { Given, When, Then, And, But, Fusion } = require( '@g_package/jest-cucumber-fusion' )
116
+ //filename: test/features/rocket-launching.steps.js
117
+ import { Given, When, Then, And, Fusion } from '@g_package/jest-cucumber-fusion'
70
118
 
71
- const { Rocket } = require( '../../src/rocket' )
119
+ import { Rocket } from '../../src/rocket.js'
72
120
  let rocket
73
121
 
74
122
  ```
75
123
 
124
+ A relative import names the file in full, extension included: `'../../src/rocket.js'`, not `'../../src/rocket'`.
125
+
76
126
  ### Add steps definitions:
77
127
 
78
128
  ```javascript
79
- //filename: rocket-launching.steps.js
80
- const { Given, When, Then, And, But, Fusion } = require( '@g_package/jest-cucumber-fusion' )
129
+ //filename: test/features/rocket-launching.steps.js
130
+ import { Given, When, Then, And, Fusion } from '@g_package/jest-cucumber-fusion'
81
131
 
82
- const { Rocket } = require( '../../src/rocket' )
132
+ import { Rocket } from '../../src/rocket.js'
83
133
  let rocket
84
134
 
85
135
  Given( 'I am Elon Musk attempting to launch a rocket into space', () => {
@@ -98,18 +148,24 @@ And( /^the booster\(s\) should land back on the launch pad$/, () => {
98
148
  expect(rocket.boostersLanded).toBe(true)
99
149
  } )
100
150
 
101
- But( 'nobody should doubt me ever again', () => {
151
+ And( 'nobody should doubt me ever again', () => {
102
152
  expect('people').not.toBe('haters')
103
153
  } )
104
154
  ```
105
155
 
156
+ `expect` and the other test globals work as usual. The `jest` object (for `jest.fn()` and friends) is not a global in Jest's ES module mode. Add `@jest/globals` to your `devDependencies` and import it:
157
+
158
+ ```javascript
159
+ import { jest } from '@jest/globals'
160
+ ```
161
+
106
162
  ### Adding the Fusion() call at the end of the Step definition file
107
- You have to match it with your Cucumber Feature definition file:
163
+ You have to match it with your Cucumber Feature definition file. The path is relative to the step definition file, so a feature file beside it is named on its own:
108
164
  ```javascript
109
- //filename: rocket-launching.steps.js
110
- const { Given, When, Then, And, But, Fusion } = require( '@g_package/jest-cucumber-fusion' )
165
+ //filename: test/features/rocket-launching.steps.js
166
+ import { Given, When, Then, And, Fusion } from '@g_package/jest-cucumber-fusion'
111
167
 
112
- const { Rocket } = require( '../../src/rocket' )
168
+ import { Rocket } from '../../src/rocket.js'
113
169
  let rocket
114
170
 
115
171
  Given( 'I am Elon Musk attempting to launch a rocket into space', () => {
@@ -128,7 +184,7 @@ And( /^the booster\(s\) should land back on the launch pad$/, () => {
128
184
  expect(rocket.boostersLanded).toBe(true)
129
185
  } )
130
186
 
131
- But( 'nobody should doubt me ever again', () => {
187
+ And( 'nobody should doubt me ever again', () => {
132
188
  expect('people').not.toBe('haters')
133
189
  } )
134
190
 
@@ -136,9 +192,19 @@ But( 'nobody should doubt me ever again', () => {
136
192
  Fusion( 'rocket-launching.feature' )
137
193
  ```
138
194
 
195
+ ### Run the tests
196
+
197
+ ```
198
+ npm test
199
+ ```
200
+
201
+ Jest reports one test for the scenario, named after it, and fails it at the first step that fails.
202
+
203
+ Writing your steps in TypeScript? See [Using TypeScript](#using-typescript).
204
+
139
205
  ## Adding coverage
140
- Since we're using jest, it is very easy to generate the code coverage of your Cucumber test:
141
- ```javascript
206
+ Fusion's tests are Jest tests, so Jest's coverage works as usual. Set these keys in your `package.json`:
207
+ ```json
142
208
  "jest": {
143
209
  "testMatch": [
144
210
  "**/*.steps.js"
@@ -152,13 +218,353 @@ Since we're using jest, it is very easy to generate the code coverage of your Cu
152
218
  }
153
219
  ```
154
220
 
221
+ For TypeScript step files, match `**/*.steps.ts` instead (see [Using TypeScript](#using-typescript)).
222
+
155
223
 
224
+ ## Setting options once for a whole run
225
+
226
+ Options can be passed to a single `Fusion` call:
227
+
228
+ ```javascript
229
+ Fusion( 'rocket-launching.feature', { tagFilter: '@smoke and not @slow' } )
230
+ ```
231
+
232
+ Or set once for every step definition file of a run, from a script that Jest's `setupFiles` lists. A global tag filter applies to every feature, so a scenario without a matching tag, such as the untagged rocket scenario above, is reported as skipped:
233
+
234
+ ```javascript
235
+ //filename: jest-fusion-config.js
236
+ import { setFusionConfiguration } from '@g_package/jest-cucumber-fusion'
237
+
238
+ setFusionConfiguration( { tagFilter: '@smoke and not @slow' } )
239
+ ```
240
+
241
+ ```json
242
+ "jest": {
243
+ "testMatch": [ "**/*.steps.js" ],
244
+ "setupFiles": [ "./jest-fusion-config.js" ]
245
+ }
246
+ ```
247
+
248
+ A per-call option still wins for its own file. See [Configuration options](./docs/AdditionalConfiguration.md) for every option and for the merge order.
249
+
250
+ If you are coming from version 2, global configuration used to go through `jest-cucumber`'s own `setJestCucumberConfiguration`. That package is no longer a dependency, so the import moves to `setFusionConfiguration` from this package; the options object is the same shape.
251
+
252
+ ## Linting with ESLint
253
+
254
+ [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) gives ESLint Jest's globals, such as `expect`, and its rules. One rule needs to be told about Fusion. `jest/no-standalone-expect` reports every `expect` that is not inside a `test` or `it` block, and in a step definition file every `expect` sits inside `Then`, `And` or another step function instead. Each step function and each `Before` and `After` hook runs inside the test that Fusion makes for its scenario, so tell the rule to treat them as test blocks.
255
+
256
+ Install ESLint and the plugin:
257
+
258
+ ```
259
+ npm install --save-dev eslint @eslint/js globals eslint-plugin-jest
260
+ ```
261
+
262
+ Then add an `eslint.config.js` to your project:
263
+
264
+ ```javascript
265
+ //filename: eslint.config.js
266
+ import js from '@eslint/js'
267
+ import globals from 'globals'
268
+ import jest from 'eslint-plugin-jest'
269
+
270
+ export default [
271
+ js.configs.recommended,
272
+ {
273
+ languageOptions: { sourceType: 'module', globals: globals.node },
274
+ },
275
+ {
276
+ files: [ '**/*.steps.js' ],
277
+ ...jest.configs[ 'flat/recommended' ],
278
+ rules: {
279
+ ...jest.configs[ 'flat/recommended' ].rules,
280
+ // Fusion runs each step and hook inside the test it makes for the scenario.
281
+ 'jest/no-standalone-expect': [ 'error', {
282
+ additionalTestBlockFunctions: [ 'Given', 'When', 'Then', 'And', 'But', 'Before', 'After' ],
283
+ } ],
284
+ },
285
+ },
286
+ ]
287
+ ```
288
+
289
+ Run it with `npx eslint .`. Without the `jest/no-standalone-expect` options, every `expect` in the steps file above is reported as `Expect must be inside of a test block`. With them, an `expect` that really is outside a step, a hook or a test is still reported.
290
+
291
+ If your shared step files are not named `*.steps.js` (see [Re-using step definitions](./docs/ReusingStepDefinitions.md)), add their names to `files`.
292
+
293
+ ## Using CommonJS instead
294
+
295
+ Fusion ships a CommonJS build beside its ES modules: `require` gets the CommonJS build and `import` gets the ES modules. Use CommonJS if your project already is CommonJS, if you need Node 18 (CommonJS runs on Node 18.14 and newer), or if you would rather not run Jest's experimental ES module mode. CommonJS needs no Jest set-up at all.
296
+
297
+ The steps in [Getting Started](#getting-started) work with these changes.
298
+
299
+ **package.json.** Leave out `"type": "module"`, and run Jest directly:
300
+
301
+ ```json
302
+ "scripts": { "test": "jest" },
303
+ "jest": { "testMatch": [ "**/*.steps.js" ] }
304
+ ```
305
+
306
+ **The code under test** exports with `module.exports`:
307
+
308
+ ```javascript
309
+ //filename: src/rocket.js
310
+ class Rocket {
311
+ constructor() {
312
+ this.isInSpace = false
313
+ this.boostersLanded = false
314
+ }
315
+
316
+ launch() {
317
+ this.isInSpace = true
318
+ this.boostersLanded = true
319
+ }
320
+ }
321
+
322
+ module.exports = { Rocket }
323
+ ```
324
+
325
+ **Step definition files** `require` the same names. A relative `require` may leave the extension out:
326
+
327
+ ```javascript
328
+ //filename: test/features/rocket-launching.steps.js
329
+ const { Given, When, Then, And, Fusion } = require( '@g_package/jest-cucumber-fusion' )
330
+
331
+ const { Rocket } = require( '../../src/rocket' )
332
+ let rocket
333
+
334
+ Given( 'I am Elon Musk attempting to launch a rocket into space', () => {
335
+ rocket = new Rocket()
336
+ } )
337
+
338
+ When( 'I launch the rocket', () => {
339
+ rocket.launch()
340
+ } )
341
+
342
+ Then( 'the rocket should end up in space', () => {
343
+ expect(rocket.isInSpace).toBe(true)
344
+ } )
345
+
346
+ And( /^the booster\(s\) should land back on the launch pad$/, () => {
347
+ expect(rocket.boostersLanded).toBe(true)
348
+ } )
349
+
350
+ And( 'nobody should doubt me ever again', () => {
351
+ expect('people').not.toBe('haters')
352
+ } )
353
+
354
+
355
+ Fusion( 'rocket-launching.feature' )
356
+ ```
357
+
358
+ The `jest` object is a global, as in any CommonJS Jest project.
359
+
360
+ **A global configuration script** `require`s `setFusionConfiguration`:
361
+
362
+ ```javascript
363
+ //filename: jest-fusion-config.js
364
+ const { setFusionConfiguration } = require( '@g_package/jest-cucumber-fusion' )
365
+
366
+ setFusionConfiguration( { tagFilter: '@smoke and not @slow' } )
367
+ ```
368
+
369
+ **ESLint.** The configuration file is CommonJS too, with `sourceType: 'commonjs'`:
370
+
371
+ ```javascript
372
+ //filename: eslint.config.js
373
+ const js = require( '@eslint/js' )
374
+ const globals = require( 'globals' )
375
+ const jest = require( 'eslint-plugin-jest' )
376
+
377
+ module.exports = [
378
+ js.configs.recommended,
379
+ {
380
+ languageOptions: { sourceType: 'commonjs', globals: globals.node },
381
+ },
382
+ {
383
+ files: [ '**/*.steps.js' ],
384
+ ...jest.configs[ 'flat/recommended' ],
385
+ rules: {
386
+ ...jest.configs[ 'flat/recommended' ].rules,
387
+ // Fusion runs each step and hook inside the test it makes for the scenario.
388
+ 'jest/no-standalone-expect': [ 'error', {
389
+ additionalTestBlockFunctions: [ 'Given', 'When', 'Then', 'And', 'But', 'Before', 'After' ],
390
+ } ],
391
+ },
392
+ },
393
+ ]
394
+ ```
395
+
396
+ **Mixing the two styles** works. A CommonJS shared step library can serve ES module step files, and a CommonJS setup script can configure ES module steps: both styles share one set of step definitions and one global configuration. An ES module imports a CommonJS file by its full name, for example `import './shared-steps.cjs'`.
397
+
398
+ The other pages in this documentation show ES modules. To use one of their examples in CommonJS, turn each `import { … } from '…'` into `const { … } = require( '…' )` and each `export` into `module.exports`.
399
+
400
+ ## Using TypeScript
401
+
402
+ Fusion ships its type definitions, and TypeScript finds the right ones for ES modules and CommonJS with no configuration. To run step definition files written in TypeScript, Jest needs a transform. [ts-jest](https://kulshekhar.github.io/ts-jest/) is the usual one.
403
+
404
+ ```
405
+ npm install --save-dev jest ts-jest typescript @types/jest @g_package/jest-cucumber-fusion
406
+ ```
407
+
408
+ ### ES modules (recommended)
409
+
410
+ ```json
411
+ "type": "module",
412
+ "scripts": {
413
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
414
+ "typecheck": "tsc --noEmit"
415
+ }
416
+ ```
417
+
418
+ ```json
419
+ //filename: tsconfig.json
420
+ {
421
+ "compilerOptions": {
422
+ "target": "ES2022",
423
+ "module": "NodeNext",
424
+ "moduleResolution": "NodeNext",
425
+ "isolatedModules": true,
426
+ "strict": true,
427
+ "types": [ "jest" ]
428
+ }
429
+ }
430
+ ```
431
+
432
+ ```javascript
433
+ //filename: jest.config.js
434
+ export default {
435
+ preset: 'ts-jest/presets/default-esm',
436
+ testMatch: [ '**/*.steps.ts' ],
437
+ moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1' },
438
+ }
439
+ ```
440
+
441
+ **Run `npm run typecheck` as well as `npm test`, for example in CI.** In ES module mode ts-jest only strips the types and does not check them, so a type error does not fail `npm test`; `tsc --noEmit` is what reports it. (`isolatedModules` tells ts-jest that this is expected. Without it, ts-jest warns `TS151002` and still does not check.)
442
+
443
+ `moduleNameMapper` lets your imports keep the `.js` extension that ES modules need, while Jest loads the `.ts` file: `import { Rocket } from '../../src/rocket.js'` loads `src/rocket.ts`.
444
+
445
+ ### CommonJS
446
+
447
+ Leave out `"type": "module"`, compile to CommonJS, and use ts-jest's default preset:
448
+
449
+ ```json
450
+ "scripts": {
451
+ "test": "jest",
452
+ "typecheck": "tsc --noEmit"
453
+ }
454
+ ```
455
+
456
+ ```json
457
+ {
458
+ "compilerOptions": {
459
+ "target": "ES2022",
460
+ "module": "CommonJS",
461
+ "strict": true,
462
+ "types": [ "jest" ]
463
+ }
464
+ }
465
+ ```
466
+
467
+ `"module": "CommonJS"` is what lets ts-jest check types here. With `NodeNext` or `Node16`, ts-jest warns `TS151002` and asks for `isolatedModules`, which would switch its checking off.
468
+
469
+ ```javascript
470
+ //filename: jest.config.js
471
+ module.exports = {
472
+ preset: 'ts-jest',
473
+ testMatch: [ '**/*.steps.ts' ],
474
+ moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1' },
475
+ }
476
+ ```
477
+
478
+ Here ts-jest checks types while it runs the tests, so a type error fails `npm test` too.
479
+
480
+ ### Typed step definitions
481
+
482
+ To move the [Getting Started](#getting-started) example to TypeScript, renaming its files to `.ts` is not enough under `strict`: `tsc --noEmit` then reports the `Rocket` class's fields as undeclared (`TS2339`) and `let rocket` as an implicit `any` (`TS7034`). Declare the fields in the class and write `let rocket: Rocket`, as the example below does. A step may also declare the type of each argument it receives: a capture or a docstring arrives as a `string`, and a data table as an `Array<Record<string, string>>` of its rows. For example, with this feature:
483
+
484
+ ```gherkin
485
+ ###filename: test/features/launch.feature
486
+ Feature: Launch
487
+
488
+ Scenario: Launching a batch of rockets
489
+ Given I am launching 3 rockets
490
+ When the countdown says
491
+ """
492
+ ignition
493
+ """
494
+ Then the manifest has
495
+ | name |
496
+ | Falcon |
497
+ ```
498
+
499
+ and this code under test:
500
+
501
+ ```typescript
502
+ //filename: src/launch.ts
503
+ export class Launch {
504
+ launched = 0
505
+ count: number
506
+
507
+ constructor( count: number ) {
508
+ this.count = count
509
+ }
510
+
511
+ countdown( word: string ): void {
512
+ if ( word === 'ignition' ) this.launched = this.count
513
+ }
514
+ }
515
+ ```
516
+
517
+ the steps declare a `string` for the capture and the docstring, and the rows for the table:
518
+
519
+ ```typescript
520
+ //filename: test/features/launch.steps.ts
521
+ import { Given, When, Then, Fusion } from '@g_package/jest-cucumber-fusion'
522
+
523
+ import { Launch } from '../../src/launch.js'
524
+
525
+ let launch: Launch
526
+
527
+ Given( /^I am launching (\d+) rockets$/, ( count: string ) => {
528
+ launch = new Launch( Number( count ) )
529
+ } )
530
+
531
+ When( 'the countdown says', ( words: string ) => {
532
+ launch.countdown( words.trim() )
533
+ } )
534
+
535
+ Then( 'the manifest has', ( rows: Array<Record<string, string>> ) => {
536
+ expect( rows ).toStrictEqual( [ { name: 'Falcon' } ] )
537
+ expect( launch.launched ).toBe( 3 )
538
+ } )
539
+
540
+ Fusion( 'launch.feature' )
541
+ ```
542
+
543
+ An argument you leave undeclared has the type `StepArgument` (`string | Array<Record<string, string>>`), which you can import from the package.
544
+
545
+ ## Troubleshooting
546
+
547
+ - **`SyntaxError: Unexpected token 'with'`** when Jest loads your ES module steps: your Node is older than 20.11, which ES module steps need. Upgrade Node, or [use CommonJS](#using-commonjs-instead), which runs from Node 18.14.
548
+ - **`npm warn EBADENGINE` on Node 18**, naming `brace-expansion` or `lru-cache`: these come from Jest 30's own dependencies, not from Fusion. CommonJS steps still run on Node 18.14 and newer.
549
+ - **ESLint on Node 18:** ESLint 10 itself needs Node 20.19 or newer. On Node 18.18 or newer, install ESLint 9 and the matching Jest plugin instead (`npm install --save-dev eslint@9 @eslint/js@9 eslint-plugin-jest@28 globals`); the configuration above works with them.
550
+ - **`No step definition matches`** for a step you did define: the step's keyword decides which definitions it can bind to. An `And` step needs an `And(...)` definition (see [Getting Started](#add-a-cucumber-step-definition-file-and-load-fusion)).
551
+ - **Jest prints only the summary, without the list of tests:** Jest shortens its output when it detects an AI agent's environment variables, such as `AI_AGENT` or `CLAUDECODE`. Run it from a terminal that does not have them.
552
+
156
553
  ## Additional Documentation
157
554
 
158
- * [Gherkin tables](./docs/GherkinTables.md)
555
+ Using the package:
556
+
159
557
  * [Step definition arguments](./docs/StepDefinitionArguments.md)
558
+ * [Gherkin tables](./docs/GherkinTables.md)
160
559
  * [Scenario outlines](./docs/ScenarioOutlines.md)
161
- * [Re-using step definitions](./docs/ReusingStepDefinitions.md)
162
- * [Configuration options](./docs/AdditionalConfiguration.md)
163
- * [Running the examples](./docs/RunningTheExamples.md)
560
+ * [Re-using step definitions](./docs/ReusingStepDefinitions.md)
164
561
  * [Language](./docs/Language.md)
562
+ * [Configuration options](./docs/AdditionalConfiguration.md): every export and option
563
+ * [Migrating to version 3](./docs/Migrating.md)
564
+
565
+ Working on the package:
566
+
567
+ * [Running the examples](./docs/RunningTheExamples.md)
568
+ * [Architecture](./docs/Architecture.md)
569
+
570
+ Every example on the guide pages above runs as written: the repository's tests copy each one into a fresh project and run it.
@@ -0,0 +1,91 @@
1
+ dist/index.cjs bundles the following packages.
2
+
3
+ @cucumber/gherkin@42.0.1 (MIT)
4
+
5
+ MIT License
6
+
7
+ Copyright (c) 2017 Cucumber Ltd, Gaspar Nagy, Björn Rasmusson, Peter Sergeant, and contributors
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ ------------------------------------------------------------------------
28
+
29
+ @cucumber/messages@34.2.1 (MIT)
30
+
31
+ MIT License
32
+
33
+ Copyright (c) 2018 Cucumber Ltd and contributors
34
+
35
+ Permission is hereby granted, free of charge, to any person obtaining a copy
36
+ of this software and associated documentation files (the "Software"), to deal
37
+ in the Software without restriction, including without limitation the rights
38
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
39
+ copies of the Software, and to permit persons to whom the Software is
40
+ furnished to do so, subject to the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be included in all
43
+ copies or substantial portions of the Software.
44
+
45
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
50
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
51
+ SOFTWARE.
52
+
53
+ ------------------------------------------------------------------------
54
+
55
+ @cucumber/tag-expressions@11.0.1 (MIT)
56
+
57
+ MIT License
58
+
59
+ Copyright (c) 2016 Cucumber Ltd and contributors
60
+
61
+ Permission is hereby granted, free of charge, to any person obtaining a copy
62
+ of this software and associated documentation files (the "Software"), to deal
63
+ in the Software without restriction, including without limitation the rights
64
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
65
+ copies of the Software, and to permit persons to whom the Software is
66
+ furnished to do so, subject to the following conditions:
67
+
68
+ The above copyright notice and this permission notice shall be included in all
69
+ copies or substantial portions of the Software.
70
+
71
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
72
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
73
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
74
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
75
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
76
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
77
+ SOFTWARE.
78
+
79
+ ------------------------------------------------------------------------
80
+
81
+ callsites@4.2.0 (MIT)
82
+
83
+ MIT License
84
+
85
+ Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
86
+
87
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
88
+
89
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
90
+
91
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.