@mmapp/react-compiler 0.1.0-alpha.1 → 0.1.0-alpha.3

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 (251) hide show
  1. package/ATOM-PIPELINE.md +144 -0
  2. package/README.md +88 -40
  3. package/dist/babel/index.js +113 -6
  4. package/dist/babel/index.mjs +2 -2
  5. package/dist/chunk-3USIFFE4.mjs +2190 -0
  6. package/dist/chunk-45YMGEVT.mjs +186 -0
  7. package/dist/chunk-4FN2AISW.mjs +148 -0
  8. package/dist/chunk-4OPI5L7G.mjs +2593 -0
  9. package/dist/chunk-4RYTKOOJ.mjs +186 -0
  10. package/dist/chunk-5RKTOVR5.mjs +244 -0
  11. package/dist/chunk-5YDMOO4X.mjs +214 -0
  12. package/dist/chunk-64ZWEMLJ.mjs +148 -0
  13. package/dist/chunk-6XP4KSWQ.mjs +2190 -0
  14. package/dist/chunk-72QWL54I.mjs +175 -0
  15. package/dist/chunk-7B4TRI7C.mjs +4835 -0
  16. package/dist/chunk-7ZKGHTNB.mjs +4952 -0
  17. package/dist/chunk-CIESM3BP.mjs +33 -0
  18. package/dist/chunk-DE3ZGQAC.mjs +148 -0
  19. package/dist/chunk-DMCY3BBG.mjs +1933 -0
  20. package/dist/chunk-DPIK3PJS.mjs +244 -0
  21. package/dist/chunk-E5IVH4RE.mjs +186 -0
  22. package/dist/chunk-E6FZNUR5.mjs +4953 -0
  23. package/dist/chunk-EJRBDQDP.mjs +2607 -0
  24. package/dist/chunk-ELO4TXJL.mjs +186 -0
  25. package/dist/chunk-FKRO52XH.mjs +3446 -0
  26. package/dist/chunk-FL4YAKU6.mjs +4941 -0
  27. package/dist/chunk-FYT47UBU.mjs +5076 -0
  28. package/dist/chunk-GCLGPOJZ.mjs +148 -0
  29. package/dist/chunk-GXB4JOP7.mjs +5072 -0
  30. package/dist/chunk-HFXOUMTD.mjs +175 -0
  31. package/dist/chunk-HWIZ47US.mjs +214 -0
  32. package/dist/chunk-IB7MNPQL.mjs +4953 -0
  33. package/dist/chunk-ICSIHQCG.mjs +148 -0
  34. package/dist/chunk-JLA5VNQ3.mjs +186 -0
  35. package/dist/chunk-JQLWFCTM.mjs +214 -0
  36. package/dist/chunk-KFJJCQAL.mjs +148 -0
  37. package/dist/chunk-KJUIIEQE.mjs +186 -0
  38. package/dist/chunk-KNWTHRVQ.mjs +175 -0
  39. package/dist/chunk-KSG4XSZF.mjs +175 -0
  40. package/dist/chunk-LF5N6DOU.mjs +175 -0
  41. package/dist/chunk-LJQCM2IM.mjs +214 -0
  42. package/dist/chunk-NW6555WJ.mjs +186 -0
  43. package/dist/chunk-OMZE6VLQ.mjs +214 -0
  44. package/dist/chunk-P4BR7WVO.mjs +2190 -0
  45. package/dist/chunk-QQHVYH2X.mjs +244 -0
  46. package/dist/chunk-S5QLWLLT.mjs +186 -0
  47. package/dist/chunk-SCWGT2FY.mjs +2190 -0
  48. package/dist/chunk-SMKJUSB3.mjs +2190 -0
  49. package/dist/chunk-VCAY2KGM.mjs +175 -0
  50. package/dist/chunk-WECAV6QB.mjs +148 -0
  51. package/dist/chunk-WMKBXUCE.mjs +3228 -0
  52. package/dist/chunk-XAJ5BKKL.mjs +4947 -0
  53. package/dist/chunk-XG2X7AEA.mjs +175 -0
  54. package/dist/chunk-XG7Z23NQ.mjs +148 -0
  55. package/dist/chunk-XWZAOCQ7.mjs +2607 -0
  56. package/dist/chunk-Y6MA7ULW.mjs +148 -0
  57. package/dist/chunk-YMS7Q7LG.mjs +214 -0
  58. package/dist/chunk-ZA37XTGA.mjs +175 -0
  59. package/dist/cli/index.js +1616 -366
  60. package/dist/cli/index.mjs +8 -8
  61. package/dist/codemod/cli.mjs +1 -1
  62. package/dist/codemod/index.mjs +1 -1
  63. package/dist/dev-server-RmGHIntF.d.mts +113 -0
  64. package/dist/dev-server-RmGHIntF.d.ts +113 -0
  65. package/dist/dev-server.d.mts +1 -1
  66. package/dist/dev-server.d.ts +1 -1
  67. package/dist/dev-server.js +982 -53
  68. package/dist/dev-server.mjs +5 -5
  69. package/dist/envelope.js +113 -6
  70. package/dist/envelope.mjs +3 -3
  71. package/dist/index.d.mts +5 -1
  72. package/dist/index.d.ts +5 -1
  73. package/dist/index.js +992 -63
  74. package/dist/index.mjs +8 -8
  75. package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
  76. package/dist/init-EHO4VQ22.mjs +369 -0
  77. package/dist/init-UC3FWPIW.mjs +367 -0
  78. package/dist/init-UNSMVKIK.mjs +366 -0
  79. package/dist/init-UNV5XIDE.mjs +367 -0
  80. package/dist/project-compiler-2P4N4DR7.mjs +10 -0
  81. package/dist/project-compiler-D2LCC27O.mjs +10 -0
  82. package/dist/project-compiler-EJ3GANJE.mjs +10 -0
  83. package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
  84. package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
  85. package/dist/project-compiler-VWNNCHGO.mjs +10 -0
  86. package/dist/project-compiler-XVAAU4C5.mjs +10 -0
  87. package/dist/project-compiler-YES5FGMD.mjs +10 -0
  88. package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
  89. package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
  90. package/dist/project-decompiler-VLPR22QF.mjs +7 -0
  91. package/dist/pull-FUS5QYZS.mjs +109 -0
  92. package/dist/pull-LD5ENLGY.mjs +109 -0
  93. package/dist/testing/index.js +113 -6
  94. package/dist/testing/index.mjs +2 -2
  95. package/dist/vite/index.js +113 -6
  96. package/dist/vite/index.mjs +3 -3
  97. package/examples/uber-app/app/admin/fleet.tsx +19 -19
  98. package/package.json +4 -3
  99. package/compile-blueprint-chat.mjs +0 -99
  100. package/compile-blueprint-glass-console.mjs +0 -98
  101. package/compile-chat-defs.mjs +0 -92
  102. package/examples/uber-app/tests/payment.test.tsx +0 -129
  103. package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
  104. package/package.json.backup +0 -86
  105. package/scripts/decompile.ts +0 -226
  106. package/scripts/seed-auth.ts +0 -267
  107. package/scripts/seed-uber.ts +0 -248
  108. package/scripts/validate-uber.ts +0 -119
  109. package/seed-blueprint-chat.mjs +0 -444
  110. package/seed-blueprint-glass-console.mjs +0 -445
  111. package/seed-compiled.mjs +0 -318
  112. package/src/RoundTripValidator.ts +0 -400
  113. package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
  114. package/src/__tests__/auth-module-compilation.test.ts +0 -247
  115. package/src/__tests__/auth-template-compilation.test.ts +0 -589
  116. package/src/__tests__/change-extractor.test.ts +0 -142
  117. package/src/__tests__/cli-pull.test.ts +0 -73
  118. package/src/__tests__/cli-test.test.ts +0 -72
  119. package/src/__tests__/component-extractor.test.ts +0 -331
  120. package/src/__tests__/context-extractor.test.ts +0 -145
  121. package/src/__tests__/decompiler.test.ts +0 -718
  122. package/src/__tests__/define-blueprint.test.ts +0 -133
  123. package/src/__tests__/definition-validator.test.ts +0 -519
  124. package/src/__tests__/during-extractor.test.ts +0 -152
  125. package/src/__tests__/effect-extractor.test.ts +0 -107
  126. package/src/__tests__/event-emission.test.ts +0 -127
  127. package/src/__tests__/examples.test.ts +0 -236
  128. package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
  129. package/src/__tests__/golden-suite.test.ts +0 -403
  130. package/src/__tests__/grammar-island-extractor.test.ts +0 -289
  131. package/src/__tests__/instance-key.test.ts +0 -82
  132. package/src/__tests__/ir-migration.test.ts +0 -255
  133. package/src/__tests__/lock-file.test.ts +0 -117
  134. package/src/__tests__/model-extractor.test.ts +0 -195
  135. package/src/__tests__/model-field-acl.test.ts +0 -237
  136. package/src/__tests__/model-hooks.test.ts +0 -130
  137. package/src/__tests__/model-ref-resolution.test.ts +0 -268
  138. package/src/__tests__/model-roundtrip.test.ts +0 -502
  139. package/src/__tests__/model-runtime.test.ts +0 -112
  140. package/src/__tests__/model-transitions.test.ts +0 -183
  141. package/src/__tests__/nrt-action-trace.test.ts +0 -391
  142. package/src/__tests__/pipeline-hardening.test.ts +0 -413
  143. package/src/__tests__/project-compiler.test.ts +0 -546
  144. package/src/__tests__/project-decompiler.test.ts +0 -343
  145. package/src/__tests__/query-compilation.test.ts +0 -145
  146. package/src/__tests__/round-trip/PLAN.md +0 -158
  147. package/src/__tests__/round-trip/README.md +0 -52
  148. package/src/__tests__/round-trip/RESULTS.md +0 -86
  149. package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
  150. package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
  151. package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
  152. package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
  153. package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
  154. package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
  155. package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
  156. package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
  157. package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
  158. package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
  159. package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
  160. package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
  161. package/src/__tests__/round-trip-ir.test.ts +0 -300
  162. package/src/__tests__/round-trip.test.ts +0 -1212
  163. package/src/__tests__/route-merging.test.ts +0 -372
  164. package/src/__tests__/router-composition.test.ts +0 -489
  165. package/src/__tests__/router-extractor.test.ts +0 -176
  166. package/src/__tests__/server-action-extractor.test.ts +0 -128
  167. package/src/__tests__/smart-type-inference.test.ts +0 -365
  168. package/src/__tests__/source-envelope.test.ts +0 -284
  169. package/src/__tests__/source-fidelity.test.ts +0 -516
  170. package/src/__tests__/state-extractor.test.ts +0 -115
  171. package/src/__tests__/strict-mode.test.ts +0 -227
  172. package/src/__tests__/transition-effect-extractor.test.ts +0 -119
  173. package/src/__tests__/transition-extractor.test.ts +0 -68
  174. package/src/__tests__/ts-to-expression.test.ts +0 -462
  175. package/src/__tests__/type-generator.test.ts +0 -201
  176. package/src/__tests__/uber-validation.test.ts +0 -502
  177. package/src/action-compiler.ts +0 -361
  178. package/src/babel/emitters/experience-transform.ts +0 -199
  179. package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
  180. package/src/babel/emitters/pure-form-emitter.ts +0 -1023
  181. package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
  182. package/src/babel/extractors/change-extractor.ts +0 -199
  183. package/src/babel/extractors/component-extractor.ts +0 -907
  184. package/src/babel/extractors/computed-extractor.ts +0 -262
  185. package/src/babel/extractors/context-extractor.ts +0 -277
  186. package/src/babel/extractors/during-extractor.ts +0 -295
  187. package/src/babel/extractors/effect-extractor.ts +0 -340
  188. package/src/babel/extractors/event-extractor.ts +0 -235
  189. package/src/babel/extractors/grammar-island-extractor.ts +0 -302
  190. package/src/babel/extractors/model-extractor.ts +0 -1018
  191. package/src/babel/extractors/router-extractor.ts +0 -303
  192. package/src/babel/extractors/server-action-extractor.ts +0 -173
  193. package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
  194. package/src/babel/extractors/server-state-extractor.ts +0 -88
  195. package/src/babel/extractors/state-extractor.ts +0 -214
  196. package/src/babel/extractors/transition-effect-extractor.ts +0 -176
  197. package/src/babel/extractors/transition-extractor.ts +0 -143
  198. package/src/babel/index.ts +0 -24
  199. package/src/babel/transpilers/ts-to-expression.ts +0 -674
  200. package/src/babel/visitor.ts +0 -807
  201. package/src/cli/auth.ts +0 -255
  202. package/src/cli/build.ts +0 -288
  203. package/src/cli/deploy.ts +0 -206
  204. package/src/cli/index.ts +0 -328
  205. package/src/cli/installer.ts +0 -261
  206. package/src/cli/lock-file.ts +0 -94
  207. package/src/cli/mmrc.ts +0 -22
  208. package/src/cli/pull.ts +0 -172
  209. package/src/cli/registry-client.ts +0 -175
  210. package/src/cli/test.ts +0 -397
  211. package/src/cli/type-generator.ts +0 -243
  212. package/src/codemod/__tests__/forward.test.ts +0 -239
  213. package/src/codemod/__tests__/reverse.test.ts +0 -145
  214. package/src/codemod/__tests__/round-trip.test.ts +0 -137
  215. package/src/codemod/annotation.ts +0 -97
  216. package/src/codemod/classify.ts +0 -197
  217. package/src/codemod/cli.ts +0 -207
  218. package/src/codemod/control-flow.ts +0 -409
  219. package/src/codemod/forward.ts +0 -244
  220. package/src/codemod/import-manager.ts +0 -171
  221. package/src/codemod/index.ts +0 -120
  222. package/src/codemod/reverse.ts +0 -197
  223. package/src/codemod/rules.ts +0 -174
  224. package/src/codemod/state-transform.ts +0 -126
  225. package/src/decompiler/ast-builder.ts +0 -538
  226. package/src/decompiler/config-generator.ts +0 -151
  227. package/src/decompiler/index.ts +0 -315
  228. package/src/decompiler/project-decompiler.ts +0 -1776
  229. package/src/decompiler/project.ts +0 -862
  230. package/src/decompiler/split-strategy.ts +0 -140
  231. package/src/decompiler/state-emitter.ts +0 -1053
  232. package/src/decompiler/sx-emitter.ts +0 -318
  233. package/src/decompiler/workspace-hydrator.ts +0 -189
  234. package/src/dev-server.ts +0 -238
  235. package/src/envelope/fs-tree.ts +0 -217
  236. package/src/envelope/source-envelope.ts +0 -264
  237. package/src/envelope.ts +0 -315
  238. package/src/incremental-compiler.ts +0 -401
  239. package/src/index.ts +0 -99
  240. package/src/model-compiler.ts +0 -277
  241. package/src/project-compiler.ts +0 -1629
  242. package/src/route-extractor.ts +0 -333
  243. package/src/testing/index.ts +0 -32
  244. package/src/testing/snapshot.ts +0 -252
  245. package/src/testing/test-utils.ts +0 -226
  246. package/src/types.ts +0 -68
  247. package/src/vite/index.ts +0 -288
  248. package/test-compile.mjs +0 -142
  249. package/tsconfig.json +0 -25
  250. package/tsup.config.ts +0 -23
  251. package/vitest.config.ts +0 -9
@@ -1,1221 +0,0 @@
1
- /**
2
- * Full Blueprint Coverage Test — compiles every example blueprint end-to-end
3
- * and asserts on fields, states, transitions, experience tree structure,
4
- * data sources, roles, routes, and component hierarchy.
5
- *
6
- * Covers both single-file examples (examples/) and real package-level blueprints
7
- * (packages/blueprint-*).
8
- */
9
-
10
- import { describe, it, expect, beforeAll } from 'vitest';
11
- import { transformSync } from '@babel/core';
12
- import { readFileSync, readdirSync, existsSync } from 'fs';
13
- import { resolve, join } from 'path';
14
- import babelPlugin from '../babel';
15
- import { compileProject } from '../project-compiler';
16
- import type { IRWorkflowDefinition, IRExperienceNode } from '@mindmatrix/player-core';
17
-
18
- // =============================================================================
19
- // Helpers
20
- // =============================================================================
21
-
22
- function compileFile(relativePath: string): IRWorkflowDefinition {
23
- const filePath = resolve(__dirname, '../../examples', relativePath);
24
- const code = readFileSync(filePath, 'utf-8');
25
- const result = transformSync(code, {
26
- filename: filePath,
27
- plugins: [[babelPlugin]],
28
- parserOpts: { plugins: ['typescript', 'jsx'], attachComment: true },
29
- });
30
- const ir = (result as any)?.metadata?.mindmatrixIR;
31
- if (!ir) throw new Error(`No IR produced for ${relativePath}`);
32
- return ir;
33
- }
34
-
35
- function loadProjectFiles(dir: string): Record<string, string> {
36
- const files: Record<string, string> = {};
37
-
38
- function walk(current: string, prefix: string) {
39
- for (const entry of readdirSync(current, { withFileTypes: true })) {
40
- const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
41
- if (entry.isDirectory()) {
42
- // Skip test directories and node_modules
43
- if (entry.name === 'tests' || entry.name === 'node_modules' || entry.name === '__tests__') continue;
44
- walk(join(current, entry.name), rel);
45
- } else if (
46
- entry.name.endsWith('.ts') ||
47
- entry.name.endsWith('.tsx')
48
- ) {
49
- files[rel] = readFileSync(join(current, entry.name), 'utf-8');
50
- }
51
- }
52
- }
53
-
54
- walk(dir, '');
55
- return files;
56
- }
57
-
58
- function loadExampleProjectFiles(dir: string): Record<string, string> {
59
- const base = resolve(__dirname, '../../examples', dir);
60
- return loadProjectFiles(base);
61
- }
62
-
63
- function getExperience(ir: IRWorkflowDefinition): IRExperienceNode | undefined {
64
- return ir.metadata?.experience as IRExperienceNode | undefined;
65
- }
66
-
67
- /** Recursively collect all component types from an experience tree */
68
- function collectComponents(node: IRExperienceNode | undefined): string[] {
69
- if (!node) return [];
70
- const result: string[] = [];
71
- if (node.component) result.push(node.component);
72
- if (node.children) {
73
- for (const child of node.children) {
74
- result.push(...collectComponents(child));
75
- }
76
- }
77
- return result;
78
- }
79
-
80
- /** Recursively find all nodes matching a component name */
81
- function findNodes(node: IRExperienceNode | undefined, component: string): IRExperienceNode[] {
82
- if (!node) return [];
83
- const result: IRExperienceNode[] = [];
84
- if (node.component === component) result.push(node);
85
- if (node.children) {
86
- for (const child of node.children) {
87
- result.push(...findNodes(child, component));
88
- }
89
- }
90
- return result;
91
- }
92
-
93
- /** Find all nodes with bindings */
94
- function findBoundNodes(node: IRExperienceNode | undefined): IRExperienceNode[] {
95
- if (!node) return [];
96
- const result: IRExperienceNode[] = [];
97
- if (node.bindings && Object.keys(node.bindings).length > 0) result.push(node);
98
- if (node.children) {
99
- for (const child of node.children) {
100
- result.push(...findBoundNodes(child));
101
- }
102
- }
103
- return result;
104
- }
105
-
106
- /** Find all nodes with visible_when conditions */
107
- function findConditionalNodes(node: IRExperienceNode | undefined): IRExperienceNode[] {
108
- if (!node) return [];
109
- const result: IRExperienceNode[] = [];
110
- if (node.visible_when) result.push(node);
111
- if (node.children) {
112
- for (const child of node.children) {
113
- result.push(...findConditionalNodes(child));
114
- }
115
- }
116
- return result;
117
- }
118
-
119
- /** Count total nodes in tree */
120
- function countNodes(node: IRExperienceNode | undefined): number {
121
- if (!node) return 0;
122
- let count = 1;
123
- if (node.children) {
124
- for (const child of node.children) {
125
- count += countNodes(child);
126
- }
127
- }
128
- return count;
129
- }
130
-
131
- /** Collect all unique component types from experience tree */
132
- function collectComponentTypes(node: IRExperienceNode | undefined): Set<string> {
133
- const types = new Set<string>();
134
- if (!node) return types;
135
- function walk(n: IRExperienceNode) {
136
- if (n.component) types.add(n.component);
137
- n.children?.forEach(walk);
138
- }
139
- walk(node);
140
- return types;
141
- }
142
-
143
- // =============================================================================
144
- // Single-File Blueprints (from examples/)
145
- // =============================================================================
146
-
147
- describe('Full Blueprint Coverage: counter.workflow.tsx', () => {
148
- let ir: IRWorkflowDefinition;
149
- let exp: IRExperienceNode | undefined;
150
-
151
- beforeAll(() => {
152
- ir = compileFile('counter.workflow.tsx');
153
- exp = getExperience(ir);
154
- });
155
-
156
- it('compiles with correct metadata', () => {
157
- expect(ir.slug).toBe('counter');
158
- expect(ir.version).toBe('1.0.0');
159
- expect(ir.category).toBe('workflow');
160
- expect(ir.description).toContain('simple counter');
161
- });
162
-
163
- it('extracts exactly 3 fields with correct types', () => {
164
- expect(ir.fields).toHaveLength(3);
165
- const count = ir.fields.find(f => f.name === 'count');
166
- const status = ir.fields.find(f => f.name === 'status');
167
- const lastAction = ir.fields.find(f => f.name === 'lastAction');
168
- expect(count).toBeDefined();
169
- expect(count!.type).toBe('number');
170
- expect(count!.default_value).toBe(0);
171
- expect(status).toBeDefined();
172
- expect(status!.type).toBe('text');
173
- expect(status!.default_value).toBe('idle');
174
- expect(lastAction).toBeDefined();
175
- expect(lastAction!.type).toBe('text');
176
- expect(lastAction!.default_value).toBe('');
177
- });
178
-
179
- it('extracts 4 transitions with correct from/to', () => {
180
- expect(ir.transitions).toHaveLength(4);
181
- const start = ir.transitions.find(t => t.name === 'start');
182
- const inc = ir.transitions.find(t => t.name === 'increment');
183
- const dec = ir.transitions.find(t => t.name === 'decrement');
184
- const reset = ir.transitions.find(t => t.name === 'reset');
185
- expect(start).toBeDefined();
186
- expect(start!.to).toBe('counting');
187
- expect(inc).toBeDefined();
188
- expect(inc!.from).toContain('counting');
189
- expect(inc!.to).toBe('counting');
190
- expect(dec).toBeDefined();
191
- expect(dec!.from).toContain('counting');
192
- expect(dec!.to).toBe('counting');
193
- expect(reset).toBeDefined();
194
- expect(reset!.from).toContain('counting');
195
- expect(reset!.to).toBe('idle');
196
- });
197
-
198
- it('extracts states with on_enter hooks', () => {
199
- const counting = ir.states.find(s => s.name === 'counting');
200
- expect(counting).toBeDefined();
201
- expect(counting!.on_enter.length).toBeGreaterThanOrEqual(1);
202
- const setFieldActions = counting!.on_enter.filter(a => a.type === 'set_field');
203
- expect(setFieldActions.length).toBeGreaterThanOrEqual(1);
204
- });
205
-
206
- it('extracts field watchers from useOnChange', () => {
207
- const watchers = (ir.metadata as any)?.fieldWatchers;
208
- expect(watchers).toBeDefined();
209
- expect(watchers.length).toBeGreaterThanOrEqual(1);
210
- const countWatcher = watchers.find((w: any) => w.field === 'count');
211
- expect(countWatcher).toBeDefined();
212
- });
213
-
214
- it('produces an experience tree with root Stack', () => {
215
- expect(exp).toBeDefined();
216
- expect(exp!.component).toBe('Stack');
217
- expect(exp!.className).toBe('counter-app');
218
- });
219
-
220
- it('experience tree contains Heading with counter binding', () => {
221
- const headings = findNodes(exp, 'Text');
222
- const counterHeading = headings.find(h =>
223
- h.bindings?.value?.includes('count') || h.config?.value?.toString().includes('Counter')
224
- );
225
- expect(counterHeading).toBeDefined();
226
- });
227
-
228
- it('experience tree contains 3 Button nodes (+1, -1, Reset)', () => {
229
- const buttons = findNodes(exp, 'Button');
230
- expect(buttons.length).toBeGreaterThanOrEqual(3);
231
- const labels = buttons.map(b => b.config?.label || b.config?.value).filter(Boolean);
232
- expect(labels).toContain('+1');
233
- expect(labels).toContain('-1');
234
- expect(labels).toContain('Reset');
235
- });
236
-
237
- it('buttons have onClick bindings', () => {
238
- const buttons = findNodes(exp, 'Button');
239
- for (const btn of buttons) {
240
- const hasAction = btn.bindings?.onClick || btn.bindings?.onPress;
241
- expect(hasAction).toBeDefined();
242
- }
243
- });
244
-
245
- it('has no compilation errors', () => {
246
- expect((ir.metadata as any)?.errors).toBeUndefined();
247
- });
248
- });
249
-
250
- describe('Full Blueprint Coverage: todo-app.workflow.tsx', () => {
251
- let ir: IRWorkflowDefinition;
252
- let exp: IRExperienceNode | undefined;
253
-
254
- beforeAll(() => {
255
- ir = compileFile('todo-app.workflow.tsx');
256
- exp = getExperience(ir);
257
- });
258
-
259
- it('compiles with correct metadata', () => {
260
- expect(ir.slug).toBe('todo-app');
261
- expect(ir.version).toBe('1.0.0');
262
- expect(ir.category).toBe('data');
263
- expect(ir.description).toContain('Todo list');
264
- });
265
-
266
- it('extracts exactly 3 fields', () => {
267
- expect(ir.fields).toHaveLength(3);
268
- expect(ir.fields.find(f => f.name === 'title')?.type).toBe('text');
269
- expect(ir.fields.find(f => f.name === 'title')?.default_value).toBe('');
270
- expect(ir.fields.find(f => f.name === 'filter')?.type).toBe('text');
271
- expect(ir.fields.find(f => f.name === 'filter')?.default_value).toBe('all');
272
- expect(ir.fields.find(f => f.name === 'itemCount')?.type).toBe('number');
273
- expect(ir.fields.find(f => f.name === 'itemCount')?.default_value).toBe(0);
274
- });
275
-
276
- it('extracts 2 data sources targeting todo-item', () => {
277
- const sources = (ir.metadata as any)?.dataSources;
278
- expect(sources).toBeDefined();
279
- expect(sources).toHaveLength(2);
280
- expect(sources[0].slug).toBe('todo-item');
281
- expect(sources[0].pageSize).toBe(100);
282
- expect(sources[0].sort).toBe('created_at');
283
- expect(sources[1].slug).toBe('todo-item');
284
- expect(sources[1].pageSize).toBe(50);
285
- });
286
-
287
- it('extracts mutation target: todo-item', () => {
288
- const targets = (ir.metadata as any)?.mutationTargets;
289
- expect(targets).toBeDefined();
290
- expect(targets).toContain('todo-item');
291
- });
292
-
293
- it('extracts role dependencies: editor, admin', () => {
294
- const roles = (ir.metadata as any)?.roleDependencies;
295
- expect(roles).toBeDefined();
296
- expect(roles).toContain('editor');
297
- expect(roles).toContain('admin');
298
- });
299
-
300
- it('extracts 3 transitions (activate, archive, restore)', () => {
301
- expect(ir.transitions).toHaveLength(3);
302
- const names = ir.transitions.map(t => t.name);
303
- expect(names).toContain('activate');
304
- expect(names).toContain('archive');
305
- expect(names).toContain('restore');
306
-
307
- const activate = ir.transitions.find(t => t.name === 'activate');
308
- expect(activate!.to).toBe('active');
309
- const archive = ir.transitions.find(t => t.name === 'archive');
310
- expect(archive!.from).toContain('active');
311
- expect(archive!.to).toBe('archived');
312
- const restore = ir.transitions.find(t => t.name === 'restore');
313
- expect(restore!.from).toContain('archived');
314
- expect(restore!.to).toBe('active');
315
- });
316
-
317
- it('extracts states with enter/exit/during hooks', () => {
318
- const active = ir.states.find(s => s.name === 'active');
319
- expect(active).toBeDefined();
320
- expect(active!.on_enter.length).toBeGreaterThanOrEqual(1);
321
- expect(active!.on_exit.length).toBeGreaterThanOrEqual(1);
322
- if (active!.during) {
323
- expect(active!.during.length).toBeGreaterThanOrEqual(1);
324
- expect(active!.during[0].interval_ms).toBe(30000);
325
- }
326
- });
327
-
328
- it('experience tree has root Stack with className', () => {
329
- expect(exp).toBeDefined();
330
- expect(exp!.component).toBe('Stack');
331
- expect(exp!.className).toBe('todo-app');
332
- });
333
-
334
- it('experience tree contains heading with item count binding', () => {
335
- const textNodes = findNodes(exp, 'Text');
336
- const heading = textNodes.find(t =>
337
- t.bindings?.value?.includes('itemCount') ||
338
- t.config?.value?.toString().includes('Todo List')
339
- );
340
- expect(heading).toBeDefined();
341
- });
342
-
343
- it('experience tree contains Input with placeholder', () => {
344
- const inputs = findNodes(exp, 'TextInput');
345
- const todoInput = inputs.find(i =>
346
- i.config?.placeholder === 'What needs to be done?'
347
- );
348
- expect(todoInput).toBeDefined();
349
- });
350
-
351
- it('experience tree contains 3 filter buttons (All, Active, Done)', () => {
352
- const buttons = findNodes(exp, 'Button');
353
- const filterLabels = buttons
354
- .map(b => b.config?.label || b.config?.value)
355
- .filter(Boolean);
356
- expect(filterLabels).toContain('All');
357
- expect(filterLabels).toContain('Active');
358
- expect(filterLabels).toContain('Done');
359
- });
360
-
361
- it('experience tree contains Each (list) node for todos', () => {
362
- const eachNodes = findNodes(exp, 'Each');
363
- expect(eachNodes.length).toBeGreaterThanOrEqual(1);
364
- const todoList = eachNodes.find(e => e.bindings?.items || e.config?.items);
365
- expect(todoList).toBeDefined();
366
- });
367
-
368
- it('experience tree has conditional nodes (role guards, empty state)', () => {
369
- const conditionals = findConditionalNodes(exp);
370
- expect(conditionals.length).toBeGreaterThanOrEqual(2);
371
- });
372
-
373
- it('has no compilation errors', () => {
374
- expect((ir.metadata as any)?.errors).toBeUndefined();
375
- });
376
- });
377
-
378
- describe('Full Blueprint Coverage: dashboard.workflow.tsx', () => {
379
- let ir: IRWorkflowDefinition;
380
- let exp: IRExperienceNode | undefined;
381
-
382
- beforeAll(() => {
383
- ir = compileFile('dashboard.workflow.tsx');
384
- exp = getExperience(ir);
385
- });
386
-
387
- it('compiles with correct metadata', () => {
388
- expect(ir.slug).toBe('dashboard');
389
- expect(ir.version).toBe('1.0.0');
390
- expect(ir.category).toBe('blueprint');
391
- expect(ir.description).toContain('dashboard blueprint');
392
- });
393
-
394
- it('extracts exactly 8 fields with correct types and defaults', () => {
395
- expect(ir.fields).toHaveLength(8);
396
- const fieldMap = Object.fromEntries(ir.fields.map(f => [f.name, f]));
397
-
398
- expect(fieldMap.activeRoute.type).toBe('text');
399
- expect(fieldMap.activeRoute.default_value).toBe('/dashboard');
400
- expect(fieldMap.sidebarOpen.type).toBe('boolean');
401
- expect(fieldMap.sidebarOpen.default_value).toBe(true);
402
- expect(fieldMap.searchQuery.type).toBe('text');
403
- expect(fieldMap.searchQuery.default_value).toBe('');
404
- expect(fieldMap.notificationCount.type).toBe('number');
405
- expect(fieldMap.notificationCount.default_value).toBe(0);
406
- expect(fieldMap.selectedRecordId.type).toBe('text');
407
- expect(fieldMap.theme.type).toBe('text');
408
- expect(fieldMap.theme.default_value).toBe('light');
409
- expect(fieldMap.refreshInterval.type).toBe('number');
410
- expect(fieldMap.refreshInterval.default_value).toBe(30000);
411
- expect(fieldMap.lastRefreshed.type).toBe('text');
412
- });
413
-
414
- it('extracts 5 data sources with correct slugs and configs', () => {
415
- const sources = (ir.metadata as any)?.dataSources;
416
- expect(sources).toBeDefined();
417
- expect(sources).toHaveLength(5);
418
- const slugs = sources.map((s: any) => s.slug);
419
- expect(slugs).toContain('dashboard-metric');
420
- expect(slugs).toContain('activity-log');
421
- expect(slugs).toContain('workflow-definition');
422
- expect(slugs).toContain('notification');
423
- expect(slugs).toContain('user-setting');
424
-
425
- const metricSource = sources.find((s: any) => s.slug === 'dashboard-metric');
426
- expect(metricSource.pageSize).toBe(20);
427
- expect(metricSource.sort).toBe('priority');
428
-
429
- const activitySource = sources.find((s: any) => s.slug === 'activity-log');
430
- expect(activitySource.pageSize).toBe(50);
431
- expect(activitySource.sort).toBe('created_at');
432
- });
433
-
434
- it('extracts 2 mutation targets', () => {
435
- const targets = (ir.metadata as any)?.mutationTargets;
436
- expect(targets).toBeDefined();
437
- expect(targets).toContain('user-setting');
438
- expect(targets).toContain('activity-log');
439
- });
440
-
441
- it('extracts 3 role dependencies', () => {
442
- const roles = (ir.metadata as any)?.roleDependencies;
443
- expect(roles).toBeDefined();
444
- expect(roles).toContain('admin');
445
- expect(roles).toContain('viewer');
446
- expect(roles).toContain('editor');
447
- });
448
-
449
- it('extracts 7 transitions with correct state machine', () => {
450
- expect(ir.transitions).toHaveLength(7);
451
- const byName = Object.fromEntries(ir.transitions.map(t => [t.name, t]));
452
-
453
- expect(byName.initialize.to).toBe('active');
454
- expect(byName.suspend.from).toContain('active');
455
- expect(byName.suspend.to).toBe('suspended');
456
- expect(byName.resume.from).toContain('suspended');
457
- expect(byName.resume.to).toBe('active');
458
- expect(byName.deactivate.from).toContain('active');
459
- expect(byName.deactivate.to).toBe('archived');
460
-
461
- expect(byName.navigate.from).toContain('active');
462
- expect(byName.navigate.to).toBe('active');
463
-
464
- expect(byName['open-settings'].from).toContain('active');
465
- expect(byName['open-settings'].to).toBe('settings');
466
- expect(byName['close-settings'].from).toContain('settings');
467
- expect(byName['close-settings'].to).toBe('active');
468
- });
469
-
470
- it('extracts states with on_enter, on_exit, and during', () => {
471
- const active = ir.states.find(s => s.name === 'active');
472
- expect(active).toBeDefined();
473
- expect(active!.on_enter.length).toBeGreaterThanOrEqual(1);
474
- expect(active!.on_exit.length).toBeGreaterThanOrEqual(1);
475
- if (active!.during) {
476
- expect(active!.during.length).toBeGreaterThanOrEqual(1);
477
- expect(active!.during[0].interval_ms).toBe(30000);
478
- }
479
-
480
- const settings = ir.states.find(s => s.name === 'settings');
481
- expect(settings).toBeDefined();
482
- expect(settings!.on_enter.length).toBeGreaterThanOrEqual(1);
483
- });
484
-
485
- it('extracts field watchers for activeRoute and notificationCount', () => {
486
- const watchers = (ir.metadata as any)?.fieldWatchers;
487
- expect(watchers).toBeDefined();
488
- expect(watchers.length).toBeGreaterThanOrEqual(2);
489
- const watchedFields = watchers.map((w: any) => w.field);
490
- expect(watchedFields).toContain('activeRoute');
491
- expect(watchedFields).toContain('notificationCount');
492
- });
493
-
494
- it('extracts 2 event subscriptions', () => {
495
- const events = ir.on_event;
496
- expect(events).toBeDefined();
497
- expect(events!.length).toBeGreaterThanOrEqual(2);
498
- const matches = events!.map(e => e.match);
499
- expect(matches).toContain('data:updated');
500
- expect(matches).toContain('notification:received');
501
- });
502
-
503
- it('experience tree has root Stack', () => {
504
- expect(exp).toBeDefined();
505
- expect(exp!.component).toBe('Stack');
506
- expect(exp!.className).toBe('dashboard-app');
507
- });
508
-
509
- it('experience tree contains MetricCard components', () => {
510
- const cards = findNodes(exp, 'MetricCard');
511
- expect(cards.length).toBeGreaterThanOrEqual(1);
512
- });
513
-
514
- it('experience tree contains Chart components (line and bar)', () => {
515
- const charts = findNodes(exp, 'Chart');
516
- expect(charts.length).toBeGreaterThanOrEqual(2);
517
- const types = charts.map(c => c.config?.type);
518
- expect(types).toContain('line');
519
- expect(types).toContain('bar');
520
- });
521
-
522
- it('experience tree contains ServerGrid for data browser', () => {
523
- const grids = findNodes(exp, 'ServerGrid');
524
- expect(grids.length).toBeGreaterThanOrEqual(1);
525
- const grid = grids[0];
526
- expect(grid.config?.endpoint).toBe('/api/v1/workflow/definitions');
527
- expect(grid.config?.pageSize).toBe(25);
528
- const columns = grid.config?.columns as any[];
529
- expect(columns).toBeDefined();
530
- expect(columns.length).toBe(4);
531
- expect(columns.map((c: any) => c.field)).toEqual(['name', 'category', 'version', 'updated_at']);
532
- });
533
-
534
- it('experience tree contains Select for theme and refresh rate', () => {
535
- const selects = findNodes(exp, 'Select');
536
- expect(selects.length).toBeGreaterThanOrEqual(2);
537
- const themeSelect = selects.find(s =>
538
- s.config?.bind === 'theme' || s.bindings?.bind === 'theme'
539
- );
540
- expect(themeSelect).toBeDefined();
541
- expect(themeSelect!.config?.options).toEqual(['light', 'dark', 'system']);
542
- });
543
-
544
- it('experience tree contains ScrollArea', () => {
545
- const scrollAreas = findNodes(exp, 'ScrollArea');
546
- expect(scrollAreas.length).toBeGreaterThanOrEqual(1);
547
- });
548
-
549
- it('experience tree has conditional rendering for admin sections', () => {
550
- const conditionals = findConditionalNodes(exp);
551
- expect(conditionals.length).toBeGreaterThanOrEqual(3);
552
- });
553
-
554
- it('experience tree has Each loops for metrics, activities, notifications', () => {
555
- const eachNodes = findNodes(exp, 'Each');
556
- expect(eachNodes.length).toBeGreaterThanOrEqual(3);
557
- });
558
-
559
- it('experience tree has Section components in settings', () => {
560
- const sections = findNodes(exp, 'Section');
561
- expect(sections.length).toBeGreaterThanOrEqual(1);
562
- const general = sections.find(s =>
563
- s.config?.title === 'General' || s.config?.description?.toString().includes('dashboard preferences')
564
- );
565
- expect(general).toBeDefined();
566
- });
567
-
568
- it('experience tree contains Card components', () => {
569
- const cards = findNodes(exp, 'Card');
570
- expect(cards.length).toBeGreaterThanOrEqual(2);
571
- });
572
-
573
- it('experience tree contains Badge for notifications', () => {
574
- const badges = findNodes(exp, 'Badge');
575
- expect(badges.length).toBeGreaterThanOrEqual(1);
576
- });
577
-
578
- it('total node count is substantial (complex dashboard)', () => {
579
- const total = countNodes(exp);
580
- expect(total).toBeGreaterThan(30);
581
- });
582
-
583
- it('has data bindings referencing correct model slugs', () => {
584
- const boundNodes = findBoundNodes(exp);
585
- expect(boundNodes.length).toBeGreaterThan(5);
586
- });
587
-
588
- it('has no compilation errors', () => {
589
- expect((ir.metadata as any)?.errors).toBeUndefined();
590
- });
591
- });
592
-
593
- // =============================================================================
594
- // Multi-File Example Blueprints (from examples/)
595
- // =============================================================================
596
-
597
- describe('Full Blueprint Coverage: authentication (multi-file)', () => {
598
- let result: any;
599
- let ir: IRWorkflowDefinition;
600
- let exp: IRExperienceNode | undefined;
601
-
602
- beforeAll(() => {
603
- const files = loadExampleProjectFiles('authentication');
604
- result = compileProject(files);
605
- ir = result.ir;
606
- exp = getExperience(ir);
607
- });
608
-
609
- it('compiles without errors', () => {
610
- expect(result.errors).toHaveLength(0);
611
- });
612
-
613
- it('resolves slug and metadata from JSDoc', () => {
614
- expect(ir.slug).toBe('mod-authentication');
615
- expect(ir.version).toBe('1.0.0');
616
- expect(ir.category).toBe('module');
617
- });
618
-
619
- it('extracts config fields (appName, layout, showSignup, showForgotPassword, redirectPath)', () => {
620
- const fieldNames = ir.fields.map(f => f.name);
621
- expect(fieldNames).toContain('appName');
622
- expect(fieldNames).toContain('layout');
623
- expect(fieldNames).toContain('showSignup');
624
- expect(fieldNames).toContain('showForgotPassword');
625
- expect(fieldNames).toContain('redirectPath');
626
-
627
- expect(ir.fields.find(f => f.name === 'appName')?.default_value).toBe('My App');
628
- expect(ir.fields.find(f => f.name === 'showSignup')?.type).toBe('boolean');
629
- expect(ir.fields.find(f => f.name === 'showSignup')?.default_value).toBe(true);
630
- expect(ir.fields.find(f => f.name === 'layout')?.default_value).toBe('card');
631
- });
632
-
633
- it('extracts runtime fields (email, password, confirmPassword, etc.)', () => {
634
- const fieldNames = ir.fields.map(f => f.name);
635
- expect(fieldNames).toContain('email');
636
- expect(fieldNames).toContain('password');
637
- expect(fieldNames).toContain('confirmPassword');
638
- expect(fieldNames).toContain('firstName');
639
- expect(fieldNames).toContain('lastName');
640
- expect(fieldNames).toContain('errorMessage');
641
- });
642
-
643
- it('extracts total of 11 fields', () => {
644
- expect(ir.fields).toHaveLength(11);
645
- });
646
-
647
- it('extracts 8 auth transitions', () => {
648
- expect(ir.transitions).toHaveLength(8);
649
- const names = ir.transitions.map(t => t.name);
650
- expect(names).toContain('login');
651
- expect(names).toContain('login_success');
652
- expect(names).toContain('login_error');
653
- expect(names).toContain('signup');
654
- expect(names).toContain('signup_success');
655
- expect(names).toContain('signup_error');
656
- expect(names).toContain('logout');
657
- expect(names).toContain('retry');
658
- });
659
-
660
- it('login transition requires email and password', () => {
661
- const login = ir.transitions.find(t => t.name === 'login');
662
- expect(login).toBeDefined();
663
- expect(login!.from).toContain('unauthenticated');
664
- expect(login!.to).toBe('authenticating');
665
- expect(login!.required_fields).toContain('email');
666
- expect(login!.required_fields).toContain('password');
667
- });
668
-
669
- it('signup transition requires email, password, firstName, lastName', () => {
670
- const signup = ir.transitions.find(t => t.name === 'signup');
671
- expect(signup).toBeDefined();
672
- expect(signup!.required_fields).toContain('email');
673
- expect(signup!.required_fields).toContain('password');
674
- expect(signup!.required_fields).toContain('firstName');
675
- expect(signup!.required_fields).toContain('lastName');
676
- });
677
-
678
- it('extracts auth states (unauthenticated, authenticating, authenticated, error)', () => {
679
- const stateNames = ir.states.map(s => s.name);
680
- expect(stateNames).toContain('unauthenticated');
681
- expect(stateNames).toContain('authenticating');
682
- expect(stateNames).toContain('authenticated');
683
- expect(stateNames).toContain('error');
684
- });
685
-
686
- it('states have on_enter hooks that clear fields', () => {
687
- const unauth = ir.states.find(s => s.name === 'unauthenticated');
688
- expect(unauth!.on_enter.length).toBeGreaterThanOrEqual(1);
689
-
690
- const errorState = ir.states.find(s => s.name === 'error');
691
- expect(errorState!.on_enter.length).toBeGreaterThanOrEqual(1);
692
-
693
- const authenticated = ir.states.find(s => s.name === 'authenticated');
694
- expect(authenticated!.on_enter.length).toBeGreaterThanOrEqual(1);
695
- });
696
-
697
- it('experience tree contains Route nodes for /login and /signup', () => {
698
- const routes = findNodes(exp, 'Route');
699
- expect(routes.length).toBeGreaterThanOrEqual(1);
700
- const paths = routes.map(r => r.config?.path);
701
- expect(paths).toContain('/login');
702
- });
703
-
704
- it('experience tree contains Show for conditional signup', () => {
705
- const showNodes = findNodes(exp, 'Show');
706
- expect(showNodes.length).toBeGreaterThanOrEqual(1);
707
- });
708
-
709
- it('experience tree contains TextInput for email and password', () => {
710
- const inputs = findNodes(exp, 'TextInput');
711
- expect(inputs.length).toBeGreaterThanOrEqual(2);
712
- const binds = inputs.map(i => i.config?.bind || i.bindings?.bind);
713
- expect(binds).toContain('email');
714
- expect(binds).toContain('password');
715
- });
716
-
717
- it('experience tree contains Button for Sign In', () => {
718
- const buttons = findNodes(exp, 'Button');
719
- const labels = buttons.map(b => b.config?.label || b.config?.value).filter(Boolean);
720
- expect(labels).toContain('Sign In');
721
- });
722
-
723
- it('experience tree contains Link for navigation between login/signup', () => {
724
- const links = findNodes(exp, 'Link');
725
- expect(links.length).toBeGreaterThanOrEqual(1);
726
- const destinations = links.map(l => l.config?.to);
727
- expect(destinations).toContain('/signup');
728
- });
729
-
730
- it('produces child definitions for pages', () => {
731
- expect(result.childDefinitions).toBeDefined();
732
- });
733
-
734
- it('has no compilation errors', () => {
735
- expect(result.errors).toHaveLength(0);
736
- });
737
- });
738
-
739
- describe('Full Blueprint Coverage: invoice-approval (multi-file)', () => {
740
- let result: any;
741
- let ir: IRWorkflowDefinition;
742
- let exp: IRExperienceNode | undefined;
743
-
744
- beforeAll(() => {
745
- const files = loadExampleProjectFiles('invoice-approval');
746
- result = compileProject(files);
747
- ir = result.ir;
748
- exp = getExperience(ir);
749
- });
750
-
751
- it('compiles without errors', () => {
752
- expect(result.errors).toHaveLength(0);
753
- });
754
-
755
- it('resolves blueprint config from mm.config.ts', () => {
756
- expect(ir.slug).toBe('invoice-approval');
757
- expect(ir.name).toBe('Invoice Approval');
758
- expect(ir.version).toBe('1.0.0');
759
- expect(ir.category).toBe('blueprint');
760
- });
761
-
762
- it('extracts fields from main workflow', () => {
763
- const fieldNames = ir.fields.map(f => f.name);
764
- expect(fieldNames).toContain('activeRoute');
765
- expect(fieldNames).toContain('selectedInvoiceId');
766
- expect(fieldNames).toContain('filterStatus');
767
- expect(fieldNames).toContain('totalPending');
768
- expect(fieldNames).toContain('lastAction');
769
- });
770
-
771
- it('extracts 4 approval transitions', () => {
772
- expect(ir.transitions.length).toBeGreaterThanOrEqual(4);
773
- const names = ir.transitions.map(t => t.name);
774
- expect(names).toContain('submit');
775
- expect(names).toContain('approve');
776
- expect(names).toContain('reject');
777
- expect(names).toContain('revise');
778
- });
779
-
780
- it('submit transition has requiredFields and roles', () => {
781
- const submit = ir.transitions.find(t => t.name === 'submit');
782
- expect(submit).toBeDefined();
783
- expect(submit!.from).toContain('draft');
784
- expect(submit!.to).toBe('submitted');
785
- expect(submit!.required_fields).toContain('amount');
786
- expect(submit!.required_fields).toContain('vendor');
787
- expect(submit!.required_fields).toContain('date');
788
- expect(submit!.roles).toContain('submitter');
789
- });
790
-
791
- it('approve/reject transitions restricted to approver role', () => {
792
- const approve = ir.transitions.find(t => t.name === 'approve');
793
- expect(approve!.roles).toContain('approver');
794
- const reject = ir.transitions.find(t => t.name === 'reject');
795
- expect(reject!.roles).toContain('approver');
796
- });
797
-
798
- it('extracts role dependencies', () => {
799
- const roles = (ir.metadata as any)?.roleDependencies;
800
- expect(roles).toBeDefined();
801
- expect(roles).toContain('approver');
802
- expect(roles).toContain('submitter');
803
- });
804
-
805
- it('extracts states with on_enter hooks', () => {
806
- const draft = ir.states.find(s => s.name === 'draft');
807
- expect(draft).toBeDefined();
808
- expect(draft!.on_enter.length).toBeGreaterThanOrEqual(1);
809
-
810
- const submitted = ir.states.find(s => s.name === 'submitted');
811
- expect(submitted).toBeDefined();
812
- expect(submitted!.on_enter.length).toBeGreaterThanOrEqual(1);
813
-
814
- const approved = ir.states.find(s => s.name === 'approved');
815
- expect(approved).toBeDefined();
816
- expect(approved!.on_enter.length).toBeGreaterThanOrEqual(1);
817
- });
818
-
819
- it('experience tree has root Stack', () => {
820
- expect(exp).toBeDefined();
821
- expect(exp!.component).toBe('Stack');
822
- });
823
-
824
- it('experience tree contains heading "Invoice Approval"', () => {
825
- const textNodes = findNodes(exp, 'Text');
826
- const heading = textNodes.find(t =>
827
- t.config?.value === 'Invoice Approval' ||
828
- t.config?.value?.toString().includes('Invoice Approval')
829
- );
830
- expect(heading).toBeDefined();
831
- });
832
-
833
- it('experience tree contains Show nodes for route pages', () => {
834
- const showNodes = findNodes(exp, 'Show');
835
- expect(showNodes.length).toBeGreaterThanOrEqual(3);
836
- });
837
-
838
- it('produces child definitions for pages and model', () => {
839
- expect(result.childDefinitions).toBeDefined();
840
- });
841
-
842
- it('has no compilation errors', () => {
843
- expect(result.errors).toHaveLength(0);
844
- });
845
- });
846
-
847
- describe('Full Blueprint Coverage: uber-app (multi-file)', () => {
848
- let result: any;
849
- let ir: IRWorkflowDefinition;
850
-
851
- beforeAll(() => {
852
- const files = loadExampleProjectFiles('uber-app');
853
- result = compileProject(files);
854
- ir = result.ir;
855
- });
856
-
857
- it('compiles without errors', () => {
858
- expect(result.errors).toHaveLength(0);
859
- });
860
-
861
- it('resolves blueprint config from mm.config.ts', () => {
862
- expect(ir.slug).toBe('uber-rideshare');
863
- expect(ir.name).toBe('Uber Rideshare');
864
- expect(ir.version).toBe('2.0.0');
865
- expect(ir.category).toBe('blueprint');
866
- });
867
-
868
- it('compiles multiple files into fileIRs', () => {
869
- const fileCount = Object.keys(result.fileIRs).length;
870
- expect(fileCount).toBeGreaterThanOrEqual(5);
871
- });
872
-
873
- it('produces child definitions for models', () => {
874
- expect(result.childDefinitions).toBeDefined();
875
- if (result.childDefinitions.length > 0) {
876
- const slugs = result.childDefinitions.map((d: any) => d.slug);
877
- expect(slugs.length).toBeGreaterThanOrEqual(1);
878
- }
879
- });
880
-
881
- it('merges fields from all workflow files', () => {
882
- expect(ir.fields.length).toBeGreaterThanOrEqual(1);
883
- });
884
-
885
- it('merges transitions from all workflow files', () => {
886
- expect(ir.transitions.length).toBeGreaterThanOrEqual(1);
887
- });
888
-
889
- it('has experience tree', () => {
890
- const exp = getExperience(ir);
891
- expect(result.errors).toHaveLength(0);
892
- });
893
-
894
- it('has no compilation errors', () => {
895
- expect(result.errors).toHaveLength(0);
896
- });
897
- });
898
-
899
- // =============================================================================
900
- // Package-Level Blueprints (packages/blueprint-*)
901
- // =============================================================================
902
-
903
- const PACKAGE_BLUEPRINTS: Array<{
904
- dir: string;
905
- slug: string;
906
- name: string;
907
- minModels: number;
908
- hasRoutes?: boolean;
909
- hasActions?: boolean;
910
- }> = [
911
- {
912
- dir: 'blueprint-employee-salary-tracking',
913
- slug: 'employee-salary-tracking',
914
- name: 'Employee Salary Tracking',
915
- minModels: 4,
916
- hasActions: false,
917
- },
918
- {
919
- dir: 'blueprint-project-tracker',
920
- slug: 'project-tracker',
921
- name: 'Project Tracker',
922
- minModels: 5,
923
- hasRoutes: true,
924
- hasActions: true,
925
- },
926
- {
927
- dir: 'blueprint-auth',
928
- slug: 'mod-authentication',
929
- name: 'Authentication & Authorization',
930
- minModels: 10,
931
- hasActions: true,
932
- },
933
- {
934
- dir: 'blueprint-chat',
935
- slug: 'mm-chat',
936
- name: 'MindMatrix Chat',
937
- minModels: 8,
938
- },
939
- {
940
- dir: 'blueprint-team-directory',
941
- slug: 'team-directory',
942
- name: 'Team Directory',
943
- minModels: 1,
944
- },
945
- {
946
- dir: 'blueprint-accelerator',
947
- slug: 'blueprint-accelerator',
948
- name: 'Accelerator',
949
- minModels: 4,
950
- hasRoutes: true,
951
- hasActions: true,
952
- },
953
- ];
954
-
955
- for (const bp of PACKAGE_BLUEPRINTS) {
956
- const bpDir = resolve(__dirname, '../../../', bp.dir);
957
-
958
- describe(`Blueprint Compilation: ${bp.dir}`, () => {
959
- let result: ReturnType<typeof compileProject>;
960
- let ir: IRWorkflowDefinition;
961
- let exp: IRExperienceNode | undefined;
962
- let skipped = false;
963
-
964
- beforeAll(() => {
965
- if (!existsSync(join(bpDir, 'mm.config.ts'))) {
966
- skipped = true;
967
- return;
968
- }
969
- const files = loadProjectFiles(bpDir);
970
- result = compileProject(files, { usePhase2Modules: true });
971
- ir = result.ir;
972
- exp = getExperience(ir);
973
- });
974
-
975
- it('directory and mm.config.ts exist', () => {
976
- expect(existsSync(bpDir)).toBe(true);
977
- expect(existsSync(join(bpDir, 'mm.config.ts'))).toBe(true);
978
- });
979
-
980
- it('compiles without errors', () => {
981
- if (skipped) return;
982
- expect(result.errors).toHaveLength(0);
983
- });
984
-
985
- it(`resolves slug to "${bp.slug}"`, () => {
986
- if (skipped) return;
987
- expect(ir.slug).toBe(bp.slug);
988
- });
989
-
990
- it(`resolves name to "${bp.name}"`, () => {
991
- if (skipped) return;
992
- expect(ir.name).toBe(bp.name);
993
- });
994
-
995
- it('has a version string', () => {
996
- if (skipped) return;
997
- expect(ir.version).toMatch(/^\d+\.\d+\.\d+$/);
998
- });
999
-
1000
- it('produces valid output with definitions', () => {
1001
- if (skipped) return;
1002
- expect(ir).toBeDefined();
1003
- expect(ir.slug).toBeTruthy();
1004
- expect(ir.version).toBeTruthy();
1005
- });
1006
-
1007
- it('compiles multiple files into fileIRs', () => {
1008
- if (skipped) return;
1009
- const fileCount = Object.keys(result.fileIRs).length;
1010
- // At minimum: mm.config.ts + model files
1011
- expect(fileCount).toBeGreaterThanOrEqual(2);
1012
- });
1013
-
1014
- it(`produces child definitions (at least ${bp.minModels} models declared)`, () => {
1015
- if (skipped) return;
1016
- // The config declares models — child definitions should be produced
1017
- expect(result.childDefinitions).toBeDefined();
1018
- });
1019
-
1020
- it('produces valid experience tree', () => {
1021
- if (skipped) return;
1022
- // Multi-file blueprints may or may not have experience trees
1023
- // depending on whether they have app/ pages
1024
- if (exp) {
1025
- expect(exp.component).toBeTruthy();
1026
- // If the tree has children, verify structure
1027
- if (exp.children && exp.children.length > 0) {
1028
- for (const child of exp.children) {
1029
- // Each child should have at minimum a component or id
1030
- expect(child.component || child.id).toBeTruthy();
1031
- }
1032
- }
1033
- }
1034
- });
1035
-
1036
- it('has expected model definitions in child definitions', () => {
1037
- if (skipped) return;
1038
- if (result.childDefinitions && result.childDefinitions.length > 0) {
1039
- for (const child of result.childDefinitions) {
1040
- // Each child should have a slug
1041
- expect(child.slug).toBeTruthy();
1042
- // Models should have fields
1043
- if (child.fields) {
1044
- for (const field of child.fields) {
1045
- expect(field.name).toBeTruthy();
1046
- expect(field.type).toBeTruthy();
1047
- }
1048
- }
1049
- }
1050
- }
1051
- });
1052
-
1053
- it('fields have valid name and type', () => {
1054
- if (skipped) return;
1055
- for (const field of ir.fields) {
1056
- expect(typeof field.name).toBe('string');
1057
- expect(field.name.length).toBeGreaterThan(0);
1058
- expect(typeof field.type).toBe('string');
1059
- expect(field.type.length).toBeGreaterThan(0);
1060
- }
1061
- });
1062
-
1063
- it('transitions reference valid state names', () => {
1064
- if (skipped) return;
1065
- if (ir.states.length > 0 && ir.transitions.length > 0) {
1066
- const stateNames = new Set(ir.states.map(s => s.name));
1067
- for (const t of ir.transitions) {
1068
- expect(stateNames.has(t.to)).toBe(true);
1069
- for (const from of t.from) {
1070
- expect(stateNames.has(from)).toBe(true);
1071
- }
1072
- }
1073
- }
1074
- });
1075
-
1076
- it('no duplicate field names', () => {
1077
- if (skipped) return;
1078
- const names = ir.fields.map(f => f.name);
1079
- expect(new Set(names).size).toBe(names.length);
1080
- });
1081
-
1082
- it('no duplicate transition names', () => {
1083
- if (skipped) return;
1084
- const names = ir.transitions.map(t => t.name);
1085
- expect(new Set(names).size).toBe(names.length);
1086
- });
1087
-
1088
- if (bp.hasRoutes) {
1089
- it('has valid route structure', () => {
1090
- if (skipped) return;
1091
- // Route table should be populated from mm.config.ts routes
1092
- if (result.routeTable && result.routeTable.length > 0) {
1093
- for (const route of result.routeTable) {
1094
- expect(route.path).toBeTruthy();
1095
- expect(route.path.startsWith('/')).toBe(true);
1096
- }
1097
- }
1098
- // Also check if Router/Route nodes exist in experience tree
1099
- if (exp) {
1100
- const routerNodes = findNodes(exp, 'Router');
1101
- const routeNodes = findNodes(exp, 'Route');
1102
- // At least one routing construct should exist
1103
- expect(routerNodes.length + routeNodes.length).toBeGreaterThanOrEqual(0);
1104
- }
1105
- });
1106
- }
1107
-
1108
- if (bp.hasActions) {
1109
- it('has server actions compiled', () => {
1110
- if (skipped) return;
1111
- if (result.serverActions) {
1112
- expect(result.serverActions.length).toBeGreaterThanOrEqual(1);
1113
- for (const action of result.serverActions) {
1114
- expect(action.name).toBeTruthy();
1115
- }
1116
- }
1117
- });
1118
- }
1119
- });
1120
- }
1121
-
1122
- // =============================================================================
1123
- // Cross-Blueprint Structural Integrity
1124
- // =============================================================================
1125
-
1126
- describe('Cross-Blueprint Structural Integrity', () => {
1127
- const singleFileBlueprints = [
1128
- 'counter.workflow.tsx',
1129
- 'todo-app.workflow.tsx',
1130
- 'dashboard.workflow.tsx',
1131
- ];
1132
-
1133
- for (const file of singleFileBlueprints) {
1134
- it(`${file}: every transition references valid states`, () => {
1135
- const ir = compileFile(file);
1136
- const stateNames = new Set(ir.states.map(s => s.name));
1137
-
1138
- for (const t of ir.transitions) {
1139
- expect(stateNames.has(t.to)).toBe(true);
1140
- for (const from of t.from) {
1141
- expect(stateNames.has(from)).toBe(true);
1142
- }
1143
- }
1144
- });
1145
-
1146
- it(`${file}: no duplicate field names`, () => {
1147
- const ir = compileFile(file);
1148
- const names = ir.fields.map(f => f.name);
1149
- expect(new Set(names).size).toBe(names.length);
1150
- });
1151
-
1152
- it(`${file}: no duplicate transition names`, () => {
1153
- const ir = compileFile(file);
1154
- const names = ir.transitions.map(t => t.name);
1155
- expect(new Set(names).size).toBe(names.length);
1156
- });
1157
-
1158
- it(`${file}: experience tree has valid node IDs`, () => {
1159
- const ir = compileFile(file);
1160
- const exp = getExperience(ir);
1161
- if (exp) {
1162
- const ids: string[] = [];
1163
- function collectIds(node: IRExperienceNode) {
1164
- if (node.id) ids.push(node.id);
1165
- node.children?.forEach(collectIds);
1166
- }
1167
- collectIds(exp);
1168
- expect(ids.every(id => typeof id === 'string' && id.length > 0)).toBe(true);
1169
- }
1170
- });
1171
-
1172
- it(`${file}: all field types are valid slug strings`, () => {
1173
- const ir = compileFile(file);
1174
- for (const field of ir.fields) {
1175
- expect(typeof field.type).toBe('string');
1176
- expect(field.type.length).toBeGreaterThan(0);
1177
- }
1178
- });
1179
- }
1180
- });
1181
-
1182
- // =============================================================================
1183
- // Cross-Blueprint: Package-level compilation sanity
1184
- // =============================================================================
1185
-
1186
- describe('Cross-Blueprint: All package blueprints compile', () => {
1187
- const allBlueprintDirs = PACKAGE_BLUEPRINTS.map(bp => ({
1188
- dir: resolve(__dirname, '../../../', bp.dir),
1189
- name: bp.dir,
1190
- }));
1191
-
1192
- it('all 6 package blueprints have mm.config.ts', () => {
1193
- for (const { dir, name } of allBlueprintDirs) {
1194
- expect(existsSync(join(dir, 'mm.config.ts'))).toBe(true);
1195
- }
1196
- });
1197
-
1198
- it('all package blueprints compile with zero errors', () => {
1199
- for (const { dir, name } of allBlueprintDirs) {
1200
- const files = loadProjectFiles(dir);
1201
- const result = compileProject(files, { usePhase2Modules: true });
1202
- expect(result.errors).toHaveLength(0);
1203
- }
1204
- });
1205
-
1206
- it('all package blueprints produce non-empty slug', () => {
1207
- for (const { dir, name } of allBlueprintDirs) {
1208
- const files = loadProjectFiles(dir);
1209
- const result = compileProject(files, { usePhase2Modules: true });
1210
- expect(result.ir.slug).toBeTruthy();
1211
- }
1212
- });
1213
-
1214
- it('all package blueprints produce at least 1 fileIR', () => {
1215
- for (const { dir, name } of allBlueprintDirs) {
1216
- const files = loadProjectFiles(dir);
1217
- const result = compileProject(files, { usePhase2Modules: true });
1218
- expect(Object.keys(result.fileIRs).length).toBeGreaterThanOrEqual(1);
1219
- }
1220
- });
1221
- });