@g_package/jest-cucumber-fusion 1.0.0 → 2.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.
- package/README.md +9 -10
- package/codecov +0 -0
- package/codecov.SHA256SUM +1 -1
- package/codecov.SHA256SUM.sig +13 -13
- package/docs/product/expectations/fix-l2-outline-regex/outline-regex-binds.md +148 -0
- package/docs/product/expectations/fix-l4-escaped-parens-outline/escaped-parens-bind-in-outlines.md +106 -0
- package/docs/product/expectations/fix-m3-singleton-reset/clean-slate-per-feature.md +133 -0
- package/package.json +11 -5
- package/src/index.d.ts +15 -5
- package/src/index.js +196 -102
- package/test/specs/features/l3-step-argument-delivery.feature +44 -0
- package/test/specs/features/m6-hooks-once-per-test.feature +21 -0
- package/test/specs/features/m6-hooks-outline-only.feature +12 -0
- package/test/specs/features/scenario-outline2.feature +6 -4
- package/test/specs/features/step-definitions/ambiguous-step-shadowing.steps.js +92 -0
- package/test/specs/features/step-definitions/basic-scenarios.steps.js +19 -2
- package/test/specs/features/step-definitions/fuzz-properties.steps.js +170 -0
- package/test/specs/features/step-definitions/hook-error.steps.js +59 -0
- package/test/specs/features/step-definitions/l2-outline-edge-cases.steps.js +327 -0
- package/test/specs/features/step-definitions/l3-step-argument-delivery.steps.js +191 -0
- package/test/specs/features/step-definitions/l4-escaped-parens-outline.steps.js +256 -0
- package/test/specs/features/step-definitions/m1-before-hooks-clobber.steps.js +64 -0
- package/test/specs/features/step-definitions/m2-duplicate-matcher.steps.js +60 -0
- package/test/specs/features/step-definitions/m3-singleton-reset.steps.js +275 -0
- package/test/specs/features/step-definitions/m4-callsite-resolution.steps.js +76 -0
- package/test/specs/features/step-definitions/m5-errors-false-silent-skip.steps.js +80 -0
- package/test/specs/features/step-definitions/m6-hooks-once-per-test.steps.js +90 -0
- package/test/specs/features/step-definitions/missing-feature-file.steps.js +23 -0
- package/test/specs/features/step-definitions/reuse-code.js +9 -4
- package/test/specs/features/step-definitions/reuse-definition.steps.js +9 -1
- package/test/specs/features/step-definitions/scenario-outline2.steps.js +28 -17
- package/test/specs/features/step-definitions/undefined-step.steps.js +39 -0
- package/test/specs/features/undefined-step.feature +4 -0
package/README.md
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Write 'pure' cucumber test in Jest without syntax clutter
|
|
4
4
|
|
|
5
|
-
[](https://github.com/b-yond-infinite-network/jest-cucumber-fusion/actions?query=workflow%3APublish)
|
|
5
|
+
[](https://github.com/gotreasa/jest-cucumber-fusion/actions?query=workflow%3A%22Continuous+Integration%22)
|
|
6
|
+
[](https://codecov.io/gh/gotreasa/jest-cucumber-fusion)
|
|
8
7
|
|
|
9
|
-
[](https://www.npmjs.com/package/jest-cucumber-fusion)
|
|
10
|
-
[](https://www.npmjs.com/package/jest-cucumber-fusion)
|
|
8
|
+
[](https://www.npmjs.com/package/@g_package/jest-cucumber-fusion)
|
|
9
|
+
[](https://www.npmjs.com/package/@g_package/jest-cucumber-fusion)
|
|
11
10
|
[](https://github.com/semantic-release/semantic-release)
|
|
12
11
|
|
|
13
12
|
|
|
@@ -32,7 +31,7 @@ With Jest-Cucumber-Fusion, it really takes only the minimal code possible:
|
|
|
32
31
|
### Install Jest Cucumber Fusion:
|
|
33
32
|
|
|
34
33
|
```
|
|
35
|
-
npm install jest-cucumber-fusion --save-dev
|
|
34
|
+
npm install @g_package/jest-cucumber-fusion --save-dev
|
|
36
35
|
```
|
|
37
36
|
|
|
38
37
|
### Add a Feature file:
|
|
@@ -59,7 +58,7 @@ Scenario: Launching a SpaceX rocket
|
|
|
59
58
|
### Add a your Cucumber Step definition file and load Fusion
|
|
60
59
|
```javascript
|
|
61
60
|
//filename: rocket-launching.steps.js
|
|
62
|
-
const { Given, When, Then, And, But, Fusion } = require( 'jest-cucumber-fusion' )
|
|
61
|
+
const { Given, When, Then, And, But, Fusion } = require( '@g_package/jest-cucumber-fusion' )
|
|
63
62
|
|
|
64
63
|
```
|
|
65
64
|
|
|
@@ -67,7 +66,7 @@ const { Given, When, Then, And, But, Fusion } = require( 'jest-cucumber-fusion'
|
|
|
67
66
|
|
|
68
67
|
```javascript
|
|
69
68
|
//filename: rocket-launching.steps.js
|
|
70
|
-
const { Given, When, Then, And, But, Fusion } = require( 'jest-cucumber-fusion' )
|
|
69
|
+
const { Given, When, Then, And, But, Fusion } = require( '@g_package/jest-cucumber-fusion' )
|
|
71
70
|
|
|
72
71
|
const { Rocket } = require( '../../src/rocket' )
|
|
73
72
|
let rocket
|
|
@@ -78,7 +77,7 @@ let rocket
|
|
|
78
77
|
|
|
79
78
|
```javascript
|
|
80
79
|
//filename: rocket-launching.steps.js
|
|
81
|
-
const { Given, When, Then, And, But, Fusion } = require( 'jest-cucumber-fusion' )
|
|
80
|
+
const { Given, When, Then, And, But, Fusion } = require( '@g_package/jest-cucumber-fusion' )
|
|
82
81
|
|
|
83
82
|
const { Rocket } = require( '../../src/rocket' )
|
|
84
83
|
let rocket
|
|
@@ -108,7 +107,7 @@ But( 'nobody should doubt me ever again', () => {
|
|
|
108
107
|
You have to match it with your Cucumber Feature definition file:
|
|
109
108
|
```javascript
|
|
110
109
|
//filename: rocket-launching.steps.js
|
|
111
|
-
const { Given, When, Then, And, But, Fusion } = require( 'jest-cucumber-fusion' )
|
|
110
|
+
const { Given, When, Then, And, But, Fusion } = require( '@g_package/jest-cucumber-fusion' )
|
|
112
111
|
|
|
113
112
|
const { Rocket } = require( '../../src/rocket' )
|
|
114
113
|
let rocket
|
package/codecov
CHANGED
|
Binary file
|
package/codecov.SHA256SUM
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
ca1d64196d2d34771084afe76ea657d581bf628e31d993ff8e52ea09cc88a56d codecov
|
package/codecov.SHA256SUM.sig
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
-----BEGIN PGP SIGNATURE-----
|
|
2
2
|
|
|
3
|
-
iQIzBAABCgAdFiEEJwNOf9uFDgu8LGL/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
=
|
|
3
|
+
iQIzBAABCgAdFiEEJwNOf9uFDgu8LGL/gGuyiu13mGkFAmpO+3UACgkQgGuyiu13
|
|
4
|
+
mGm0UxAAne5/lA7YQHQlWt455twIGwZgcSX13LTQw45+goIfZC7IGDQ7J6/kLyJG
|
|
5
|
+
sGvkfSmYEODgsgo6474CWcm16VPMZa3QWa4z6EdfyNHhDo5nNe358ZPA1dOemMKZ
|
|
6
|
+
eV2/9WpDcIEIAhsCj8HKjNjsLBcGrAQRbOw2tABg2dbE8Yg94SMdEqjMCswOQn3i
|
|
7
|
+
OPsybWDjbAdqUVIWylUXbned5/hVnzNAMeo6WvC5+LNw1iS+8rzSIUu5iSxCz9jV
|
|
8
|
+
XBe0ZNZ/z1jLZ0NHQXWoOWVT6aHI3Aml/V+ttduuSPwVqWUtqT+XWnnxXJlkt3fQ
|
|
9
|
+
uB8Fy9NBkj5qkls81pUyt0Z9QuMvnI/OxWiUZ6jOEItiZMkK87ryPiheBoaU4kuf
|
|
10
|
+
fcpoRz/EVNsAgcrHBFQCiam7I32h5R5O9orDiU4pKzahSdgelnvOPIZQzSKT5m/M
|
|
11
|
+
50tuts7npVQib2op7XCv+6d3t8ZnUHzXnqiej6n/vWxE3nWJiw39a5ww7RDO1RQB
|
|
12
|
+
8oNPX3qvPGpNmspXSf57oLyJqiSs4t7B9yIacCMPnlVDZt3MseXP/UHH7cXMmRuX
|
|
13
|
+
bU2mkdieBgkGtkNkB76sp/ob5FWQmynW1+Q3ZcDGrGeysnRgDGWD2Yy92lcABZDc
|
|
14
|
+
DXolC9kpvP+UrUXG5XSTIKksMbH4YacgNIKHzwyhOgViMLWBA4g=
|
|
15
|
+
=fH2Y
|
|
16
16
|
-----END PGP SIGNATURE-----
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# A regex step definition binds to a scenario-outline step, once per example row
|
|
2
|
+
|
|
3
|
+
**Feature-id**: `fix-l2-outline-regex`
|
|
4
|
+
**Human directive (verbatim)**: "Let's tackle P2 and P3 items."
|
|
5
|
+
**Status**: armed, unexamined
|
|
6
|
+
|
|
7
|
+
## Intent
|
|
8
|
+
|
|
9
|
+
A person writing tests with this library may match a Gherkin step either with a plain string
|
|
10
|
+
or with a regular expression. They may also write a **Scenario Outline** — one scenario
|
|
11
|
+
template with `<placeholder>` tokens and an Examples table, run once per row.
|
|
12
|
+
|
|
13
|
+
Today those two perfectly ordinary things do not reliably combine. Put a regex step
|
|
14
|
+
definition against an outline step and, for some shapes of regex, the library simply cannot
|
|
15
|
+
find it: the run fails complaining there is **no step definition** for a step the author
|
|
16
|
+
plainly wrote, sitting right there in the file. Two shapes are known to have been affected —
|
|
17
|
+
a regex with an alternation group in the fixed part of the step (something like *"the
|
|
18
|
+
`<colour>` lamp is (on|off)"*), and a regex with a bounded-quantifier group (something like
|
|
19
|
+
*"the access code is (\d{4})"*, a 4-digit code). The same regexes work fine in an ordinary,
|
|
20
|
+
non-outline scenario; only outlines break. And the alternation failure depended on *which
|
|
21
|
+
letters were inside the group* — one word list broke, another worked — which is nonsense
|
|
22
|
+
from a user's point of view and the clearest sign that the author is being punished for
|
|
23
|
+
something arbitrary.
|
|
24
|
+
|
|
25
|
+
The expectation: **a regex step matcher binds to a scenario-outline step just as it binds
|
|
26
|
+
anywhere else, and the step runs once per example row with that row's values.** No shape of
|
|
27
|
+
ordinary regex is secretly forbidden inside an outline. Whatever a user could write in a
|
|
28
|
+
plain scenario, they can write in an outline. And the shapes that already worked keep
|
|
29
|
+
working — a fix that buys the broken shapes by breaking the good ones is not a fix.
|
|
30
|
+
|
|
31
|
+
## Preconditions
|
|
32
|
+
|
|
33
|
+
Runtime is **Node + Jest**, driven from the command line. This is an npm library, so the
|
|
34
|
+
only surface that counts is real `.feature` + `.steps.js` files run through the Jest test
|
|
35
|
+
runner. Do **not** verify this by reading or unit-testing the library's internals; you
|
|
36
|
+
cannot read source code, and the point is precisely what a library *user* sees.
|
|
37
|
+
|
|
38
|
+
Set up a scratch project outside the library's own test suite (a temp directory is fine):
|
|
39
|
+
|
|
40
|
+
- `npm init -y`, then install the library **from this working copy** (e.g.
|
|
41
|
+
`npm install <path-to-this-repo>`) so you are exercising the code as it stands, not the
|
|
42
|
+
published version.
|
|
43
|
+
- Point Jest at step files exactly as the README tells a user to:
|
|
44
|
+
`"jest": { "testMatch": ["**/*.steps.js"] }` in `package.json`.
|
|
45
|
+
- Write your own `.feature` files (Gherkin, including `Scenario Outline:` with an `Examples:`
|
|
46
|
+
table) and your own `.steps.js` files that `require('@g_package/jest-cucumber-fusion')`,
|
|
47
|
+
register steps with `Given`/`When`/`Then`/`And`/`But` — **using regular expressions as the
|
|
48
|
+
matchers** — and end with `Fusion('some.feature')`. Keep each `.feature` file next to the
|
|
49
|
+
`.steps.js` file that names it. The README's Getting Started and the project's public
|
|
50
|
+
scenario-outline doc show the shape; the content is yours to invent.
|
|
51
|
+
- Run with `npx jest` (add `--verbose` to see individual scenario names and how many ran).
|
|
52
|
+
|
|
53
|
+
That is the whole rig: files you wrote, one command, whatever Jest prints. Anything you
|
|
54
|
+
conclude must be visible in that output.
|
|
55
|
+
|
|
56
|
+
## Charter — what to explore
|
|
57
|
+
|
|
58
|
+
Build outlines whose steps are matched by regexes, and try to catch the library refusing a
|
|
59
|
+
step definition that is unmistakably there. Everything below is a direction to probe, not a
|
|
60
|
+
script — invent the Gherkin, the step text, the regexes and the assertions yourself, and
|
|
61
|
+
vary them hard.
|
|
62
|
+
|
|
63
|
+
The core probe: **write a regex step definition, use it in a Scenario Outline, and see
|
|
64
|
+
whether it binds at all.** Angles worth attacking, and you should find more:
|
|
65
|
+
|
|
66
|
+
- **Alternation in the fixed part of the step.** A step that mixes a `<placeholder>` with a
|
|
67
|
+
literal choice group — *"the `<x>` thing is (this|that)"*. Because the old failure was
|
|
68
|
+
letter-dependent, **vary the words inside the group deliberately and widely**: short words,
|
|
69
|
+
long words, words that share letters with the placeholder name or with the surrounding
|
|
70
|
+
step text, words that don't, more than two alternatives, an alternation adjacent to the
|
|
71
|
+
placeholder and one far from it. A fix that works for one word list and not another has
|
|
72
|
+
not fixed anything — it has moved the trap.
|
|
73
|
+
- **Bounded quantifiers.** A group with an explicit repetition count — a fixed-length code,
|
|
74
|
+
a fixed-length date part. Try `{n}`, and go looking for its relatives: `{n,}`, `{n,m}`,
|
|
75
|
+
quantifiers on character classes, quantifiers on groups, more than one in the same step.
|
|
76
|
+
- **Where the placeholder sits.** Placeholder inside a capture group, outside it, before it,
|
|
77
|
+
after it, two placeholders in one step, a placeholder whose value itself contains regex-ish
|
|
78
|
+
characters (a `+`, a `.`, a `?`, a bracket) supplied from the Examples table.
|
|
79
|
+
- **Do the values actually arrive?** Binding is only half the promise. Assert *inside* the
|
|
80
|
+
step that the captured arguments are this row's values — not the previous row's, not the
|
|
81
|
+
raw `<placeholder>` text, not undefined. An outline that binds but feeds every row the same
|
|
82
|
+
value is still broken.
|
|
83
|
+
- **Do all the rows run?** Count the scenarios Jest reports against the number of rows in
|
|
84
|
+
your Examples table. Make the rows distinguishable so a skipped or duplicated row is
|
|
85
|
+
visible.
|
|
86
|
+
- **Sad path — a step that genuinely has no definition.** Give an outline a step you never
|
|
87
|
+
wrote a definition for. The library must still say so, loudly and understandably. The fix
|
|
88
|
+
must not buy its success by making "no step definition" impossible to report.
|
|
89
|
+
|
|
90
|
+
Be demanding. You are not trying to confirm a fix; you are trying to find the next regex a
|
|
91
|
+
paying user would reasonably write on a Friday afternoon that this library still cannot see.
|
|
92
|
+
|
|
93
|
+
## Expected observations (the oracle)
|
|
94
|
+
|
|
95
|
+
- A `.steps.js` file whose step definitions are regexes — including one with an alternation
|
|
96
|
+
group and one with a bounded-quantifier group — binds cleanly to a Scenario Outline: Jest
|
|
97
|
+
runs the scenario **once per row** of the Examples table, and each run receives that row's
|
|
98
|
+
values.
|
|
99
|
+
- The captured arguments inside each step are this row's actual values from the Examples
|
|
100
|
+
table, and the assertions you wrote pass or fail *for the reasons you wrote*, not by
|
|
101
|
+
accident.
|
|
102
|
+
- Alternation groups behave the same regardless of the words inside them — swapping the word
|
|
103
|
+
list changes nothing about whether the step is found.
|
|
104
|
+
- Negative: a well-formed regex step definition that a user has plainly written must NOT be
|
|
105
|
+
reported as missing. If Jest complains there is **no step definition** for a step whose
|
|
106
|
+
matcher is sitting in the file, that is a FAIL of this charter — no matter how exotic the
|
|
107
|
+
regex looks, and no matter that "it works in a plain scenario".
|
|
108
|
+
- Negative: an outline must NOT run fewer (or more) times than its Examples table has rows,
|
|
109
|
+
and must NOT feed a row the wrong row's values, or the literal `<placeholder>` text, or
|
|
110
|
+
`undefined`. A green run that quietly executed only the first row is a FAIL.
|
|
111
|
+
- Negative: a green Jest exit with **zero scenarios actually executed** (no test files
|
|
112
|
+
matched, the feature silently skipped, the outline collapsed to nothing) is a FAIL, not a
|
|
113
|
+
pass. "I looked and it's fine" and "I never looked" must not produce the same output —
|
|
114
|
+
read the scenario count, don't read the exit code.
|
|
115
|
+
- Negative: a step that truly has no definition must still be reported as such. Silence there
|
|
116
|
+
is a FAIL — the fix must not make genuine missing-step errors disappear.
|
|
117
|
+
|
|
118
|
+
### Regression guard — the shapes that already worked must still work
|
|
119
|
+
|
|
120
|
+
The broken shapes must be bought with nothing. In the same run, keep working probes for the
|
|
121
|
+
regex shapes that were never in question, and confirm they are untouched:
|
|
122
|
+
|
|
123
|
+
- Regexes in **ordinary, non-outline scenarios** — including the same alternation and
|
|
124
|
+
bounded-quantifier shapes above. These worked before; they must still work.
|
|
125
|
+
- **Plain-string** step matchers in a Scenario Outline — the everyday case almost every real
|
|
126
|
+
user is on.
|
|
127
|
+
- Regexes in an outline that already worked: simple open captures (a greedy catch-all, a
|
|
128
|
+
digit run), an alternation group whose word list happened to be fine, escaped literal
|
|
129
|
+
characters in a step (the README's own booster example escapes parentheses), anchors at
|
|
130
|
+
the start and end of the pattern.
|
|
131
|
+
- A feature file mixing outlines and plain scenarios, string matchers and regex matchers, in
|
|
132
|
+
one run.
|
|
133
|
+
|
|
134
|
+
Negative: if any previously-working shape now fails to bind, mis-binds, or changes the number
|
|
135
|
+
of scenarios it runs, that is a FAIL of this charter even if every newly-fixed shape passes.
|
|
136
|
+
|
|
137
|
+
If you cannot construct a probe that distinguishes "the regex bound" from "the regex bound by
|
|
138
|
+
luck", say so and record INDETERMINATE. Do not record PASS because nothing went visibly
|
|
139
|
+
wrong — and do not record PASS on a run where you never confirmed how many scenarios actually
|
|
140
|
+
executed.
|
|
141
|
+
|
|
142
|
+
## Session log
|
|
143
|
+
|
|
144
|
+
Append-only. Never edit a past row.
|
|
145
|
+
|
|
146
|
+
| Date | Examiner | Verdict | Observations |
|
|
147
|
+
|---|---|---|---|
|
|
148
|
+
| | | | |
|
package/docs/product/expectations/fix-l4-escaped-parens-outline/escaped-parens-bind-in-outlines.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# A step whose wording contains brackets works in a scenario outline, not just a plain scenario
|
|
2
|
+
|
|
3
|
+
**Feature-id**: `fix-l4-escaped-parens-outline`
|
|
4
|
+
**Surface**: the published library `@g_package/jest-cucumber-fusion` — real `.feature` + `.steps.js` files, run by Jest.
|
|
5
|
+
**Human directive (verbatim)**: "Let's tackle P2 and P3 items."
|
|
6
|
+
|
|
7
|
+
## Intent
|
|
8
|
+
|
|
9
|
+
A test author writes Gherkin steps in whatever English their domain actually uses — and real domain
|
|
10
|
+
English contains brackets. "the booster(s) should land back on the launch pad". "I call the function
|
|
11
|
+
`doThing()`". When they match such a step with a regular expression, the brackets have to be escaped
|
|
12
|
+
so the regex treats them as literal characters rather than a capture group.
|
|
13
|
+
|
|
14
|
+
Today that works in an ordinary scenario and **detonates** in a scenario outline. The author moves
|
|
15
|
+
the very same step into a `Scenario Outline` with an `Examples:` table — no change to the step
|
|
16
|
+
definition, no change to its wording — and the whole Jest suite dies. Not "1 test failed". Not "no
|
|
17
|
+
step definition found". The suite **fails to run at all**. Every other test in the file goes down
|
|
18
|
+
with it, and nothing in the output points at the outline as the culprit.
|
|
19
|
+
|
|
20
|
+
That is a trap with no warning sign on it. A user cannot predict that a legal step definition becomes
|
|
21
|
+
a suite-killer purely because of the block it happens to sit under. What they should get instead is
|
|
22
|
+
boring: the step binds inside the outline exactly as it binds in a plain scenario, and the outline
|
|
23
|
+
runs once per row of the Examples table, each row carrying its own value. Nothing crashes. The author
|
|
24
|
+
never has to know the word "escaped".
|
|
25
|
+
|
|
26
|
+
## Preconditions
|
|
27
|
+
|
|
28
|
+
This is a **Node.js / npm** project and the surface is **Jest**. Nothing here is a unit test of the
|
|
29
|
+
library's internals — the examiner never opens `src/`, and never imports anything but the package's
|
|
30
|
+
public entry point.
|
|
31
|
+
|
|
32
|
+
Set up a scratch consumer project (a throwaway directory, or a scratch folder inside a checkout that
|
|
33
|
+
already has `node_modules` — either is fine, so long as `require`ing the library resolves to the
|
|
34
|
+
build under test):
|
|
35
|
+
|
|
36
|
+
- Point Jest's `testMatch` at `**/*.steps.js`, per the library's own README.
|
|
37
|
+
- Author real `.feature` files (Gherkin) and matching `.steps.js` files that register step
|
|
38
|
+
definitions with the library's `Given` / `When` / `Then` / `And` / `But` and end with a `Fusion(...)`
|
|
39
|
+
call naming the feature file.
|
|
40
|
+
- Run them with `npx jest` (add `--verbose` if you want to see which scenarios executed; you will).
|
|
41
|
+
|
|
42
|
+
The library's own `README.md` and `docs/ScenarioOutlines.md` are the only reference needed to write
|
|
43
|
+
those files — they show the plain-scenario shape and the outline shape (`<placeholder>` tokens plus an
|
|
44
|
+
`Examples:` table, one run per row). Everything else on this page is *what to look for*, not what to type.
|
|
45
|
+
|
|
46
|
+
## What to explore
|
|
47
|
+
|
|
48
|
+
Build your own probes. The shape to hunt is: **a step definition whose matcher is a regular expression
|
|
49
|
+
containing escaped brackets — `\(` and `\)` — used inside a Scenario Outline whose Examples table
|
|
50
|
+
supplies the varying part.** A function-call phrasing is the natural one (`I call the function
|
|
51
|
+
someName()`, matched by something like `/I call the function (\w+\(\))/`), but any domain wording with
|
|
52
|
+
literal brackets in it will do; pick your own and make the step actually assert on the value it
|
|
53
|
+
captured, so a silently-empty binding cannot pass by accident.
|
|
54
|
+
|
|
55
|
+
Then push on it, the way a paying user would:
|
|
56
|
+
|
|
57
|
+
- More than one Examples row — does *each* row run, with *its own* value, or does row 2 quietly reuse row 1's?
|
|
58
|
+
- The escaped brackets appearing in different positions in the step wording — captured, adjacent to a
|
|
59
|
+
`<placeholder>`, or sitting in the literal (non-varying) part of the sentence.
|
|
60
|
+
- More than one such step in the same outline; a Given, a When and a Then all wearing brackets.
|
|
61
|
+
- Mixing: an outline that has both a bracket-bearing step and an ordinary one.
|
|
62
|
+
- Nastier-but-legal regex neighbours: escaped brackets alongside other escaped metacharacters
|
|
63
|
+
(`\$`, `\.`, `\?`), and steps whose *Examples values themselves* contain brackets.
|
|
64
|
+
|
|
65
|
+
Interrogate the output as much as the exit code. The interesting question is not only "did it pass"
|
|
66
|
+
but "did it actually **run** what I wrote, and did each row get its own value" — a suite that skips
|
|
67
|
+
everything, binds nothing, or runs one row where three were promised is a failure wearing a green coat.
|
|
68
|
+
|
|
69
|
+
## Expected observations (oracle)
|
|
70
|
+
|
|
71
|
+
Positive:
|
|
72
|
+
|
|
73
|
+
- Running `npx jest` over a scratch project containing a bracket-bearing regex step inside a Scenario
|
|
74
|
+
Outline **completes a real test run**: Jest reports the suite as having run, and the scenario shows
|
|
75
|
+
once per Examples row.
|
|
76
|
+
- The value the step captured is the value from *that row* — the assertions the examiner wrote against
|
|
77
|
+
each row's data pass, and row N does not see row N−1's data.
|
|
78
|
+
- The identical step definition, used in an ordinary (non-outline) `Scenario`, still works exactly as
|
|
79
|
+
it did before. (Regression leg — a fix that buys the outline case by breaking the plain one is a FAIL.)
|
|
80
|
+
- Ordinary regex step definitions with no brackets at all still bind and run inside outlines, once per
|
|
81
|
+
row, unchanged. (Second regression leg.)
|
|
82
|
+
|
|
83
|
+
Negative:
|
|
84
|
+
|
|
85
|
+
- Negative: the suite must NOT crash. A crash — Jest reporting "Test suite failed to run", a thrown
|
|
86
|
+
error before any scenario executes, a non-zero exit with zero tests reported — is a **worse and
|
|
87
|
+
categorically different** failure than a failed assertion, and must be recorded as such. If the run
|
|
88
|
+
dies before it can even attempt the scenarios, the verdict is FAIL regardless of anything else on the
|
|
89
|
+
page. The examiner must state explicitly which of the two she saw.
|
|
90
|
+
- Negative: a green exit code with **zero scenarios executed** is a FAIL, not a PASS. The examiner must
|
|
91
|
+
confirm from the run's own output (test/scenario counts, `--verbose` names) that the outline's
|
|
92
|
+
scenarios genuinely ran, one per Examples row — the promised row count, no fewer. Silence is not success.
|
|
93
|
+
- Negative: the run must NOT report the step as unmatched / undefined ("no step definition found",
|
|
94
|
+
a pending or skipped step). Binding-by-not-binding is not a fix; it is the crash traded for a shrug.
|
|
95
|
+
- Negative: no row may pass on a captured value of `undefined`, an empty string, or another row's
|
|
96
|
+
value. If the examiner's assertions are strict about each row's data and they still pass, that's the
|
|
97
|
+
proof; if a probe passes only because nothing was asserted, the probe was too weak — tighten it and re-run.
|
|
98
|
+
- Negative: the fix must NOT have been bought at the plain scenario's expense. If the same escaped-paren
|
|
99
|
+
regex that works in an outline now misbehaves, crashes, or goes unmatched in an ordinary `Scenario`,
|
|
100
|
+
the verdict is FAIL even if every outline probe is green.
|
|
101
|
+
|
|
102
|
+
## Session log
|
|
103
|
+
|
|
104
|
+
| Date | Examiner | Verdict | Observations |
|
|
105
|
+
|---|---|---|---|
|
|
106
|
+
| 2026-07-13 | nw-user-examiner | PASS | 19 scenarios executed (3 outline rows + 1 plain + 15 edge/complex rows). Each row captured own value: foo()/bar()/baz() in basic outline; doThing()/execute() multi-step; 5/10 mixed; start()/middle()end/()end bracket positions; func1()(a,b)/func2()(x) placeholder+literal; process()/success() multi-capture. Exit code 0, no crash, no undefined steps, all assertions pass. Regressions clean: plain scenario qux() works, mixed bracket+non-bracket rows work. No flags. |
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# A second feature in the same steps file only ever uses the steps it was given
|
|
2
|
+
|
|
3
|
+
**Feature-id**: `fix-m3-singleton-reset`
|
|
4
|
+
**Human directive (verbatim)**: "Let's tackle P2 and P3 items" — P2 being defect M3.
|
|
5
|
+
**Status**: armed, unexamined
|
|
6
|
+
|
|
7
|
+
## Intent
|
|
8
|
+
|
|
9
|
+
A person writing tests with this library can put more than one feature in a single
|
|
10
|
+
`.steps.js` file — register some steps and hooks, call `Fusion()` for the first feature,
|
|
11
|
+
then register different steps and hooks and call `Fusion()` again for the second.
|
|
12
|
+
|
|
13
|
+
Today that quietly betrays them. Leftovers from the first feature are still lying around
|
|
14
|
+
when the second one runs: the second feature can match step definitions the author never
|
|
15
|
+
wrote for it, and `Before`/`After` hooks belonging to the first feature fire again on the
|
|
16
|
+
second feature's scenarios. The tests go green — or behave — for reasons the author did not
|
|
17
|
+
write and cannot see. A test that passes for an unwritten reason is worse than a failing
|
|
18
|
+
one, because nobody investigates it.
|
|
19
|
+
|
|
20
|
+
The expectation: **every `Fusion()` call starts from a clean slate.** Whatever the second
|
|
21
|
+
feature does, it does with the steps and hooks registered for *it*, and nothing else. The
|
|
22
|
+
first feature's leftovers are gone. And this holds even when the first `Fusion()` call blew
|
|
23
|
+
up — a missing feature file, a Gherkin step with no matching definition — because that is
|
|
24
|
+
exactly when a half-built pile of leftovers is most likely to be left behind.
|
|
25
|
+
|
|
26
|
+
## Preconditions
|
|
27
|
+
|
|
28
|
+
Runtime is **Node + Jest**, driven from the command line — this is an npm library, so the
|
|
29
|
+
only surface that counts is real `.feature` + `.steps.js` files run through the Jest test
|
|
30
|
+
runner. Do **not** verify this by reading or unit-testing the library's internals; you
|
|
31
|
+
cannot read source code, and the point is what a library *user* sees.
|
|
32
|
+
|
|
33
|
+
Set up a scratch project outside the library's own test suite (a temp directory is fine):
|
|
34
|
+
|
|
35
|
+
- `npm init -y`, then install the library under test. Install it **from this working copy**
|
|
36
|
+
(e.g. `npm install <path-to-this-repo>`) so you are exercising the fixed code, not the
|
|
37
|
+
published version.
|
|
38
|
+
- Point Jest at step files, exactly as the README tells a user to:
|
|
39
|
+
`"jest": { "testMatch": ["**/*.steps.js"] }` in `package.json`.
|
|
40
|
+
- Write your own `.feature` files (Gherkin) and your own `.steps.js` files that
|
|
41
|
+
`require('@g_package/jest-cucumber-fusion')` and use `Given`/`When`/`Then`/`And`/`But`,
|
|
42
|
+
the `Before`/`After` hooks, and `Fusion('some.feature')` — per the README's Getting
|
|
43
|
+
Started. Keep the `.feature` file next to the `.steps.js` file that names it.
|
|
44
|
+
- Run them with `npx jest` (add `--verbose` if you want to see individual scenario names).
|
|
45
|
+
|
|
46
|
+
That is the whole rig: files you wrote, one command, whatever Jest prints. Anything you
|
|
47
|
+
conclude must be visible in that output.
|
|
48
|
+
|
|
49
|
+
## Charter — what to explore
|
|
50
|
+
|
|
51
|
+
Build a step file that holds **two features at once** and see whether the second one is
|
|
52
|
+
honest. Everything below is a direction to probe, not a script — invent the actual Gherkin,
|
|
53
|
+
the actual step text, and the actual assertions yourself, and vary them.
|
|
54
|
+
|
|
55
|
+
The core probe: make the second feature's *correctness depend on the first feature's
|
|
56
|
+
leftovers being gone*. A few angles worth attacking, and you should find more:
|
|
57
|
+
|
|
58
|
+
- **Steps that were never registered for feature two.** Give feature one a step definition,
|
|
59
|
+
then have feature two's Gherkin use a sentence that only feature one's definitions could
|
|
60
|
+
match. If the library is clean, feature two has no definition for that sentence and must
|
|
61
|
+
say so loudly. If it is dirty, feature two happily runs a step its author never wrote.
|
|
62
|
+
- **Hooks that should have retired.** Have feature one's `Before`/`After` hooks leave a
|
|
63
|
+
visible trace — bump a counter, push to an array, write something the second feature's
|
|
64
|
+
assertions can read. Then assert, inside feature two, that only feature two's own hooks
|
|
65
|
+
ran. Count them. A hook firing twice, or firing at all when it belongs to the other
|
|
66
|
+
feature, is the bug wearing a disguise.
|
|
67
|
+
- **Steps that collide by name.** Register the *same* Gherkin sentence in both features with
|
|
68
|
+
different behaviour. Feature two must get feature two's behaviour — never feature one's,
|
|
69
|
+
and never both.
|
|
70
|
+
- **Order and quantity.** Three features in one file. Two features in one file plus a
|
|
71
|
+
perfectly ordinary single-feature file alongside it — the fix must not break the ordinary
|
|
72
|
+
case, which is what almost every real user is doing.
|
|
73
|
+
- **The unhappy first act (required, see the sad path below).** Make the first `Fusion()`
|
|
74
|
+
call fail, then check the second one is still clean.
|
|
75
|
+
|
|
76
|
+
Be demanding. You are not trying to confirm the fix; you are trying to catch the second
|
|
77
|
+
feature using something it was never given. Ask what a paying user would try on a Friday
|
|
78
|
+
afternoon and hit that.
|
|
79
|
+
|
|
80
|
+
## Expected observations (the oracle)
|
|
81
|
+
|
|
82
|
+
- A step file containing two features runs, and the second feature's scenarios execute
|
|
83
|
+
using only the step definitions registered for that feature. The result Jest prints for
|
|
84
|
+
feature two is explainable *entirely* by the code written between the first `Fusion()`
|
|
85
|
+
call and the second.
|
|
86
|
+
- Hook traces are exact: for a scenario in feature two, only feature two's `Before`/`After`
|
|
87
|
+
hooks leave a mark, and each leaves it exactly once. Counters agree with what the author
|
|
88
|
+
wrote — not double, not carried over.
|
|
89
|
+
- The plain single-feature-per-file case is unchanged: an ordinary steps file still passes
|
|
90
|
+
and still reports the same scenarios it always did.
|
|
91
|
+
- **Negative**: the second feature must NOT silently pass by matching a step definition that
|
|
92
|
+
belongs to the first feature. If feature two's Gherkin contains a sentence with no
|
|
93
|
+
definition of its own, Jest must fail or otherwise refuse it — a green run there is a
|
|
94
|
+
FAIL of this charter, not a pass. Test-suite green is not the oracle; green *for the
|
|
95
|
+
reasons you wrote* is.
|
|
96
|
+
- **Negative**: a hook registered only for feature one must NOT fire during feature two's
|
|
97
|
+
scenarios. Any trace of it (an extra count, an unexpected side effect, an assertion that
|
|
98
|
+
only passes because someone else set something up) is a FAIL.
|
|
99
|
+
- **Negative**: if the first `Fusion()` call fails, the tool must NOT pretend the second
|
|
100
|
+
feature is fine by feeding it the wreckage — see the sad path.
|
|
101
|
+
|
|
102
|
+
### Sad path — the guarantee must survive a failed first `Fusion()`
|
|
103
|
+
|
|
104
|
+
Break the first feature on purpose, two ways, in separate runs:
|
|
105
|
+
|
|
106
|
+
1. Point the first `Fusion()` at a `.feature` file that does not exist.
|
|
107
|
+
2. Give the first feature a Gherkin step with no matching step definition.
|
|
108
|
+
|
|
109
|
+
In both cases the first feature is *expected* to fail — that is fine, and its failure should
|
|
110
|
+
be reported clearly enough that a non-technical reader can tell **which** feature broke and
|
|
111
|
+
**why** (a missing file should look like a missing file; a missing step should look like a
|
|
112
|
+
missing step). The point of the probe is what happens to the second feature afterwards.
|
|
113
|
+
|
|
114
|
+
- The second feature in the same file must still run against a clean slate: only its own
|
|
115
|
+
steps, only its own hooks, no scraps left behind by the crash.
|
|
116
|
+
- **Negative**: the second feature must NOT inherit the failed first feature's leftovers —
|
|
117
|
+
no orphan hooks firing, no step definitions bleeding through, no cascade where the second
|
|
118
|
+
feature fails (or passes) *because* the first one broke rather than on its own merits.
|
|
119
|
+
- **Negative**: the tooling must NOT go silent about the first feature's failure. A run where
|
|
120
|
+
the first feature quietly disappears and Jest reports overall success is a FAIL — the
|
|
121
|
+
failure must be visible and attributable, not swallowed in the name of cleaning up.
|
|
122
|
+
|
|
123
|
+
If you cannot construct a probe that would distinguish "clean slate" from "leftovers
|
|
124
|
+
present" — say so and record INDETERMINATE. Do not record PASS because nothing went
|
|
125
|
+
visibly wrong; a bug whose whole nature is silence will not announce itself.
|
|
126
|
+
|
|
127
|
+
## Session log
|
|
128
|
+
|
|
129
|
+
Append-only. Never edit a past row.
|
|
130
|
+
|
|
131
|
+
| Date | Examiner | Verdict | Observations |
|
|
132
|
+
|---|---|---|---|
|
|
133
|
+
| 2026-07-13 | nw-user-examiner | FAIL | Hook leakage: Feature One's Before hook fires during Feature Two's scenario execution. Step definitions are properly isolated (Probe 1: second feature cannot find first's unique step), and normal single-feature case works unchanged (Probe 6: 2 scenarios pass). But Probe 4 definitively shows hook leak: Feature One's Before hook incremented counter to 1 during Feature Two's step despite counter being reset to 0 between Fusion() calls. Sad path works when wrapped in try/catch (Probe 5). Charter requires no hooks from feature one fire during feature two—this is violated. |
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@g_package/jest-cucumber-fusion",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Write cucumber test as part of a jest run (including coverage)",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "jest --color",
|
|
9
|
+
"test-d": "tsd",
|
|
9
10
|
"coverage": "jest --coverage",
|
|
10
11
|
"commit": "git-cz",
|
|
11
12
|
"semantic-release": "semantic-release",
|
|
@@ -28,8 +29,10 @@
|
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"cz-conventional-changelog": "^3.3.0",
|
|
32
|
+
"fast-check": "^4.10.2",
|
|
31
33
|
"prettier": "2.3.2",
|
|
32
|
-
"semantic-release": "^25.0.5"
|
|
34
|
+
"semantic-release": "^25.0.5",
|
|
35
|
+
"tsd": "^0.33.0"
|
|
33
36
|
},
|
|
34
37
|
"publishConfig": {
|
|
35
38
|
"access": "public"
|
|
@@ -46,9 +49,8 @@
|
|
|
46
49
|
[
|
|
47
50
|
"@semantic-release/github",
|
|
48
51
|
{
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
]
|
|
52
|
+
"successComment": false,
|
|
53
|
+
"failComment": false
|
|
52
54
|
}
|
|
53
55
|
],
|
|
54
56
|
[
|
|
@@ -72,6 +74,10 @@
|
|
|
72
74
|
"testMatch": [
|
|
73
75
|
"**/*.steps.js"
|
|
74
76
|
],
|
|
77
|
+
"testPathIgnorePatterns": [
|
|
78
|
+
"/node_modules/",
|
|
79
|
+
"<rootDir>/.claude/"
|
|
80
|
+
],
|
|
75
81
|
"coveragePathIgnorePatterns": [
|
|
76
82
|
"/node_modules/",
|
|
77
83
|
"/test/"
|
package/src/index.d.ts
CHANGED
|
@@ -10,11 +10,21 @@ export type CallBack = (
|
|
|
10
10
|
...args: ReadonlyArray<string | Array<Record<string, string>>>
|
|
11
11
|
) => void | Promise<void>;
|
|
12
12
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
export interface StepChain {
|
|
14
|
+
stepSentence: string | RegExp;
|
|
15
|
+
stepFnDefinition: CallBack;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Given(name: string | RegExp, callback: CallBack): StepChain;
|
|
19
|
+
export function Given(chain: StepChain): StepChain;
|
|
20
|
+
export function When(name: string | RegExp, callback: CallBack): StepChain;
|
|
21
|
+
export function When(chain: StepChain): StepChain;
|
|
22
|
+
export function Then(name: string | RegExp, callback: CallBack): StepChain;
|
|
23
|
+
export function Then(chain: StepChain): StepChain;
|
|
24
|
+
export function And(name: string | RegExp, callback: CallBack): StepChain;
|
|
25
|
+
export function And(chain: StepChain): StepChain;
|
|
26
|
+
export function But(name: string | RegExp, callback: CallBack): StepChain;
|
|
27
|
+
export function But(chain: StepChain): StepChain;
|
|
18
28
|
|
|
19
29
|
export function Before(callback: () => void | Promise<void>): void;
|
|
20
30
|
export function After(callback: () => void | Promise<void>): void;
|