meno-core 1.0.54 → 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 (296) hide show
  1. package/bin/cli.ts +19 -205
  2. package/build-astro.ts +31 -27
  3. package/dist/bin/cli.js +15 -154
  4. package/dist/bin/cli.js.map +2 -2
  5. package/dist/chunks/{chunk-7HWQUVTU.js → chunk-2IIQK7T3.js} +1353 -347
  6. package/dist/chunks/chunk-2IIQK7T3.js.map +7 -0
  7. package/dist/chunks/{chunk-3XER4E5W.js → chunk-4ZRU52J2.js} +6 -5
  8. package/dist/chunks/{chunk-3XER4E5W.js.map → chunk-4ZRU52J2.js.map} +2 -2
  9. package/dist/chunks/{chunk-NVRBTSQG.js → chunk-5X4HCN7J.js} +4 -4
  10. package/dist/chunks/chunk-5X4HCN7J.js.map +7 -0
  11. package/dist/chunks/{chunk-FZITJSSS.js → chunk-7CCVOL5M.js} +14 -14
  12. package/dist/chunks/chunk-7CCVOL5M.js.map +7 -0
  13. package/dist/chunks/{chunk-HIZMY3EP.js → chunk-AR7LHIFY.js} +8 -2
  14. package/dist/chunks/chunk-AR7LHIFY.js.map +7 -0
  15. package/dist/chunks/{chunk-AE3QK5QW.js → chunk-NUP7H7D3.js} +31 -26
  16. package/dist/chunks/chunk-NUP7H7D3.js.map +7 -0
  17. package/dist/chunks/{chunk-5ETZFREW.js → chunk-QWTQZHG3.js} +186 -79
  18. package/dist/chunks/chunk-QWTQZHG3.js.map +7 -0
  19. package/dist/chunks/{chunk-STDY3OVM.js → chunk-XTKNX4FW.js} +3 -3
  20. package/dist/chunks/{chunk-STDY3OVM.js.map → chunk-XTKNX4FW.js.map} +2 -2
  21. package/dist/chunks/{fs-ZI5JEU7V.js → fs-G3ANWEFP.js} +2 -2
  22. package/dist/lib/client/index.js +381 -159
  23. package/dist/lib/client/index.js.map +3 -3
  24. package/dist/lib/server/index.js +14345 -6265
  25. package/dist/lib/server/index.js.map +4 -4
  26. package/dist/lib/shared/index.js +292 -87
  27. package/dist/lib/shared/index.js.map +4 -4
  28. package/dist/lib/shared/richtext/index.js +1 -1
  29. package/dist/lib/test-utils/index.js +27 -21
  30. package/dist/lib/test-utils/index.js.map +2 -2
  31. package/lib/client/ErrorBoundary.test.tsx +147 -90
  32. package/lib/client/ErrorBoundary.tsx +8 -5
  33. package/lib/client/componentRegistry.ts +0 -1
  34. package/lib/client/core/ComponentBuilder.test.ts +150 -0
  35. package/lib/client/core/ComponentBuilder.ts +112 -12
  36. package/lib/client/core/ComponentRenderer.test.tsx +1 -2
  37. package/lib/client/core/builders/embedBuilder.ts +1 -1
  38. package/lib/client/core/builders/linkBuilder.ts +2 -2
  39. package/lib/client/core/builders/linkNodeBuilder.ts +1 -1
  40. package/lib/client/core/builders/listBuilder.ts +41 -4
  41. package/lib/client/core/builders/localeListBuilder.ts +2 -2
  42. package/lib/client/core/cmsTemplateProcessor.ts +1 -2
  43. package/lib/client/hmr/HMRManager.tsx +39 -30
  44. package/lib/client/hmrCssReload.ts +35 -6
  45. package/lib/client/hmrWebSocket.ts +5 -5
  46. package/lib/client/hooks/useColorVariables.ts +12 -9
  47. package/lib/client/hooks/usePropertyAutocomplete.ts +1 -1
  48. package/lib/client/hooks/useVariables.ts +3 -1
  49. package/lib/client/meno-filter/MenoFilter.test.ts +28 -28
  50. package/lib/client/meno-filter/MenoFilter.ts +9 -6
  51. package/lib/client/meno-filter/bindings.ts +3 -3
  52. package/lib/client/meno-filter/init.ts +3 -3
  53. package/lib/client/meno-filter/renderer.ts +8 -4
  54. package/lib/client/meno-filter/ui.ts +1 -1
  55. package/lib/client/meno-filter/updates.ts +3 -2
  56. package/lib/client/meno-filter/utils.ts +1 -1
  57. package/lib/client/navigation.test.ts +40 -44
  58. package/lib/client/responsiveStyleResolver.knownTokens.test.ts +71 -0
  59. package/lib/client/responsiveStyleResolver.test.ts +5 -5
  60. package/lib/client/responsiveStyleResolver.ts +61 -4
  61. package/lib/client/routing/RouteLoader.test.ts +1 -1
  62. package/lib/client/routing/RouteLoader.ts +8 -3
  63. package/lib/client/routing/Router.tsx +181 -152
  64. package/lib/client/scripts/ScriptExecutor.ts +3 -3
  65. package/lib/client/services/PrefetchService.test.ts +1 -1
  66. package/lib/client/services/PrefetchService.ts +13 -5
  67. package/lib/client/styleProcessor.ts +9 -3
  68. package/lib/client/styles/StyleInjector.ts +1 -1
  69. package/lib/client/styles/UtilityClassCollector.ts +54 -13
  70. package/lib/client/templateEngine.test.ts +17 -23
  71. package/lib/client/templateEngine.ts +7 -13
  72. package/lib/client/theme.test.ts +1 -1
  73. package/lib/server/__integration__/server-lifecycle.test.ts +3 -1
  74. package/lib/server/__integration__/static-assets.test.ts +2 -2
  75. package/lib/server/__integration__/test-helpers.ts +3 -4
  76. package/lib/server/astro/cmsPageEmitter.ts +4 -5
  77. package/lib/server/astro/componentEmitter.ts +11 -8
  78. package/lib/server/astro/nodeToAstro.test.ts +4 -4
  79. package/lib/server/astro/nodeToAstro.ts +41 -36
  80. package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +11 -11
  81. package/lib/server/astro/normalizeOrphanTemplateProps.ts +7 -6
  82. package/lib/server/astro/pageEmitter.ts +1 -1
  83. package/lib/server/astro/templateTransformer.ts +3 -3
  84. package/lib/server/createServer.ts +2 -2
  85. package/lib/server/cssGenerator.ts +2 -2
  86. package/lib/server/fileWatcher.test.ts +41 -4
  87. package/lib/server/fileWatcher.ts +102 -23
  88. package/lib/server/index.ts +7 -16
  89. package/lib/server/jsonLoader.test.ts +3 -3
  90. package/lib/server/jsonLoader.ts +8 -5
  91. package/lib/server/middleware/cors.test.ts +1 -1
  92. package/lib/server/middleware/cors.ts +2 -2
  93. package/lib/server/middleware/errorHandler.test.ts +2 -2
  94. package/lib/server/middleware/logger.test.ts +3 -3
  95. package/lib/server/middleware/logger.ts +7 -7
  96. package/lib/server/migrateTemplates.ts +3 -3
  97. package/lib/server/projectContext.ts +1 -1
  98. package/lib/server/providers/fileSystemCMSProvider.test.ts +18 -4
  99. package/lib/server/providers/fileSystemCMSProvider.ts +27 -18
  100. package/lib/server/routes/api/cms.ts +1 -1
  101. package/lib/server/routes/api/colors.test.ts +2 -2
  102. package/lib/server/routes/api/components.ts +1 -1
  103. package/lib/server/routes/api/config.ts +3 -3
  104. package/lib/server/routes/api/core-routes.ts +2 -2
  105. package/lib/server/routes/api/enums.test.ts +2 -2
  106. package/lib/server/routes/api/functions.ts +1 -1
  107. package/lib/server/routes/api/variables.test.ts +2 -2
  108. package/lib/server/routes/index.ts +2 -2
  109. package/lib/server/routes/pages.ts +1 -2
  110. package/lib/server/routes/static.ts +1 -2
  111. package/lib/server/runtime/bundler.ts +65 -43
  112. package/lib/server/runtime/fs.ts +13 -13
  113. package/lib/server/runtime/httpServer.ts +3 -3
  114. package/lib/server/services/ColorService.test.ts +3 -3
  115. package/lib/server/services/ColorService.ts +5 -2
  116. package/lib/server/services/EnumService.test.ts +2 -2
  117. package/lib/server/services/EnumService.ts +4 -1
  118. package/lib/server/services/VariableService.test.ts +7 -7
  119. package/lib/server/services/VariableService.ts +5 -2
  120. package/lib/server/services/cmsService.test.ts +44 -44
  121. package/lib/server/services/cmsService.ts +17 -97
  122. package/lib/server/services/componentService.test.ts +10 -16
  123. package/lib/server/services/componentService.ts +20 -12
  124. package/lib/server/services/configService.test.ts +13 -14
  125. package/lib/server/services/configService.ts +30 -0
  126. package/lib/server/services/fileWatcherService.ts +2 -2
  127. package/lib/server/services/pageService.test.ts +7 -7
  128. package/lib/server/services/pageService.ts +18 -5
  129. package/lib/server/ssr/attributeBuilder.ts +2 -2
  130. package/lib/server/ssr/cmsSSRProcessor.ts +1 -2
  131. package/lib/server/ssr/errorOverlay.ts +1 -1
  132. package/lib/server/ssr/htmlGenerator.nonce.test.ts +6 -5
  133. package/lib/server/ssr/htmlGenerator.test.ts +0 -1
  134. package/lib/server/ssr/htmlGenerator.ts +7 -7
  135. package/lib/server/ssr/imageMetadata.ts +2 -2
  136. package/lib/server/ssr/jsCollector.test.ts +5 -5
  137. package/lib/server/ssr/liveReloadIntegration.test.ts +7 -8
  138. package/lib/server/ssr/ssrRenderer.branches.test.ts +9 -9
  139. package/lib/server/ssr/ssrRenderer.test.ts +125 -15
  140. package/lib/server/ssr/ssrRenderer.ts +206 -38
  141. package/lib/server/ssrRenderer.test.ts +6 -6
  142. package/lib/server/utils/jsonLineMapper.ts +7 -7
  143. package/lib/server/validateStyleCoverage.ts +0 -1
  144. package/lib/server/websocketManager.ts +12 -0
  145. package/lib/shared/breakpoints.test.ts +5 -5
  146. package/lib/shared/cmsQuery.test.ts +81 -0
  147. package/lib/shared/cmsQuery.ts +92 -0
  148. package/lib/shared/cmsQueryParser.ts +11 -7
  149. package/lib/shared/colorConversions.ts +9 -9
  150. package/lib/shared/colorVariableUtils.test.ts +22 -0
  151. package/lib/shared/colorVariableUtils.ts +19 -5
  152. package/lib/shared/constants.ts +4 -0
  153. package/lib/shared/cssGeneration.test.ts +23 -0
  154. package/lib/shared/cssGeneration.ts +40 -27
  155. package/lib/shared/cssProperties.test.ts +64 -64
  156. package/lib/shared/cssProperties.ts +2 -2
  157. package/lib/shared/elementClassName.ts +2 -2
  158. package/lib/shared/errorLogger.test.ts +12 -12
  159. package/lib/shared/expressionEvaluator.test.ts +27 -1
  160. package/lib/shared/expressionEvaluator.ts +9 -0
  161. package/lib/shared/fontLoader.test.ts +13 -13
  162. package/lib/shared/fontLoader.ts +2 -2
  163. package/lib/shared/gradientUtils.test.ts +2 -2
  164. package/lib/shared/gradientUtils.ts +15 -8
  165. package/lib/shared/hrefRefs.test.ts +2 -2
  166. package/lib/shared/hrefRefs.ts +1 -1
  167. package/lib/shared/i18n.test.ts +3 -3
  168. package/lib/shared/i18n.ts +5 -4
  169. package/lib/shared/index.ts +23 -0
  170. package/lib/shared/inlineSvgStyleRules.ts +12 -9
  171. package/lib/shared/interactiveStyleMappings.test.ts +21 -21
  172. package/lib/shared/interactiveStyleMappings.ts +2 -8
  173. package/lib/shared/itemTemplateUtils.test.ts +5 -5
  174. package/lib/shared/itemTemplateUtils.ts +9 -5
  175. package/lib/shared/jsonRepair.ts +8 -8
  176. package/lib/shared/libraryLoader.test.ts +6 -6
  177. package/lib/shared/linkUtils.ts +1 -1
  178. package/lib/shared/logger.test.ts +66 -0
  179. package/lib/shared/logger.ts +94 -0
  180. package/lib/shared/markdown.ts +25 -0
  181. package/lib/shared/nodeUtils.test.ts +13 -9
  182. package/lib/shared/nodeUtils.ts +27 -4
  183. package/lib/shared/pathSecurity.ts +1 -1
  184. package/lib/shared/pathUtils.ts +1 -2
  185. package/lib/shared/paths/PathConverter.ts +3 -3
  186. package/lib/shared/paths/PathUtils.ts +4 -3
  187. package/lib/shared/paths/PathValidator.ts +1 -1
  188. package/lib/shared/permissions.test.ts +168 -0
  189. package/lib/shared/permissions.ts +162 -0
  190. package/lib/shared/propResolver.test.ts +1 -1
  191. package/lib/shared/propResolver.ts +1 -1
  192. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +5 -3
  193. package/lib/shared/registry/ClientRegistry.ts +0 -1
  194. package/lib/shared/registry/ComponentRegistry.test.ts +3 -3
  195. package/lib/shared/registry/ComponentRegistry.ts +6 -1
  196. package/lib/shared/registry/NodeTypeDefinition.ts +2 -2
  197. package/lib/shared/registry/SSRRegistry.ts +0 -1
  198. package/lib/shared/registry/fieldPresets.ts +3 -1
  199. package/lib/shared/registry/nodeTypes/CustomNodeType.ts +103 -0
  200. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +1 -1
  201. package/lib/shared/registry/nodeTypes/IslandNodeType.ts +129 -0
  202. package/lib/shared/registry/nodeTypes/ListNodeType.ts +43 -7
  203. package/lib/shared/registry/nodeTypes/MarkdownNodeType.ts +78 -0
  204. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +6 -1
  205. package/lib/shared/registry/nodeTypes/index.ts +16 -1
  206. package/lib/shared/responsiveScaling.test.ts +1 -3
  207. package/lib/shared/responsiveScaling.ts +6 -5
  208. package/lib/shared/responsiveStyleUtils.test.ts +0 -1
  209. package/lib/shared/richtext/htmlToTiptap.test.ts +116 -116
  210. package/lib/shared/richtext/htmlToTiptap.ts +1 -1
  211. package/lib/shared/styleNodeUtils.ts +17 -14
  212. package/lib/shared/themeDefaults.test.ts +0 -3
  213. package/lib/shared/tree/PathBuilder.test.ts +1 -7
  214. package/lib/shared/tree/PathBuilder.ts +15 -13
  215. package/lib/shared/treePathUtils.test.ts +49 -2
  216. package/lib/shared/treePathUtils.ts +40 -4
  217. package/lib/shared/types/api.ts +105 -7
  218. package/lib/shared/types/cms.test.ts +34 -0
  219. package/lib/shared/types/cms.ts +65 -13
  220. package/lib/shared/types/components.ts +24 -0
  221. package/lib/shared/types/errors.test.ts +1 -1
  222. package/lib/shared/types/index.ts +23 -0
  223. package/lib/shared/types/permissions.ts +139 -0
  224. package/lib/shared/types/variables.ts +3 -3
  225. package/lib/shared/utilityClassConfig.ts +103 -0
  226. package/lib/shared/utilityClassMapper.durability.test.ts +117 -0
  227. package/lib/shared/utilityClassMapper.test.ts +253 -26
  228. package/lib/shared/utilityClassMapper.ts +160 -29
  229. package/lib/shared/utilityClassNames.test.ts +38 -0
  230. package/lib/shared/utilityClassNames.ts +155 -23
  231. package/lib/shared/validation/index.ts +1 -0
  232. package/lib/shared/validation/permissionsValidators.test.ts +49 -0
  233. package/lib/shared/validation/permissionsValidators.ts +49 -0
  234. package/lib/shared/validation/propValidator.test.ts +1 -1
  235. package/lib/shared/validation/propValidator.ts +1 -1
  236. package/lib/shared/validation/schemas.test.ts +41 -0
  237. package/lib/shared/validation/schemas.ts +159 -13
  238. package/lib/shared/validation/validators.test.ts +2 -2
  239. package/lib/shared/viewportUnits.test.ts +13 -0
  240. package/lib/shared/viewportUnits.ts +12 -5
  241. package/lib/test-utils/factories/ConsoleMockFactory.ts +7 -7
  242. package/lib/test-utils/factories/DomMockFactory.ts +10 -4
  243. package/lib/test-utils/factories/ServerMockFactory.ts +1 -1
  244. package/lib/test-utils/factories/StoreMockFactory.ts +5 -5
  245. package/lib/test-utils/mockFactories.ts +1 -1
  246. package/lib/test-utils/mocks.ts +3 -3
  247. package/package.json +3 -5
  248. package/scripts/build-for-publish.mjs +2 -4
  249. package/scripts/build-meno-filter.ts +3 -3
  250. package/tsconfig.json +1 -0
  251. package/vite.config.ts +1 -1
  252. package/build-next.ts +0 -1374
  253. package/build-static.test.ts +0 -453
  254. package/build-static.ts +0 -1072
  255. package/dist/build-static.js +0 -37
  256. package/dist/chunks/chunk-2FN4UOVO.js +0 -6465
  257. package/dist/chunks/chunk-2FN4UOVO.js.map +0 -7
  258. package/dist/chunks/chunk-5ETZFREW.js.map +0 -7
  259. package/dist/chunks/chunk-7E4IF5L7.js +0 -245
  260. package/dist/chunks/chunk-7E4IF5L7.js.map +0 -7
  261. package/dist/chunks/chunk-7HWQUVTU.js.map +0 -7
  262. package/dist/chunks/chunk-AE3QK5QW.js.map +0 -7
  263. package/dist/chunks/chunk-F6KTJYGV.js +0 -322
  264. package/dist/chunks/chunk-F6KTJYGV.js.map +0 -7
  265. package/dist/chunks/chunk-FZITJSSS.js.map +0 -7
  266. package/dist/chunks/chunk-GSYYA5GX.js +0 -77
  267. package/dist/chunks/chunk-GSYYA5GX.js.map +0 -7
  268. package/dist/chunks/chunk-HIZMY3EP.js.map +0 -7
  269. package/dist/chunks/chunk-I2WEGYA7.js +0 -424
  270. package/dist/chunks/chunk-I2WEGYA7.js.map +0 -7
  271. package/dist/chunks/chunk-JNO3CNLJ.js +0 -464
  272. package/dist/chunks/chunk-JNO3CNLJ.js.map +0 -7
  273. package/dist/chunks/chunk-NVRBTSQG.js.map +0 -7
  274. package/dist/chunks/chunk-Q4OBWKXG.js +0 -1143
  275. package/dist/chunks/chunk-Q4OBWKXG.js.map +0 -7
  276. package/dist/chunks/chunk-QTE32Y53.js +0 -3829
  277. package/dist/chunks/chunk-QTE32Y53.js.map +0 -7
  278. package/dist/chunks/configService-PRJZF7Y6.js +0 -14
  279. package/dist/chunks/configService-PRJZF7Y6.js.map +0 -7
  280. package/dist/chunks/constants-KIQEYMAM.js +0 -46
  281. package/dist/chunks/constants-KIQEYMAM.js.map +0 -7
  282. package/dist/chunks/fs-ZI5JEU7V.js.map +0 -7
  283. package/dist/entries/server-router.js +0 -62
  284. package/dist/entries/server-router.js.map +0 -7
  285. package/entries/server-router.tsx +0 -75
  286. package/lib/server/providers/fileSystemPageProvider.test.ts +0 -84
  287. package/lib/server/providers/fileSystemPageProvider.ts +0 -179
  288. package/lib/server/webflow/buildWebflow.ts +0 -623
  289. package/lib/server/webflow/index.ts +0 -23
  290. package/lib/server/webflow/nodeToWebflow.test.ts +0 -3102
  291. package/lib/server/webflow/nodeToWebflow.ts +0 -2372
  292. package/lib/server/webflow/styleMapper.test.ts +0 -348
  293. package/lib/server/webflow/styleMapper.ts +0 -687
  294. package/lib/server/webflow/templateWrapper.ts +0 -49
  295. package/lib/server/webflow/types.ts +0 -389
  296. /package/dist/{build-static.js.map → chunks/fs-G3ANWEFP.js.map} +0 -0
@@ -50,7 +50,7 @@ function findNode(doc: TiptapDocument, type: string): TiptapNode | undefined {
50
50
  }
51
51
 
52
52
  /** Collect all nodes of a given type */
53
- function findAllNodes(doc: TiptapDocument, type: string): TiptapNode[] {
53
+ function _findAllNodes(doc: TiptapDocument, type: string): TiptapNode[] {
54
54
  const result: TiptapNode[] = [];
55
55
  function search(nodes: TiptapNode[]) {
56
56
  for (const n of nodes) {
@@ -90,10 +90,10 @@ describe('htmlToTiptap', () => {
90
90
  const result = htmlToTiptap('Hello world');
91
91
  expect(result.type).toBe('doc');
92
92
  expect(result.content.length).toBe(1);
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');
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');
97
97
  });
98
98
  });
99
99
 
@@ -104,13 +104,13 @@ describe('htmlToTiptap', () => {
104
104
  test('should convert <p> to paragraph node', () => {
105
105
  const result = htmlToTiptap('<p>Hello</p>');
106
106
  expect(result.content.length).toBe(1);
107
- expect(result.content[0].type).toBe('paragraph');
108
- 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');
109
109
  });
110
110
 
111
111
  test('should convert <p> with text-align style to paragraph with textAlign attr', () => {
112
112
  const result = htmlToTiptap('<p style="text-align: center">Centered</p>');
113
- const p = result.content[0];
113
+ const p = result.content[0]!;
114
114
  expect(p.type).toBe('paragraph');
115
115
  expect(p.attrs).toBeDefined();
116
116
  expect(p.attrs!.textAlign).toBe('center');
@@ -118,12 +118,12 @@ describe('htmlToTiptap', () => {
118
118
 
119
119
  test('should convert <p> with text-align justify', () => {
120
120
  const result = htmlToTiptap('<p style="text-align: justify">Justified</p>');
121
- expect(result.content[0].attrs!.textAlign).toBe('justify');
121
+ expect(result.content[0]!.attrs!.textAlign).toBe('justify');
122
122
  });
123
123
 
124
124
  test('should not set textAlign for unsupported alignment value', () => {
125
125
  const result = htmlToTiptap('<p style="text-align: start">Text</p>');
126
- const p = result.content[0];
126
+ const p = result.content[0]!;
127
127
  // 'start' is not in the allowed list
128
128
  expect(p.attrs?.textAlign).toBeUndefined();
129
129
  });
@@ -132,16 +132,16 @@ describe('htmlToTiptap', () => {
132
132
  for (let level = 1; level <= 6; level++) {
133
133
  test(`should convert <h${level}> to heading with level ${level}`, () => {
134
134
  const result = htmlToTiptap(`<h${level}>Heading ${level}</h${level}>`);
135
- const heading = result.content[0];
135
+ const heading = result.content[0]!;
136
136
  expect(heading.type).toBe('heading');
137
137
  expect(heading.attrs!.level).toBe(level);
138
- expect(heading.content![0].text).toBe(`Heading ${level}`);
138
+ expect(heading.content![0]!.text).toBe(`Heading ${level}`);
139
139
  });
140
140
  }
141
141
 
142
142
  test('should convert <h2> with text-align to heading with textAlign', () => {
143
143
  const result = htmlToTiptap('<h2 style="text-align: right">Right Heading</h2>');
144
- const heading = result.content[0];
144
+ const heading = result.content[0]!;
145
145
  expect(heading.type).toBe('heading');
146
146
  expect(heading.attrs!.level).toBe(2);
147
147
  expect(heading.attrs!.textAlign).toBe('right');
@@ -152,97 +152,97 @@ describe('htmlToTiptap', () => {
152
152
  test('should convert <ul><li> to bulletList with listItem children', () => {
153
153
  const result = htmlToTiptap('<ul><li>Item 1</li><li>Item 2</li></ul>');
154
154
  expect(result.content.length).toBe(1);
155
- const list = result.content[0];
155
+ const list = result.content[0]!;
156
156
  expect(list.type).toBe('bulletList');
157
157
  expect(list.content!.length).toBe(2);
158
- expect(list.content![0].type).toBe('listItem');
159
- expect(list.content![1].type).toBe('listItem');
158
+ expect(list.content![0]!.type).toBe('listItem');
159
+ expect(list.content![1]!.type).toBe('listItem');
160
160
  });
161
161
 
162
162
  test('should convert <ol><li> to orderedList', () => {
163
163
  const result = htmlToTiptap('<ol><li>First</li><li>Second</li></ol>');
164
- const list = result.content[0];
164
+ const list = result.content[0]!;
165
165
  expect(list.type).toBe('orderedList');
166
166
  expect(list.content!.length).toBe(2);
167
167
  });
168
168
 
169
169
  test('should convert <ol start="3"> to orderedList with start attr', () => {
170
170
  const result = htmlToTiptap('<ol start="3"><li>Third</li></ol>');
171
- const list = result.content[0];
171
+ const list = result.content[0]!;
172
172
  expect(list.type).toBe('orderedList');
173
173
  expect(list.attrs!.start).toBe(3);
174
174
  });
175
175
 
176
176
  test('should wrap list item inline content in paragraph', () => {
177
177
  const result = htmlToTiptap('<ul><li>Simple text</li></ul>');
178
- const listItem = result.content[0].content![0];
178
+ const listItem = result.content[0]!.content![0]!;
179
179
  expect(listItem.type).toBe('listItem');
180
- expect(listItem.content![0].type).toBe('paragraph');
181
- 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');
182
182
  });
183
183
 
184
184
  test('should preserve block content inside list items', () => {
185
185
  const result = htmlToTiptap('<ul><li><p>Block content</p></li></ul>');
186
- const listItem = result.content[0].content![0];
186
+ const listItem = result.content[0]!.content![0]!;
187
187
  expect(listItem.type).toBe('listItem');
188
- expect(listItem.content![0].type).toBe('paragraph');
188
+ expect(listItem.content![0]!.type).toBe('paragraph');
189
189
  });
190
190
 
191
191
  test('should give empty list item an empty paragraph', () => {
192
192
  const result = htmlToTiptap('<ul><li></li></ul>');
193
- const listItem = result.content[0].content![0];
193
+ const listItem = result.content[0]!.content![0]!;
194
194
  expect(listItem.type).toBe('listItem');
195
195
  expect(listItem.content!.length).toBe(1);
196
- expect(listItem.content![0].type).toBe('paragraph');
196
+ expect(listItem.content![0]!.type).toBe('paragraph');
197
197
  });
198
198
  });
199
199
 
200
200
  test('should convert <blockquote> to blockquote node', () => {
201
201
  const result = htmlToTiptap('<blockquote>Quoted text</blockquote>');
202
- const bq = result.content[0];
202
+ const bq = result.content[0]!;
203
203
  expect(bq.type).toBe('blockquote');
204
204
  // Content should be wrapped in paragraph
205
- expect(bq.content![0].type).toBe('paragraph');
205
+ expect(bq.content![0]!.type).toBe('paragraph');
206
206
  });
207
207
 
208
208
  test('should convert empty <blockquote> to blockquote with empty paragraph', () => {
209
209
  const result = htmlToTiptap('<blockquote></blockquote>');
210
- const bq = result.content[0];
210
+ const bq = result.content[0]!;
211
211
  expect(bq.type).toBe('blockquote');
212
212
  expect(bq.content!.length).toBe(1);
213
- expect(bq.content![0].type).toBe('paragraph');
213
+ expect(bq.content![0]!.type).toBe('paragraph');
214
214
  });
215
215
 
216
216
  test('should convert <pre><code> to codeBlock', () => {
217
217
  const result = htmlToTiptap('<pre><code>const x = 1;</code></pre>');
218
- const codeBlock = result.content[0];
218
+ const codeBlock = result.content[0]!;
219
219
  expect(codeBlock.type).toBe('codeBlock');
220
- expect(codeBlock.content![0].text).toBe('const x = 1;');
220
+ expect(codeBlock.content![0]!.text).toBe('const x = 1;');
221
221
  });
222
222
 
223
223
  test('should parse language from <code class="language-javascript">', () => {
224
224
  const result = htmlToTiptap('<pre><code class="language-javascript">let y = 2;</code></pre>');
225
- const codeBlock = result.content[0];
225
+ const codeBlock = result.content[0]!;
226
226
  expect(codeBlock.type).toBe('codeBlock');
227
227
  expect(codeBlock.attrs!.language).toBe('javascript');
228
228
  });
229
229
 
230
230
  test('should handle <pre> without <code> child', () => {
231
231
  const result = htmlToTiptap('<pre>Plain preformatted</pre>');
232
- const codeBlock = result.content[0];
232
+ const codeBlock = result.content[0]!;
233
233
  expect(codeBlock.type).toBe('codeBlock');
234
- expect(codeBlock.content![0].text).toBe('Plain preformatted');
234
+ expect(codeBlock.content![0]!.text).toBe('Plain preformatted');
235
235
  });
236
236
 
237
237
  test('should convert <hr> to horizontalRule', () => {
238
238
  const result = htmlToTiptap('<p>Above</p><hr><p>Below</p>');
239
- const hr = result.content[1];
239
+ const hr = result.content[1]!;
240
240
  expect(hr.type).toBe('horizontalRule');
241
241
  });
242
242
 
243
243
  test('should convert <br> to hardBreak', () => {
244
244
  const result = htmlToTiptap('<p>Line 1<br>Line 2</p>');
245
- const p = result.content[0];
245
+ const p = result.content[0]!;
246
246
  expect(p.type).toBe('paragraph');
247
247
  // Should contain text, hardBreak, text
248
248
  const types = p.content!.map((n) => n.type);
@@ -280,7 +280,7 @@ describe('htmlToTiptap', () => {
280
280
  describe('inline marks', () => {
281
281
  test('should convert <strong> to bold mark', () => {
282
282
  const result = htmlToTiptap('<p><strong>Bold text</strong></p>');
283
- const textNode = result.content[0].content![0];
283
+ const textNode = result.content[0]!.content![0]!;
284
284
  expect(textNode.text).toBe('Bold text');
285
285
  expect(textNode.marks).toBeDefined();
286
286
  expect(textNode.marks!.some((m) => m.type === 'bold')).toBe(true);
@@ -288,51 +288,51 @@ describe('htmlToTiptap', () => {
288
288
 
289
289
  test('should convert <b> to bold mark', () => {
290
290
  const result = htmlToTiptap('<p><b>Bold text</b></p>');
291
- const textNode = result.content[0].content![0];
291
+ const textNode = result.content[0]!.content![0]!;
292
292
  expect(textNode.marks!.some((m) => m.type === 'bold')).toBe(true);
293
293
  });
294
294
 
295
295
  test('should convert <em> to italic mark', () => {
296
296
  const result = htmlToTiptap('<p><em>Italic text</em></p>');
297
- const textNode = result.content[0].content![0];
297
+ const textNode = result.content[0]!.content![0]!;
298
298
  expect(textNode.text).toBe('Italic text');
299
299
  expect(textNode.marks!.some((m) => m.type === 'italic')).toBe(true);
300
300
  });
301
301
 
302
302
  test('should convert <i> to italic mark', () => {
303
303
  const result = htmlToTiptap('<p><i>Italic text</i></p>');
304
- const textNode = result.content[0].content![0];
304
+ const textNode = result.content[0]!.content![0]!;
305
305
  expect(textNode.marks!.some((m) => m.type === 'italic')).toBe(true);
306
306
  });
307
307
 
308
308
  test('should convert <u> to underline mark', () => {
309
309
  const result = htmlToTiptap('<p><u>Underlined</u></p>');
310
- const textNode = result.content[0].content![0];
310
+ const textNode = result.content[0]!.content![0]!;
311
311
  expect(textNode.marks!.some((m) => m.type === 'underline')).toBe(true);
312
312
  });
313
313
 
314
314
  test('should convert <s> to strike mark', () => {
315
315
  const result = htmlToTiptap('<p><s>Struck</s></p>');
316
- const textNode = result.content[0].content![0];
316
+ const textNode = result.content[0]!.content![0]!;
317
317
  expect(textNode.marks!.some((m) => m.type === 'strike')).toBe(true);
318
318
  });
319
319
 
320
320
  test('should convert <del> to strike mark', () => {
321
321
  const result = htmlToTiptap('<p><del>Deleted</del></p>');
322
- const textNode = result.content[0].content![0];
322
+ const textNode = result.content[0]!.content![0]!;
323
323
  expect(textNode.marks!.some((m) => m.type === 'strike')).toBe(true);
324
324
  });
325
325
 
326
326
  test('should convert <code> to code mark', () => {
327
327
  const result = htmlToTiptap('<p><code>inline code</code></p>');
328
- const textNode = result.content[0].content![0];
328
+ const textNode = result.content[0]!.content![0]!;
329
329
  expect(textNode.text).toBe('inline code');
330
330
  expect(textNode.marks!.some((m) => m.type === 'code')).toBe(true);
331
331
  });
332
332
 
333
333
  test('should convert <a> to text with link mark', () => {
334
334
  const result = htmlToTiptap('<p><a href="https://example.com">Click here</a></p>');
335
- const textNode = result.content[0].content![0];
335
+ const textNode = result.content[0]!.content![0]!;
336
336
  expect(textNode.text).toBe('Click here');
337
337
  const linkMark = textNode.marks!.find((m) => m.type === 'link');
338
338
  expect(linkMark).toBeDefined();
@@ -341,7 +341,7 @@ describe('htmlToTiptap', () => {
341
341
 
342
342
  test('should convert <a> with target and rel attributes', () => {
343
343
  const result = htmlToTiptap('<p><a href="/page" target="_blank" rel="noopener">Link</a></p>');
344
- const textNode = result.content[0].content![0];
344
+ const textNode = result.content[0]!.content![0]!;
345
345
  const linkMark = textNode.marks!.find((m) => m.type === 'link');
346
346
  expect(linkMark!.attrs!.href).toBe('/page');
347
347
  expect(linkMark!.attrs!.target).toBe('_blank');
@@ -355,7 +355,7 @@ describe('htmlToTiptap', () => {
355
355
  describe('nested marks', () => {
356
356
  test('should apply both bold and italic marks for <strong><em>', () => {
357
357
  const result = htmlToTiptap('<p><strong><em>Bold and italic</em></strong></p>');
358
- const textNode = result.content[0].content![0];
358
+ const textNode = result.content[0]!.content![0]!;
359
359
  expect(textNode.text).toBe('Bold and italic');
360
360
  expect(textNode.marks!.length).toBeGreaterThanOrEqual(2);
361
361
  const markTypes = textNode.marks!.map((m) => m.type);
@@ -365,7 +365,7 @@ describe('htmlToTiptap', () => {
365
365
 
366
366
  test('should apply bold and italic for <em><strong> (reverse nesting)', () => {
367
367
  const result = htmlToTiptap('<p><em><strong>Italic and bold</strong></em></p>');
368
- const textNode = result.content[0].content![0];
368
+ const textNode = result.content[0]!.content![0]!;
369
369
  const markTypes = textNode.marks!.map((m) => m.type);
370
370
  expect(markTypes).toContain('bold');
371
371
  expect(markTypes).toContain('italic');
@@ -373,7 +373,7 @@ describe('htmlToTiptap', () => {
373
373
 
374
374
  test('should handle bold inside link', () => {
375
375
  const result = htmlToTiptap('<p><a href="/url"><strong>Bold link</strong></a></p>');
376
- const textNode = result.content[0].content![0];
376
+ const textNode = result.content[0]!.content![0]!;
377
377
  expect(textNode.text).toBe('Bold link');
378
378
  const markTypes = textNode.marks!.map((m) => m.type);
379
379
  expect(markTypes).toContain('bold');
@@ -387,7 +387,7 @@ describe('htmlToTiptap', () => {
387
387
  describe('span handling', () => {
388
388
  test('should convert <span data-meno-span="custom"> to textStyle mark', () => {
389
389
  const result = htmlToTiptap('<p><span data-meno-span="custom">Styled</span></p>');
390
- const textNode = result.content[0].content![0];
390
+ const textNode = result.content[0]!.content![0]!;
391
391
  expect(textNode.text).toBe('Styled');
392
392
  const styleMark = textNode.marks!.find((m) => m.type === 'textStyle');
393
393
  expect(styleMark).toBeDefined();
@@ -396,7 +396,7 @@ describe('htmlToTiptap', () => {
396
396
 
397
397
  test('should pass through <span> without special attrs as just children', () => {
398
398
  const result = htmlToTiptap('<p><span>Plain span</span></p>');
399
- const textNode = result.content[0].content![0];
399
+ const textNode = result.content[0]!.content![0]!;
400
400
  expect(textNode.type).toBe('text');
401
401
  expect(textNode.text).toBe('Plain span');
402
402
  // Should not have any marks (or at least no textStyle mark)
@@ -406,7 +406,7 @@ describe('htmlToTiptap', () => {
406
406
 
407
407
  test('should handle <span> with style but no data-meno-span (returns children)', () => {
408
408
  const result = htmlToTiptap('<p><span style="color: red">Red</span></p>');
409
- const textNode = result.content[0].content![0];
409
+ const textNode = result.content[0]!.content![0]!;
410
410
  expect(textNode.text).toBe('Red');
411
411
  // Currently the implementation returns children without marks for styled spans
412
412
  const hasTextStyle = textNode.marks?.some((m) => m.type === 'textStyle');
@@ -435,15 +435,15 @@ describe('htmlToTiptap', () => {
435
435
  expect(table!.content!.length).toBe(2); // 2 rows
436
436
 
437
437
  // First row should have tableHeader cells
438
- const headerRow = table!.content![0];
438
+ const headerRow = table!.content![0]!;
439
439
  expect(headerRow.type).toBe('tableRow');
440
- expect(headerRow.content![0].type).toBe('tableHeader');
441
- expect(headerRow.content![1].type).toBe('tableHeader');
440
+ expect(headerRow.content![0]!.type).toBe('tableHeader');
441
+ expect(headerRow.content![1]!.type).toBe('tableHeader');
442
442
 
443
443
  // Second row should have tableCell cells
444
- const bodyRow = table!.content![1];
444
+ const bodyRow = table!.content![1]!;
445
445
  expect(bodyRow.type).toBe('tableRow');
446
- expect(bodyRow.content![0].type).toBe('tableCell');
446
+ expect(bodyRow.content![0]!.type).toBe('tableCell');
447
447
  });
448
448
 
449
449
  test('should handle table cells with colspan and rowspan', () => {
@@ -461,9 +461,9 @@ describe('htmlToTiptap', () => {
461
461
  const table = findNode(result, 'table');
462
462
  expect(table).toBeDefined();
463
463
  expect(table!.content!.length).toBe(1);
464
- const row = table!.content![0];
464
+ const row = table!.content![0]!;
465
465
  expect(row.type).toBe('tableRow');
466
- expect(row.content![0].type).toBe('tableCell');
466
+ expect(row.content![0]!.type).toBe('tableCell');
467
467
  });
468
468
 
469
469
  test('should give empty table cells an empty paragraph', () => {
@@ -472,7 +472,7 @@ describe('htmlToTiptap', () => {
472
472
  const cell = findNode(result, 'tableCell');
473
473
  expect(cell).toBeDefined();
474
474
  expect(cell!.content!.length).toBe(1);
475
- expect(cell!.content![0].type).toBe('paragraph');
475
+ expect(cell!.content![0]!.type).toBe('paragraph');
476
476
  });
477
477
 
478
478
  test('should handle <th> cells in thead as tableHeader', () => {
@@ -498,33 +498,33 @@ describe('htmlToTiptap', () => {
498
498
  test('should parse <div> children without wrapping node', () => {
499
499
  const result = htmlToTiptap('<div><p>Inside div</p></div>');
500
500
  // The div should not produce a wrapping node; its children (the <p>) should be at top level
501
- expect(result.content[0].type).toBe('paragraph');
502
- 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');
503
503
  });
504
504
 
505
505
  test('should parse <article> children without wrapping node', () => {
506
506
  const result = htmlToTiptap('<article><p>Article content</p></article>');
507
- expect(result.content[0].type).toBe('paragraph');
507
+ expect(result.content[0]!.type).toBe('paragraph');
508
508
  });
509
509
 
510
510
  test('should parse <section> children without wrapping node', () => {
511
511
  const result = htmlToTiptap('<section><p>Section content</p></section>');
512
- expect(result.content[0].type).toBe('paragraph');
512
+ expect(result.content[0]!.type).toBe('paragraph');
513
513
  });
514
514
 
515
515
  test('should parse <main> children without wrapping node', () => {
516
516
  const result = htmlToTiptap('<main><p>Main content</p></main>');
517
- expect(result.content[0].type).toBe('paragraph');
517
+ expect(result.content[0]!.type).toBe('paragraph');
518
518
  });
519
519
 
520
520
  test('should parse <header> children without wrapping node', () => {
521
521
  const result = htmlToTiptap('<header><h1>Title</h1></header>');
522
- expect(result.content[0].type).toBe('heading');
522
+ expect(result.content[0]!.type).toBe('heading');
523
523
  });
524
524
 
525
525
  test('should parse <footer> children without wrapping node', () => {
526
526
  const result = htmlToTiptap('<footer><p>Footer</p></footer>');
527
- expect(result.content[0].type).toBe('paragraph');
527
+ expect(result.content[0]!.type).toBe('paragraph');
528
528
  });
529
529
  });
530
530
 
@@ -535,7 +535,7 @@ describe('htmlToTiptap', () => {
535
535
  test('should wrap top-level text in a paragraph', () => {
536
536
  const result = htmlToTiptap('Just text');
537
537
  expect(result.content.length).toBe(1);
538
- expect(result.content[0].type).toBe('paragraph');
538
+ expect(result.content[0]!.type).toBe('paragraph');
539
539
  });
540
540
 
541
541
  test('should wrap mixed inline and block siblings correctly', () => {
@@ -543,21 +543,21 @@ describe('htmlToTiptap', () => {
543
543
  const result = htmlToTiptap('Before<p>Block</p>After');
544
544
  // 'Before' should be wrapped in a paragraph, then the <p>, then 'After' in a paragraph
545
545
  expect(result.content.length).toBe(3);
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');
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');
552
552
  });
553
553
 
554
554
  test('should group consecutive inline elements into one paragraph', () => {
555
555
  const result = htmlToTiptap('<strong>Bold</strong> and <em>italic</em>');
556
556
  // All inline content should end up in a single paragraph
557
557
  expect(result.content.length).toBe(1);
558
- expect(result.content[0].type).toBe('paragraph');
558
+ expect(result.content[0]!.type).toBe('paragraph');
559
559
  // Should have multiple text nodes inside
560
- expect(result.content[0].content!.length).toBeGreaterThanOrEqual(2);
560
+ expect(result.content[0]!.content!.length).toBeGreaterThanOrEqual(2);
561
561
  });
562
562
  });
563
563
 
@@ -568,27 +568,27 @@ describe('htmlToTiptap', () => {
568
568
  test('should convert multiple paragraphs', () => {
569
569
  const result = htmlToTiptap('<p>First</p><p>Second</p><p>Third</p>');
570
570
  expect(result.content.length).toBe(3);
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');
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');
574
574
  });
575
575
 
576
576
  test('should convert paragraph with mixed text and marks', () => {
577
577
  const result = htmlToTiptap('<p>Normal <strong>bold</strong> normal</p>');
578
- const p = result.content[0];
578
+ const p = result.content[0]!;
579
579
  expect(p.type).toBe('paragraph');
580
580
  expect(p.content!.length).toBe(3);
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');
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');
586
586
  });
587
587
 
588
588
  test('should handle empty paragraph', () => {
589
589
  const result = htmlToTiptap('<p></p>');
590
590
  expect(result.content.length).toBe(1);
591
- const p = result.content[0];
591
+ const p = result.content[0]!;
592
592
  expect(p.type).toBe('paragraph');
593
593
  // Empty paragraph should not have content property (or have undefined content)
594
594
  expect(p.content).toBeUndefined();
@@ -597,16 +597,16 @@ describe('htmlToTiptap', () => {
597
597
  test('should handle heading followed by paragraph', () => {
598
598
  const result = htmlToTiptap('<h1>Title</h1><p>Body text</p>');
599
599
  expect(result.content.length).toBe(2);
600
- expect(result.content[0].type).toBe('heading');
601
- expect(result.content[1].type).toBe('paragraph');
600
+ expect(result.content[0]!.type).toBe('heading');
601
+ expect(result.content[1]!.type).toBe('paragraph');
602
602
  });
603
603
 
604
604
  test('should handle blockquote containing paragraph', () => {
605
605
  const result = htmlToTiptap('<blockquote><p>Quoted paragraph</p></blockquote>');
606
- const bq = result.content[0];
606
+ const bq = result.content[0]!;
607
607
  expect(bq.type).toBe('blockquote');
608
- expect(bq.content![0].type).toBe('paragraph');
609
- 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');
610
610
  });
611
611
  });
612
612
 
@@ -617,8 +617,8 @@ describe('htmlToTiptap', () => {
617
617
  test('should parse children of unknown elements', () => {
618
618
  const result = htmlToTiptap('<custom-element><p>Content</p></custom-element>');
619
619
  // Unknown element should have its children parsed
620
- expect(result.content[0].type).toBe('paragraph');
621
- 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');
622
622
  });
623
623
  });
624
624
 
@@ -644,20 +644,20 @@ describe('htmlToTiptap', () => {
644
644
  describe('code block edge cases', () => {
645
645
  test('should handle <pre><code> with no language class', () => {
646
646
  const result = htmlToTiptap('<pre><code>plain code</code></pre>');
647
- const codeBlock = result.content[0];
647
+ const codeBlock = result.content[0]!;
648
648
  expect(codeBlock.type).toBe('codeBlock');
649
649
  expect(codeBlock.attrs?.language).toBeUndefined();
650
650
  });
651
651
 
652
652
  test('should handle <pre><code class="language-python">', () => {
653
653
  const result = htmlToTiptap('<pre><code class="language-python">print("hi")</code></pre>');
654
- const codeBlock = result.content[0];
654
+ const codeBlock = result.content[0]!;
655
655
  expect(codeBlock.attrs!.language).toBe('python');
656
656
  });
657
657
 
658
658
  test('should handle empty code block', () => {
659
659
  const result = htmlToTiptap('<pre><code></code></pre>');
660
- const codeBlock = result.content[0];
660
+ const codeBlock = result.content[0]!;
661
661
  expect(codeBlock.type).toBe('codeBlock');
662
662
  expect(codeBlock.content).toEqual([]);
663
663
  });
@@ -689,7 +689,7 @@ describe('htmlToTiptap', () => {
689
689
  describe('link edge cases', () => {
690
690
  test('should handle <a> without href', () => {
691
691
  const result = htmlToTiptap('<p><a>No href</a></p>');
692
- const textNode = result.content[0].content![0];
692
+ const textNode = result.content[0]!.content![0]!;
693
693
  const linkMark = textNode.marks!.find((m) => m.type === 'link');
694
694
  expect(linkMark).toBeDefined();
695
695
  expect(linkMark!.attrs!.href).toBe('');
@@ -697,7 +697,7 @@ describe('htmlToTiptap', () => {
697
697
 
698
698
  test('should handle <a> with only href (no target or rel)', () => {
699
699
  const result = htmlToTiptap('<p><a href="/page">Link</a></p>');
700
- const textNode = result.content[0].content![0];
700
+ const textNode = result.content[0]!.content![0]!;
701
701
  const linkMark = textNode.marks!.find((m) => m.type === 'link');
702
702
  expect(linkMark!.attrs!.href).toBe('/page');
703
703
  expect(linkMark!.attrs!.target).toBeUndefined();
@@ -719,9 +719,9 @@ describe('htmlToTiptap', () => {
719
719
  test('should handle <hr> between paragraphs', () => {
720
720
  const result = htmlToTiptap('<p>Above</p><hr><p>Below</p>');
721
721
  expect(result.content.length).toBe(3);
722
- expect(result.content[0].type).toBe('paragraph');
723
- expect(result.content[1].type).toBe('horizontalRule');
724
- 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');
725
725
  });
726
726
  });
727
727
 
@@ -732,9 +732,9 @@ describe('htmlToTiptap', () => {
732
732
  test('should handle nested <ul> inside <li>', () => {
733
733
  const html = '<ul><li>Parent<ul><li>Child</li></ul></li></ul>';
734
734
  const result = htmlToTiptap(html);
735
- const outerList = result.content[0];
735
+ const outerList = result.content[0]!;
736
736
  expect(outerList.type).toBe('bulletList');
737
- const listItem = outerList.content![0];
737
+ const listItem = outerList.content![0]!;
738
738
  expect(listItem.type).toBe('listItem');
739
739
  // The list item should contain both a paragraph (for "Parent") and a nested bulletList
740
740
  const nestedList = listItem.content!.find((n) => n.type === 'bulletList');
@@ -748,7 +748,7 @@ describe('htmlToTiptap', () => {
748
748
  describe('whitespace handling', () => {
749
749
  test('should preserve meaningful whitespace in text', () => {
750
750
  const result = htmlToTiptap('<p>Hello World</p>');
751
- expect(result.content[0].content![0].text).toBe('Hello World');
751
+ expect(result.content[0]!.content![0]!.text).toBe('Hello World');
752
752
  });
753
753
  });
754
754
 
@@ -763,10 +763,10 @@ describe('htmlToTiptap', () => {
763
763
  const result = htmlToTiptap('<p>Hello <strong>world</strong></p>');
764
764
  expect(result.type).toBe('doc');
765
765
  expect(result.content.length).toBe(1);
766
- expect(result.content[0].type).toBe('paragraph');
766
+ expect(result.content[0]!.type).toBe('paragraph');
767
767
  // Should strip HTML tags and return plain text
768
- expect(result.content[0].content![0].type).toBe('text');
769
- 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');
770
770
  } finally {
771
771
  (globalThis as any).DOMParser = originalDOMParser;
772
772
  }
@@ -777,7 +777,7 @@ describe('htmlToTiptap', () => {
777
777
  delete (globalThis as any).DOMParser;
778
778
  try {
779
779
  const result = htmlToTiptap('Just plain text');
780
- expect(result.content[0].content![0].text).toBe('Just plain text');
780
+ expect(result.content[0]!.content![0]!.text).toBe('Just plain text');
781
781
  } finally {
782
782
  (globalThis as any).DOMParser = originalDOMParser;
783
783
  }
@@ -790,7 +790,7 @@ describe('htmlToTiptap', () => {
790
790
  describe('non-element node types', () => {
791
791
  test('should ignore HTML comments', () => {
792
792
  const result = htmlToTiptap('<p>Before<!-- comment -->After</p>');
793
- const p = result.content[0];
793
+ const p = result.content[0]!;
794
794
  expect(p.type).toBe('paragraph');
795
795
  // Comment node should be ignored, only text nodes remain
796
796
  const textNodes = p.content!.filter((n) => n.type === 'text');
@@ -807,7 +807,7 @@ describe('htmlToTiptap', () => {
807
807
  describe('table elements parsed standalone via parseNode', () => {
808
808
  test('should handle <strike> tag as strike mark', () => {
809
809
  const result = htmlToTiptap('<p><strike>Strikethrough</strike></p>');
810
- const textNode = result.content[0].content![0];
810
+ const textNode = result.content[0]!.content![0]!;
811
811
  expect(textNode.marks!.some((m) => m.type === 'strike')).toBe(true);
812
812
  });
813
813
 
@@ -850,7 +850,7 @@ describe('htmlToTiptap', () => {
850
850
  test('should apply mark recursively to nested inline content', () => {
851
851
  // <strong> wrapping a <br> and text - hardBreak is a non-text node
852
852
  const result = htmlToTiptap('<p><strong>Before<br>After</strong></p>');
853
- const p = result.content[0];
853
+ const p = result.content[0]!;
854
854
  expect(p.type).toBe('paragraph');
855
855
  // Should have text nodes with bold mark and a hardBreak
856
856
  const boldTexts = p.content!.filter((n) => n.type === 'text' && n.marks?.some((m) => m.type === 'bold'));
@@ -863,8 +863,8 @@ describe('htmlToTiptap', () => {
863
863
  // but is not a text node itself. However, parseLink returns text nodes with link mark,
864
864
  // so the bold mark is applied to text nodes that already have marks.
865
865
  const result = htmlToTiptap('<p><strong><a href="/url">Link text</a></strong></p>');
866
- const p = result.content[0];
867
- const textNode = p.content![0];
866
+ const p = result.content[0]!;
867
+ const textNode = p.content![0]!;
868
868
  expect(textNode.type).toBe('text');
869
869
  expect(textNode.text).toBe('Link text');
870
870
  const markTypes = textNode.marks!.map((m) => m.type);
@@ -881,7 +881,7 @@ describe('htmlToTiptap', () => {
881
881
  // This should trigger the 'tr' case in parseNode switch (line 207-208)
882
882
  const result = htmlToTiptap('<div><tr><td>Cell</td></tr></div>');
883
883
  // The tr is parsed via parseTableRow -> produces tableRow
884
- const tableRow = findNode(result, 'tableRow');
884
+ const _tableRow = findNode(result, 'tableRow');
885
885
  // Depending on happy-dom's handling, the <tr> may or may not survive
886
886
  // But parseNode should handle it
887
887
  expect(result.type).toBe('doc');
@@ -906,7 +906,7 @@ describe('htmlToTiptap', () => {
906
906
  describe('empty heading', () => {
907
907
  test('should handle empty heading', () => {
908
908
  const result = htmlToTiptap('<h1></h1>');
909
- const heading = result.content[0];
909
+ const heading = result.content[0]!;
910
910
  expect(heading.type).toBe('heading');
911
911
  expect(heading.attrs!.level).toBe(1);
912
912
  expect(heading.content).toBeUndefined();
@@ -951,7 +951,7 @@ describe('htmlToTiptap', () => {
951
951
  const result = htmlToTiptap('<div><li>Item outside list</li></div>');
952
952
  const listItem = findNode(result, 'listItem');
953
953
  expect(listItem).toBeDefined();
954
- expect(listItem!.content![0].type).toBe('paragraph');
954
+ expect(listItem!.content![0]!.type).toBe('paragraph');
955
955
  });
956
956
  });
957
957
  });