@ripplo/testing 0.7.11 → 0.7.13

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 CHANGED
@@ -47,16 +47,14 @@ If you're unsure, it's backend. A value that's cached in the browser but owned b
47
47
  ### `field(options)`
48
48
 
49
49
  ```ts
50
- field({ value: v.email() }); // required, stable — Ripplo checks the app's value matches what the test expected
50
+ field({ value: v.email() }); // required — Ripplo checks the app's value matches what the test expected
51
51
  field({ value: v.word(), optional: true }); // nullable; `null` is a valid set/assert value
52
- field({ value: v.number({ min: 0, max: 100 }), stable: false }); // adopt-only, drift not checked
53
52
  ```
54
53
 
55
- | option | meaning |
56
- | ---------- | -------------------------------------------------------------------------------------------------------------------- |
57
- | `value` | the value-space (`v.*`) — required |
58
- | `optional` | `true` ⇒ the field is nullable (`null` allowed in `of`/`updated`/assertions); Ripplo treats `null ≡ absent` |
59
- | `stable` | default `true` (Ripplo checks the app's value matches what the test expected). `false` = adopt-only, drift unchecked |
54
+ | option | meaning |
55
+ | ---------- | ----------------------------------------------------------------------------------------------------------- |
56
+ | `value` | the value-space (`v.*`) — required |
57
+ | `optional` | `true` ⇒ the field is nullable (`null` allowed in `of`/`updated`/assertions); Ripplo treats `null ≡ absent` |
60
58
 
61
59
  ### Identity: `id()` and `key(options)`
62
60
 
@@ -165,19 +163,20 @@ The compiler enumerates one concrete test per `when` branch at `ripplo compile`/
165
163
 
166
164
  Each returns a step; chain `.expect(...assertions)` to assert after it runs.
167
165
 
168
- | action | signature | example |
169
- | ---------- | ------------------------------ | ---------------------------------------- |
170
- | `goto` | `` goto`/path/${handle.id}` `` | `` goto`/projects/${p.id}` `` |
171
- | `click` | `click(locator)` | `click(button("Save"))` |
172
- | `dblclick` | `dblclick(locator)` | `dblclick(testId\`row-${r.id}\`)` |
173
- | `fill` | `fill(locator, value)` | `fill(textbox("Name"), "Acme")` |
174
- | `clear` | `clear(locator)` | `clear(textbox("Name"))` |
175
- | `select` | `select(locator, value)` | `select(combobox("Plan"), "pro")` |
176
- | `check` | `check(locator)` | `check(checkbox("Agree"))` |
177
- | `uncheck` | `uncheck(locator)` | `uncheck(checkbox("Agree"))` |
178
- | `hover` | `hover(locator)` | `hover(button("More"))` |
179
- | `upload` | `upload(locator, files)` | `upload(testId\`avatar\`, ["face.png"])` |
180
- | `press` | `press(key, locator?)` | `press("Enter", textbox("Search"))` |
166
+ | action | signature | example |
167
+ | ---------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
168
+ | `goto` | `` goto`/path/${handle.id}` `` | `` goto`/projects/${p.id}` `` |
169
+ | `click` | `click(locator)` | `click(button("Save"))` |
170
+ | `dblclick` | `dblclick(locator)` | `dblclick(testId\`row-${r.id}\`)` |
171
+ | `fill` | `fill(locator, value)` | `fill(textbox("Name"), "Acme")` |
172
+ | `clear` | `clear(locator)` | `clear(textbox("Name"))` |
173
+ | `select` | `select(locator, value)` | `select(combobox("Plan"), "pro")` |
174
+ | | drives native `<select>` and ARIA comboboxes (react-select, etc.); `value` is the visible option text | `select(combobox("Timezone"), "Europe/London")` |
175
+ | `check` | `check(locator)` | `check(checkbox("Agree"))` |
176
+ | `uncheck` | `uncheck(locator)` | `uncheck(checkbox("Agree"))` |
177
+ | `hover` | `hover(locator)` | `hover(button("More"))` |
178
+ | `upload` | `upload(locator, files)` | `upload(testId\`avatar\`, ["face.png"])` |
179
+ | `press` | `press(key, locator?)` | `press("Enter", textbox("Search"))` |
181
180
 
182
181
  `fill`/`select`/`value`/`text` accept a literal string or a `Binding<string>` (e.g. `arbitrary(...)` or a handle field). `goto`/`testId` are template literals — interpolate handle ids directly.
183
182
 
@@ -232,6 +231,7 @@ Used inside a step's `.expect(...)`.
232
231
  | `disabled` | `disabled(locator)` | element is disabled |
233
232
  | `enabled` | `enabled(locator)` | element is enabled |
234
233
  | `focused` | `focused(locator)` | element has focus |
234
+ | `checked` | `checked(locator)` | checkbox, radio, or switch is checked |
235
235
  | `value` | `value(locator, string\|binding)` | input's value equals |
236
236
  | `text` | `text(locator, string\|binding)` | element's text equals |
237
237
  | `not` | `not(assertion)` | negation — `not(visible(role("dialog")))` |
@@ -143,7 +143,6 @@ declare const propSpecSchema: z.ZodObject<{
143
143
  eventual: "eventual";
144
144
  }>>;
145
145
  optional: z.ZodBoolean;
146
- stable: z.ZodBoolean;
147
146
  type: z.ZodEnum<{
148
147
  string: "string";
149
148
  number: "number";
@@ -171,7 +170,6 @@ declare const entitySchemaSchema: z.ZodObject<{
171
170
  eventual: "eventual";
172
171
  }>>;
173
172
  optional: z.ZodBoolean;
174
- stable: z.ZodBoolean;
175
173
  type: z.ZodEnum<{
176
174
  string: "string";
177
175
  number: "number";
@@ -591,7 +589,6 @@ declare const lockfileSchema: z.ZodObject<{
591
589
  eventual: "eventual";
592
590
  }>>;
593
591
  optional: z.ZodBoolean;
594
- stable: z.ZodBoolean;
595
592
  type: z.ZodEnum<{
596
593
  string: "string";
597
594
  number: "number";
@@ -1012,6 +1009,7 @@ declare function visible(locator: Locator): LeafPredicate;
1012
1009
  declare function disabled(locator: Locator): LeafPredicate;
1013
1010
  declare function enabled(locator: Locator): LeafPredicate;
1014
1011
  declare function focused(locator: Locator): LeafPredicate;
1012
+ declare function checked(locator: Locator): LeafPredicate;
1015
1013
  declare function value(locator: Locator, binding: Binding<string> | string): LeafPredicate;
1016
1014
  declare function text(locator: Locator, binding: Binding<string> | string): LeafPredicate;
1017
1015
  declare function not(input: ConditionInput): ConditionPredicate;
@@ -1029,11 +1027,11 @@ interface BranchInput {
1029
1027
  readonly branch: WhenBranch;
1030
1028
  }
1031
1029
  interface NamedBranch {
1032
- readonly expect: (consequence: PredicateInput) => BranchInput;
1030
+ readonly expect: (...consequences: ReadonlyArray<PredicateInput>) => BranchInput;
1033
1031
  readonly if: (condition: ConditionInput) => ConditionedBranch;
1034
1032
  }
1035
1033
  interface ConditionedBranch {
1036
- readonly expect: (consequence: PredicateInput) => BranchInput;
1034
+ readonly expect: (...consequences: ReadonlyArray<PredicateInput>) => BranchInput;
1037
1035
  }
1038
1036
  declare function branch(name: string): NamedBranch;
1039
1037
  declare function when(...branches: ReadonlyArray<BranchInput>): LeafPredicate;
@@ -1075,7 +1073,6 @@ interface PropBuilder<T extends Primitive, Opt extends boolean = boolean> {
1075
1073
  type Mods = Partial<{
1076
1074
  readonly consistency: ConsistencyClass;
1077
1075
  readonly optional: boolean;
1078
- readonly stable: boolean;
1079
1076
  }>;
1080
1077
  type IsOptional = {
1081
1078
  readonly optional: true;
@@ -1292,4 +1289,4 @@ interface Engine {
1292
1289
  }
1293
1290
  declare function createEngine<R extends DeclSource>(ripplo: R, impls: EngineImpls<R, "backend">, teardown: Teardown): Engine;
1294
1291
 
1295
- export { field as $, type AnyBinding as A, type Binding as B, type Capture as C, type DeclSource as D, type Engine as E, type FieldHandle as F, type GivenItem as G, type Handle as H, type SingletonConfig as I, type SingletonImpl as J, type SingletonValue as K, type Locator as L, type WithinMatcher as M, type NamedBranch as N, and as O, type PredicateInput as P, branch as Q, type Row as R, type Step as S, changed as T, type UniqueNames as U, count as V, type Workflow as W, createEngine as X, disabled as Y, enabled as Z, entity as _, type EntityList as a, focused as a0, id as a1, key as a2, not as a3, singleton as a4, text as a5, v as a6, value as a7, visible as a8, when as a9, within as aa, type SingletonList as b, type Lockfile as c, type BrowserSingleton as d, type LeafPredicate as e, type Primitive as f, type Primitive$1 as g, type SingletonDef as h, type AbsenceHandle as i, type BranchInput as j, CLIENT_MOUNT_KEY as k, CLIENT_SEED_KEY as l, type ConditionInput as m, type ConditionPredicate as n, type ConditionedBranch as o, type CountCondition as p, type DuplicateEntityName as q, type EngineError as r, type EngineImpls as s, type EngineRead as t, type EntityDef as u, type EntityHandle as v, type EntityImpl as w, type FieldOptions as x, type Fields as y, type Selection as z };
1292
+ export { entity as $, type AnyBinding as A, type Binding as B, type Capture as C, type DeclSource as D, type Engine as E, type FieldHandle as F, type GivenItem as G, type Handle as H, type SingletonConfig as I, type SingletonImpl as J, type SingletonValue as K, type Locator as L, type WithinMatcher as M, type NamedBranch as N, and as O, type PredicateInput as P, branch as Q, type Row as R, type Step as S, changed as T, type UniqueNames as U, checked as V, type Workflow as W, count as X, createEngine as Y, disabled as Z, enabled as _, type EntityList as a, field as a0, focused as a1, id as a2, key as a3, not as a4, singleton as a5, text as a6, v as a7, value as a8, visible as a9, when as aa, within as ab, type SingletonList as b, type Lockfile as c, type BrowserSingleton as d, type LeafPredicate as e, type Primitive as f, type Primitive$1 as g, type SingletonDef as h, type AbsenceHandle as i, type BranchInput as j, CLIENT_MOUNT_KEY as k, CLIENT_SEED_KEY as l, type ConditionInput as m, type ConditionPredicate as n, type ConditionedBranch as o, type CountCondition as p, type DuplicateEntityName as q, type EngineError as r, type EngineImpls as s, type EngineRead as t, type EntityDef as u, type EntityHandle as v, type EntityImpl as w, type FieldOptions as x, type Fields as y, type Selection as z };
package/dist/express.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Router } from 'express';
2
- import { E as Engine } from './engine-Dv3trweR.js';
2
+ import { E as Engine } from './engine-D-X3F_hc.js';
3
3
  import 'neverthrow';
4
4
  import 'zod';
5
5
 
package/dist/express.js CHANGED
@@ -71,7 +71,6 @@ var valueSpaceSchema = z.object({
71
71
  var propSpecSchema = z.object({
72
72
  consistency: consistencyClassSchema.default("strict"),
73
73
  optional: z.boolean(),
74
- stable: z.boolean(),
75
74
  type: primitiveTypeSchema,
76
75
  valueSpace: z.string().min(1).optional()
77
76
  });
@@ -146,7 +145,7 @@ var conditionSchema = z2.lazy(
146
145
  var whenBranchSchema = z2.lazy(
147
146
  () => z2.object({
148
147
  condition: z2.union([conditionSchema, z2.undefined()]).optional().transform((value) => value),
149
- consequence: predicateSchema,
148
+ consequence: z2.array(predicateSchema),
150
149
  name: z2.string().min(1)
151
150
  })
152
151
  );
@@ -156,6 +155,7 @@ var predicateSchema = z2.lazy(
156
155
  z2.object({ kind: z2.literal("disabled"), locator: locatorSchema, wait }),
157
156
  z2.object({ kind: z2.literal("enabled"), locator: locatorSchema, wait }),
158
157
  z2.object({ kind: z2.literal("focused"), locator: locatorSchema, wait }),
158
+ z2.object({ kind: z2.literal("checked"), locator: locatorSchema, wait }),
159
159
  z2.object({ kind: z2.literal("value"), locator: locatorSchema, value: stringValueSchema, wait }),
160
160
  z2.object({ kind: z2.literal("text"), locator: locatorSchema, value: stringValueSchema, wait }),
161
161
  singletonPredicateSchema,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as Capture, S as Step, P as PredicateInput, L as Locator, B as Binding, A as AnyBinding, G as GivenItem, W as Workflow, a as EntityList, b as SingletonList, c as Lockfile, U as UniqueNames, d as BrowserSingleton, e as LeafPredicate, f as Primitive, F as FieldHandle, D as DeclSource, R as Row, g as Primitive$1, h as SingletonDef } from './engine-Dv3trweR.js';
2
- export { i as AbsenceHandle, j as BranchInput, k as CLIENT_MOUNT_KEY, l as CLIENT_SEED_KEY, m as ConditionInput, n as ConditionPredicate, o as ConditionedBranch, p as CountCondition, q as DuplicateEntityName, E as Engine, r as EngineError, s as EngineImpls, t as EngineRead, u as EntityDef, v as EntityHandle, w as EntityImpl, x as FieldOptions, y as Fields, H as Handle, N as NamedBranch, z as Selection, I as SingletonConfig, J as SingletonImpl, K as SingletonValue, M as WithinMatcher, O as and, Q as branch, T as changed, V as count, X as createEngine, Y as disabled, Z as enabled, _ as entity, $ as field, a0 as focused, a1 as id, a2 as key, a3 as not, a4 as singleton, a5 as text, a6 as v, a7 as value, a8 as visible, a9 as when, aa as within } from './engine-Dv3trweR.js';
1
+ import { C as Capture, S as Step, P as PredicateInput, L as Locator, B as Binding, A as AnyBinding, G as GivenItem, W as Workflow, a as EntityList, b as SingletonList, c as Lockfile, U as UniqueNames, d as BrowserSingleton, e as LeafPredicate, f as Primitive, F as FieldHandle, D as DeclSource, R as Row, g as Primitive$1, h as SingletonDef } from './engine-D-X3F_hc.js';
2
+ export { i as AbsenceHandle, j as BranchInput, k as CLIENT_MOUNT_KEY, l as CLIENT_SEED_KEY, m as ConditionInput, n as ConditionPredicate, o as ConditionedBranch, p as CountCondition, q as DuplicateEntityName, E as Engine, r as EngineError, s as EngineImpls, t as EngineRead, u as EntityDef, v as EntityHandle, w as EntityImpl, x as FieldOptions, y as Fields, H as Handle, N as NamedBranch, z as Selection, I as SingletonConfig, J as SingletonImpl, K as SingletonValue, M as WithinMatcher, O as and, Q as branch, T as changed, V as checked, X as count, Y as createEngine, Z as disabled, _ as enabled, $ as entity, a0 as field, a1 as focused, a2 as id, a3 as key, a4 as not, a5 as singleton, a6 as text, a7 as v, a8 as value, a9 as visible, aa as when, ab as within } from './engine-D-X3F_hc.js';
3
3
  import 'neverthrow';
4
4
  import 'zod';
5
5
 
package/dist/index.js CHANGED
@@ -98,6 +98,9 @@ function enabled(locator) {
98
98
  function focused(locator) {
99
99
  return leaf({ kind: "focused", locator, wait: void 0 });
100
100
  }
101
+ function checked(locator) {
102
+ return leaf({ kind: "checked", locator, wait: void 0 });
103
+ }
101
104
  function value(locator, binding) {
102
105
  return leaf({ kind: "value", locator, value: binding, wait: void 0 });
103
106
  }
@@ -125,9 +128,9 @@ function count(entity2) {
125
128
  };
126
129
  }
127
130
  function branch(name) {
128
- const close = (condition) => (consequence) => {
129
- const resolved = toPredicate(consequence);
130
- if (containsWhen(resolved)) {
131
+ const close = (condition) => (...consequences) => {
132
+ const resolved = consequences.map((consequence) => toPredicate(consequence));
133
+ if (resolved.some((predicate) => containsWhen(predicate))) {
131
134
  throw new Error(
132
135
  `branch "${name}" nests a when() inside its consequence \u2014 whens are one level deep; split the scenario into another workflow`
133
136
  );
@@ -270,7 +273,6 @@ function field(options) {
270
273
  spec: {
271
274
  consistency: options.consistency ?? "strict",
272
275
  optional: options.optional ?? false,
273
- stable: options.stable ?? true,
274
276
  type: options.value.primitive
275
277
  },
276
278
  valueSpace: options.value
@@ -280,7 +282,7 @@ function id() {
280
282
  return {
281
283
  idKind: "surrogate",
282
284
  prop: propBuilder({
283
- spec: { consistency: "strict", optional: false, stable: true, type: "string" },
285
+ spec: { consistency: "strict", optional: false, type: "string" },
284
286
  valueSpace: void 0
285
287
  })
286
288
  };
@@ -292,7 +294,6 @@ function key(options) {
292
294
  spec: {
293
295
  consistency: "strict",
294
296
  optional: false,
295
- stable: true,
296
297
  type: options.value.primitive
297
298
  },
298
299
  valueSpace: options.value
@@ -598,6 +599,20 @@ function stepBuilder(action, expected, captures) {
598
599
  };
599
600
  }
600
601
 
602
+ // src/coherence.ts
603
+ function assertEntityCoherence(entities) {
604
+ const counts = entities.reduce(
605
+ (acc, d) => new Map([...acc, [d.entity, (acc.get(d.entity) ?? 0) + 1]]),
606
+ /* @__PURE__ */ new Map()
607
+ );
608
+ const over = entities.find((d) => d.kind === "only" && (counts.get(d.entity) ?? 0) > 1);
609
+ if (over != null) {
610
+ throw new Error(
611
+ `entity "${over.entity}" is declared only() but seeded ${String(counts.get(over.entity) ?? 0)} times \u2014 only() means a single exclusive instance; use of() for multiple instances`
612
+ );
613
+ }
614
+ }
615
+
601
616
  // src/finalize.ts
602
617
  function finalize(body) {
603
618
  const descriptors = [...new Set(body.given.map((item) => item.__entity))];
@@ -611,6 +626,7 @@ function finalize(body) {
611
626
  )
612
627
  );
613
628
  assertNoMaybeRefs({ absences, entities, singletonStates });
629
+ assertEntityCoherence(entities);
614
630
  const captures = body.steps.flatMap((builder) => builder.captures);
615
631
  assertScopedConditions(body.steps, new Set(singletonStates.map((s) => s.singleton)));
616
632
  const aliases = assignAliases([...entities, ...captures.map((c) => c.descriptor)]);
@@ -717,7 +733,9 @@ function assertPredicateScoped(predicate, given) {
717
733
  );
718
734
  }
719
735
  });
720
- assertPredicateScoped(row2.consequence, given);
736
+ row2.consequence.forEach((consequence) => {
737
+ assertPredicateScoped(consequence, given);
738
+ });
721
739
  });
722
740
  }
723
741
  function conditionSingletons(predicate) {
@@ -812,7 +830,8 @@ function predicateBindings(predicate) {
812
830
  case "visible":
813
831
  case "disabled":
814
832
  case "enabled":
815
- case "focused": {
833
+ case "focused":
834
+ case "checked": {
816
835
  return locatorBindings(predicate.locator);
817
836
  }
818
837
  case "value":
@@ -839,7 +858,7 @@ function predicateBindings(predicate) {
839
858
  case "when": {
840
859
  return predicate.branches.flatMap((row2) => [
841
860
  ...row2.condition == null ? [] : predicateBindings(row2.condition),
842
- ...predicateBindings(row2.consequence)
861
+ ...row2.consequence.flatMap((consequence) => predicateBindings(consequence))
843
862
  ]);
844
863
  }
845
864
  case "and": {
@@ -959,7 +978,8 @@ function resolvePredicate(predicate, ctx) {
959
978
  case "visible":
960
979
  case "disabled":
961
980
  case "enabled":
962
- case "focused": {
981
+ case "focused":
982
+ case "checked": {
963
983
  return { ...predicate, locator: resolveLocator(predicate.locator, ctx) };
964
984
  }
965
985
  case "value":
@@ -994,7 +1014,7 @@ function resolvePredicate(predicate, ctx) {
994
1014
  ...predicate,
995
1015
  branches: predicate.branches.map((row2) => ({
996
1016
  condition: row2.condition == null ? void 0 : resolveCondition(row2.condition, ctx),
997
- consequence: resolvePredicate(row2.consequence, ctx),
1017
+ consequence: row2.consequence.map((consequence) => resolvePredicate(consequence, ctx)),
998
1018
  name: row2.name
999
1019
  }))
1000
1020
  };
@@ -1366,7 +1386,6 @@ var valueSpaceSchema = z.object({
1366
1386
  var propSpecSchema = z.object({
1367
1387
  consistency: consistencyClassSchema.default("strict"),
1368
1388
  optional: z.boolean(),
1369
- stable: z.boolean(),
1370
1389
  type: primitiveTypeSchema,
1371
1390
  valueSpace: z.string().min(1).optional()
1372
1391
  });
@@ -1441,7 +1460,7 @@ var conditionSchema = z2.lazy(
1441
1460
  var whenBranchSchema = z2.lazy(
1442
1461
  () => z2.object({
1443
1462
  condition: z2.union([conditionSchema, z2.undefined()]).optional().transform((value2) => value2),
1444
- consequence: predicateSchema,
1463
+ consequence: z2.array(predicateSchema),
1445
1464
  name: z2.string().min(1)
1446
1465
  })
1447
1466
  );
@@ -1451,6 +1470,7 @@ var predicateSchema = z2.lazy(
1451
1470
  z2.object({ kind: z2.literal("disabled"), locator: locatorSchema, wait }),
1452
1471
  z2.object({ kind: z2.literal("enabled"), locator: locatorSchema, wait }),
1453
1472
  z2.object({ kind: z2.literal("focused"), locator: locatorSchema, wait }),
1473
+ z2.object({ kind: z2.literal("checked"), locator: locatorSchema, wait }),
1454
1474
  z2.object({ kind: z2.literal("value"), locator: locatorSchema, value: stringValueSchema, wait }),
1455
1475
  z2.object({ kind: z2.literal("text"), locator: locatorSchema, value: stringValueSchema, wait }),
1456
1476
  singletonPredicateSchema,
@@ -1707,6 +1727,54 @@ function mountClientEngine(ripplo, impls, { enabled: enabled2 }) {
1707
1727
  Reflect.set(globalThis, CLIENT_MOUNT_KEY, createClientEngine(ripplo, impls));
1708
1728
  }
1709
1729
 
1730
+ // src/expand-refs.ts
1731
+ function usedRefHeads({
1732
+ singletons,
1733
+ steps,
1734
+ workflow: workflow2,
1735
+ world
1736
+ }) {
1737
+ const values = [
1738
+ ...world.flatMap((setup) => Object.values(setup.set)),
1739
+ ...workflow2.absent.flatMap((absence) => Object.values(absence.where)),
1740
+ ...Object.values(singletons),
1741
+ ...steps.flatMap((step) => stepRefValues(step))
1742
+ ];
1743
+ return new Set(values.flatMap((value2) => refStrings(value2)).map((ref) => headOf(ref)));
1744
+ }
1745
+ function collectRefObjects(node) {
1746
+ if (Array.isArray(node)) {
1747
+ return node.flatMap((item) => collectRefObjects(item));
1748
+ }
1749
+ if (node == null || typeof node !== "object") {
1750
+ return [];
1751
+ }
1752
+ if ("ref" in node && typeof node.ref === "string" && Object.keys(node).length === 1) {
1753
+ return [node.ref];
1754
+ }
1755
+ return Object.values(node).flatMap((value2) => collectRefObjects(value2));
1756
+ }
1757
+ function isRefValue(value2) {
1758
+ return value2 != null && typeof value2 === "object" && "ref" in value2 ? value2.ref : void 0;
1759
+ }
1760
+ function headOf(ref) {
1761
+ const dot = ref.indexOf(".");
1762
+ return dot === -1 ? ref : ref.slice(0, dot);
1763
+ }
1764
+ function stepRefValues(step) {
1765
+ return collectRefObjects(step).map((ref) => ({ ref }));
1766
+ }
1767
+ function refStrings(value2) {
1768
+ const ref = isRefValue(value2);
1769
+ if (ref != null) {
1770
+ return [ref];
1771
+ }
1772
+ if (value2 != null && typeof value2 === "object" && "template" in value2) {
1773
+ return value2.template.flatMap((segment) => typeof segment === "string" ? [] : [segment.ref]);
1774
+ }
1775
+ return [];
1776
+ }
1777
+
1710
1778
  // src/expand.ts
1711
1779
  var MAX_CANDIDATES = 4096;
1712
1780
  function singletonValuesOf(singletons) {
@@ -1822,7 +1890,7 @@ function targetsIn(predicate) {
1822
1890
  }
1823
1891
  return predicate.branches.flatMap((row2, index) => [
1824
1892
  { index, name: row2.name, when: predicate },
1825
- ...targetsIn(row2.consequence)
1893
+ ...row2.consequence.flatMap((consequence) => targetsIn(consequence))
1826
1894
  ]);
1827
1895
  }
1828
1896
  function proposeCandidates(workflow2, options) {
@@ -1879,7 +1947,7 @@ function conditionLiteralsIn(predicate) {
1879
1947
  }
1880
1948
  return predicate.branches.flatMap((row2) => [
1881
1949
  ...row2.condition == null ? [] : singletonLiteralsInCondition(row2.condition),
1882
- ...conditionLiteralsIn(row2.consequence)
1950
+ ...row2.consequence.flatMap((consequence) => conditionLiteralsIn(consequence))
1883
1951
  ]);
1884
1952
  }
1885
1953
  function singletonLiteralsInCondition(condition) {
@@ -1979,11 +2047,18 @@ function foldWhen(acc, when2, state) {
1979
2047
  return acc;
1980
2048
  }
1981
2049
  const choices = new Map([...acc.choices, [when2, picked]]);
1982
- const consequence = row2.consequence;
1983
- if (consequence.kind === "when") {
1984
- return foldWhen({ choices, sets: acc.sets }, consequence, state);
1985
- }
1986
- return { choices, sets: { ...acc.sets, ...singletonSets([consequence]) } };
2050
+ return row2.consequence.reduce(
2051
+ (folded, consequence) => {
2052
+ if (folded == null) {
2053
+ return null;
2054
+ }
2055
+ if (consequence.kind === "when") {
2056
+ return foldWhen(folded, consequence, state);
2057
+ }
2058
+ return { choices: folded.choices, sets: { ...folded.sets, ...singletonSets([consequence]) } };
2059
+ },
2060
+ { choices, sets: acc.sets }
2061
+ );
1987
2062
  }
1988
2063
  function pickBranch(when2, state) {
1989
2064
  return when2.branches.reduce((picked, row2, index) => {
@@ -2030,9 +2105,6 @@ function compareValues(seeded, wanted) {
2030
2105
  }
2031
2106
  return sameSetValue(seeded, wanted);
2032
2107
  }
2033
- function isRefValue(value2) {
2034
- return value2 != null && typeof value2 === "object" && "ref" in value2 ? value2.ref : void 0;
2035
- }
2036
2108
  function isTemplateValue(value2) {
2037
2109
  return value2 != null && typeof value2 === "object" && "template" in value2;
2038
2110
  }
@@ -2138,45 +2210,7 @@ function resolvePredicate2(predicate, choices) {
2138
2210
  }
2139
2211
  const picked = choices.get(predicate);
2140
2212
  const row2 = picked == null ? void 0 : predicate.branches[picked];
2141
- return row2 == null ? [] : resolvePredicate2(row2.consequence, choices);
2142
- }
2143
- function usedRefHeads({ singletons, steps, workflow: workflow2, world }) {
2144
- const values = [
2145
- ...world.flatMap((setup) => Object.values(setup.set)),
2146
- ...workflow2.absent.flatMap((absence) => Object.values(absence.where)),
2147
- ...Object.values(singletons),
2148
- ...steps.flatMap((step) => stepRefValues(step))
2149
- ];
2150
- return new Set(values.flatMap((value2) => refStrings(value2)).map((ref) => headOf(ref)));
2151
- }
2152
- function stepRefValues(step) {
2153
- return collectRefObjects(step).map((ref) => ({ ref }));
2154
- }
2155
- function collectRefObjects(node) {
2156
- if (Array.isArray(node)) {
2157
- return node.flatMap((item) => collectRefObjects(item));
2158
- }
2159
- if (node == null || typeof node !== "object") {
2160
- return [];
2161
- }
2162
- if ("ref" in node && typeof node.ref === "string" && Object.keys(node).length === 1) {
2163
- return [node.ref];
2164
- }
2165
- return Object.values(node).flatMap((value2) => collectRefObjects(value2));
2166
- }
2167
- function refStrings(value2) {
2168
- const ref = isRefValue(value2);
2169
- if (ref != null) {
2170
- return [ref];
2171
- }
2172
- if (value2 != null && typeof value2 === "object" && "template" in value2) {
2173
- return value2.template.flatMap((segment) => typeof segment === "string" ? [] : [segment.ref]);
2174
- }
2175
- return [];
2176
- }
2177
- function headOf(ref) {
2178
- const dot = ref.indexOf(".");
2179
- return dot === -1 ? ref : ref.slice(0, dot);
2213
+ return row2 == null ? [] : row2.consequence.flatMap((c) => resolvePredicate2(c, choices));
2180
2214
  }
2181
2215
 
2182
2216
  // src/build.ts
@@ -2292,6 +2326,7 @@ export {
2292
2326
  changed,
2293
2327
  check,
2294
2328
  checkbox,
2329
+ checked,
2295
2330
  clear,
2296
2331
  click,
2297
2332
  columnheader,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ripplo/testing",
3
- "version": "0.7.11",
3
+ "version": "0.7.13",
4
4
  "description": "Typed test DSL for Ripplo — declare entities and user flows, compile to a lockfile",
5
5
  "homepage": "https://ripplo.ai",
6
6
  "license": "SEE LICENSE IN LICENSE.md",