@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,718 +0,0 @@
1
- /**
2
- * Decompiler Tests — verifies IR → .workflow.tsx output AND round-trip fidelity.
3
- *
4
- * Two test categories:
5
- * 1. Unit tests: IR → TSX output correctness (structure, imports, hooks)
6
- * 2. Round-trip tests: IR → decompile → recompile → compare IR equivalence
7
- */
8
-
9
- import { describe, it, expect } from 'vitest';
10
- import { decompile } from '../decompiler';
11
- import type { DecompilerInput } from '../decompiler';
12
- import { decompileToTSX, normalizeDecompilerInput } from '../babel/emitters/ir-to-tsx-emitter';
13
- import { validateRoundTrip } from '../RoundTripValidator';
14
- import type { IRWorkflowDefinition } from '@mindmatrix/player-core';
15
-
16
- function makeDefinition(overrides: Partial<DecompilerInput> = {}): DecompilerInput {
17
- return {
18
- slug: 'test-workflow',
19
- name: 'Test Workflow',
20
- version: '1.0.0',
21
- description: 'A test workflow',
22
- category: 'workflow',
23
- fields: [],
24
- states: [],
25
- transitions: [],
26
- roles: [],
27
- tags: [],
28
- metadata: {},
29
- ...overrides,
30
- };
31
- }
32
-
33
- describe('Decompiler', () => {
34
- describe('basic output', () => {
35
- it('should produce valid TSX with annotation and export', () => {
36
- const result = decompile(makeDefinition());
37
- expect(result.code).toContain('@workflow');
38
- expect(result.code).toContain('slug="test-workflow"');
39
- expect(result.code).toContain('export default function TestWorkflow');
40
- expect(result.code).toContain('return null');
41
- expect(result.componentName).toBe('TestWorkflow');
42
- });
43
-
44
- it('should use custom component name', () => {
45
- const result = decompile(makeDefinition(), { componentName: 'MyApp' });
46
- expect(result.code).toContain('function MyApp');
47
- expect(result.componentName).toBe('MyApp');
48
- });
49
-
50
- it('should omit annotation when disabled', () => {
51
- const result = decompile(makeDefinition(), { includeAnnotation: false });
52
- expect(result.code).not.toContain('@workflow');
53
- });
54
- });
55
-
56
- describe('field declarations', () => {
57
- it('should emit useState for text fields', () => {
58
- const result = decompile(makeDefinition({
59
- fields: [
60
- { name: 'user_name', type: 'text', required: false, default_value: 'Alice' },
61
- ],
62
- }));
63
- expect(result.code).toContain('useState');
64
- expect(result.code).toContain('userName');
65
- expect(result.code).toContain('setUserName');
66
- expect(result.code).toContain('"Alice"');
67
- expect(result.imports).toContain('@mindmatrix/react');
68
- });
69
-
70
- it('should emit useState for number fields with type annotation', () => {
71
- const result = decompile(makeDefinition({
72
- fields: [
73
- { name: 'count', type: 'number', required: false, default_value: 0 },
74
- ],
75
- }));
76
- expect(result.code).toContain('useState<number>');
77
- expect(result.code).toContain('count');
78
- expect(result.code).toContain('setCount');
79
- });
80
-
81
- it('should emit useState for boolean fields', () => {
82
- const result = decompile(makeDefinition({
83
- fields: [
84
- { name: 'is_active', type: 'boolean', required: false, default_value: false },
85
- ],
86
- }));
87
- expect(result.code).toContain('useState');
88
- expect(result.code).toContain('isActive');
89
- expect(result.code).toContain('false');
90
- });
91
-
92
- it('should skip computed fields', () => {
93
- const result = decompile(makeDefinition({
94
- fields: [
95
- { name: 'total', type: 'number', required: false, computed: 'price * quantity' },
96
- ],
97
- }));
98
- expect(result.code).not.toContain('total');
99
- });
100
- });
101
-
102
- describe('state effects', () => {
103
- it('should emit useOnEnter for states with on_enter actions', () => {
104
- const result = decompile(makeDefinition({
105
- states: [{
106
- name: 'active',
107
- type: 'REGULAR',
108
- description: '',
109
- on_enter: [{ id: 'a1', type: 'set_field', mode: 'sync', config: { field: 'status', expression: '"active"' } }],
110
- during: [],
111
- on_exit: [],
112
- }],
113
- }));
114
- expect(result.code).toContain('useOnEnter');
115
- expect(result.code).toContain('"active"');
116
- expect(result.code).toContain('setStatus');
117
- });
118
-
119
- it('should emit useOnExit for states with on_exit actions', () => {
120
- const result = decompile(makeDefinition({
121
- states: [{
122
- name: 'active',
123
- type: 'REGULAR',
124
- description: '',
125
- on_enter: [],
126
- during: [],
127
- on_exit: [{ id: 'a2', type: 'log_event', mode: 'sync', config: { message: 'Exiting active state' } }],
128
- }],
129
- }));
130
- expect(result.code).toContain('useOnExit');
131
- expect(result.code).toContain('console.log');
132
- expect(result.code).toContain('Exiting active state');
133
- });
134
-
135
- it('should emit useWhileIn for during actions', () => {
136
- const result = decompile(makeDefinition({
137
- states: [{
138
- name: 'polling',
139
- type: 'REGULAR',
140
- description: '',
141
- on_enter: [],
142
- on_exit: [],
143
- during: [{
144
- id: 'd1',
145
- type: 'interval',
146
- interval_ms: 5000,
147
- actions: [{ id: 'a3', type: 'log_event', mode: 'sync', config: { message: 'polling...' } }],
148
- }],
149
- }],
150
- }));
151
- expect(result.code).toContain('useWhileIn');
152
- expect(result.code).toContain('"polling"');
153
- expect(result.code).toContain('5000');
154
- });
155
- });
156
-
157
- describe('transitions', () => {
158
- it('should emit useTransition with from/to', () => {
159
- const result = decompile(makeDefinition({
160
- transitions: [{
161
- name: 'submit',
162
- from: ['draft'],
163
- to: 'pending',
164
- actions: [],
165
- conditions: [],
166
- }],
167
- }));
168
- expect(result.code).toContain('useTransition');
169
- expect(result.code).toContain('"submit"');
170
- expect(result.code).toContain('from: "draft"');
171
- expect(result.code).toContain('to: "pending"');
172
- });
173
-
174
- it('should emit conditions as when prop', () => {
175
- const result = decompile(makeDefinition({
176
- transitions: [{
177
- name: 'approve',
178
- from: ['pending'],
179
- to: 'approved',
180
- actions: [],
181
- conditions: [{ field: 'amount', operator: 'gt', value: 1000 }],
182
- }],
183
- }));
184
- expect(result.code).toContain('when:');
185
- expect(result.code).toContain('amount > 1000');
186
- });
187
-
188
- it('should emit roles array', () => {
189
- const result = decompile(makeDefinition({
190
- transitions: [{
191
- name: 'approve',
192
- from: ['pending'],
193
- to: 'approved',
194
- actions: [],
195
- conditions: [],
196
- roles: ['admin', 'manager'],
197
- }],
198
- }));
199
- expect(result.code).toContain('roles:');
200
- expect(result.code).toContain('"admin"');
201
- expect(result.code).toContain('"manager"');
202
- });
203
- });
204
-
205
- describe('action types', () => {
206
- it('should emit emit_event as emitEvent()', () => {
207
- const result = decompile(makeDefinition({
208
- states: [{
209
- name: 's1',
210
- type: 'REGULAR',
211
- description: '',
212
- on_enter: [{ id: 'a1', type: 'emit_event', mode: 'sync', config: { event: 'order.placed', payload: { orderId: 123 } } }],
213
- during: [],
214
- on_exit: [],
215
- }],
216
- }));
217
- expect(result.code).toContain('emitEvent');
218
- expect(result.code).toContain('"order.placed"');
219
- });
220
-
221
- it('should emit call_workflow as callWorkflow()', () => {
222
- const result = decompile(makeDefinition({
223
- states: [{
224
- name: 's1',
225
- type: 'REGULAR',
226
- description: '',
227
- on_enter: [{ id: 'a1', type: 'call_workflow', mode: 'sync', config: { slug: 'calculate-tax' } }],
228
- during: [],
229
- on_exit: [],
230
- }],
231
- }));
232
- expect(result.code).toContain('callWorkflow');
233
- expect(result.code).toContain('"calculate-tax"');
234
- });
235
-
236
- it('should emit spawn_instance as spawnInstance()', () => {
237
- const result = decompile(makeDefinition({
238
- states: [{
239
- name: 's1',
240
- type: 'REGULAR',
241
- description: '',
242
- on_enter: [{ id: 'a1', type: 'spawn_instance', mode: 'async', config: { slug: 'child-workflow' } }],
243
- during: [],
244
- on_exit: [],
245
- }],
246
- }));
247
- expect(result.code).toContain('spawnInstance');
248
- expect(result.code).toContain('"child-workflow"');
249
- });
250
-
251
- it('should emit http_request as fetch()', () => {
252
- const result = decompile(makeDefinition({
253
- states: [{
254
- name: 's1',
255
- type: 'REGULAR',
256
- description: '',
257
- on_enter: [{ id: 'a1', type: 'http_request', mode: 'async', config: { url: 'https://api.example.com', method: 'POST' } }],
258
- during: [],
259
- on_exit: [],
260
- }],
261
- }));
262
- expect(result.code).toContain('fetch');
263
- expect(result.code).toContain('"https://api.example.com"');
264
- expect(result.code).toContain('"POST"');
265
- });
266
-
267
- it('should emit unknown actions as commented void expressions', () => {
268
- const result = decompile(makeDefinition({
269
- states: [{
270
- name: 's1',
271
- type: 'REGULAR',
272
- description: '',
273
- on_enter: [{ id: 'a1', type: 'custom_action', mode: 'sync', config: { key: 'value' } }],
274
- during: [],
275
- on_exit: [],
276
- }],
277
- }));
278
- expect(result.code).toContain('void 0');
279
- expect(result.code).toContain('custom_action');
280
- });
281
-
282
- it('should skip noop actions', () => {
283
- const result = decompile(makeDefinition({
284
- states: [{
285
- name: 's1',
286
- type: 'REGULAR',
287
- description: '',
288
- on_enter: [{ id: 'a1', type: 'noop', mode: 'sync', config: {} }],
289
- during: [],
290
- on_exit: [],
291
- }],
292
- }));
293
- // noop should produce an empty callback (just ;)
294
- expect(result.code).not.toContain('noop');
295
- });
296
- });
297
-
298
- describe('binding expressions', () => {
299
- it('should handle dot-path expressions in JSX bindings', () => {
300
- const result = decompile(makeDefinition({
301
- experience: {
302
- id: 'root',
303
- component: 'Text',
304
- children: [],
305
- bindings: { content: 'user.profile.name' },
306
- },
307
- }));
308
- // Should produce member expression, not a string literal
309
- expect(result.code).toContain('user');
310
- expect(result.code).toContain('profile');
311
- expect(result.code).toContain('name');
312
- });
313
- });
314
-
315
- describe('event subscriptions', () => {
316
- it('should emit useOnEvent for workflow-level events', () => {
317
- const result = decompile(makeDefinition({
318
- on_event: [{
319
- match: 'order.placed',
320
- actions: [{ type: 'set_field', field: 'status', expression: '"received"' }],
321
- }],
322
- }));
323
- expect(result.code).toContain('useOnEvent');
324
- expect(result.code).toContain('"order.placed"');
325
- });
326
- });
327
-
328
- describe('field watchers', () => {
329
- it('should emit useOnChange from metadata.fieldWatchers', () => {
330
- const result = decompile(makeDefinition({
331
- metadata: {
332
- fieldWatchers: [{
333
- id: 'watcher_1',
334
- field: 'status',
335
- actions: [{ id: 'auto_1', type: 'log_event', mode: 'auto', config: { message: 'status changed' } }],
336
- }],
337
- },
338
- }));
339
- expect(result.code).toContain('useOnChange');
340
- expect(result.code).toContain('"status"');
341
- });
342
- });
343
-
344
- describe('transition effects', () => {
345
- it('should emit useOnTransition from metadata.transitionEffects', () => {
346
- const result = decompile(makeDefinition({
347
- metadata: {
348
- transitionEffects: [{
349
- id: 'effect_1',
350
- transition: 'submit',
351
- actions: [{ id: 'auto_1', type: 'log_event', mode: 'auto', config: { message: 'submitted' } }],
352
- }],
353
- },
354
- }));
355
- expect(result.code).toContain('useOnTransition');
356
- expect(result.code).toContain('"submit"');
357
- });
358
- });
359
-
360
- describe('experience tree', () => {
361
- it('should emit JSX tree with nested components', () => {
362
- const result = decompile(makeDefinition({
363
- experience: {
364
- id: 'root',
365
- component: 'Stack',
366
- config: { gap: 16 },
367
- children: [
368
- { id: 'h1', component: 'Heading', config: { level: 2, value: 'Hello' } },
369
- { id: 'btn', component: 'Button', config: { label: 'Click me' } },
370
- ],
371
- },
372
- }));
373
- expect(result.code).toContain('<Stack');
374
- expect(result.code).toContain('gap={16}');
375
- expect(result.code).toContain('<Heading');
376
- expect(result.code).toContain('<Button');
377
- });
378
-
379
- it('should emit Show wrapper for visible_when', () => {
380
- const result = decompile(makeDefinition({
381
- experience: {
382
- id: 'root',
383
- component: 'Text',
384
- visible_when: '$local.is_admin',
385
- config: { value: 'Admin Panel' },
386
- },
387
- }));
388
- expect(result.code).toContain('<Show');
389
- expect(result.code).toContain('when={');
390
- });
391
-
392
- it('should emit sx prop from style IR', () => {
393
- const result = decompile(makeDefinition({
394
- experience: {
395
- id: 'root',
396
- component: 'Stack',
397
- style: {
398
- padding: { top: 16, right: 16, bottom: 16, left: 16 },
399
- gap: 8,
400
- },
401
- children: [],
402
- },
403
- }));
404
- expect(result.code).toContain('sx=');
405
- });
406
- });
407
-
408
- describe('ir-to-tsx-emitter bridge', () => {
409
- it('should produce identical output to decompile()', () => {
410
- const def = makeDefinition({
411
- fields: [{ name: 'count', type: 'number', required: false, default_value: 0 }],
412
- transitions: [{ name: 'inc', from: ['idle'], to: 'counting', actions: [] }],
413
- });
414
-
415
- const directResult = decompile(def);
416
- const bridgeResult = decompileToTSX(def);
417
-
418
- expect(bridgeResult.code).toBe(directResult.code);
419
- expect(bridgeResult.componentName).toBe(directResult.componentName);
420
- });
421
-
422
- it('should normalize input from views.default', () => {
423
- const def: IRWorkflowDefinition = {
424
- slug: 'test',
425
- name: 'Test',
426
- version: '1.0.0',
427
- category: 'workflow',
428
- fields: [],
429
- states: [],
430
- transitions: [],
431
- roles: [],
432
- };
433
- // Simulate stored IR format where experience is in views.default
434
- (def as any).views = {
435
- default: { id: 'root', component: 'Stack', children: [] },
436
- };
437
-
438
- const normalized = normalizeDecompilerInput(def);
439
- expect(normalized.experience).toBeDefined();
440
- expect(normalized.experience!.component).toBe('Stack');
441
- });
442
- });
443
- });
444
-
445
- // =============================================================================
446
- // Round-Trip Tests: IR → decompile → recompile → compare IR
447
- // =============================================================================
448
-
449
- describe('Round-Trip: IR → TSX → IR equivalence', () => {
450
- describe('fields-only workflow', () => {
451
- it('should preserve text and number fields through round trip', () => {
452
- const ir = makeDefinition({
453
- slug: 'field-test',
454
- fields: [
455
- { name: 'title', type: 'text', required: false, default_value: 'Untitled' },
456
- { name: 'count', type: 'number', required: false, default_value: 0 },
457
- { name: 'is_active', type: 'boolean', required: false, default_value: false },
458
- ],
459
- });
460
-
461
- const result = validateRoundTrip(ir);
462
- expect(result.roundTrippedIR).not.toBeNull();
463
-
464
- // All fields should survive
465
- const rtFields = result.roundTrippedIR!.fields.map(f => f.name).sort();
466
- expect(rtFields).toContain('title');
467
- expect(rtFields).toContain('count');
468
- expect(rtFields).toContain('isActive');
469
- });
470
- });
471
-
472
- describe('transitions with roles and conditions', () => {
473
- it('should preserve transition metadata through round trip', () => {
474
- const ir = makeDefinition({
475
- slug: 'approval-flow',
476
- transitions: [
477
- {
478
- name: 'submit',
479
- from: ['draft'],
480
- to: 'pending',
481
- actions: [],
482
- roles: ['author'],
483
- required_fields: ['title', 'body'],
484
- },
485
- {
486
- name: 'approve',
487
- from: ['pending'],
488
- to: 'approved',
489
- actions: [],
490
- roles: ['reviewer'],
491
- conditions: [{ field: 'score', operator: 'gte', value: 80 }],
492
- },
493
- ],
494
- });
495
-
496
- const result = validateRoundTrip(ir);
497
- expect(result.roundTrippedIR).not.toBeNull();
498
-
499
- const rtTransitions = result.roundTrippedIR!.transitions;
500
- expect(rtTransitions.length).toBe(2);
501
-
502
- const submit = rtTransitions.find(t => t.name === 'submit');
503
- expect(submit).toBeDefined();
504
- expect(submit!.to).toBe('pending');
505
- expect(submit!.roles).toContain('author');
506
- expect(submit!.required_fields).toEqual(expect.arrayContaining(['title', 'body']));
507
-
508
- const approve = rtTransitions.find(t => t.name === 'approve');
509
- expect(approve).toBeDefined();
510
- expect(approve!.to).toBe('approved');
511
- expect(approve!.roles).toContain('reviewer');
512
- });
513
- });
514
-
515
- describe('state effects (enter, exit, during)', () => {
516
- it('should preserve on_enter and on_exit action counts', () => {
517
- const ir = makeDefinition({
518
- slug: 'effect-test',
519
- states: [
520
- {
521
- name: 'active',
522
- type: 'REGULAR',
523
- on_enter: [
524
- { id: 'a1', type: 'set_field', mode: 'auto', config: { field: 'status', value: 'active' } },
525
- { id: 'a2', type: 'log_event', mode: 'auto', config: { message: 'entered active' } },
526
- ],
527
- on_exit: [
528
- { id: 'a3', type: 'log_event', mode: 'auto', config: { message: 'leaving active' } },
529
- ],
530
- during: [],
531
- },
532
- ],
533
- });
534
-
535
- const result = validateRoundTrip(ir);
536
- expect(result.roundTrippedIR).not.toBeNull();
537
-
538
- const activeState = result.roundTrippedIR!.states.find(s => s.name === 'active');
539
- expect(activeState).toBeDefined();
540
- expect(activeState!.on_enter.length).toBe(2);
541
- expect(activeState!.on_exit.length).toBe(1);
542
- });
543
-
544
- it('should preserve during actions (useWhileIn)', () => {
545
- const ir = makeDefinition({
546
- slug: 'during-test',
547
- states: [{
548
- name: 'monitoring',
549
- type: 'REGULAR',
550
- on_enter: [],
551
- on_exit: [],
552
- during: [{
553
- id: 'd1',
554
- type: 'interval',
555
- interval_ms: 30000,
556
- actions: [{ id: 'a1', type: 'log_event', mode: 'auto', config: { message: 'heartbeat' } }],
557
- }],
558
- }],
559
- });
560
-
561
- const result = validateRoundTrip(ir);
562
- expect(result.roundTrippedIR).not.toBeNull();
563
-
564
- const state = result.roundTrippedIR!.states.find(s => s.name === 'monitoring');
565
- expect(state).toBeDefined();
566
- expect(state!.during.length).toBe(1);
567
- });
568
- });
569
-
570
- describe('event subscriptions', () => {
571
- it('should preserve on_event subscriptions through round trip', () => {
572
- const ir = makeDefinition({
573
- slug: 'event-test',
574
- on_event: [{
575
- match: 'message.received',
576
- actions: [{ type: 'set_field', field: 'message_count', expression: 'message_count + 1' }],
577
- }],
578
- });
579
-
580
- const result = validateRoundTrip(ir);
581
- const errors = result.diffs.filter(d => d.severity === 'error');
582
- // Events should round-trip (1 in, 1 out)
583
- const eventDiff = errors.find(d => d.path === 'on_event.length');
584
- expect(eventDiff).toBeUndefined();
585
- });
586
- });
587
-
588
- describe('combined workflow (fields + states + transitions + experience)', () => {
589
- it('should handle a complete workflow round trip', () => {
590
- const ir = makeDefinition({
591
- slug: 'order-tracker',
592
- category: 'workflow',
593
- fields: [
594
- { name: 'status', type: 'text', required: false, default_value: 'draft' },
595
- { name: 'total', type: 'number', required: false, default_value: 0 },
596
- ],
597
- states: [
598
- {
599
- name: 'draft',
600
- type: 'START',
601
- on_enter: [{ id: 'a1', type: 'set_field', mode: 'auto', config: { field: 'status', value: 'draft' } }],
602
- on_exit: [],
603
- during: [],
604
- },
605
- {
606
- name: 'submitted',
607
- type: 'REGULAR',
608
- on_enter: [{ id: 'a2', type: 'log_event', mode: 'auto', config: { message: 'Order submitted' } }],
609
- on_exit: [],
610
- during: [],
611
- },
612
- {
613
- name: 'approved',
614
- type: 'END',
615
- on_enter: [],
616
- on_exit: [],
617
- during: [],
618
- },
619
- ],
620
- transitions: [
621
- { name: 'submit', from: ['draft'], to: 'submitted', actions: [] },
622
- { name: 'approve', from: ['submitted'], to: 'approved', actions: [], roles: ['admin'] },
623
- ],
624
- experience: {
625
- id: 'root',
626
- component: 'Stack',
627
- config: { gap: 16 },
628
- children: [
629
- { id: 'h1', component: 'Heading', config: { value: 'Order Tracker' } },
630
- { id: 'txt', component: 'Text', bindings: { value: '$local.status' } },
631
- { id: 'btn', component: 'Button', config: { label: 'Submit' }, bindings: { onClick: '$action.transition(submit)' } },
632
- ],
633
- },
634
- });
635
-
636
- const result = validateRoundTrip(ir);
637
- expect(result.roundTrippedIR).not.toBeNull();
638
-
639
- // Verify structure preserved
640
- expect(result.roundTrippedIR!.slug).toBe('order-tracker');
641
- expect(result.roundTrippedIR!.fields.length).toBeGreaterThanOrEqual(2);
642
- expect(result.roundTrippedIR!.transitions.length).toBe(2);
643
-
644
- const criticalErrors = result.diffs.filter(d => d.severity === 'error');
645
- // Expect no critical errors (some warnings are OK for type inference)
646
- expect(criticalErrors.length).toBe(0);
647
- });
648
- });
649
-
650
- describe('auto transitions', () => {
651
- it('should preserve auto flag on transitions', () => {
652
- const ir = makeDefinition({
653
- slug: 'auto-test',
654
- transitions: [{
655
- name: 'auto-advance',
656
- from: ['waiting'],
657
- to: 'processing',
658
- actions: [],
659
- auto: true,
660
- }],
661
- });
662
-
663
- const result = validateRoundTrip(ir);
664
- expect(result.roundTrippedIR).not.toBeNull();
665
-
666
- const autoTrans = result.roundTrippedIR!.transitions.find(t => t.name === 'auto-advance');
667
- expect(autoTrans).toBeDefined();
668
- expect(autoTrans!.auto).toBe(true);
669
- });
670
- });
671
-
672
- describe('multi-from transitions', () => {
673
- it('should preserve multiple from states', () => {
674
- const ir = makeDefinition({
675
- slug: 'multi-from',
676
- transitions: [{
677
- name: 'reset',
678
- from: ['active', 'paused', 'error'],
679
- to: 'idle',
680
- actions: [],
681
- }],
682
- });
683
-
684
- const result = validateRoundTrip(ir);
685
- expect(result.roundTrippedIR).not.toBeNull();
686
-
687
- const reset = result.roundTrippedIR!.transitions.find(t => t.name === 'reset');
688
- expect(reset).toBeDefined();
689
- expect(reset!.from.sort()).toEqual(['active', 'error', 'paused']);
690
- });
691
- });
692
-
693
- describe('decompile generates valid compilable code', () => {
694
- it('should produce code that parses without errors', () => {
695
- const ir = makeDefinition({
696
- slug: 'parse-test',
697
- fields: [
698
- { name: 'name', type: 'text', required: false, default_value: '' },
699
- ],
700
- states: [{
701
- name: 'idle',
702
- type: 'START',
703
- on_enter: [{ id: 'a1', type: 'set_field', mode: 'auto', config: { field: 'name', value: 'default' } }],
704
- on_exit: [],
705
- during: [],
706
- }],
707
- transitions: [{ name: 'start', from: ['idle'], to: 'active', actions: [] }],
708
- });
709
-
710
- const result = validateRoundTrip(ir);
711
- // The round trip should produce an IR (meaning compilation succeeded)
712
- expect(result.roundTrippedIR).not.toBeNull();
713
- expect(result.generatedSource).toContain('useState');
714
- expect(result.generatedSource).toContain('useOnEnter');
715
- expect(result.generatedSource).toContain('useTransition');
716
- });
717
- });
718
- });