@specverse/engines 4.1.22 → 4.1.24

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.
Files changed (74) hide show
  1. package/dist/inference/ui-contracts/index.d.ts +38 -0
  2. package/dist/inference/ui-contracts/index.d.ts.map +1 -0
  3. package/dist/inference/ui-contracts/index.js +212 -0
  4. package/dist/inference/ui-contracts/index.js.map +1 -0
  5. package/dist/inference/ui-contracts/rules/_shared.d.ts +32 -0
  6. package/dist/inference/ui-contracts/rules/_shared.d.ts.map +1 -0
  7. package/dist/inference/ui-contracts/rules/_shared.js +103 -0
  8. package/dist/inference/ui-contracts/rules/_shared.js.map +1 -0
  9. package/dist/inference/ui-contracts/rules/action-buttons-present.d.ts +21 -0
  10. package/dist/inference/ui-contracts/rules/action-buttons-present.d.ts.map +1 -0
  11. package/dist/inference/ui-contracts/rules/action-buttons-present.js +62 -0
  12. package/dist/inference/ui-contracts/rules/action-buttons-present.js.map +1 -0
  13. package/dist/inference/ui-contracts/rules/create-reflects-in-list.d.ts +22 -0
  14. package/dist/inference/ui-contracts/rules/create-reflects-in-list.d.ts.map +1 -0
  15. package/dist/inference/ui-contracts/rules/create-reflects-in-list.js +58 -0
  16. package/dist/inference/ui-contracts/rules/create-reflects-in-list.js.map +1 -0
  17. package/dist/inference/ui-contracts/rules/delete-reflects-in-list.d.ts +22 -0
  18. package/dist/inference/ui-contracts/rules/delete-reflects-in-list.d.ts.map +1 -0
  19. package/dist/inference/ui-contracts/rules/delete-reflects-in-list.js +64 -0
  20. package/dist/inference/ui-contracts/rules/delete-reflects-in-list.js.map +1 -0
  21. package/dist/inference/ui-contracts/rules/detail-view-renders.d.ts +24 -0
  22. package/dist/inference/ui-contracts/rules/detail-view-renders.d.ts.map +1 -0
  23. package/dist/inference/ui-contracts/rules/detail-view-renders.js +34 -0
  24. package/dist/inference/ui-contracts/rules/detail-view-renders.js.map +1 -0
  25. package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.d.ts +21 -0
  26. package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.d.ts.map +1 -0
  27. package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.js +63 -0
  28. package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.js.map +1 -0
  29. package/dist/inference/ui-contracts/rules/form-shows-required-indicators.d.ts +15 -0
  30. package/dist/inference/ui-contracts/rules/form-shows-required-indicators.d.ts.map +1 -0
  31. package/dist/inference/ui-contracts/rules/form-shows-required-indicators.js +38 -0
  32. package/dist/inference/ui-contracts/rules/form-shows-required-indicators.js.map +1 -0
  33. package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.d.ts +17 -0
  34. package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.d.ts.map +1 -0
  35. package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.js +39 -0
  36. package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.js.map +1 -0
  37. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts +25 -0
  38. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts.map +1 -0
  39. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js +66 -0
  40. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js.map +1 -0
  41. package/dist/inference/ui-contracts/rules/list-shows-business-columns.d.ts +17 -0
  42. package/dist/inference/ui-contracts/rules/list-shows-business-columns.d.ts.map +1 -0
  43. package/dist/inference/ui-contracts/rules/list-shows-business-columns.js +39 -0
  44. package/dist/inference/ui-contracts/rules/list-shows-business-columns.js.map +1 -0
  45. package/dist/inference/ui-contracts/rules/list-view-renders.d.ts +19 -0
  46. package/dist/inference/ui-contracts/rules/list-view-renders.d.ts.map +1 -0
  47. package/dist/inference/ui-contracts/rules/list-view-renders.js +29 -0
  48. package/dist/inference/ui-contracts/rules/list-view-renders.js.map +1 -0
  49. package/dist/inference/ui-contracts/rules/nav-has-model-entries.d.ts +20 -0
  50. package/dist/inference/ui-contracts/rules/nav-has-model-entries.d.ts.map +1 -0
  51. package/dist/inference/ui-contracts/rules/nav-has-model-entries.js +29 -0
  52. package/dist/inference/ui-contracts/rules/nav-has-model-entries.js.map +1 -0
  53. package/dist/inference/ui-contracts/test-case-types.d.ts +135 -0
  54. package/dist/inference/ui-contracts/test-case-types.d.ts.map +1 -0
  55. package/dist/inference/ui-contracts/test-case-types.js +14 -0
  56. package/dist/inference/ui-contracts/test-case-types.js.map +1 -0
  57. package/dist/inference/ui-contracts/translator.d.ts +17 -0
  58. package/dist/inference/ui-contracts/translator.d.ts.map +1 -0
  59. package/dist/inference/ui-contracts/translator.js +142 -0
  60. package/dist/inference/ui-contracts/translator.js.map +1 -0
  61. package/dist/libs/instance-factories/applications/templates/react/api-client-generator.js +5 -2
  62. package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +11 -0
  63. package/dist/libs/instance-factories/scaffolding/templates/generic/package-json-generator.js +10 -0
  64. package/dist/libs/instance-factories/views/templates/react/components-generator.js +34 -23
  65. package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +51 -81
  66. package/dist/realize/index.d.ts.map +1 -1
  67. package/dist/realize/index.js +204 -0
  68. package/dist/realize/index.js.map +1 -1
  69. package/libs/instance-factories/applications/templates/react/api-client-generator.ts +5 -2
  70. package/libs/instance-factories/applications/templates/react/vite-config-generator.ts +11 -0
  71. package/libs/instance-factories/scaffolding/templates/generic/package-json-generator.ts +13 -0
  72. package/libs/instance-factories/views/templates/react/components-generator.ts +34 -23
  73. package/libs/instance-factories/views/templates/react/hooks-generator.ts +72 -88
  74. package/package.json +1 -1
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @specverse/engines/inference/ui-contracts
3
+ *
4
+ * Spec-inferred UI contract test generation. The realize pipeline
5
+ * calls `generateContractTests(spec)` which runs every registered
6
+ * rule over the normalized spec and returns a map of filename →
7
+ * spec file content. The caller writes those to disk.
8
+ *
9
+ * Design: see docs/proposals/UI-CONTRACT-INFERENCE.md.
10
+ */
11
+ import type { NormalizedSpec } from './test-case-types.js';
12
+ export * from './test-case-types.js';
13
+ export { renderSpecFile } from './translator.js';
14
+ export interface ContractTestFile {
15
+ /** Filename relative to the contract test output directory. */
16
+ filename: string;
17
+ /** The Playwright .spec.ts contents. */
18
+ contents: string;
19
+ /** Number of individual test() blocks in the file. */
20
+ testCount: number;
21
+ }
22
+ /**
23
+ * Run every rule over the spec and render each rule's output as a
24
+ * single .spec.ts file. Rules that produce zero test cases (e.g.
25
+ * because no spec elements matched) are skipped.
26
+ */
27
+ export declare function generateContractTests(spec: NormalizedSpec): ContractTestFile[];
28
+ /**
29
+ * Convert a parsed/inferred spec (as produced by the parser or the
30
+ * inference engine) into the NormalizedSpec shape that rules consume.
31
+ *
32
+ * The parser returns components as an array with each holding
33
+ * `models`, `controllers`, etc. This function flattens to a single
34
+ * top-level view. It handles both object-keyed and array-shaped
35
+ * collections.
36
+ */
37
+ export declare function normalizeSpec(raw: any): NormalizedSpec;
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/inference/ui-contracts/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAA4B,MAAM,sBAAsB,CAAC;AAcrF,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAwBjD,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,cAAc,GAAG,gBAAgB,EAAE,CAc9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,cAAc,CA6ItD"}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * @specverse/engines/inference/ui-contracts
3
+ *
4
+ * Spec-inferred UI contract test generation. The realize pipeline
5
+ * calls `generateContractTests(spec)` which runs every registered
6
+ * rule over the normalized spec and returns a map of filename →
7
+ * spec file content. The caller writes those to disk.
8
+ *
9
+ * Design: see docs/proposals/UI-CONTRACT-INFERENCE.md.
10
+ */
11
+ import { renderSpecFile } from './translator.js';
12
+ import { navHasModelEntries, RULE_ID as NAV_HAS_MODEL_ENTRIES_RULE_ID } from './rules/nav-has-model-entries.js';
13
+ import { listViewRenders, RULE_ID as LIST_VIEW_RENDERS_RULE_ID } from './rules/list-view-renders.js';
14
+ import { detailViewRenders, RULE_ID as DETAIL_VIEW_RENDERS_RULE_ID } from './rules/detail-view-renders.js';
15
+ import { listShowsBusinessColumns, RULE_ID as LIST_SHOWS_BUSINESS_COLUMNS_RULE_ID } from './rules/list-shows-business-columns.js';
16
+ import { formShowsRequiredIndicators, RULE_ID as FORM_SHOWS_REQUIRED_INDICATORS_RULE_ID } from './rules/form-shows-required-indicators.js';
17
+ import { lifecycleStateVisibleInDetail, RULE_ID as LIFECYCLE_STATE_VISIBLE_RULE_ID } from './rules/lifecycle-state-visible-in-detail.js';
18
+ import { hasManyShowsChildrenInDetail, RULE_ID as HASMANY_SHOWS_CHILDREN_RULE_ID } from './rules/hasmany-shows-children-in-detail.js';
19
+ import { actionButtonsPresent, RULE_ID as ACTION_BUTTONS_PRESENT_RULE_ID } from './rules/action-buttons-present.js';
20
+ import { createReflectsInList, RULE_ID as CREATE_REFLECTS_RULE_ID } from './rules/create-reflects-in-list.js';
21
+ import { deleteReflectsInList, RULE_ID as DELETE_REFLECTS_RULE_ID } from './rules/delete-reflects-in-list.js';
22
+ import { evolveReflectsInList, RULE_ID as EVOLVE_REFLECTS_RULE_ID } from './rules/evolve-reflects-in-list.js';
23
+ export * from './test-case-types.js';
24
+ export { renderSpecFile } from './translator.js';
25
+ /**
26
+ * Registered rules. Order doesn't matter for output — each rule
27
+ * produces its own spec file. Adding a rule = pushing a new entry
28
+ * here + creating a file under rules/.
29
+ */
30
+ const RULES = [
31
+ // Rendering
32
+ { id: NAV_HAS_MODEL_ENTRIES_RULE_ID, rule: navHasModelEntries },
33
+ { id: LIST_VIEW_RENDERS_RULE_ID, rule: listViewRenders },
34
+ { id: DETAIL_VIEW_RENDERS_RULE_ID, rule: detailViewRenders },
35
+ // Shape
36
+ { id: LIST_SHOWS_BUSINESS_COLUMNS_RULE_ID, rule: listShowsBusinessColumns },
37
+ { id: FORM_SHOWS_REQUIRED_INDICATORS_RULE_ID, rule: formShowsRequiredIndicators },
38
+ { id: LIFECYCLE_STATE_VISIBLE_RULE_ID, rule: lifecycleStateVisibleInDetail },
39
+ { id: HASMANY_SHOWS_CHILDREN_RULE_ID, rule: hasManyShowsChildrenInDetail },
40
+ { id: ACTION_BUTTONS_PRESENT_RULE_ID, rule: actionButtonsPresent },
41
+ // State-sync
42
+ { id: CREATE_REFLECTS_RULE_ID, rule: createReflectsInList },
43
+ { id: DELETE_REFLECTS_RULE_ID, rule: deleteReflectsInList },
44
+ { id: EVOLVE_REFLECTS_RULE_ID, rule: evolveReflectsInList },
45
+ ];
46
+ /**
47
+ * Run every rule over the spec and render each rule's output as a
48
+ * single .spec.ts file. Rules that produce zero test cases (e.g.
49
+ * because no spec elements matched) are skipped.
50
+ */
51
+ export function generateContractTests(spec) {
52
+ const files = [];
53
+ for (const { id, rule } of RULES) {
54
+ const cases = rule(spec);
55
+ if (cases.length === 0)
56
+ continue;
57
+ files.push({
58
+ filename: `${id}.spec.ts`,
59
+ contents: renderSpecFile(id, cases),
60
+ testCount: cases.length,
61
+ });
62
+ }
63
+ return files;
64
+ }
65
+ /**
66
+ * Convert a parsed/inferred spec (as produced by the parser or the
67
+ * inference engine) into the NormalizedSpec shape that rules consume.
68
+ *
69
+ * The parser returns components as an array with each holding
70
+ * `models`, `controllers`, etc. This function flattens to a single
71
+ * top-level view. It handles both object-keyed and array-shaped
72
+ * collections.
73
+ */
74
+ export function normalizeSpec(raw) {
75
+ const components = raw?.components || {};
76
+ const componentList = Array.isArray(components)
77
+ ? components
78
+ : Object.values(components);
79
+ const models = [];
80
+ const controllers = [];
81
+ const services = [];
82
+ const views = [];
83
+ // Root-level collections (used by inferred specs that have already
84
+ // been flattened by realize)
85
+ const collect = (collection, into) => {
86
+ if (!collection)
87
+ return;
88
+ if (Array.isArray(collection)) {
89
+ into.push(...collection);
90
+ }
91
+ else {
92
+ for (const [name, value] of Object.entries(collection)) {
93
+ into.push({ name, ...value });
94
+ }
95
+ }
96
+ };
97
+ collect(raw?.models, models);
98
+ collect(raw?.controllers, controllers);
99
+ collect(raw?.services, services);
100
+ collect(raw?.views, views);
101
+ for (const comp of componentList) {
102
+ collect(comp?.models, models);
103
+ collect(comp?.controllers, controllers);
104
+ collect(comp?.services, services);
105
+ collect(comp?.views, views);
106
+ }
107
+ // Dedupe by name — the realize pipeline often passes both a
108
+ // root-level flattened collection AND the component-level source,
109
+ // so we'd otherwise double-count every model.
110
+ const dedupeByName = (items) => {
111
+ const seen = new Set();
112
+ const out = [];
113
+ for (const item of items) {
114
+ const name = item?.name;
115
+ if (!name || seen.has(name))
116
+ continue;
117
+ seen.add(name);
118
+ out.push(item);
119
+ }
120
+ return out;
121
+ };
122
+ const uniqModels = dedupeByName(models);
123
+ const uniqControllers = dedupeByName(controllers);
124
+ const uniqServices = dedupeByName(services);
125
+ const uniqViews = dedupeByName(views);
126
+ /**
127
+ * Normalize an attributes collection from either array or object
128
+ * form, and each entry from either shorthand string
129
+ * ("String required") or object form.
130
+ */
131
+ const normalizeAttributes = (raw) => {
132
+ if (!raw)
133
+ return undefined;
134
+ const entries = Array.isArray(raw)
135
+ ? raw.map((a) => [a?.name || '', a])
136
+ : Object.entries(raw);
137
+ return entries
138
+ .filter(([name]) => !!name)
139
+ .map(([name, def]) => {
140
+ if (typeof def === 'string') {
141
+ const tokens = def.split(/\s+/);
142
+ const type = tokens[0] || 'String';
143
+ const required = /\brequired\b/.test(def);
144
+ const category = /\bcategory=metadata\b/.test(def)
145
+ ? 'metadata'
146
+ : /\bcategory=relationship\b/.test(def)
147
+ ? 'relationship'
148
+ : /\bauto=now\b/.test(def) || name === 'id'
149
+ ? 'metadata'
150
+ : 'business';
151
+ return { name, type, required, category };
152
+ }
153
+ return {
154
+ name,
155
+ type: def?.type || 'String',
156
+ required: !!def?.required,
157
+ category: def?.category || (name === 'id' ? 'metadata' : 'business'),
158
+ };
159
+ });
160
+ };
161
+ /**
162
+ * Normalize a relationships collection. Supports:
163
+ * - Array of {name, type, target}
164
+ * - Object keyed by name, values as {type, target} or shorthand "hasMany Post"
165
+ */
166
+ const normalizeRelationships = (raw) => {
167
+ if (!raw)
168
+ return undefined;
169
+ const entries = Array.isArray(raw)
170
+ ? raw.map((r) => [r?.name || '', r])
171
+ : Object.entries(raw);
172
+ return entries
173
+ .filter(([name]) => !!name)
174
+ .map(([name, def]) => {
175
+ if (typeof def === 'string') {
176
+ // "hasMany Post cascade" → type=hasMany, target=Post
177
+ const tokens = def.split(/\s+/);
178
+ const type = tokens[0] || '';
179
+ const target = tokens[1];
180
+ return { name, type, target };
181
+ }
182
+ return {
183
+ name,
184
+ type: def?.type || '',
185
+ target: def?.target || def?.targetModel,
186
+ };
187
+ });
188
+ };
189
+ return {
190
+ models: uniqModels.map(m => ({
191
+ name: m.name,
192
+ attributes: normalizeAttributes(m.attributes),
193
+ lifecycles: m.lifecycles,
194
+ relationships: normalizeRelationships(m.relationships),
195
+ })),
196
+ controllers: uniqControllers.map(c => ({
197
+ name: c.name,
198
+ model: c.model || c.modelReference,
199
+ cured: c.cured,
200
+ })),
201
+ services: uniqServices.map(s => ({
202
+ name: s.name,
203
+ operations: s.operations,
204
+ })),
205
+ views: uniqViews.map(v => ({
206
+ name: v.name,
207
+ type: v.type,
208
+ model: v.primaryModel || v.model,
209
+ })),
210
+ };
211
+ }
212
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/inference/ui-contracts/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared helpers for rules that need sample data / seedability
3
+ * analysis. Kept separate from test-case-types.ts so rules can
4
+ * import pure utilities without pulling in the TestStep union.
5
+ */
6
+ import type { ModelInfo, AttributeInfo } from '../test-case-types.js';
7
+ /**
8
+ * Pick the attribute to use as the entity's human-readable display
9
+ * value in assertions. Preference order:
10
+ * 1. First required business String named 'title'/'name'/'label'
11
+ * 2. First required business String attribute
12
+ * 3. First business String attribute
13
+ * 4. null — caller should skip this rule for this model
14
+ */
15
+ export declare function pickDisplayAttribute(model: ModelInfo): AttributeInfo | null;
16
+ /**
17
+ * Build a seed-data payload for a model that's plausible enough for
18
+ * the backend to accept. Fills in every required business attribute
19
+ * with a marker-tagged value so contract-test-created entities are
20
+ * easy to identify.
21
+ *
22
+ * Returns null if the model has any required belongsTo relationship —
23
+ * seeding parent entities is out of scope for v1 state-sync rules.
24
+ * Phase 5+ may add a topological-seed helper that handles dependents.
25
+ */
26
+ export declare function buildSeedData(model: ModelInfo, marker: string): Record<string, unknown> | null;
27
+ /**
28
+ * Extract the list of lifecycle state names from a model's lifecycles
29
+ * block. Returns null if the model has no lifecycle.
30
+ */
31
+ export declare function getLifecycleStates(model: ModelInfo): string[] | null;
32
+ //# sourceMappingURL=_shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/_shared.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtE;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,GAAG,aAAa,GAAG,IAAI,CAa3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAiBhC;AAuBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,GAAG,IAAI,CAepE"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Shared helpers for rules that need sample data / seedability
3
+ * analysis. Kept separate from test-case-types.ts so rules can
4
+ * import pure utilities without pulling in the TestStep union.
5
+ */
6
+ /**
7
+ * Pick the attribute to use as the entity's human-readable display
8
+ * value in assertions. Preference order:
9
+ * 1. First required business String named 'title'/'name'/'label'
10
+ * 2. First required business String attribute
11
+ * 3. First business String attribute
12
+ * 4. null — caller should skip this rule for this model
13
+ */
14
+ export function pickDisplayAttribute(model) {
15
+ const attrs = model.attributes || [];
16
+ const business = attrs.filter(a => a.category !== 'metadata' && a.category !== 'relationship');
17
+ const strings = business.filter(a => !a.type || /^String$|^Email$/i.test(a.type));
18
+ const named = strings.find(a => /^(title|name|label)$/i.test(a.name) && a.required);
19
+ if (named)
20
+ return named;
21
+ const requiredStr = strings.find(a => a.required);
22
+ if (requiredStr)
23
+ return requiredStr;
24
+ if (strings.length > 0)
25
+ return strings[0];
26
+ return null;
27
+ }
28
+ /**
29
+ * Build a seed-data payload for a model that's plausible enough for
30
+ * the backend to accept. Fills in every required business attribute
31
+ * with a marker-tagged value so contract-test-created entities are
32
+ * easy to identify.
33
+ *
34
+ * Returns null if the model has any required belongsTo relationship —
35
+ * seeding parent entities is out of scope for v1 state-sync rules.
36
+ * Phase 5+ may add a topological-seed helper that handles dependents.
37
+ */
38
+ export function buildSeedData(model, marker) {
39
+ // If the model has a required belongsTo relationship, we'd need to
40
+ // seed the parent first. v1 skips these; only top-level models get
41
+ // state-sync rules.
42
+ const rels = model.relationships || [];
43
+ const hasRequiredBelongsTo = rels.some(r => /belongsTo/i.test(String(r.type || '')));
44
+ if (hasRequiredBelongsTo)
45
+ return null;
46
+ const data = {};
47
+ const attrs = model.attributes || [];
48
+ for (const attr of attrs) {
49
+ if (attr.category === 'metadata')
50
+ continue;
51
+ if (attr.category === 'relationship')
52
+ continue;
53
+ if (!attr.required && !isLifecycleField(attr))
54
+ continue;
55
+ data[attr.name] = sampleValueFor(attr, marker);
56
+ }
57
+ return data;
58
+ }
59
+ function isLifecycleField(attr) {
60
+ // Heuristic: `status` / `state` attributes with an enumerated
61
+ // value list belong to a lifecycle. The parser usually strips
62
+ // the value list from the type at normalization time, so we
63
+ // rely on naming convention here. Good enough for v1.
64
+ return /^(status|state)$/i.test(attr.name);
65
+ }
66
+ function sampleValueFor(attr, marker) {
67
+ const type = (attr.type || 'String').trim();
68
+ // Lifecycle/enum fields: pick a plausible initial state. For poll-app
69
+ // it's 'draft'. Future work: parse the enum list from the spec and
70
+ // pick the first value.
71
+ if (isLifecycleField(attr))
72
+ return 'draft';
73
+ if (/^Integer$|^Int$|^Number$|^Float$/i.test(type))
74
+ return 0;
75
+ if (/^Boolean$/i.test(type))
76
+ return false;
77
+ if (/^DateTime$|^Date$/i.test(type))
78
+ return new Date().toISOString();
79
+ // String, Email, UUID → tag with the marker so assertions find it
80
+ return `${marker}-${attr.name}`;
81
+ }
82
+ /**
83
+ * Extract the list of lifecycle state names from a model's lifecycles
84
+ * block. Returns null if the model has no lifecycle.
85
+ */
86
+ export function getLifecycleStates(model) {
87
+ const lifecycles = model.lifecycles;
88
+ if (!lifecycles)
89
+ return null;
90
+ const first = Object.values(lifecycles)[0];
91
+ if (!first)
92
+ return null;
93
+ // Form 1: { flow: 'draft -> open -> closed' }
94
+ if (typeof first.flow === 'string') {
95
+ return first.flow.split('->').map((s) => s.trim()).filter(Boolean);
96
+ }
97
+ // Form 2: { states: [...] }
98
+ if (Array.isArray(first.states)) {
99
+ return first.states.map((s) => (typeof s === 'string' ? s : s?.name)).filter(Boolean);
100
+ }
101
+ return null;
102
+ }
103
+ //# sourceMappingURL=_shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_shared.js","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/_shared.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Rule: action-buttons-present
3
+ *
4
+ * For every CURED operation supported by a controller, the
5
+ * corresponding model's view should expose a visible action trigger
6
+ * (button, menu item, etc.) labeled with the operation name.
7
+ *
8
+ * CURED = create / update / retrieve / validate / evolve / delete.
9
+ * The spec declares which ones a controller supports via
10
+ * `cured: { create: {}, retrieve: {}, ... }`.
11
+ *
12
+ * v1 looks for operation labels as visible text. A stricter
13
+ * variant could use `getByRole('button', { name })` once the
14
+ * runtime's button markup stabilizes.
15
+ *
16
+ * One TestCase per (controller, cured-operation) pair.
17
+ */
18
+ import type { UiContractRule } from '../test-case-types.js';
19
+ export declare const RULE_ID = "action-buttons-present";
20
+ export declare const actionButtonsPresent: UiContractRule;
21
+ //# sourceMappingURL=action-buttons-present.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-buttons-present.d.ts","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/action-buttons-present.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAA4B,MAAM,uBAAuB,CAAC;AAEtF,eAAO,MAAM,OAAO,2BAA2B,CAAC;AAiBhD,eAAO,MAAM,oBAAoB,EAAE,cAyBlC,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Rule: action-buttons-present
3
+ *
4
+ * For every CURED operation supported by a controller, the
5
+ * corresponding model's view should expose a visible action trigger
6
+ * (button, menu item, etc.) labeled with the operation name.
7
+ *
8
+ * CURED = create / update / retrieve / validate / evolve / delete.
9
+ * The spec declares which ones a controller supports via
10
+ * `cured: { create: {}, retrieve: {}, ... }`.
11
+ *
12
+ * v1 looks for operation labels as visible text. A stricter
13
+ * variant could use `getByRole('button', { name })` once the
14
+ * runtime's button markup stabilizes.
15
+ *
16
+ * One TestCase per (controller, cured-operation) pair.
17
+ */
18
+ export const RULE_ID = 'action-buttons-present';
19
+ /**
20
+ * Canonical CURED labels. Used both as the trigger text to search for
21
+ * AND as the keys inside `controller.cured`. If a controller declares
22
+ * a custom action, we'll pick it up too via the Object.keys iteration
23
+ * below — this set just records which labels are considered canonical.
24
+ */
25
+ const CURED_LABELS = new Set([
26
+ 'create',
27
+ 'retrieve',
28
+ 'update',
29
+ 'validate',
30
+ 'evolve',
31
+ 'delete',
32
+ ]);
33
+ export const actionButtonsPresent = (spec) => {
34
+ const cases = [];
35
+ for (const controller of spec.controllers || []) {
36
+ const cured = controller.cured;
37
+ if (!cured)
38
+ continue;
39
+ const modelName = controller.model || controller.name.replace(/Controller$/, '');
40
+ for (const opName of Object.keys(cured)) {
41
+ // Skip the common 'retrieve' and 'validate' which typically
42
+ // don't have their own button (they fire implicitly). Keep
43
+ // create/update/delete/evolve — the ones users trigger.
44
+ if (!CURED_LABELS.has(opName))
45
+ continue;
46
+ if (opName === 'retrieve' || opName === 'validate')
47
+ continue;
48
+ cases.push({
49
+ ruleId: RULE_ID,
50
+ specElement: `${controller.name}.${opName}`,
51
+ name: `[${RULE_ID}] ${modelName}.${opName} action button is visible`,
52
+ steps: [
53
+ { action: 'bootRuntime' },
54
+ { action: 'navigateToModel', modelName },
55
+ { action: 'expectActionButton', modelName, actionLabel: opName },
56
+ ],
57
+ });
58
+ }
59
+ }
60
+ return cases;
61
+ };
62
+ //# sourceMappingURL=action-buttons-present.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-buttons-present.js","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/action-buttons-present.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,MAAM,CAAC,MAAM,OAAO,GAAG,wBAAwB,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC;IAC3B,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,QAAQ;CACT,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAmB,CAAC,IAAoB,EAAc,EAAE;IACvF,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;QAChD,MAAM,KAAK,GAAG,UAAU,CAAC,KAA4C,CAAC;QACtE,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACjF,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACxC,4DAA4D;YAC5D,2DAA2D;YAC3D,wDAAwD;YACxD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAS;YACxC,IAAI,MAAM,KAAK,UAAU,IAAI,MAAM,KAAK,UAAU;gBAAE,SAAS;YAC7D,KAAK,CAAC,IAAI,CAAC;gBACT,MAAM,EAAE,OAAO;gBACf,WAAW,EAAE,GAAG,UAAU,CAAC,IAAI,IAAI,MAAM,EAAE;gBAC3C,IAAI,EAAE,IAAI,OAAO,KAAK,SAAS,IAAI,MAAM,2BAA2B;gBACpE,KAAK,EAAE;oBACL,EAAE,MAAM,EAAE,aAAa,EAAE;oBACzB,EAAE,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE;oBACxC,EAAE,MAAM,EAAE,oBAAoB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE;iBACjE;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Rule: create-reflects-in-list
3
+ *
4
+ * State-sync contract: after a mutation creates an entity, every
5
+ * view reflecting that model must converge to show it within one
6
+ * WebSocket round-trip. v1 seeds the entity via a direct backend
7
+ * POST (through the vite dev proxy) rather than driving the UI
8
+ * form flow — the form UI has known gaps in empty state (see
9
+ * docs/plans/2026-04-14-RUNTIME-GAPS-FROM-CONTRACT-TESTS.md), so
10
+ * this rule deliberately tests the state-sync path on its own.
11
+ *
12
+ * Skipped for models whose creation would require seeding parent
13
+ * entities first (any required belongsTo relationship). For
14
+ * poll-app this means: Poll gets a test; Option and Vote are
15
+ * skipped until topological seed support lands.
16
+ *
17
+ * One TestCase per seedable model.
18
+ */
19
+ import type { UiContractRule } from '../test-case-types.js';
20
+ export declare const RULE_ID = "create-reflects-in-list";
21
+ export declare const createReflectsInList: UiContractRule;
22
+ //# sourceMappingURL=create-reflects-in-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-reflects-in-list.d.ts","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/create-reflects-in-list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAA4B,MAAM,uBAAuB,CAAC;AAGtF,eAAO,MAAM,OAAO,4BAA4B,CAAC;AAEjD,eAAO,MAAM,oBAAoB,EAAE,cAkClC,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Rule: create-reflects-in-list
3
+ *
4
+ * State-sync contract: after a mutation creates an entity, every
5
+ * view reflecting that model must converge to show it within one
6
+ * WebSocket round-trip. v1 seeds the entity via a direct backend
7
+ * POST (through the vite dev proxy) rather than driving the UI
8
+ * form flow — the form UI has known gaps in empty state (see
9
+ * docs/plans/2026-04-14-RUNTIME-GAPS-FROM-CONTRACT-TESTS.md), so
10
+ * this rule deliberately tests the state-sync path on its own.
11
+ *
12
+ * Skipped for models whose creation would require seeding parent
13
+ * entities first (any required belongsTo relationship). For
14
+ * poll-app this means: Poll gets a test; Option and Vote are
15
+ * skipped until topological seed support lands.
16
+ *
17
+ * One TestCase per seedable model.
18
+ */
19
+ import { pickDisplayAttribute, buildSeedData } from './_shared.js';
20
+ export const RULE_ID = 'create-reflects-in-list';
21
+ export const createReflectsInList = (spec) => {
22
+ const cases = [];
23
+ for (const model of spec.models) {
24
+ const display = pickDisplayAttribute(model);
25
+ if (!display)
26
+ continue;
27
+ const marker = `ct-create-${model.name}`;
28
+ const seedData = buildSeedData(model, marker);
29
+ if (!seedData)
30
+ continue; // skip — requires parent entities
31
+ const displayValue = seedData[display.name];
32
+ if (typeof displayValue !== 'string')
33
+ continue;
34
+ cases.push({
35
+ ruleId: RULE_ID,
36
+ specElement: model.name,
37
+ name: `[${RULE_ID}] creating a ${model.name} reflects in its list view`,
38
+ steps: [
39
+ { action: 'bootRuntime' },
40
+ { action: 'navigateToModel', modelName: model.name },
41
+ {
42
+ action: 'createEntity',
43
+ modelName: model.name,
44
+ data: seedData,
45
+ bindAs: '{ id: _createId, data: created }',
46
+ uniqueField: display.name,
47
+ },
48
+ {
49
+ action: 'expectEntityInList',
50
+ modelName: model.name,
51
+ displayExpr: `String(created[${JSON.stringify(display.name)}])`,
52
+ },
53
+ ],
54
+ });
55
+ }
56
+ return cases;
57
+ };
58
+ //# sourceMappingURL=create-reflects-in-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-reflects-in-list.js","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/create-reflects-in-list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEnE,MAAM,CAAC,MAAM,OAAO,GAAG,yBAAyB,CAAC;AAEjD,MAAM,CAAC,MAAM,oBAAoB,GAAmB,CAAC,IAAoB,EAAc,EAAE;IACvF,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,MAAM,MAAM,GAAG,aAAa,KAAK,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC9C,IAAI,CAAC,QAAQ;YAAE,SAAS,CAAC,kCAAkC;QAC3D,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,OAAO,YAAY,KAAK,QAAQ;YAAE,SAAS;QAE/C,KAAK,CAAC,IAAI,CAAC;YACT,MAAM,EAAE,OAAO;YACf,WAAW,EAAE,KAAK,CAAC,IAAI;YACvB,IAAI,EAAE,IAAI,OAAO,gBAAgB,KAAK,CAAC,IAAI,4BAA4B;YACvE,KAAK,EAAE;gBACL,EAAE,MAAM,EAAE,aAAa,EAAE;gBACzB,EAAE,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE;gBACpD;oBACE,MAAM,EAAE,cAAc;oBACtB,SAAS,EAAE,KAAK,CAAC,IAAI;oBACrB,IAAI,EAAE,QAAQ;oBACd,MAAM,EAAE,kCAAkC;oBAC1C,WAAW,EAAE,OAAO,CAAC,IAAI;iBAC1B;gBACD;oBACE,MAAM,EAAE,oBAAoB;oBAC5B,SAAS,EAAE,KAAK,CAAC,IAAI;oBACrB,WAAW,EAAE,kBAAkB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI;iBAChE;aACF;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Rule: delete-reflects-in-list
3
+ *
4
+ * Mirror of create-reflects-in-list, for the delete path. Seeds an
5
+ * entity via backend POST, asserts it appears in the list, then
6
+ * deletes it via backend DELETE and asserts it disappears. The
7
+ * disappear-after-delete check exercises the full state-sync chain:
8
+ * backend publishes a Deleted event → WebSocket pushes to frontend
9
+ * → useEntitySync invalidates the ['entities', modelName] query key
10
+ * → React Query refetches → the list view re-renders without the
11
+ * deleted row.
12
+ *
13
+ * If any link in that chain is broken (e.g. the mutation hook
14
+ * forgets to invalidate, or the WebSocket bridge drops the event),
15
+ * this test is the one that catches it.
16
+ *
17
+ * One TestCase per seedable model.
18
+ */
19
+ import type { UiContractRule } from '../test-case-types.js';
20
+ export declare const RULE_ID = "delete-reflects-in-list";
21
+ export declare const deleteReflectsInList: UiContractRule;
22
+ //# sourceMappingURL=delete-reflects-in-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete-reflects-in-list.d.ts","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/delete-reflects-in-list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAA4B,MAAM,uBAAuB,CAAC;AAGtF,eAAO,MAAM,OAAO,4BAA4B,CAAC;AAEjD,eAAO,MAAM,oBAAoB,EAAE,cAwClC,CAAC"}