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

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 (265) hide show
  1. package/ATOM-PIPELINE.md +144 -0
  2. package/README.md +88 -40
  3. package/dist/babel/index.js +2814 -277
  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-52XHYD2V.mjs +214 -0
  11. package/dist/chunk-5GUFFFGL.mjs +148 -0
  12. package/dist/chunk-5RKTOVR5.mjs +244 -0
  13. package/dist/chunk-5YDMOO4X.mjs +214 -0
  14. package/dist/chunk-64ZWEMLJ.mjs +148 -0
  15. package/dist/chunk-6XP4KSWQ.mjs +2190 -0
  16. package/dist/chunk-72QWL54I.mjs +175 -0
  17. package/dist/chunk-7B4TRI7C.mjs +4835 -0
  18. package/dist/chunk-7ZKGHTNB.mjs +4952 -0
  19. package/dist/chunk-CIESM3BP.mjs +33 -0
  20. package/dist/chunk-DE3ZGQAC.mjs +148 -0
  21. package/dist/chunk-DMCY3BBG.mjs +1933 -0
  22. package/dist/chunk-DPIK3PJS.mjs +244 -0
  23. package/dist/chunk-E5IVH4RE.mjs +186 -0
  24. package/dist/chunk-E6FZNUR5.mjs +4953 -0
  25. package/dist/chunk-EJRBDQDP.mjs +2607 -0
  26. package/dist/chunk-ELO4TXJL.mjs +186 -0
  27. package/dist/chunk-EO6SYNCG.mjs +175 -0
  28. package/dist/chunk-FKRO52XH.mjs +3446 -0
  29. package/dist/chunk-FL4YAKU6.mjs +4941 -0
  30. package/dist/chunk-FYT47UBU.mjs +5076 -0
  31. package/dist/chunk-GCLGPOJZ.mjs +148 -0
  32. package/dist/chunk-GXB4JOP7.mjs +5072 -0
  33. package/dist/chunk-HFXOUMTD.mjs +175 -0
  34. package/dist/chunk-HWIZ47US.mjs +214 -0
  35. package/dist/chunk-IB7MNPQL.mjs +4953 -0
  36. package/dist/chunk-ICSIHQCG.mjs +148 -0
  37. package/dist/chunk-J7JUAHS4.mjs +186 -0
  38. package/dist/chunk-JLA5VNQ3.mjs +186 -0
  39. package/dist/chunk-JQLWFCTM.mjs +214 -0
  40. package/dist/chunk-KFJJCQAL.mjs +148 -0
  41. package/dist/chunk-KJUIIEQE.mjs +186 -0
  42. package/dist/chunk-KNWTHRVQ.mjs +175 -0
  43. package/dist/chunk-KSG4XSZF.mjs +175 -0
  44. package/dist/chunk-LF5N6DOU.mjs +175 -0
  45. package/dist/chunk-LJQCM2IM.mjs +214 -0
  46. package/dist/chunk-NTB7OEX2.mjs +2918 -0
  47. package/dist/chunk-NW6555WJ.mjs +186 -0
  48. package/dist/chunk-OMZE6VLQ.mjs +214 -0
  49. package/dist/chunk-OPJKP747.mjs +7506 -0
  50. package/dist/chunk-P4BR7WVO.mjs +2190 -0
  51. package/dist/chunk-QQHVYH2X.mjs +244 -0
  52. package/dist/chunk-S5QLWLLT.mjs +186 -0
  53. package/dist/chunk-SCWGT2FY.mjs +2190 -0
  54. package/dist/chunk-SMKJUSB3.mjs +2190 -0
  55. package/dist/chunk-THFYE5ZX.mjs +244 -0
  56. package/dist/chunk-VCAY2KGM.mjs +175 -0
  57. package/dist/chunk-WBYMW4NQ.mjs +3450 -0
  58. package/dist/chunk-WECAV6QB.mjs +148 -0
  59. package/dist/chunk-WMKBXUCE.mjs +3228 -0
  60. package/dist/chunk-XAJ5BKKL.mjs +4947 -0
  61. package/dist/chunk-XG2X7AEA.mjs +175 -0
  62. package/dist/chunk-XG7Z23NQ.mjs +148 -0
  63. package/dist/chunk-XWZAOCQ7.mjs +2607 -0
  64. package/dist/chunk-Y6MA7ULW.mjs +148 -0
  65. package/dist/chunk-YMS7Q7LG.mjs +214 -0
  66. package/dist/chunk-ZA37XTGA.mjs +175 -0
  67. package/dist/cli/index.js +13189 -6838
  68. package/dist/cli/index.mjs +140 -22
  69. package/dist/codemod/cli.mjs +1 -1
  70. package/dist/codemod/index.mjs +1 -1
  71. package/dist/config-PL24KEWL.mjs +219 -0
  72. package/dist/dev-server-RmGHIntF.d.mts +113 -0
  73. package/dist/dev-server-RmGHIntF.d.ts +113 -0
  74. package/dist/dev-server.d.mts +1 -1
  75. package/dist/dev-server.d.ts +1 -1
  76. package/dist/dev-server.js +4135 -440
  77. package/dist/dev-server.mjs +5 -5
  78. package/dist/envelope.js +2812 -275
  79. package/dist/envelope.mjs +3 -3
  80. package/dist/index.d.mts +161 -2
  81. package/dist/index.d.ts +161 -2
  82. package/dist/index.js +4429 -428
  83. package/dist/index.mjs +217 -9
  84. package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
  85. package/dist/init-DQDX3QK6.mjs +369 -0
  86. package/dist/init-EHO4VQ22.mjs +369 -0
  87. package/dist/init-UC3FWPIW.mjs +367 -0
  88. package/dist/init-UNSMVKIK.mjs +366 -0
  89. package/dist/init-UNV5XIDE.mjs +367 -0
  90. package/dist/project-compiler-2P4N4DR7.mjs +10 -0
  91. package/dist/project-compiler-D2LCC27O.mjs +10 -0
  92. package/dist/project-compiler-EJ3GANJE.mjs +10 -0
  93. package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
  94. package/dist/project-compiler-OP2VVGJQ.mjs +10 -0
  95. package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
  96. package/dist/project-compiler-VWNNCHGO.mjs +10 -0
  97. package/dist/project-compiler-XVAAU4C5.mjs +10 -0
  98. package/dist/project-compiler-YES5FGMD.mjs +10 -0
  99. package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
  100. package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
  101. package/dist/project-decompiler-US7GAVIC.mjs +7 -0
  102. package/dist/project-decompiler-VLPR22QF.mjs +7 -0
  103. package/dist/pull-FUS5QYZS.mjs +109 -0
  104. package/dist/pull-LD5ENLGY.mjs +109 -0
  105. package/dist/pull-P44LDRWB.mjs +109 -0
  106. package/dist/testing/index.js +2822 -285
  107. package/dist/testing/index.mjs +2 -2
  108. package/dist/verify-SEIXUGN4.mjs +1833 -0
  109. package/dist/vite/index.js +2815 -278
  110. package/dist/vite/index.mjs +3 -3
  111. package/examples/uber-app/app/admin/fleet.tsx +19 -19
  112. package/package.json +16 -6
  113. package/compile-blueprint-chat.mjs +0 -99
  114. package/compile-blueprint-glass-console.mjs +0 -98
  115. package/compile-chat-defs.mjs +0 -92
  116. package/examples/uber-app/tests/payment.test.tsx +0 -129
  117. package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
  118. package/package.json.backup +0 -86
  119. package/scripts/decompile.ts +0 -226
  120. package/scripts/seed-auth.ts +0 -267
  121. package/scripts/seed-uber.ts +0 -248
  122. package/scripts/validate-uber.ts +0 -119
  123. package/seed-blueprint-chat.mjs +0 -444
  124. package/seed-blueprint-glass-console.mjs +0 -445
  125. package/seed-compiled.mjs +0 -318
  126. package/src/RoundTripValidator.ts +0 -400
  127. package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
  128. package/src/__tests__/auth-module-compilation.test.ts +0 -247
  129. package/src/__tests__/auth-template-compilation.test.ts +0 -589
  130. package/src/__tests__/change-extractor.test.ts +0 -142
  131. package/src/__tests__/cli-pull.test.ts +0 -73
  132. package/src/__tests__/cli-test.test.ts +0 -72
  133. package/src/__tests__/component-extractor.test.ts +0 -331
  134. package/src/__tests__/context-extractor.test.ts +0 -145
  135. package/src/__tests__/decompiler.test.ts +0 -718
  136. package/src/__tests__/define-blueprint.test.ts +0 -133
  137. package/src/__tests__/definition-validator.test.ts +0 -519
  138. package/src/__tests__/during-extractor.test.ts +0 -152
  139. package/src/__tests__/effect-extractor.test.ts +0 -107
  140. package/src/__tests__/event-emission.test.ts +0 -127
  141. package/src/__tests__/examples.test.ts +0 -236
  142. package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
  143. package/src/__tests__/golden-suite.test.ts +0 -403
  144. package/src/__tests__/grammar-island-extractor.test.ts +0 -289
  145. package/src/__tests__/instance-key.test.ts +0 -82
  146. package/src/__tests__/ir-migration.test.ts +0 -255
  147. package/src/__tests__/lock-file.test.ts +0 -117
  148. package/src/__tests__/model-extractor.test.ts +0 -195
  149. package/src/__tests__/model-field-acl.test.ts +0 -237
  150. package/src/__tests__/model-hooks.test.ts +0 -130
  151. package/src/__tests__/model-ref-resolution.test.ts +0 -268
  152. package/src/__tests__/model-roundtrip.test.ts +0 -502
  153. package/src/__tests__/model-runtime.test.ts +0 -112
  154. package/src/__tests__/model-transitions.test.ts +0 -183
  155. package/src/__tests__/nrt-action-trace.test.ts +0 -391
  156. package/src/__tests__/pipeline-hardening.test.ts +0 -413
  157. package/src/__tests__/project-compiler.test.ts +0 -546
  158. package/src/__tests__/project-decompiler.test.ts +0 -343
  159. package/src/__tests__/query-compilation.test.ts +0 -145
  160. package/src/__tests__/round-trip/PLAN.md +0 -158
  161. package/src/__tests__/round-trip/README.md +0 -52
  162. package/src/__tests__/round-trip/RESULTS.md +0 -86
  163. package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
  164. package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
  165. package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
  166. package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
  167. package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
  168. package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
  169. package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
  170. package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
  171. package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
  172. package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
  173. package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
  174. package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
  175. package/src/__tests__/round-trip-ir.test.ts +0 -300
  176. package/src/__tests__/round-trip.test.ts +0 -1212
  177. package/src/__tests__/route-merging.test.ts +0 -372
  178. package/src/__tests__/router-composition.test.ts +0 -489
  179. package/src/__tests__/router-extractor.test.ts +0 -176
  180. package/src/__tests__/server-action-extractor.test.ts +0 -128
  181. package/src/__tests__/smart-type-inference.test.ts +0 -365
  182. package/src/__tests__/source-envelope.test.ts +0 -284
  183. package/src/__tests__/source-fidelity.test.ts +0 -516
  184. package/src/__tests__/state-extractor.test.ts +0 -115
  185. package/src/__tests__/strict-mode.test.ts +0 -227
  186. package/src/__tests__/transition-effect-extractor.test.ts +0 -119
  187. package/src/__tests__/transition-extractor.test.ts +0 -68
  188. package/src/__tests__/ts-to-expression.test.ts +0 -462
  189. package/src/__tests__/type-generator.test.ts +0 -201
  190. package/src/__tests__/uber-validation.test.ts +0 -502
  191. package/src/action-compiler.ts +0 -361
  192. package/src/babel/emitters/experience-transform.ts +0 -199
  193. package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
  194. package/src/babel/emitters/pure-form-emitter.ts +0 -1023
  195. package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
  196. package/src/babel/extractors/change-extractor.ts +0 -199
  197. package/src/babel/extractors/component-extractor.ts +0 -907
  198. package/src/babel/extractors/computed-extractor.ts +0 -262
  199. package/src/babel/extractors/context-extractor.ts +0 -277
  200. package/src/babel/extractors/during-extractor.ts +0 -295
  201. package/src/babel/extractors/effect-extractor.ts +0 -340
  202. package/src/babel/extractors/event-extractor.ts +0 -235
  203. package/src/babel/extractors/grammar-island-extractor.ts +0 -302
  204. package/src/babel/extractors/model-extractor.ts +0 -1018
  205. package/src/babel/extractors/router-extractor.ts +0 -303
  206. package/src/babel/extractors/server-action-extractor.ts +0 -173
  207. package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
  208. package/src/babel/extractors/server-state-extractor.ts +0 -88
  209. package/src/babel/extractors/state-extractor.ts +0 -214
  210. package/src/babel/extractors/transition-effect-extractor.ts +0 -176
  211. package/src/babel/extractors/transition-extractor.ts +0 -143
  212. package/src/babel/index.ts +0 -24
  213. package/src/babel/transpilers/ts-to-expression.ts +0 -674
  214. package/src/babel/visitor.ts +0 -807
  215. package/src/cli/auth.ts +0 -255
  216. package/src/cli/build.ts +0 -288
  217. package/src/cli/deploy.ts +0 -206
  218. package/src/cli/index.ts +0 -328
  219. package/src/cli/installer.ts +0 -261
  220. package/src/cli/lock-file.ts +0 -94
  221. package/src/cli/mmrc.ts +0 -22
  222. package/src/cli/pull.ts +0 -172
  223. package/src/cli/registry-client.ts +0 -175
  224. package/src/cli/test.ts +0 -397
  225. package/src/cli/type-generator.ts +0 -243
  226. package/src/codemod/__tests__/forward.test.ts +0 -239
  227. package/src/codemod/__tests__/reverse.test.ts +0 -145
  228. package/src/codemod/__tests__/round-trip.test.ts +0 -137
  229. package/src/codemod/annotation.ts +0 -97
  230. package/src/codemod/classify.ts +0 -197
  231. package/src/codemod/cli.ts +0 -207
  232. package/src/codemod/control-flow.ts +0 -409
  233. package/src/codemod/forward.ts +0 -244
  234. package/src/codemod/import-manager.ts +0 -171
  235. package/src/codemod/index.ts +0 -120
  236. package/src/codemod/reverse.ts +0 -197
  237. package/src/codemod/rules.ts +0 -174
  238. package/src/codemod/state-transform.ts +0 -126
  239. package/src/decompiler/ast-builder.ts +0 -538
  240. package/src/decompiler/config-generator.ts +0 -151
  241. package/src/decompiler/index.ts +0 -315
  242. package/src/decompiler/project-decompiler.ts +0 -1776
  243. package/src/decompiler/project.ts +0 -862
  244. package/src/decompiler/split-strategy.ts +0 -140
  245. package/src/decompiler/state-emitter.ts +0 -1053
  246. package/src/decompiler/sx-emitter.ts +0 -318
  247. package/src/decompiler/workspace-hydrator.ts +0 -189
  248. package/src/dev-server.ts +0 -238
  249. package/src/envelope/fs-tree.ts +0 -217
  250. package/src/envelope/source-envelope.ts +0 -264
  251. package/src/envelope.ts +0 -315
  252. package/src/incremental-compiler.ts +0 -401
  253. package/src/index.ts +0 -99
  254. package/src/model-compiler.ts +0 -277
  255. package/src/project-compiler.ts +0 -1629
  256. package/src/route-extractor.ts +0 -333
  257. package/src/testing/index.ts +0 -32
  258. package/src/testing/snapshot.ts +0 -252
  259. package/src/testing/test-utils.ts +0 -226
  260. package/src/types.ts +0 -68
  261. package/src/vite/index.ts +0 -288
  262. package/test-compile.mjs +0 -142
  263. package/tsconfig.json +0 -25
  264. package/tsup.config.ts +0 -23
  265. 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
- });