@ripplo/testing 0.7.9 → 0.7.10
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/DSL.md +66 -32
- package/dist/{engine-BfvzXgLg.d.ts → engine-mt1A6IKW.d.ts} +251 -42
- package/dist/express.d.ts +1 -1
- package/dist/express.js +45 -20
- package/dist/index.d.ts +9 -9
- package/dist/index.js +647 -81
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -108,7 +108,13 @@ function not(input) {
|
|
|
108
108
|
if (isConditionInput(input)) {
|
|
109
109
|
return condLeaf({ kind: "not", predicate: input.predicate });
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
const predicate = toPredicate(input);
|
|
112
|
+
if (predicate.kind === "when") {
|
|
113
|
+
throw new Error(
|
|
114
|
+
"when() cannot be wrapped in not() \u2014 express the negation in branch conditions"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return leaf({ kind: "not", predicate });
|
|
112
118
|
}
|
|
113
119
|
function and(...conditions) {
|
|
114
120
|
return condLeaf({ kind: "and", predicates: conditions.map((c) => c.predicate) });
|
|
@@ -118,26 +124,52 @@ function count(entity2) {
|
|
|
118
124
|
is: (n) => condLeaf({ entity: entity2.name, kind: "count", value: n })
|
|
119
125
|
};
|
|
120
126
|
}
|
|
121
|
-
function
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
127
|
+
function branch(name) {
|
|
128
|
+
const close = (condition) => (consequence) => {
|
|
129
|
+
const resolved = toPredicate(consequence);
|
|
130
|
+
if (containsWhen(resolved)) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`branch "${name}" nests a when() inside its consequence \u2014 whens are one level deep; split the scenario into another workflow`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return { __branch: true, branch: { condition, consequence: resolved, name } };
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
expect: close(void 0),
|
|
139
|
+
if: (condition) => ({
|
|
140
|
+
expect: close(condition.predicate)
|
|
141
|
+
})
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function when(...branches) {
|
|
145
|
+
if (branches.length === 0) {
|
|
146
|
+
throw new Error("when() needs at least one branch(...)");
|
|
147
|
+
}
|
|
148
|
+
const rows = branches.map((input) => input.branch);
|
|
149
|
+
const fallbacks = rows.filter((row2) => row2.condition == null);
|
|
150
|
+
if (fallbacks.length > 1) {
|
|
151
|
+
throw new Error("when() allows one unconditional branch \u2014 give the others .if(...) conditions");
|
|
152
|
+
}
|
|
153
|
+
if (fallbacks.length === 1 && rows.at(-1)?.condition != null) {
|
|
154
|
+
throw new Error("when() requires the unconditional branch to come last");
|
|
136
155
|
}
|
|
137
|
-
|
|
156
|
+
const dup = rows.find((row2, index) => rows.findIndex((r) => r.name === row2.name) !== index);
|
|
157
|
+
if (dup != null) {
|
|
158
|
+
throw new Error(`when() branch name "${dup.name}" is used twice \u2014 branch names must be unique`);
|
|
159
|
+
}
|
|
160
|
+
return leaf({ branches: rows, kind: "when" });
|
|
138
161
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
162
|
+
function containsWhen(predicate) {
|
|
163
|
+
if (predicate.kind === "when") {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
if (predicate.kind === "not") {
|
|
167
|
+
return containsWhen(predicate.predicate);
|
|
168
|
+
}
|
|
169
|
+
if (predicate.kind === "and") {
|
|
170
|
+
return predicate.predicates.some((p) => containsWhen(p));
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
141
173
|
}
|
|
142
174
|
|
|
143
175
|
// src/util.ts
|
|
@@ -676,14 +708,17 @@ function assertPredicateScoped(predicate, given) {
|
|
|
676
708
|
if (predicate.kind !== "when") {
|
|
677
709
|
return;
|
|
678
710
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
711
|
+
predicate.branches.forEach((row2) => {
|
|
712
|
+
const names = row2.condition == null ? [] : conditionSingletons(row2.condition);
|
|
713
|
+
names.forEach((name) => {
|
|
714
|
+
if (!given.has(name)) {
|
|
715
|
+
throw new Error(
|
|
716
|
+
`when() conditions on singleton "${name}", which is not in the workflow's given \u2014 add ${name}.of(...) to given`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
assertPredicateScoped(row2.consequence, given);
|
|
685
721
|
});
|
|
686
|
-
assertPredicateScoped(predicate.consequence, given);
|
|
687
722
|
}
|
|
688
723
|
function conditionSingletons(predicate) {
|
|
689
724
|
if (predicate.kind === "singleton") {
|
|
@@ -802,11 +837,10 @@ function predicateBindings(predicate) {
|
|
|
802
837
|
return predicateBindings(predicate.predicate);
|
|
803
838
|
}
|
|
804
839
|
case "when": {
|
|
805
|
-
return [
|
|
806
|
-
...predicateBindings(
|
|
807
|
-
...predicateBindings(
|
|
808
|
-
|
|
809
|
-
];
|
|
840
|
+
return predicate.branches.flatMap((row2) => [
|
|
841
|
+
...row2.condition == null ? [] : predicateBindings(row2.condition),
|
|
842
|
+
...predicateBindings(row2.consequence)
|
|
843
|
+
]);
|
|
810
844
|
}
|
|
811
845
|
case "and": {
|
|
812
846
|
return predicate.predicates.flatMap((p) => predicateBindings(p));
|
|
@@ -958,9 +992,11 @@ function resolvePredicate(predicate, ctx) {
|
|
|
958
992
|
case "when": {
|
|
959
993
|
return {
|
|
960
994
|
...predicate,
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
995
|
+
branches: predicate.branches.map((row2) => ({
|
|
996
|
+
condition: row2.condition == null ? void 0 : resolveCondition(row2.condition, ctx),
|
|
997
|
+
consequence: resolvePredicate(row2.consequence, ctx),
|
|
998
|
+
name: row2.name
|
|
999
|
+
}))
|
|
964
1000
|
};
|
|
965
1001
|
}
|
|
966
1002
|
case "and": {
|
|
@@ -1004,9 +1040,34 @@ function whereMap(map, ctx) {
|
|
|
1004
1040
|
function resolveWhere(value2, ctx) {
|
|
1005
1041
|
return isWithin2(value2) ? { ...value2, selection: { ...value2.selection, where: whereMap(value2.selection.where, ctx) } } : resolveValue(value2, ctx);
|
|
1006
1042
|
}
|
|
1043
|
+
function resolveCondition(condition, ctx) {
|
|
1044
|
+
switch (condition.kind) {
|
|
1045
|
+
case "singleton": {
|
|
1046
|
+
return {
|
|
1047
|
+
...condition,
|
|
1048
|
+
assertion: {
|
|
1049
|
+
...condition.assertion,
|
|
1050
|
+
value: resolveValue(condition.assertion.value, ctx)
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
case "count": {
|
|
1055
|
+
return condition;
|
|
1056
|
+
}
|
|
1057
|
+
case "not": {
|
|
1058
|
+
return { ...condition, predicate: resolveCondition(condition.predicate, ctx) };
|
|
1059
|
+
}
|
|
1060
|
+
case "and": {
|
|
1061
|
+
return {
|
|
1062
|
+
...condition,
|
|
1063
|
+
predicates: condition.predicates.map((p) => resolveCondition(p, ctx))
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1007
1068
|
|
|
1008
|
-
// src/
|
|
1009
|
-
function
|
|
1069
|
+
// src/workflow.ts
|
|
1070
|
+
function workflow(intent, fn) {
|
|
1010
1071
|
const sourcePath = captureSourcePath();
|
|
1011
1072
|
if (fn == null) {
|
|
1012
1073
|
return { spec: stubSpec(intent, sourcePath) };
|
|
@@ -1024,6 +1085,7 @@ function test(intent, fn) {
|
|
|
1024
1085
|
sourcePath,
|
|
1025
1086
|
steps: final.steps,
|
|
1026
1087
|
stub: false,
|
|
1088
|
+
tests: [],
|
|
1027
1089
|
world: final.world
|
|
1028
1090
|
}
|
|
1029
1091
|
};
|
|
@@ -1040,23 +1102,24 @@ function stubSpec(intent, sourcePath) {
|
|
|
1040
1102
|
sourcePath,
|
|
1041
1103
|
steps: [],
|
|
1042
1104
|
stub: true,
|
|
1105
|
+
tests: [],
|
|
1043
1106
|
world: []
|
|
1044
1107
|
};
|
|
1045
1108
|
}
|
|
1046
|
-
var
|
|
1109
|
+
var WORKFLOWS_ANCHOR_PATTERN = /[/\\]\.ripplo[/\\]workflows[/\\]([^):]+?)(?::\d+:\d+\)?)?$/;
|
|
1047
1110
|
function captureSourcePath() {
|
|
1048
1111
|
const stack = new Error("capture").stack;
|
|
1049
1112
|
if (stack == null) {
|
|
1050
1113
|
return void 0;
|
|
1051
1114
|
}
|
|
1052
|
-
const match = stack.split("\n").map((line) =>
|
|
1115
|
+
const match = stack.split("\n").map((line) => WORKFLOWS_ANCHOR_PATTERN.exec(line)).find((m) => m != null);
|
|
1053
1116
|
const captured = match?.[1];
|
|
1054
1117
|
return captured == null ? void 0 : captured.replaceAll("\\", "/");
|
|
1055
1118
|
}
|
|
1056
1119
|
function slugify(intent) {
|
|
1057
1120
|
const slug = intent.toLowerCase().replaceAll(/[^a-z0-9]+/g, " ").trim().split(" ").join("-");
|
|
1058
1121
|
if (slug.length === 0) {
|
|
1059
|
-
throw new Error(`
|
|
1122
|
+
throw new Error(`workflow intent "${intent}" slugifies to an empty string`);
|
|
1060
1123
|
}
|
|
1061
1124
|
return slug;
|
|
1062
1125
|
}
|
|
@@ -1357,6 +1420,32 @@ var withinSchema = z2.object({
|
|
|
1357
1420
|
selection: selectionSchema
|
|
1358
1421
|
});
|
|
1359
1422
|
var wait = z2.union([budgetSchema, z2.undefined()]).optional().transform((value2) => value2);
|
|
1423
|
+
var singletonPredicateSchema = z2.object({
|
|
1424
|
+
assertion: singletonAssertionSchema,
|
|
1425
|
+
kind: z2.literal("singleton"),
|
|
1426
|
+
singleton: z2.string().min(1),
|
|
1427
|
+
wait
|
|
1428
|
+
});
|
|
1429
|
+
var countPredicateSchema = z2.object({
|
|
1430
|
+
entity: z2.string().min(1),
|
|
1431
|
+
kind: z2.literal("count"),
|
|
1432
|
+
value: z2.number().int().nonnegative()
|
|
1433
|
+
});
|
|
1434
|
+
var conditionSchema = z2.lazy(
|
|
1435
|
+
() => z2.discriminatedUnion("kind", [
|
|
1436
|
+
singletonPredicateSchema,
|
|
1437
|
+
countPredicateSchema,
|
|
1438
|
+
z2.object({ kind: z2.literal("not"), predicate: conditionSchema }),
|
|
1439
|
+
z2.object({ kind: z2.literal("and"), predicates: z2.array(conditionSchema) })
|
|
1440
|
+
])
|
|
1441
|
+
);
|
|
1442
|
+
var whenBranchSchema = z2.lazy(
|
|
1443
|
+
() => z2.object({
|
|
1444
|
+
condition: z2.union([conditionSchema, z2.undefined()]).optional().transform((value2) => value2),
|
|
1445
|
+
consequence: predicateSchema,
|
|
1446
|
+
name: z2.string().min(1)
|
|
1447
|
+
})
|
|
1448
|
+
);
|
|
1360
1449
|
var predicateSchema = z2.lazy(
|
|
1361
1450
|
() => z2.discriminatedUnion("kind", [
|
|
1362
1451
|
z2.object({ kind: z2.literal("visible"), locator: locatorSchema, wait }),
|
|
@@ -1365,12 +1454,7 @@ var predicateSchema = z2.lazy(
|
|
|
1365
1454
|
z2.object({ kind: z2.literal("focused"), locator: locatorSchema, wait }),
|
|
1366
1455
|
z2.object({ kind: z2.literal("value"), locator: locatorSchema, value: stringValueSchema, wait }),
|
|
1367
1456
|
z2.object({ kind: z2.literal("text"), locator: locatorSchema, value: stringValueSchema, wait }),
|
|
1368
|
-
|
|
1369
|
-
assertion: singletonAssertionSchema,
|
|
1370
|
-
kind: z2.literal("singleton"),
|
|
1371
|
-
singleton: z2.string().min(1),
|
|
1372
|
-
wait
|
|
1373
|
-
}),
|
|
1457
|
+
singletonPredicateSchema,
|
|
1374
1458
|
z2.object({
|
|
1375
1459
|
kind: z2.literal("browser"),
|
|
1376
1460
|
name: browserSingletonSchema,
|
|
@@ -1386,17 +1470,8 @@ var predicateSchema = z2.lazy(
|
|
|
1386
1470
|
}),
|
|
1387
1471
|
z2.object({ kind: z2.literal("not"), predicate: predicateSchema }),
|
|
1388
1472
|
z2.object({ kind: z2.literal("and"), predicates: z2.array(predicateSchema) }),
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
kind: z2.literal("count"),
|
|
1392
|
-
value: z2.number().int().nonnegative()
|
|
1393
|
-
}),
|
|
1394
|
-
z2.object({
|
|
1395
|
-
condition: predicateSchema,
|
|
1396
|
-
consequence: predicateSchema,
|
|
1397
|
-
kind: z2.literal("when"),
|
|
1398
|
-
otherwise: z2.union([predicateSchema, z2.undefined()]).optional().transform((value2) => value2)
|
|
1399
|
-
})
|
|
1473
|
+
countPredicateSchema,
|
|
1474
|
+
z2.object({ branches: z2.array(whenBranchSchema), kind: z2.literal("when") })
|
|
1400
1475
|
])
|
|
1401
1476
|
);
|
|
1402
1477
|
|
|
@@ -1497,7 +1572,19 @@ var absenceSchema = z4.object({
|
|
|
1497
1572
|
entity: z4.string().min(1),
|
|
1498
1573
|
where: z4.record(z4.string().min(1), setValueSchema)
|
|
1499
1574
|
});
|
|
1500
|
-
var
|
|
1575
|
+
var resolvedTestSchema = z4.object({
|
|
1576
|
+
absent: z4.array(absenceSchema).default([]),
|
|
1577
|
+
exclusive: z4.array(z4.string().min(1)).default([]),
|
|
1578
|
+
intent: z4.string().min(1),
|
|
1579
|
+
name: z4.string().min(1),
|
|
1580
|
+
params: z4.record(z4.string().min(1), paramSchema),
|
|
1581
|
+
singletons: z4.record(z4.string().min(1), setValueSchema).default({}),
|
|
1582
|
+
slug: z4.string().min(1),
|
|
1583
|
+
steps: z4.array(stepSchema).default([]),
|
|
1584
|
+
workflow: z4.string().min(1),
|
|
1585
|
+
world: z4.array(setupSchema).default([])
|
|
1586
|
+
});
|
|
1587
|
+
var workflowSchema = z4.object({
|
|
1501
1588
|
absent: z4.array(absenceSchema).default([]),
|
|
1502
1589
|
exclusive: z4.array(z4.string().min(1)).default([]),
|
|
1503
1590
|
intent: z4.string().min(1),
|
|
@@ -1508,6 +1595,7 @@ var testSchema = z4.object({
|
|
|
1508
1595
|
sourcePath: z4.string().min(1).optional(),
|
|
1509
1596
|
steps: z4.array(stepSchema).default([]),
|
|
1510
1597
|
stub: z4.boolean().default(false),
|
|
1598
|
+
tests: z4.array(resolvedTestSchema).default([]),
|
|
1511
1599
|
world: z4.array(setupSchema).default([])
|
|
1512
1600
|
});
|
|
1513
1601
|
var fixtureEntrySchema = z4.object({
|
|
@@ -1518,8 +1606,8 @@ var lockfileSchema = z4.object({
|
|
|
1518
1606
|
entities: z4.array(entitySchemaSchema),
|
|
1519
1607
|
fixtures: z4.record(z4.string().min(1), fixtureEntrySchema).default({}),
|
|
1520
1608
|
singletons: z4.array(singletonSchemaSchema).default([]),
|
|
1521
|
-
|
|
1522
|
-
|
|
1609
|
+
valueSpaces: z4.array(valueSpaceSchema),
|
|
1610
|
+
workflows: z4.array(workflowSchema)
|
|
1523
1611
|
});
|
|
1524
1612
|
var lockfileCodec = defineCodec({ name: "ripplo-lockfile", schema: lockfileSchema });
|
|
1525
1613
|
|
|
@@ -1531,6 +1619,9 @@ var stepDescriptorSchema = z5.object({
|
|
|
1531
1619
|
target: z5.string(),
|
|
1532
1620
|
value: z5.string()
|
|
1533
1621
|
});
|
|
1622
|
+
function slugify2(name) {
|
|
1623
|
+
return name.toLowerCase().replaceAll(/[^a-z0-9]/g, "-").split("-").filter((part) => part.length > 0).join("-");
|
|
1624
|
+
}
|
|
1534
1625
|
|
|
1535
1626
|
// ../spec/src/session.ts
|
|
1536
1627
|
import { z as z6 } from "zod";
|
|
@@ -1618,17 +1709,491 @@ function mountClientEngine(ripplo, impls, { enabled: enabled2 }) {
|
|
|
1618
1709
|
Reflect.set(globalThis, CLIENT_MOUNT_KEY, createClientEngine(ripplo, impls));
|
|
1619
1710
|
}
|
|
1620
1711
|
|
|
1712
|
+
// src/expand.ts
|
|
1713
|
+
var MAX_CANDIDATES = 4096;
|
|
1714
|
+
function singletonValuesOf(singletons) {
|
|
1715
|
+
return new Map(
|
|
1716
|
+
singletons.flatMap((singleton2) => {
|
|
1717
|
+
const space = singleton2.valueSpaces.find((s) => s.name === singleton2.schema.valueSpace);
|
|
1718
|
+
return space?.values == null ? [] : [[singleton2.schema.name, space.values]];
|
|
1719
|
+
})
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
function expandWorkflow(workflow2, options) {
|
|
1723
|
+
if (workflow2.stub) {
|
|
1724
|
+
return { ...workflow2, tests: [] };
|
|
1725
|
+
}
|
|
1726
|
+
assertUniqueBranchNames(workflow2);
|
|
1727
|
+
const targets = collectTargets(workflow2.steps);
|
|
1728
|
+
const whenIds = new Map(targets.map((target, index) => [target.when, index]));
|
|
1729
|
+
const sims = proposeCandidates(workflow2, options).map((candidate) => simulate(workflow2, candidate)).filter((sim) => sim != null);
|
|
1730
|
+
if (targets.length === 0) {
|
|
1731
|
+
return { ...workflow2, tests: [requireMainTest(workflow2, sims)] };
|
|
1732
|
+
}
|
|
1733
|
+
const { tests } = targets.reduce(
|
|
1734
|
+
(acc, target) => coverTarget({ acc, sims, target, whenIds, workflow: workflow2 }),
|
|
1735
|
+
{ covered: /* @__PURE__ */ new Set(), tests: [] }
|
|
1736
|
+
);
|
|
1737
|
+
return { ...workflow2, tests };
|
|
1738
|
+
}
|
|
1739
|
+
function resolveValidTest(workflow2, sim, name) {
|
|
1740
|
+
const test = resolveTest(workflow2, sim, name);
|
|
1741
|
+
return danglingRefHeads(test).length === 0 ? test : null;
|
|
1742
|
+
}
|
|
1743
|
+
function danglingRefHeads(test) {
|
|
1744
|
+
const known = /* @__PURE__ */ new Set([
|
|
1745
|
+
...test.world.map((setup) => setup.as),
|
|
1746
|
+
...Object.keys(test.params),
|
|
1747
|
+
...test.steps.flatMap((step) => createdAliasesIn(step))
|
|
1748
|
+
]);
|
|
1749
|
+
return [...new Set(collectRefObjects(test).map((ref) => headOf(ref)))].filter(
|
|
1750
|
+
(head) => !known.has(head)
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
function createdAliasesIn(step) {
|
|
1754
|
+
return step.expect.flatMap((predicate) => {
|
|
1755
|
+
if (predicate.kind !== "state" || predicate.assertion.kind === "deleted") {
|
|
1756
|
+
return [];
|
|
1757
|
+
}
|
|
1758
|
+
return [predicate.assertion.as];
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
function coverTarget({ acc, sims, target, whenIds, workflow: workflow2 }) {
|
|
1762
|
+
const key2 = choiceKey(whenIds, target.when, target.index);
|
|
1763
|
+
if (acc.covered.has(key2)) {
|
|
1764
|
+
return { covered: acc.covered, tests: acc.tests };
|
|
1765
|
+
}
|
|
1766
|
+
const picked = sims.filter((s) => s.choices.get(target.when) === target.index).reduce((found, s) => {
|
|
1767
|
+
if (found != null) {
|
|
1768
|
+
return found;
|
|
1769
|
+
}
|
|
1770
|
+
const test = resolveValidTest(workflow2, s, target.name);
|
|
1771
|
+
return test == null ? null : { sim: s, test };
|
|
1772
|
+
}, null);
|
|
1773
|
+
if (picked == null) {
|
|
1774
|
+
throw new Error(
|
|
1775
|
+
`workflow "${workflow2.name}": branch "${target.name}" is unreachable \u2014 no combination of optional entities and singleton values reaches it`
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
const reached = [...picked.sim.choices].map(([when2, index]) => choiceKey(whenIds, when2, index));
|
|
1779
|
+
return {
|
|
1780
|
+
covered: /* @__PURE__ */ new Set([...acc.covered, ...reached]),
|
|
1781
|
+
tests: [...acc.tests, picked.test]
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
function choiceKey(whenIds, when2, index) {
|
|
1785
|
+
const id2 = whenIds.get(when2);
|
|
1786
|
+
if (id2 == null) {
|
|
1787
|
+
throw new Error("internal: when node missing from target index");
|
|
1788
|
+
}
|
|
1789
|
+
return `${String(id2)}:${String(index)}`;
|
|
1790
|
+
}
|
|
1791
|
+
function requireMainTest(workflow2, sims) {
|
|
1792
|
+
const test = sims.reduce(
|
|
1793
|
+
(found, sim) => found ?? resolveValidTest(workflow2, sim, "main"),
|
|
1794
|
+
null
|
|
1795
|
+
);
|
|
1796
|
+
if (test == null) {
|
|
1797
|
+
throw new Error(
|
|
1798
|
+
`workflow "${workflow2.name}": no valid seed found \u2014 steps may reference optional entities no candidate provides, or a condition mentions values the solver cannot pin`
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1801
|
+
return test;
|
|
1802
|
+
}
|
|
1803
|
+
function assertUniqueBranchNames(workflow2) {
|
|
1804
|
+
const names = collectTargets(workflow2.steps).map((target) => target.name);
|
|
1805
|
+
const dup = names.find((name, index) => names.indexOf(name) !== index);
|
|
1806
|
+
if (dup != null) {
|
|
1807
|
+
throw new Error(
|
|
1808
|
+
`workflow "${workflow2.name}": branch name "${dup}" is used twice \u2014 branch names must be unique within a workflow`
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
function collectTargets(steps) {
|
|
1813
|
+
return steps.flatMap((step) => step.expect.flatMap((predicate) => targetsIn(predicate)));
|
|
1814
|
+
}
|
|
1815
|
+
function targetsIn(predicate) {
|
|
1816
|
+
if (predicate.kind === "not") {
|
|
1817
|
+
return targetsIn(predicate.predicate);
|
|
1818
|
+
}
|
|
1819
|
+
if (predicate.kind === "and") {
|
|
1820
|
+
return predicate.predicates.flatMap((p) => targetsIn(p));
|
|
1821
|
+
}
|
|
1822
|
+
if (predicate.kind !== "when") {
|
|
1823
|
+
return [];
|
|
1824
|
+
}
|
|
1825
|
+
return predicate.branches.flatMap((row2, index) => [
|
|
1826
|
+
{ index, name: row2.name, when: predicate },
|
|
1827
|
+
...targetsIn(row2.consequence)
|
|
1828
|
+
]);
|
|
1829
|
+
}
|
|
1830
|
+
function proposeCandidates(workflow2, options) {
|
|
1831
|
+
const subsets = maybeSubsets(workflow2.maybe);
|
|
1832
|
+
const pinSets = pinAssignments(workflow2, options);
|
|
1833
|
+
if (subsets.length * pinSets.length > MAX_CANDIDATES) {
|
|
1834
|
+
throw new Error(
|
|
1835
|
+
`workflow "${workflow2.name}": too many optional entities and singleton values to solve \u2014 split the workflow or convert maybe(...) entities to of(...)`
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
return subsets.flatMap((maybes) => pinSets.map((pins) => ({ maybes, pins })));
|
|
1839
|
+
}
|
|
1840
|
+
function maybeSubsets(maybe) {
|
|
1841
|
+
const masks = Array.from({ length: 1 << maybe.length }, (_, mask) => mask);
|
|
1842
|
+
return masks.map((mask) => ({ mask, size: maybe.filter((_, i) => (mask & 1 << i) !== 0).length })).toSorted((a, b) => a.size === b.size ? a.mask - b.mask : a.size - b.size).map(({ mask }) => maybe.filter((_, i) => (mask & 1 << i) !== 0));
|
|
1843
|
+
}
|
|
1844
|
+
function pinAssignments(workflow2, options) {
|
|
1845
|
+
const domains = pinDomains(workflow2, options);
|
|
1846
|
+
return Object.entries(domains).reduce(
|
|
1847
|
+
(acc, [param, domain]) => acc.flatMap((pins) => domain.map((value2) => ({ ...pins, [param]: value2 }))),
|
|
1848
|
+
[{}]
|
|
1849
|
+
);
|
|
1850
|
+
}
|
|
1851
|
+
function pinDomains(workflow2, options) {
|
|
1852
|
+
const literals = conditionSingletonLiterals(workflow2.steps);
|
|
1853
|
+
return Object.entries(workflow2.singletons).reduce((acc, [name, value2]) => {
|
|
1854
|
+
const param = paramRefOf(value2);
|
|
1855
|
+
const compared = literals.get(name);
|
|
1856
|
+
if (param == null || compared == null) {
|
|
1857
|
+
return acc;
|
|
1858
|
+
}
|
|
1859
|
+
const enumValues = options.singletonValues.get(name);
|
|
1860
|
+
return { ...acc, [param]: withComplements(compared, enumValues) };
|
|
1861
|
+
}, {});
|
|
1862
|
+
}
|
|
1863
|
+
function conditionSingletonLiterals(steps) {
|
|
1864
|
+
const pairs = steps.flatMap(
|
|
1865
|
+
(step) => step.expect.flatMap((predicate) => conditionLiteralsIn(predicate))
|
|
1866
|
+
);
|
|
1867
|
+
return pairs.reduce(
|
|
1868
|
+
(acc, [name, literal]) => new Map([...acc, [name, [.../* @__PURE__ */ new Set([...acc.get(name) ?? [], literal])]]]),
|
|
1869
|
+
/* @__PURE__ */ new Map()
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
function conditionLiteralsIn(predicate) {
|
|
1873
|
+
if (predicate.kind === "not") {
|
|
1874
|
+
return conditionLiteralsIn(predicate.predicate);
|
|
1875
|
+
}
|
|
1876
|
+
if (predicate.kind === "and") {
|
|
1877
|
+
return predicate.predicates.flatMap((p) => conditionLiteralsIn(p));
|
|
1878
|
+
}
|
|
1879
|
+
if (predicate.kind !== "when") {
|
|
1880
|
+
return [];
|
|
1881
|
+
}
|
|
1882
|
+
return predicate.branches.flatMap((row2) => [
|
|
1883
|
+
...row2.condition == null ? [] : singletonLiteralsInCondition(row2.condition),
|
|
1884
|
+
...conditionLiteralsIn(row2.consequence)
|
|
1885
|
+
]);
|
|
1886
|
+
}
|
|
1887
|
+
function singletonLiteralsInCondition(condition) {
|
|
1888
|
+
switch (condition.kind) {
|
|
1889
|
+
case "singleton": {
|
|
1890
|
+
const value2 = condition.assertion.value;
|
|
1891
|
+
return value2 == null || typeof value2 !== "object" ? [[condition.singleton, value2]] : [];
|
|
1892
|
+
}
|
|
1893
|
+
case "count": {
|
|
1894
|
+
return [];
|
|
1895
|
+
}
|
|
1896
|
+
case "not": {
|
|
1897
|
+
return singletonLiteralsInCondition(condition.predicate);
|
|
1898
|
+
}
|
|
1899
|
+
case "and": {
|
|
1900
|
+
return condition.predicates.flatMap((p) => singletonLiteralsInCondition(p));
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
function paramRefOf(value2) {
|
|
1905
|
+
if (value2 == null || typeof value2 !== "object" || !("ref" in value2)) {
|
|
1906
|
+
return void 0;
|
|
1907
|
+
}
|
|
1908
|
+
return value2.ref.includes(".") ? void 0 : value2.ref;
|
|
1909
|
+
}
|
|
1910
|
+
function withComplements(values, enumValues) {
|
|
1911
|
+
if (enumValues != null) {
|
|
1912
|
+
return [.../* @__PURE__ */ new Set([...values, ...enumValues])];
|
|
1913
|
+
}
|
|
1914
|
+
const complements = values.flatMap((value2) => {
|
|
1915
|
+
if (typeof value2 === "boolean") {
|
|
1916
|
+
return [!value2];
|
|
1917
|
+
}
|
|
1918
|
+
if (typeof value2 === "number") {
|
|
1919
|
+
return [value2 + 1];
|
|
1920
|
+
}
|
|
1921
|
+
if (typeof value2 === "string") {
|
|
1922
|
+
return [syntheticDistinct(value2, values)];
|
|
1923
|
+
}
|
|
1924
|
+
return [];
|
|
1925
|
+
});
|
|
1926
|
+
return [.../* @__PURE__ */ new Set([...values, ...complements])];
|
|
1927
|
+
}
|
|
1928
|
+
function syntheticDistinct(seed, taken) {
|
|
1929
|
+
const candidate = `${seed}-alt`;
|
|
1930
|
+
return taken.includes(candidate) ? syntheticDistinct(candidate, taken) : candidate;
|
|
1931
|
+
}
|
|
1932
|
+
function simulate(workflow2, candidate) {
|
|
1933
|
+
const initial = {
|
|
1934
|
+
choices: /* @__PURE__ */ new Map(),
|
|
1935
|
+
state: {
|
|
1936
|
+
rows: [...workflow2.world, ...candidate.maybes],
|
|
1937
|
+
singles: seedSingles(workflow2.singletons, candidate.pins)
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1940
|
+
const folded = workflow2.steps.reduce(
|
|
1941
|
+
(acc, step) => acc == null ? null : stepSim(acc, step),
|
|
1942
|
+
initial
|
|
1943
|
+
);
|
|
1944
|
+
return folded == null ? null : { candidate, choices: folded.choices };
|
|
1945
|
+
}
|
|
1946
|
+
function seedSingles(singletons, pins) {
|
|
1947
|
+
return Object.fromEntries(
|
|
1948
|
+
Object.entries(singletons).map(([name, value2]) => {
|
|
1949
|
+
const param = paramRefOf(value2);
|
|
1950
|
+
return [name, param != null && param in pins ? pins[param] ?? null : value2];
|
|
1951
|
+
})
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
function stepSim(acc, step) {
|
|
1955
|
+
const rows = step.expect.filter((p) => p.kind === "state").reduce((current, effect) => applyEffect(current, effect), acc.state.rows);
|
|
1956
|
+
const preWhens = { rows, singles: acc.state.singles };
|
|
1957
|
+
const whens = step.expect.filter((p) => p.kind === "when");
|
|
1958
|
+
const resolved = whens.reduce((current, when2) => current == null ? null : foldWhen(current, when2, preWhens), {
|
|
1959
|
+
choices: acc.choices,
|
|
1960
|
+
sets: {}
|
|
1961
|
+
});
|
|
1962
|
+
if (resolved == null) {
|
|
1963
|
+
return null;
|
|
1964
|
+
}
|
|
1965
|
+
const immediate = singletonSets(step.expect);
|
|
1966
|
+
return {
|
|
1967
|
+
choices: resolved.choices,
|
|
1968
|
+
state: { rows, singles: { ...preWhens.singles, ...immediate, ...resolved.sets } }
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
function foldWhen(acc, when2, state) {
|
|
1972
|
+
const picked = pickBranch(when2, state);
|
|
1973
|
+
if (picked === "unknown") {
|
|
1974
|
+
return null;
|
|
1975
|
+
}
|
|
1976
|
+
if (picked == null) {
|
|
1977
|
+
return acc;
|
|
1978
|
+
}
|
|
1979
|
+
const row2 = when2.branches[picked];
|
|
1980
|
+
if (row2 == null) {
|
|
1981
|
+
return acc;
|
|
1982
|
+
}
|
|
1983
|
+
const choices = new Map([...acc.choices, [when2, picked]]);
|
|
1984
|
+
const consequence = row2.consequence;
|
|
1985
|
+
if (consequence.kind === "when") {
|
|
1986
|
+
return foldWhen({ choices, sets: acc.sets }, consequence, state);
|
|
1987
|
+
}
|
|
1988
|
+
return { choices, sets: { ...acc.sets, ...singletonSets([consequence]) } };
|
|
1989
|
+
}
|
|
1990
|
+
function pickBranch(when2, state) {
|
|
1991
|
+
return when2.branches.reduce((picked, row2, index) => {
|
|
1992
|
+
if (picked !== void 0) {
|
|
1993
|
+
return picked;
|
|
1994
|
+
}
|
|
1995
|
+
if (row2.condition == null) {
|
|
1996
|
+
return index;
|
|
1997
|
+
}
|
|
1998
|
+
const holds = evalCondition(row2.condition, state);
|
|
1999
|
+
if (holds === "unknown") {
|
|
2000
|
+
return "unknown";
|
|
2001
|
+
}
|
|
2002
|
+
return holds ? index : void 0;
|
|
2003
|
+
}, void 0);
|
|
2004
|
+
}
|
|
2005
|
+
function evalCondition(condition, state) {
|
|
2006
|
+
switch (condition.kind) {
|
|
2007
|
+
case "count": {
|
|
2008
|
+
return state.rows.filter((row2) => row2.entity === condition.entity).length === condition.value;
|
|
2009
|
+
}
|
|
2010
|
+
case "singleton": {
|
|
2011
|
+
return compareValues(state.singles[condition.singleton], condition.assertion.value);
|
|
2012
|
+
}
|
|
2013
|
+
case "not": {
|
|
2014
|
+
return negate(evalCondition(condition.predicate, state));
|
|
2015
|
+
}
|
|
2016
|
+
case "and": {
|
|
2017
|
+
return conjoin(condition.predicates.map((p) => evalCondition(p, state)));
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
function compareValues(seeded, wanted) {
|
|
2022
|
+
if (seeded === void 0) {
|
|
2023
|
+
return "unknown";
|
|
2024
|
+
}
|
|
2025
|
+
const seededRef = isRefValue(seeded);
|
|
2026
|
+
const wantedRef = isRefValue(wanted);
|
|
2027
|
+
if (seededRef != null && wantedRef != null) {
|
|
2028
|
+
return seededRef === wantedRef ? true : "unknown";
|
|
2029
|
+
}
|
|
2030
|
+
if (seededRef != null || wantedRef != null || isTemplateValue(seeded) || isTemplateValue(wanted)) {
|
|
2031
|
+
return "unknown";
|
|
2032
|
+
}
|
|
2033
|
+
return sameSetValue(seeded, wanted);
|
|
2034
|
+
}
|
|
2035
|
+
function isRefValue(value2) {
|
|
2036
|
+
return value2 != null && typeof value2 === "object" && "ref" in value2 ? value2.ref : void 0;
|
|
2037
|
+
}
|
|
2038
|
+
function isTemplateValue(value2) {
|
|
2039
|
+
return value2 != null && typeof value2 === "object" && "template" in value2;
|
|
2040
|
+
}
|
|
2041
|
+
function negate(value2) {
|
|
2042
|
+
return value2 === "unknown" ? "unknown" : !value2;
|
|
2043
|
+
}
|
|
2044
|
+
function conjoin(values) {
|
|
2045
|
+
if (values.includes(false)) {
|
|
2046
|
+
return false;
|
|
2047
|
+
}
|
|
2048
|
+
return values.every((v2) => v2 === true) ? true : "unknown";
|
|
2049
|
+
}
|
|
2050
|
+
function singletonSets(predicates) {
|
|
2051
|
+
return Object.fromEntries(
|
|
2052
|
+
predicates.flatMap((predicate) => {
|
|
2053
|
+
if (predicate.kind === "singleton") {
|
|
2054
|
+
return [[predicate.singleton, predicate.assertion.value]];
|
|
2055
|
+
}
|
|
2056
|
+
return [];
|
|
2057
|
+
})
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
function applyEffect(rows, effect) {
|
|
2061
|
+
if (effect.assertion.kind === "created") {
|
|
2062
|
+
return [
|
|
2063
|
+
...rows,
|
|
2064
|
+
{ as: effect.assertion.as, entity: effect.entity, set: effect.assertion.props }
|
|
2065
|
+
];
|
|
2066
|
+
}
|
|
2067
|
+
if (effect.assertion.kind === "deleted") {
|
|
2068
|
+
return rows.filter((row2) => row2.entity !== effect.entity || !matchesKey(row2, effect.key, rows));
|
|
2069
|
+
}
|
|
2070
|
+
return rows;
|
|
2071
|
+
}
|
|
2072
|
+
function matchesKey(row2, key2, rows) {
|
|
2073
|
+
return Object.entries(key2).every(([field2, want]) => matchesField({ field: field2, row: row2, rows, want }));
|
|
2074
|
+
}
|
|
2075
|
+
function matchesField({ field: field2, row: row2, rows, want }) {
|
|
2076
|
+
if (isWithinValue(want)) {
|
|
2077
|
+
const candidates = rows.filter(
|
|
2078
|
+
(candidate) => candidate.entity === want.selection.entity && matchesKey(candidate, want.selection.where, rows)
|
|
2079
|
+
).map((candidate) => fieldValue(candidate, want.field));
|
|
2080
|
+
const own = fieldValue(row2, field2);
|
|
2081
|
+
return candidates.some((candidate) => valuesEqual(own, candidate));
|
|
2082
|
+
}
|
|
2083
|
+
return valuesEqual(fieldValue(row2, field2), want);
|
|
2084
|
+
}
|
|
2085
|
+
function isWithinValue(value2) {
|
|
2086
|
+
return value2 != null && typeof value2 === "object" && "kind" in value2;
|
|
2087
|
+
}
|
|
2088
|
+
function fieldValue(row2, field2) {
|
|
2089
|
+
return row2.set[field2] ?? { ref: `${row2.as}.${field2}` };
|
|
2090
|
+
}
|
|
2091
|
+
function valuesEqual(a, b) {
|
|
2092
|
+
const aRef = isRefValue(a);
|
|
2093
|
+
const bRef = isRefValue(b);
|
|
2094
|
+
if (aRef != null || bRef != null) {
|
|
2095
|
+
return aRef === bRef;
|
|
2096
|
+
}
|
|
2097
|
+
if (isTemplateValue(a) || isTemplateValue(b)) {
|
|
2098
|
+
return false;
|
|
2099
|
+
}
|
|
2100
|
+
return sameSetValue(a, b);
|
|
2101
|
+
}
|
|
2102
|
+
function resolveTest(workflow2, sim, name) {
|
|
2103
|
+
const steps = workflow2.steps.map((step) => ({
|
|
2104
|
+
action: step.action,
|
|
2105
|
+
expect: step.expect.flatMap((predicate) => resolvePredicate2(predicate, sim.choices))
|
|
2106
|
+
}));
|
|
2107
|
+
const world = [...workflow2.world, ...sim.candidate.maybes];
|
|
2108
|
+
const singletons = seedSingles(workflow2.singletons, sim.candidate.pins);
|
|
2109
|
+
const used = usedRefHeads({ singletons, steps, workflow: workflow2, world });
|
|
2110
|
+
return {
|
|
2111
|
+
absent: workflow2.absent,
|
|
2112
|
+
exclusive: workflow2.exclusive,
|
|
2113
|
+
intent: workflow2.intent,
|
|
2114
|
+
name,
|
|
2115
|
+
params: Object.fromEntries(
|
|
2116
|
+
Object.entries(workflow2.params).filter(([param]) => used.has(param))
|
|
2117
|
+
),
|
|
2118
|
+
singletons,
|
|
2119
|
+
slug: slugify2(name),
|
|
2120
|
+
steps,
|
|
2121
|
+
workflow: workflow2.name,
|
|
2122
|
+
world
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
function resolvePredicate2(predicate, choices) {
|
|
2126
|
+
if (predicate.kind === "not") {
|
|
2127
|
+
const inner = resolvePredicate2(predicate.predicate, choices);
|
|
2128
|
+
return inner.map((p) => ({ kind: "not", predicate: p }));
|
|
2129
|
+
}
|
|
2130
|
+
if (predicate.kind === "and") {
|
|
2131
|
+
return [
|
|
2132
|
+
{
|
|
2133
|
+
kind: "and",
|
|
2134
|
+
predicates: predicate.predicates.flatMap((p) => resolvePredicate2(p, choices))
|
|
2135
|
+
}
|
|
2136
|
+
];
|
|
2137
|
+
}
|
|
2138
|
+
if (predicate.kind !== "when") {
|
|
2139
|
+
return [predicate];
|
|
2140
|
+
}
|
|
2141
|
+
const picked = choices.get(predicate);
|
|
2142
|
+
const row2 = picked == null ? void 0 : predicate.branches[picked];
|
|
2143
|
+
return row2 == null ? [] : resolvePredicate2(row2.consequence, choices);
|
|
2144
|
+
}
|
|
2145
|
+
function usedRefHeads({ singletons, steps, workflow: workflow2, world }) {
|
|
2146
|
+
const values = [
|
|
2147
|
+
...world.flatMap((setup) => Object.values(setup.set)),
|
|
2148
|
+
...workflow2.absent.flatMap((absence) => Object.values(absence.where)),
|
|
2149
|
+
...Object.values(singletons),
|
|
2150
|
+
...steps.flatMap((step) => stepRefValues(step))
|
|
2151
|
+
];
|
|
2152
|
+
return new Set(values.flatMap((value2) => refStrings(value2)).map((ref) => headOf(ref)));
|
|
2153
|
+
}
|
|
2154
|
+
function stepRefValues(step) {
|
|
2155
|
+
return collectRefObjects(step).map((ref) => ({ ref }));
|
|
2156
|
+
}
|
|
2157
|
+
function collectRefObjects(node) {
|
|
2158
|
+
if (Array.isArray(node)) {
|
|
2159
|
+
return node.flatMap((item) => collectRefObjects(item));
|
|
2160
|
+
}
|
|
2161
|
+
if (node == null || typeof node !== "object") {
|
|
2162
|
+
return [];
|
|
2163
|
+
}
|
|
2164
|
+
if ("ref" in node && typeof node.ref === "string" && Object.keys(node).length === 1) {
|
|
2165
|
+
return [node.ref];
|
|
2166
|
+
}
|
|
2167
|
+
return Object.values(node).flatMap((value2) => collectRefObjects(value2));
|
|
2168
|
+
}
|
|
2169
|
+
function refStrings(value2) {
|
|
2170
|
+
const ref = isRefValue(value2);
|
|
2171
|
+
if (ref != null) {
|
|
2172
|
+
return [ref];
|
|
2173
|
+
}
|
|
2174
|
+
if (value2 != null && typeof value2 === "object" && "template" in value2) {
|
|
2175
|
+
return value2.template.flatMap((segment) => typeof segment === "string" ? [] : [segment.ref]);
|
|
2176
|
+
}
|
|
2177
|
+
return [];
|
|
2178
|
+
}
|
|
2179
|
+
function headOf(ref) {
|
|
2180
|
+
const dot = ref.indexOf(".");
|
|
2181
|
+
return dot === -1 ? ref : ref.slice(0, dot);
|
|
2182
|
+
}
|
|
2183
|
+
|
|
1621
2184
|
// src/build.ts
|
|
1622
2185
|
function buildLockfile(input) {
|
|
1623
2186
|
assertUniqueNames(input.entities);
|
|
1624
2187
|
const lockfile = lockfileSchema.parse({
|
|
1625
2188
|
entities: input.entities.map((handle) => handle.schema),
|
|
1626
2189
|
singletons: input.singletons.map((handle) => handle.schema),
|
|
1627
|
-
tests: input.tests.map((ripploTest) => ripploTest.spec),
|
|
1628
2190
|
valueSpaces: dedupeByName([
|
|
1629
2191
|
...input.entities.flatMap((handle) => handle.valueSpaces),
|
|
1630
2192
|
...input.singletons.flatMap((handle) => handle.valueSpaces)
|
|
1631
|
-
])
|
|
2193
|
+
]),
|
|
2194
|
+
workflows: input.workflows.map(
|
|
2195
|
+
(ripploWorkflow) => expandWorkflow(ripploWorkflow.spec, { singletonValues: singletonValuesOf(input.singletons) })
|
|
2196
|
+
)
|
|
1632
2197
|
});
|
|
1633
2198
|
assertNoContradictions(lockfile);
|
|
1634
2199
|
return lockfile;
|
|
@@ -1645,20 +2210,20 @@ function dedupeByName(spaces) {
|
|
|
1645
2210
|
return [...byName.values()];
|
|
1646
2211
|
}
|
|
1647
2212
|
function assertNoContradictions(lockfile) {
|
|
1648
|
-
lockfile.
|
|
1649
|
-
assertNoContradiction(
|
|
1650
|
-
assertNoDanglingRefs(
|
|
2213
|
+
lockfile.workflows.forEach((workflow2) => {
|
|
2214
|
+
assertNoContradiction(workflow2);
|
|
2215
|
+
assertNoDanglingRefs(workflow2);
|
|
1651
2216
|
});
|
|
1652
2217
|
}
|
|
1653
|
-
function assertNoContradiction(
|
|
1654
|
-
const setups = [...
|
|
1655
|
-
|
|
2218
|
+
function assertNoContradiction(workflow2) {
|
|
2219
|
+
const setups = [...workflow2.world, ...workflow2.maybe];
|
|
2220
|
+
workflow2.absent.forEach((absence) => {
|
|
1656
2221
|
const clash = setups.find(
|
|
1657
2222
|
(setup) => setup.entity === absence.entity && whereMatches(absence.where, setup.set)
|
|
1658
2223
|
);
|
|
1659
2224
|
if (clash != null) {
|
|
1660
2225
|
throw new Error(
|
|
1661
|
-
`test "${
|
|
2226
|
+
`test "${workflow2.name}": creates a "${absence.entity}" ("${clash.as}") that a none(${absence.entity}, \u2026) in its world forbids`
|
|
1662
2227
|
);
|
|
1663
2228
|
}
|
|
1664
2229
|
});
|
|
@@ -1669,19 +2234,19 @@ function whereMatches(where, set) {
|
|
|
1669
2234
|
function sameValue(a, b) {
|
|
1670
2235
|
return b !== void 0 && sameSetValue(a, b);
|
|
1671
2236
|
}
|
|
1672
|
-
function assertNoDanglingRefs(
|
|
1673
|
-
const aliases = new Set([...
|
|
1674
|
-
const paramKeys = new Set(Object.keys(
|
|
2237
|
+
function assertNoDanglingRefs(workflow2) {
|
|
2238
|
+
const aliases = new Set([...workflow2.world, ...workflow2.maybe].map((setup) => setup.as));
|
|
2239
|
+
const paramKeys = new Set(Object.keys(workflow2.params));
|
|
1675
2240
|
const fieldSets = [
|
|
1676
|
-
...
|
|
1677
|
-
...
|
|
1678
|
-
...
|
|
2241
|
+
...workflow2.world.map((setup) => setup.set),
|
|
2242
|
+
...workflow2.maybe.map((setup) => setup.set),
|
|
2243
|
+
...workflow2.absent.map((absence) => absence.where)
|
|
1679
2244
|
];
|
|
1680
2245
|
fieldSets.forEach((set) => {
|
|
1681
|
-
assertSetRefs(
|
|
2246
|
+
assertSetRefs(workflow2.name, set, aliases, paramKeys);
|
|
1682
2247
|
});
|
|
1683
2248
|
}
|
|
1684
|
-
function assertSetRefs(
|
|
2249
|
+
function assertSetRefs(workflowName, set, aliases, paramKeys) {
|
|
1685
2250
|
Object.values(set).forEach((value2) => {
|
|
1686
2251
|
if (!isRef(value2) || paramKeys.has(value2.ref)) {
|
|
1687
2252
|
return;
|
|
@@ -1693,7 +2258,7 @@ function assertSetRefs(testName, set, aliases, paramKeys) {
|
|
|
1693
2258
|
const aliasPath = value2.ref.slice(0, lastDot);
|
|
1694
2259
|
if (!aliases.has(aliasPath)) {
|
|
1695
2260
|
throw new Error(
|
|
1696
|
-
`test "${
|
|
2261
|
+
`test "${workflowName}": ref "${value2.ref}" points at unknown alias "${aliasPath}"`
|
|
1697
2262
|
);
|
|
1698
2263
|
}
|
|
1699
2264
|
});
|
|
@@ -1709,10 +2274,10 @@ function createRipplo(input) {
|
|
|
1709
2274
|
lockfile: buildLockfile({
|
|
1710
2275
|
entities: input.entities,
|
|
1711
2276
|
singletons: input.singletons,
|
|
1712
|
-
|
|
2277
|
+
workflows: input.workflows
|
|
1713
2278
|
}),
|
|
1714
2279
|
singletons: input.singletons,
|
|
1715
|
-
|
|
2280
|
+
workflows: input.workflows
|
|
1716
2281
|
};
|
|
1717
2282
|
}
|
|
1718
2283
|
export {
|
|
@@ -1723,6 +2288,7 @@ export {
|
|
|
1723
2288
|
and,
|
|
1724
2289
|
arbitrary,
|
|
1725
2290
|
banner,
|
|
2291
|
+
branch,
|
|
1726
2292
|
button,
|
|
1727
2293
|
cell,
|
|
1728
2294
|
changed,
|
|
@@ -1784,7 +2350,6 @@ export {
|
|
|
1784
2350
|
table,
|
|
1785
2351
|
tablist,
|
|
1786
2352
|
tabpanel,
|
|
1787
|
-
test,
|
|
1788
2353
|
testId,
|
|
1789
2354
|
text,
|
|
1790
2355
|
textbox,
|
|
@@ -1799,5 +2364,6 @@ export {
|
|
|
1799
2364
|
viewport,
|
|
1800
2365
|
visible,
|
|
1801
2366
|
when,
|
|
1802
|
-
within
|
|
2367
|
+
within,
|
|
2368
|
+
workflow
|
|
1803
2369
|
};
|