@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.cjs
CHANGED
|
@@ -1,62 +1,44 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
-
get: ((k) => from[k]).bind(null, key),
|
|
14
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
-
value: mod,
|
|
21
|
-
enumerable: true
|
|
22
|
-
}) : target, mod));
|
|
23
|
-
//#endregion
|
|
24
|
-
let _cucumber_cucumber = require("@cucumber/cucumber");
|
|
2
|
+
const require_gherkinParser = require("./gherkinParser-CHpkEwii.cjs");
|
|
3
|
+
const require_hooks = require("./hooks-CGYzwDOv.cjs");
|
|
25
4
|
let lodash = require("lodash");
|
|
26
|
-
lodash = __toESM(lodash, 1);
|
|
27
|
-
let node_fs_promises = require("node:fs/promises");
|
|
28
|
-
let node_path = require("node:path");
|
|
29
|
-
node_path = __toESM(node_path, 1);
|
|
5
|
+
lodash = require_gherkinParser.__toESM(lodash, 1);
|
|
30
6
|
let typescript = require("typescript");
|
|
31
|
-
typescript = __toESM(typescript, 1);
|
|
32
|
-
let node_fs = require("node:fs");
|
|
33
|
-
node_fs = __toESM(node_fs, 1);
|
|
34
|
-
let _cucumber_gherkin = require("@cucumber/gherkin");
|
|
35
|
-
let _cucumber_messages = require("@cucumber/messages");
|
|
36
|
-
_cucumber_messages = __toESM(_cucumber_messages, 1);
|
|
7
|
+
typescript = require_gherkinParser.__toESM(typescript, 1);
|
|
37
8
|
//#region src/builderTypeUtils.ts
|
|
38
9
|
const isString = (statement) => typeof statement === "string";
|
|
39
10
|
//#endregion
|
|
40
11
|
//#region src/parsers.ts
|
|
41
|
-
/** Matches a quoted string (`{string}`) and
|
|
12
|
+
/** Matches a quoted string (`{string}`) and strips the surrounding quotes. */
|
|
42
13
|
const stringParser = {
|
|
43
|
-
|
|
44
|
-
|
|
14
|
+
name: "string",
|
|
15
|
+
regexp: [/"([^"\\]*(\\.[^"\\]*)*)"/, /'([^'\\]*(\\.[^'\\]*)*)'/],
|
|
16
|
+
parse: (value) => {
|
|
17
|
+
const match = /^"([\s\S]*)"$/.exec(value) ?? /^'([\s\S]*)'$/.exec(value);
|
|
18
|
+
return match ? match[1].replace(/\\(["'])/g, "$1") : value;
|
|
19
|
+
}
|
|
45
20
|
};
|
|
46
21
|
/** Matches an unquoted integer (`{int}`). */
|
|
47
22
|
const intParser = {
|
|
48
|
-
|
|
49
|
-
|
|
23
|
+
name: "int",
|
|
24
|
+
regexp: /-?\d+/,
|
|
25
|
+
parse: (value) => parseInt(value, 10)
|
|
50
26
|
};
|
|
51
27
|
/** Matches an unquoted floating point number (`{float}`). */
|
|
52
28
|
const numberParser = {
|
|
53
|
-
|
|
54
|
-
|
|
29
|
+
name: "float",
|
|
30
|
+
regexp: /-?\d*\.?\d+/,
|
|
31
|
+
parse: (value) => parseFloat(value)
|
|
55
32
|
};
|
|
56
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Matches an unquoted `true`/`false` word (`{boolean}`) and parses it to a
|
|
35
|
+
* boolean. Unlike the others this is a genuine custom parameter type — cucumber
|
|
36
|
+
* has no built-in `boolean` — so its `regexp`/`parse` are what the matcher uses.
|
|
37
|
+
*/
|
|
57
38
|
const booleanParser = {
|
|
58
|
-
|
|
59
|
-
|
|
39
|
+
name: "boolean",
|
|
40
|
+
regexp: /true|false/,
|
|
41
|
+
parse: (value) => value === "true"
|
|
60
42
|
};
|
|
61
43
|
//#endregion
|
|
62
44
|
//#region src/utils.ts
|
|
@@ -95,187 +77,123 @@ const requireFromThen = (keys, world) => {
|
|
|
95
77
|
};
|
|
96
78
|
//#endregion
|
|
97
79
|
//#region src/common.ts
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
80
|
+
/**
|
|
81
|
+
* The runtime core of the builder chain. `addStep` is deliberately phase-agnostic:
|
|
82
|
+
* the calling builder (given/when/then) has already computed the exact type of the
|
|
83
|
+
* step function via its two type parameters:
|
|
84
|
+
*
|
|
85
|
+
* - `StepFnInput` — the `{ variables, given, when, then }` object the step receives,
|
|
86
|
+
* with each phase already narrowed to its declared dependencies.
|
|
87
|
+
* - `StepFnOutput` — the phase-appropriate return type (`Partial<State>`, or `void`).
|
|
88
|
+
*
|
|
89
|
+
* Everything else is plain runtime data (a statement function, the step type, the
|
|
90
|
+
* dependency map, the parsers), so `addStep` carries no generics for them.
|
|
91
|
+
*/
|
|
103
92
|
const addStep = (statement, stepType, dependencies = {
|
|
104
93
|
given: {},
|
|
105
94
|
when: {},
|
|
106
95
|
then: {}
|
|
107
96
|
}, declaredParsers) => (stepFunction) => {
|
|
108
|
-
const statementFunction = statement;
|
|
109
97
|
const { given: givenDependencies, when: whenDependencies, then: thenDependencies } = dependencies;
|
|
110
|
-
const argCount =
|
|
98
|
+
const argCount = statement.length;
|
|
111
99
|
const parsers = declaredParsers ?? Array.from({ length: argCount }, () => stringParser);
|
|
112
|
-
const expression =
|
|
100
|
+
const expression = statement(...parsers.map((parser) => `{${parser.name}}`));
|
|
101
|
+
const execute = async (world, capturedArgs) => {
|
|
102
|
+
const requiredKeys = (deps) => Object.entries(deps).filter(([, value]) => value === "required").map(([key]) => key);
|
|
103
|
+
const result = await stepFunction({
|
|
104
|
+
variables: capturedArgs,
|
|
105
|
+
given: {
|
|
106
|
+
...lodash.default.pick(world.given, Object.keys(givenDependencies)),
|
|
107
|
+
...requireFromGiven(requiredKeys(givenDependencies), world)
|
|
108
|
+
},
|
|
109
|
+
when: {
|
|
110
|
+
...lodash.default.pick(world.when, Object.keys(whenDependencies)),
|
|
111
|
+
...requireFromWhen(requiredKeys(whenDependencies), world)
|
|
112
|
+
},
|
|
113
|
+
then: {
|
|
114
|
+
...lodash.default.pick(world.then, Object.keys(thenDependencies)),
|
|
115
|
+
...requireFromThen(requiredKeys(thenDependencies), world)
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
world[stepType].merge({ ...result });
|
|
119
|
+
};
|
|
120
|
+
require_hooks.globalRegistry.add({
|
|
121
|
+
stepType,
|
|
122
|
+
expression,
|
|
123
|
+
parsers,
|
|
124
|
+
execute
|
|
125
|
+
});
|
|
113
126
|
return {
|
|
114
127
|
statement,
|
|
115
128
|
expression,
|
|
116
129
|
dependencies,
|
|
117
130
|
stepType,
|
|
118
|
-
stepFunction
|
|
119
|
-
register: () => {
|
|
120
|
-
const cucStepFunction = Object.defineProperty(async function(...args) {
|
|
121
|
-
const coercedArgs = parsers.map((parser, index) => parser.parse(args[index]));
|
|
122
|
-
const ensuredGivenValues = requireFromGiven(Object.entries(givenDependencies ?? {}).filter(([, value]) => value === "required").map(([key]) => key), this);
|
|
123
|
-
const narrowedGiven = {
|
|
124
|
-
...lodash.default.pick(this.given, Object.keys(givenDependencies ?? {})),
|
|
125
|
-
...ensuredGivenValues
|
|
126
|
-
};
|
|
127
|
-
const ensuredWhenValues = requireFromWhen(Object.entries(whenDependencies ?? {}).filter(([, value]) => value === "required").map(([key]) => key), this);
|
|
128
|
-
const narrowedWhen = {
|
|
129
|
-
...lodash.default.pick(this.when, Object.keys(whenDependencies ?? {})),
|
|
130
|
-
...ensuredWhenValues
|
|
131
|
-
};
|
|
132
|
-
const ensuredThenValues = requireFromThen(Object.entries(thenDependencies ?? {}).filter(([, value]) => value === "required").map(([key]) => key), this);
|
|
133
|
-
const result = await stepFunction({
|
|
134
|
-
variables: coercedArgs,
|
|
135
|
-
given: narrowedGiven,
|
|
136
|
-
when: narrowedWhen,
|
|
137
|
-
then: {
|
|
138
|
-
...lodash.default.pick(this.then, Object.keys(thenDependencies ?? {})),
|
|
139
|
-
...ensuredThenValues
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
this[stepType].merge({ ...result });
|
|
143
|
-
}, "length", {
|
|
144
|
-
value: argCount,
|
|
145
|
-
configurable: true
|
|
146
|
-
});
|
|
147
|
-
const cucStep = cucFunctionMap[stepType];
|
|
148
|
-
cucStep(expression, cucStepFunction);
|
|
149
|
-
return {
|
|
150
|
-
stepType,
|
|
151
|
-
expression,
|
|
152
|
-
dependencies,
|
|
153
|
-
statement: statementFunction,
|
|
154
|
-
stepFunction
|
|
155
|
-
};
|
|
156
|
-
}
|
|
131
|
+
stepFunction
|
|
157
132
|
};
|
|
158
133
|
};
|
|
159
134
|
//#endregion
|
|
160
135
|
//#region src/given.ts
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
};
|
|
174
|
-
const givenStatement = (stepType) => (statement) => {
|
|
175
|
-
let normalizedStatement;
|
|
176
|
-
if (isString(statement)) normalizedStatement = (() => statement);
|
|
177
|
-
else normalizedStatement = statement;
|
|
136
|
+
const GIVEN = "given";
|
|
137
|
+
const givenDependencies = (statement, parsers) => (dependencies) => ({ step: addStep(statement, GIVEN, {
|
|
138
|
+
...dependencies,
|
|
139
|
+
when: {},
|
|
140
|
+
then: {}
|
|
141
|
+
}, parsers) });
|
|
142
|
+
const givenParsers = (statement) => (parsers) => ({
|
|
143
|
+
dependencies: givenDependencies(statement, parsers),
|
|
144
|
+
step: addStep(statement, GIVEN, void 0, parsers)
|
|
145
|
+
});
|
|
146
|
+
const givenStatement = () => (statement) => {
|
|
147
|
+
const normalizedStatement = isString(statement) ? () => statement : statement;
|
|
178
148
|
return {
|
|
179
|
-
dependencies: givenDependencies(normalizedStatement
|
|
180
|
-
parsers: givenParsers(normalizedStatement
|
|
181
|
-
step: addStep(normalizedStatement,
|
|
149
|
+
dependencies: givenDependencies(normalizedStatement),
|
|
150
|
+
parsers: givenParsers(normalizedStatement),
|
|
151
|
+
step: addStep(normalizedStatement, GIVEN)
|
|
182
152
|
};
|
|
183
153
|
};
|
|
184
|
-
const givenBuilder = () => {
|
|
185
|
-
return { statement: givenStatement("given") };
|
|
186
|
-
};
|
|
154
|
+
const givenBuilder = () => ({ statement: givenStatement() });
|
|
187
155
|
//#endregion
|
|
188
156
|
//#region src/when.ts
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
};
|
|
202
|
-
const whenStatement = (stepType) => (statement) => {
|
|
203
|
-
let normalizedStatement;
|
|
204
|
-
if (isString(statement)) normalizedStatement = (() => statement);
|
|
205
|
-
else normalizedStatement = statement;
|
|
157
|
+
const WHEN = "when";
|
|
158
|
+
const whenDependencies = (statement, parsers) => (dependencies) => ({ step: addStep(statement, WHEN, {
|
|
159
|
+
given: dependencies.given ?? {},
|
|
160
|
+
when: dependencies.when ?? {},
|
|
161
|
+
then: {}
|
|
162
|
+
}, parsers) });
|
|
163
|
+
const whenParsers = (statement) => (parsers) => ({
|
|
164
|
+
dependencies: whenDependencies(statement, parsers),
|
|
165
|
+
step: addStep(statement, WHEN, void 0, parsers)
|
|
166
|
+
});
|
|
167
|
+
const whenStatement = () => (statement) => {
|
|
168
|
+
const normalizedStatement = isString(statement) ? () => statement : statement;
|
|
206
169
|
return {
|
|
207
|
-
dependencies: whenDependencies(normalizedStatement
|
|
208
|
-
parsers: whenParsers(normalizedStatement
|
|
209
|
-
step: addStep(normalizedStatement,
|
|
170
|
+
dependencies: whenDependencies(normalizedStatement),
|
|
171
|
+
parsers: whenParsers(normalizedStatement),
|
|
172
|
+
step: addStep(normalizedStatement, WHEN)
|
|
210
173
|
};
|
|
211
174
|
};
|
|
212
|
-
const whenBuilder = () => {
|
|
213
|
-
return { statement: whenStatement("when") };
|
|
214
|
-
};
|
|
175
|
+
const whenBuilder = () => ({ statement: whenStatement() });
|
|
215
176
|
//#endregion
|
|
216
177
|
//#region src/then.ts
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
178
|
+
const THEN = "then";
|
|
179
|
+
const thenDependencies = (statement, parsers) => (dependencies) => ({ step: addStep(statement, THEN, {
|
|
180
|
+
given: dependencies.given ?? {},
|
|
181
|
+
when: dependencies.when ?? {},
|
|
182
|
+
then: dependencies.then ?? {}
|
|
183
|
+
}, parsers) });
|
|
184
|
+
const thenParsers = (statement) => (parsers) => ({
|
|
185
|
+
dependencies: thenDependencies(statement, parsers),
|
|
186
|
+
step: addStep(statement, THEN, void 0, parsers)
|
|
187
|
+
});
|
|
188
|
+
const thenStatement = () => (statement) => {
|
|
189
|
+
const normalizedStatement = isString(statement) ? () => statement : statement;
|
|
225
190
|
return {
|
|
226
|
-
dependencies: thenDependencies(
|
|
227
|
-
|
|
191
|
+
dependencies: thenDependencies(normalizedStatement),
|
|
192
|
+
parsers: thenParsers(normalizedStatement),
|
|
193
|
+
step: addStep(normalizedStatement, THEN)
|
|
228
194
|
};
|
|
229
195
|
};
|
|
230
|
-
const
|
|
231
|
-
let normalizedStatement;
|
|
232
|
-
if (isString(statement)) normalizedStatement = (() => statement);
|
|
233
|
-
else normalizedStatement = statement;
|
|
234
|
-
return {
|
|
235
|
-
dependencies: thenDependencies(normalizedStatement, stepType),
|
|
236
|
-
parsers: thenParsers(normalizedStatement, stepType),
|
|
237
|
-
step: addStep(normalizedStatement, stepType)
|
|
238
|
-
};
|
|
239
|
-
};
|
|
240
|
-
const thenBuilder = () => {
|
|
241
|
-
return { statement: thenStatement("then") };
|
|
242
|
-
};
|
|
243
|
-
//#endregion
|
|
244
|
-
//#region src/world.ts
|
|
245
|
-
function mergeCustomizer(objValue, srcValue) {
|
|
246
|
-
if (lodash.default.isArray(objValue)) return objValue.concat(srcValue);
|
|
247
|
-
else if (objValue && !lodash.default.isPlainObject(objValue) && objValue !== srcValue) throw new Error(`Merge would have destroyed previous value ${objValue} with ${srcValue}`);
|
|
248
|
-
return objValue;
|
|
249
|
-
}
|
|
250
|
-
var BasicWorld = class {
|
|
251
|
-
givenState = {};
|
|
252
|
-
whenState = {};
|
|
253
|
-
thenState = {};
|
|
254
|
-
get given() {
|
|
255
|
-
return {
|
|
256
|
-
...this.givenState,
|
|
257
|
-
merge: (newState) => {
|
|
258
|
-
this.givenState = lodash.default.merge({ ...this.givenState }, newState, mergeCustomizer);
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
get when() {
|
|
263
|
-
return {
|
|
264
|
-
...this.whenState,
|
|
265
|
-
merge: (newState) => {
|
|
266
|
-
this.whenState = lodash.default.merge({ ...this.whenState }, newState, mergeCustomizer);
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
get then() {
|
|
271
|
-
return {
|
|
272
|
-
...this.thenState,
|
|
273
|
-
merge: (newState) => {
|
|
274
|
-
this.thenState = lodash.default.merge({ ...this.thenState }, newState, mergeCustomizer);
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
};
|
|
196
|
+
const thenBuilder = () => ({ statement: thenStatement() });
|
|
279
197
|
//#endregion
|
|
280
198
|
//#region src/analyzer/stepExtractor.ts
|
|
281
199
|
const BUILDER_NAMES = {
|
|
@@ -311,7 +229,7 @@ function extractStepDefinitions(filePaths, tsConfigPath) {
|
|
|
311
229
|
function extractFromSourceFile(sourceFile, checker) {
|
|
312
230
|
const results = [];
|
|
313
231
|
function visit(node) {
|
|
314
|
-
if (typescript.default.isCallExpression(node) && typescript.default.isPropertyAccessExpression(node.expression) && node.expression.name.text === "
|
|
232
|
+
if (typescript.default.isCallExpression(node) && typescript.default.isPropertyAccessExpression(node.expression) && node.expression.name.text === "step") {
|
|
315
233
|
const meta = extractFromRegisterCall(node, sourceFile, checker);
|
|
316
234
|
if (meta) results.push(meta);
|
|
317
235
|
}
|
|
@@ -494,105 +412,6 @@ function resolveReExportedCall(chain, checker) {
|
|
|
494
412
|
return null;
|
|
495
413
|
}
|
|
496
414
|
//#endregion
|
|
497
|
-
//#region src/analyzer/gherkinParser.ts
|
|
498
|
-
function parseFeatureFiles(filePaths) {
|
|
499
|
-
const scenarios = [];
|
|
500
|
-
for (const filePath of filePaths) {
|
|
501
|
-
const parsed = parseFeatureContent(node_fs.readFileSync(filePath, "utf-8"), filePath);
|
|
502
|
-
scenarios.push(...parsed);
|
|
503
|
-
}
|
|
504
|
-
return scenarios;
|
|
505
|
-
}
|
|
506
|
-
function parseFeatureContent(content, filePath) {
|
|
507
|
-
const feature = new _cucumber_gherkin.Parser(new _cucumber_gherkin.AstBuilder(_cucumber_messages.IdGenerator.uuid()), new _cucumber_gherkin.GherkinClassicTokenMatcher()).parse(content).feature;
|
|
508
|
-
if (!feature) return [];
|
|
509
|
-
const featureBackground = [];
|
|
510
|
-
const scenarios = [];
|
|
511
|
-
for (const child of feature.children) {
|
|
512
|
-
if (child.background) featureBackground.push(...child.background.steps);
|
|
513
|
-
if (child.scenario) scenarios.push(...expandScenario(child.scenario, featureBackground, filePath));
|
|
514
|
-
if (child.rule) {
|
|
515
|
-
const ruleBackground = [...featureBackground];
|
|
516
|
-
for (const ruleChild of child.rule.children) {
|
|
517
|
-
if (ruleChild.background) ruleBackground.push(...ruleChild.background.steps);
|
|
518
|
-
if (ruleChild.scenario) scenarios.push(...expandScenario(ruleChild.scenario, ruleBackground, filePath));
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
return scenarios;
|
|
523
|
-
}
|
|
524
|
-
function expandScenario(scenario, backgroundSteps, filePath) {
|
|
525
|
-
if (!(scenario.examples.length > 0 && scenario.examples.some((e) => e.tableBody.length > 0))) {
|
|
526
|
-
const bgParsed = convertSteps(backgroundSteps);
|
|
527
|
-
const scenarioParsed = convertSteps(scenario.steps);
|
|
528
|
-
const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioParsed]);
|
|
529
|
-
return [{
|
|
530
|
-
name: scenario.name,
|
|
531
|
-
file: filePath,
|
|
532
|
-
steps: allSteps
|
|
533
|
-
}];
|
|
534
|
-
}
|
|
535
|
-
const results = [];
|
|
536
|
-
for (const example of scenario.examples) {
|
|
537
|
-
if (!example.tableHeader || example.tableBody.length === 0) continue;
|
|
538
|
-
const headers = example.tableHeader.cells.map((c) => c.value);
|
|
539
|
-
for (const row of example.tableBody) {
|
|
540
|
-
const values = row.cells.map((c) => c.value);
|
|
541
|
-
const substitution = {};
|
|
542
|
-
headers.forEach((h, i) => {
|
|
543
|
-
substitution[h] = values[i];
|
|
544
|
-
});
|
|
545
|
-
const bgParsed = convertSteps(backgroundSteps);
|
|
546
|
-
const scenarioSteps = convertSteps(scenario.steps).map((step) => ({
|
|
547
|
-
...step,
|
|
548
|
-
text: substituteExampleValues(step.text, substitution)
|
|
549
|
-
}));
|
|
550
|
-
const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioSteps]);
|
|
551
|
-
results.push({
|
|
552
|
-
name: `${scenario.name} (${values.join(", ")})`,
|
|
553
|
-
file: filePath,
|
|
554
|
-
steps: allSteps
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
return results;
|
|
559
|
-
}
|
|
560
|
-
function convertSteps(steps) {
|
|
561
|
-
return steps.map((step) => ({
|
|
562
|
-
keyword: normalizeKeyword(step.keyword),
|
|
563
|
-
text: step.text,
|
|
564
|
-
line: step.location.line,
|
|
565
|
-
column: (step.location.column ?? 1) + step.keyword.length
|
|
566
|
-
}));
|
|
567
|
-
}
|
|
568
|
-
function normalizeKeyword(keyword) {
|
|
569
|
-
const trimmed = keyword.trim();
|
|
570
|
-
if (trimmed === "Given") return "Given";
|
|
571
|
-
if (trimmed === "When") return "When";
|
|
572
|
-
if (trimmed === "Then") return "Then";
|
|
573
|
-
if (trimmed === "And") return "And";
|
|
574
|
-
if (trimmed === "But") return "But";
|
|
575
|
-
return "Given";
|
|
576
|
-
}
|
|
577
|
-
function resolveEffectiveKeywords(steps) {
|
|
578
|
-
let lastEffective = "Given";
|
|
579
|
-
return steps.map((step) => {
|
|
580
|
-
let effectiveKeyword;
|
|
581
|
-
if (step.keyword === "And" || step.keyword === "But") effectiveKeyword = lastEffective;
|
|
582
|
-
else effectiveKeyword = step.keyword;
|
|
583
|
-
lastEffective = effectiveKeyword;
|
|
584
|
-
return {
|
|
585
|
-
...step,
|
|
586
|
-
effectiveKeyword
|
|
587
|
-
};
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
function substituteExampleValues(text, substitution) {
|
|
591
|
-
let result = text;
|
|
592
|
-
for (const [key, value] of Object.entries(substitution)) result = result.replace(new RegExp(`<${key}>`, "g"), value);
|
|
593
|
-
return result;
|
|
594
|
-
}
|
|
595
|
-
//#endregion
|
|
596
415
|
//#region src/analyzer/stepMatcher.ts
|
|
597
416
|
function compileDefinitions(definitions) {
|
|
598
417
|
const compiled = [];
|
|
@@ -727,12 +546,12 @@ function runRules(rules, scenario, matchedSteps) {
|
|
|
727
546
|
//#region src/analyzer/index.ts
|
|
728
547
|
async function analyze(config, options) {
|
|
729
548
|
const rules = options?.rules ?? defaultRules;
|
|
730
|
-
const stepFilePaths = await
|
|
731
|
-
const featureFilePaths = await
|
|
549
|
+
const stepFilePaths = await require_gherkinParser.globFiles(config.stepFiles);
|
|
550
|
+
const featureFilePaths = await require_gherkinParser.globFiles(config.featureFiles);
|
|
732
551
|
if (stepFilePaths.length === 0) return [];
|
|
733
552
|
if (featureFilePaths.length === 0) return [];
|
|
734
553
|
const stepDefinitions = extractStepDefinitions(stepFilePaths, config.tsConfigPath);
|
|
735
|
-
const scenarios = parseFeatureFiles(featureFilePaths);
|
|
554
|
+
const scenarios = require_gherkinParser.parseFeatureFiles(featureFilePaths);
|
|
736
555
|
const diagnostics = [];
|
|
737
556
|
for (const scenario of scenarios) {
|
|
738
557
|
const scenarioDiags = runRules(rules, scenario, matchScenarioSteps(scenario, stepDefinitions));
|
|
@@ -740,11 +559,6 @@ async function analyze(config, options) {
|
|
|
740
559
|
}
|
|
741
560
|
return diagnostics;
|
|
742
561
|
}
|
|
743
|
-
async function resolveGlobs(patterns) {
|
|
744
|
-
const files = [];
|
|
745
|
-
for (const pattern of patterns) for await (const file of (0, node_fs_promises.glob)(pattern)) files.push(node_path.resolve(file));
|
|
746
|
-
return [...new Set(files)];
|
|
747
|
-
}
|
|
748
562
|
//#endregion
|
|
749
563
|
//#region src/init.ts
|
|
750
564
|
const createBuilders = () => {
|
|
@@ -755,19 +569,84 @@ const createBuilders = () => {
|
|
|
755
569
|
};
|
|
756
570
|
};
|
|
757
571
|
//#endregion
|
|
572
|
+
//#region src/hooks.ts
|
|
573
|
+
/**
|
|
574
|
+
* Run before every scenario, with access to that scenario's fresh world. For
|
|
575
|
+
* side effects only (reset a mock, seed an external system) — return values are
|
|
576
|
+
* ignored; seed test *state* with given steps so the dependency graph stays the
|
|
577
|
+
* single source of truth.
|
|
578
|
+
*/
|
|
579
|
+
function beforeScenario(fn) {
|
|
580
|
+
require_hooks.globalHookRegistry.add({
|
|
581
|
+
scope: "scenario",
|
|
582
|
+
timing: "before",
|
|
583
|
+
fn
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Run after every scenario (even when a step failed), with access to that
|
|
588
|
+
* scenario's world. Runs in reverse registration order so teardown unwinds
|
|
589
|
+
* setup. For cleanup side effects only.
|
|
590
|
+
*/
|
|
591
|
+
function afterScenario(fn) {
|
|
592
|
+
require_hooks.globalHookRegistry.add({
|
|
593
|
+
scope: "scenario",
|
|
594
|
+
timing: "after",
|
|
595
|
+
fn
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
/** Run once at the start of each feature file. No world exists at this boundary. */
|
|
599
|
+
function beforeFeature(fn) {
|
|
600
|
+
require_hooks.globalHookRegistry.add({
|
|
601
|
+
scope: "feature",
|
|
602
|
+
timing: "before",
|
|
603
|
+
fn
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
/** Run once at the end of each feature file (reverse registration order). */
|
|
607
|
+
function afterFeature(fn) {
|
|
608
|
+
require_hooks.globalHookRegistry.add({
|
|
609
|
+
scope: "feature",
|
|
610
|
+
timing: "after",
|
|
611
|
+
fn
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
/** Run once before the entire test run, across all feature files. */
|
|
615
|
+
function beforeAll(fn) {
|
|
616
|
+
require_hooks.globalHookRegistry.add({
|
|
617
|
+
scope: "global",
|
|
618
|
+
timing: "before",
|
|
619
|
+
fn
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
/** Run once after the entire test run (reverse registration order). */
|
|
623
|
+
function afterAll(fn) {
|
|
624
|
+
require_hooks.globalHookRegistry.add({
|
|
625
|
+
scope: "global",
|
|
626
|
+
timing: "after",
|
|
627
|
+
fn
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
//#endregion
|
|
758
631
|
//#region src/index.ts
|
|
759
632
|
const analyzer = {
|
|
760
633
|
analyze,
|
|
761
634
|
extractStepDefinitions,
|
|
762
|
-
parseFeatureFiles,
|
|
763
|
-
parseFeatureContent,
|
|
635
|
+
parseFeatureFiles: require_gherkinParser.parseFeatureFiles,
|
|
636
|
+
parseFeatureContent: require_gherkinParser.parseFeatureContent,
|
|
764
637
|
matchScenarioSteps,
|
|
765
638
|
findMatchingDefinitions,
|
|
766
639
|
defaultRules
|
|
767
640
|
};
|
|
768
641
|
//#endregion
|
|
769
|
-
exports.BasicWorld = BasicWorld;
|
|
642
|
+
exports.BasicWorld = require_gherkinParser.BasicWorld;
|
|
643
|
+
exports.afterAll = afterAll;
|
|
644
|
+
exports.afterFeature = afterFeature;
|
|
645
|
+
exports.afterScenario = afterScenario;
|
|
770
646
|
exports.analyzer = analyzer;
|
|
647
|
+
exports.beforeAll = beforeAll;
|
|
648
|
+
exports.beforeFeature = beforeFeature;
|
|
649
|
+
exports.beforeScenario = beforeScenario;
|
|
771
650
|
exports.booleanParser = booleanParser;
|
|
772
651
|
exports.createBuilders = createBuilders;
|
|
773
652
|
exports.givenBuilder = givenBuilder;
|