meno-core 1.0.53 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. package/.claude/settings.local.json +1 -3
  2. package/bin/cli.ts +66 -261
  3. package/build-astro.ts +150 -128
  4. package/dist/bin/cli.js +42 -185
  5. package/dist/bin/cli.js.map +2 -2
  6. package/dist/chunks/chunk-2AR55GYH.js +42 -0
  7. package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
  8. package/dist/chunks/{chunk-JGWFTO6P.js → chunk-2IIQK7T3.js} +2624 -1389
  9. package/dist/chunks/chunk-2IIQK7T3.js.map +7 -0
  10. package/dist/chunks/chunk-4ZRU52J2.js +169 -0
  11. package/dist/chunks/chunk-4ZRU52J2.js.map +7 -0
  12. package/dist/chunks/{chunk-UB44F4Z2.js → chunk-5X4HCN7J.js} +5 -7
  13. package/dist/chunks/chunk-5X4HCN7J.js.map +7 -0
  14. package/dist/chunks/{chunk-WQFG7PAH.js → chunk-7CCVOL5M.js} +15 -19
  15. package/dist/chunks/chunk-7CCVOL5M.js.map +7 -0
  16. package/dist/chunks/{chunk-YBLHKYFF.js → chunk-AR7LHIFY.js} +8 -2
  17. package/dist/chunks/chunk-AR7LHIFY.js.map +7 -0
  18. package/dist/chunks/{chunk-O3NAGJP4.js → chunk-NUP7H7D3.js} +135 -41
  19. package/dist/chunks/chunk-NUP7H7D3.js.map +7 -0
  20. package/dist/chunks/{chunk-X754AHS5.js → chunk-QWTQZHG3.js} +194 -90
  21. package/dist/chunks/chunk-QWTQZHG3.js.map +7 -0
  22. package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-XTKNX4FW.js} +216 -87
  23. package/dist/chunks/chunk-XTKNX4FW.js.map +7 -0
  24. package/dist/chunks/{fs-JGINUXGL.js → fs-G3ANWEFP.js} +2 -2
  25. package/dist/lib/client/index.js +1003 -475
  26. package/dist/lib/client/index.js.map +3 -3
  27. package/dist/lib/server/index.js +14393 -6371
  28. package/dist/lib/server/index.js.map +4 -4
  29. package/dist/lib/shared/index.js +342 -101
  30. package/dist/lib/shared/index.js.map +4 -4
  31. package/dist/lib/shared/richtext/index.js +1 -1
  32. package/dist/lib/test-utils/index.js +64 -80
  33. package/dist/lib/test-utils/index.js.map +2 -2
  34. package/entries/client-router.tsx +10 -8
  35. package/lib/client/ClientInitializer.ts +8 -8
  36. package/lib/client/ErrorBoundary.test.tsx +246 -186
  37. package/lib/client/ErrorBoundary.tsx +183 -132
  38. package/lib/client/componentRegistry.test.ts +96 -108
  39. package/lib/client/componentRegistry.ts +1 -3
  40. package/lib/client/contexts/ThemeContext.tsx +3 -2
  41. package/lib/client/core/ComponentBuilder.test.ts +663 -560
  42. package/lib/client/core/ComponentBuilder.ts +427 -155
  43. package/lib/client/core/ComponentRenderer.test.tsx +2 -4
  44. package/lib/client/core/ComponentRenderer.tsx +46 -33
  45. package/lib/client/core/builders/embedBuilder.ts +242 -54
  46. package/lib/client/core/builders/linkBuilder.ts +72 -45
  47. package/lib/client/core/builders/linkNodeBuilder.ts +79 -54
  48. package/lib/client/core/builders/listBuilder.ts +155 -89
  49. package/lib/client/core/builders/localeListBuilder.ts +97 -62
  50. package/lib/client/core/builders/types.ts +5 -5
  51. package/lib/client/core/cmsTemplateProcessor.ts +8 -9
  52. package/lib/client/elementRegistry.ts +3 -3
  53. package/lib/client/fontFamiliesService.test.ts +2 -10
  54. package/lib/client/fontFamiliesService.ts +3 -3
  55. package/lib/client/hmr/HMRManager.tsx +47 -30
  56. package/lib/client/hmrCssReload.ts +49 -14
  57. package/lib/client/hmrWebSocket.ts +14 -19
  58. package/lib/client/hooks/useColorVariables.test.ts +21 -21
  59. package/lib/client/hooks/useColorVariables.ts +23 -18
  60. package/lib/client/hooks/usePropertyAutocomplete.ts +4 -6
  61. package/lib/client/hooks/useVariables.ts +6 -4
  62. package/lib/client/hydration/HydrationUtils.test.ts +24 -25
  63. package/lib/client/hydration/HydrationUtils.ts +3 -4
  64. package/lib/client/i18nConfigService.test.ts +2 -7
  65. package/lib/client/i18nConfigService.ts +2 -2
  66. package/lib/client/meno-filter/MenoFilter.test.ts +47 -49
  67. package/lib/client/meno-filter/MenoFilter.ts +14 -15
  68. package/lib/client/meno-filter/bindings.ts +18 -43
  69. package/lib/client/meno-filter/init.ts +4 -4
  70. package/lib/client/meno-filter/renderer.ts +30 -32
  71. package/lib/client/meno-filter/script.generated.ts +1 -3
  72. package/lib/client/meno-filter/ui.ts +4 -6
  73. package/lib/client/meno-filter/updates.ts +18 -23
  74. package/lib/client/meno-filter/utils.ts +1 -1
  75. package/lib/client/navigation.test.ts +174 -178
  76. package/lib/client/navigation.ts +0 -1
  77. package/lib/client/responsiveStyleResolver.knownTokens.test.ts +71 -0
  78. package/lib/client/responsiveStyleResolver.test.ts +234 -232
  79. package/lib/client/responsiveStyleResolver.ts +74 -20
  80. package/lib/client/routing/RouteLoader.test.ts +24 -25
  81. package/lib/client/routing/RouteLoader.ts +27 -35
  82. package/lib/client/routing/Router.tsx +210 -153
  83. package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
  84. package/lib/client/scripts/ScriptExecutor.ts +18 -36
  85. package/lib/client/services/PrefetchService.test.ts +3 -3
  86. package/lib/client/services/PrefetchService.ts +23 -29
  87. package/lib/client/styleProcessor.test.ts +9 -9
  88. package/lib/client/styleProcessor.ts +24 -15
  89. package/lib/client/styles/StyleInjector.test.ts +122 -115
  90. package/lib/client/styles/StyleInjector.ts +10 -7
  91. package/lib/client/styles/UtilityClassCollector.ts +62 -28
  92. package/lib/client/styles/cspNonce.test.ts +2 -5
  93. package/lib/client/templateEngine.test.ts +554 -488
  94. package/lib/client/templateEngine.ts +203 -73
  95. package/lib/client/theme.test.ts +1 -1
  96. package/lib/client/theme.ts +0 -1
  97. package/lib/client/utils/toast.ts +0 -1
  98. package/lib/server/__integration__/api-routes.test.ts +8 -4
  99. package/lib/server/__integration__/cms-integration.test.ts +1 -4
  100. package/lib/server/__integration__/server-lifecycle.test.ts +4 -5
  101. package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
  102. package/lib/server/__integration__/static-assets.test.ts +3 -3
  103. package/lib/server/__integration__/test-helpers.ts +87 -74
  104. package/lib/server/ab/generateFunctions.ts +12 -10
  105. package/lib/server/astro/cmsPageEmitter.ts +26 -42
  106. package/lib/server/astro/componentEmitter.ts +35 -43
  107. package/lib/server/astro/cssCollector.ts +10 -26
  108. package/lib/server/astro/nodeToAstro.test.ts +1754 -34
  109. package/lib/server/astro/nodeToAstro.ts +232 -216
  110. package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +115 -119
  111. package/lib/server/astro/normalizeOrphanTemplateProps.ts +11 -18
  112. package/lib/server/astro/pageEmitter.ts +9 -13
  113. package/lib/server/astro/tailwindMapper.test.ts +10 -37
  114. package/lib/server/astro/tailwindMapper.ts +33 -40
  115. package/lib/server/astro/templateTransformer.ts +17 -20
  116. package/lib/server/createServer.ts +7 -19
  117. package/lib/server/cssGenerator.test.ts +35 -44
  118. package/lib/server/cssGenerator.ts +8 -19
  119. package/lib/server/fileWatcher.test.ts +164 -13
  120. package/lib/server/fileWatcher.ts +209 -124
  121. package/lib/server/index.ts +14 -17
  122. package/lib/server/jsonLoader.test.ts +39 -2
  123. package/lib/server/jsonLoader.ts +39 -34
  124. package/lib/server/middleware/cors.test.ts +21 -21
  125. package/lib/server/middleware/cors.ts +28 -4
  126. package/lib/server/middleware/errorHandler.test.ts +7 -5
  127. package/lib/server/middleware/errorHandler.ts +3 -8
  128. package/lib/server/middleware/index.ts +0 -1
  129. package/lib/server/middleware/logger.test.ts +10 -8
  130. package/lib/server/middleware/logger.ts +17 -29
  131. package/lib/server/migrateTemplates.ts +3 -3
  132. package/lib/server/pageCache.test.ts +76 -77
  133. package/lib/server/pageCache.ts +0 -1
  134. package/lib/server/projectContext.ts +5 -4
  135. package/lib/server/providers/fileSystemCMSProvider.test.ts +142 -99
  136. package/lib/server/providers/fileSystemCMSProvider.ts +62 -38
  137. package/lib/server/routes/api/cms.test.ts +26 -14
  138. package/lib/server/routes/api/cms.ts +9 -14
  139. package/lib/server/routes/api/colors.test.ts +2 -2
  140. package/lib/server/routes/api/components.ts +31 -35
  141. package/lib/server/routes/api/config.ts +3 -4
  142. package/lib/server/routes/api/core-routes.ts +49 -76
  143. package/lib/server/routes/api/enums.test.ts +2 -2
  144. package/lib/server/routes/api/functions.ts +9 -17
  145. package/lib/server/routes/api/index.ts +3 -6
  146. package/lib/server/routes/api/pages.ts +8 -11
  147. package/lib/server/routes/api/shared.test.ts +1 -1
  148. package/lib/server/routes/api/shared.ts +12 -9
  149. package/lib/server/routes/api/variables.test.ts +3 -5
  150. package/lib/server/routes/api/variables.ts +1 -1
  151. package/lib/server/routes/index.ts +25 -28
  152. package/lib/server/routes/pages.ts +35 -31
  153. package/lib/server/routes/static.ts +17 -6
  154. package/lib/server/runtime/bundler.ts +98 -61
  155. package/lib/server/runtime/fs.ts +16 -26
  156. package/lib/server/runtime/httpServer.ts +8 -12
  157. package/lib/server/services/ColorService.test.ts +3 -3
  158. package/lib/server/services/ColorService.ts +37 -29
  159. package/lib/server/services/EnumService.test.ts +4 -8
  160. package/lib/server/services/EnumService.ts +11 -3
  161. package/lib/server/services/VariableService.test.ts +8 -12
  162. package/lib/server/services/VariableService.ts +11 -3
  163. package/lib/server/services/cmsService.test.ts +158 -120
  164. package/lib/server/services/cmsService.ts +36 -146
  165. package/lib/server/services/componentService.test.ts +125 -45
  166. package/lib/server/services/componentService.ts +70 -46
  167. package/lib/server/services/configService.test.ts +22 -45
  168. package/lib/server/services/configService.ts +50 -27
  169. package/lib/server/services/fileWatcherService.ts +42 -8
  170. package/lib/server/services/index.ts +0 -1
  171. package/lib/server/services/pageService.test.ts +8 -10
  172. package/lib/server/services/pageService.ts +32 -18
  173. package/lib/server/ssr/attributeBuilder.ts +18 -8
  174. package/lib/server/ssr/buildErrorOverlay.ts +11 -12
  175. package/lib/server/ssr/clientDataInjector.ts +7 -21
  176. package/lib/server/ssr/cmsSSRProcessor.ts +4 -8
  177. package/lib/server/ssr/cssCollector.ts +1 -1
  178. package/lib/server/ssr/errorOverlay.test.ts +1 -1
  179. package/lib/server/ssr/errorOverlay.ts +3 -9
  180. package/lib/server/ssr/htmlGenerator.nonce.test.ts +9 -14
  181. package/lib/server/ssr/htmlGenerator.test.ts +120 -44
  182. package/lib/server/ssr/htmlGenerator.ts +126 -91
  183. package/lib/server/ssr/imageMetadata.test.ts +3 -1
  184. package/lib/server/ssr/imageMetadata.ts +27 -21
  185. package/lib/server/ssr/jsCollector.test.ts +8 -18
  186. package/lib/server/ssr/jsCollector.ts +3 -8
  187. package/lib/server/ssr/liveReloadIntegration.test.ts +76 -36
  188. package/lib/server/ssr/metaTagGenerator.ts +2 -2
  189. package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
  190. package/lib/server/ssr/ssrRenderer.test.ts +316 -256
  191. package/lib/server/ssr/ssrRenderer.ts +795 -243
  192. package/lib/server/ssrRenderer.test.ts +1048 -954
  193. package/lib/server/utils/jsonLineMapper.test.ts +28 -28
  194. package/lib/server/utils/jsonLineMapper.ts +7 -7
  195. package/lib/server/validateStyleCoverage.ts +18 -21
  196. package/lib/server/websocketManager.ts +28 -19
  197. package/lib/shared/attributeNodeUtils.test.ts +15 -15
  198. package/lib/shared/attributeNodeUtils.ts +5 -12
  199. package/lib/shared/breakpoints.test.ts +5 -5
  200. package/lib/shared/breakpoints.ts +4 -11
  201. package/lib/shared/cmsQuery.test.ts +81 -0
  202. package/lib/shared/cmsQuery.ts +92 -0
  203. package/lib/shared/cmsQueryParser.test.ts +50 -42
  204. package/lib/shared/cmsQueryParser.ts +60 -38
  205. package/lib/shared/colorConversions.ts +9 -9
  206. package/lib/shared/colorVariableUtils.test.ts +27 -5
  207. package/lib/shared/colorVariableUtils.ts +21 -12
  208. package/lib/shared/componentRefs.ts +1 -5
  209. package/lib/shared/constants.test.ts +3 -3
  210. package/lib/shared/constants.ts +8 -8
  211. package/lib/shared/cssGeneration.test.ts +285 -144
  212. package/lib/shared/cssGeneration.ts +217 -531
  213. package/lib/shared/cssNamedColors.ts +152 -30
  214. package/lib/shared/cssProperties.test.ts +69 -70
  215. package/lib/shared/cssProperties.ts +298 -114
  216. package/lib/shared/elementClassName.test.ts +109 -109
  217. package/lib/shared/elementClassName.ts +3 -3
  218. package/lib/shared/elementUtils.ts +12 -16
  219. package/lib/shared/errorLogger.test.ts +12 -12
  220. package/lib/shared/errorLogger.ts +2 -10
  221. package/lib/shared/errors.test.ts +2 -13
  222. package/lib/shared/errors.ts +2 -8
  223. package/lib/shared/expressionEvaluator.test.ts +145 -0
  224. package/lib/shared/expressionEvaluator.ts +52 -24
  225. package/lib/shared/fontLoader.test.ts +32 -18
  226. package/lib/shared/fontLoader.ts +2 -2
  227. package/lib/shared/friendlyError.ts +2 -3
  228. package/lib/shared/gradientUtils.test.ts +3 -7
  229. package/lib/shared/gradientUtils.ts +17 -14
  230. package/lib/shared/hrefRefs.test.ts +2 -2
  231. package/lib/shared/hrefRefs.ts +2 -10
  232. package/lib/shared/i18n.test.ts +4 -4
  233. package/lib/shared/i18n.ts +18 -38
  234. package/lib/shared/index.ts +27 -0
  235. package/lib/shared/inlineSvgStyleRules.ts +14 -13
  236. package/lib/shared/interactiveStyleMappings.test.ts +32 -54
  237. package/lib/shared/interactiveStyleMappings.ts +11 -24
  238. package/lib/shared/interactiveStyles.test.ts +165 -188
  239. package/lib/shared/interfaces/contentProvider.ts +1 -1
  240. package/lib/shared/itemTemplateUtils.test.ts +11 -17
  241. package/lib/shared/itemTemplateUtils.ts +27 -39
  242. package/lib/shared/jsonRepair.ts +16 -10
  243. package/lib/shared/libraryLoader.test.ts +21 -55
  244. package/lib/shared/libraryLoader.ts +7 -22
  245. package/lib/shared/linkUtils.ts +1 -1
  246. package/lib/shared/logger.test.ts +66 -0
  247. package/lib/shared/logger.ts +94 -0
  248. package/lib/shared/markdown.ts +25 -0
  249. package/lib/shared/netlifyLocale404.test.ts +179 -0
  250. package/lib/shared/netlifyLocale404.ts +110 -0
  251. package/lib/shared/nodeUtils.test.ts +28 -16
  252. package/lib/shared/nodeUtils.ts +76 -23
  253. package/lib/shared/pathArrayUtils.test.ts +1 -2
  254. package/lib/shared/pathArrayUtils.ts +1 -1
  255. package/lib/shared/pathSecurity.ts +2 -2
  256. package/lib/shared/pathUtils.test.ts +4 -6
  257. package/lib/shared/pathUtils.ts +42 -49
  258. package/lib/shared/paths/Path.test.ts +2 -2
  259. package/lib/shared/paths/Path.ts +0 -1
  260. package/lib/shared/paths/PathConverter.test.ts +1 -1
  261. package/lib/shared/paths/PathConverter.ts +17 -20
  262. package/lib/shared/paths/PathUtils.ts +13 -13
  263. package/lib/shared/paths/PathValidator.test.ts +2 -15
  264. package/lib/shared/paths/PathValidator.ts +12 -10
  265. package/lib/shared/paths/index.ts +1 -2
  266. package/lib/shared/permissions.test.ts +168 -0
  267. package/lib/shared/permissions.ts +162 -0
  268. package/lib/shared/propResolver.test.ts +240 -244
  269. package/lib/shared/propResolver.ts +15 -26
  270. package/lib/shared/pxToRem.test.ts +7 -6
  271. package/lib/shared/pxToRem.ts +2 -5
  272. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +12 -6
  273. package/lib/shared/registry/ClientRegistry.ts +0 -2
  274. package/lib/shared/registry/ComponentRegistry.test.ts +46 -32
  275. package/lib/shared/registry/ComponentRegistry.ts +12 -9
  276. package/lib/shared/registry/NodeTypeDefinition.ts +16 -9
  277. package/lib/shared/registry/RegistryManager.ts +1 -2
  278. package/lib/shared/registry/SSRRegistry.ts +0 -2
  279. package/lib/shared/registry/createNodeType.ts +7 -9
  280. package/lib/shared/registry/defineNodeType.ts +2 -6
  281. package/lib/shared/registry/fieldPresets.ts +3 -1
  282. package/lib/shared/registry/index.ts +0 -1
  283. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
  284. package/lib/shared/registry/nodeTypes/CustomNodeType.ts +103 -0
  285. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
  286. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +48 -19
  287. package/lib/shared/registry/nodeTypes/IslandNodeType.ts +129 -0
  288. package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
  289. package/lib/shared/registry/nodeTypes/ListNodeType.ts +117 -77
  290. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
  291. package/lib/shared/registry/nodeTypes/MarkdownNodeType.ts +78 -0
  292. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +12 -8
  293. package/lib/shared/registry/nodeTypes/index.ts +26 -3
  294. package/lib/shared/responsiveScaling.test.ts +16 -34
  295. package/lib/shared/responsiveScaling.ts +61 -42
  296. package/lib/shared/responsiveStyleUtils.test.ts +0 -1
  297. package/lib/shared/responsiveStyleUtils.ts +11 -13
  298. package/lib/shared/richtext/htmlToTiptap.test.ts +139 -130
  299. package/lib/shared/richtext/htmlToTiptap.ts +2 -4
  300. package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
  301. package/lib/shared/richtext/types.ts +1 -8
  302. package/lib/shared/slugTranslator.test.ts +13 -13
  303. package/lib/shared/slugTranslator.ts +12 -16
  304. package/lib/shared/slugify.ts +9 -15
  305. package/lib/shared/styleNodeUtils.test.ts +8 -8
  306. package/lib/shared/styleNodeUtils.ts +18 -20
  307. package/lib/shared/styleUtils.test.ts +87 -61
  308. package/lib/shared/styleUtils.ts +5 -6
  309. package/lib/shared/themeDefaults.test.ts +11 -14
  310. package/lib/shared/themeDefaults.ts +3 -4
  311. package/lib/shared/tree/PathBuilder.test.ts +63 -145
  312. package/lib/shared/tree/PathBuilder.ts +59 -47
  313. package/lib/shared/treePathUtils.test.ts +51 -12
  314. package/lib/shared/treePathUtils.ts +91 -60
  315. package/lib/shared/types/api.ts +105 -8
  316. package/lib/shared/types/cms.test.ts +34 -0
  317. package/lib/shared/types/cms.ts +90 -34
  318. package/lib/shared/types/comment.ts +49 -16
  319. package/lib/shared/types/components.ts +51 -25
  320. package/lib/shared/types/errors.test.ts +1 -6
  321. package/lib/shared/types/errors.ts +3 -7
  322. package/lib/shared/types/experiments.ts +28 -28
  323. package/lib/shared/types/index.ts +25 -2
  324. package/lib/shared/types/permissions.ts +139 -0
  325. package/lib/shared/types/styles.ts +0 -1
  326. package/lib/shared/types/variables.test.ts +4 -13
  327. package/lib/shared/types/variables.ts +51 -30
  328. package/lib/shared/types.ts +1 -2
  329. package/lib/shared/utilityClassConfig.ts +751 -321
  330. package/lib/shared/utilityClassMapper.durability.test.ts +117 -0
  331. package/lib/shared/utilityClassMapper.test.ts +433 -95
  332. package/lib/shared/utilityClassMapper.ts +322 -249
  333. package/lib/shared/utilityClassNames.test.ts +38 -0
  334. package/lib/shared/utilityClassNames.ts +458 -0
  335. package/lib/shared/utils.test.ts +2 -10
  336. package/lib/shared/utils.ts +19 -10
  337. package/lib/shared/validation/cmsValidators.ts +2 -1
  338. package/lib/shared/validation/commentValidators.test.ts +53 -0
  339. package/lib/shared/validation/commentValidators.ts +12 -1
  340. package/lib/shared/validation/index.ts +1 -0
  341. package/lib/shared/validation/permissionsValidators.test.ts +49 -0
  342. package/lib/shared/validation/permissionsValidators.ts +49 -0
  343. package/lib/shared/validation/propValidator.test.ts +19 -21
  344. package/lib/shared/validation/propValidator.ts +13 -18
  345. package/lib/shared/validation/schemas.test.ts +64 -32
  346. package/lib/shared/validation/schemas.ts +616 -345
  347. package/lib/shared/validation/validators.test.ts +3 -8
  348. package/lib/shared/validation/validators.ts +89 -68
  349. package/lib/shared/viewportUnits.integration.test.ts +14 -10
  350. package/lib/shared/viewportUnits.test.ts +24 -23
  351. package/lib/shared/viewportUnits.ts +12 -5
  352. package/lib/test-utils/dom-setup.ts +1 -1
  353. package/lib/test-utils/factories/ConsoleMockFactory.ts +10 -14
  354. package/lib/test-utils/factories/DomMockFactory.ts +17 -23
  355. package/lib/test-utils/factories/EventMockFactory.ts +7 -13
  356. package/lib/test-utils/factories/FetchMockFactory.ts +39 -57
  357. package/lib/test-utils/factories/ServerMockFactory.ts +6 -10
  358. package/lib/test-utils/factories/StoreMockFactory.ts +18 -29
  359. package/lib/test-utils/fixtures.ts +45 -45
  360. package/lib/test-utils/helpers/asyncHelpers.test.ts +15 -18
  361. package/lib/test-utils/helpers/asyncHelpers.ts +11 -20
  362. package/lib/test-utils/helpers.ts +1 -5
  363. package/lib/test-utils/index.ts +0 -4
  364. package/lib/test-utils/mockFactories.ts +13 -19
  365. package/lib/test-utils/mocks.ts +6 -4
  366. package/package.json +3 -5
  367. package/scripts/build-for-publish.mjs +2 -4
  368. package/scripts/build-meno-filter.ts +4 -7
  369. package/tsconfig.json +1 -0
  370. package/vite.config.ts +4 -4
  371. package/build-next.ts +0 -1361
  372. package/build-static.test.ts +0 -424
  373. package/build-static.ts +0 -1074
  374. package/dist/build-static.js +0 -36
  375. package/dist/chunks/chunk-2MHDV5BF.js +0 -251
  376. package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
  377. package/dist/chunks/chunk-AZQYF6KE.js +0 -559
  378. package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
  379. package/dist/chunks/chunk-GZHGVVW3.js +0 -322
  380. package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
  381. package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
  382. package/dist/chunks/chunk-I7YIGZXT.js +0 -467
  383. package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
  384. package/dist/chunks/chunk-IGYR22T6.js +0 -6192
  385. package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
  386. package/dist/chunks/chunk-JGP5A3Y5.js +0 -1136
  387. package/dist/chunks/chunk-JGP5A3Y5.js.map +0 -7
  388. package/dist/chunks/chunk-JGWFTO6P.js.map +0 -7
  389. package/dist/chunks/chunk-O3NAGJP4.js.map +0 -7
  390. package/dist/chunks/chunk-QB2LNO4W.js +0 -77
  391. package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
  392. package/dist/chunks/chunk-R6XHAFBF.js +0 -3821
  393. package/dist/chunks/chunk-R6XHAFBF.js.map +0 -7
  394. package/dist/chunks/chunk-UB44F4Z2.js.map +0 -7
  395. package/dist/chunks/chunk-WQFG7PAH.js.map +0 -7
  396. package/dist/chunks/chunk-X754AHS5.js.map +0 -7
  397. package/dist/chunks/chunk-YBLHKYFF.js.map +0 -7
  398. package/dist/chunks/configService-R3OGU2UD.js +0 -13
  399. package/dist/chunks/configService-R3OGU2UD.js.map +0 -7
  400. package/dist/chunks/constants-STK2YBIW.js +0 -46
  401. package/dist/chunks/constants-STK2YBIW.js.map +0 -7
  402. package/dist/chunks/fs-JGINUXGL.js.map +0 -7
  403. package/dist/entries/server-router.js +0 -67
  404. package/dist/entries/server-router.js.map +0 -7
  405. package/entries/server-router.tsx +0 -81
  406. package/lib/server/providers/fileSystemPageProvider.ts +0 -160
  407. package/lib/server/webflow/buildWebflow.ts +0 -635
  408. package/lib/server/webflow/index.ts +0 -23
  409. package/lib/server/webflow/nodeToWebflow.test.ts +0 -3170
  410. package/lib/server/webflow/nodeToWebflow.ts +0 -2435
  411. package/lib/server/webflow/styleMapper.test.ts +0 -389
  412. package/lib/server/webflow/styleMapper.ts +0 -695
  413. package/lib/server/webflow/templateWrapper.ts +0 -49
  414. package/lib/server/webflow/types.ts +0 -396
  415. /package/dist/{build-static.js.map → chunks/fs-G3ANWEFP.js.map} +0 -0
@@ -4,15 +4,30 @@
4
4
  * Tests the conversion of HTML strings into Tiptap document format.
5
5
  * Uses happy-dom DOMParser for browser-like HTML parsing.
6
6
  */
7
- import { describe, test, expect, beforeAll } from 'bun:test';
8
- import { Window } from 'happy-dom';
7
+ import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
8
+ import { window as domWindow } from '../../test-utils/dom-setup';
9
9
  import { htmlToTiptap } from './htmlToTiptap';
10
10
  import type { TiptapDocument, TiptapNode } from './types';
11
11
 
12
- // Register DOMParser globally so htmlToTiptap uses the DOM-based path
12
+ // Register DOMParser globally so htmlToTiptap uses the DOM-based path.
13
+ // IMPORTANT: reuse the preloaded dom-setup window — it carries the
14
+ // `window.SyntaxError` patch that happy-dom's selector parser needs (it does
15
+ // `new this.window.SyntaxError(...)` on every querySelector). A fresh
16
+ // `new Window()` lacks the patch, so every element.querySelector() in
17
+ // parseCodeBlock/parseTable throws "undefined is not a constructor".
18
+ const originalDOMParser = (globalThis as { DOMParser?: unknown }).DOMParser;
13
19
  beforeAll(() => {
14
- const win = new Window({ url: 'http://localhost:3000' });
15
- (globalThis as any).DOMParser = win.DOMParser;
20
+ (globalThis as { DOMParser?: unknown }).DOMParser = domWindow.DOMParser;
21
+ });
22
+
23
+ // Restore the global afterwards so it doesn't leak into later test files —
24
+ // htmlToTiptap (and other modules) branch on `typeof DOMParser`.
25
+ afterAll(() => {
26
+ if (originalDOMParser === undefined) {
27
+ delete (globalThis as { DOMParser?: unknown }).DOMParser;
28
+ } else {
29
+ (globalThis as { DOMParser?: unknown }).DOMParser = originalDOMParser;
30
+ }
16
31
  });
17
32
 
18
33
  // ---------------------------------------------------------------------------
@@ -35,7 +50,7 @@ function findNode(doc: TiptapDocument, type: string): TiptapNode | undefined {
35
50
  }
36
51
 
37
52
  /** Collect all nodes of a given type */
38
- function findAllNodes(doc: TiptapDocument, type: string): TiptapNode[] {
53
+ function _findAllNodes(doc: TiptapDocument, type: string): TiptapNode[] {
39
54
  const result: TiptapNode[] = [];
40
55
  function search(nodes: TiptapNode[]) {
41
56
  for (const n of nodes) {
@@ -75,10 +90,10 @@ describe('htmlToTiptap', () => {
75
90
  const result = htmlToTiptap('Hello world');
76
91
  expect(result.type).toBe('doc');
77
92
  expect(result.content.length).toBe(1);
78
- expect(result.content[0].type).toBe('paragraph');
79
- expect(result.content[0].content).toBeDefined();
80
- expect(result.content[0].content![0].type).toBe('text');
81
- expect(result.content[0].content![0].text).toBe('Hello world');
93
+ expect(result.content[0]!.type).toBe('paragraph');
94
+ expect(result.content[0]!.content).toBeDefined();
95
+ expect(result.content[0]!.content![0]!.type).toBe('text');
96
+ expect(result.content[0]!.content![0]!.text).toBe('Hello world');
82
97
  });
83
98
  });
84
99
 
@@ -89,13 +104,13 @@ describe('htmlToTiptap', () => {
89
104
  test('should convert <p> to paragraph node', () => {
90
105
  const result = htmlToTiptap('<p>Hello</p>');
91
106
  expect(result.content.length).toBe(1);
92
- expect(result.content[0].type).toBe('paragraph');
93
- expect(result.content[0].content![0].text).toBe('Hello');
107
+ expect(result.content[0]!.type).toBe('paragraph');
108
+ expect(result.content[0]!.content![0]!.text).toBe('Hello');
94
109
  });
95
110
 
96
111
  test('should convert <p> with text-align style to paragraph with textAlign attr', () => {
97
112
  const result = htmlToTiptap('<p style="text-align: center">Centered</p>');
98
- const p = result.content[0];
113
+ const p = result.content[0]!;
99
114
  expect(p.type).toBe('paragraph');
100
115
  expect(p.attrs).toBeDefined();
101
116
  expect(p.attrs!.textAlign).toBe('center');
@@ -103,12 +118,12 @@ describe('htmlToTiptap', () => {
103
118
 
104
119
  test('should convert <p> with text-align justify', () => {
105
120
  const result = htmlToTiptap('<p style="text-align: justify">Justified</p>');
106
- expect(result.content[0].attrs!.textAlign).toBe('justify');
121
+ expect(result.content[0]!.attrs!.textAlign).toBe('justify');
107
122
  });
108
123
 
109
124
  test('should not set textAlign for unsupported alignment value', () => {
110
125
  const result = htmlToTiptap('<p style="text-align: start">Text</p>');
111
- const p = result.content[0];
126
+ const p = result.content[0]!;
112
127
  // 'start' is not in the allowed list
113
128
  expect(p.attrs?.textAlign).toBeUndefined();
114
129
  });
@@ -117,16 +132,16 @@ describe('htmlToTiptap', () => {
117
132
  for (let level = 1; level <= 6; level++) {
118
133
  test(`should convert <h${level}> to heading with level ${level}`, () => {
119
134
  const result = htmlToTiptap(`<h${level}>Heading ${level}</h${level}>`);
120
- const heading = result.content[0];
135
+ const heading = result.content[0]!;
121
136
  expect(heading.type).toBe('heading');
122
137
  expect(heading.attrs!.level).toBe(level);
123
- expect(heading.content![0].text).toBe(`Heading ${level}`);
138
+ expect(heading.content![0]!.text).toBe(`Heading ${level}`);
124
139
  });
125
140
  }
126
141
 
127
142
  test('should convert <h2> with text-align to heading with textAlign', () => {
128
143
  const result = htmlToTiptap('<h2 style="text-align: right">Right Heading</h2>');
129
- const heading = result.content[0];
144
+ const heading = result.content[0]!;
130
145
  expect(heading.type).toBe('heading');
131
146
  expect(heading.attrs!.level).toBe(2);
132
147
  expect(heading.attrs!.textAlign).toBe('right');
@@ -137,97 +152,97 @@ describe('htmlToTiptap', () => {
137
152
  test('should convert <ul><li> to bulletList with listItem children', () => {
138
153
  const result = htmlToTiptap('<ul><li>Item 1</li><li>Item 2</li></ul>');
139
154
  expect(result.content.length).toBe(1);
140
- const list = result.content[0];
155
+ const list = result.content[0]!;
141
156
  expect(list.type).toBe('bulletList');
142
157
  expect(list.content!.length).toBe(2);
143
- expect(list.content![0].type).toBe('listItem');
144
- expect(list.content![1].type).toBe('listItem');
158
+ expect(list.content![0]!.type).toBe('listItem');
159
+ expect(list.content![1]!.type).toBe('listItem');
145
160
  });
146
161
 
147
162
  test('should convert <ol><li> to orderedList', () => {
148
163
  const result = htmlToTiptap('<ol><li>First</li><li>Second</li></ol>');
149
- const list = result.content[0];
164
+ const list = result.content[0]!;
150
165
  expect(list.type).toBe('orderedList');
151
166
  expect(list.content!.length).toBe(2);
152
167
  });
153
168
 
154
169
  test('should convert <ol start="3"> to orderedList with start attr', () => {
155
170
  const result = htmlToTiptap('<ol start="3"><li>Third</li></ol>');
156
- const list = result.content[0];
171
+ const list = result.content[0]!;
157
172
  expect(list.type).toBe('orderedList');
158
173
  expect(list.attrs!.start).toBe(3);
159
174
  });
160
175
 
161
176
  test('should wrap list item inline content in paragraph', () => {
162
177
  const result = htmlToTiptap('<ul><li>Simple text</li></ul>');
163
- const listItem = result.content[0].content![0];
178
+ const listItem = result.content[0]!.content![0]!;
164
179
  expect(listItem.type).toBe('listItem');
165
- expect(listItem.content![0].type).toBe('paragraph');
166
- expect(listItem.content![0].content![0].text).toBe('Simple text');
180
+ expect(listItem.content![0]!.type).toBe('paragraph');
181
+ expect(listItem.content![0]!.content![0]!.text).toBe('Simple text');
167
182
  });
168
183
 
169
184
  test('should preserve block content inside list items', () => {
170
185
  const result = htmlToTiptap('<ul><li><p>Block content</p></li></ul>');
171
- const listItem = result.content[0].content![0];
186
+ const listItem = result.content[0]!.content![0]!;
172
187
  expect(listItem.type).toBe('listItem');
173
- expect(listItem.content![0].type).toBe('paragraph');
188
+ expect(listItem.content![0]!.type).toBe('paragraph');
174
189
  });
175
190
 
176
191
  test('should give empty list item an empty paragraph', () => {
177
192
  const result = htmlToTiptap('<ul><li></li></ul>');
178
- const listItem = result.content[0].content![0];
193
+ const listItem = result.content[0]!.content![0]!;
179
194
  expect(listItem.type).toBe('listItem');
180
195
  expect(listItem.content!.length).toBe(1);
181
- expect(listItem.content![0].type).toBe('paragraph');
196
+ expect(listItem.content![0]!.type).toBe('paragraph');
182
197
  });
183
198
  });
184
199
 
185
200
  test('should convert <blockquote> to blockquote node', () => {
186
201
  const result = htmlToTiptap('<blockquote>Quoted text</blockquote>');
187
- const bq = result.content[0];
202
+ const bq = result.content[0]!;
188
203
  expect(bq.type).toBe('blockquote');
189
204
  // Content should be wrapped in paragraph
190
- expect(bq.content![0].type).toBe('paragraph');
205
+ expect(bq.content![0]!.type).toBe('paragraph');
191
206
  });
192
207
 
193
208
  test('should convert empty <blockquote> to blockquote with empty paragraph', () => {
194
209
  const result = htmlToTiptap('<blockquote></blockquote>');
195
- const bq = result.content[0];
210
+ const bq = result.content[0]!;
196
211
  expect(bq.type).toBe('blockquote');
197
212
  expect(bq.content!.length).toBe(1);
198
- expect(bq.content![0].type).toBe('paragraph');
213
+ expect(bq.content![0]!.type).toBe('paragraph');
199
214
  });
200
215
 
201
216
  test('should convert <pre><code> to codeBlock', () => {
202
217
  const result = htmlToTiptap('<pre><code>const x = 1;</code></pre>');
203
- const codeBlock = result.content[0];
218
+ const codeBlock = result.content[0]!;
204
219
  expect(codeBlock.type).toBe('codeBlock');
205
- expect(codeBlock.content![0].text).toBe('const x = 1;');
220
+ expect(codeBlock.content![0]!.text).toBe('const x = 1;');
206
221
  });
207
222
 
208
223
  test('should parse language from <code class="language-javascript">', () => {
209
224
  const result = htmlToTiptap('<pre><code class="language-javascript">let y = 2;</code></pre>');
210
- const codeBlock = result.content[0];
225
+ const codeBlock = result.content[0]!;
211
226
  expect(codeBlock.type).toBe('codeBlock');
212
227
  expect(codeBlock.attrs!.language).toBe('javascript');
213
228
  });
214
229
 
215
230
  test('should handle <pre> without <code> child', () => {
216
231
  const result = htmlToTiptap('<pre>Plain preformatted</pre>');
217
- const codeBlock = result.content[0];
232
+ const codeBlock = result.content[0]!;
218
233
  expect(codeBlock.type).toBe('codeBlock');
219
- expect(codeBlock.content![0].text).toBe('Plain preformatted');
234
+ expect(codeBlock.content![0]!.text).toBe('Plain preformatted');
220
235
  });
221
236
 
222
237
  test('should convert <hr> to horizontalRule', () => {
223
238
  const result = htmlToTiptap('<p>Above</p><hr><p>Below</p>');
224
- const hr = result.content[1];
239
+ const hr = result.content[1]!;
225
240
  expect(hr.type).toBe('horizontalRule');
226
241
  });
227
242
 
228
243
  test('should convert <br> to hardBreak', () => {
229
244
  const result = htmlToTiptap('<p>Line 1<br>Line 2</p>');
230
- const p = result.content[0];
245
+ const p = result.content[0]!;
231
246
  expect(p.type).toBe('paragraph');
232
247
  // Should contain text, hardBreak, text
233
248
  const types = p.content!.map((n) => n.type);
@@ -265,7 +280,7 @@ describe('htmlToTiptap', () => {
265
280
  describe('inline marks', () => {
266
281
  test('should convert <strong> to bold mark', () => {
267
282
  const result = htmlToTiptap('<p><strong>Bold text</strong></p>');
268
- const textNode = result.content[0].content![0];
283
+ const textNode = result.content[0]!.content![0]!;
269
284
  expect(textNode.text).toBe('Bold text');
270
285
  expect(textNode.marks).toBeDefined();
271
286
  expect(textNode.marks!.some((m) => m.type === 'bold')).toBe(true);
@@ -273,51 +288,51 @@ describe('htmlToTiptap', () => {
273
288
 
274
289
  test('should convert <b> to bold mark', () => {
275
290
  const result = htmlToTiptap('<p><b>Bold text</b></p>');
276
- const textNode = result.content[0].content![0];
291
+ const textNode = result.content[0]!.content![0]!;
277
292
  expect(textNode.marks!.some((m) => m.type === 'bold')).toBe(true);
278
293
  });
279
294
 
280
295
  test('should convert <em> to italic mark', () => {
281
296
  const result = htmlToTiptap('<p><em>Italic text</em></p>');
282
- const textNode = result.content[0].content![0];
297
+ const textNode = result.content[0]!.content![0]!;
283
298
  expect(textNode.text).toBe('Italic text');
284
299
  expect(textNode.marks!.some((m) => m.type === 'italic')).toBe(true);
285
300
  });
286
301
 
287
302
  test('should convert <i> to italic mark', () => {
288
303
  const result = htmlToTiptap('<p><i>Italic text</i></p>');
289
- const textNode = result.content[0].content![0];
304
+ const textNode = result.content[0]!.content![0]!;
290
305
  expect(textNode.marks!.some((m) => m.type === 'italic')).toBe(true);
291
306
  });
292
307
 
293
308
  test('should convert <u> to underline mark', () => {
294
309
  const result = htmlToTiptap('<p><u>Underlined</u></p>');
295
- const textNode = result.content[0].content![0];
310
+ const textNode = result.content[0]!.content![0]!;
296
311
  expect(textNode.marks!.some((m) => m.type === 'underline')).toBe(true);
297
312
  });
298
313
 
299
314
  test('should convert <s> to strike mark', () => {
300
315
  const result = htmlToTiptap('<p><s>Struck</s></p>');
301
- const textNode = result.content[0].content![0];
316
+ const textNode = result.content[0]!.content![0]!;
302
317
  expect(textNode.marks!.some((m) => m.type === 'strike')).toBe(true);
303
318
  });
304
319
 
305
320
  test('should convert <del> to strike mark', () => {
306
321
  const result = htmlToTiptap('<p><del>Deleted</del></p>');
307
- const textNode = result.content[0].content![0];
322
+ const textNode = result.content[0]!.content![0]!;
308
323
  expect(textNode.marks!.some((m) => m.type === 'strike')).toBe(true);
309
324
  });
310
325
 
311
326
  test('should convert <code> to code mark', () => {
312
327
  const result = htmlToTiptap('<p><code>inline code</code></p>');
313
- const textNode = result.content[0].content![0];
328
+ const textNode = result.content[0]!.content![0]!;
314
329
  expect(textNode.text).toBe('inline code');
315
330
  expect(textNode.marks!.some((m) => m.type === 'code')).toBe(true);
316
331
  });
317
332
 
318
333
  test('should convert <a> to text with link mark', () => {
319
334
  const result = htmlToTiptap('<p><a href="https://example.com">Click here</a></p>');
320
- const textNode = result.content[0].content![0];
335
+ const textNode = result.content[0]!.content![0]!;
321
336
  expect(textNode.text).toBe('Click here');
322
337
  const linkMark = textNode.marks!.find((m) => m.type === 'link');
323
338
  expect(linkMark).toBeDefined();
@@ -325,10 +340,8 @@ describe('htmlToTiptap', () => {
325
340
  });
326
341
 
327
342
  test('should convert <a> with target and rel attributes', () => {
328
- const result = htmlToTiptap(
329
- '<p><a href="/page" target="_blank" rel="noopener">Link</a></p>'
330
- );
331
- const textNode = result.content[0].content![0];
343
+ const result = htmlToTiptap('<p><a href="/page" target="_blank" rel="noopener">Link</a></p>');
344
+ const textNode = result.content[0]!.content![0]!;
332
345
  const linkMark = textNode.marks!.find((m) => m.type === 'link');
333
346
  expect(linkMark!.attrs!.href).toBe('/page');
334
347
  expect(linkMark!.attrs!.target).toBe('_blank');
@@ -342,7 +355,7 @@ describe('htmlToTiptap', () => {
342
355
  describe('nested marks', () => {
343
356
  test('should apply both bold and italic marks for <strong><em>', () => {
344
357
  const result = htmlToTiptap('<p><strong><em>Bold and italic</em></strong></p>');
345
- const textNode = result.content[0].content![0];
358
+ const textNode = result.content[0]!.content![0]!;
346
359
  expect(textNode.text).toBe('Bold and italic');
347
360
  expect(textNode.marks!.length).toBeGreaterThanOrEqual(2);
348
361
  const markTypes = textNode.marks!.map((m) => m.type);
@@ -352,17 +365,15 @@ describe('htmlToTiptap', () => {
352
365
 
353
366
  test('should apply bold and italic for <em><strong> (reverse nesting)', () => {
354
367
  const result = htmlToTiptap('<p><em><strong>Italic and bold</strong></em></p>');
355
- const textNode = result.content[0].content![0];
368
+ const textNode = result.content[0]!.content![0]!;
356
369
  const markTypes = textNode.marks!.map((m) => m.type);
357
370
  expect(markTypes).toContain('bold');
358
371
  expect(markTypes).toContain('italic');
359
372
  });
360
373
 
361
374
  test('should handle bold inside link', () => {
362
- const result = htmlToTiptap(
363
- '<p><a href="/url"><strong>Bold link</strong></a></p>'
364
- );
365
- const textNode = result.content[0].content![0];
375
+ const result = htmlToTiptap('<p><a href="/url"><strong>Bold link</strong></a></p>');
376
+ const textNode = result.content[0]!.content![0]!;
366
377
  expect(textNode.text).toBe('Bold link');
367
378
  const markTypes = textNode.marks!.map((m) => m.type);
368
379
  expect(markTypes).toContain('bold');
@@ -376,7 +387,7 @@ describe('htmlToTiptap', () => {
376
387
  describe('span handling', () => {
377
388
  test('should convert <span data-meno-span="custom"> to textStyle mark', () => {
378
389
  const result = htmlToTiptap('<p><span data-meno-span="custom">Styled</span></p>');
379
- const textNode = result.content[0].content![0];
390
+ const textNode = result.content[0]!.content![0]!;
380
391
  expect(textNode.text).toBe('Styled');
381
392
  const styleMark = textNode.marks!.find((m) => m.type === 'textStyle');
382
393
  expect(styleMark).toBeDefined();
@@ -385,7 +396,7 @@ describe('htmlToTiptap', () => {
385
396
 
386
397
  test('should pass through <span> without special attrs as just children', () => {
387
398
  const result = htmlToTiptap('<p><span>Plain span</span></p>');
388
- const textNode = result.content[0].content![0];
399
+ const textNode = result.content[0]!.content![0]!;
389
400
  expect(textNode.type).toBe('text');
390
401
  expect(textNode.text).toBe('Plain span');
391
402
  // Should not have any marks (or at least no textStyle mark)
@@ -395,7 +406,7 @@ describe('htmlToTiptap', () => {
395
406
 
396
407
  test('should handle <span> with style but no data-meno-span (returns children)', () => {
397
408
  const result = htmlToTiptap('<p><span style="color: red">Red</span></p>');
398
- const textNode = result.content[0].content![0];
409
+ const textNode = result.content[0]!.content![0]!;
399
410
  expect(textNode.text).toBe('Red');
400
411
  // Currently the implementation returns children without marks for styled spans
401
412
  const hasTextStyle = textNode.marks?.some((m) => m.type === 'textStyle');
@@ -424,15 +435,15 @@ describe('htmlToTiptap', () => {
424
435
  expect(table!.content!.length).toBe(2); // 2 rows
425
436
 
426
437
  // First row should have tableHeader cells
427
- const headerRow = table!.content![0];
438
+ const headerRow = table!.content![0]!;
428
439
  expect(headerRow.type).toBe('tableRow');
429
- expect(headerRow.content![0].type).toBe('tableHeader');
430
- expect(headerRow.content![1].type).toBe('tableHeader');
440
+ expect(headerRow.content![0]!.type).toBe('tableHeader');
441
+ expect(headerRow.content![1]!.type).toBe('tableHeader');
431
442
 
432
443
  // Second row should have tableCell cells
433
- const bodyRow = table!.content![1];
444
+ const bodyRow = table!.content![1]!;
434
445
  expect(bodyRow.type).toBe('tableRow');
435
- expect(bodyRow.content![0].type).toBe('tableCell');
446
+ expect(bodyRow.content![0]!.type).toBe('tableCell');
436
447
  });
437
448
 
438
449
  test('should handle table cells with colspan and rowspan', () => {
@@ -450,9 +461,9 @@ describe('htmlToTiptap', () => {
450
461
  const table = findNode(result, 'table');
451
462
  expect(table).toBeDefined();
452
463
  expect(table!.content!.length).toBe(1);
453
- const row = table!.content![0];
464
+ const row = table!.content![0]!;
454
465
  expect(row.type).toBe('tableRow');
455
- expect(row.content![0].type).toBe('tableCell');
466
+ expect(row.content![0]!.type).toBe('tableCell');
456
467
  });
457
468
 
458
469
  test('should give empty table cells an empty paragraph', () => {
@@ -461,7 +472,7 @@ describe('htmlToTiptap', () => {
461
472
  const cell = findNode(result, 'tableCell');
462
473
  expect(cell).toBeDefined();
463
474
  expect(cell!.content!.length).toBe(1);
464
- expect(cell!.content![0].type).toBe('paragraph');
475
+ expect(cell!.content![0]!.type).toBe('paragraph');
465
476
  });
466
477
 
467
478
  test('should handle <th> cells in thead as tableHeader', () => {
@@ -487,33 +498,33 @@ describe('htmlToTiptap', () => {
487
498
  test('should parse <div> children without wrapping node', () => {
488
499
  const result = htmlToTiptap('<div><p>Inside div</p></div>');
489
500
  // The div should not produce a wrapping node; its children (the <p>) should be at top level
490
- expect(result.content[0].type).toBe('paragraph');
491
- expect(result.content[0].content![0].text).toBe('Inside div');
501
+ expect(result.content[0]!.type).toBe('paragraph');
502
+ expect(result.content[0]!.content![0]!.text).toBe('Inside div');
492
503
  });
493
504
 
494
505
  test('should parse <article> children without wrapping node', () => {
495
506
  const result = htmlToTiptap('<article><p>Article content</p></article>');
496
- expect(result.content[0].type).toBe('paragraph');
507
+ expect(result.content[0]!.type).toBe('paragraph');
497
508
  });
498
509
 
499
510
  test('should parse <section> children without wrapping node', () => {
500
511
  const result = htmlToTiptap('<section><p>Section content</p></section>');
501
- expect(result.content[0].type).toBe('paragraph');
512
+ expect(result.content[0]!.type).toBe('paragraph');
502
513
  });
503
514
 
504
515
  test('should parse <main> children without wrapping node', () => {
505
516
  const result = htmlToTiptap('<main><p>Main content</p></main>');
506
- expect(result.content[0].type).toBe('paragraph');
517
+ expect(result.content[0]!.type).toBe('paragraph');
507
518
  });
508
519
 
509
520
  test('should parse <header> children without wrapping node', () => {
510
521
  const result = htmlToTiptap('<header><h1>Title</h1></header>');
511
- expect(result.content[0].type).toBe('heading');
522
+ expect(result.content[0]!.type).toBe('heading');
512
523
  });
513
524
 
514
525
  test('should parse <footer> children without wrapping node', () => {
515
526
  const result = htmlToTiptap('<footer><p>Footer</p></footer>');
516
- expect(result.content[0].type).toBe('paragraph');
527
+ expect(result.content[0]!.type).toBe('paragraph');
517
528
  });
518
529
  });
519
530
 
@@ -524,7 +535,7 @@ describe('htmlToTiptap', () => {
524
535
  test('should wrap top-level text in a paragraph', () => {
525
536
  const result = htmlToTiptap('Just text');
526
537
  expect(result.content.length).toBe(1);
527
- expect(result.content[0].type).toBe('paragraph');
538
+ expect(result.content[0]!.type).toBe('paragraph');
528
539
  });
529
540
 
530
541
  test('should wrap mixed inline and block siblings correctly', () => {
@@ -532,21 +543,21 @@ describe('htmlToTiptap', () => {
532
543
  const result = htmlToTiptap('Before<p>Block</p>After');
533
544
  // 'Before' should be wrapped in a paragraph, then the <p>, then 'After' in a paragraph
534
545
  expect(result.content.length).toBe(3);
535
- expect(result.content[0].type).toBe('paragraph');
536
- expect(result.content[0].content![0].text).toBe('Before');
537
- expect(result.content[1].type).toBe('paragraph');
538
- expect(result.content[1].content![0].text).toBe('Block');
539
- expect(result.content[2].type).toBe('paragraph');
540
- expect(result.content[2].content![0].text).toBe('After');
546
+ expect(result.content[0]!.type).toBe('paragraph');
547
+ expect(result.content[0]!.content![0]!.text).toBe('Before');
548
+ expect(result.content[1]!.type).toBe('paragraph');
549
+ expect(result.content[1]!.content![0]!.text).toBe('Block');
550
+ expect(result.content[2]!.type).toBe('paragraph');
551
+ expect(result.content[2]!.content![0]!.text).toBe('After');
541
552
  });
542
553
 
543
554
  test('should group consecutive inline elements into one paragraph', () => {
544
555
  const result = htmlToTiptap('<strong>Bold</strong> and <em>italic</em>');
545
556
  // All inline content should end up in a single paragraph
546
557
  expect(result.content.length).toBe(1);
547
- expect(result.content[0].type).toBe('paragraph');
558
+ expect(result.content[0]!.type).toBe('paragraph');
548
559
  // Should have multiple text nodes inside
549
- expect(result.content[0].content!.length).toBeGreaterThanOrEqual(2);
560
+ expect(result.content[0]!.content!.length).toBeGreaterThanOrEqual(2);
550
561
  });
551
562
  });
552
563
 
@@ -557,27 +568,27 @@ describe('htmlToTiptap', () => {
557
568
  test('should convert multiple paragraphs', () => {
558
569
  const result = htmlToTiptap('<p>First</p><p>Second</p><p>Third</p>');
559
570
  expect(result.content.length).toBe(3);
560
- expect(result.content[0].content![0].text).toBe('First');
561
- expect(result.content[1].content![0].text).toBe('Second');
562
- expect(result.content[2].content![0].text).toBe('Third');
571
+ expect(result.content[0]!.content![0]!.text).toBe('First');
572
+ expect(result.content[1]!.content![0]!.text).toBe('Second');
573
+ expect(result.content[2]!.content![0]!.text).toBe('Third');
563
574
  });
564
575
 
565
576
  test('should convert paragraph with mixed text and marks', () => {
566
577
  const result = htmlToTiptap('<p>Normal <strong>bold</strong> normal</p>');
567
- const p = result.content[0];
578
+ const p = result.content[0]!;
568
579
  expect(p.type).toBe('paragraph');
569
580
  expect(p.content!.length).toBe(3);
570
- expect(p.content![0].text).toBe('Normal ');
571
- expect(p.content![0].marks).toBeUndefined();
572
- expect(p.content![1].text).toBe('bold');
573
- expect(p.content![1].marks![0].type).toBe('bold');
574
- expect(p.content![2].text).toBe(' normal');
581
+ expect(p.content![0]!.text).toBe('Normal ');
582
+ expect(p.content![0]!.marks).toBeUndefined();
583
+ expect(p.content![1]!.text).toBe('bold');
584
+ expect(p.content![1]!.marks![0]!.type).toBe('bold');
585
+ expect(p.content![2]!.text).toBe(' normal');
575
586
  });
576
587
 
577
588
  test('should handle empty paragraph', () => {
578
589
  const result = htmlToTiptap('<p></p>');
579
590
  expect(result.content.length).toBe(1);
580
- const p = result.content[0];
591
+ const p = result.content[0]!;
581
592
  expect(p.type).toBe('paragraph');
582
593
  // Empty paragraph should not have content property (or have undefined content)
583
594
  expect(p.content).toBeUndefined();
@@ -586,16 +597,16 @@ describe('htmlToTiptap', () => {
586
597
  test('should handle heading followed by paragraph', () => {
587
598
  const result = htmlToTiptap('<h1>Title</h1><p>Body text</p>');
588
599
  expect(result.content.length).toBe(2);
589
- expect(result.content[0].type).toBe('heading');
590
- expect(result.content[1].type).toBe('paragraph');
600
+ expect(result.content[0]!.type).toBe('heading');
601
+ expect(result.content[1]!.type).toBe('paragraph');
591
602
  });
592
603
 
593
604
  test('should handle blockquote containing paragraph', () => {
594
605
  const result = htmlToTiptap('<blockquote><p>Quoted paragraph</p></blockquote>');
595
- const bq = result.content[0];
606
+ const bq = result.content[0]!;
596
607
  expect(bq.type).toBe('blockquote');
597
- expect(bq.content![0].type).toBe('paragraph');
598
- expect(bq.content![0].content![0].text).toBe('Quoted paragraph');
608
+ expect(bq.content![0]!.type).toBe('paragraph');
609
+ expect(bq.content![0]!.content![0]!.text).toBe('Quoted paragraph');
599
610
  });
600
611
  });
601
612
 
@@ -606,8 +617,8 @@ describe('htmlToTiptap', () => {
606
617
  test('should parse children of unknown elements', () => {
607
618
  const result = htmlToTiptap('<custom-element><p>Content</p></custom-element>');
608
619
  // Unknown element should have its children parsed
609
- expect(result.content[0].type).toBe('paragraph');
610
- expect(result.content[0].content![0].text).toBe('Content');
620
+ expect(result.content[0]!.type).toBe('paragraph');
621
+ expect(result.content[0]!.content![0]!.text).toBe('Content');
611
622
  });
612
623
  });
613
624
 
@@ -633,20 +644,20 @@ describe('htmlToTiptap', () => {
633
644
  describe('code block edge cases', () => {
634
645
  test('should handle <pre><code> with no language class', () => {
635
646
  const result = htmlToTiptap('<pre><code>plain code</code></pre>');
636
- const codeBlock = result.content[0];
647
+ const codeBlock = result.content[0]!;
637
648
  expect(codeBlock.type).toBe('codeBlock');
638
649
  expect(codeBlock.attrs?.language).toBeUndefined();
639
650
  });
640
651
 
641
652
  test('should handle <pre><code class="language-python">', () => {
642
653
  const result = htmlToTiptap('<pre><code class="language-python">print("hi")</code></pre>');
643
- const codeBlock = result.content[0];
654
+ const codeBlock = result.content[0]!;
644
655
  expect(codeBlock.attrs!.language).toBe('python');
645
656
  });
646
657
 
647
658
  test('should handle empty code block', () => {
648
659
  const result = htmlToTiptap('<pre><code></code></pre>');
649
- const codeBlock = result.content[0];
660
+ const codeBlock = result.content[0]!;
650
661
  expect(codeBlock.type).toBe('codeBlock');
651
662
  expect(codeBlock.content).toEqual([]);
652
663
  });
@@ -678,7 +689,7 @@ describe('htmlToTiptap', () => {
678
689
  describe('link edge cases', () => {
679
690
  test('should handle <a> without href', () => {
680
691
  const result = htmlToTiptap('<p><a>No href</a></p>');
681
- const textNode = result.content[0].content![0];
692
+ const textNode = result.content[0]!.content![0]!;
682
693
  const linkMark = textNode.marks!.find((m) => m.type === 'link');
683
694
  expect(linkMark).toBeDefined();
684
695
  expect(linkMark!.attrs!.href).toBe('');
@@ -686,7 +697,7 @@ describe('htmlToTiptap', () => {
686
697
 
687
698
  test('should handle <a> with only href (no target or rel)', () => {
688
699
  const result = htmlToTiptap('<p><a href="/page">Link</a></p>');
689
- const textNode = result.content[0].content![0];
700
+ const textNode = result.content[0]!.content![0]!;
690
701
  const linkMark = textNode.marks!.find((m) => m.type === 'link');
691
702
  expect(linkMark!.attrs!.href).toBe('/page');
692
703
  expect(linkMark!.attrs!.target).toBeUndefined();
@@ -708,9 +719,9 @@ describe('htmlToTiptap', () => {
708
719
  test('should handle <hr> between paragraphs', () => {
709
720
  const result = htmlToTiptap('<p>Above</p><hr><p>Below</p>');
710
721
  expect(result.content.length).toBe(3);
711
- expect(result.content[0].type).toBe('paragraph');
712
- expect(result.content[1].type).toBe('horizontalRule');
713
- expect(result.content[2].type).toBe('paragraph');
722
+ expect(result.content[0]!.type).toBe('paragraph');
723
+ expect(result.content[1]!.type).toBe('horizontalRule');
724
+ expect(result.content[2]!.type).toBe('paragraph');
714
725
  });
715
726
  });
716
727
 
@@ -721,9 +732,9 @@ describe('htmlToTiptap', () => {
721
732
  test('should handle nested <ul> inside <li>', () => {
722
733
  const html = '<ul><li>Parent<ul><li>Child</li></ul></li></ul>';
723
734
  const result = htmlToTiptap(html);
724
- const outerList = result.content[0];
735
+ const outerList = result.content[0]!;
725
736
  expect(outerList.type).toBe('bulletList');
726
- const listItem = outerList.content![0];
737
+ const listItem = outerList.content![0]!;
727
738
  expect(listItem.type).toBe('listItem');
728
739
  // The list item should contain both a paragraph (for "Parent") and a nested bulletList
729
740
  const nestedList = listItem.content!.find((n) => n.type === 'bulletList');
@@ -737,7 +748,7 @@ describe('htmlToTiptap', () => {
737
748
  describe('whitespace handling', () => {
738
749
  test('should preserve meaningful whitespace in text', () => {
739
750
  const result = htmlToTiptap('<p>Hello World</p>');
740
- expect(result.content[0].content![0].text).toBe('Hello World');
751
+ expect(result.content[0]!.content![0]!.text).toBe('Hello World');
741
752
  });
742
753
  });
743
754
 
@@ -752,10 +763,10 @@ describe('htmlToTiptap', () => {
752
763
  const result = htmlToTiptap('<p>Hello <strong>world</strong></p>');
753
764
  expect(result.type).toBe('doc');
754
765
  expect(result.content.length).toBe(1);
755
- expect(result.content[0].type).toBe('paragraph');
766
+ expect(result.content[0]!.type).toBe('paragraph');
756
767
  // Should strip HTML tags and return plain text
757
- expect(result.content[0].content![0].type).toBe('text');
758
- expect(result.content[0].content![0].text).toBe('Hello world');
768
+ expect(result.content[0]!.content![0]!.type).toBe('text');
769
+ expect(result.content[0]!.content![0]!.text).toBe('Hello world');
759
770
  } finally {
760
771
  (globalThis as any).DOMParser = originalDOMParser;
761
772
  }
@@ -766,7 +777,7 @@ describe('htmlToTiptap', () => {
766
777
  delete (globalThis as any).DOMParser;
767
778
  try {
768
779
  const result = htmlToTiptap('Just plain text');
769
- expect(result.content[0].content![0].text).toBe('Just plain text');
780
+ expect(result.content[0]!.content![0]!.text).toBe('Just plain text');
770
781
  } finally {
771
782
  (globalThis as any).DOMParser = originalDOMParser;
772
783
  }
@@ -779,7 +790,7 @@ describe('htmlToTiptap', () => {
779
790
  describe('non-element node types', () => {
780
791
  test('should ignore HTML comments', () => {
781
792
  const result = htmlToTiptap('<p>Before<!-- comment -->After</p>');
782
- const p = result.content[0];
793
+ const p = result.content[0]!;
783
794
  expect(p.type).toBe('paragraph');
784
795
  // Comment node should be ignored, only text nodes remain
785
796
  const textNodes = p.content!.filter((n) => n.type === 'text');
@@ -796,7 +807,7 @@ describe('htmlToTiptap', () => {
796
807
  describe('table elements parsed standalone via parseNode', () => {
797
808
  test('should handle <strike> tag as strike mark', () => {
798
809
  const result = htmlToTiptap('<p><strike>Strikethrough</strike></p>');
799
- const textNode = result.content[0].content![0];
810
+ const textNode = result.content[0]!.content![0]!;
800
811
  expect(textNode.marks!.some((m) => m.type === 'strike')).toBe(true);
801
812
  });
802
813
 
@@ -839,12 +850,10 @@ describe('htmlToTiptap', () => {
839
850
  test('should apply mark recursively to nested inline content', () => {
840
851
  // <strong> wrapping a <br> and text - hardBreak is a non-text node
841
852
  const result = htmlToTiptap('<p><strong>Before<br>After</strong></p>');
842
- const p = result.content[0];
853
+ const p = result.content[0]!;
843
854
  expect(p.type).toBe('paragraph');
844
855
  // Should have text nodes with bold mark and a hardBreak
845
- const boldTexts = p.content!.filter(
846
- (n) => n.type === 'text' && n.marks?.some((m) => m.type === 'bold')
847
- );
856
+ const boldTexts = p.content!.filter((n) => n.type === 'text' && n.marks?.some((m) => m.type === 'bold'));
848
857
  expect(boldTexts.length).toBeGreaterThanOrEqual(1);
849
858
  });
850
859
 
@@ -854,8 +863,8 @@ describe('htmlToTiptap', () => {
854
863
  // but is not a text node itself. However, parseLink returns text nodes with link mark,
855
864
  // so the bold mark is applied to text nodes that already have marks.
856
865
  const result = htmlToTiptap('<p><strong><a href="/url">Link text</a></strong></p>');
857
- const p = result.content[0];
858
- const textNode = p.content![0];
866
+ const p = result.content[0]!;
867
+ const textNode = p.content![0]!;
859
868
  expect(textNode.type).toBe('text');
860
869
  expect(textNode.text).toBe('Link text');
861
870
  const markTypes = textNode.marks!.map((m) => m.type);
@@ -872,7 +881,7 @@ describe('htmlToTiptap', () => {
872
881
  // This should trigger the 'tr' case in parseNode switch (line 207-208)
873
882
  const result = htmlToTiptap('<div><tr><td>Cell</td></tr></div>');
874
883
  // The tr is parsed via parseTableRow -> produces tableRow
875
- const tableRow = findNode(result, 'tableRow');
884
+ const _tableRow = findNode(result, 'tableRow');
876
885
  // Depending on happy-dom's handling, the <tr> may or may not survive
877
886
  // But parseNode should handle it
878
887
  expect(result.type).toBe('doc');
@@ -897,7 +906,7 @@ describe('htmlToTiptap', () => {
897
906
  describe('empty heading', () => {
898
907
  test('should handle empty heading', () => {
899
908
  const result = htmlToTiptap('<h1></h1>');
900
- const heading = result.content[0];
909
+ const heading = result.content[0]!;
901
910
  expect(heading.type).toBe('heading');
902
911
  expect(heading.attrs!.level).toBe(1);
903
912
  expect(heading.content).toBeUndefined();
@@ -942,7 +951,7 @@ describe('htmlToTiptap', () => {
942
951
  const result = htmlToTiptap('<div><li>Item outside list</li></div>');
943
952
  const listItem = findNode(result, 'listItem');
944
953
  expect(listItem).toBeDefined();
945
- expect(listItem!.content![0].type).toBe('paragraph');
954
+ expect(listItem!.content![0]!.type).toBe('paragraph');
946
955
  });
947
956
  });
948
957
  });