@systemfsoftware/xstate-test 0.0.0-bootstrap.0 → 2.0.0-beta.2
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 +9 -0
- package/LICENSE +21 -0
- package/README.md +1821 -2
- package/dist/adapter-Dd4Go0U3.d.ts +58 -0
- package/dist/effect-schema.d.ts +28 -0
- package/dist/effect-schema.mjs +40 -0
- package/dist/finalizerError-DOptAIDs.mjs +8 -0
- package/dist/index-CSbyUxJr.d.ts +2062 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.mjs +178 -0
- package/dist/playwright.d.ts +204 -0
- package/dist/playwright.mjs +287 -0
- package/dist/propertyTest-5bO0RsUu.mjs +4660 -0
- package/dist/propertyTest-BHnBV8ax.d.ts +147 -0
- package/dist/schema.d.ts +59 -0
- package/dist/schema.mjs +822 -0
- package/dist/vitest.d.ts +74 -0
- package/dist/vitest.mjs +139 -0
- package/package.json +86 -5
package/dist/schema.mjs
ADDED
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
import { dual } from "effect/Function";
|
|
2
|
+
import * as DateTime from "effect/DateTime";
|
|
3
|
+
import * as fc from "fast-check";
|
|
4
|
+
// src/schema.ts
|
|
5
|
+
const SUPPORTED_LIBRARIES = "Zod (v3 and v4) and Effect Schema (via `@xstate/test/effect-schema`)";
|
|
6
|
+
function unsupported(message) {
|
|
7
|
+
throw new Error(`[@xstate/test] ${message}`);
|
|
8
|
+
}
|
|
9
|
+
function unsatisfiable(path, reason) {
|
|
10
|
+
return unsupported(`Unsatisfiable Zod schema at '${path}': ${reason}. Pass an explicit generator for this payload.`);
|
|
11
|
+
}
|
|
12
|
+
const MAX_FILTER_ATTEMPTS = 1e3;
|
|
13
|
+
/**
|
|
14
|
+
* Like `.filter()`, but throws after `MAX_FILTER_ATTEMPTS` consecutive
|
|
15
|
+
* rejections instead of looping forever. Shrinking filters without a limit.
|
|
16
|
+
*/
|
|
17
|
+
var BoundedFilterArbitrary = class extends fc.Arbitrary {
|
|
18
|
+
arbitrary;
|
|
19
|
+
predicate;
|
|
20
|
+
description;
|
|
21
|
+
path;
|
|
22
|
+
constructor(arbitrary, predicate, description, path) {
|
|
23
|
+
super();
|
|
24
|
+
this.arbitrary = arbitrary;
|
|
25
|
+
this.predicate = predicate;
|
|
26
|
+
this.description = description;
|
|
27
|
+
this.path = path;
|
|
28
|
+
}
|
|
29
|
+
generate(mrng, biasFactor) {
|
|
30
|
+
for (let attempt = 0; attempt < MAX_FILTER_ATTEMPTS; attempt++) {
|
|
31
|
+
const generated = this.arbitrary.generate(mrng, biasFactor);
|
|
32
|
+
if (this.predicate(generated.value)) return generated;
|
|
33
|
+
}
|
|
34
|
+
return unsupported(`Could not generate a value satisfying ${this.description} at '${this.path}' after ${MAX_FILTER_ATTEMPTS} attempts. Pass an explicit generator for this payload.`);
|
|
35
|
+
}
|
|
36
|
+
canShrinkWithoutContext(value) {
|
|
37
|
+
return this.arbitrary.canShrinkWithoutContext(value) && this.predicate(value);
|
|
38
|
+
}
|
|
39
|
+
shrink(value, context) {
|
|
40
|
+
return this.arbitrary.shrink(value, context).filter((shrunk) => this.predicate(shrunk.value));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
function getZodDef(schema) {
|
|
44
|
+
if (schema === null || typeof schema !== "object" && typeof schema !== "function") return;
|
|
45
|
+
const candidate = schema;
|
|
46
|
+
const def = candidate._zod?.def ?? candidate._def;
|
|
47
|
+
if (def === void 0 || typeof def !== "object") return;
|
|
48
|
+
return def;
|
|
49
|
+
}
|
|
50
|
+
function getZodKind(def) {
|
|
51
|
+
if (typeof def.type === "string") return def.type;
|
|
52
|
+
if (typeof def.typeName === "string") return def.typeName.replace(/^Zod/, "").toLowerCase();
|
|
53
|
+
}
|
|
54
|
+
function getZodShape(def) {
|
|
55
|
+
const shape = def["shape"];
|
|
56
|
+
return typeof shape === "function" ? shape() : shape ?? {};
|
|
57
|
+
}
|
|
58
|
+
function isZodOptionalKind(schema) {
|
|
59
|
+
const def = getZodDef(schema);
|
|
60
|
+
if (def === void 0) return false;
|
|
61
|
+
const kind = getZodKind(def);
|
|
62
|
+
return kind === "optional" || kind === "default" || kind === "prefault";
|
|
63
|
+
}
|
|
64
|
+
function unsupportedCheck(name, kind, path) {
|
|
65
|
+
return unsupported(`Unsupported Zod check '${name}' on ${kind} at '${path}'. Pass an explicit generator for this payload.`);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Collects the raw check definitions of a schema. Zod v4 wraps each check in a
|
|
69
|
+
* `_zod.def`, and a format schema (`z.email()`, `z.int()`) carries its check on
|
|
70
|
+
* the schema def itself.
|
|
71
|
+
*/
|
|
72
|
+
function getZodCheckDefs(def) {
|
|
73
|
+
const raw = [];
|
|
74
|
+
if (typeof def["check"] === "string") raw.push(def);
|
|
75
|
+
if (Array.isArray(def["checks"])) raw.push(...def["checks"]);
|
|
76
|
+
return raw.map((check) => check?._zod?.def ?? check);
|
|
77
|
+
}
|
|
78
|
+
function numberFormatChecks(format, name, kind, path) {
|
|
79
|
+
const bounded = (min, max) => [
|
|
80
|
+
{ kind: "int" },
|
|
81
|
+
{
|
|
82
|
+
kind: "min",
|
|
83
|
+
value: min,
|
|
84
|
+
inclusive: true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
kind: "max",
|
|
88
|
+
value: max,
|
|
89
|
+
inclusive: true
|
|
90
|
+
}
|
|
91
|
+
];
|
|
92
|
+
switch (format) {
|
|
93
|
+
case "int":
|
|
94
|
+
case "safeint": return [{ kind: "int" }];
|
|
95
|
+
case "int32": return bounded(-2147483648, 2147483647);
|
|
96
|
+
case "uint32": return bounded(0, 4294967295);
|
|
97
|
+
case "int64": return bounded(-(2n ** 63n), 2n ** 63n - 1n);
|
|
98
|
+
case "uint64": return bounded(0n, 2n ** 64n - 1n);
|
|
99
|
+
case "float64": return [];
|
|
100
|
+
default: return unsupportedCheck(`${name}:${format}`, kind, path);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Normalizes every check declared on a schema, per Zod major version. */
|
|
104
|
+
function getZodChecks(def, kind, path) {
|
|
105
|
+
const checks = [];
|
|
106
|
+
for (const check of getZodCheckDefs(def)) {
|
|
107
|
+
const name = typeof check["check"] === "string" ? check["check"] : typeof check["kind"] === "string" ? check["kind"] : void 0;
|
|
108
|
+
switch (name) {
|
|
109
|
+
case "greater_than":
|
|
110
|
+
checks.push({
|
|
111
|
+
kind: "min",
|
|
112
|
+
value: check["value"],
|
|
113
|
+
inclusive: check["inclusive"] !== false
|
|
114
|
+
});
|
|
115
|
+
break;
|
|
116
|
+
case "less_than":
|
|
117
|
+
checks.push({
|
|
118
|
+
kind: "max",
|
|
119
|
+
value: check["value"],
|
|
120
|
+
inclusive: check["inclusive"] !== false
|
|
121
|
+
});
|
|
122
|
+
break;
|
|
123
|
+
case "min_length":
|
|
124
|
+
case "min_size":
|
|
125
|
+
checks.push({
|
|
126
|
+
kind: "minLength",
|
|
127
|
+
value: check["minimum"]
|
|
128
|
+
});
|
|
129
|
+
break;
|
|
130
|
+
case "max_length":
|
|
131
|
+
case "max_size":
|
|
132
|
+
checks.push({
|
|
133
|
+
kind: "maxLength",
|
|
134
|
+
value: check["maximum"]
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
137
|
+
case "length_equals":
|
|
138
|
+
case "size_equals":
|
|
139
|
+
checks.push({
|
|
140
|
+
kind: "length",
|
|
141
|
+
value: check["length"] ?? check["size"]
|
|
142
|
+
});
|
|
143
|
+
break;
|
|
144
|
+
case "multiple_of":
|
|
145
|
+
checks.push({
|
|
146
|
+
kind: "multipleOf",
|
|
147
|
+
value: check["value"]
|
|
148
|
+
});
|
|
149
|
+
break;
|
|
150
|
+
case "number_format":
|
|
151
|
+
case "bigint_format":
|
|
152
|
+
checks.push(...numberFormatChecks(String(check["format"]), name, kind, path));
|
|
153
|
+
break;
|
|
154
|
+
case "string_format":
|
|
155
|
+
checks.push({
|
|
156
|
+
kind: "format",
|
|
157
|
+
format: String(check["format"]),
|
|
158
|
+
...check["pattern"] instanceof RegExp ? { pattern: check["pattern"] } : {},
|
|
159
|
+
value: check["prefix"] ?? check["suffix"] ?? check["includes"]
|
|
160
|
+
});
|
|
161
|
+
break;
|
|
162
|
+
case "overwrite":
|
|
163
|
+
if (typeof check["tx"] !== "function") unsupportedCheck(name, kind, path);
|
|
164
|
+
checks.push({
|
|
165
|
+
kind: "transform",
|
|
166
|
+
apply: check["tx"]
|
|
167
|
+
});
|
|
168
|
+
break;
|
|
169
|
+
case "min":
|
|
170
|
+
checks.push(kind === "string" ? {
|
|
171
|
+
kind: "minLength",
|
|
172
|
+
value: check["value"]
|
|
173
|
+
} : {
|
|
174
|
+
kind: "min",
|
|
175
|
+
value: check["value"],
|
|
176
|
+
inclusive: check["inclusive"] !== false
|
|
177
|
+
});
|
|
178
|
+
break;
|
|
179
|
+
case "max":
|
|
180
|
+
checks.push(kind === "string" ? {
|
|
181
|
+
kind: "maxLength",
|
|
182
|
+
value: check["value"]
|
|
183
|
+
} : {
|
|
184
|
+
kind: "max",
|
|
185
|
+
value: check["value"],
|
|
186
|
+
inclusive: check["inclusive"] !== false
|
|
187
|
+
});
|
|
188
|
+
break;
|
|
189
|
+
case "length":
|
|
190
|
+
checks.push({
|
|
191
|
+
kind: "length",
|
|
192
|
+
value: check["value"]
|
|
193
|
+
});
|
|
194
|
+
break;
|
|
195
|
+
case "int":
|
|
196
|
+
checks.push({ kind: "int" });
|
|
197
|
+
break;
|
|
198
|
+
case "multipleOf":
|
|
199
|
+
checks.push({
|
|
200
|
+
kind: "multipleOf",
|
|
201
|
+
value: check["value"]
|
|
202
|
+
});
|
|
203
|
+
break;
|
|
204
|
+
case "finite":
|
|
205
|
+
case "safe": break;
|
|
206
|
+
case "email":
|
|
207
|
+
case "uuid":
|
|
208
|
+
case "url":
|
|
209
|
+
checks.push({
|
|
210
|
+
kind: "format",
|
|
211
|
+
format: name
|
|
212
|
+
});
|
|
213
|
+
break;
|
|
214
|
+
case "regex":
|
|
215
|
+
if (!(check["regex"] instanceof RegExp)) unsupportedCheck(name, kind, path);
|
|
216
|
+
checks.push({
|
|
217
|
+
kind: "format",
|
|
218
|
+
format: "regex",
|
|
219
|
+
pattern: check["regex"]
|
|
220
|
+
});
|
|
221
|
+
break;
|
|
222
|
+
case "startsWith":
|
|
223
|
+
checks.push({
|
|
224
|
+
kind: "format",
|
|
225
|
+
format: "starts_with",
|
|
226
|
+
value: check["value"]
|
|
227
|
+
});
|
|
228
|
+
break;
|
|
229
|
+
case "endsWith":
|
|
230
|
+
checks.push({
|
|
231
|
+
kind: "format",
|
|
232
|
+
format: "ends_with",
|
|
233
|
+
value: check["value"]
|
|
234
|
+
});
|
|
235
|
+
break;
|
|
236
|
+
case "includes":
|
|
237
|
+
checks.push({
|
|
238
|
+
kind: "format",
|
|
239
|
+
format: "includes",
|
|
240
|
+
value: check["value"]
|
|
241
|
+
});
|
|
242
|
+
break;
|
|
243
|
+
case "trim":
|
|
244
|
+
checks.push({
|
|
245
|
+
kind: "transform",
|
|
246
|
+
apply: (value) => value.trim()
|
|
247
|
+
});
|
|
248
|
+
break;
|
|
249
|
+
case "toLowerCase":
|
|
250
|
+
checks.push({
|
|
251
|
+
kind: "transform",
|
|
252
|
+
apply: (value) => value.toLowerCase()
|
|
253
|
+
});
|
|
254
|
+
break;
|
|
255
|
+
case "toUpperCase":
|
|
256
|
+
checks.push({
|
|
257
|
+
kind: "transform",
|
|
258
|
+
apply: (value) => value.toUpperCase()
|
|
259
|
+
});
|
|
260
|
+
break;
|
|
261
|
+
default: unsupportedCheck(name ?? "unknown", kind, path);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return checks;
|
|
265
|
+
}
|
|
266
|
+
/** Reads `minLength`/`maxLength`/`length` checks, rejecting anything else. */
|
|
267
|
+
function zodSizeBounds(checks, path) {
|
|
268
|
+
const bounds = {};
|
|
269
|
+
for (const check of checks) switch (check.kind) {
|
|
270
|
+
case "minLength":
|
|
271
|
+
bounds.minLength = Math.max(bounds.minLength ?? 0, check.value);
|
|
272
|
+
break;
|
|
273
|
+
case "maxLength":
|
|
274
|
+
bounds.maxLength = Math.min(bounds.maxLength ?? Infinity, check.value);
|
|
275
|
+
break;
|
|
276
|
+
case "length":
|
|
277
|
+
bounds.minLength = check.value;
|
|
278
|
+
bounds.maxLength = check.value;
|
|
279
|
+
break;
|
|
280
|
+
default: unsupportedCheck(check.kind, "collection", path);
|
|
281
|
+
}
|
|
282
|
+
return bounds;
|
|
283
|
+
}
|
|
284
|
+
/** Merges the size bounds Zod v3 keeps on the def itself, not in `checks`. */
|
|
285
|
+
function zodLegacySizeBounds(def, bounds) {
|
|
286
|
+
const read = (value) => typeof value?.value === "number" ? value.value : void 0;
|
|
287
|
+
const exact = read(def["exactLength"]);
|
|
288
|
+
const min = exact ?? read(def["minLength"]) ?? read(def["minSize"]);
|
|
289
|
+
const max = exact ?? read(def["maxLength"]) ?? read(def["maxSize"]);
|
|
290
|
+
const minLength = min ?? bounds.minLength;
|
|
291
|
+
const maxLength = max ?? bounds.maxLength;
|
|
292
|
+
return {
|
|
293
|
+
...minLength === void 0 ? {} : { minLength },
|
|
294
|
+
...maxLength === void 0 || maxLength === Infinity ? {} : { maxLength }
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function toNumber(value) {
|
|
298
|
+
return typeof value === "bigint" ? Number(value) : value instanceof Date ? value.getTime() : value;
|
|
299
|
+
}
|
|
300
|
+
function zodNumericBounds(checks, kind, path) {
|
|
301
|
+
const bounds = {
|
|
302
|
+
minExcluded: false,
|
|
303
|
+
maxExcluded: false,
|
|
304
|
+
int: false,
|
|
305
|
+
multipleOf: []
|
|
306
|
+
};
|
|
307
|
+
for (const check of checks) switch (check.kind) {
|
|
308
|
+
case "min": {
|
|
309
|
+
const value = toNumber(check.value);
|
|
310
|
+
if (bounds.min === void 0 || value > bounds.min) {
|
|
311
|
+
bounds.min = value;
|
|
312
|
+
bounds.minExcluded = !check.inclusive;
|
|
313
|
+
} else if (value === bounds.min && !check.inclusive) bounds.minExcluded = true;
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
case "max": {
|
|
317
|
+
const value = toNumber(check.value);
|
|
318
|
+
if (bounds.max === void 0 || value < bounds.max) {
|
|
319
|
+
bounds.max = value;
|
|
320
|
+
bounds.maxExcluded = !check.inclusive;
|
|
321
|
+
} else if (value === bounds.max && !check.inclusive) bounds.maxExcluded = true;
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
case "int":
|
|
325
|
+
bounds.int = true;
|
|
326
|
+
break;
|
|
327
|
+
case "multipleOf":
|
|
328
|
+
bounds.multipleOf.push(toNumber(check.value));
|
|
329
|
+
break;
|
|
330
|
+
default: unsupportedCheck(check.kind, kind, path);
|
|
331
|
+
}
|
|
332
|
+
return bounds;
|
|
333
|
+
}
|
|
334
|
+
const INT_SPAN = 2 ** 31 - 1;
|
|
335
|
+
function integerRange(bounds) {
|
|
336
|
+
let min = bounds.min === void 0 ? -INT_SPAN : Math.ceil(bounds.min) + (bounds.minExcluded && Number.isInteger(bounds.min) ? 1 : 0);
|
|
337
|
+
let max = bounds.max === void 0 ? INT_SPAN : Math.floor(bounds.max) - (bounds.maxExcluded && Number.isInteger(bounds.max) ? 1 : 0);
|
|
338
|
+
min = Math.max(min, -Number.MAX_SAFE_INTEGER);
|
|
339
|
+
max = Math.min(max, Number.MAX_SAFE_INTEGER);
|
|
340
|
+
return {
|
|
341
|
+
min,
|
|
342
|
+
max
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function gcd(a, b) {
|
|
346
|
+
while (b !== 0n) [a, b] = [b, a % b];
|
|
347
|
+
return a;
|
|
348
|
+
}
|
|
349
|
+
/** Reads a finite, non-zero number as the exact fraction of its decimal form. */
|
|
350
|
+
function toFraction(value) {
|
|
351
|
+
if (!Number.isFinite(value) || value === 0) return;
|
|
352
|
+
const match = /^(\d+)(?:\.(\d+))?(?:e([+-]\d+))?$/.exec(String(Math.abs(value)));
|
|
353
|
+
if (match === null) return;
|
|
354
|
+
const [, whole, decimals = "", exponent = "0"] = match;
|
|
355
|
+
const scale = Number(exponent) - decimals.length;
|
|
356
|
+
const numerator = BigInt(whole + decimals) * (scale > 0 ? 10n ** BigInt(scale) : 1n);
|
|
357
|
+
const denominator = scale < 0 ? 10n ** BigInt(-scale) : 1n;
|
|
358
|
+
const divisor = gcd(numerator, denominator);
|
|
359
|
+
return [numerator / divisor, denominator / divisor];
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Combines every `multipleOf` step (and `int`, a step of 1) into their least
|
|
363
|
+
* common multiple, as an exact fraction.
|
|
364
|
+
*/
|
|
365
|
+
function combinedStep(bounds, path) {
|
|
366
|
+
const steps = bounds.int ? [...bounds.multipleOf, 1] : bounds.multipleOf;
|
|
367
|
+
let result;
|
|
368
|
+
for (const step of steps) {
|
|
369
|
+
const fraction = toFraction(step);
|
|
370
|
+
if (fraction === void 0) return unsupported(`Unsupported Zod multipleOf(${step}) on number at '${path}'. Pass an explicit generator for this payload.`);
|
|
371
|
+
if (result === void 0) {
|
|
372
|
+
result = fraction;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const [a, b] = result;
|
|
376
|
+
const [c, d] = fraction;
|
|
377
|
+
result = [a / gcd(a, c) * c, gcd(b, d)];
|
|
378
|
+
}
|
|
379
|
+
if (result === void 0 || result[0] > BigInt(Number.MAX_SAFE_INTEGER) || result[1] > BigInt(Number.MAX_SAFE_INTEGER)) return unsupported(`Unsupported Zod multipleOf combination (${steps.join(", ")}) on number at '${path}': no common multiple can be generated exactly. Pass an explicit generator for this payload.`);
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
function zodMultipleOfArbitrary(bounds, path) {
|
|
383
|
+
const [stepNumerator, stepDenominator] = combinedStep(bounds, path);
|
|
384
|
+
const numerator = Number(stepNumerator);
|
|
385
|
+
const denominator = Number(stepDenominator);
|
|
386
|
+
const valueOf = (factor) => factor * numerator / denominator;
|
|
387
|
+
const step = numerator / denominator;
|
|
388
|
+
const limit = Math.min(INT_SPAN, Math.floor(Number.MAX_SAFE_INTEGER / numerator));
|
|
389
|
+
const aboveMin = (value) => bounds.min === void 0 || (bounds.minExcluded ? value > bounds.min : value >= bounds.min);
|
|
390
|
+
const belowMax = (value) => bounds.max === void 0 || (bounds.maxExcluded ? value < bounds.max : value <= bounds.max);
|
|
391
|
+
let min = bounds.min === void 0 ? -limit : Math.max(-limit, Math.ceil(bounds.min / step) - 1);
|
|
392
|
+
let max = bounds.max === void 0 ? limit : Math.min(limit, Math.floor(bounds.max / step) + 1);
|
|
393
|
+
while (min <= max && !aboveMin(valueOf(min))) min++;
|
|
394
|
+
while (max >= min && !belowMax(valueOf(max))) max--;
|
|
395
|
+
if (min > max) unsupported(`Zod number at '${path}' declares a multipleOf that no value in its range satisfies.`);
|
|
396
|
+
return fc.integer({
|
|
397
|
+
min,
|
|
398
|
+
max
|
|
399
|
+
}).map(valueOf);
|
|
400
|
+
}
|
|
401
|
+
function zodNumberArbitrary(def, kind, path) {
|
|
402
|
+
const bounds = zodNumericBounds(getZodChecks(def, kind, path), kind, path);
|
|
403
|
+
const format = def["format"];
|
|
404
|
+
if (!bounds.int && typeof format === "string" && (format.includes("int") || kind === "int")) bounds.int = true;
|
|
405
|
+
if (bounds.multipleOf.length !== 0) return zodMultipleOfArbitrary(bounds, path);
|
|
406
|
+
if (bounds.int) {
|
|
407
|
+
const { min, max } = integerRange(bounds);
|
|
408
|
+
if (min > max) unsupported(`Zod number at '${path}' declares an empty integer range.`);
|
|
409
|
+
return fc.integer({
|
|
410
|
+
min,
|
|
411
|
+
max
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
if (bounds.max === 0 && bounds.maxExcluded) {
|
|
415
|
+
bounds.max = -Number.MIN_VALUE;
|
|
416
|
+
bounds.maxExcluded = false;
|
|
417
|
+
}
|
|
418
|
+
if (bounds.min === 0 && bounds.minExcluded) {
|
|
419
|
+
bounds.min = Number.MIN_VALUE;
|
|
420
|
+
bounds.minExcluded = false;
|
|
421
|
+
}
|
|
422
|
+
if (bounds.min !== void 0 && bounds.max !== void 0 && (bounds.min > bounds.max || bounds.min === bounds.max && (bounds.minExcluded || bounds.maxExcluded))) unsatisfiable(path, "the numeric range is empty");
|
|
423
|
+
return fc.double({
|
|
424
|
+
noNaN: true,
|
|
425
|
+
noDefaultInfinity: true,
|
|
426
|
+
...bounds.min === void 0 ? {} : {
|
|
427
|
+
min: bounds.min,
|
|
428
|
+
minExcluded: bounds.minExcluded
|
|
429
|
+
},
|
|
430
|
+
...bounds.max === void 0 ? {} : {
|
|
431
|
+
max: bounds.max,
|
|
432
|
+
maxExcluded: bounds.maxExcluded
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
function zodBigIntArbitrary(def, kind, path) {
|
|
437
|
+
let min;
|
|
438
|
+
let max;
|
|
439
|
+
for (const check of getZodChecks(def, kind, path)) switch (check.kind) {
|
|
440
|
+
case "min": {
|
|
441
|
+
const value = BigInt(check.value) + (check.inclusive ? 0n : 1n);
|
|
442
|
+
min = min === void 0 || value > min ? value : min;
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
case "max": {
|
|
446
|
+
const value = BigInt(check.value) - (check.inclusive ? 0n : 1n);
|
|
447
|
+
max = max === void 0 || value < max ? value : max;
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
case "int": break;
|
|
451
|
+
default: unsupportedCheck(check.kind, "bigint", path);
|
|
452
|
+
}
|
|
453
|
+
if (min !== void 0 && max !== void 0 && min > max) unsatisfiable(path, "the bigint range is empty");
|
|
454
|
+
return fc.bigInt({
|
|
455
|
+
...min === void 0 ? {} : { min },
|
|
456
|
+
...max === void 0 ? {} : { max }
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function zodDateArbitrary(def, kind, path) {
|
|
460
|
+
let min;
|
|
461
|
+
let max;
|
|
462
|
+
for (const check of getZodChecks(def, kind, path)) switch (check.kind) {
|
|
463
|
+
case "min": {
|
|
464
|
+
const value = DateTime.toDate(DateTime.makeUnsafe(toNumber(check.value) + (check.inclusive ? 0 : 1)));
|
|
465
|
+
min = min === void 0 || value > min ? value : min;
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
case "max": {
|
|
469
|
+
const value = DateTime.toDate(DateTime.makeUnsafe(toNumber(check.value) - (check.inclusive ? 0 : 1)));
|
|
470
|
+
max = max === void 0 || value < max ? value : max;
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
default: unsupportedCheck(check.kind, "date", path);
|
|
474
|
+
}
|
|
475
|
+
if (min !== void 0 && max !== void 0 && min > max) unsatisfiable(path, "the date range is empty");
|
|
476
|
+
return fc.date({
|
|
477
|
+
noInvalidDate: true,
|
|
478
|
+
...min === void 0 ? {} : { min },
|
|
479
|
+
...max === void 0 ? {} : { max }
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
const ALPHANUMERIC = fc.constantFrom(..."abcdefghijklmnopqrstuvwxyz0123456789".split(""));
|
|
483
|
+
const EMAIL_SUFFIX = ".co";
|
|
484
|
+
/** `a@b.co`, the shortest address Zod accepts. */
|
|
485
|
+
const EMAIL_MIN_LENGTH = 6;
|
|
486
|
+
/**
|
|
487
|
+
* `fc.emailAddress()` covers RFC 5322, which is wider than the address Zod
|
|
488
|
+
* accepts, so a conservative address within the length bounds is generated
|
|
489
|
+
* instead.
|
|
490
|
+
*/
|
|
491
|
+
function emailArbitrary(minLength, maxLength) {
|
|
492
|
+
const fixed = 4;
|
|
493
|
+
const min = Math.max(2, minLength - fixed);
|
|
494
|
+
const max = Math.min(Math.max(20, min), maxLength - fixed);
|
|
495
|
+
if (min > max) return;
|
|
496
|
+
const part = (length) => fc.string({
|
|
497
|
+
unit: ALPHANUMERIC,
|
|
498
|
+
minLength: length,
|
|
499
|
+
maxLength: length
|
|
500
|
+
});
|
|
501
|
+
return fc.integer({
|
|
502
|
+
min,
|
|
503
|
+
max
|
|
504
|
+
}).chain((total) => fc.integer({
|
|
505
|
+
min: 1,
|
|
506
|
+
max: total - 1
|
|
507
|
+
}).chain((local) => fc.tuple(part(local), part(total - local)))).map(([local, domain]) => `${local}@${domain}${EMAIL_SUFFIX}`);
|
|
508
|
+
}
|
|
509
|
+
const UUID_LENGTH = 36;
|
|
510
|
+
/** The generator for a named format or pattern, honoring length bounds. */
|
|
511
|
+
function stringFormatArbitrary(check, minLength, maxLength, path) {
|
|
512
|
+
switch (check.format) {
|
|
513
|
+
case "email": {
|
|
514
|
+
const arbitrary = emailArbitrary(minLength, maxLength);
|
|
515
|
+
if (arbitrary === void 0) unsatisfiable(path, `an email address has at least ${EMAIL_MIN_LENGTH} characters, outside the length bounds ${minLength}..${maxLength}`);
|
|
516
|
+
return arbitrary;
|
|
517
|
+
}
|
|
518
|
+
case "uuid":
|
|
519
|
+
case "guid":
|
|
520
|
+
if (minLength > UUID_LENGTH || maxLength < UUID_LENGTH) unsatisfiable(path, `a ${check.format} has ${UUID_LENGTH} characters, outside the length bounds ${minLength}..${maxLength}`);
|
|
521
|
+
return check.pattern !== void 0 ? fc.stringMatching(check.pattern) : fc.uuid();
|
|
522
|
+
case "url": return fc.webUrl();
|
|
523
|
+
default:
|
|
524
|
+
if (check.pattern === void 0) return unsupportedCheck(check.format === "regex" ? "regex" : `string_format:${check.format}`, "string", path);
|
|
525
|
+
return fc.stringMatching(check.pattern);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function zodStringArbitrary(def, kind, path) {
|
|
529
|
+
let minLength;
|
|
530
|
+
let maxLength;
|
|
531
|
+
let prefix = "";
|
|
532
|
+
let suffix = "";
|
|
533
|
+
let infix = "";
|
|
534
|
+
const formats = [];
|
|
535
|
+
const transforms = [];
|
|
536
|
+
for (const check of getZodChecks(def, kind, path)) switch (check.kind) {
|
|
537
|
+
case "minLength":
|
|
538
|
+
minLength = Math.max(minLength ?? 0, check.value);
|
|
539
|
+
break;
|
|
540
|
+
case "maxLength":
|
|
541
|
+
maxLength = Math.min(maxLength ?? Infinity, check.value);
|
|
542
|
+
break;
|
|
543
|
+
case "length":
|
|
544
|
+
minLength = check.value;
|
|
545
|
+
maxLength = check.value;
|
|
546
|
+
break;
|
|
547
|
+
case "transform":
|
|
548
|
+
transforms.push(check.apply);
|
|
549
|
+
break;
|
|
550
|
+
case "format":
|
|
551
|
+
switch (check.format) {
|
|
552
|
+
case "starts_with":
|
|
553
|
+
prefix += check.value ?? "";
|
|
554
|
+
break;
|
|
555
|
+
case "ends_with":
|
|
556
|
+
suffix = (check.value ?? "") + suffix;
|
|
557
|
+
break;
|
|
558
|
+
case "includes":
|
|
559
|
+
infix += check.value ?? "";
|
|
560
|
+
break;
|
|
561
|
+
default: formats.push(check);
|
|
562
|
+
}
|
|
563
|
+
break;
|
|
564
|
+
default: unsupportedCheck(check.kind, "string", path);
|
|
565
|
+
}
|
|
566
|
+
if (minLength !== void 0 && maxLength !== void 0 && minLength > maxLength) unsatisfiable(path, `the minimum length ${minLength} exceeds the maximum length ${maxLength}`);
|
|
567
|
+
if (formats.length > 1) unsupported(`Unsupported combination of Zod string formats ${formats.map((check) => `'${check.format}'`).join(" and ")} at '${path}'. Pass an explicit generator for this payload.`);
|
|
568
|
+
const format = formats[0];
|
|
569
|
+
if (format !== void 0 && (prefix.length > 0 || suffix.length > 0 || infix.length > 0)) unsupported(`Unsupported combination of Zod string format '${format.format}' with startsWith/endsWith/includes at '${path}'. Pass an explicit generator for this payload.`);
|
|
570
|
+
let arbitrary;
|
|
571
|
+
if (format !== void 0) arbitrary = stringFormatArbitrary(format, minLength ?? 0, maxLength ?? Infinity, path);
|
|
572
|
+
else {
|
|
573
|
+
const fixed = prefix.length + infix.length + suffix.length;
|
|
574
|
+
const innerMax = maxLength === void 0 || maxLength === Infinity ? void 0 : maxLength - fixed;
|
|
575
|
+
if (innerMax !== void 0 && innerMax < 0) unsupported(`Zod string at '${path}' declares a maxLength shorter than its required substrings.`);
|
|
576
|
+
arbitrary = fc.string({
|
|
577
|
+
minLength: Math.max(0, (minLength ?? 0) - fixed),
|
|
578
|
+
...innerMax === void 0 ? {} : { maxLength: innerMax }
|
|
579
|
+
}).map((value) => prefix + infix + value + suffix);
|
|
580
|
+
}
|
|
581
|
+
if (transforms.length !== 0) arbitrary = arbitrary.map((value) => transforms.reduce((result, transform) => transform(result), value));
|
|
582
|
+
if ((minLength !== void 0 || maxLength !== void 0 && maxLength !== Infinity) && (format !== void 0 || transforms.length !== 0)) arbitrary = new BoundedFilterArbitrary(arbitrary, (value) => value.length >= (minLength ?? 0) && value.length <= (maxLength ?? Infinity), `the length bounds ${minLength ?? 0}..${maxLength ?? Infinity}`, path);
|
|
583
|
+
return arbitrary;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* The values of an enum, without the reverse mappings (`{ 0: 'A' }`) that
|
|
587
|
+
* TypeScript adds to numeric enums.
|
|
588
|
+
*/
|
|
589
|
+
function zodEnumValues(def) {
|
|
590
|
+
if (Array.isArray(def["values"])) return def["values"];
|
|
591
|
+
const entries = def["entries"] ?? def["values"] ?? {};
|
|
592
|
+
return Object.entries(entries).filter(([key, value]) => !(typeof value === "string" && String(Number(key)) === key && entries[value] === Number(key))).map(([, value]) => value);
|
|
593
|
+
}
|
|
594
|
+
function zodLiteralValues(def) {
|
|
595
|
+
return Array.isArray(def["values"]) ? def["values"] : [def["value"]];
|
|
596
|
+
}
|
|
597
|
+
/** An upper bound on the number of distinct values a schema accepts. */
|
|
598
|
+
function zodDomainSize(schema) {
|
|
599
|
+
const def = getZodDef(schema);
|
|
600
|
+
if (def === void 0) return Infinity;
|
|
601
|
+
switch (getZodKind(def)) {
|
|
602
|
+
case "literal": return zodLiteralValues(def).length;
|
|
603
|
+
case "enum":
|
|
604
|
+
case "nativeenum": return zodEnumValues(def).length;
|
|
605
|
+
case "boolean": return 2;
|
|
606
|
+
case "null":
|
|
607
|
+
case "undefined":
|
|
608
|
+
case "void": return 1;
|
|
609
|
+
case "union": return (def["options"] ?? []).reduce((size, option) => size + zodDomainSize(option), 0);
|
|
610
|
+
case "optional":
|
|
611
|
+
case "nullable": return zodDomainSize(def["innerType"]) + 1;
|
|
612
|
+
case "default":
|
|
613
|
+
case "prefault":
|
|
614
|
+
case "catch":
|
|
615
|
+
case "readonly":
|
|
616
|
+
case "nonoptional": return zodDomainSize(def["innerType"]);
|
|
617
|
+
default: return Infinity;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function checkSizeBounds(bounds, path) {
|
|
621
|
+
if (bounds.minLength !== void 0 && bounds.maxLength !== void 0 && bounds.minLength > bounds.maxLength) unsatisfiable(path, `the minimum size ${bounds.minLength} exceeds the maximum size ${bounds.maxLength}`);
|
|
622
|
+
}
|
|
623
|
+
function isZodExactOptional(schema) {
|
|
624
|
+
return (schema._zod?.traits)?.has("$ZodExactOptional") === true;
|
|
625
|
+
}
|
|
626
|
+
function fromZod(schema, path, convertingZodDefs) {
|
|
627
|
+
const def = getZodDef(schema);
|
|
628
|
+
if (def === void 0) unsupported(`Expected a Zod schema at '${path}'.`);
|
|
629
|
+
if (convertingZodDefs.has(def)) unsupported(`Recursive Zod schema at '${path}'. Recursive schemas cannot be derived; pass an explicit generator for this payload.`);
|
|
630
|
+
convertingZodDefs.add(def);
|
|
631
|
+
try {
|
|
632
|
+
return fromZodDef(schema, def, path, convertingZodDefs);
|
|
633
|
+
} finally {
|
|
634
|
+
convertingZodDefs.delete(def);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function fromZodDef(schema, def, path, convertingZodDefs) {
|
|
638
|
+
const kind = getZodKind(def);
|
|
639
|
+
switch (kind) {
|
|
640
|
+
case "string": return zodStringArbitrary(def, kind, path);
|
|
641
|
+
case "number":
|
|
642
|
+
case "int": return zodNumberArbitrary(def, kind, path);
|
|
643
|
+
case "bigint": return zodBigIntArbitrary(def, kind, path);
|
|
644
|
+
case "boolean": return fc.boolean();
|
|
645
|
+
case "date": return zodDateArbitrary(def, kind, path);
|
|
646
|
+
case "null": return fc.constant(null);
|
|
647
|
+
case "undefined":
|
|
648
|
+
case "void": return fc.constant(void 0);
|
|
649
|
+
case "any":
|
|
650
|
+
case "unknown": return fc.anything();
|
|
651
|
+
case "literal": {
|
|
652
|
+
const values = zodLiteralValues(def);
|
|
653
|
+
if (values.length === 0) unsupported(`Zod literal at '${path}' declares no values.`);
|
|
654
|
+
return fc.constantFrom(...values);
|
|
655
|
+
}
|
|
656
|
+
case "enum":
|
|
657
|
+
case "nativeenum": {
|
|
658
|
+
const values = zodEnumValues(def);
|
|
659
|
+
if (values.length === 0) unsupported(`Zod enum at '${path}' declares no values.`);
|
|
660
|
+
return fc.constantFrom(...values);
|
|
661
|
+
}
|
|
662
|
+
case "union": {
|
|
663
|
+
const options = def["options"] ?? [];
|
|
664
|
+
if (options.length === 0) unsupported(`Zod union at '${path}' declares no options.`);
|
|
665
|
+
return fc.oneof(...options.map((option, index) => fromZod(option, `${path}|${String(index)}`, convertingZodDefs)));
|
|
666
|
+
}
|
|
667
|
+
case "array": {
|
|
668
|
+
const bounds = zodLegacySizeBounds(def, zodSizeBounds(getZodChecks(def, kind, path), path));
|
|
669
|
+
checkSizeBounds(bounds, path);
|
|
670
|
+
return fc.array(fromZod(def["element"] ?? def.type, `${path}[]`, convertingZodDefs), bounds);
|
|
671
|
+
}
|
|
672
|
+
case "set": {
|
|
673
|
+
const bounds = zodLegacySizeBounds(def, zodSizeBounds(getZodChecks(def, kind, path), path));
|
|
674
|
+
checkSizeBounds(bounds, path);
|
|
675
|
+
const domainSize = zodDomainSize(def["valueType"]);
|
|
676
|
+
if ((bounds.minLength ?? 0) > domainSize) unsatisfiable(path, `the set needs at least ${bounds.minLength} distinct elements, but its element schema accepts only ${domainSize}`);
|
|
677
|
+
return fc.uniqueArray(fromZod(def["valueType"], `${path}[]`, convertingZodDefs), bounds).map((values) => new Set(values));
|
|
678
|
+
}
|
|
679
|
+
case "tuple": {
|
|
680
|
+
const items = def["items"] ?? [];
|
|
681
|
+
return fc.tuple(...items.map((item, index) => fromZod(item, `${path}[${index}]`, convertingZodDefs)));
|
|
682
|
+
}
|
|
683
|
+
case "record": {
|
|
684
|
+
const keys = def["keyType"]?._zod?.values;
|
|
685
|
+
if (keys !== void 0) {
|
|
686
|
+
const model = {};
|
|
687
|
+
for (const key of keys) {
|
|
688
|
+
if (typeof key !== "string" && typeof key !== "number") unsupported(`Unsupported Zod record key ${String(key)} at '${path}'. Pass an explicit generator for this payload.`);
|
|
689
|
+
model[String(key)] = fromZod(def["valueType"], `${path}.${key}`, convertingZodDefs);
|
|
690
|
+
}
|
|
691
|
+
return fc.record(model);
|
|
692
|
+
}
|
|
693
|
+
return fc.dictionary(fromZod(def["keyType"], `${path}.<key>`, convertingZodDefs), fromZod(def["valueType"], `${path}.<value>`, convertingZodDefs));
|
|
694
|
+
}
|
|
695
|
+
case "optional": return isZodExactOptional(schema) ? fromZod(def["innerType"], path, convertingZodDefs) : fc.option(fromZod(def["innerType"], path, convertingZodDefs), { nil: void 0 });
|
|
696
|
+
case "nullable": return fc.option(fromZod(def["innerType"], path, convertingZodDefs), { nil: null });
|
|
697
|
+
case "default":
|
|
698
|
+
case "prefault":
|
|
699
|
+
case "catch":
|
|
700
|
+
case "readonly":
|
|
701
|
+
case "nonoptional": return fromZod(def["innerType"], path, convertingZodDefs);
|
|
702
|
+
case "lazy":
|
|
703
|
+
if (typeof def["getter"] !== "function") return unsupported(`Zod lazy schema at '${path}' has no getter.`);
|
|
704
|
+
return fromZod(def["getter"](), path, convertingZodDefs);
|
|
705
|
+
case "object":
|
|
706
|
+
case "interface": return zodObjectArbitrary(getZodShape(def), path, convertingZodDefs);
|
|
707
|
+
default: return unsupported(`Unsupported Zod schema kind '${kind ?? "unknown"}' at '${path}'.`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function zodObjectArbitrary(shape, path, convertingZodDefs) {
|
|
711
|
+
const model = {};
|
|
712
|
+
const requiredKeys = [];
|
|
713
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
714
|
+
model[key] = fromZod(value, path.length > 0 ? `${path}.${key}` : key, convertingZodDefs);
|
|
715
|
+
if (!isZodOptionalKind(value)) requiredKeys.push(key);
|
|
716
|
+
}
|
|
717
|
+
return fc.record(model, { requiredKeys });
|
|
718
|
+
}
|
|
719
|
+
function isZodSchema(schema) {
|
|
720
|
+
const def = getZodDef(schema);
|
|
721
|
+
return def !== void 0 && getZodKind(def) !== void 0;
|
|
722
|
+
}
|
|
723
|
+
/** ------------------------------------------------------------ Generic --- */
|
|
724
|
+
const zodConverter = (schema, path) => isZodSchema(schema) ? fromZod(schema, path, /* @__PURE__ */ new Set()) : void 0;
|
|
725
|
+
function isEffectSchema(schema) {
|
|
726
|
+
if (schema === null || typeof schema !== "object" && typeof schema !== "function") return false;
|
|
727
|
+
const ast = schema.ast;
|
|
728
|
+
return ast !== void 0 && ast !== null;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* `types<...>()` declares a payload's TypeScript type and nothing else, so no
|
|
732
|
+
* generator can be derived from it. Implicit derivation skips these; explicit
|
|
733
|
+
* `eventsFromSchemas()` still reports them.
|
|
734
|
+
*
|
|
735
|
+
* @internal
|
|
736
|
+
*/
|
|
737
|
+
function isTypeOnlySchema(schema) {
|
|
738
|
+
return isStandardSchema(schema) && schema["~standard"].vendor === "xstate.types";
|
|
739
|
+
}
|
|
740
|
+
function isStandardSchema(schema) {
|
|
741
|
+
return schema !== null && (typeof schema === "object" || typeof schema === "function") && "~standard" in schema;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Converts a declared payload schema into a FastCheck arbitrary. Only schema
|
|
745
|
+
* libraries whose structure can be inspected are supported; a Standard Schema
|
|
746
|
+
* alone only validates, so nothing can be generated from it.
|
|
747
|
+
*
|
|
748
|
+
* @experimental
|
|
749
|
+
*/
|
|
750
|
+
function arbitraryFromSchema(schema, options = {}, path = "") {
|
|
751
|
+
const converters = [...options.converters ?? [], zodConverter];
|
|
752
|
+
for (const converter of converters) {
|
|
753
|
+
const arbitrary = converter(schema, path);
|
|
754
|
+
if (arbitrary !== void 0) return arbitrary;
|
|
755
|
+
}
|
|
756
|
+
const fallback = options.fallback?.(schema, path);
|
|
757
|
+
if (fallback !== void 0) return fallback;
|
|
758
|
+
if (isEffectSchema(schema)) return unsupported(`Effect Schema found at '${path}'. Import \`eventsFromSchemas\` from '@xstate/test/effect-schema' to generate from Effect Schemas.`);
|
|
759
|
+
if (isTypeOnlySchema(schema)) return unsupported(`The schema at '${path}' is a type-only schema (\`types<...>()\`). Type-only declarations carry no runtime structure, so no generator can be derived. Declare a runtime schema (${SUPPORTED_LIBRARIES}) or pass an explicit generator.`);
|
|
760
|
+
if (isStandardSchema(schema)) return unsupported(`The schema at '${path}' implements Standard Schema but is not a recognized schema library. Standard Schema only validates, so no generator can be derived. Supported libraries: ${SUPPORTED_LIBRARIES}. Pass a \`fallback\` converter to handle it.`);
|
|
761
|
+
return unsupported(`Unrecognized schema at '${path}'. Supported libraries: ${SUPPORTED_LIBRARIES}. Pass a \`fallback\` converter to \`eventsFromSchemas()\` to handle other schemas, or an explicit generator in \`events\`.`);
|
|
762
|
+
}
|
|
763
|
+
/** Strips a top-level `type` key; event-map keys supply the event type. */
|
|
764
|
+
function stripType(arbitrary, eventType) {
|
|
765
|
+
return arbitrary.map((value) => {
|
|
766
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`Property event "${eventType}" generated a non-object payload (${typeof value === "string" ? JSON.stringify(value) : String(value)}). Event payloads must be plain objects; use a \`resolve\` function to map generated values onto an event payload.`);
|
|
767
|
+
const { type: _type, ...payload } = value;
|
|
768
|
+
return payload;
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
/** Mirrors xstate's event descriptor matching (`'user.*'`, `'*'`). */
|
|
772
|
+
function matchesEventDescriptor(eventType, descriptor) {
|
|
773
|
+
if (descriptor === eventType || descriptor === "*") return true;
|
|
774
|
+
if (!descriptor.endsWith(".*")) return false;
|
|
775
|
+
const descriptorTokens = descriptor.split(".");
|
|
776
|
+
const eventTokens = eventType.split(".");
|
|
777
|
+
for (let index = 0; index < descriptorTokens.length; index++) {
|
|
778
|
+
if (descriptorTokens[index] === "*") return index === descriptorTokens.length - 1;
|
|
779
|
+
if (descriptorTokens[index] !== eventTokens[index]) return false;
|
|
780
|
+
}
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
/** Whether a data-first `eventsFromSchemas` argument is a state machine. */
|
|
784
|
+
const isStateMachineArgument = (value) => typeof value === "object" && value !== null && "transition" in value;
|
|
785
|
+
/**
|
|
786
|
+
* Derives event generators from the event schemas declared on a machine
|
|
787
|
+
* (`setup({ schemas: { events } })` or `createMachine({ schemas: { events } })`).
|
|
788
|
+
*
|
|
789
|
+
* Type-only event declarations (`types.events`) are erased at runtime and
|
|
790
|
+
* cannot be used here.
|
|
791
|
+
*
|
|
792
|
+
* @experimental
|
|
793
|
+
*/
|
|
794
|
+
const eventsFromSchemas = dual((args) => args.length >= 2 || isStateMachineArgument(args[0]), function eventsFromSchemas(machine, options = {}) {
|
|
795
|
+
const schemas = machine.schemas?.events ?? {};
|
|
796
|
+
const wildcards = Object.keys(schemas).filter((key) => key.includes("*"));
|
|
797
|
+
const generators = {};
|
|
798
|
+
for (const [eventType, schema] of Object.entries(schemas)) {
|
|
799
|
+
if (eventType.includes("*")) continue;
|
|
800
|
+
generators[eventType] = stripType(arbitraryFromSchema(schema, options, eventType), eventType);
|
|
801
|
+
}
|
|
802
|
+
for (const eventType of machine.events) {
|
|
803
|
+
if (typeof eventType !== "string" || eventType.includes("*") || eventType.startsWith("xstate.") || eventType.startsWith("@xstate.") || generators[eventType] !== void 0) continue;
|
|
804
|
+
const descriptor = wildcards.find((key) => matchesEventDescriptor(eventType, key));
|
|
805
|
+
if (descriptor !== void 0) generators[eventType] = stripType(arbitraryFromSchema(schemas[descriptor], options, eventType), eventType);
|
|
806
|
+
else if ((options.eventsWithoutSchema ?? "empty") === "empty") generators[eventType] = fc.constant({});
|
|
807
|
+
}
|
|
808
|
+
return generators;
|
|
809
|
+
});
|
|
810
|
+
/**
|
|
811
|
+
* Merges derived generators with explicit ones. Explicit entries win for event
|
|
812
|
+
* types declared in both.
|
|
813
|
+
*
|
|
814
|
+
* @experimental
|
|
815
|
+
*/
|
|
816
|
+
const mergeEventGenerators = dual(2, function mergeEventGenerators(derived, explicit) {
|
|
817
|
+
return {
|
|
818
|
+
...derived,
|
|
819
|
+
...explicit
|
|
820
|
+
};
|
|
821
|
+
});
|
|
822
|
+
export { arbitraryFromSchema, eventsFromSchemas, isTypeOnlySchema, mergeEventGenerators };
|