meno-core 1.0.53 → 1.0.54

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 (387) hide show
  1. package/.claude/settings.local.json +1 -3
  2. package/bin/cli.ts +48 -57
  3. package/build-astro.ts +131 -113
  4. package/build-next.ts +109 -96
  5. package/build-static.test.ts +39 -10
  6. package/build-static.ts +120 -122
  7. package/dist/bin/cli.js +34 -38
  8. package/dist/bin/cli.js.map +2 -2
  9. package/dist/build-static.js +12 -11
  10. package/dist/chunks/chunk-2AR55GYH.js +42 -0
  11. package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
  12. package/dist/chunks/{chunk-IGYR22T6.js → chunk-2FN4UOVO.js} +512 -239
  13. package/dist/chunks/chunk-2FN4UOVO.js.map +7 -0
  14. package/dist/chunks/chunk-3XER4E5W.js +168 -0
  15. package/dist/chunks/chunk-3XER4E5W.js.map +7 -0
  16. package/dist/chunks/{chunk-X754AHS5.js → chunk-5ETZFREW.js} +9 -12
  17. package/dist/chunks/chunk-5ETZFREW.js.map +7 -0
  18. package/dist/chunks/{chunk-2MHDV5BF.js → chunk-7E4IF5L7.js} +15 -21
  19. package/dist/chunks/chunk-7E4IF5L7.js.map +7 -0
  20. package/dist/chunks/{chunk-JGWFTO6P.js → chunk-7HWQUVTU.js} +1578 -1349
  21. package/dist/chunks/chunk-7HWQUVTU.js.map +7 -0
  22. package/dist/chunks/{chunk-O3NAGJP4.js → chunk-AE3QK5QW.js} +110 -21
  23. package/dist/chunks/chunk-AE3QK5QW.js.map +7 -0
  24. package/dist/chunks/{chunk-GZHGVVW3.js → chunk-F6KTJYGV.js} +7 -7
  25. package/dist/chunks/chunk-F6KTJYGV.js.map +7 -0
  26. package/dist/chunks/{chunk-WQFG7PAH.js → chunk-FZITJSSS.js} +2 -6
  27. package/dist/chunks/chunk-FZITJSSS.js.map +7 -0
  28. package/dist/chunks/{chunk-QB2LNO4W.js → chunk-GSYYA5GX.js} +2 -2
  29. package/dist/chunks/chunk-GSYYA5GX.js.map +7 -0
  30. package/dist/chunks/{chunk-YBLHKYFF.js → chunk-HIZMY3EP.js} +1 -1
  31. package/dist/chunks/chunk-HIZMY3EP.js.map +7 -0
  32. package/dist/chunks/{chunk-AZQYF6KE.js → chunk-I2WEGYA7.js} +41 -176
  33. package/dist/chunks/chunk-I2WEGYA7.js.map +7 -0
  34. package/dist/chunks/{chunk-I7YIGZXT.js → chunk-JNO3CNLJ.js} +6 -9
  35. package/dist/chunks/chunk-JNO3CNLJ.js.map +7 -0
  36. package/dist/chunks/{chunk-UB44F4Z2.js → chunk-NVRBTSQG.js} +2 -4
  37. package/dist/chunks/chunk-NVRBTSQG.js.map +7 -0
  38. package/dist/chunks/{chunk-JGP5A3Y5.js → chunk-Q4OBWKXG.js} +40 -33
  39. package/dist/chunks/chunk-Q4OBWKXG.js.map +7 -0
  40. package/dist/chunks/{chunk-R6XHAFBF.js → chunk-QTE32Y53.js} +250 -242
  41. package/dist/chunks/chunk-QTE32Y53.js.map +7 -0
  42. package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-STDY3OVM.js} +214 -85
  43. package/dist/chunks/chunk-STDY3OVM.js.map +7 -0
  44. package/dist/chunks/configService-PRJZF7Y6.js +14 -0
  45. package/dist/chunks/{constants-STK2YBIW.js → constants-KIQEYMAM.js} +2 -2
  46. package/dist/chunks/{fs-JGINUXGL.js → fs-ZI5JEU7V.js} +2 -2
  47. package/dist/entries/server-router.js +14 -19
  48. package/dist/entries/server-router.js.map +2 -2
  49. package/dist/lib/client/index.js +638 -332
  50. package/dist/lib/client/index.js.map +2 -2
  51. package/dist/lib/server/index.js +177 -235
  52. package/dist/lib/server/index.js.map +2 -2
  53. package/dist/lib/shared/index.js +80 -44
  54. package/dist/lib/shared/index.js.map +2 -2
  55. package/dist/lib/shared/richtext/index.js +1 -1
  56. package/dist/lib/test-utils/index.js +38 -60
  57. package/dist/lib/test-utils/index.js.map +2 -2
  58. package/entries/client-router.tsx +10 -8
  59. package/entries/server-router.tsx +1 -7
  60. package/lib/client/ClientInitializer.ts +8 -8
  61. package/lib/client/ErrorBoundary.test.tsx +146 -143
  62. package/lib/client/ErrorBoundary.tsx +175 -127
  63. package/lib/client/componentRegistry.test.ts +96 -108
  64. package/lib/client/componentRegistry.ts +1 -2
  65. package/lib/client/contexts/ThemeContext.tsx +3 -2
  66. package/lib/client/core/ComponentBuilder.test.ts +513 -560
  67. package/lib/client/core/ComponentBuilder.ts +318 -146
  68. package/lib/client/core/ComponentRenderer.test.tsx +1 -2
  69. package/lib/client/core/ComponentRenderer.tsx +46 -33
  70. package/lib/client/core/builders/embedBuilder.ts +241 -53
  71. package/lib/client/core/builders/linkBuilder.ts +71 -44
  72. package/lib/client/core/builders/linkNodeBuilder.ts +78 -53
  73. package/lib/client/core/builders/listBuilder.ts +115 -86
  74. package/lib/client/core/builders/localeListBuilder.ts +95 -60
  75. package/lib/client/core/builders/types.ts +5 -5
  76. package/lib/client/core/cmsTemplateProcessor.ts +7 -7
  77. package/lib/client/elementRegistry.ts +3 -3
  78. package/lib/client/fontFamiliesService.test.ts +2 -10
  79. package/lib/client/fontFamiliesService.ts +3 -3
  80. package/lib/client/hmr/HMRManager.tsx +8 -0
  81. package/lib/client/hmrCssReload.ts +14 -8
  82. package/lib/client/hmrWebSocket.ts +9 -14
  83. package/lib/client/hooks/useColorVariables.test.ts +21 -21
  84. package/lib/client/hooks/useColorVariables.ts +12 -10
  85. package/lib/client/hooks/usePropertyAutocomplete.ts +3 -5
  86. package/lib/client/hooks/useVariables.ts +4 -4
  87. package/lib/client/hydration/HydrationUtils.test.ts +24 -25
  88. package/lib/client/hydration/HydrationUtils.ts +3 -4
  89. package/lib/client/i18nConfigService.test.ts +2 -7
  90. package/lib/client/i18nConfigService.ts +2 -2
  91. package/lib/client/meno-filter/MenoFilter.test.ts +19 -21
  92. package/lib/client/meno-filter/MenoFilter.ts +5 -9
  93. package/lib/client/meno-filter/bindings.ts +15 -40
  94. package/lib/client/meno-filter/init.ts +1 -1
  95. package/lib/client/meno-filter/renderer.ts +23 -29
  96. package/lib/client/meno-filter/script.generated.ts +1 -3
  97. package/lib/client/meno-filter/ui.ts +3 -5
  98. package/lib/client/meno-filter/updates.ts +15 -21
  99. package/lib/client/navigation.test.ts +159 -159
  100. package/lib/client/navigation.ts +0 -1
  101. package/lib/client/responsiveStyleResolver.test.ts +230 -228
  102. package/lib/client/responsiveStyleResolver.ts +13 -16
  103. package/lib/client/routing/RouteLoader.test.ts +23 -24
  104. package/lib/client/routing/RouteLoader.ts +22 -35
  105. package/lib/client/routing/Router.tsx +31 -3
  106. package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
  107. package/lib/client/scripts/ScriptExecutor.ts +15 -33
  108. package/lib/client/services/PrefetchService.test.ts +2 -2
  109. package/lib/client/services/PrefetchService.ts +10 -24
  110. package/lib/client/styleProcessor.test.ts +9 -9
  111. package/lib/client/styleProcessor.ts +18 -15
  112. package/lib/client/styles/StyleInjector.test.ts +122 -115
  113. package/lib/client/styles/StyleInjector.ts +9 -6
  114. package/lib/client/styles/UtilityClassCollector.ts +19 -26
  115. package/lib/client/styles/cspNonce.test.ts +2 -5
  116. package/lib/client/templateEngine.test.ts +554 -482
  117. package/lib/client/templateEngine.ts +200 -64
  118. package/lib/client/theme.ts +0 -1
  119. package/lib/client/utils/toast.ts +0 -1
  120. package/lib/server/__integration__/api-routes.test.ts +8 -4
  121. package/lib/server/__integration__/cms-integration.test.ts +1 -4
  122. package/lib/server/__integration__/server-lifecycle.test.ts +2 -5
  123. package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
  124. package/lib/server/__integration__/static-assets.test.ts +1 -1
  125. package/lib/server/__integration__/test-helpers.ts +84 -70
  126. package/lib/server/ab/generateFunctions.ts +12 -10
  127. package/lib/server/astro/cmsPageEmitter.ts +23 -38
  128. package/lib/server/astro/componentEmitter.ts +25 -36
  129. package/lib/server/astro/cssCollector.ts +10 -26
  130. package/lib/server/astro/nodeToAstro.test.ts +1750 -30
  131. package/lib/server/astro/nodeToAstro.ts +198 -187
  132. package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +105 -109
  133. package/lib/server/astro/normalizeOrphanTemplateProps.ts +4 -12
  134. package/lib/server/astro/pageEmitter.ts +9 -13
  135. package/lib/server/astro/tailwindMapper.test.ts +10 -37
  136. package/lib/server/astro/tailwindMapper.ts +33 -40
  137. package/lib/server/astro/templateTransformer.ts +14 -17
  138. package/lib/server/createServer.ts +5 -17
  139. package/lib/server/cssGenerator.test.ts +35 -44
  140. package/lib/server/cssGenerator.ts +6 -17
  141. package/lib/server/fileWatcher.test.ts +124 -10
  142. package/lib/server/fileWatcher.ts +124 -118
  143. package/lib/server/index.ts +7 -1
  144. package/lib/server/jsonLoader.test.ts +39 -2
  145. package/lib/server/jsonLoader.ts +33 -31
  146. package/lib/server/middleware/cors.test.ts +20 -20
  147. package/lib/server/middleware/cors.ts +28 -4
  148. package/lib/server/middleware/errorHandler.test.ts +5 -3
  149. package/lib/server/middleware/errorHandler.ts +3 -8
  150. package/lib/server/middleware/index.ts +0 -1
  151. package/lib/server/middleware/logger.test.ts +7 -5
  152. package/lib/server/middleware/logger.ts +10 -22
  153. package/lib/server/pageCache.test.ts +76 -77
  154. package/lib/server/pageCache.ts +0 -1
  155. package/lib/server/projectContext.ts +4 -3
  156. package/lib/server/providers/fileSystemCMSProvider.test.ts +124 -95
  157. package/lib/server/providers/fileSystemCMSProvider.ts +35 -20
  158. package/lib/server/providers/fileSystemPageProvider.test.ts +84 -0
  159. package/lib/server/providers/fileSystemPageProvider.ts +31 -12
  160. package/lib/server/routes/api/cms.test.ts +26 -14
  161. package/lib/server/routes/api/cms.ts +9 -14
  162. package/lib/server/routes/api/components.ts +30 -34
  163. package/lib/server/routes/api/config.ts +0 -1
  164. package/lib/server/routes/api/core-routes.ts +47 -74
  165. package/lib/server/routes/api/functions.ts +8 -16
  166. package/lib/server/routes/api/index.ts +3 -6
  167. package/lib/server/routes/api/pages.ts +8 -11
  168. package/lib/server/routes/api/shared.test.ts +1 -1
  169. package/lib/server/routes/api/shared.ts +12 -9
  170. package/lib/server/routes/api/variables.test.ts +1 -3
  171. package/lib/server/routes/api/variables.ts +1 -1
  172. package/lib/server/routes/index.ts +23 -26
  173. package/lib/server/routes/pages.ts +34 -29
  174. package/lib/server/routes/static.ts +16 -4
  175. package/lib/server/runtime/bundler.ts +47 -32
  176. package/lib/server/runtime/fs.ts +3 -13
  177. package/lib/server/runtime/httpServer.ts +5 -9
  178. package/lib/server/services/ColorService.ts +32 -27
  179. package/lib/server/services/EnumService.test.ts +2 -6
  180. package/lib/server/services/EnumService.ts +7 -2
  181. package/lib/server/services/VariableService.test.ts +1 -5
  182. package/lib/server/services/VariableService.ts +6 -1
  183. package/lib/server/services/cmsService.test.ts +116 -78
  184. package/lib/server/services/cmsService.ts +24 -54
  185. package/lib/server/services/componentService.test.ts +122 -36
  186. package/lib/server/services/componentService.ts +54 -38
  187. package/lib/server/services/configService.test.ts +9 -31
  188. package/lib/server/services/configService.ts +20 -27
  189. package/lib/server/services/fileWatcherService.ts +40 -6
  190. package/lib/server/services/index.ts +0 -1
  191. package/lib/server/services/pageService.test.ts +1 -3
  192. package/lib/server/services/pageService.ts +14 -13
  193. package/lib/server/ssr/attributeBuilder.ts +16 -6
  194. package/lib/server/ssr/buildErrorOverlay.ts +11 -12
  195. package/lib/server/ssr/clientDataInjector.ts +7 -21
  196. package/lib/server/ssr/cmsSSRProcessor.ts +3 -6
  197. package/lib/server/ssr/cssCollector.ts +1 -1
  198. package/lib/server/ssr/errorOverlay.test.ts +1 -1
  199. package/lib/server/ssr/errorOverlay.ts +3 -9
  200. package/lib/server/ssr/htmlGenerator.nonce.test.ts +3 -9
  201. package/lib/server/ssr/htmlGenerator.test.ts +120 -43
  202. package/lib/server/ssr/htmlGenerator.ts +120 -85
  203. package/lib/server/ssr/imageMetadata.test.ts +3 -1
  204. package/lib/server/ssr/imageMetadata.ts +25 -19
  205. package/lib/server/ssr/jsCollector.test.ts +3 -13
  206. package/lib/server/ssr/jsCollector.ts +3 -8
  207. package/lib/server/ssr/liveReloadIntegration.test.ts +69 -28
  208. package/lib/server/ssr/metaTagGenerator.ts +2 -2
  209. package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
  210. package/lib/server/ssr/ssrRenderer.test.ts +196 -246
  211. package/lib/server/ssr/ssrRenderer.ts +609 -225
  212. package/lib/server/ssrRenderer.test.ts +1044 -950
  213. package/lib/server/utils/jsonLineMapper.test.ts +28 -28
  214. package/lib/server/utils/jsonLineMapper.ts +1 -1
  215. package/lib/server/validateStyleCoverage.ts +18 -20
  216. package/lib/server/webflow/buildWebflow.ts +41 -53
  217. package/lib/server/webflow/nodeToWebflow.test.ts +150 -218
  218. package/lib/server/webflow/nodeToWebflow.ts +195 -258
  219. package/lib/server/webflow/styleMapper.test.ts +15 -56
  220. package/lib/server/webflow/styleMapper.ts +33 -41
  221. package/lib/server/webflow/types.ts +1 -8
  222. package/lib/server/websocketManager.ts +16 -19
  223. package/lib/shared/attributeNodeUtils.test.ts +15 -15
  224. package/lib/shared/attributeNodeUtils.ts +5 -12
  225. package/lib/shared/breakpoints.ts +4 -11
  226. package/lib/shared/cmsQueryParser.test.ts +50 -42
  227. package/lib/shared/cmsQueryParser.ts +49 -31
  228. package/lib/shared/colorVariableUtils.test.ts +5 -5
  229. package/lib/shared/colorVariableUtils.ts +3 -8
  230. package/lib/shared/componentRefs.ts +1 -5
  231. package/lib/shared/constants.test.ts +3 -3
  232. package/lib/shared/constants.ts +4 -8
  233. package/lib/shared/cssGeneration.test.ts +262 -144
  234. package/lib/shared/cssGeneration.ts +189 -516
  235. package/lib/shared/cssNamedColors.ts +152 -30
  236. package/lib/shared/cssProperties.test.ts +5 -6
  237. package/lib/shared/cssProperties.ts +296 -112
  238. package/lib/shared/elementClassName.test.ts +109 -109
  239. package/lib/shared/elementClassName.ts +1 -1
  240. package/lib/shared/elementUtils.ts +12 -16
  241. package/lib/shared/errorLogger.ts +2 -10
  242. package/lib/shared/errors.test.ts +2 -13
  243. package/lib/shared/errors.ts +2 -8
  244. package/lib/shared/expressionEvaluator.test.ts +119 -0
  245. package/lib/shared/expressionEvaluator.ts +43 -24
  246. package/lib/shared/fontLoader.test.ts +19 -5
  247. package/lib/shared/friendlyError.ts +2 -3
  248. package/lib/shared/gradientUtils.test.ts +1 -5
  249. package/lib/shared/gradientUtils.ts +2 -6
  250. package/lib/shared/hrefRefs.ts +2 -10
  251. package/lib/shared/i18n.test.ts +1 -1
  252. package/lib/shared/i18n.ts +13 -34
  253. package/lib/shared/index.ts +4 -0
  254. package/lib/shared/inlineSvgStyleRules.ts +2 -4
  255. package/lib/shared/interactiveStyleMappings.test.ts +11 -33
  256. package/lib/shared/interactiveStyleMappings.ts +9 -16
  257. package/lib/shared/interactiveStyles.test.ts +165 -188
  258. package/lib/shared/interfaces/contentProvider.ts +1 -1
  259. package/lib/shared/itemTemplateUtils.test.ts +6 -12
  260. package/lib/shared/itemTemplateUtils.ts +19 -35
  261. package/lib/shared/jsonRepair.ts +8 -2
  262. package/lib/shared/libraryLoader.test.ts +15 -49
  263. package/lib/shared/libraryLoader.ts +7 -22
  264. package/lib/shared/netlifyLocale404.test.ts +179 -0
  265. package/lib/shared/netlifyLocale404.ts +110 -0
  266. package/lib/shared/nodeUtils.test.ts +24 -16
  267. package/lib/shared/nodeUtils.ts +49 -19
  268. package/lib/shared/pathArrayUtils.test.ts +1 -2
  269. package/lib/shared/pathArrayUtils.ts +1 -1
  270. package/lib/shared/pathSecurity.ts +1 -1
  271. package/lib/shared/pathUtils.test.ts +4 -6
  272. package/lib/shared/pathUtils.ts +42 -48
  273. package/lib/shared/paths/Path.test.ts +2 -2
  274. package/lib/shared/paths/Path.ts +0 -1
  275. package/lib/shared/paths/PathConverter.test.ts +1 -1
  276. package/lib/shared/paths/PathConverter.ts +14 -17
  277. package/lib/shared/paths/PathUtils.ts +9 -10
  278. package/lib/shared/paths/PathValidator.test.ts +2 -15
  279. package/lib/shared/paths/PathValidator.ts +11 -9
  280. package/lib/shared/paths/index.ts +1 -2
  281. package/lib/shared/propResolver.test.ts +240 -244
  282. package/lib/shared/propResolver.ts +14 -25
  283. package/lib/shared/pxToRem.test.ts +7 -6
  284. package/lib/shared/pxToRem.ts +2 -5
  285. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +9 -5
  286. package/lib/shared/registry/ClientRegistry.ts +0 -1
  287. package/lib/shared/registry/ComponentRegistry.test.ts +43 -29
  288. package/lib/shared/registry/ComponentRegistry.ts +9 -11
  289. package/lib/shared/registry/NodeTypeDefinition.ts +15 -8
  290. package/lib/shared/registry/RegistryManager.ts +1 -2
  291. package/lib/shared/registry/SSRRegistry.ts +0 -1
  292. package/lib/shared/registry/createNodeType.ts +7 -9
  293. package/lib/shared/registry/defineNodeType.ts +2 -6
  294. package/lib/shared/registry/index.ts +0 -1
  295. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
  296. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
  297. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +47 -18
  298. package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
  299. package/lib/shared/registry/nodeTypes/ListNodeType.ts +78 -74
  300. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
  301. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +6 -7
  302. package/lib/shared/registry/nodeTypes/index.ts +10 -2
  303. package/lib/shared/responsiveScaling.test.ts +15 -31
  304. package/lib/shared/responsiveScaling.ts +55 -37
  305. package/lib/shared/responsiveStyleUtils.ts +11 -13
  306. package/lib/shared/richtext/htmlToTiptap.test.ts +23 -14
  307. package/lib/shared/richtext/htmlToTiptap.ts +1 -3
  308. package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
  309. package/lib/shared/richtext/types.ts +1 -8
  310. package/lib/shared/slugTranslator.test.ts +13 -13
  311. package/lib/shared/slugTranslator.ts +12 -16
  312. package/lib/shared/slugify.ts +9 -15
  313. package/lib/shared/styleNodeUtils.test.ts +8 -8
  314. package/lib/shared/styleNodeUtils.ts +5 -10
  315. package/lib/shared/styleUtils.test.ts +87 -61
  316. package/lib/shared/styleUtils.ts +5 -6
  317. package/lib/shared/themeDefaults.test.ts +11 -11
  318. package/lib/shared/themeDefaults.ts +3 -4
  319. package/lib/shared/tree/PathBuilder.test.ts +62 -138
  320. package/lib/shared/tree/PathBuilder.ts +49 -39
  321. package/lib/shared/treePathUtils.test.ts +2 -10
  322. package/lib/shared/treePathUtils.ts +54 -59
  323. package/lib/shared/types/api.ts +1 -2
  324. package/lib/shared/types/cms.ts +25 -21
  325. package/lib/shared/types/comment.ts +49 -16
  326. package/lib/shared/types/components.ts +27 -25
  327. package/lib/shared/types/errors.test.ts +1 -6
  328. package/lib/shared/types/errors.ts +3 -7
  329. package/lib/shared/types/experiments.ts +28 -28
  330. package/lib/shared/types/index.ts +2 -2
  331. package/lib/shared/types/styles.ts +0 -1
  332. package/lib/shared/types/variables.test.ts +4 -13
  333. package/lib/shared/types/variables.ts +48 -27
  334. package/lib/shared/types.ts +1 -2
  335. package/lib/shared/utilityClassConfig.ts +648 -321
  336. package/lib/shared/utilityClassMapper.test.ts +203 -92
  337. package/lib/shared/utilityClassMapper.ts +188 -246
  338. package/lib/shared/utilityClassNames.ts +326 -0
  339. package/lib/shared/utils.test.ts +2 -10
  340. package/lib/shared/utils.ts +19 -10
  341. package/lib/shared/validation/cmsValidators.ts +2 -1
  342. package/lib/shared/validation/commentValidators.test.ts +53 -0
  343. package/lib/shared/validation/commentValidators.ts +12 -1
  344. package/lib/shared/validation/propValidator.test.ts +18 -20
  345. package/lib/shared/validation/propValidator.ts +12 -17
  346. package/lib/shared/validation/schemas.test.ts +24 -33
  347. package/lib/shared/validation/schemas.ts +469 -344
  348. package/lib/shared/validation/validators.test.ts +1 -6
  349. package/lib/shared/validation/validators.ts +89 -68
  350. package/lib/shared/viewportUnits.integration.test.ts +14 -10
  351. package/lib/shared/viewportUnits.test.ts +11 -23
  352. package/lib/test-utils/dom-setup.ts +1 -1
  353. package/lib/test-utils/factories/ConsoleMockFactory.ts +3 -7
  354. package/lib/test-utils/factories/DomMockFactory.ts +7 -19
  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 +5 -9
  358. package/lib/test-utils/factories/StoreMockFactory.ts +14 -25
  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 +12 -18
  365. package/lib/test-utils/mocks.ts +4 -2
  366. package/package.json +1 -1
  367. package/scripts/build-meno-filter.ts +1 -4
  368. package/vite.config.ts +4 -4
  369. package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
  370. package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
  371. package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
  372. package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
  373. package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
  374. package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
  375. package/dist/chunks/chunk-JGP5A3Y5.js.map +0 -7
  376. package/dist/chunks/chunk-JGWFTO6P.js.map +0 -7
  377. package/dist/chunks/chunk-O3NAGJP4.js.map +0 -7
  378. package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
  379. package/dist/chunks/chunk-R6XHAFBF.js.map +0 -7
  380. package/dist/chunks/chunk-UB44F4Z2.js.map +0 -7
  381. package/dist/chunks/chunk-WQFG7PAH.js.map +0 -7
  382. package/dist/chunks/chunk-X754AHS5.js.map +0 -7
  383. package/dist/chunks/chunk-YBLHKYFF.js.map +0 -7
  384. package/dist/chunks/configService-R3OGU2UD.js +0 -13
  385. /package/dist/chunks/{configService-R3OGU2UD.js.map → configService-PRJZF7Y6.js.map} +0 -0
  386. /package/dist/chunks/{constants-STK2YBIW.js.map → constants-KIQEYMAM.js.map} +0 -0
  387. /package/dist/chunks/{fs-JGINUXGL.js.map → fs-ZI5JEU7V.js.map} +0 -0
@@ -1,14 +1,14 @@
1
- import { test, expect, describe, beforeEach, mock } from "bun:test";
2
- import { ComponentBuilder } from "./ComponentBuilder";
3
- import { ComponentRegistry } from "../componentRegistry";
4
- import { ElementRegistry } from "../elementRegistry";
5
- import type { ComponentNode, ListNode, CMSItem } from "../../shared/types";
6
- import { NODE_TYPE } from "../../shared/constants";
7
- import { createMockElementRegistry } from "../../test-utils/mocks";
1
+ import { test, expect, describe, beforeEach, mock } from 'bun:test';
2
+ import { ComponentBuilder } from './ComponentBuilder';
3
+ import { ComponentRegistry } from '../componentRegistry';
4
+ import type { ElementRegistry } from '../elementRegistry';
5
+ import type { ComponentNode, ListNode, CMSItem } from '../../shared/types';
6
+ import { NODE_TYPE } from '../../shared/constants';
7
+ import { createMockElementRegistry } from '../../test-utils/mocks';
8
8
 
9
9
  // Note: Using typed mocks from test-utils/mocks instead of inline 'as any' casts
10
10
 
11
- describe("ComponentBuilder", () => {
11
+ describe('ComponentBuilder', () => {
12
12
  let componentRegistry: ComponentRegistry;
13
13
  let elementRegistry: ElementRegistry;
14
14
  let builder: ComponentBuilder;
@@ -32,52 +32,52 @@ describe("ComponentBuilder", () => {
32
32
  });
33
33
  });
34
34
 
35
- describe("buildComponent - Basic Cases", () => {
36
- test("should return null for null node", () => {
35
+ describe('buildComponent - Basic Cases', () => {
36
+ test('should return null for null node', () => {
37
37
  const result = builder.buildComponent({ node: null });
38
38
  expect(result).toBeNull();
39
39
  });
40
40
 
41
- test("should return null for undefined node", () => {
41
+ test('should return null for undefined node', () => {
42
42
  const result = builder.buildComponent({ node: undefined });
43
43
  expect(result).toBeNull();
44
44
  });
45
45
 
46
- test("should return string for string node", () => {
47
- const result = builder.buildComponent({ node: "Hello" });
48
- expect(result).toBe("Hello");
46
+ test('should return string for string node', () => {
47
+ const result = builder.buildComponent({ node: 'Hello' });
48
+ expect(result).toBe('Hello');
49
49
  });
50
50
 
51
- test("should return number for number node", () => {
51
+ test('should return number for number node', () => {
52
52
  const result = builder.buildComponent({ node: 42 });
53
53
  expect(result).toBe(42);
54
54
  });
55
55
 
56
- test("should return array for array of nodes", () => {
56
+ test('should return array for array of nodes', () => {
57
57
  const nodes: ComponentNode[] = [
58
- { type: "node", tag: "div", children: ["Hello"] },
59
- { type: "node", tag: "span", children: ["World"] },
58
+ { type: 'node', tag: 'div', children: ['Hello'] },
59
+ { type: 'node', tag: 'span', children: ['World'] },
60
60
  ];
61
61
  const result = builder.buildComponent({ node: nodes });
62
62
  expect(Array.isArray(result)).toBe(true);
63
63
  expect((result as any[]).length).toBe(2);
64
64
  });
65
65
 
66
- test("should return ReactElement for HTML node", () => {
66
+ test('should return ReactElement for HTML node', () => {
67
67
  const node: ComponentNode = {
68
- type: "node",
69
- tag: "div",
70
- children: ["Test"],
68
+ type: 'node',
69
+ tag: 'div',
70
+ children: ['Test'],
71
71
  };
72
72
  const result = builder.buildComponent({ node });
73
73
  expect(result).not.toBeNull();
74
- expect(typeof result).toBe("object");
74
+ expect(typeof result).toBe('object');
75
75
  });
76
76
 
77
- test("should handle HTML node with empty tag gracefully", () => {
77
+ test('should handle HTML node with empty tag gracefully', () => {
78
78
  const node: ComponentNode = {
79
- type: "node",
80
- tag: "",
79
+ type: 'node',
80
+ tag: '',
81
81
  children: [],
82
82
  };
83
83
 
@@ -85,124 +85,124 @@ describe("ComponentBuilder", () => {
85
85
 
86
86
  // Implementation returns a div element for empty tag (graceful fallback)
87
87
  expect(result).not.toBeNull();
88
- expect(typeof result).toBe("object");
88
+ expect(typeof result).toBe('object');
89
89
  });
90
90
 
91
- test("resolves an _i18n value object as the node to the default locale string", () => {
91
+ test('resolves an _i18n value object as the node to the default locale string', () => {
92
92
  // Mirrors the SSR behavior: authors can pass localized strings anywhere
93
93
  // `children` is accepted. The client resolves _i18n at the buildComponent
94
94
  // entry point so all downstream code sees a plain string.
95
95
  const i18nConfig = {
96
- defaultLocale: "en",
96
+ defaultLocale: 'en',
97
97
  locales: [
98
- { code: "en", name: "EN", nativeName: "English", langTag: "en-US" },
99
- { code: "pl", name: "PL", nativeName: "Polski", langTag: "pl-PL" },
98
+ { code: 'en', name: 'EN', nativeName: 'English', langTag: 'en-US' },
99
+ { code: 'pl', name: 'PL', nativeName: 'Polski', langTag: 'pl-PL' },
100
100
  ],
101
101
  };
102
102
  const result = builder.buildComponent({
103
- node: { _i18n: true, en: "Hello", pl: "Cześć" } as any,
103
+ node: { _i18n: true, en: 'Hello', pl: 'Cześć' } as any,
104
104
  i18nConfig,
105
105
  });
106
- expect(result).toBe("Hello");
106
+ expect(result).toBe('Hello');
107
107
  });
108
108
 
109
- test("resolves an _i18n value object to the active locale", () => {
109
+ test('resolves an _i18n value object to the active locale', () => {
110
110
  const i18nConfig = {
111
- defaultLocale: "en",
111
+ defaultLocale: 'en',
112
112
  locales: [
113
- { code: "en", name: "EN", nativeName: "English", langTag: "en-US" },
114
- { code: "pl", name: "PL", nativeName: "Polski", langTag: "pl-PL" },
113
+ { code: 'en', name: 'EN', nativeName: 'English', langTag: 'en-US' },
114
+ { code: 'pl', name: 'PL', nativeName: 'Polski', langTag: 'pl-PL' },
115
115
  ],
116
116
  };
117
117
  const result = builder.buildComponent({
118
- node: { _i18n: true, en: "Hello", pl: "Cześć" } as any,
118
+ node: { _i18n: true, en: 'Hello', pl: 'Cześć' } as any,
119
119
  i18nConfig,
120
- locale: "pl",
120
+ locale: 'pl',
121
121
  });
122
- expect(result).toBe("Cześć");
122
+ expect(result).toBe('Cześć');
123
123
  });
124
124
  });
125
125
 
126
- describe("buildComponent - Component Instances", () => {
127
- test("should handle non-existent component gracefully", () => {
126
+ describe('buildComponent - Component Instances', () => {
127
+ test('should handle non-existent component gracefully', () => {
128
128
  const node: ComponentNode = {
129
- type: "component",
130
- component: "NonExistent",
129
+ type: 'component',
130
+ component: 'NonExistent',
131
131
  };
132
132
  const result = builder.buildComponent({ node });
133
133
  // Returns error boundary or placeholder element for missing component
134
134
  expect(result).not.toBeNull();
135
- expect(typeof result).toBe("object");
135
+ expect(typeof result).toBe('object');
136
136
  });
137
137
 
138
- test("should build component instance with structure", () => {
138
+ test('should build component instance with structure', () => {
139
139
  const componentDef = {
140
140
  component: {
141
141
  interface: {},
142
142
  structure: {
143
- type: "node",
144
- tag: "div",
145
- children: ["Component Content"],
143
+ type: 'node',
144
+ tag: 'div',
145
+ children: ['Component Content'],
146
146
  },
147
147
  },
148
148
  };
149
- componentRegistry.register("TestComponent", componentDef as any);
149
+ componentRegistry.register('TestComponent', componentDef as any);
150
150
 
151
151
  const node: ComponentNode = {
152
- type: "component",
153
- component: "TestComponent",
152
+ type: 'component',
153
+ component: 'TestComponent',
154
154
  };
155
155
  const result = builder.buildComponent({ node });
156
156
  expect(result).not.toBeNull();
157
- expect(typeof result).toBe("object");
157
+ expect(typeof result).toBe('object');
158
158
  });
159
159
 
160
- test("should handle component missing structure gracefully", () => {
160
+ test('should handle component missing structure gracefully', () => {
161
161
  // Test with component that has no component property at all
162
162
  const componentDef = {
163
163
  // Missing component property entirely
164
164
  };
165
- componentRegistry.register("BrokenComponent", componentDef as any);
165
+ componentRegistry.register('BrokenComponent', componentDef as any);
166
166
 
167
167
  const node: ComponentNode = {
168
- type: "component",
169
- component: "BrokenComponent",
168
+ type: 'component',
169
+ component: 'BrokenComponent',
170
170
  };
171
171
 
172
172
  const result = builder.buildComponent({ node });
173
173
 
174
174
  // When componentDef.component is missing, it should return ErrorBoundary
175
175
  expect(result).not.toBeNull();
176
- expect(typeof result).toBe("object");
176
+ expect(typeof result).toBe('object');
177
177
  });
178
178
 
179
- test("should resolve props with defaults from interface", () => {
179
+ test('should resolve props with defaults from interface', () => {
180
180
  const componentDef = {
181
181
  component: {
182
182
  interface: {
183
183
  title: {
184
- type: "string",
185
- default: "Default Title",
184
+ type: 'string',
185
+ default: 'Default Title',
186
186
  },
187
187
  },
188
188
  structure: {
189
- type: "node",
190
- tag: "div",
189
+ type: 'node',
190
+ tag: 'div',
191
191
  children: [],
192
192
  },
193
193
  },
194
194
  };
195
- componentRegistry.register("ComponentWithDefaults", componentDef as any);
195
+ componentRegistry.register('ComponentWithDefaults', componentDef as any);
196
196
 
197
197
  const node: ComponentNode = {
198
- type: "component",
199
- component: "ComponentWithDefaults",
198
+ type: 'component',
199
+ component: 'ComponentWithDefaults',
200
200
  };
201
201
  const result = builder.buildComponent({ node });
202
202
  expect(result).not.toBeNull();
203
203
  });
204
204
 
205
- test("should handle component building errors gracefully", () => {
205
+ test('should handle component building errors gracefully', () => {
206
206
  // This test verifies error handling is in place
207
207
  // We can't easily mock processStructure in Bun, so we test with a valid component
208
208
  // Error handling is verified by the try-catch in buildComponent
@@ -210,65 +210,65 @@ describe("ComponentBuilder", () => {
210
210
  component: {
211
211
  interface: {},
212
212
  structure: {
213
- type: "node",
214
- tag: "div",
213
+ type: 'node',
214
+ tag: 'div',
215
215
  children: [],
216
216
  },
217
217
  },
218
218
  };
219
- componentRegistry.register("ValidComponent", componentDef as any);
219
+ componentRegistry.register('ValidComponent', componentDef as any);
220
220
 
221
221
  const node: ComponentNode = {
222
- type: "component",
223
- component: "ValidComponent",
222
+ type: 'component',
223
+ component: 'ValidComponent',
224
224
  };
225
-
225
+
226
226
  const result = builder.buildComponent({ node });
227
-
227
+
228
228
  // Component should build successfully
229
229
  expect(result).not.toBeNull();
230
- expect(typeof result).toBe("object");
230
+ expect(typeof result).toBe('object');
231
231
  });
232
232
  });
233
233
 
234
- describe("buildComponent - Link Component", () => {
235
- test("should render Link component as anchor tag", () => {
234
+ describe('buildComponent - Link Component', () => {
235
+ test('should render Link component as anchor tag', () => {
236
236
  const node: ComponentNode = {
237
- type: "node",
238
- tag: "Link",
237
+ type: 'node',
238
+ tag: 'Link',
239
239
  props: {
240
- to: "/about",
240
+ to: '/about',
241
241
  },
242
- children: ["Click me"],
242
+ children: ['Click me'],
243
243
  };
244
244
  const result = builder.buildComponent({ node });
245
245
  expect(result).not.toBeNull();
246
- expect(typeof result).toBe("object");
246
+ expect(typeof result).toBe('object');
247
247
  });
248
248
 
249
- test("should handle Link navigation when selecting mode is disabled", () => {
249
+ test('should handle Link navigation when selecting mode is disabled', () => {
250
250
  const node: ComponentNode = {
251
- type: "node",
252
- tag: "Link",
251
+ type: 'node',
252
+ tag: 'Link',
253
253
  props: {
254
- to: "/test",
254
+ to: '/test',
255
255
  },
256
256
  children: [],
257
257
  };
258
-
258
+
259
259
  // Link component should render regardless of selecting mode
260
260
  // The actual behavior is tested through integration tests
261
261
  const result = builder.buildComponent({ node });
262
262
  expect(result).not.toBeNull();
263
- expect(typeof result).toBe("object");
263
+ expect(typeof result).toBe('object');
264
264
  });
265
265
  });
266
266
 
267
- describe("buildComponent - HTML Elements", () => {
268
- test("should register element via ref callback", () => {
267
+ describe('buildComponent - HTML Elements', () => {
268
+ test('should register element via ref callback', () => {
269
269
  const node: ComponentNode = {
270
- type: "node",
271
- tag: "div",
270
+ type: 'node',
271
+ tag: 'div',
272
272
  children: [],
273
273
  };
274
274
  const result = builder.buildComponent({ node });
@@ -277,14 +277,14 @@ describe("ComponentBuilder", () => {
277
277
  // We can't easily test this without rendering, but we verify the ref is set
278
278
  });
279
279
 
280
- test("should extract and merge styles", () => {
280
+ test('should extract and merge styles', () => {
281
281
  const node: ComponentNode = {
282
- type: "node",
283
- tag: "div",
282
+ type: 'node',
283
+ tag: 'div',
284
284
  style: {
285
285
  base: {
286
- color: "red",
287
- fontSize: "16px",
286
+ color: 'red',
287
+ fontSize: '16px',
288
288
  },
289
289
  },
290
290
  children: [],
@@ -293,13 +293,13 @@ describe("ComponentBuilder", () => {
293
293
  expect(result).not.toBeNull();
294
294
  });
295
295
 
296
- test("should extract and merge attributes", () => {
296
+ test('should extract and merge attributes', () => {
297
297
  const node: ComponentNode = {
298
- type: "node",
299
- tag: "div",
298
+ type: 'node',
299
+ tag: 'div',
300
300
  attributes: {
301
- id: "test-id",
302
- className: "test-class",
301
+ id: 'test-id',
302
+ className: 'test-class',
303
303
  },
304
304
  children: [],
305
305
  };
@@ -307,16 +307,16 @@ describe("ComponentBuilder", () => {
307
307
  expect(result).not.toBeNull();
308
308
  });
309
309
 
310
- test("should filter out internal props", () => {
310
+ test('should filter out internal props', () => {
311
311
  const node: ComponentNode = {
312
- type: "node",
313
- tag: "div",
312
+ type: 'node',
313
+ tag: 'div',
314
314
  props: {
315
- type: "should-be-removed",
316
- tag: "should-be-removed",
317
- component: "should-be-removed",
318
- children: "should-be-removed",
319
- validProp: "should-remain",
315
+ type: 'should-be-removed',
316
+ tag: 'should-be-removed',
317
+ component: 'should-be-removed',
318
+ children: 'should-be-removed',
319
+ validProp: 'should-remain',
320
320
  },
321
321
  children: [],
322
322
  };
@@ -324,44 +324,44 @@ describe("ComponentBuilder", () => {
324
324
  expect(result).not.toBeNull();
325
325
  });
326
326
 
327
- test("should detect component root via __componentProps", () => {
327
+ test('should detect component root via __componentProps', () => {
328
328
  const node: ComponentNode = {
329
- type: "node",
330
- tag: "div",
329
+ type: 'node',
330
+ tag: 'div',
331
331
  children: [],
332
332
  };
333
333
  const result = builder.buildComponent({
334
334
  node,
335
335
  customProps: {
336
- __componentProps: { title: "Test" },
336
+ __componentProps: { title: 'Test' },
337
337
  },
338
338
  });
339
339
  expect(result).not.toBeNull();
340
340
  });
341
341
 
342
- test("should remap body tag to div to keep SSR and client DOMs identical", () => {
342
+ test('should remap body tag to div to keep SSR and client DOMs identical', () => {
343
343
  const node: ComponentNode = {
344
- type: "node",
345
- tag: "body",
346
- attributes: { className: "page-root" },
347
- children: ["Content"],
344
+ type: 'node',
345
+ tag: 'body',
346
+ attributes: { className: 'page-root' },
347
+ children: ['Content'],
348
348
  };
349
349
  const result = builder.buildComponent({ node }) as any;
350
350
  expect(result).not.toBeNull();
351
- expect(result.type).toBe("div");
352
- expect(result.props.className).toBe("page-root");
351
+ expect(result.type).toBe('div');
352
+ expect(result.props.className).toBe('page-root');
353
353
  });
354
354
 
355
- test("should remap html and head tags to div", () => {
356
- for (const tag of ["html", "head", "BODY"] as const) {
357
- const node: ComponentNode = { type: "node", tag, children: [] };
355
+ test('should remap html and head tags to div', () => {
356
+ for (const tag of ['html', 'head', 'BODY'] as const) {
357
+ const node: ComponentNode = { type: 'node', tag, children: [] };
358
358
  const result = builder.buildComponent({ node }) as any;
359
- expect(result.type).toBe("div");
359
+ expect(result.type).toBe('div');
360
360
  }
361
361
  });
362
362
  });
363
363
 
364
- describe("buildChildren", () => {
364
+ describe('buildChildren', () => {
365
365
  const defaultCtx = {
366
366
  elementPath: [0] as number[],
367
367
  parentComponentName: null,
@@ -369,42 +369,42 @@ describe("ComponentBuilder", () => {
369
369
  componentContext: null,
370
370
  };
371
371
 
372
- test("should return array for array of children", () => {
372
+ test('should return array for array of children', () => {
373
373
  const children: ComponentNode[] = [
374
- { type: "node", tag: "div", children: ["Child 1"] },
375
- { type: "node", tag: "span", children: ["Child 2"] },
374
+ { type: 'node', tag: 'div', children: ['Child 1'] },
375
+ { type: 'node', tag: 'span', children: ['Child 2'] },
376
376
  ];
377
377
  const result = builder.buildChildren(children, defaultCtx);
378
378
  expect(Array.isArray(result)).toBe(true);
379
379
  expect((result as any[]).length).toBe(2);
380
380
  });
381
381
 
382
- test("should return single element for single child", () => {
382
+ test('should return single element for single child', () => {
383
383
  const child: ComponentNode = {
384
- type: "node",
385
- tag: "div",
386
- children: ["Single child"],
384
+ type: 'node',
385
+ tag: 'div',
386
+ children: ['Single child'],
387
387
  };
388
388
  const result = builder.buildChildren(child, defaultCtx);
389
389
  expect(result).not.toBeNull();
390
390
  expect(Array.isArray(result)).toBe(false);
391
391
  });
392
392
 
393
- test("should return null for undefined children", () => {
393
+ test('should return null for undefined children', () => {
394
394
  const result = builder.buildChildren(undefined, defaultCtx);
395
395
  expect(result).toBeNull();
396
396
  });
397
397
 
398
- test("should return empty array for empty array", () => {
398
+ test('should return empty array for empty array', () => {
399
399
  const result = builder.buildChildren([], defaultCtx);
400
400
  expect(Array.isArray(result)).toBe(true);
401
401
  expect((result as any[]).length).toBe(0);
402
402
  });
403
403
 
404
- test("should generate correct paths for children", () => {
404
+ test('should generate correct paths for children', () => {
405
405
  const children: ComponentNode[] = [
406
- { type: "node", tag: "div", children: [] },
407
- { type: "node", tag: "span", children: [] },
406
+ { type: 'node', tag: 'div', children: [] },
407
+ { type: 'node', tag: 'span', children: [] },
408
408
  ];
409
409
  const result = builder.buildChildren(children, { ...defaultCtx, elementPath: [0, 1] });
410
410
  expect(Array.isArray(result)).toBe(true);
@@ -412,51 +412,51 @@ describe("ComponentBuilder", () => {
412
412
  });
413
413
  });
414
414
 
415
- describe("Helper Functions", () => {
416
- test("getParentComponentNameForNestedComponent should prioritize componentContext", () => {
415
+ describe('Helper Functions', () => {
416
+ test('getParentComponentNameForNestedComponent should prioritize componentContext', () => {
417
417
  // This is tested indirectly through component building
418
418
  // We can't directly test private methods, but we can verify behavior
419
419
  const componentDef = {
420
420
  component: {
421
421
  interface: {},
422
422
  structure: {
423
- type: "node",
424
- tag: "div",
423
+ type: 'node',
424
+ tag: 'div',
425
425
  children: [],
426
426
  },
427
427
  },
428
428
  };
429
- componentRegistry.register("ParentComponent", componentDef as any);
429
+ componentRegistry.register('ParentComponent', componentDef as any);
430
430
 
431
431
  const node: ComponentNode = {
432
- type: "component",
433
- component: "ParentComponent",
432
+ type: 'component',
433
+ component: 'ParentComponent',
434
434
  };
435
435
  const result = builder.buildComponent({
436
436
  node,
437
- componentContext: "ContextComponent",
438
- parentComponentName: "ParentName",
437
+ componentContext: 'ContextComponent',
438
+ parentComponentName: 'ParentName',
439
439
  });
440
440
  expect(result).not.toBeNull();
441
441
  });
442
442
 
443
- test("getEffectiveParentComponentName should use componentContext when available", () => {
443
+ test('getEffectiveParentComponentName should use componentContext when available', () => {
444
444
  // Tested indirectly through component building
445
445
  const node: ComponentNode = {
446
- type: "node",
447
- tag: "div",
446
+ type: 'node',
447
+ tag: 'div',
448
448
  children: [],
449
449
  };
450
450
  const result = builder.buildComponent({
451
451
  node,
452
- componentContext: "ContextComponent",
453
- parentComponentName: "ParentName",
452
+ componentContext: 'ContextComponent',
453
+ parentComponentName: 'ParentName',
454
454
  });
455
455
  expect(result).not.toBeNull();
456
456
  });
457
457
  });
458
458
 
459
- describe("Path Assignment for Slot Markers", () => {
459
+ describe('Path Assignment for Slot Markers', () => {
460
460
  /**
461
461
  * KNOWN ISSUE: Slot markers consume path indices incorrectly
462
462
  *
@@ -474,21 +474,18 @@ describe("ComponentBuilder", () => {
474
474
  * TODO: Fix path assignment to skip slot markers when assigning indices,
475
475
  * or adjust indices after slot expansion.
476
476
  */
477
- test.skip("slot markers should not consume path indices", () => {
477
+ test.skip('slot markers should not consume path indices', () => {
478
478
  const sectionDef = {
479
479
  component: {
480
480
  interface: {},
481
481
  structure: {
482
- type: "node",
483
- tag: "div",
482
+ type: 'node',
483
+ tag: 'div',
484
484
  children: [
485
485
  {
486
- type: "node",
487
- tag: "div",
488
- children: [
489
- { type: "slot" },
490
- { type: "component", component: "Card" },
491
- ],
486
+ type: 'node',
487
+ tag: 'div',
488
+ children: [{ type: 'slot' }, { type: 'component', component: 'Card' }],
492
489
  },
493
490
  ],
494
491
  },
@@ -499,20 +496,20 @@ describe("ComponentBuilder", () => {
499
496
  component: {
500
497
  interface: {},
501
498
  structure: {
502
- type: "node",
503
- tag: "div",
504
- children: ["Card Content"],
499
+ type: 'node',
500
+ tag: 'div',
501
+ children: ['Card Content'],
505
502
  },
506
503
  },
507
504
  };
508
505
 
509
- componentRegistry.register("Section", sectionDef as any);
510
- componentRegistry.register("Card", cardDef as any);
506
+ componentRegistry.register('Section', sectionDef as any);
507
+ componentRegistry.register('Card', cardDef as any);
511
508
 
512
509
  const pageNode: ComponentNode = {
513
- type: "component",
514
- component: "Section",
515
- children: [{ type: "node", tag: "p", children: ["Instance Child"] }],
510
+ type: 'component',
511
+ component: 'Section',
512
+ children: [{ type: 'node', tag: 'p', children: ['Instance Child'] }],
516
513
  };
517
514
 
518
515
  const result = builder.buildComponent({ node: pageNode });
@@ -524,51 +521,51 @@ describe("ComponentBuilder", () => {
524
521
  // OR Card gets a path that properly reflects its position relative to expanded slot content
525
522
  });
526
523
 
527
- test("should build component with slot marker correctly", () => {
524
+ test('should build component with slot marker correctly', () => {
528
525
  // Button.json: button > children
529
526
  const buttonDef = {
530
527
  component: {
531
528
  interface: {
532
- children: { type: "string", default: "Click me" },
529
+ children: { type: 'string', default: 'Click me' },
533
530
  },
534
531
  structure: {
535
- type: "node",
536
- tag: "button",
537
- children: [{ type: "slot" }],
532
+ type: 'node',
533
+ tag: 'button',
534
+ children: [{ type: 'slot' }],
538
535
  },
539
536
  },
540
537
  };
541
538
 
542
- componentRegistry.register("Button", buttonDef as any);
539
+ componentRegistry.register('Button', buttonDef as any);
543
540
 
544
541
  const pageNode: ComponentNode = {
545
- type: "component",
546
- component: "Button",
547
- children: [{ type: "node", tag: "span", children: ["Submit"] }],
542
+ type: 'component',
543
+ component: 'Button',
544
+ children: [{ type: 'node', tag: 'span', children: ['Submit'] }],
548
545
  };
549
546
 
550
547
  const result = builder.buildComponent({ node: pageNode });
551
548
  expect(result).not.toBeNull();
552
- expect(typeof result).toBe("object");
549
+ expect(typeof result).toBe('object');
553
550
  });
554
551
 
555
- test("should build nested children with multiple siblings after slot", () => {
552
+ test('should build nested children with multiple siblings after slot', () => {
556
553
  // Structure: div > div > [slot, Card, Button]
557
554
 
558
555
  const sectionDef = {
559
556
  component: {
560
557
  interface: {},
561
558
  structure: {
562
- type: "node",
563
- tag: "div",
559
+ type: 'node',
560
+ tag: 'div',
564
561
  children: [
565
562
  {
566
- type: "node",
567
- tag: "div",
563
+ type: 'node',
564
+ tag: 'div',
568
565
  children: [
569
- { type: "slot" },
570
- { type: "component", component: "Card" },
571
- { type: "component", component: "Button" },
566
+ { type: 'slot' },
567
+ { type: 'component', component: 'Card' },
568
+ { type: 'component', component: 'Button' },
572
569
  ],
573
570
  },
574
571
  ],
@@ -580,9 +577,9 @@ describe("ComponentBuilder", () => {
580
577
  component: {
581
578
  interface: {},
582
579
  structure: {
583
- type: "node",
584
- tag: "div",
585
- children: ["Card"],
580
+ type: 'node',
581
+ tag: 'div',
582
+ children: ['Card'],
586
583
  },
587
584
  },
588
585
  };
@@ -591,57 +588,54 @@ describe("ComponentBuilder", () => {
591
588
  component: {
592
589
  interface: {},
593
590
  structure: {
594
- type: "node",
595
- tag: "button",
596
- children: ["Button"],
591
+ type: 'node',
592
+ tag: 'button',
593
+ children: ['Button'],
597
594
  },
598
595
  },
599
596
  };
600
597
 
601
- componentRegistry.register("Section", sectionDef as any);
602
- componentRegistry.register("Card", cardDef as any);
603
- componentRegistry.register("Button", buttonDef as any);
598
+ componentRegistry.register('Section', sectionDef as any);
599
+ componentRegistry.register('Card', cardDef as any);
600
+ componentRegistry.register('Button', buttonDef as any);
604
601
 
605
602
  const pageNode: ComponentNode = {
606
- type: "component",
607
- component: "Section",
608
- children: [{ type: "node", tag: "p", children: ["Content"] }],
603
+ type: 'component',
604
+ component: 'Section',
605
+ children: [{ type: 'node', tag: 'p', children: ['Content'] }],
609
606
  };
610
607
 
611
608
  const result = builder.buildComponent({ node: pageNode });
612
609
  expect(result).not.toBeNull();
613
- expect(typeof result).toBe("object");
610
+ expect(typeof result).toBe('object');
614
611
  });
615
612
  });
616
613
 
617
- describe("Integration Tests", () => {
618
- test("should build complex component tree", () => {
614
+ describe('Integration Tests', () => {
615
+ test('should build complex component tree', () => {
619
616
  const cardDef = {
620
617
  component: {
621
618
  interface: {
622
- title: { type: "string", default: "Card Title" },
619
+ title: { type: 'string', default: 'Card Title' },
623
620
  },
624
621
  structure: {
625
- type: "node",
626
- tag: "div",
627
- children: [
628
- { type: "node", tag: "h2", children: ["{{title}}"] },
629
- { type: "slot" },
630
- ],
622
+ type: 'node',
623
+ tag: 'div',
624
+ children: [{ type: 'node', tag: 'h2', children: ['{{title}}'] }, { type: 'slot' }],
631
625
  },
632
626
  },
633
627
  };
634
- componentRegistry.register("Card", cardDef as any);
628
+ componentRegistry.register('Card', cardDef as any);
635
629
 
636
630
  const pageNode: ComponentNode = {
637
- type: "node",
638
- tag: "div",
631
+ type: 'node',
632
+ tag: 'div',
639
633
  children: [
640
634
  {
641
- type: "component",
642
- component: "Card",
643
- props: { title: "My Card" },
644
- children: [{ type: "node", tag: "p", children: ["Card content"] }],
635
+ type: 'component',
636
+ component: 'Card',
637
+ props: { title: 'My Card' },
638
+ children: [{ type: 'node', tag: 'p', children: ['Card content'] }],
645
639
  },
646
640
  ],
647
641
  };
@@ -649,90 +643,86 @@ describe("ComponentBuilder", () => {
649
643
  expect(result).not.toBeNull();
650
644
  });
651
645
 
652
- test("should handle nested component instances", () => {
646
+ test('should handle nested component instances', () => {
653
647
  const innerDef = {
654
648
  component: {
655
649
  interface: {},
656
650
  structure: {
657
- type: "node",
658
- tag: "span",
659
- children: ["Inner"],
651
+ type: 'node',
652
+ tag: 'span',
653
+ children: ['Inner'],
660
654
  },
661
655
  },
662
656
  };
663
- componentRegistry.register("Inner", innerDef as any);
657
+ componentRegistry.register('Inner', innerDef as any);
664
658
 
665
659
  const outerDef = {
666
660
  component: {
667
661
  interface: {},
668
662
  structure: {
669
- type: "node",
670
- tag: "div",
671
- children: [
672
- { type: "component", component: "Inner" },
673
- ],
663
+ type: 'node',
664
+ tag: 'div',
665
+ children: [{ type: 'component', component: 'Inner' }],
674
666
  },
675
667
  },
676
668
  };
677
- componentRegistry.register("Outer", outerDef as any);
669
+ componentRegistry.register('Outer', outerDef as any);
678
670
 
679
671
  const node: ComponentNode = {
680
- type: "component",
681
- component: "Outer",
672
+ type: 'component',
673
+ component: 'Outer',
682
674
  };
683
675
  const result = builder.buildComponent({ node });
684
676
  expect(result).not.toBeNull();
685
677
  });
686
678
 
687
- test("should propagate component context correctly", () => {
679
+ test('should propagate component context correctly', () => {
688
680
  const componentDef = {
689
681
  component: {
690
682
  interface: {},
691
683
  structure: {
692
- type: "node",
693
- tag: "div",
694
- children: [
695
- { type: "node", tag: "span", children: ["Child"] },
696
- ],
684
+ type: 'node',
685
+ tag: 'div',
686
+ children: [{ type: 'node', tag: 'span', children: ['Child'] }],
697
687
  },
698
688
  },
699
689
  };
700
- componentRegistry.register("TestComponent", componentDef as any);
690
+ componentRegistry.register('TestComponent', componentDef as any);
701
691
 
702
692
  const node: ComponentNode = {
703
- type: "component",
704
- component: "TestComponent",
693
+ type: 'component',
694
+ component: 'TestComponent',
705
695
  };
706
696
  const result = builder.buildComponent({
707
697
  node,
708
- componentContext: "TestComponent",
698
+ componentContext: 'TestComponent',
709
699
  });
710
700
  expect(result).not.toBeNull();
711
701
  });
712
702
 
713
- test("should respect viewportWidth for responsive styles", () => {
703
+ test('should respect viewportWidth for responsive styles', () => {
714
704
  const node: ComponentNode = {
715
- type: "node",
716
- tag: "div",
705
+ type: 'node',
706
+ tag: 'div',
717
707
  style: {
718
- base: { fontSize: "24px" },
719
- tablet: { fontSize: "18px" },
720
- mobile: { fontSize: "16px" },
708
+ base: { fontSize: '24px' },
709
+ tablet: { fontSize: '18px' },
710
+ mobile: { fontSize: '16px' },
721
711
  },
722
712
  children: [],
723
713
  };
724
-
714
+
725
715
  const resultDesktop = builder.buildComponent({ node, viewportWidth: 1920 });
726
716
  const resultTablet = builder.buildComponent({ node, viewportWidth: 768 });
727
717
  const resultMobile = builder.buildComponent({ node, viewportWidth: 375 });
728
-
718
+
729
719
  expect(resultDesktop).not.toBeNull();
730
720
  expect(resultTablet).not.toBeNull();
731
721
  expect(resultMobile).not.toBeNull();
732
722
  });
733
723
  });
734
724
 
735
- describe("buildComponent - List (Collection Mode)", () => {
725
+ describe('buildComponent - List (Collection Mode)', () => {
736
726
  // Helper to create mock CMS items
737
727
  const createMockCMSItems = (count: number): CMSItem[] => {
738
728
  return Array.from({ length: count }, (_, i) => ({
@@ -747,7 +737,7 @@ describe("ComponentBuilder", () => {
747
737
  const createCollectionListNode = (
748
738
  source: string,
749
739
  children: ComponentNode[],
750
- options: { limit?: number; offset?: number } = {}
740
+ options: { limit?: number; offset?: number } = {},
751
741
  ): ListNode => ({
752
742
  type: NODE_TYPE.LIST,
753
743
  sourceType: 'collection',
@@ -756,11 +746,9 @@ describe("ComponentBuilder", () => {
756
746
  ...options,
757
747
  });
758
748
 
759
- test("should render children for each CMS item", () => {
749
+ test('should render children for each CMS item', () => {
760
750
  const items = createMockCMSItems(3);
761
- const node = createCollectionListNode("posts", [
762
- { type: "node", tag: "div", children: ["Item content"] },
763
- ]);
751
+ const node = createCollectionListNode('posts', [{ type: 'node', tag: 'div', children: ['Item content'] }]);
764
752
 
765
753
  const result = builder.buildComponent({
766
754
  node: node as unknown as ComponentNode,
@@ -768,18 +756,16 @@ describe("ComponentBuilder", () => {
768
756
  });
769
757
 
770
758
  expect(result).not.toBeNull();
771
- expect(typeof result).toBe("object");
759
+ expect(typeof result).toBe('object');
772
760
  // Result should have props.children with 3 items
773
761
  const element = result as { props?: { children?: unknown[] } };
774
762
  expect(Array.isArray(element?.props?.children)).toBe(true);
775
763
  expect(element?.props?.children?.length).toBe(3);
776
764
  });
777
765
 
778
- test("should add data-cms-item-index attribute to each item wrapper", () => {
766
+ test('should add data-cms-item-index attribute to each item wrapper', () => {
779
767
  const items = createMockCMSItems(3);
780
- const node = createCollectionListNode("posts", [
781
- { type: "node", tag: "div", children: ["Item content"] },
782
- ]);
768
+ const node = createCollectionListNode('posts', [{ type: 'node', tag: 'div', children: ['Item content'] }]);
783
769
 
784
770
  const result = builder.buildComponent({
785
771
  node: node as unknown as ComponentNode,
@@ -801,13 +787,9 @@ describe("ComponentBuilder", () => {
801
787
  });
802
788
  });
803
789
 
804
- test("should interpolate {{item.field}} in text", () => {
805
- const items: CMSItem[] = [
806
- { _id: "1", _slug: "test", title: "Hello World" },
807
- ];
808
- const node = createCollectionListNode("posts", [
809
- { type: "node", tag: "h1", children: ["{{item.title}}"] },
810
- ]);
790
+ test('should interpolate {{item.field}} in text', () => {
791
+ const items: CMSItem[] = [{ _id: '1', _slug: 'test', title: 'Hello World' }];
792
+ const node = createCollectionListNode('posts', [{ type: 'node', tag: 'h1', children: ['{{item.title}}'] }]);
811
793
 
812
794
  const result = builder.buildComponent({
813
795
  node: node as unknown as ComponentNode,
@@ -816,13 +798,13 @@ describe("ComponentBuilder", () => {
816
798
 
817
799
  expect(result).not.toBeNull();
818
800
  // The text should be interpolated - we verify by checking result structure
819
- expect(typeof result).toBe("object");
801
+ expect(typeof result).toBe('object');
820
802
  });
821
803
 
822
- test("should interpolate {{itemIndex}}, {{itemFirst}}, {{itemLast}}", () => {
804
+ test('should interpolate {{itemIndex}}, {{itemFirst}}, {{itemLast}}', () => {
823
805
  const items = createMockCMSItems(3);
824
- const node = createCollectionListNode("posts", [
825
- { type: "node", tag: "span", children: ["Index: {{itemIndex}}, First: {{itemFirst}}, Last: {{itemLast}}"] },
806
+ const node = createCollectionListNode('posts', [
807
+ { type: 'node', tag: 'span', children: ['Index: {{itemIndex}}, First: {{itemFirst}}, Last: {{itemLast}}'] },
826
808
  ]);
827
809
 
828
810
  const result = builder.buildComponent({
@@ -831,16 +813,14 @@ describe("ComponentBuilder", () => {
831
813
  });
832
814
 
833
815
  expect(result).not.toBeNull();
834
- expect(typeof result).toBe("object");
816
+ expect(typeof result).toBe('object');
835
817
  // Should have 3 item containers
836
818
  const element = result as { props?: { children?: unknown[] } };
837
819
  expect(element?.props?.children?.length).toBe(3);
838
820
  });
839
821
 
840
- test("should show empty state when no items", () => {
841
- const node = createCollectionListNode("posts", [
842
- { type: "node", tag: "div", children: ["Item"] },
843
- ]);
822
+ test('should show empty state when no items', () => {
823
+ const node = createCollectionListNode('posts', [{ type: 'node', tag: 'div', children: ['Item'] }]);
844
824
 
845
825
  const result = builder.buildComponent({
846
826
  node: node as unknown as ComponentNode,
@@ -848,16 +828,14 @@ describe("ComponentBuilder", () => {
848
828
  });
849
829
 
850
830
  expect(result).not.toBeNull();
851
- expect(typeof result).toBe("object");
831
+ expect(typeof result).toBe('object');
852
832
  // Empty state should be a single div with message
853
833
  const element = result as { props?: { children?: unknown } };
854
834
  expect(element?.props?.children).not.toBeNull();
855
835
  });
856
836
 
857
- test("should show empty state when collection not in map", () => {
858
- const node = createCollectionListNode("posts", [
859
- { type: "node", tag: "div", children: ["Item"] },
860
- ]);
837
+ test('should show empty state when collection not in map', () => {
838
+ const node = createCollectionListNode('posts', [{ type: 'node', tag: 'div', children: ['Item'] }]);
861
839
 
862
840
  const result = builder.buildComponent({
863
841
  node: node as unknown as ComponentNode,
@@ -865,14 +843,12 @@ describe("ComponentBuilder", () => {
865
843
  });
866
844
 
867
845
  expect(result).not.toBeNull();
868
- expect(typeof result).toBe("object");
846
+ expect(typeof result).toBe('object');
869
847
  });
870
848
 
871
- test("should apply limit to items", () => {
849
+ test('should apply limit to items', () => {
872
850
  const items = createMockCMSItems(5);
873
- const node = createCollectionListNode("posts", [
874
- { type: "node", tag: "div", children: ["Item"] },
875
- ], { limit: 2 });
851
+ const node = createCollectionListNode('posts', [{ type: 'node', tag: 'div', children: ['Item'] }], { limit: 2 });
876
852
 
877
853
  const result = builder.buildComponent({
878
854
  node: node as unknown as ComponentNode,
@@ -885,11 +861,9 @@ describe("ComponentBuilder", () => {
885
861
  expect(element?.props?.children?.length).toBe(2);
886
862
  });
887
863
 
888
- test("should apply offset to items", () => {
864
+ test('should apply offset to items', () => {
889
865
  const items = createMockCMSItems(5);
890
- const node = createCollectionListNode("posts", [
891
- { type: "node", tag: "div", children: ["Item"] },
892
- ], { offset: 2 });
866
+ const node = createCollectionListNode('posts', [{ type: 'node', tag: 'div', children: ['Item'] }], { offset: 2 });
893
867
 
894
868
  const result = builder.buildComponent({
895
869
  node: node as unknown as ComponentNode,
@@ -902,11 +876,12 @@ describe("ComponentBuilder", () => {
902
876
  expect(element?.props?.children?.length).toBe(3); // 5 - 2 = 3
903
877
  });
904
878
 
905
- test("should apply both limit and offset", () => {
879
+ test('should apply both limit and offset', () => {
906
880
  const items = createMockCMSItems(5);
907
- const node = createCollectionListNode("posts", [
908
- { type: "node", tag: "div", children: ["Item"] },
909
- ], { offset: 1, limit: 2 });
881
+ const node = createCollectionListNode('posts', [{ type: 'node', tag: 'div', children: ['Item'] }], {
882
+ offset: 1,
883
+ limit: 2,
884
+ });
910
885
 
911
886
  const result = builder.buildComponent({
912
887
  node: node as unknown as ComponentNode,
@@ -919,19 +894,19 @@ describe("ComponentBuilder", () => {
919
894
  expect(element?.props?.children?.length).toBe(2);
920
895
  });
921
896
 
922
- test("should filter items with simple object filter", () => {
897
+ test('should filter items with simple object filter', () => {
923
898
  const items: CMSItem[] = [
924
- { _id: "1", _slug: "post1", featured: true, title: "Featured Post" },
925
- { _id: "2", _slug: "post2", featured: false, title: "Regular Post 1" },
926
- { _id: "3", _slug: "post3", featured: true, title: "Another Featured" },
927
- { _id: "4", _slug: "post4", featured: false, title: "Regular Post 2" },
899
+ { _id: '1', _slug: 'post1', featured: true, title: 'Featured Post' },
900
+ { _id: '2', _slug: 'post2', featured: false, title: 'Regular Post 1' },
901
+ { _id: '3', _slug: 'post3', featured: true, title: 'Another Featured' },
902
+ { _id: '4', _slug: 'post4', featured: false, title: 'Regular Post 2' },
928
903
  ];
929
904
  const node: ListNode = {
930
905
  type: NODE_TYPE.LIST,
931
906
  sourceType: 'collection',
932
- source: "posts",
907
+ source: 'posts',
933
908
  filter: { featured: true },
934
- children: [{ type: "node", tag: "div", children: ["Item"] }],
909
+ children: [{ type: 'node', tag: 'div', children: ['Item'] }],
935
910
  };
936
911
 
937
912
  const result = builder.buildComponent({
@@ -945,19 +920,19 @@ describe("ComponentBuilder", () => {
945
920
  expect(element?.props?.children?.length).toBe(2); // Only featured items
946
921
  });
947
922
 
948
- test("should filter items with operator condition", () => {
923
+ test('should filter items with operator condition', () => {
949
924
  const items: CMSItem[] = [
950
- { _id: "1", _slug: "post1", price: 50, title: "Cheap" },
951
- { _id: "2", _slug: "post2", price: 150, title: "Expensive 1" },
952
- { _id: "3", _slug: "post3", price: 200, title: "Expensive 2" },
953
- { _id: "4", _slug: "post4", price: 75, title: "Mid" },
925
+ { _id: '1', _slug: 'post1', price: 50, title: 'Cheap' },
926
+ { _id: '2', _slug: 'post2', price: 150, title: 'Expensive 1' },
927
+ { _id: '3', _slug: 'post3', price: 200, title: 'Expensive 2' },
928
+ { _id: '4', _slug: 'post4', price: 75, title: 'Mid' },
954
929
  ];
955
930
  const node: ListNode = {
956
931
  type: NODE_TYPE.LIST,
957
932
  sourceType: 'collection',
958
- source: "posts",
959
- filter: { field: "price", operator: "gt", value: 100 },
960
- children: [{ type: "node", tag: "div", children: ["Item"] }],
933
+ source: 'posts',
934
+ filter: { field: 'price', operator: 'gt', value: 100 },
935
+ children: [{ type: 'node', tag: 'div', children: ['Item'] }],
961
936
  };
962
937
 
963
938
  const result = builder.buildComponent({
@@ -971,24 +946,24 @@ describe("ComponentBuilder", () => {
971
946
  expect(element?.props?.children?.length).toBe(2); // Only items with price > 100
972
947
  });
973
948
 
974
- test("should filter items with template value from cmsContext", () => {
949
+ test('should filter items with template value from cmsContext', () => {
975
950
  const items: CMSItem[] = [
976
- { _id: "1", _slug: "post1", category: "tech", title: "Tech Post 1" },
977
- { _id: "2", _slug: "post2", category: "news", title: "News Post" },
978
- { _id: "3", _slug: "post3", category: "tech", title: "Tech Post 2" },
951
+ { _id: '1', _slug: 'post1', category: 'tech', title: 'Tech Post 1' },
952
+ { _id: '2', _slug: 'post2', category: 'news', title: 'News Post' },
953
+ { _id: '3', _slug: 'post3', category: 'tech', title: 'Tech Post 2' },
979
954
  ];
980
955
  const node: ListNode = {
981
956
  type: NODE_TYPE.LIST,
982
957
  sourceType: 'collection',
983
- source: "posts",
984
- filter: { category: "{{cms.category}}" },
985
- children: [{ type: "node", tag: "div", children: ["Item"] }],
958
+ source: 'posts',
959
+ filter: { category: '{{cms.category}}' },
960
+ children: [{ type: 'node', tag: 'div', children: ['Item'] }],
986
961
  };
987
962
 
988
963
  const result = builder.buildComponent({
989
964
  node: node as unknown as ComponentNode,
990
965
  collectionItemsMap: { posts: items },
991
- cmsContext: { category: "tech" },
966
+ cmsContext: { category: 'tech' },
992
967
  });
993
968
 
994
969
  expect(result).not.toBeNull();
@@ -997,18 +972,18 @@ describe("ComponentBuilder", () => {
997
972
  expect(element?.props?.children?.length).toBe(2); // Only tech posts
998
973
  });
999
974
 
1000
- test("should sort items ascending", () => {
975
+ test('should sort items ascending', () => {
1001
976
  const items: CMSItem[] = [
1002
- { _id: "1", _slug: "c", title: "C" },
1003
- { _id: "2", _slug: "a", title: "A" },
1004
- { _id: "3", _slug: "b", title: "B" },
977
+ { _id: '1', _slug: 'c', title: 'C' },
978
+ { _id: '2', _slug: 'a', title: 'A' },
979
+ { _id: '3', _slug: 'b', title: 'B' },
1005
980
  ];
1006
981
  const node: ListNode = {
1007
982
  type: NODE_TYPE.LIST,
1008
983
  sourceType: 'collection',
1009
- source: "posts",
1010
- sort: { field: "title", order: "asc" },
1011
- children: [{ type: "node", tag: "div", children: ["{{item.title}}"] }],
984
+ source: 'posts',
985
+ sort: { field: 'title', order: 'asc' },
986
+ children: [{ type: 'node', tag: 'div', children: ['{{item.title}}'] }],
1012
987
  };
1013
988
 
1014
989
  const result = builder.buildComponent({
@@ -1023,18 +998,18 @@ describe("ComponentBuilder", () => {
1023
998
  // We can't easily check the order of rendered children without more complex inspection
1024
999
  });
1025
1000
 
1026
- test("should sort items descending", () => {
1001
+ test('should sort items descending', () => {
1027
1002
  const items: CMSItem[] = [
1028
- { _id: "1", _slug: "p1", date: 100 },
1029
- { _id: "2", _slug: "p2", date: 300 },
1030
- { _id: "3", _slug: "p3", date: 200 },
1003
+ { _id: '1', _slug: 'p1', date: 100 },
1004
+ { _id: '2', _slug: 'p2', date: 300 },
1005
+ { _id: '3', _slug: 'p3', date: 200 },
1031
1006
  ];
1032
1007
  const node: ListNode = {
1033
1008
  type: NODE_TYPE.LIST,
1034
1009
  sourceType: 'collection',
1035
- source: "posts",
1036
- sort: { field: "date", order: "desc" },
1037
- children: [{ type: "node", tag: "div", children: ["Item"] }],
1010
+ source: 'posts',
1011
+ sort: { field: 'date', order: 'desc' },
1012
+ children: [{ type: 'node', tag: 'div', children: ['Item'] }],
1038
1013
  };
1039
1014
 
1040
1015
  const result = builder.buildComponent({
@@ -1048,22 +1023,22 @@ describe("ComponentBuilder", () => {
1048
1023
  expect(element?.props?.children?.length).toBe(3);
1049
1024
  });
1050
1025
 
1051
- test("should apply filter BEFORE limit", () => {
1026
+ test('should apply filter BEFORE limit', () => {
1052
1027
  // 5 items: 2 featured, 3 not featured
1053
1028
  const items: CMSItem[] = [
1054
- { _id: "1", _slug: "p1", featured: true, title: "Featured 1" },
1055
- { _id: "2", _slug: "p2", featured: false, title: "Regular 1" },
1056
- { _id: "3", _slug: "p3", featured: false, title: "Regular 2" },
1057
- { _id: "4", _slug: "p4", featured: true, title: "Featured 2" },
1058
- { _id: "5", _slug: "p5", featured: false, title: "Regular 3" },
1029
+ { _id: '1', _slug: 'p1', featured: true, title: 'Featured 1' },
1030
+ { _id: '2', _slug: 'p2', featured: false, title: 'Regular 1' },
1031
+ { _id: '3', _slug: 'p3', featured: false, title: 'Regular 2' },
1032
+ { _id: '4', _slug: 'p4', featured: true, title: 'Featured 2' },
1033
+ { _id: '5', _slug: 'p5', featured: false, title: 'Regular 3' },
1059
1034
  ];
1060
1035
  const node: ListNode = {
1061
1036
  type: NODE_TYPE.LIST,
1062
1037
  sourceType: 'collection',
1063
- source: "posts",
1038
+ source: 'posts',
1064
1039
  filter: { featured: true },
1065
1040
  limit: 3, // Without proper order, this would limit before filtering
1066
- children: [{ type: "node", tag: "div", children: ["Item"] }],
1041
+ children: [{ type: 'node', tag: 'div', children: ['Item'] }],
1067
1042
  };
1068
1043
 
1069
1044
  const result = builder.buildComponent({
@@ -1078,21 +1053,21 @@ describe("ComponentBuilder", () => {
1078
1053
  expect(element?.props?.children?.length).toBe(2);
1079
1054
  });
1080
1055
 
1081
- test("should apply sort BEFORE limit", () => {
1056
+ test('should apply sort BEFORE limit', () => {
1082
1057
  const items: CMSItem[] = [
1083
- { _id: "1", _slug: "p1", date: 100 },
1084
- { _id: "2", _slug: "p2", date: 300 },
1085
- { _id: "3", _slug: "p3", date: 200 },
1086
- { _id: "4", _slug: "p4", date: 400 },
1087
- { _id: "5", _slug: "p5", date: 50 },
1058
+ { _id: '1', _slug: 'p1', date: 100 },
1059
+ { _id: '2', _slug: 'p2', date: 300 },
1060
+ { _id: '3', _slug: 'p3', date: 200 },
1061
+ { _id: '4', _slug: 'p4', date: 400 },
1062
+ { _id: '5', _slug: 'p5', date: 50 },
1088
1063
  ];
1089
1064
  const node: ListNode = {
1090
1065
  type: NODE_TYPE.LIST,
1091
1066
  sourceType: 'collection',
1092
- source: "posts",
1093
- sort: { field: "date", order: "desc" },
1067
+ source: 'posts',
1068
+ sort: { field: 'date', order: 'desc' },
1094
1069
  limit: 2, // Should get 2 most recent (highest date)
1095
- children: [{ type: "node", tag: "div", children: ["Item"] }],
1070
+ children: [{ type: 'node', tag: 'div', children: ['Item'] }],
1096
1071
  };
1097
1072
 
1098
1073
  const result = builder.buildComponent({
@@ -1106,22 +1081,22 @@ describe("ComponentBuilder", () => {
1106
1081
  expect(element?.props?.children?.length).toBe(2);
1107
1082
  });
1108
1083
 
1109
- test("should apply multiple filter conditions with AND logic", () => {
1084
+ test('should apply multiple filter conditions with AND logic', () => {
1110
1085
  const items: CMSItem[] = [
1111
- { _id: "1", _slug: "p1", featured: true, price: 150 },
1112
- { _id: "2", _slug: "p2", featured: false, price: 200 },
1113
- { _id: "3", _slug: "p3", featured: true, price: 50 },
1114
- { _id: "4", _slug: "p4", featured: true, price: 200 },
1086
+ { _id: '1', _slug: 'p1', featured: true, price: 150 },
1087
+ { _id: '2', _slug: 'p2', featured: false, price: 200 },
1088
+ { _id: '3', _slug: 'p3', featured: true, price: 50 },
1089
+ { _id: '4', _slug: 'p4', featured: true, price: 200 },
1115
1090
  ];
1116
1091
  const node: ListNode = {
1117
1092
  type: NODE_TYPE.LIST,
1118
1093
  sourceType: 'collection',
1119
- source: "posts",
1094
+ source: 'posts',
1120
1095
  filter: [
1121
- { field: "featured", value: true },
1122
- { field: "price", operator: "gte", value: 100 }
1096
+ { field: 'featured', value: true },
1097
+ { field: 'price', operator: 'gte', value: 100 },
1123
1098
  ],
1124
- children: [{ type: "node", tag: "div", children: ["Item"] }],
1099
+ children: [{ type: 'node', tag: 'div', children: ['Item'] }],
1125
1100
  };
1126
1101
 
1127
1102
  const result = builder.buildComponent({
@@ -1137,7 +1112,7 @@ describe("ComponentBuilder", () => {
1137
1112
  });
1138
1113
  });
1139
1114
 
1140
- describe("Interactive Styles - Element Class Generation", () => {
1115
+ describe('Interactive Styles - Element Class Generation', () => {
1141
1116
  /**
1142
1117
  * These tests verify that element classes are generated correctly
1143
1118
  * for interactive styles in both page and component contexts.
@@ -1149,11 +1124,11 @@ describe("ComponentBuilder", () => {
1149
1124
  * 4. The same component element always gets the same class
1150
1125
  */
1151
1126
 
1152
- test("should not add element class when no interactive styles", () => {
1127
+ test('should not add element class when no interactive styles', () => {
1153
1128
  const node: ComponentNode = {
1154
- type: "node",
1155
- tag: "div",
1156
- children: ["No interactive styles"],
1129
+ type: 'node',
1130
+ tag: 'div',
1131
+ children: ['No interactive styles'],
1157
1132
  };
1158
1133
 
1159
1134
  const result = builder.buildComponent({ node });
@@ -1161,12 +1136,12 @@ describe("ComponentBuilder", () => {
1161
1136
  // No element class should be added when there are no interactive styles
1162
1137
  });
1163
1138
 
1164
- test("should add element class when node has generateElementClass flag", () => {
1139
+ test('should add element class when node has generateElementClass flag', () => {
1165
1140
  const node: ComponentNode = {
1166
- type: "node",
1167
- tag: "div",
1141
+ type: 'node',
1142
+ tag: 'div',
1168
1143
  generateElementClass: true,
1169
- children: ["Has element class flag"],
1144
+ children: ['Has element class flag'],
1170
1145
  };
1171
1146
 
1172
1147
  const result = builder.buildComponent({ node });
@@ -1174,14 +1149,12 @@ describe("ComponentBuilder", () => {
1174
1149
  // Element class should be generated when flag is set
1175
1150
  });
1176
1151
 
1177
- test("should add element class when node has interactive styles", () => {
1152
+ test('should add element class when node has interactive styles', () => {
1178
1153
  const node: ComponentNode = {
1179
- type: "node",
1180
- tag: "div",
1181
- interactiveStyles: [
1182
- { postfix: ":hover", style: { backgroundColor: "primary" } },
1183
- ],
1184
- children: ["Has interactive styles"],
1154
+ type: 'node',
1155
+ tag: 'div',
1156
+ interactiveStyles: [{ postfix: ':hover', style: { backgroundColor: 'primary' } }],
1157
+ children: ['Has interactive styles'],
1185
1158
  };
1186
1159
 
1187
1160
  const result = builder.buildComponent({ node });
@@ -1189,23 +1162,21 @@ describe("ComponentBuilder", () => {
1189
1162
  // Element class should be generated for interactive styles
1190
1163
  });
1191
1164
 
1192
- test("should use page-based class for page elements", () => {
1165
+ test('should use page-based class for page elements', () => {
1193
1166
  // Create builder with page name getter
1194
1167
  const pageBuilder = new ComponentBuilder({
1195
1168
  componentRegistry,
1196
1169
  elementRegistry,
1197
- getCurrentPageName: () => "about",
1198
- getCurrentFileType: () => "page",
1170
+ getCurrentPageName: () => 'about',
1171
+ getCurrentFileType: () => 'page',
1199
1172
  });
1200
1173
 
1201
1174
  const node: ComponentNode = {
1202
- type: "node",
1203
- tag: "button",
1204
- label: "submit-button",
1205
- interactiveStyles: [
1206
- { postfix: ":hover", style: { opacity: "0.9" } },
1207
- ],
1208
- children: ["Submit"],
1175
+ type: 'node',
1176
+ tag: 'button',
1177
+ label: 'submit-button',
1178
+ interactiveStyles: [{ postfix: ':hover', style: { opacity: '0.9' } }],
1179
+ children: ['Submit'],
1209
1180
  };
1210
1181
 
1211
1182
  const result = pageBuilder.buildComponent({ node });
@@ -1213,28 +1184,26 @@ describe("ComponentBuilder", () => {
1213
1184
  // Element class should be p_about_submit-button for page elements
1214
1185
  });
1215
1186
 
1216
- test("should use component-based class when inside component context", () => {
1187
+ test('should use component-based class when inside component context', () => {
1217
1188
  const node: ComponentNode = {
1218
- type: "node",
1219
- tag: "button",
1220
- label: "action-button",
1221
- interactiveStyles: [
1222
- { postfix: ":hover", style: { backgroundColor: "primary-dark" } },
1223
- ],
1224
- children: ["Click"],
1189
+ type: 'node',
1190
+ tag: 'button',
1191
+ label: 'action-button',
1192
+ interactiveStyles: [{ postfix: ':hover', style: { backgroundColor: 'primary-dark' } }],
1193
+ children: ['Click'],
1225
1194
  };
1226
1195
 
1227
1196
  // Build with componentContext set (inside a component)
1228
1197
  const result = builder.buildComponent({
1229
1198
  node,
1230
- componentContext: "Button",
1199
+ componentContext: 'Button',
1231
1200
  });
1232
1201
 
1233
1202
  expect(result).not.toBeNull();
1234
1203
  // Element class should be c_button_action-button for component elements
1235
1204
  });
1236
1205
 
1237
- test("should use relative path for component elements", () => {
1206
+ test('should use relative path for component elements', () => {
1238
1207
  // When a component is placed at different positions on a page,
1239
1208
  // its internal elements should always get the same class
1240
1209
  // because they use paths relative to the component root.
@@ -1243,32 +1212,28 @@ describe("ComponentBuilder", () => {
1243
1212
  component: {
1244
1213
  interface: {},
1245
1214
  structure: {
1246
- type: "node",
1247
- tag: "div",
1248
- label: "root",
1249
- interactiveStyles: [
1250
- { postfix: ":hover", style: { boxShadow: "0 4px 12px rgba(0,0,0,0.1)" } },
1251
- ],
1215
+ type: 'node',
1216
+ tag: 'div',
1217
+ label: 'root',
1218
+ interactiveStyles: [{ postfix: ':hover', style: { boxShadow: '0 4px 12px rgba(0,0,0,0.1)' } }],
1252
1219
  children: [
1253
1220
  {
1254
- type: "node",
1255
- tag: "span",
1256
- label: "label",
1257
- interactiveStyles: [
1258
- { postfix: ":hover", style: { color: "primary" } },
1259
- ],
1260
- children: ["Label"],
1221
+ type: 'node',
1222
+ tag: 'span',
1223
+ label: 'label',
1224
+ interactiveStyles: [{ postfix: ':hover', style: { color: 'primary' } }],
1225
+ children: ['Label'],
1261
1226
  },
1262
1227
  ],
1263
1228
  },
1264
1229
  },
1265
1230
  };
1266
- componentRegistry.register("HoverCard", componentDef as any);
1231
+ componentRegistry.register('HoverCard', componentDef as any);
1267
1232
 
1268
1233
  // First instance at page path [0]
1269
1234
  const node1: ComponentNode = {
1270
- type: "component",
1271
- component: "HoverCard",
1235
+ type: 'component',
1236
+ component: 'HoverCard',
1272
1237
  };
1273
1238
 
1274
1239
  const result1 = builder.buildComponent({
@@ -1291,7 +1256,7 @@ describe("ComponentBuilder", () => {
1291
1256
  // component-relative paths, not page-absolute paths
1292
1257
  });
1293
1258
 
1294
- test("should generate different classes for page vs component with same name", () => {
1259
+ test('should generate different classes for page vs component with same name', () => {
1295
1260
  // A page named "button" and a component named "Button" should
1296
1261
  // produce different element classes (p_button_... vs c_button_...)
1297
1262
 
@@ -1299,22 +1264,20 @@ describe("ComponentBuilder", () => {
1299
1264
  component: {
1300
1265
  interface: {},
1301
1266
  structure: {
1302
- type: "node",
1303
- tag: "button",
1304
- label: "root",
1305
- interactiveStyles: [
1306
- { postfix: ":hover", style: { opacity: "0.9" } },
1307
- ],
1308
- children: ["Component Button"],
1267
+ type: 'node',
1268
+ tag: 'button',
1269
+ label: 'root',
1270
+ interactiveStyles: [{ postfix: ':hover', style: { opacity: '0.9' } }],
1271
+ children: ['Component Button'],
1309
1272
  },
1310
1273
  },
1311
1274
  };
1312
- componentRegistry.register("Button", componentDef as any);
1275
+ componentRegistry.register('Button', componentDef as any);
1313
1276
 
1314
1277
  // Component instance
1315
1278
  const componentNode: ComponentNode = {
1316
- type: "component",
1317
- component: "Button",
1279
+ type: 'component',
1280
+ component: 'Button',
1318
1281
  };
1319
1282
 
1320
1283
  const componentResult = builder.buildComponent({ node: componentNode });
@@ -1324,18 +1287,16 @@ describe("ComponentBuilder", () => {
1324
1287
  const pageBuilder = new ComponentBuilder({
1325
1288
  componentRegistry,
1326
1289
  elementRegistry,
1327
- getCurrentPageName: () => "button",
1328
- getCurrentFileType: () => "page",
1290
+ getCurrentPageName: () => 'button',
1291
+ getCurrentFileType: () => 'page',
1329
1292
  });
1330
1293
 
1331
1294
  const pageNode: ComponentNode = {
1332
- type: "node",
1333
- tag: "button",
1334
- label: "root",
1335
- interactiveStyles: [
1336
- { postfix: ":hover", style: { opacity: "0.9" } },
1337
- ],
1338
- children: ["Page Button"],
1295
+ type: 'node',
1296
+ tag: 'button',
1297
+ label: 'root',
1298
+ interactiveStyles: [{ postfix: ':hover', style: { opacity: '0.9' } }],
1299
+ children: ['Page Button'],
1339
1300
  };
1340
1301
 
1341
1302
  const pageResult = pageBuilder.buildComponent({ node: pageNode });
@@ -1344,26 +1305,24 @@ describe("ComponentBuilder", () => {
1344
1305
  // Component uses c_button_root, page uses p_button_root
1345
1306
  });
1346
1307
 
1347
- test("should propagate componentContext through nested children", () => {
1308
+ test('should propagate componentContext through nested children', () => {
1348
1309
  const componentDef = {
1349
1310
  component: {
1350
1311
  interface: {},
1351
1312
  structure: {
1352
- type: "node",
1353
- tag: "div",
1313
+ type: 'node',
1314
+ tag: 'div',
1354
1315
  children: [
1355
1316
  {
1356
- type: "node",
1357
- tag: "div",
1317
+ type: 'node',
1318
+ tag: 'div',
1358
1319
  children: [
1359
1320
  {
1360
- type: "node",
1361
- tag: "button",
1362
- label: "deeply-nested",
1363
- interactiveStyles: [
1364
- { postfix: ":hover", style: { color: "primary" } },
1365
- ],
1366
- children: ["Nested Button"],
1321
+ type: 'node',
1322
+ tag: 'button',
1323
+ label: 'deeply-nested',
1324
+ interactiveStyles: [{ postfix: ':hover', style: { color: 'primary' } }],
1325
+ children: ['Nested Button'],
1367
1326
  },
1368
1327
  ],
1369
1328
  },
@@ -1371,11 +1330,11 @@ describe("ComponentBuilder", () => {
1371
1330
  },
1372
1331
  },
1373
1332
  };
1374
- componentRegistry.register("DeepComponent", componentDef as any);
1333
+ componentRegistry.register('DeepComponent', componentDef as any);
1375
1334
 
1376
1335
  const node: ComponentNode = {
1377
- type: "component",
1378
- component: "DeepComponent",
1336
+ type: 'component',
1337
+ component: 'DeepComponent',
1379
1338
  };
1380
1339
 
1381
1340
  const result = builder.buildComponent({ node });
@@ -1385,50 +1344,50 @@ describe("ComponentBuilder", () => {
1385
1344
  // because componentContext is propagated through the tree
1386
1345
  });
1387
1346
 
1388
- test("should handle multiple interactive states on same element", () => {
1347
+ test('should handle multiple interactive states on same element', () => {
1389
1348
  const node: ComponentNode = {
1390
- type: "node",
1391
- tag: "button",
1392
- label: "multi-state",
1349
+ type: 'node',
1350
+ tag: 'button',
1351
+ label: 'multi-state',
1393
1352
  interactiveStyles: [
1394
- { postfix: ":hover", style: { backgroundColor: "primary-light" } },
1395
- { postfix: ":active", style: { backgroundColor: "primary-dark" } },
1396
- { postfix: ":focus", style: { outline: "2px solid var(--primary)" } },
1397
- { postfix: ":disabled", style: { opacity: "0.5", cursor: "not-allowed" } },
1353
+ { postfix: ':hover', style: { backgroundColor: 'primary-light' } },
1354
+ { postfix: ':active', style: { backgroundColor: 'primary-dark' } },
1355
+ { postfix: ':focus', style: { outline: '2px solid var(--primary)' } },
1356
+ { postfix: ':disabled', style: { opacity: '0.5', cursor: 'not-allowed' } },
1398
1357
  ],
1399
- children: ["Multi-state Button"],
1358
+ children: ['Multi-state Button'],
1400
1359
  };
1401
1360
 
1402
1361
  const result = builder.buildComponent({
1403
1362
  node,
1404
- componentContext: "MultiButton",
1363
+ componentContext: 'MultiButton',
1405
1364
  });
1406
1365
 
1407
1366
  expect(result).not.toBeNull();
1408
1367
  // All states should use the same element class: c_multibutton_multi-state
1409
1368
  });
1410
1369
 
1411
- test("should handle responsive interactive styles", () => {
1370
+ test('should handle responsive interactive styles', () => {
1412
1371
  const node: ComponentNode = {
1413
- type: "node",
1414
- tag: "div",
1415
- label: "responsive-hover",
1372
+ type: 'node',
1373
+ tag: 'div',
1374
+ label: 'responsive-hover',
1416
1375
  interactiveStyles: [
1417
1376
  {
1418
- postfix: ":hover",
1377
+ postfix: ':hover',
1419
1378
  style: {
1420
- base: { padding: "20px" },
1421
- tablet: { padding: "16px" },
1422
- mobile: { padding: "12px" },
1379
+ base: { padding: '20px' },
1380
+ tablet: { padding: '16px' },
1381
+ mobile: { padding: '12px' },
1423
1382
  },
1424
1383
  },
1425
1384
  ],
1426
- children: ["Responsive hover"],
1385
+ children: ['Responsive hover'],
1427
1386
  };
1428
1387
 
1429
1388
  const result = builder.buildComponent({
1430
1389
  node,
1431
- componentContext: "ResponsiveCard",
1390
+ componentContext: 'ResponsiveCard',
1432
1391
  });
1433
1392
 
1434
1393
  expect(result).not.toBeNull();
@@ -1436,27 +1395,27 @@ describe("ComponentBuilder", () => {
1436
1395
  });
1437
1396
  });
1438
1397
 
1439
- describe("buildComponent - Conditional Rendering (if property)", () => {
1440
- test("should render node when if is true", () => {
1398
+ describe('buildComponent - Conditional Rendering (if property)', () => {
1399
+ test('should render node when if is true', () => {
1441
1400
  const node: ComponentNode = {
1442
- type: "node",
1443
- tag: "div",
1401
+ type: 'node',
1402
+ tag: 'div',
1444
1403
  if: true,
1445
- children: ["Visible content"],
1404
+ children: ['Visible content'],
1446
1405
  } as any;
1447
1406
 
1448
1407
  const result = builder.buildComponent({ node });
1449
1408
 
1450
1409
  expect(result).not.toBeNull();
1451
- expect(typeof result).toBe("object");
1410
+ expect(typeof result).toBe('object');
1452
1411
  });
1453
1412
 
1454
- test("should skip node when if is false", () => {
1413
+ test('should skip node when if is false', () => {
1455
1414
  const node: ComponentNode = {
1456
- type: "node",
1457
- tag: "div",
1415
+ type: 'node',
1416
+ tag: 'div',
1458
1417
  if: false,
1459
- children: ["Hidden content"],
1418
+ children: ['Hidden content'],
1460
1419
  } as any;
1461
1420
 
1462
1421
  const result = builder.buildComponent({ node });
@@ -1464,11 +1423,11 @@ describe("ComponentBuilder", () => {
1464
1423
  expect(result).toBeNull();
1465
1424
  });
1466
1425
 
1467
- test("should render node when if property is absent", () => {
1426
+ test('should render node when if property is absent', () => {
1468
1427
  const node: ComponentNode = {
1469
- type: "node",
1470
- tag: "div",
1471
- children: ["Default visible"],
1428
+ type: 'node',
1429
+ tag: 'div',
1430
+ children: ['Default visible'],
1472
1431
  };
1473
1432
 
1474
1433
  const result = builder.buildComponent({ node });
@@ -1476,41 +1435,41 @@ describe("ComponentBuilder", () => {
1476
1435
  expect(result).not.toBeNull();
1477
1436
  });
1478
1437
 
1479
- test("should resolve if mapping from component props", () => {
1438
+ test('should resolve if mapping from component props', () => {
1480
1439
  const node: ComponentNode = {
1481
- type: "node",
1482
- tag: "div",
1440
+ type: 'node',
1441
+ tag: 'div',
1483
1442
  if: {
1484
1443
  _mapping: true,
1485
- prop: "showBanner",
1486
- values: { "true": true, "false": false },
1444
+ prop: 'showBanner',
1445
+ values: { true: true, false: false },
1487
1446
  },
1488
- children: ["Conditional banner"],
1447
+ children: ['Conditional banner'],
1489
1448
  } as any;
1490
1449
 
1491
1450
  // When prop is 'true', should render
1492
1451
  const resultVisible = builder.buildComponent({
1493
1452
  node,
1494
- componentResolvedProps: { showBanner: "true" },
1453
+ componentResolvedProps: { showBanner: 'true' },
1495
1454
  });
1496
1455
  expect(resultVisible).not.toBeNull();
1497
1456
 
1498
1457
  // When prop is 'false', should skip
1499
1458
  const resultHidden = builder.buildComponent({
1500
1459
  node,
1501
- componentResolvedProps: { showBanner: "false" },
1460
+ componentResolvedProps: { showBanner: 'false' },
1502
1461
  });
1503
1462
  expect(resultHidden).toBeNull();
1504
1463
  });
1505
1464
 
1506
- test("should skip children when parent if is false", () => {
1465
+ test('should skip children when parent if is false', () => {
1507
1466
  const node: ComponentNode = {
1508
- type: "node",
1509
- tag: "div",
1467
+ type: 'node',
1468
+ tag: 'div',
1510
1469
  if: false,
1511
1470
  children: [
1512
- { type: "node", tag: "span", children: ["Child 1"] },
1513
- { type: "node", tag: "span", children: ["Child 2"] },
1471
+ { type: 'node', tag: 'span', children: ['Child 1'] },
1472
+ { type: 'node', tag: 'span', children: ['Child 2'] },
1514
1473
  ],
1515
1474
  } as any;
1516
1475
 
@@ -1519,16 +1478,16 @@ describe("ComponentBuilder", () => {
1519
1478
  expect(result).toBeNull();
1520
1479
  });
1521
1480
 
1522
- test("should handle if with component nodes", () => {
1523
- componentRegistry.register("TestComponent", {
1481
+ test('should handle if with component nodes', () => {
1482
+ componentRegistry.register('TestComponent', {
1524
1483
  component: {
1525
- structure: { type: "node", tag: "div", children: ["Component content"] },
1484
+ structure: { type: 'node', tag: 'div', children: ['Component content'] },
1526
1485
  },
1527
1486
  } as any);
1528
1487
 
1529
1488
  const node: ComponentNode = {
1530
- type: "component",
1531
- component: "TestComponent",
1489
+ type: 'component',
1490
+ component: 'TestComponent',
1532
1491
  if: false,
1533
1492
  } as any;
1534
1493
 
@@ -1537,10 +1496,10 @@ describe("ComponentBuilder", () => {
1537
1496
  expect(result).toBeNull();
1538
1497
  });
1539
1498
 
1540
- test("should handle if with embed nodes", () => {
1499
+ test('should handle if with embed nodes', () => {
1541
1500
  const node: ComponentNode = {
1542
- type: "embed",
1543
- html: "<svg></svg>",
1501
+ type: 'embed',
1502
+ html: '<svg></svg>',
1544
1503
  if: false,
1545
1504
  } as any;
1546
1505
 
@@ -1549,12 +1508,12 @@ describe("ComponentBuilder", () => {
1549
1508
  expect(result).toBeNull();
1550
1509
  });
1551
1510
 
1552
- test("should handle if with link nodes", () => {
1511
+ test('should handle if with link nodes', () => {
1553
1512
  const node: ComponentNode = {
1554
- type: "link",
1555
- href: "/page",
1513
+ type: 'link',
1514
+ href: '/page',
1556
1515
  if: false,
1557
- children: ["Hidden link"],
1516
+ children: ['Hidden link'],
1558
1517
  } as any;
1559
1518
 
1560
1519
  const result = builder.buildComponent({ node });
@@ -1562,16 +1521,16 @@ describe("ComponentBuilder", () => {
1562
1521
  expect(result).toBeNull();
1563
1522
  });
1564
1523
 
1565
- test("treats unresolved string-template if as falsy", () => {
1524
+ test('treats unresolved string-template if as falsy', () => {
1566
1525
  // When `if: "{{buttonText}}"` references an undefined prop,
1567
1526
  // evaluateTemplate preserves the original template string (backward-compat).
1568
1527
  // Without component/CMS/item context to resolve it, treat the node as hidden
1569
1528
  // — matches user expectation when binding visibility to an unset prop.
1570
1529
  const node: ComponentNode = {
1571
- type: "link",
1572
- href: "/page",
1573
- if: "{{buttonText}}",
1574
- children: ["Hidden link"],
1530
+ type: 'link',
1531
+ href: '/page',
1532
+ if: '{{buttonText}}',
1533
+ children: ['Hidden link'],
1575
1534
  } as any;
1576
1535
 
1577
1536
  const result = builder.buildComponent({ node });
@@ -1579,9 +1538,9 @@ describe("ComponentBuilder", () => {
1579
1538
  expect(result).toBeNull();
1580
1539
  });
1581
1540
 
1582
- test("should handle if with locale-list nodes", () => {
1541
+ test('should handle if with locale-list nodes', () => {
1583
1542
  const node: ComponentNode = {
1584
- type: "locale-list",
1543
+ type: 'locale-list',
1585
1544
  if: false,
1586
1545
  } as any;
1587
1546
 
@@ -1590,29 +1549,29 @@ describe("ComponentBuilder", () => {
1590
1549
  expect(result).toBeNull();
1591
1550
  });
1592
1551
 
1593
- test("should render when mapping prop value not found (default true)", () => {
1552
+ test('should render when mapping prop value not found (default true)', () => {
1594
1553
  const node: ComponentNode = {
1595
- type: "node",
1596
- tag: "div",
1554
+ type: 'node',
1555
+ tag: 'div',
1597
1556
  if: {
1598
1557
  _mapping: true,
1599
- prop: "status",
1600
- values: { "active": true, "inactive": false },
1558
+ prop: 'status',
1559
+ values: { active: true, inactive: false },
1601
1560
  },
1602
- children: ["Content"],
1561
+ children: ['Content'],
1603
1562
  } as any;
1604
1563
 
1605
1564
  // 'unknown' is not in the values map, should default to true
1606
1565
  const result = builder.buildComponent({
1607
1566
  node,
1608
- componentResolvedProps: { status: "unknown" },
1567
+ componentResolvedProps: { status: 'unknown' },
1609
1568
  });
1610
1569
 
1611
1570
  expect(result).not.toBeNull();
1612
1571
  });
1613
1572
  });
1614
1573
 
1615
- describe("Edge Case 7: List Paths Tracking (Collection Mode)", () => {
1574
+ describe('Edge Case 7: List Paths Tracking (Collection Mode)', () => {
1616
1575
  const createMockCMSItems = (count: number): CMSItem[] => {
1617
1576
  return Array.from({ length: count }, (_, i) => ({
1618
1577
  _id: `item-${i}`,
@@ -1621,13 +1580,13 @@ describe("ComponentBuilder", () => {
1621
1580
  }));
1622
1581
  };
1623
1582
 
1624
- test("should pass listPaths through buildChildren", () => {
1583
+ test('should pass listPaths through buildChildren', () => {
1625
1584
  // Create a simple list with items
1626
1585
  const node: ListNode = {
1627
1586
  type: NODE_TYPE.LIST,
1628
1587
  sourceType: 'collection',
1629
- source: "posts",
1630
- children: [{ type: "node", tag: "div", children: ["Item"] }],
1588
+ source: 'posts',
1589
+ children: [{ type: 'node', tag: 'div', children: ['Item'] }],
1631
1590
  };
1632
1591
 
1633
1592
  const items = createMockCMSItems(2);
@@ -1643,18 +1602,18 @@ describe("ComponentBuilder", () => {
1643
1602
  expect(element?.props?.children?.length).toBe(2);
1644
1603
  });
1645
1604
 
1646
- test("should track listPaths in nested lists", () => {
1605
+ test('should track listPaths in nested lists', () => {
1647
1606
  // Outer list with inner list
1648
1607
  const outerNode: ListNode = {
1649
1608
  type: NODE_TYPE.LIST,
1650
1609
  sourceType: 'collection',
1651
- source: "categories",
1610
+ source: 'categories',
1652
1611
  children: [
1653
1612
  {
1654
1613
  type: NODE_TYPE.LIST,
1655
1614
  sourceType: 'collection',
1656
- source: "posts",
1657
- children: [{ type: "node", tag: "span", children: ["Post"] }],
1615
+ source: 'posts',
1616
+ children: [{ type: 'node', tag: 'span', children: ['Post'] }],
1658
1617
  } as any,
1659
1618
  ],
1660
1619
  };
@@ -1673,28 +1632,26 @@ describe("ComponentBuilder", () => {
1673
1632
  });
1674
1633
  });
1675
1634
 
1676
- describe("Edge Case 5: Slot Content Context Handling", () => {
1677
- test("should not pass componentContext to slot content elements", () => {
1635
+ describe('Edge Case 5: Slot Content Context Handling', () => {
1636
+ test('should not pass componentContext to slot content elements', () => {
1678
1637
  // Register a component with a slot
1679
- componentRegistry.register("Container", {
1680
- type: "Container",
1638
+ componentRegistry.register('Container', {
1639
+ type: 'Container',
1681
1640
  component: {
1682
1641
  interface: {},
1683
1642
  structure: {
1684
- type: "node",
1685
- tag: "div",
1686
- children: [{ type: "slot" }],
1643
+ type: 'node',
1644
+ tag: 'div',
1645
+ children: [{ type: 'slot' }],
1687
1646
  },
1688
1647
  },
1689
1648
  });
1690
1649
 
1691
1650
  // Component instance with children (slot content)
1692
1651
  const node: ComponentNode = {
1693
- type: "component",
1694
- component: "Container",
1695
- children: [
1696
- { type: "node", tag: "span", children: ["Slot content"] },
1697
- ],
1652
+ type: 'component',
1653
+ component: 'Container',
1654
+ children: [{ type: 'node', tag: 'span', children: ['Slot content'] }],
1698
1655
  };
1699
1656
 
1700
1657
  const result = builder.buildComponent({
@@ -1704,29 +1661,29 @@ describe("ComponentBuilder", () => {
1704
1661
 
1705
1662
  // Should render successfully
1706
1663
  expect(result).not.toBeNull();
1707
- expect(typeof result).toBe("object");
1664
+ expect(typeof result).toBe('object');
1708
1665
  });
1709
1666
 
1710
- test("should mark slot content with _isSlotContent flag", () => {
1667
+ test('should mark slot content with _isSlotContent flag', () => {
1711
1668
  // This tests that slot content is marked, which affects context handling
1712
- componentRegistry.register("Wrapper", {
1713
- type: "Wrapper",
1669
+ componentRegistry.register('Wrapper', {
1670
+ type: 'Wrapper',
1714
1671
  component: {
1715
1672
  interface: {},
1716
1673
  structure: {
1717
- type: "node",
1718
- tag: "section",
1719
- children: [{ type: "slot" }],
1674
+ type: 'node',
1675
+ tag: 'section',
1676
+ children: [{ type: 'slot' }],
1720
1677
  },
1721
1678
  },
1722
1679
  });
1723
1680
 
1724
1681
  const node: ComponentNode = {
1725
- type: "component",
1726
- component: "Wrapper",
1682
+ type: 'component',
1683
+ component: 'Wrapper',
1727
1684
  children: [
1728
- { type: "node", tag: "div", children: ["Child 1"] },
1729
- { type: "node", tag: "div", children: ["Child 2"] },
1685
+ { type: 'node', tag: 'div', children: ['Child 1'] },
1686
+ { type: 'node', tag: 'div', children: ['Child 2'] },
1730
1687
  ],
1731
1688
  };
1732
1689
 
@@ -1738,27 +1695,24 @@ describe("ComponentBuilder", () => {
1738
1695
  expect(element?.props?.children).toBeDefined();
1739
1696
  });
1740
1697
 
1741
- test("should preserve componentContext for component structure elements", () => {
1698
+ test('should preserve componentContext for component structure elements', () => {
1742
1699
  // Register a component
1743
- componentRegistry.register("Card", {
1744
- type: "Card",
1700
+ componentRegistry.register('Card', {
1701
+ type: 'Card',
1745
1702
  component: {
1746
1703
  interface: {},
1747
1704
  structure: {
1748
- type: "node",
1749
- tag: "article",
1750
- children: [
1751
- { type: "node", tag: "h2", children: ["Header"] },
1752
- { type: "slot" },
1753
- ],
1705
+ type: 'node',
1706
+ tag: 'article',
1707
+ children: [{ type: 'node', tag: 'h2', children: ['Header'] }, { type: 'slot' }],
1754
1708
  },
1755
1709
  },
1756
1710
  });
1757
1711
 
1758
1712
  const node: ComponentNode = {
1759
- type: "component",
1760
- component: "Card",
1761
- children: [{ type: "node", tag: "p", children: ["Content"] }],
1713
+ type: 'component',
1714
+ component: 'Card',
1715
+ children: [{ type: 'node', tag: 'p', children: ['Content'] }],
1762
1716
  };
1763
1717
 
1764
1718
  const result = builder.buildComponent({ node });
@@ -1768,4 +1722,3 @@ describe("ComponentBuilder", () => {
1768
1722
  });
1769
1723
  });
1770
1724
  });
1771
-