@step-forge/step-forge 0.0.7-beta.6 → 0.0.7
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/.claude/settings.local.json +17 -0
- package/.eslintignore +6 -0
- package/.eslintrc +18 -0
- package/.prettierignore +6 -0
- package/.prettierrc +15 -0
- package/CHANGELOG.md +28 -0
- package/CLAUDE.md +115 -0
- package/README.md +45 -39
- package/cucumber.mjs +39 -0
- package/dist/step-forge.cjs +1 -1
- package/dist/step-forge.js +143 -120
- package/dist/types/src/builderTypeUtils.d.ts +14 -0
- package/dist/types/src/common.d.ts +32 -0
- package/dist/types/src/given.d.ts +73 -0
- package/dist/types/src/index.d.ts +5 -0
- package/dist/types/src/then.d.ts +73 -0
- package/dist/types/src/utils.d.ts +5 -0
- package/dist/types/src/when.d.ts +72 -0
- package/dist/types/src/world.d.ts +21 -0
- package/docs/assets/state_deps.gif +0 -0
- package/dts-bundle-generator.config.cjs +19 -0
- package/features/TESTING.md +100 -0
- package/features/analyzer/analyzer.feature +83 -0
- package/features/analyzer/fixtures/ambiguous-step.feature +5 -0
- package/features/analyzer/fixtures/missing-given-dep.feature +5 -0
- package/features/analyzer/fixtures/missing-multiple-deps.feature +6 -0
- package/features/analyzer/fixtures/missing-when-dep.feature +5 -0
- package/features/analyzer/fixtures/steps.ts +113 -0
- package/features/analyzer/fixtures/undefined-step.feature +5 -0
- package/features/analyzer/fixtures/undefined-with-missing-dep.feature +5 -0
- package/features/analyzer/fixtures/valid-and-but-keywords.feature +8 -0
- package/features/analyzer/fixtures/valid-deps.feature +6 -0
- package/features/analyzer/fixtures/valid-no-deps.feature +6 -0
- package/features/analyzer/fixtures/valid-optional-deps.feature +6 -0
- package/features/analyzer/fixtures/valid-variables.feature +6 -0
- package/features/basic.feature +21 -0
- package/features/exported.feature +6 -0
- package/features/steps/analyzerSteps.ts +53 -0
- package/features/steps/commonSteps.ts +93 -0
- package/features/steps/exportedSteps.ts +42 -0
- package/features/steps/world.ts +29 -0
- package/package.json +33 -26
- package/rolldown.config.mjs +34 -0
- package/src/analyzer/cli.ts +96 -0
- package/src/analyzer/gherkinParser.ts +175 -0
- package/src/analyzer/index.ts +77 -0
- package/src/analyzer/rules/ambiguousStepRule.ts +36 -0
- package/src/analyzer/rules/dependencyRule.ts +59 -0
- package/src/analyzer/rules/index.ts +23 -0
- package/src/analyzer/rules/undefinedStepRule.ts +31 -0
- package/src/analyzer/stepExtractor.ts +432 -0
- package/src/analyzer/stepMatcher.ts +67 -0
- package/src/analyzer/types.ts +55 -0
- package/src/builderTypeUtils.ts +27 -0
- package/src/common.ts +138 -0
- package/src/given.ts +102 -0
- package/src/index.ts +6 -0
- package/src/then.ts +128 -0
- package/src/utils.ts +74 -0
- package/src/when.ts +118 -0
- package/src/world.ts +90 -0
- package/test/givenCompilationTests.ts +130 -0
- package/test/testUtils.ts +30 -0
- package/test/thenCompilationTests.ts +207 -0
- package/test/whenCompilationTests.ts +157 -0
- package/ts-node-esm-register.js +8 -0
- package/tsconfig.cucumber.json +14 -0
- package/tsconfig.json +29 -0
- package/dist/types/index.d.ts +0 -301
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const config = {
|
|
2
|
+
compilationOptions: {
|
|
3
|
+
preferredConfigPath: "./tsconfig.json",
|
|
4
|
+
},
|
|
5
|
+
entries: [
|
|
6
|
+
{
|
|
7
|
+
filePath: "./src/index.ts",
|
|
8
|
+
outFile: "./build/dist/index.d.ts",
|
|
9
|
+
noCheck: false,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
filePath: "./src/analyzer/index.ts",
|
|
13
|
+
outFile: "./build/dist/analyzer.d.ts",
|
|
14
|
+
noCheck: false,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = config;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
All tests use Cucumber.js in a self-testing pattern: feature files in `features/` with step definitions in `features/steps/` exercise the library.
|
|
4
|
+
|
|
5
|
+
## Test Scripts
|
|
6
|
+
|
|
7
|
+
| Script | Profile | Description |
|
|
8
|
+
| ----------------------- | --------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `npm test` | `all` | Run all tests. Suppresses stderr for clean output. Use for normal development. |
|
|
10
|
+
| `npm run test:debug` | `all` | Run all tests with full output (stderr included). Use when a test fails and you need stack traces or error details. |
|
|
11
|
+
| `npm run test:cucumber` | `default` | Same paths as `all`, but does not set `PORT` or `LOG_LEVEL`. |
|
|
12
|
+
| `npm run test:ci` | `ci` | CI-oriented profile. Does not import `src/**/*.ts` (only step defs). Enables `publish`. |
|
|
13
|
+
|
|
14
|
+
All profiles run with `parallel: 1` and use `tsx` as the TypeScript loader.
|
|
15
|
+
|
|
16
|
+
There is no way to run a single test file. To run a subset, use Cucumber tags or modify the feature files temporarily.
|
|
17
|
+
|
|
18
|
+
## Directory Structure
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
features/
|
|
22
|
+
basic.feature ← Builder pattern tests (variables, deps, state)
|
|
23
|
+
exported.feature ← Re-exported builder tests
|
|
24
|
+
steps/
|
|
25
|
+
commonSteps.ts ← Step defs for basic.feature
|
|
26
|
+
exportedSteps.ts ← Step defs for exported.feature
|
|
27
|
+
analyzerSteps.ts ← Step defs for analyzer tests
|
|
28
|
+
world.ts ← World state types
|
|
29
|
+
analyzer/
|
|
30
|
+
analyzer.feature ← Analyzer test scenarios (run by Cucumber)
|
|
31
|
+
fixtures/
|
|
32
|
+
steps.ts ← Fixture step definitions (data, NOT run by Cucumber)
|
|
33
|
+
valid-no-deps.feature ← Fixture feature files (data, NOT run by Cucumber)
|
|
34
|
+
valid-deps.feature
|
|
35
|
+
...
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Builder Tests
|
|
39
|
+
|
|
40
|
+
`basic.feature` and `exported.feature` test the core builder pattern — step registration, variable extraction, dependency resolution, and state merging. Their step definitions in `features/steps/` use the builders directly and assert on the resulting world state.
|
|
41
|
+
|
|
42
|
+
Type-safety tests in `test/` use `@ts-expect-error` annotations and validate at `tsc` compile time, not at runtime.
|
|
43
|
+
|
|
44
|
+
## Analyzer Tests
|
|
45
|
+
|
|
46
|
+
The analyzer test suite under `features/analyzer/` tests the `analyze()` API against fixture files. The key distinction: **fixture files are data, not tests**. The analyzer's extractor reads `fixtures/steps.ts` as a TypeScript AST, and the parser reads `fixtures/*.feature` as Gherkin data. Cucumber never executes them.
|
|
47
|
+
|
|
48
|
+
The `cucumber.mjs` config uses non-recursive path globs (`./features/*.feature`, `./features/analyzer/*.feature`) so fixture files in `features/analyzer/fixtures/` are excluded from Cucumber's test discovery.
|
|
49
|
+
|
|
50
|
+
### Available Steps
|
|
51
|
+
|
|
52
|
+
Step definitions in `features/steps/analyzerSteps.ts` provide:
|
|
53
|
+
|
|
54
|
+
| Step | Purpose |
|
|
55
|
+
| ----------------------------------------- | ------------------------------------------------------ |
|
|
56
|
+
| `Given step definitions from {string}` | Set the fixture step file (relative to `fixtures/`) |
|
|
57
|
+
| `Given a feature file {string}` | Set the fixture feature file (relative to `fixtures/`) |
|
|
58
|
+
| `When I analyze the files` | Call `analyze()` with the configured files |
|
|
59
|
+
| `Then there should be no errors` | Assert zero errors in diagnostics |
|
|
60
|
+
| `Then there should be {int} error/errors` | Assert exact error count |
|
|
61
|
+
| `Then an error should mention {string}` | Assert an error message contains the substring |
|
|
62
|
+
|
|
63
|
+
### Adding New Analyzer Tests
|
|
64
|
+
|
|
65
|
+
1. **Add step definition patterns** (if needed) to `features/analyzer/fixtures/steps.ts`. This file must be valid TypeScript that compiles, but it is never executed — only parsed by the AST extractor.
|
|
66
|
+
|
|
67
|
+
2. **Create a fixture feature file** in `features/analyzer/fixtures/` using the step expressions from `steps.ts`. This file is parsed by the Gherkin parser as data.
|
|
68
|
+
|
|
69
|
+
3. **Add a scenario** to `features/analyzer/analyzer.feature`:
|
|
70
|
+
|
|
71
|
+
```gherkin
|
|
72
|
+
Scenario: Description of what you're testing
|
|
73
|
+
Given a feature file "your-new-fixture.feature"
|
|
74
|
+
When I analyze the files
|
|
75
|
+
Then there should be no errors
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The Background already provides `Given step definitions from "steps.ts"`, so you only need the `Given a feature file` line in each scenario.
|
|
79
|
+
|
|
80
|
+
4. **Run `npm run test:debug`** to verify. Use `test:debug` instead of `npm test` so you can see error details if something fails.
|
|
81
|
+
|
|
82
|
+
### Fixture Step Definitions
|
|
83
|
+
|
|
84
|
+
`features/analyzer/fixtures/steps.ts` contains builder calls covering these patterns:
|
|
85
|
+
|
|
86
|
+
| Expression | Type | Dependencies | Produces |
|
|
87
|
+
| ----------------------------- | ----- | ------------------------- | --------- |
|
|
88
|
+
| `a user` | given | none | `user` |
|
|
89
|
+
| `a user named {string}` | given | none | `user` |
|
|
90
|
+
| `an account` | given | none | `account` |
|
|
91
|
+
| `I started` | given | none | (nothing) |
|
|
92
|
+
| `I save the user` | when | `given.user: required` | `user` |
|
|
93
|
+
| `I delete the account` | when | `given.account: required` | `result` |
|
|
94
|
+
| `I got here` | when | none | (nothing) |
|
|
95
|
+
| `everything was good` | then | none | (nothing) |
|
|
96
|
+
| `there is a user` | then | `when.user: required` | (nothing) |
|
|
97
|
+
| `the user's name is {string}` | then | `when.user: required` | (nothing) |
|
|
98
|
+
| `the account might exist` | then | `given.account: optional` | (nothing) |
|
|
99
|
+
|
|
100
|
+
When adding new patterns, add them to this file and they'll be available to all fixture feature files.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Feature: Analyzer dependency verification
|
|
2
|
+
|
|
3
|
+
Background:
|
|
4
|
+
Given step definitions from "steps.ts"
|
|
5
|
+
|
|
6
|
+
# --- Passing scenarios ---
|
|
7
|
+
|
|
8
|
+
Scenario: No dependencies produces no errors
|
|
9
|
+
Given a feature file "valid-no-deps.feature"
|
|
10
|
+
When I analyze the files
|
|
11
|
+
Then there should be no errors
|
|
12
|
+
|
|
13
|
+
Scenario: Satisfied required dependencies produce no errors
|
|
14
|
+
Given a feature file "valid-deps.feature"
|
|
15
|
+
When I analyze the files
|
|
16
|
+
Then there should be no errors
|
|
17
|
+
|
|
18
|
+
Scenario: Variables with satisfied dependencies produce no errors
|
|
19
|
+
Given a feature file "valid-variables.feature"
|
|
20
|
+
When I analyze the files
|
|
21
|
+
Then there should be no errors
|
|
22
|
+
|
|
23
|
+
Scenario: Optional dependencies not produced produce no errors
|
|
24
|
+
Given a feature file "valid-optional-deps.feature"
|
|
25
|
+
When I analyze the files
|
|
26
|
+
Then there should be no errors
|
|
27
|
+
|
|
28
|
+
Scenario: And/But keywords resolve correctly with satisfied deps
|
|
29
|
+
Given a feature file "valid-and-but-keywords.feature"
|
|
30
|
+
When I analyze the files
|
|
31
|
+
Then there should be no errors
|
|
32
|
+
|
|
33
|
+
# --- Undefined step scenarios ---
|
|
34
|
+
|
|
35
|
+
Scenario: Undefined step reports an error
|
|
36
|
+
Given a feature file "undefined-step.feature"
|
|
37
|
+
When I analyze the files
|
|
38
|
+
Then there should be 1 error
|
|
39
|
+
And there is 1 error for rule "undefined-step"
|
|
40
|
+
And an error should mention "I do something that does not exist"
|
|
41
|
+
And an error should mention "does not match any step definition"
|
|
42
|
+
|
|
43
|
+
Scenario: Undefined step combined with missing dependency
|
|
44
|
+
Given a feature file "undefined-with-missing-dep.feature"
|
|
45
|
+
When I analyze the files
|
|
46
|
+
Then there should be 2 errors
|
|
47
|
+
And there is 1 error for rule "undefined-step"
|
|
48
|
+
And an error should mention "I do something that does not exist"
|
|
49
|
+
And there is 1 error for rule "dependency-check"
|
|
50
|
+
And an error should mention "given.user"
|
|
51
|
+
|
|
52
|
+
# --- Ambiguous step scenarios ---
|
|
53
|
+
|
|
54
|
+
Scenario: Ambiguous step reports an error
|
|
55
|
+
Given a feature file "ambiguous-step.feature"
|
|
56
|
+
When I analyze the files
|
|
57
|
+
Then there should be 1 error
|
|
58
|
+
And there is 1 error for rule "ambiguous-step"
|
|
59
|
+
And an error should mention "matches multiple step definitions"
|
|
60
|
+
|
|
61
|
+
# --- Failing scenarios ---
|
|
62
|
+
|
|
63
|
+
Scenario: Missing required given dependency reports an error
|
|
64
|
+
Given a feature file "missing-given-dep.feature"
|
|
65
|
+
When I analyze the files
|
|
66
|
+
Then there should be 1 error
|
|
67
|
+
And there is 1 error for rule "dependency-check"
|
|
68
|
+
And an error should mention "given.user"
|
|
69
|
+
|
|
70
|
+
Scenario: Missing required when dependency reports an error
|
|
71
|
+
Given a feature file "missing-when-dep.feature"
|
|
72
|
+
When I analyze the files
|
|
73
|
+
Then there should be 1 error
|
|
74
|
+
And there is 1 error for rule "dependency-check"
|
|
75
|
+
And an error should mention "when.user"
|
|
76
|
+
|
|
77
|
+
Scenario: Multiple missing dependencies reports multiple errors
|
|
78
|
+
Given a feature file "missing-multiple-deps.feature"
|
|
79
|
+
When I analyze the files
|
|
80
|
+
Then there should be 2 errors
|
|
81
|
+
And there are 2 errors for rule "dependency-check"
|
|
82
|
+
And an error should mention "given.user"
|
|
83
|
+
And an error should mention "given.account"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { givenBuilder } from "../../../src/given";
|
|
2
|
+
import { whenBuilder } from "../../../src/when";
|
|
3
|
+
import { thenBuilder } from "../../../src/then";
|
|
4
|
+
|
|
5
|
+
type GivenState = {
|
|
6
|
+
user: { type: string; token: string };
|
|
7
|
+
account: { id: string };
|
|
8
|
+
};
|
|
9
|
+
type WhenState = {
|
|
10
|
+
user: { type: string; token: string; saved: boolean };
|
|
11
|
+
result: { success: boolean };
|
|
12
|
+
};
|
|
13
|
+
type ThenState = Record<string, never>;
|
|
14
|
+
|
|
15
|
+
// --- No dependency steps --- //
|
|
16
|
+
|
|
17
|
+
givenBuilder<GivenState>()
|
|
18
|
+
.statement("I started")
|
|
19
|
+
.step(() => ({}))
|
|
20
|
+
.register();
|
|
21
|
+
|
|
22
|
+
whenBuilder<GivenState, WhenState>()
|
|
23
|
+
.statement("I got here")
|
|
24
|
+
.step(() => ({}))
|
|
25
|
+
.register();
|
|
26
|
+
|
|
27
|
+
thenBuilder<GivenState, WhenState, ThenState>()
|
|
28
|
+
.statement("everything was good")
|
|
29
|
+
.step(() => {})
|
|
30
|
+
.register();
|
|
31
|
+
|
|
32
|
+
// --- Steps that produce state --- //
|
|
33
|
+
|
|
34
|
+
givenBuilder<GivenState>()
|
|
35
|
+
.statement("a user")
|
|
36
|
+
.step(() => {
|
|
37
|
+
return {
|
|
38
|
+
user: { type: "person", token: "abc" },
|
|
39
|
+
};
|
|
40
|
+
})
|
|
41
|
+
.register();
|
|
42
|
+
|
|
43
|
+
givenBuilder<GivenState>()
|
|
44
|
+
.statement((name: string) => `a user named ${name}`)
|
|
45
|
+
.step(({ variables: [name] }) => {
|
|
46
|
+
return {
|
|
47
|
+
user: { type: "person", token: name },
|
|
48
|
+
};
|
|
49
|
+
})
|
|
50
|
+
.register();
|
|
51
|
+
|
|
52
|
+
givenBuilder<GivenState>()
|
|
53
|
+
.statement("an account")
|
|
54
|
+
.step(() => {
|
|
55
|
+
return {
|
|
56
|
+
account: { id: "acct-1" },
|
|
57
|
+
};
|
|
58
|
+
})
|
|
59
|
+
.register();
|
|
60
|
+
|
|
61
|
+
// --- Ambiguous steps (same expression, two definitions) --- //
|
|
62
|
+
|
|
63
|
+
givenBuilder<GivenState>()
|
|
64
|
+
.statement("the system is ready")
|
|
65
|
+
.step(() => ({}))
|
|
66
|
+
.register();
|
|
67
|
+
|
|
68
|
+
givenBuilder<GivenState>()
|
|
69
|
+
.statement("the system is ready")
|
|
70
|
+
.step(() => ({}))
|
|
71
|
+
.register();
|
|
72
|
+
|
|
73
|
+
// --- When steps with dependencies --- //
|
|
74
|
+
|
|
75
|
+
whenBuilder<GivenState, WhenState>()
|
|
76
|
+
.statement("I save the user")
|
|
77
|
+
.dependencies({ given: { user: "required" } })
|
|
78
|
+
.step(({ given: { user } }) => {
|
|
79
|
+
return {
|
|
80
|
+
user: { ...user, saved: true },
|
|
81
|
+
};
|
|
82
|
+
})
|
|
83
|
+
.register();
|
|
84
|
+
|
|
85
|
+
whenBuilder<GivenState, WhenState>()
|
|
86
|
+
.statement("I delete the account")
|
|
87
|
+
.dependencies({ given: { account: "required" } })
|
|
88
|
+
.step(() => {
|
|
89
|
+
return {
|
|
90
|
+
result: { success: true },
|
|
91
|
+
};
|
|
92
|
+
})
|
|
93
|
+
.register();
|
|
94
|
+
|
|
95
|
+
// --- Then steps with dependencies --- //
|
|
96
|
+
|
|
97
|
+
thenBuilder<GivenState, WhenState, ThenState>()
|
|
98
|
+
.statement("there is a user")
|
|
99
|
+
.dependencies({ when: { user: "required" } })
|
|
100
|
+
.step(() => {})
|
|
101
|
+
.register();
|
|
102
|
+
|
|
103
|
+
thenBuilder<GivenState, WhenState, ThenState>()
|
|
104
|
+
.statement((name: string) => `the user's name is ${name}`)
|
|
105
|
+
.dependencies({ when: { user: "required" } })
|
|
106
|
+
.step(() => {})
|
|
107
|
+
.register();
|
|
108
|
+
|
|
109
|
+
thenBuilder<GivenState, WhenState, ThenState>()
|
|
110
|
+
.statement("the account might exist")
|
|
111
|
+
.dependencies({ given: { account: "optional" } })
|
|
112
|
+
.step(() => {})
|
|
113
|
+
.register();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Feature: Steps written in gherkin can be matched to Step Forge steps
|
|
2
|
+
|
|
3
|
+
Scenario: Basic feature steps with no dependencies or variables can be run as a cucumber scenario
|
|
4
|
+
Given I started
|
|
5
|
+
When I got here
|
|
6
|
+
Then everything was good
|
|
7
|
+
|
|
8
|
+
Scenario: Basic feature steps with dependencies can be run as a cucumber scenario
|
|
9
|
+
Given a user
|
|
10
|
+
When I save the user
|
|
11
|
+
Then there is a user
|
|
12
|
+
|
|
13
|
+
Scenario: Basic feature steps with variables can be run as a cucumber scenario
|
|
14
|
+
Given a user named "John"
|
|
15
|
+
When I save the user
|
|
16
|
+
Then there is a user
|
|
17
|
+
|
|
18
|
+
Scenario: Basic feature steps with dependencies and variables can be run as a cucumber scenario
|
|
19
|
+
Given a user
|
|
20
|
+
When I name the user "John"
|
|
21
|
+
Then the user's name is "John"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { Given, When, Then } from "@cucumber/cucumber";
|
|
4
|
+
import { expect } from "earl";
|
|
5
|
+
import { analyze, Diagnostic } from "../../src/analyzer/index";
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const fixturesDir = path.resolve(__dirname, "../analyzer/fixtures");
|
|
9
|
+
|
|
10
|
+
let fixtureStepFile: string;
|
|
11
|
+
let fixtureFeatureFile: string;
|
|
12
|
+
let diagnostics: Diagnostic[];
|
|
13
|
+
|
|
14
|
+
Given("step definitions from {string}", function (fileName: string) {
|
|
15
|
+
fixtureStepFile = path.join(fixturesDir, fileName);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
Given("a feature file {string}", function (fileName: string) {
|
|
19
|
+
fixtureFeatureFile = path.join(fixturesDir, fileName);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
When("I analyze the files", async function () {
|
|
23
|
+
diagnostics = await analyze({
|
|
24
|
+
stepFiles: [fixtureStepFile],
|
|
25
|
+
featureFiles: [fixtureFeatureFile],
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
Then("there should be no errors", function () {
|
|
30
|
+
const errors = diagnostics.filter((d) => d.severity === "error");
|
|
31
|
+
expect(errors).toHaveLength(0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
Then("there should be {int} error/errors", function (count: number) {
|
|
35
|
+
const errors = diagnostics.filter((d) => d.severity === "error");
|
|
36
|
+
expect(errors).toHaveLength(count);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
Then("an error should mention {string}", function (substring: string) {
|
|
40
|
+
const errors = diagnostics.filter((d) => d.severity === "error");
|
|
41
|
+
const found = errors.some((e) => e.message.includes(substring));
|
|
42
|
+
expect(found).toEqual(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
Then(
|
|
46
|
+
"there is/are {int} error/errors for rule {string}",
|
|
47
|
+
function (count: number, rule: string) {
|
|
48
|
+
const errors = diagnostics.filter(
|
|
49
|
+
(d) => d.severity === "error" && d.rule === rule
|
|
50
|
+
);
|
|
51
|
+
expect(errors).toHaveLength(count);
|
|
52
|
+
}
|
|
53
|
+
);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { givenBuilder } from "../../src/given";
|
|
2
|
+
import { whenBuilder } from "../../src/when";
|
|
3
|
+
import { thenBuilder } from "../../src/then";
|
|
4
|
+
import { GivenState, ThenState, WhenState } from "./world";
|
|
5
|
+
import { expect } from "earl";
|
|
6
|
+
|
|
7
|
+
// --- No dependency no variable steps --- //
|
|
8
|
+
givenBuilder<GivenState>()
|
|
9
|
+
.statement("I started")
|
|
10
|
+
.step(() => ({}))
|
|
11
|
+
.register();
|
|
12
|
+
|
|
13
|
+
whenBuilder<GivenState, WhenState>()
|
|
14
|
+
.statement("I got here")
|
|
15
|
+
.step(() => ({}))
|
|
16
|
+
.register();
|
|
17
|
+
|
|
18
|
+
thenBuilder<GivenState, WhenState, ThenState>()
|
|
19
|
+
.statement("everything was good")
|
|
20
|
+
.step(() => ({}))
|
|
21
|
+
.register();
|
|
22
|
+
|
|
23
|
+
// --- Dependency only steps --- //
|
|
24
|
+
givenBuilder<GivenState>()
|
|
25
|
+
.statement("a user")
|
|
26
|
+
.step(() => {
|
|
27
|
+
return {
|
|
28
|
+
user: {
|
|
29
|
+
type: "person",
|
|
30
|
+
token: "random",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
})
|
|
34
|
+
.register();
|
|
35
|
+
|
|
36
|
+
whenBuilder<GivenState, WhenState>()
|
|
37
|
+
.statement("I save the user")
|
|
38
|
+
.dependencies({ given: { user: "required" } })
|
|
39
|
+
.step(({ given: { user } }) => {
|
|
40
|
+
return {
|
|
41
|
+
user: {
|
|
42
|
+
...user,
|
|
43
|
+
saved: true,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
})
|
|
47
|
+
.register();
|
|
48
|
+
|
|
49
|
+
thenBuilder<GivenState, WhenState, ThenState>()
|
|
50
|
+
.statement("there is a user")
|
|
51
|
+
.dependencies({ when: { user: "required" } })
|
|
52
|
+
.step(({ when: { user } }) => {
|
|
53
|
+
expect(user.saved).toBeTruthy();
|
|
54
|
+
})
|
|
55
|
+
.register();
|
|
56
|
+
|
|
57
|
+
// --- Variable only steps --- //
|
|
58
|
+
givenBuilder<GivenState>()
|
|
59
|
+
.statement((userName: string) => `a user named ${userName}`)
|
|
60
|
+
.step(({ variables: [userName] }) => {
|
|
61
|
+
return {
|
|
62
|
+
user: {
|
|
63
|
+
type: "person",
|
|
64
|
+
token: userName,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
})
|
|
68
|
+
.register();
|
|
69
|
+
|
|
70
|
+
// --- More complex steps --- //
|
|
71
|
+
|
|
72
|
+
whenBuilder<GivenState, WhenState>()
|
|
73
|
+
.statement((userName: string) => `I name the user ${userName}`)
|
|
74
|
+
.dependencies({ given: { user: "required" } })
|
|
75
|
+
.step(({ given: { user }, variables: [userName] }) => {
|
|
76
|
+
return {
|
|
77
|
+
user: {
|
|
78
|
+
...user,
|
|
79
|
+
token: userName,
|
|
80
|
+
saved: true,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
})
|
|
84
|
+
.register();
|
|
85
|
+
|
|
86
|
+
thenBuilder<GivenState, WhenState, ThenState>()
|
|
87
|
+
.statement((userName: string) => `the user's name is ${userName}`)
|
|
88
|
+
.dependencies({ when: { user: "required" } })
|
|
89
|
+
.step(({ when: { user }, variables: [userName] }) => {
|
|
90
|
+
const token = user.token;
|
|
91
|
+
expect(token).toEqual(userName);
|
|
92
|
+
})
|
|
93
|
+
.register();
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { expect } from "earl";
|
|
2
|
+
import { givenBuilder } from "../../src/given";
|
|
3
|
+
import { thenBuilder } from "../../src/then";
|
|
4
|
+
import { whenBuilder } from "../../src/when";
|
|
5
|
+
import { GivenState, ThenState, WhenState } from "./world";
|
|
6
|
+
|
|
7
|
+
const Given = givenBuilder<GivenState>().statement;
|
|
8
|
+
const When = whenBuilder<GivenState, WhenState>().statement;
|
|
9
|
+
const Then = thenBuilder<GivenState, WhenState, ThenState>().statement;
|
|
10
|
+
|
|
11
|
+
Given("a bank user")
|
|
12
|
+
.step(() => {
|
|
13
|
+
return {
|
|
14
|
+
user: {
|
|
15
|
+
type: "customer",
|
|
16
|
+
token: "random",
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
})
|
|
20
|
+
.register();
|
|
21
|
+
|
|
22
|
+
When((amount: string, currency: string) => `I deposit ${amount} ${currency}`)
|
|
23
|
+
.dependencies({ given: { user: "required" } })
|
|
24
|
+
.step(({ variables: [rawAmount, currency], given: { user } }) => {
|
|
25
|
+
const amount = parseFloat(rawAmount);
|
|
26
|
+
return {
|
|
27
|
+
deposit: {
|
|
28
|
+
amount,
|
|
29
|
+
currency,
|
|
30
|
+
user,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
})
|
|
34
|
+
.register();
|
|
35
|
+
|
|
36
|
+
Then((amount: string) => `the balance is ${amount}`)
|
|
37
|
+
.dependencies({ when: { deposit: "required" } })
|
|
38
|
+
.step(({ when: { deposit }, variables: [rawAmount] }) => {
|
|
39
|
+
const amount = parseFloat(rawAmount);
|
|
40
|
+
expect(deposit.amount).toEqual(amount);
|
|
41
|
+
})
|
|
42
|
+
.register();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { setWorldConstructor } from "@cucumber/cucumber";
|
|
2
|
+
import { BasicWorld } from "../../src/world";
|
|
3
|
+
|
|
4
|
+
export interface GivenState {
|
|
5
|
+
user: {
|
|
6
|
+
type: string;
|
|
7
|
+
token: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface WhenState {
|
|
12
|
+
user: {
|
|
13
|
+
type: string;
|
|
14
|
+
token: string;
|
|
15
|
+
saved: boolean;
|
|
16
|
+
};
|
|
17
|
+
deposit: {
|
|
18
|
+
amount: number;
|
|
19
|
+
currency: string;
|
|
20
|
+
user: {
|
|
21
|
+
type: string;
|
|
22
|
+
token: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ThenState {}
|
|
28
|
+
|
|
29
|
+
setWorldConstructor(BasicWorld<GivenState, WhenState, ThenState>);
|