@g_package/jest-cucumber-fusion 2.0.0 → 3.0.1
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.
- package/README.md +444 -38
- package/dist/THIRD_PARTY_LICENSES.txt +91 -0
- package/dist/index.cjs +9924 -0
- package/dist/index.d.cts +145 -0
- package/package.json +60 -10
- package/scripts/prepare-hooks.js +23 -0
- package/src/code-suggestion.js +240 -0
- package/src/configuration.js +147 -0
- package/src/feature-source.js +329 -0
- package/src/index.d.ts +123 -13
- package/src/index.js +153 -420
- package/src/keywords.js +56 -0
- package/src/scenario-name.js +128 -0
- package/src/shared-state.js +36 -0
- package/src/step-argument.js +56 -0
- package/src/step-matching.js +89 -0
- package/src/tag-filter.js +90 -0
- package/src/test-registration.js +178 -0
- package/src/value-description.js +26 -0
- package/.prettierignore +0 -16
- package/.prettierrc.json +0 -0
- package/codecov +0 -0
- package/codecov.SHA256SUM +0 -1
- package/codecov.SHA256SUM.sig +0 -16
- package/docs/AdditionalConfiguration.md +0 -155
- package/docs/GherkinTables.md +0 -61
- package/docs/Language.md +0 -76
- package/docs/ReusingStepDefinitions.md +0 -110
- package/docs/RunningTheExamples.md +0 -16
- package/docs/ScenarioOutlines.md +0 -43
- package/docs/StepDefinitionArguments.md +0 -37
- package/docs/product/expectations/fix-l2-outline-regex/outline-regex-binds.md +0 -148
- package/docs/product/expectations/fix-l4-escaped-parens-outline/escaped-parens-bind-in-outlines.md +0 -106
- package/docs/product/expectations/fix-m3-singleton-reset/clean-slate-per-feature.md +0 -133
- package/test/specs/features/basic-scenarios.feature +0 -28
- package/test/specs/features/l3-step-argument-delivery.feature +0 -44
- package/test/specs/features/language.feature +0 -41
- package/test/specs/features/m6-hooks-once-per-test.feature +0 -21
- package/test/specs/features/m6-hooks-outline-only.feature +0 -12
- package/test/specs/features/reuse-definition.feature +0 -13
- package/test/specs/features/scenario-outline2.feature +0 -73
- package/test/specs/features/scenario-outlines.feature +0 -88
- package/test/specs/features/step-definitions/ambiguous-step-shadowing.steps.js +0 -92
- package/test/specs/features/step-definitions/basic-scenarios.steps.js +0 -62
- package/test/specs/features/step-definitions/fuzz-properties.steps.js +0 -170
- package/test/specs/features/step-definitions/hook-error.steps.js +0 -59
- package/test/specs/features/step-definitions/l2-outline-edge-cases.steps.js +0 -327
- package/test/specs/features/step-definitions/l3-step-argument-delivery.steps.js +0 -191
- package/test/specs/features/step-definitions/l4-escaped-parens-outline.steps.js +0 -256
- package/test/specs/features/step-definitions/language.steps.js +0 -86
- package/test/specs/features/step-definitions/m1-before-hooks-clobber.steps.js +0 -64
- package/test/specs/features/step-definitions/m2-duplicate-matcher.steps.js +0 -60
- package/test/specs/features/step-definitions/m3-singleton-reset.steps.js +0 -275
- package/test/specs/features/step-definitions/m4-callsite-resolution.steps.js +0 -76
- package/test/specs/features/step-definitions/m5-errors-false-silent-skip.steps.js +0 -80
- package/test/specs/features/step-definitions/m6-hooks-once-per-test.steps.js +0 -90
- package/test/specs/features/step-definitions/missing-feature-file.steps.js +0 -23
- package/test/specs/features/step-definitions/reuse-code.js +0 -22
- package/test/specs/features/step-definitions/reuse-definition.steps.js +0 -30
- package/test/specs/features/step-definitions/scenario-outline2.steps.js +0 -57
- package/test/specs/features/step-definitions/scenario-outlines.steps.js +0 -110
- package/test/specs/features/step-definitions/undefined-step.steps.js +0 -39
- package/test/specs/features/step-definitions/using-dynamic-values.steps.js +0 -70
- package/test/specs/features/step-definitions/using-gherkin-tables.steps.js +0 -42
- package/test/specs/features/undefined-step.feature +0 -4
- package/test/specs/features/using-dynamic-values.feature +0 -34
- package/test/specs/features/using-gherkin-tables.feature +0 -16
- package/test/src/bank-account.js +0 -17
- package/test/src/online-sales.js +0 -33
- package/test/src/rocket.js +0 -13
- 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
|
|
3
|
+
Write Cucumber feature files and run them as Jest tests, without the scaffolding.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/gotreasa/jest-cucumber-fusion/actions?query=workflow%3A%22Continuous+Integration%22)
|
|
6
6
|
[](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
|
-
|
|
15
|
-
|
|
16
|
-
Jest-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
52
|
-
|
|
102
|
+
### Add a Cucumber step definition file and load Fusion
|
|
53
103
|
```javascript
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
+
//filename: test/features/rocket-launching.steps.js
|
|
117
|
+
import { Given, When, Then, And, Fusion } from '@g_package/jest-cucumber-fusion'
|
|
70
118
|
|
|
71
|
-
|
|
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
|
-
|
|
129
|
+
//filename: test/features/rocket-launching.steps.js
|
|
130
|
+
import { Given, When, Then, And, Fusion } from '@g_package/jest-cucumber-fusion'
|
|
81
131
|
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
+
//filename: test/features/rocket-launching.steps.js
|
|
166
|
+
import { Given, When, Then, And, Fusion } from '@g_package/jest-cucumber-fusion'
|
|
111
167
|
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
```
|
|
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
|
-
|
|
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.
|