@step-forge/step-forge 0.0.19 → 0.0.21
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 +5 -2
- package/RUNTIME.md +212 -0
- package/dist/{analyzer-QH03uejK.js → analyzer-DYfdoSIS.js} +55 -16
- package/dist/analyzer-DYfdoSIS.js.map +1 -0
- package/dist/analyzer-cli.js +1 -1
- package/dist/analyzer-cli.js.map +1 -1
- package/dist/analyzer.d.ts +14 -0
- package/dist/analyzer.js +1 -1
- package/dist/cli.cjs +470 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +471 -0
- package/dist/cli.js.map +1 -0
- package/dist/engine-DPRxs6eC.js +181 -0
- package/dist/engine-DPRxs6eC.js.map +1 -0
- package/dist/engine-DWAIlwWp.cjs +204 -0
- package/dist/engine-DWAIlwWp.cjs.map +1 -0
- package/dist/gherkinParser-CHpkEwii.cjs +243 -0
- package/dist/gherkinParser-CHpkEwii.cjs.map +1 -0
- package/dist/gherkinParser-CKARHgt4.js +188 -0
- package/dist/gherkinParser-CKARHgt4.js.map +1 -0
- package/dist/hooks-CGYzwDOv.cjs +117 -0
- package/dist/hooks-CGYzwDOv.cjs.map +1 -0
- package/dist/hooks-CywugMQQ.js +82 -0
- package/dist/hooks-CywugMQQ.js.map +1 -0
- package/dist/hooks-Dar49TtT.d.cts +189 -0
- package/dist/hooks-Dar49TtT.d.ts +189 -0
- package/dist/runtime.cjs +13 -0
- package/dist/runtime.d.cts +142 -0
- package/dist/runtime.d.ts +142 -0
- package/dist/runtime.js +3 -0
- package/dist/step-forge.cjs +185 -306
- package/dist/step-forge.cjs.map +1 -1
- package/dist/step-forge.d.cts +193 -733
- package/dist/step-forge.d.ts +193 -733
- package/dist/step-forge.js +174 -276
- package/dist/step-forge.js.map +1 -1
- package/package.json +20 -9
- package/dist/analyzer-QH03uejK.js.map +0 -1
package/dist/step-forge.js
CHANGED
|
@@ -1,34 +1,41 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o as globalRegistry, r as globalHookRegistry } from "./hooks-CywugMQQ.js";
|
|
2
|
+
import { i as BasicWorld, n as parseFeatureFiles, r as globFiles, t as parseFeatureContent } from "./gherkinParser-CKARHgt4.js";
|
|
2
3
|
import _ from "lodash";
|
|
3
|
-
import { glob } from "node:fs/promises";
|
|
4
|
-
import * as path from "node:path";
|
|
5
4
|
import ts from "typescript";
|
|
6
|
-
import * as fs from "node:fs";
|
|
7
|
-
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
|
|
8
|
-
import * as messages from "@cucumber/messages";
|
|
9
5
|
//#region src/builderTypeUtils.ts
|
|
10
6
|
const isString = (statement) => typeof statement === "string";
|
|
11
7
|
//#endregion
|
|
12
8
|
//#region src/parsers.ts
|
|
13
|
-
/** Matches a quoted string (`{string}`) and
|
|
9
|
+
/** Matches a quoted string (`{string}`) and strips the surrounding quotes. */
|
|
14
10
|
const stringParser = {
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
name: "string",
|
|
12
|
+
regexp: [/"([^"\\]*(\\.[^"\\]*)*)"/, /'([^'\\]*(\\.[^'\\]*)*)'/],
|
|
13
|
+
parse: (value) => {
|
|
14
|
+
const match = /^"([\s\S]*)"$/.exec(value) ?? /^'([\s\S]*)'$/.exec(value);
|
|
15
|
+
return match ? match[1].replace(/\\(["'])/g, "$1") : value;
|
|
16
|
+
}
|
|
17
17
|
};
|
|
18
18
|
/** Matches an unquoted integer (`{int}`). */
|
|
19
19
|
const intParser = {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
name: "int",
|
|
21
|
+
regexp: /-?\d+/,
|
|
22
|
+
parse: (value) => parseInt(value, 10)
|
|
22
23
|
};
|
|
23
24
|
/** Matches an unquoted floating point number (`{float}`). */
|
|
24
25
|
const numberParser = {
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
name: "float",
|
|
27
|
+
regexp: /-?\d*\.?\d+/,
|
|
28
|
+
parse: (value) => parseFloat(value)
|
|
27
29
|
};
|
|
28
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Matches an unquoted `true`/`false` word (`{boolean}`) and parses it to a
|
|
32
|
+
* boolean. Unlike the others this is a genuine custom parameter type — cucumber
|
|
33
|
+
* has no built-in `boolean` — so its `regexp`/`parse` are what the matcher uses.
|
|
34
|
+
*/
|
|
29
35
|
const booleanParser = {
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
name: "boolean",
|
|
37
|
+
regexp: /true|false/,
|
|
38
|
+
parse: (value) => value === "true"
|
|
32
39
|
};
|
|
33
40
|
//#endregion
|
|
34
41
|
//#region src/utils.ts
|
|
@@ -67,187 +74,123 @@ const requireFromThen = (keys, world) => {
|
|
|
67
74
|
};
|
|
68
75
|
//#endregion
|
|
69
76
|
//#region src/common.ts
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The runtime core of the builder chain. `addStep` is deliberately phase-agnostic:
|
|
79
|
+
* the calling builder (given/when/then) has already computed the exact type of the
|
|
80
|
+
* step function via its two type parameters:
|
|
81
|
+
*
|
|
82
|
+
* - `StepFnInput` — the `{ variables, given, when, then }` object the step receives,
|
|
83
|
+
* with each phase already narrowed to its declared dependencies.
|
|
84
|
+
* - `StepFnOutput` — the phase-appropriate return type (`Partial<State>`, or `void`).
|
|
85
|
+
*
|
|
86
|
+
* Everything else is plain runtime data (a statement function, the step type, the
|
|
87
|
+
* dependency map, the parsers), so `addStep` carries no generics for them.
|
|
88
|
+
*/
|
|
75
89
|
const addStep = (statement, stepType, dependencies = {
|
|
76
90
|
given: {},
|
|
77
91
|
when: {},
|
|
78
92
|
then: {}
|
|
79
93
|
}, declaredParsers) => (stepFunction) => {
|
|
80
|
-
const statementFunction = statement;
|
|
81
94
|
const { given: givenDependencies, when: whenDependencies, then: thenDependencies } = dependencies;
|
|
82
|
-
const argCount =
|
|
95
|
+
const argCount = statement.length;
|
|
83
96
|
const parsers = declaredParsers ?? Array.from({ length: argCount }, () => stringParser);
|
|
84
|
-
const expression =
|
|
97
|
+
const expression = statement(...parsers.map((parser) => `{${parser.name}}`));
|
|
98
|
+
const execute = async (world, capturedArgs) => {
|
|
99
|
+
const requiredKeys = (deps) => Object.entries(deps).filter(([, value]) => value === "required").map(([key]) => key);
|
|
100
|
+
const result = await stepFunction({
|
|
101
|
+
variables: capturedArgs,
|
|
102
|
+
given: {
|
|
103
|
+
..._.pick(world.given, Object.keys(givenDependencies)),
|
|
104
|
+
...requireFromGiven(requiredKeys(givenDependencies), world)
|
|
105
|
+
},
|
|
106
|
+
when: {
|
|
107
|
+
..._.pick(world.when, Object.keys(whenDependencies)),
|
|
108
|
+
...requireFromWhen(requiredKeys(whenDependencies), world)
|
|
109
|
+
},
|
|
110
|
+
then: {
|
|
111
|
+
..._.pick(world.then, Object.keys(thenDependencies)),
|
|
112
|
+
...requireFromThen(requiredKeys(thenDependencies), world)
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
world[stepType].merge({ ...result });
|
|
116
|
+
};
|
|
117
|
+
globalRegistry.add({
|
|
118
|
+
stepType,
|
|
119
|
+
expression,
|
|
120
|
+
parsers,
|
|
121
|
+
execute
|
|
122
|
+
});
|
|
85
123
|
return {
|
|
86
124
|
statement,
|
|
87
125
|
expression,
|
|
88
126
|
dependencies,
|
|
89
127
|
stepType,
|
|
90
|
-
stepFunction
|
|
91
|
-
register: () => {
|
|
92
|
-
const cucStepFunction = Object.defineProperty(async function(...args) {
|
|
93
|
-
const coercedArgs = parsers.map((parser, index) => parser.parse(args[index]));
|
|
94
|
-
const ensuredGivenValues = requireFromGiven(Object.entries(givenDependencies ?? {}).filter(([, value]) => value === "required").map(([key]) => key), this);
|
|
95
|
-
const narrowedGiven = {
|
|
96
|
-
..._.pick(this.given, Object.keys(givenDependencies ?? {})),
|
|
97
|
-
...ensuredGivenValues
|
|
98
|
-
};
|
|
99
|
-
const ensuredWhenValues = requireFromWhen(Object.entries(whenDependencies ?? {}).filter(([, value]) => value === "required").map(([key]) => key), this);
|
|
100
|
-
const narrowedWhen = {
|
|
101
|
-
..._.pick(this.when, Object.keys(whenDependencies ?? {})),
|
|
102
|
-
...ensuredWhenValues
|
|
103
|
-
};
|
|
104
|
-
const ensuredThenValues = requireFromThen(Object.entries(thenDependencies ?? {}).filter(([, value]) => value === "required").map(([key]) => key), this);
|
|
105
|
-
const result = await stepFunction({
|
|
106
|
-
variables: coercedArgs,
|
|
107
|
-
given: narrowedGiven,
|
|
108
|
-
when: narrowedWhen,
|
|
109
|
-
then: {
|
|
110
|
-
..._.pick(this.then, Object.keys(thenDependencies ?? {})),
|
|
111
|
-
...ensuredThenValues
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
this[stepType].merge({ ...result });
|
|
115
|
-
}, "length", {
|
|
116
|
-
value: argCount,
|
|
117
|
-
configurable: true
|
|
118
|
-
});
|
|
119
|
-
const cucStep = cucFunctionMap[stepType];
|
|
120
|
-
cucStep(expression, cucStepFunction);
|
|
121
|
-
return {
|
|
122
|
-
stepType,
|
|
123
|
-
expression,
|
|
124
|
-
dependencies,
|
|
125
|
-
statement: statementFunction,
|
|
126
|
-
stepFunction
|
|
127
|
-
};
|
|
128
|
-
}
|
|
128
|
+
stepFunction
|
|
129
129
|
};
|
|
130
130
|
};
|
|
131
131
|
//#endregion
|
|
132
132
|
//#region src/given.ts
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
};
|
|
146
|
-
const givenStatement = (stepType) => (statement) => {
|
|
147
|
-
let normalizedStatement;
|
|
148
|
-
if (isString(statement)) normalizedStatement = (() => statement);
|
|
149
|
-
else normalizedStatement = statement;
|
|
133
|
+
const GIVEN = "given";
|
|
134
|
+
const givenDependencies = (statement, parsers) => (dependencies) => ({ step: addStep(statement, GIVEN, {
|
|
135
|
+
...dependencies,
|
|
136
|
+
when: {},
|
|
137
|
+
then: {}
|
|
138
|
+
}, parsers) });
|
|
139
|
+
const givenParsers = (statement) => (parsers) => ({
|
|
140
|
+
dependencies: givenDependencies(statement, parsers),
|
|
141
|
+
step: addStep(statement, GIVEN, void 0, parsers)
|
|
142
|
+
});
|
|
143
|
+
const givenStatement = () => (statement) => {
|
|
144
|
+
const normalizedStatement = isString(statement) ? () => statement : statement;
|
|
150
145
|
return {
|
|
151
|
-
dependencies: givenDependencies(normalizedStatement
|
|
152
|
-
parsers: givenParsers(normalizedStatement
|
|
153
|
-
step: addStep(normalizedStatement,
|
|
146
|
+
dependencies: givenDependencies(normalizedStatement),
|
|
147
|
+
parsers: givenParsers(normalizedStatement),
|
|
148
|
+
step: addStep(normalizedStatement, GIVEN)
|
|
154
149
|
};
|
|
155
150
|
};
|
|
156
|
-
const givenBuilder = () => {
|
|
157
|
-
return { statement: givenStatement("given") };
|
|
158
|
-
};
|
|
151
|
+
const givenBuilder = () => ({ statement: givenStatement() });
|
|
159
152
|
//#endregion
|
|
160
153
|
//#region src/when.ts
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
};
|
|
174
|
-
const whenStatement = (stepType) => (statement) => {
|
|
175
|
-
let normalizedStatement;
|
|
176
|
-
if (isString(statement)) normalizedStatement = (() => statement);
|
|
177
|
-
else normalizedStatement = statement;
|
|
154
|
+
const WHEN = "when";
|
|
155
|
+
const whenDependencies = (statement, parsers) => (dependencies) => ({ step: addStep(statement, WHEN, {
|
|
156
|
+
given: dependencies.given ?? {},
|
|
157
|
+
when: dependencies.when ?? {},
|
|
158
|
+
then: {}
|
|
159
|
+
}, parsers) });
|
|
160
|
+
const whenParsers = (statement) => (parsers) => ({
|
|
161
|
+
dependencies: whenDependencies(statement, parsers),
|
|
162
|
+
step: addStep(statement, WHEN, void 0, parsers)
|
|
163
|
+
});
|
|
164
|
+
const whenStatement = () => (statement) => {
|
|
165
|
+
const normalizedStatement = isString(statement) ? () => statement : statement;
|
|
178
166
|
return {
|
|
179
|
-
dependencies: whenDependencies(normalizedStatement
|
|
180
|
-
parsers: whenParsers(normalizedStatement
|
|
181
|
-
step: addStep(normalizedStatement,
|
|
167
|
+
dependencies: whenDependencies(normalizedStatement),
|
|
168
|
+
parsers: whenParsers(normalizedStatement),
|
|
169
|
+
step: addStep(normalizedStatement, WHEN)
|
|
182
170
|
};
|
|
183
171
|
};
|
|
184
|
-
const whenBuilder = () => {
|
|
185
|
-
return { statement: whenStatement("when") };
|
|
186
|
-
};
|
|
172
|
+
const whenBuilder = () => ({ statement: whenStatement() });
|
|
187
173
|
//#endregion
|
|
188
174
|
//#region src/then.ts
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
};
|
|
202
|
-
const thenStatement = (stepType) => (statement) => {
|
|
203
|
-
let normalizedStatement;
|
|
204
|
-
if (isString(statement)) normalizedStatement = (() => statement);
|
|
205
|
-
else normalizedStatement = statement;
|
|
175
|
+
const THEN = "then";
|
|
176
|
+
const thenDependencies = (statement, parsers) => (dependencies) => ({ step: addStep(statement, THEN, {
|
|
177
|
+
given: dependencies.given ?? {},
|
|
178
|
+
when: dependencies.when ?? {},
|
|
179
|
+
then: dependencies.then ?? {}
|
|
180
|
+
}, parsers) });
|
|
181
|
+
const thenParsers = (statement) => (parsers) => ({
|
|
182
|
+
dependencies: thenDependencies(statement, parsers),
|
|
183
|
+
step: addStep(statement, THEN, void 0, parsers)
|
|
184
|
+
});
|
|
185
|
+
const thenStatement = () => (statement) => {
|
|
186
|
+
const normalizedStatement = isString(statement) ? () => statement : statement;
|
|
206
187
|
return {
|
|
207
|
-
dependencies: thenDependencies(normalizedStatement
|
|
208
|
-
parsers: thenParsers(normalizedStatement
|
|
209
|
-
step: addStep(normalizedStatement,
|
|
188
|
+
dependencies: thenDependencies(normalizedStatement),
|
|
189
|
+
parsers: thenParsers(normalizedStatement),
|
|
190
|
+
step: addStep(normalizedStatement, THEN)
|
|
210
191
|
};
|
|
211
192
|
};
|
|
212
|
-
const thenBuilder = () => {
|
|
213
|
-
return { statement: thenStatement("then") };
|
|
214
|
-
};
|
|
215
|
-
//#endregion
|
|
216
|
-
//#region src/world.ts
|
|
217
|
-
function mergeCustomizer(objValue, srcValue) {
|
|
218
|
-
if (_.isArray(objValue)) return objValue.concat(srcValue);
|
|
219
|
-
else if (objValue && !_.isPlainObject(objValue) && objValue !== srcValue) throw new Error(`Merge would have destroyed previous value ${objValue} with ${srcValue}`);
|
|
220
|
-
return objValue;
|
|
221
|
-
}
|
|
222
|
-
var BasicWorld = class {
|
|
223
|
-
givenState = {};
|
|
224
|
-
whenState = {};
|
|
225
|
-
thenState = {};
|
|
226
|
-
get given() {
|
|
227
|
-
return {
|
|
228
|
-
...this.givenState,
|
|
229
|
-
merge: (newState) => {
|
|
230
|
-
this.givenState = _.merge({ ...this.givenState }, newState, mergeCustomizer);
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
get when() {
|
|
235
|
-
return {
|
|
236
|
-
...this.whenState,
|
|
237
|
-
merge: (newState) => {
|
|
238
|
-
this.whenState = _.merge({ ...this.whenState }, newState, mergeCustomizer);
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
get then() {
|
|
243
|
-
return {
|
|
244
|
-
...this.thenState,
|
|
245
|
-
merge: (newState) => {
|
|
246
|
-
this.thenState = _.merge({ ...this.thenState }, newState, mergeCustomizer);
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
};
|
|
193
|
+
const thenBuilder = () => ({ statement: thenStatement() });
|
|
251
194
|
//#endregion
|
|
252
195
|
//#region src/analyzer/stepExtractor.ts
|
|
253
196
|
const BUILDER_NAMES = {
|
|
@@ -283,7 +226,7 @@ function extractStepDefinitions(filePaths, tsConfigPath) {
|
|
|
283
226
|
function extractFromSourceFile(sourceFile, checker) {
|
|
284
227
|
const results = [];
|
|
285
228
|
function visit(node) {
|
|
286
|
-
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "
|
|
229
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "step") {
|
|
287
230
|
const meta = extractFromRegisterCall(node, sourceFile, checker);
|
|
288
231
|
if (meta) results.push(meta);
|
|
289
232
|
}
|
|
@@ -466,105 +409,6 @@ function resolveReExportedCall(chain, checker) {
|
|
|
466
409
|
return null;
|
|
467
410
|
}
|
|
468
411
|
//#endregion
|
|
469
|
-
//#region src/analyzer/gherkinParser.ts
|
|
470
|
-
function parseFeatureFiles(filePaths) {
|
|
471
|
-
const scenarios = [];
|
|
472
|
-
for (const filePath of filePaths) {
|
|
473
|
-
const parsed = parseFeatureContent(fs.readFileSync(filePath, "utf-8"), filePath);
|
|
474
|
-
scenarios.push(...parsed);
|
|
475
|
-
}
|
|
476
|
-
return scenarios;
|
|
477
|
-
}
|
|
478
|
-
function parseFeatureContent(content, filePath) {
|
|
479
|
-
const feature = new Parser(new AstBuilder(messages.IdGenerator.uuid()), new GherkinClassicTokenMatcher()).parse(content).feature;
|
|
480
|
-
if (!feature) return [];
|
|
481
|
-
const featureBackground = [];
|
|
482
|
-
const scenarios = [];
|
|
483
|
-
for (const child of feature.children) {
|
|
484
|
-
if (child.background) featureBackground.push(...child.background.steps);
|
|
485
|
-
if (child.scenario) scenarios.push(...expandScenario(child.scenario, featureBackground, filePath));
|
|
486
|
-
if (child.rule) {
|
|
487
|
-
const ruleBackground = [...featureBackground];
|
|
488
|
-
for (const ruleChild of child.rule.children) {
|
|
489
|
-
if (ruleChild.background) ruleBackground.push(...ruleChild.background.steps);
|
|
490
|
-
if (ruleChild.scenario) scenarios.push(...expandScenario(ruleChild.scenario, ruleBackground, filePath));
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
return scenarios;
|
|
495
|
-
}
|
|
496
|
-
function expandScenario(scenario, backgroundSteps, filePath) {
|
|
497
|
-
if (!(scenario.examples.length > 0 && scenario.examples.some((e) => e.tableBody.length > 0))) {
|
|
498
|
-
const bgParsed = convertSteps(backgroundSteps);
|
|
499
|
-
const scenarioParsed = convertSteps(scenario.steps);
|
|
500
|
-
const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioParsed]);
|
|
501
|
-
return [{
|
|
502
|
-
name: scenario.name,
|
|
503
|
-
file: filePath,
|
|
504
|
-
steps: allSteps
|
|
505
|
-
}];
|
|
506
|
-
}
|
|
507
|
-
const results = [];
|
|
508
|
-
for (const example of scenario.examples) {
|
|
509
|
-
if (!example.tableHeader || example.tableBody.length === 0) continue;
|
|
510
|
-
const headers = example.tableHeader.cells.map((c) => c.value);
|
|
511
|
-
for (const row of example.tableBody) {
|
|
512
|
-
const values = row.cells.map((c) => c.value);
|
|
513
|
-
const substitution = {};
|
|
514
|
-
headers.forEach((h, i) => {
|
|
515
|
-
substitution[h] = values[i];
|
|
516
|
-
});
|
|
517
|
-
const bgParsed = convertSteps(backgroundSteps);
|
|
518
|
-
const scenarioSteps = convertSteps(scenario.steps).map((step) => ({
|
|
519
|
-
...step,
|
|
520
|
-
text: substituteExampleValues(step.text, substitution)
|
|
521
|
-
}));
|
|
522
|
-
const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioSteps]);
|
|
523
|
-
results.push({
|
|
524
|
-
name: `${scenario.name} (${values.join(", ")})`,
|
|
525
|
-
file: filePath,
|
|
526
|
-
steps: allSteps
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
return results;
|
|
531
|
-
}
|
|
532
|
-
function convertSteps(steps) {
|
|
533
|
-
return steps.map((step) => ({
|
|
534
|
-
keyword: normalizeKeyword(step.keyword),
|
|
535
|
-
text: step.text,
|
|
536
|
-
line: step.location.line,
|
|
537
|
-
column: (step.location.column ?? 1) + step.keyword.length
|
|
538
|
-
}));
|
|
539
|
-
}
|
|
540
|
-
function normalizeKeyword(keyword) {
|
|
541
|
-
const trimmed = keyword.trim();
|
|
542
|
-
if (trimmed === "Given") return "Given";
|
|
543
|
-
if (trimmed === "When") return "When";
|
|
544
|
-
if (trimmed === "Then") return "Then";
|
|
545
|
-
if (trimmed === "And") return "And";
|
|
546
|
-
if (trimmed === "But") return "But";
|
|
547
|
-
return "Given";
|
|
548
|
-
}
|
|
549
|
-
function resolveEffectiveKeywords(steps) {
|
|
550
|
-
let lastEffective = "Given";
|
|
551
|
-
return steps.map((step) => {
|
|
552
|
-
let effectiveKeyword;
|
|
553
|
-
if (step.keyword === "And" || step.keyword === "But") effectiveKeyword = lastEffective;
|
|
554
|
-
else effectiveKeyword = step.keyword;
|
|
555
|
-
lastEffective = effectiveKeyword;
|
|
556
|
-
return {
|
|
557
|
-
...step,
|
|
558
|
-
effectiveKeyword
|
|
559
|
-
};
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
function substituteExampleValues(text, substitution) {
|
|
563
|
-
let result = text;
|
|
564
|
-
for (const [key, value] of Object.entries(substitution)) result = result.replace(new RegExp(`<${key}>`, "g"), value);
|
|
565
|
-
return result;
|
|
566
|
-
}
|
|
567
|
-
//#endregion
|
|
568
412
|
//#region src/analyzer/stepMatcher.ts
|
|
569
413
|
function compileDefinitions(definitions) {
|
|
570
414
|
const compiled = [];
|
|
@@ -699,8 +543,8 @@ function runRules(rules, scenario, matchedSteps) {
|
|
|
699
543
|
//#region src/analyzer/index.ts
|
|
700
544
|
async function analyze(config, options) {
|
|
701
545
|
const rules = options?.rules ?? defaultRules;
|
|
702
|
-
const stepFilePaths = await
|
|
703
|
-
const featureFilePaths = await
|
|
546
|
+
const stepFilePaths = await globFiles(config.stepFiles);
|
|
547
|
+
const featureFilePaths = await globFiles(config.featureFiles);
|
|
704
548
|
if (stepFilePaths.length === 0) return [];
|
|
705
549
|
if (featureFilePaths.length === 0) return [];
|
|
706
550
|
const stepDefinitions = extractStepDefinitions(stepFilePaths, config.tsConfigPath);
|
|
@@ -712,11 +556,6 @@ async function analyze(config, options) {
|
|
|
712
556
|
}
|
|
713
557
|
return diagnostics;
|
|
714
558
|
}
|
|
715
|
-
async function resolveGlobs(patterns) {
|
|
716
|
-
const files = [];
|
|
717
|
-
for (const pattern of patterns) for await (const file of glob(pattern)) files.push(path.resolve(file));
|
|
718
|
-
return [...new Set(files)];
|
|
719
|
-
}
|
|
720
559
|
//#endregion
|
|
721
560
|
//#region src/init.ts
|
|
722
561
|
const createBuilders = () => {
|
|
@@ -727,6 +566,65 @@ const createBuilders = () => {
|
|
|
727
566
|
};
|
|
728
567
|
};
|
|
729
568
|
//#endregion
|
|
569
|
+
//#region src/hooks.ts
|
|
570
|
+
/**
|
|
571
|
+
* Run before every scenario, with access to that scenario's fresh world. For
|
|
572
|
+
* side effects only (reset a mock, seed an external system) — return values are
|
|
573
|
+
* ignored; seed test *state* with given steps so the dependency graph stays the
|
|
574
|
+
* single source of truth.
|
|
575
|
+
*/
|
|
576
|
+
function beforeScenario(fn) {
|
|
577
|
+
globalHookRegistry.add({
|
|
578
|
+
scope: "scenario",
|
|
579
|
+
timing: "before",
|
|
580
|
+
fn
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Run after every scenario (even when a step failed), with access to that
|
|
585
|
+
* scenario's world. Runs in reverse registration order so teardown unwinds
|
|
586
|
+
* setup. For cleanup side effects only.
|
|
587
|
+
*/
|
|
588
|
+
function afterScenario(fn) {
|
|
589
|
+
globalHookRegistry.add({
|
|
590
|
+
scope: "scenario",
|
|
591
|
+
timing: "after",
|
|
592
|
+
fn
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
/** Run once at the start of each feature file. No world exists at this boundary. */
|
|
596
|
+
function beforeFeature(fn) {
|
|
597
|
+
globalHookRegistry.add({
|
|
598
|
+
scope: "feature",
|
|
599
|
+
timing: "before",
|
|
600
|
+
fn
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
/** Run once at the end of each feature file (reverse registration order). */
|
|
604
|
+
function afterFeature(fn) {
|
|
605
|
+
globalHookRegistry.add({
|
|
606
|
+
scope: "feature",
|
|
607
|
+
timing: "after",
|
|
608
|
+
fn
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
/** Run once before the entire test run, across all feature files. */
|
|
612
|
+
function beforeAll(fn) {
|
|
613
|
+
globalHookRegistry.add({
|
|
614
|
+
scope: "global",
|
|
615
|
+
timing: "before",
|
|
616
|
+
fn
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
/** Run once after the entire test run (reverse registration order). */
|
|
620
|
+
function afterAll(fn) {
|
|
621
|
+
globalHookRegistry.add({
|
|
622
|
+
scope: "global",
|
|
623
|
+
timing: "after",
|
|
624
|
+
fn
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
//#endregion
|
|
730
628
|
//#region src/index.ts
|
|
731
629
|
const analyzer = {
|
|
732
630
|
analyze,
|
|
@@ -738,6 +636,6 @@ const analyzer = {
|
|
|
738
636
|
defaultRules
|
|
739
637
|
};
|
|
740
638
|
//#endregion
|
|
741
|
-
export { BasicWorld, analyzer, booleanParser, createBuilders, givenBuilder, intParser, numberParser, stringParser, thenBuilder, whenBuilder };
|
|
639
|
+
export { BasicWorld, afterAll, afterFeature, afterScenario, analyzer, beforeAll, beforeFeature, beforeScenario, booleanParser, createBuilders, givenBuilder, intParser, numberParser, stringParser, thenBuilder, whenBuilder };
|
|
742
640
|
|
|
743
641
|
//# sourceMappingURL=step-forge.js.map
|