@systemfsoftware/storybook-gherkin 3.0.4 → 3.0.6
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/CHANGELOG.md +12 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.mjs +238 -106
- package/package.json +24 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @systemfsoftware/storybook-gherkin
|
|
2
2
|
|
|
3
|
+
## 3.0.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Bump the stryker catalog to pull in the latest patch releases of the stryker-js ecosystem.
|
|
8
|
+
|
|
9
|
+
## 3.0.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Dependencies resolve to Effect 4.0.0-rc.116 and Vitest 5.
|
|
14
|
+
|
|
3
15
|
## 3.0.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { screen as screen_2 } from 'storybook/test';
|
|
|
5
5
|
import { Simplify } from 'type-fest';
|
|
6
6
|
import { UnionToIntersection } from 'type-fest';
|
|
7
7
|
import { UserEventObject } from 'storybook/test';
|
|
8
|
-
import { within } from 'storybook/test';
|
|
9
8
|
import { YieldableError } from 'effect/Cause';
|
|
10
9
|
|
|
11
10
|
export declare const And: StepCtor;
|
|
@@ -19,7 +18,7 @@ declare const BackgroundNotGiven_base: Schema.Class<BackgroundNotGiven, Schema.T
|
|
|
19
18
|
|
|
20
19
|
export declare const But: StepCtor;
|
|
21
20
|
|
|
22
|
-
export declare type Canvas =
|
|
21
|
+
export declare type Canvas = typeof screen_2;
|
|
23
22
|
|
|
24
23
|
export declare type CapsOf<THoles extends readonly Hole[]> = THoles extends readonly [] ? {} : Simplify<UnionToIntersection<{ [K in keyof THoles]: THoles[K] extends Capture<infer N, infer A> ? { [P in N]: A; } : {}; }[number]>>;
|
|
25
24
|
|
package/dist/index.mjs
CHANGED
|
@@ -3,11 +3,12 @@ import { screen } from "storybook/test";
|
|
|
3
3
|
//#region src/Capture.ts
|
|
4
4
|
const CaptureTag = { _tag: "Capture" };
|
|
5
5
|
function capture(name, options) {
|
|
6
|
+
const resolved = options ?? {};
|
|
6
7
|
return {
|
|
7
8
|
...CaptureTag,
|
|
8
9
|
name,
|
|
9
|
-
schema:
|
|
10
|
-
default:
|
|
10
|
+
schema: resolved.schema,
|
|
11
|
+
default: resolved.default
|
|
11
12
|
};
|
|
12
13
|
}
|
|
13
14
|
//#endregion
|
|
@@ -67,9 +68,22 @@ var CaptureDecodeFailed = class extends Schema.TaggedError()("CaptureDecodeFaile
|
|
|
67
68
|
}) {};
|
|
68
69
|
//#endregion
|
|
69
70
|
//#region src/Steps.ts
|
|
70
|
-
const
|
|
71
|
+
const emptyIfMissing = (part) => {
|
|
72
|
+
if (part === void 0) return "";
|
|
73
|
+
return part;
|
|
74
|
+
};
|
|
75
|
+
const joinStep = (step, renderHole) => [emptyIfMissing(step.parts[0]), ...step.captures.flatMap((cap, i) => [renderHole(cap), emptyIfMissing(step.parts[i + 1])])].join("");
|
|
71
76
|
const displayPattern = (step) => joinStep(step, (cap) => `{${cap.name}}`);
|
|
72
|
-
const
|
|
77
|
+
const firstString = (left, right) => {
|
|
78
|
+
if (left !== void 0) return left;
|
|
79
|
+
return right;
|
|
80
|
+
};
|
|
81
|
+
const holeText = (cap, values) => {
|
|
82
|
+
const fromValues = values[cap.name];
|
|
83
|
+
if (fromValues !== void 0) return fromValues;
|
|
84
|
+
return firstString(cap.default, `{${cap.name}}`);
|
|
85
|
+
};
|
|
86
|
+
const renderStepText = (step, values) => joinStep(step, (cap) => holeText(cap, values));
|
|
73
87
|
const resolveKeyword = (keyword, previous) => Match.value(keyword).pipe(Match.when("Given", () => "Given"), Match.when("When", () => "When"), Match.when("Then", () => "Then"), Match.when("And", () => previous), Match.when("But", () => previous), Match.when("Star", () => previous), Match.exhaustive);
|
|
74
88
|
const resolveKeywords = (steps) => {
|
|
75
89
|
return Array$1.mapAccum(steps, "Given", (previous, s) => {
|
|
@@ -82,44 +96,63 @@ const resolveKeywords = (steps) => {
|
|
|
82
96
|
};
|
|
83
97
|
const STEP_TAG = "Step";
|
|
84
98
|
const StepTag = { _tag: STEP_TAG };
|
|
99
|
+
const partAt = (statics, index) => emptyIfMissing(statics[index]);
|
|
100
|
+
const appendLiteral = (current, literal, trailing) => current + literal + trailing;
|
|
101
|
+
const consumeNonStringHole = (current, hole, trailing, parts, captures) => {
|
|
102
|
+
if (typeof hole === "number") return appendLiteral(current, String(hole), trailing);
|
|
103
|
+
parts.push(current);
|
|
104
|
+
captures.push({
|
|
105
|
+
name: hole.name,
|
|
106
|
+
schema: hole.schema,
|
|
107
|
+
default: hole.default
|
|
108
|
+
});
|
|
109
|
+
return trailing;
|
|
110
|
+
};
|
|
111
|
+
const consumeHole = (current, hole, trailing, parts, captures) => {
|
|
112
|
+
if (typeof hole === "string") return appendLiteral(current, hole, trailing);
|
|
113
|
+
return consumeNonStringHole(current, hole, trailing, parts, captures);
|
|
114
|
+
};
|
|
115
|
+
const rememberCapture = (model, seen, cap) => {
|
|
116
|
+
if (seen.has(cap.name)) throw DuplicateCapture.make({
|
|
117
|
+
step: displayPattern(model),
|
|
118
|
+
name: cap.name
|
|
119
|
+
});
|
|
120
|
+
seen.add(cap.name);
|
|
121
|
+
};
|
|
122
|
+
const assertUniqueCaptures = (model, captures) => {
|
|
123
|
+
const seen = /* @__PURE__ */ new Set();
|
|
124
|
+
for (const cap of captures) rememberCapture(model, seen, cap);
|
|
125
|
+
};
|
|
85
126
|
const buildModel = (keyword, statics, holes) => {
|
|
86
127
|
const parts = [];
|
|
87
128
|
const captures = [];
|
|
88
|
-
let current = statics
|
|
89
|
-
for (const [i, hole] of holes.entries())
|
|
90
|
-
else if (typeof hole === "number") current += String(hole) + (statics[i + 1] ?? "");
|
|
91
|
-
else {
|
|
92
|
-
parts.push(current);
|
|
93
|
-
current = statics[i + 1] ?? "";
|
|
94
|
-
captures.push({
|
|
95
|
-
name: hole.name,
|
|
96
|
-
schema: hole.schema,
|
|
97
|
-
default: hole.default
|
|
98
|
-
});
|
|
99
|
-
}
|
|
129
|
+
let current = partAt(statics, 0);
|
|
130
|
+
for (const [i, hole] of holes.entries()) current = consumeHole(current, hole, partAt(statics, i + 1), parts, captures);
|
|
100
131
|
parts.push(current);
|
|
101
132
|
const model = {
|
|
102
133
|
keyword,
|
|
103
134
|
parts,
|
|
104
135
|
captures
|
|
105
136
|
};
|
|
106
|
-
|
|
107
|
-
for (const cap of captures) {
|
|
108
|
-
if (seen.has(cap.name)) throw DuplicateCapture.make({
|
|
109
|
-
step: displayPattern(model),
|
|
110
|
-
name: cap.name
|
|
111
|
-
});
|
|
112
|
-
seen.add(cap.name);
|
|
113
|
-
}
|
|
137
|
+
assertUniqueCaptures(model, captures);
|
|
114
138
|
return model;
|
|
115
139
|
};
|
|
140
|
+
const captureRaw = (cap, values) => {
|
|
141
|
+
const fromValues = values[cap.name];
|
|
142
|
+
if (fromValues !== void 0) return fromValues;
|
|
143
|
+
return cap.default;
|
|
144
|
+
};
|
|
145
|
+
const rawOrEmpty = (raw) => {
|
|
146
|
+
if (raw === void 0) return "";
|
|
147
|
+
return raw;
|
|
148
|
+
};
|
|
116
149
|
const decodeCapture = (cap, values, model) => {
|
|
117
|
-
const raw =
|
|
150
|
+
const raw = captureRaw(cap, values);
|
|
118
151
|
if (cap.schema === void 0) return Effect.succeed(raw);
|
|
119
152
|
return Schema.decodeEffect(cap.schema)(raw).pipe(Effect.mapError((error) => CaptureDecodeFailed.make({
|
|
120
153
|
step: displayPattern(model),
|
|
121
154
|
capture: cap.name,
|
|
122
|
-
value: raw
|
|
155
|
+
value: rawOrEmpty(raw),
|
|
123
156
|
cause: error
|
|
124
157
|
})));
|
|
125
158
|
};
|
|
@@ -146,13 +179,28 @@ const Then = makeStepCtor("Then");
|
|
|
146
179
|
const And = makeStepCtor("And");
|
|
147
180
|
const But = makeStepCtor("But");
|
|
148
181
|
const Star = makeStepCtor("Star");
|
|
182
|
+
const isNonNullObject = (value) => {
|
|
183
|
+
if (typeof value !== "object") return false;
|
|
184
|
+
return value !== null;
|
|
185
|
+
};
|
|
186
|
+
const hasModelAndRun = (value) => {
|
|
187
|
+
if (!("model" in value)) return false;
|
|
188
|
+
return "run" in value;
|
|
189
|
+
};
|
|
190
|
+
const hasStepFields = (value) => {
|
|
191
|
+
if (Reflect.get(value, "_tag") !== STEP_TAG) return false;
|
|
192
|
+
return hasModelAndRun(value);
|
|
193
|
+
};
|
|
149
194
|
const isStep = (value) => {
|
|
150
|
-
if (
|
|
151
|
-
return
|
|
195
|
+
if (!isNonNullObject(value)) return false;
|
|
196
|
+
return hasStepFields(value);
|
|
152
197
|
};
|
|
153
198
|
//#endregion
|
|
154
199
|
//#region src/Feature.ts
|
|
155
|
-
const displayKeyword = (model) =>
|
|
200
|
+
const displayKeyword = (model) => {
|
|
201
|
+
if (model.keyword === "Star") return "*";
|
|
202
|
+
return model.keyword;
|
|
203
|
+
};
|
|
156
204
|
const buildStepContext = (ctx) => ({
|
|
157
205
|
canvas: ctx.canvas,
|
|
158
206
|
screen,
|
|
@@ -167,12 +215,6 @@ const buildStepContext = (ctx) => ({
|
|
|
167
215
|
reporting: ctx.reporting,
|
|
168
216
|
context: ctx
|
|
169
217
|
});
|
|
170
|
-
/**
|
|
171
|
-
* Total classification of an interpreted program's `Exit`, shared by the play
|
|
172
|
-
* edge and the step bridge: interruption resolves silently, success returns
|
|
173
|
-
* the value, and any other cause is rethrown as the original error instance
|
|
174
|
-
* (`Cause.squash`) so Storybook's panel keeps the matcher diff.
|
|
175
|
-
*/
|
|
176
218
|
const squashExit = (exit) => Exit.match(exit, {
|
|
177
219
|
onSuccess: (value) => value,
|
|
178
220
|
onFailure: (cause) => {
|
|
@@ -180,17 +222,6 @@ const squashExit = (exit) => Exit.match(exit, {
|
|
|
180
222
|
throw Cause.squash(cause);
|
|
181
223
|
}
|
|
182
224
|
});
|
|
183
|
-
/**
|
|
184
|
-
* One scenario step, composed as an Effect. Storybook's instrumented `step`
|
|
185
|
-
* expects a promise whose settlement tracks the step's work, so the body runs
|
|
186
|
-
* in a child fiber that settles a deferred; the bridge promise given to
|
|
187
|
-
* `stepCtx.step` awaits that deferred and rejects with the step's original error.
|
|
188
|
-
* The bridge interprets only this pure signalling effect; user code runs in
|
|
189
|
-
* the single play-edge interpretation. The `ensuring` finalizer interrupts
|
|
190
|
-
* the child on every parent exit — success (no-op on a joined fiber),
|
|
191
|
-
* failure, and interruption — so an independently failed `stepCtx.step` never
|
|
192
|
-
* orphans a running step body.
|
|
193
|
-
*/
|
|
194
225
|
const runStep = (step, values, stepCtx) => {
|
|
195
226
|
const label = `${displayKeyword(step.model)} ${renderStepText(step.model, values)}`;
|
|
196
227
|
return Deferred.make().pipe(Effect.flatMap((done) => {
|
|
@@ -202,7 +233,6 @@ const executeSteps = (ordered, values, ctx) => {
|
|
|
202
233
|
const stepCtx = buildStepContext(ctx);
|
|
203
234
|
return Effect.forEach(ordered, (s) => runStep(s, values, stepCtx), { discard: true });
|
|
204
235
|
};
|
|
205
|
-
/** The single interpretation edge of the package. */
|
|
206
236
|
const interpretPlay = (context, program, ctx) => Effect.runPromiseExitWith(context)(program, { signal: ctx.abortSignal }).then(squashExit);
|
|
207
237
|
const rowValuesFor = (row) => Object.fromEntries(Object.entries(row).filter(([k]) => k !== "name"));
|
|
208
238
|
const sortKeys = (keys) => {
|
|
@@ -210,41 +240,95 @@ const sortKeys = (keys) => {
|
|
|
210
240
|
sorted.sort();
|
|
211
241
|
return sorted;
|
|
212
242
|
};
|
|
213
|
-
const
|
|
243
|
+
const assertNonEmptyScenario = (fullName, models) => {
|
|
214
244
|
if (models.length === 0) throw EmptyScenario.make({ scenario: fullName });
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
245
|
+
};
|
|
246
|
+
const isThen = (r) => r.resolved === "Then";
|
|
247
|
+
const assertHasThen = (fullName, models) => {
|
|
248
|
+
if (!resolveKeywords(models).some(isThen)) throw MissingThen.make({ scenario: fullName });
|
|
249
|
+
};
|
|
250
|
+
const captureIsBound = (cap, withRecord) => {
|
|
251
|
+
if (cap.default !== void 0) return true;
|
|
252
|
+
return Object.prototype.hasOwnProperty.call(withRecord, cap.name);
|
|
253
|
+
};
|
|
254
|
+
const assertCaptureBound = (fullName, stepModel, cap, withRecord) => {
|
|
255
|
+
if (captureIsBound(cap, withRecord)) return;
|
|
256
|
+
throw UnresolvedCapture.make({
|
|
257
|
+
scenario: fullName,
|
|
258
|
+
step: displayPattern(stepModel),
|
|
259
|
+
capture: cap.name
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
const assertCapturesBound = (fullName, stepModel, withRecord) => {
|
|
263
|
+
for (const cap of stepModel.captures) assertCaptureBound(fullName, stepModel, cap, withRecord);
|
|
264
|
+
};
|
|
265
|
+
const validateScenarioSteps = (fullName, models, withRecord) => {
|
|
266
|
+
assertNonEmptyScenario(fullName, models);
|
|
267
|
+
assertHasThen(fullName, models);
|
|
268
|
+
for (const stepModel of models) assertCapturesBound(fullName, stepModel, withRecord);
|
|
225
269
|
};
|
|
226
270
|
const isScenarioOptions = (value) => !isStep(value) && !Array.isArray(value);
|
|
227
|
-
const
|
|
271
|
+
const optionsIfPresent = (firstArg) => {
|
|
272
|
+
if (!isScenarioOptions(firstArg)) return void 0;
|
|
273
|
+
return firstArg;
|
|
274
|
+
};
|
|
275
|
+
const readOptions = (rest) => {
|
|
228
276
|
const firstArg = rest[0];
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
277
|
+
if (firstArg === void 0) return void 0;
|
|
278
|
+
return optionsIfPresent(firstArg);
|
|
279
|
+
};
|
|
280
|
+
const bodyAfterOptions = (rest, options) => {
|
|
281
|
+
if (options === void 0) return rest;
|
|
282
|
+
return rest.slice(1);
|
|
283
|
+
};
|
|
284
|
+
const pushInnerStep = (steps, inner) => {
|
|
285
|
+
if (!isStep(inner)) throw new TypeError(`Steps group contains a non-step value of type ${typeof inner}`);
|
|
286
|
+
steps.push(inner);
|
|
287
|
+
};
|
|
288
|
+
const pushStepGroup = (steps, item) => {
|
|
289
|
+
for (const inner of item) pushInnerStep(steps, inner);
|
|
290
|
+
};
|
|
291
|
+
const pushIfGroup = (steps, item) => {
|
|
292
|
+
if (Array.isArray(item)) {
|
|
293
|
+
pushStepGroup(steps, item);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
throw new TypeError(`Scenario arguments must be steps or step groups; got type ${typeof item}`);
|
|
297
|
+
};
|
|
298
|
+
const pushScenarioItem = (steps, item) => {
|
|
299
|
+
if (isStep(item)) {
|
|
300
|
+
steps.push(item);
|
|
301
|
+
return;
|
|
236
302
|
}
|
|
237
|
-
|
|
303
|
+
pushIfGroup(steps, item);
|
|
304
|
+
};
|
|
305
|
+
const parseScenarioArgs = (rest) => {
|
|
306
|
+
const options = readOptions(rest);
|
|
307
|
+
const body = bodyAfterOptions(rest, options);
|
|
308
|
+
const steps = [];
|
|
309
|
+
for (const item of body) pushScenarioItem(steps, item);
|
|
238
310
|
return {
|
|
239
311
|
options,
|
|
240
312
|
steps
|
|
241
313
|
};
|
|
242
314
|
};
|
|
315
|
+
const qualifyName = (prefix, name) => {
|
|
316
|
+
if (prefix === "") return name;
|
|
317
|
+
return `${prefix}: ${name}`;
|
|
318
|
+
};
|
|
319
|
+
const recordOrEmpty = (value) => {
|
|
320
|
+
if (value === void 0) return {};
|
|
321
|
+
return value;
|
|
322
|
+
};
|
|
323
|
+
const withRecordOf = (options) => {
|
|
324
|
+
if (options === void 0) return {};
|
|
325
|
+
return recordOrEmpty(options.with);
|
|
326
|
+
};
|
|
243
327
|
const makeScenario = (background, prefix, context) => {
|
|
244
328
|
function scenario(name, ...rest) {
|
|
245
329
|
const { options, steps } = parseScenarioArgs(rest);
|
|
246
|
-
const fullName = prefix
|
|
247
|
-
const withRecord = options
|
|
330
|
+
const fullName = qualifyName(prefix, name);
|
|
331
|
+
const withRecord = withRecordOf(options);
|
|
248
332
|
validateScenarioSteps(fullName, steps.map((s) => s.model), withRecord);
|
|
249
333
|
return {
|
|
250
334
|
name: fullName,
|
|
@@ -253,40 +337,77 @@ const makeScenario = (background, prefix, context) => {
|
|
|
253
337
|
}
|
|
254
338
|
return scenario;
|
|
255
339
|
};
|
|
256
|
-
const
|
|
340
|
+
const assertOutlineNonEmpty = (rows, fullName) => {
|
|
257
341
|
if (rows.length === 0) throw OutlineEmpty.make({ outline: fullName });
|
|
342
|
+
};
|
|
343
|
+
const isNotName = (k) => k !== "name";
|
|
344
|
+
const keysExceptName = (row) => sortKeys(Object.keys(row).filter(isNotName));
|
|
345
|
+
const firstRowKeys = (rows) => {
|
|
346
|
+
const first = rows[0];
|
|
347
|
+
if (first === void 0) return [];
|
|
348
|
+
return keysExceptName(first);
|
|
349
|
+
};
|
|
350
|
+
const rememberRowName = (seen, row, fullName) => {
|
|
351
|
+
if (seen.has(row.name)) throw OutlineDuplicateRowName.make({
|
|
352
|
+
outline: fullName,
|
|
353
|
+
name: row.name
|
|
354
|
+
});
|
|
355
|
+
seen.add(row.name);
|
|
356
|
+
};
|
|
357
|
+
const keyAtMatches = (actual, expected, i) => actual[i] === expected[i];
|
|
358
|
+
const keysMatch = (actual, expected) => {
|
|
359
|
+
if (actual.length !== expected.length) return false;
|
|
360
|
+
return actual.every((_, i) => keyAtMatches(actual, expected, i));
|
|
361
|
+
};
|
|
362
|
+
const assertKeysConsistent = (row, firstKeys, fullName) => {
|
|
363
|
+
const actual = keysExceptName(row);
|
|
364
|
+
if (keysMatch(actual, firstKeys)) return;
|
|
365
|
+
throw OutlineInconsistentKeys.make({
|
|
366
|
+
outline: fullName,
|
|
367
|
+
row: row.name,
|
|
368
|
+
expected: [...firstKeys],
|
|
369
|
+
actual: [...actual]
|
|
370
|
+
});
|
|
371
|
+
};
|
|
372
|
+
const assertRowHasCapture = (row, cap, fullName) => {
|
|
373
|
+
if (Object.prototype.hasOwnProperty.call(row, cap)) return;
|
|
374
|
+
throw OutlineMissingCapture.make({
|
|
375
|
+
outline: fullName,
|
|
376
|
+
row: row.name,
|
|
377
|
+
capture: cap
|
|
378
|
+
});
|
|
379
|
+
};
|
|
380
|
+
const assertRowCaptures = (row, captureNames, fullName) => {
|
|
381
|
+
for (const cap of captureNames) assertRowHasCapture(row, cap, fullName);
|
|
382
|
+
};
|
|
383
|
+
const validateOneRow = (seenRowNames, row, firstKeys, captureNames, fullName) => {
|
|
384
|
+
rememberRowName(seenRowNames, row, fullName);
|
|
385
|
+
assertKeysConsistent(row, firstKeys, fullName);
|
|
386
|
+
assertRowCaptures(row, captureNames, fullName);
|
|
387
|
+
};
|
|
388
|
+
const validateOutlineRows = (rows, captureNames, fullName) => {
|
|
389
|
+
assertOutlineNonEmpty(rows, fullName);
|
|
258
390
|
const seenRowNames = /* @__PURE__ */ new Set();
|
|
259
|
-
const firstKeys =
|
|
260
|
-
for (const row of rows)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
row: row.name,
|
|
270
|
-
expected: [...firstKeys],
|
|
271
|
-
actual: [...actual]
|
|
272
|
-
});
|
|
273
|
-
for (const cap of captureNames) if (!Object.prototype.hasOwnProperty.call(row, cap)) throw OutlineMissingCapture.make({
|
|
274
|
-
outline: fullName,
|
|
275
|
-
row: row.name,
|
|
276
|
-
capture: cap
|
|
277
|
-
});
|
|
278
|
-
}
|
|
391
|
+
const firstKeys = firstRowKeys(rows);
|
|
392
|
+
for (const row of rows) validateOneRow(seenRowNames, row, firstKeys, captureNames, fullName);
|
|
393
|
+
};
|
|
394
|
+
const addModelCaptures = (captureNames, m) => {
|
|
395
|
+
for (const c of m.captures) captureNames.add(c.name);
|
|
396
|
+
};
|
|
397
|
+
const collectCaptureNames = (models) => {
|
|
398
|
+
const captureNames = /* @__PURE__ */ new Set();
|
|
399
|
+
for (const m of models) addModelCaptures(captureNames, m);
|
|
400
|
+
return captureNames;
|
|
279
401
|
};
|
|
280
402
|
const makeOutline = (background, prefix, context) => {
|
|
281
403
|
function outline(name, ...rest) {
|
|
282
404
|
const { options, steps } = parseScenarioArgs(rest);
|
|
283
|
-
const fullName = prefix
|
|
284
|
-
const withRecord = options
|
|
405
|
+
const fullName = qualifyName(prefix, name);
|
|
406
|
+
const withRecord = withRecordOf(options);
|
|
285
407
|
const models = steps.map((s) => s.model);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const captureNames =
|
|
289
|
-
for (const m of models) for (const c of m.captures) captureNames.add(c.name);
|
|
408
|
+
assertNonEmptyScenario(fullName, models);
|
|
409
|
+
assertHasThen(fullName, models);
|
|
410
|
+
const captureNames = collectCaptureNames(models);
|
|
290
411
|
const buildRowSpec = (row) => {
|
|
291
412
|
const values = {
|
|
292
413
|
...withRecord,
|
|
@@ -307,17 +428,24 @@ const makeOutline = (background, prefix, context) => {
|
|
|
307
428
|
}
|
|
308
429
|
return outline;
|
|
309
430
|
};
|
|
431
|
+
const assertResolvedGiven = (step, resolved) => {
|
|
432
|
+
if (resolved === "Given") return;
|
|
433
|
+
throw BackgroundNotGiven.make({
|
|
434
|
+
step: displayPattern(step.model),
|
|
435
|
+
resolved
|
|
436
|
+
});
|
|
437
|
+
};
|
|
438
|
+
const assertBackgroundResolved = (step, resolvedEntry) => {
|
|
439
|
+
if (resolvedEntry === void 0) return;
|
|
440
|
+
assertResolvedGiven(step, resolvedEntry.resolved);
|
|
441
|
+
};
|
|
442
|
+
const assertBackgroundStep = (step, resolvedEntry) => {
|
|
443
|
+
if (step === void 0) return;
|
|
444
|
+
assertBackgroundResolved(step, resolvedEntry);
|
|
445
|
+
};
|
|
310
446
|
const makeBackground = (background) => (...steps) => {
|
|
311
447
|
const resolvedKeywords = resolveKeywords(steps.map((s) => s.model));
|
|
312
|
-
for (let i = 0; i < steps.length; i++)
|
|
313
|
-
const step = steps[i];
|
|
314
|
-
const resolvedEntry = resolvedKeywords[i];
|
|
315
|
-
if (step === void 0 || resolvedEntry === void 0) continue;
|
|
316
|
-
if (resolvedEntry.resolved !== "Given") throw BackgroundNotGiven.make({
|
|
317
|
-
step: displayPattern(step.model),
|
|
318
|
-
resolved: resolvedEntry.resolved
|
|
319
|
-
});
|
|
320
|
-
}
|
|
448
|
+
for (let i = 0; i < steps.length; i++) assertBackgroundStep(steps[i], resolvedKeywords[i]);
|
|
321
449
|
background.push(...steps);
|
|
322
450
|
};
|
|
323
451
|
const makeFeature = (meta, context) => {
|
|
@@ -334,13 +462,17 @@ const makeFeature = (meta, context) => {
|
|
|
334
462
|
})
|
|
335
463
|
};
|
|
336
464
|
};
|
|
465
|
+
const contextOf = (options) => {
|
|
466
|
+
if (options.context === void 0) return Context.empty();
|
|
467
|
+
return options.context;
|
|
468
|
+
};
|
|
337
469
|
/**
|
|
338
470
|
* Declare a feature: a story set whose scenarios execute as CSF `play`
|
|
339
471
|
* functions. `options.context` (default `Context.empty()`) is the Effect
|
|
340
472
|
* context interpreting each scenario's composed step program exactly once,
|
|
341
473
|
* at the play edge.
|
|
342
474
|
*/
|
|
343
|
-
const feature = (meta, options = {}) => makeFeature(meta, options
|
|
475
|
+
const feature = (meta, options = {}) => makeFeature(meta, contextOf(options));
|
|
344
476
|
const Steps = (...steps) => [...steps];
|
|
345
477
|
const From = (story) => {
|
|
346
478
|
const play = story.play;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/storybook-gherkin",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.6",
|
|
5
5
|
"author": "Ryan Lee <drdgvhbh@gmail.com>",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -36,42 +36,44 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"type-fest": "^5.
|
|
39
|
+
"type-fest": "^5.10.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"effect": "4.0.0-rc.
|
|
42
|
+
"effect": "4.0.0-rc.116",
|
|
43
43
|
"storybook": ">=10.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@microsoft/api-extractor": "^7.
|
|
47
|
-
"@storybook/addon-vitest": "^10.
|
|
48
|
-
"@storybook/react-vite": "^10.
|
|
49
|
-
"@systemfsoftware/arethetypeswrong-cli": "^4.
|
|
50
|
-
"@types/node": "^
|
|
51
|
-
"@types/react": "^19.
|
|
52
|
-
"@types/react-dom": "^19.
|
|
53
|
-
"@vitest/browser": "^
|
|
54
|
-
"@vitest/browser-playwright": "^
|
|
55
|
-
"effect": "4.0.0-rc.
|
|
56
|
-
"oxlint": "
|
|
46
|
+
"@microsoft/api-extractor": "^7.59.1",
|
|
47
|
+
"@storybook/addon-vitest": "^10.6.0",
|
|
48
|
+
"@storybook/react-vite": "^10.6.0",
|
|
49
|
+
"@systemfsoftware/arethetypeswrong-cli": "^4.2.0",
|
|
50
|
+
"@types/node": "^26",
|
|
51
|
+
"@types/react": "^19.3.0",
|
|
52
|
+
"@types/react-dom": "^19.3.0",
|
|
53
|
+
"@vitest/browser": "^5",
|
|
54
|
+
"@vitest/browser-playwright": "^5",
|
|
55
|
+
"effect": "4.0.0-rc.116",
|
|
56
|
+
"oxlint": "~1.82.0",
|
|
57
57
|
"playwright": "^1",
|
|
58
|
-
"react": "^19.
|
|
59
|
-
"react-dom": "^19.
|
|
58
|
+
"react": "^19.3.0",
|
|
59
|
+
"react-dom": "^19.3.0",
|
|
60
60
|
"rimraf": "^6.1.3",
|
|
61
|
-
"storybook": "^10.
|
|
62
|
-
"tsdown": "^0.
|
|
61
|
+
"storybook": "^10.6.0",
|
|
62
|
+
"tsdown": "^0.23.0",
|
|
63
63
|
"typescript": "^7",
|
|
64
64
|
"vite": "^8",
|
|
65
|
-
"vitest": "^
|
|
65
|
+
"vitest": "^5",
|
|
66
|
+
"@systemfsoftware/all": "^2.1.2",
|
|
66
67
|
"@systemfsoftware/oxlint-config": "^0.1.0",
|
|
67
|
-
"@systemfsoftware/
|
|
68
|
+
"@systemfsoftware/tsdown-config": "^0.1.0",
|
|
69
|
+
"@systemfsoftware/tsconfig": "^1.3.6"
|
|
68
70
|
},
|
|
69
71
|
"publishConfig": {
|
|
70
72
|
"provenance": true
|
|
71
73
|
},
|
|
72
74
|
"scripts": {
|
|
73
75
|
"clean": "rimraf dist",
|
|
74
|
-
"build": "tsdown && pnpm dts:check && pnpm api:check",
|
|
76
|
+
"build": "tsdown -l warn && pnpm dts:check && pnpm api:check",
|
|
75
77
|
"typecheck": "tsc --noEmit --incremental",
|
|
76
78
|
"lint": "f=${OXLINT_FORMAT:-${AGENT:+agent}}; oxlint . --config oxlint.config.ts --format=${f:-default}",
|
|
77
79
|
"lint:tsgo": "effect-tsgo diagnostics --project tsconfig.json --format ${TSGO_FORMAT:-text}",
|
|
@@ -79,7 +81,7 @@
|
|
|
79
81
|
"dts:check": "node scripts/check-dts.mjs",
|
|
80
82
|
"storybook": "storybook dev -p 6006 --no-open",
|
|
81
83
|
"test:browser": "pnpm build && vitest run",
|
|
82
|
-
"api:check": "tsdown && api-extractor run",
|
|
84
|
+
"api:check": "tsdown -l warn && api-extractor-quiet run",
|
|
83
85
|
"api:update": "api-extractor run --local"
|
|
84
86
|
}
|
|
85
87
|
}
|