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.
- package/bin/cli.ts +19 -205
- package/build-astro.ts +31 -27
- package/dist/bin/cli.js +15 -154
- package/dist/bin/cli.js.map +2 -2
- package/dist/chunks/{chunk-7HWQUVTU.js → chunk-2IIQK7T3.js} +1353 -347
- package/dist/chunks/chunk-2IIQK7T3.js.map +7 -0
- package/dist/chunks/{chunk-3XER4E5W.js → chunk-4ZRU52J2.js} +6 -5
- package/dist/chunks/{chunk-3XER4E5W.js.map → chunk-4ZRU52J2.js.map} +2 -2
- package/dist/chunks/{chunk-NVRBTSQG.js → chunk-5X4HCN7J.js} +4 -4
- package/dist/chunks/chunk-5X4HCN7J.js.map +7 -0
- package/dist/chunks/{chunk-FZITJSSS.js → chunk-7CCVOL5M.js} +14 -14
- package/dist/chunks/chunk-7CCVOL5M.js.map +7 -0
- package/dist/chunks/{chunk-HIZMY3EP.js → chunk-AR7LHIFY.js} +8 -2
- package/dist/chunks/chunk-AR7LHIFY.js.map +7 -0
- package/dist/chunks/{chunk-AE3QK5QW.js → chunk-NUP7H7D3.js} +31 -26
- package/dist/chunks/chunk-NUP7H7D3.js.map +7 -0
- package/dist/chunks/{chunk-5ETZFREW.js → chunk-QWTQZHG3.js} +186 -79
- package/dist/chunks/chunk-QWTQZHG3.js.map +7 -0
- package/dist/chunks/{chunk-STDY3OVM.js → chunk-XTKNX4FW.js} +3 -3
- package/dist/chunks/{chunk-STDY3OVM.js.map → chunk-XTKNX4FW.js.map} +2 -2
- package/dist/chunks/{fs-ZI5JEU7V.js → fs-G3ANWEFP.js} +2 -2
- package/dist/lib/client/index.js +381 -159
- package/dist/lib/client/index.js.map +3 -3
- package/dist/lib/server/index.js +14345 -6265
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +292 -87
- package/dist/lib/shared/index.js.map +4 -4
- package/dist/lib/shared/richtext/index.js +1 -1
- package/dist/lib/test-utils/index.js +27 -21
- package/dist/lib/test-utils/index.js.map +2 -2
- package/lib/client/ErrorBoundary.test.tsx +147 -90
- package/lib/client/ErrorBoundary.tsx +8 -5
- package/lib/client/componentRegistry.ts +0 -1
- package/lib/client/core/ComponentBuilder.test.ts +150 -0
- package/lib/client/core/ComponentBuilder.ts +112 -12
- package/lib/client/core/ComponentRenderer.test.tsx +1 -2
- package/lib/client/core/builders/embedBuilder.ts +1 -1
- package/lib/client/core/builders/linkBuilder.ts +2 -2
- package/lib/client/core/builders/linkNodeBuilder.ts +1 -1
- package/lib/client/core/builders/listBuilder.ts +41 -4
- package/lib/client/core/builders/localeListBuilder.ts +2 -2
- package/lib/client/core/cmsTemplateProcessor.ts +1 -2
- package/lib/client/hmr/HMRManager.tsx +39 -30
- package/lib/client/hmrCssReload.ts +35 -6
- package/lib/client/hmrWebSocket.ts +5 -5
- package/lib/client/hooks/useColorVariables.ts +12 -9
- package/lib/client/hooks/usePropertyAutocomplete.ts +1 -1
- package/lib/client/hooks/useVariables.ts +3 -1
- package/lib/client/meno-filter/MenoFilter.test.ts +28 -28
- package/lib/client/meno-filter/MenoFilter.ts +9 -6
- package/lib/client/meno-filter/bindings.ts +3 -3
- package/lib/client/meno-filter/init.ts +3 -3
- package/lib/client/meno-filter/renderer.ts +8 -4
- package/lib/client/meno-filter/ui.ts +1 -1
- package/lib/client/meno-filter/updates.ts +3 -2
- package/lib/client/meno-filter/utils.ts +1 -1
- package/lib/client/navigation.test.ts +40 -44
- package/lib/client/responsiveStyleResolver.knownTokens.test.ts +71 -0
- package/lib/client/responsiveStyleResolver.test.ts +5 -5
- package/lib/client/responsiveStyleResolver.ts +61 -4
- package/lib/client/routing/RouteLoader.test.ts +1 -1
- package/lib/client/routing/RouteLoader.ts +8 -3
- package/lib/client/routing/Router.tsx +181 -152
- package/lib/client/scripts/ScriptExecutor.ts +3 -3
- package/lib/client/services/PrefetchService.test.ts +1 -1
- package/lib/client/services/PrefetchService.ts +13 -5
- package/lib/client/styleProcessor.ts +9 -3
- package/lib/client/styles/StyleInjector.ts +1 -1
- package/lib/client/styles/UtilityClassCollector.ts +54 -13
- package/lib/client/templateEngine.test.ts +17 -23
- package/lib/client/templateEngine.ts +7 -13
- package/lib/client/theme.test.ts +1 -1
- package/lib/server/__integration__/server-lifecycle.test.ts +3 -1
- package/lib/server/__integration__/static-assets.test.ts +2 -2
- package/lib/server/__integration__/test-helpers.ts +3 -4
- package/lib/server/astro/cmsPageEmitter.ts +4 -5
- package/lib/server/astro/componentEmitter.ts +11 -8
- package/lib/server/astro/nodeToAstro.test.ts +4 -4
- package/lib/server/astro/nodeToAstro.ts +41 -36
- package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +11 -11
- package/lib/server/astro/normalizeOrphanTemplateProps.ts +7 -6
- package/lib/server/astro/pageEmitter.ts +1 -1
- package/lib/server/astro/templateTransformer.ts +3 -3
- package/lib/server/createServer.ts +2 -2
- package/lib/server/cssGenerator.ts +2 -2
- package/lib/server/fileWatcher.test.ts +41 -4
- package/lib/server/fileWatcher.ts +102 -23
- package/lib/server/index.ts +7 -16
- package/lib/server/jsonLoader.test.ts +3 -3
- package/lib/server/jsonLoader.ts +8 -5
- package/lib/server/middleware/cors.test.ts +1 -1
- package/lib/server/middleware/cors.ts +2 -2
- package/lib/server/middleware/errorHandler.test.ts +2 -2
- package/lib/server/middleware/logger.test.ts +3 -3
- package/lib/server/middleware/logger.ts +7 -7
- package/lib/server/migrateTemplates.ts +3 -3
- package/lib/server/projectContext.ts +1 -1
- package/lib/server/providers/fileSystemCMSProvider.test.ts +18 -4
- package/lib/server/providers/fileSystemCMSProvider.ts +27 -18
- package/lib/server/routes/api/cms.ts +1 -1
- package/lib/server/routes/api/colors.test.ts +2 -2
- package/lib/server/routes/api/components.ts +1 -1
- package/lib/server/routes/api/config.ts +3 -3
- package/lib/server/routes/api/core-routes.ts +2 -2
- package/lib/server/routes/api/enums.test.ts +2 -2
- package/lib/server/routes/api/functions.ts +1 -1
- package/lib/server/routes/api/variables.test.ts +2 -2
- package/lib/server/routes/index.ts +2 -2
- package/lib/server/routes/pages.ts +1 -2
- package/lib/server/routes/static.ts +1 -2
- package/lib/server/runtime/bundler.ts +65 -43
- package/lib/server/runtime/fs.ts +13 -13
- package/lib/server/runtime/httpServer.ts +3 -3
- package/lib/server/services/ColorService.test.ts +3 -3
- package/lib/server/services/ColorService.ts +5 -2
- package/lib/server/services/EnumService.test.ts +2 -2
- package/lib/server/services/EnumService.ts +4 -1
- package/lib/server/services/VariableService.test.ts +7 -7
- package/lib/server/services/VariableService.ts +5 -2
- package/lib/server/services/cmsService.test.ts +44 -44
- package/lib/server/services/cmsService.ts +17 -97
- package/lib/server/services/componentService.test.ts +10 -16
- package/lib/server/services/componentService.ts +20 -12
- package/lib/server/services/configService.test.ts +13 -14
- package/lib/server/services/configService.ts +30 -0
- package/lib/server/services/fileWatcherService.ts +2 -2
- package/lib/server/services/pageService.test.ts +7 -7
- package/lib/server/services/pageService.ts +18 -5
- package/lib/server/ssr/attributeBuilder.ts +2 -2
- package/lib/server/ssr/cmsSSRProcessor.ts +1 -2
- package/lib/server/ssr/errorOverlay.ts +1 -1
- package/lib/server/ssr/htmlGenerator.nonce.test.ts +6 -5
- package/lib/server/ssr/htmlGenerator.test.ts +0 -1
- package/lib/server/ssr/htmlGenerator.ts +7 -7
- package/lib/server/ssr/imageMetadata.ts +2 -2
- package/lib/server/ssr/jsCollector.test.ts +5 -5
- package/lib/server/ssr/liveReloadIntegration.test.ts +7 -8
- package/lib/server/ssr/ssrRenderer.branches.test.ts +9 -9
- package/lib/server/ssr/ssrRenderer.test.ts +125 -15
- package/lib/server/ssr/ssrRenderer.ts +206 -38
- package/lib/server/ssrRenderer.test.ts +6 -6
- package/lib/server/utils/jsonLineMapper.ts +7 -7
- package/lib/server/validateStyleCoverage.ts +0 -1
- package/lib/server/websocketManager.ts +12 -0
- package/lib/shared/breakpoints.test.ts +5 -5
- package/lib/shared/cmsQuery.test.ts +81 -0
- package/lib/shared/cmsQuery.ts +92 -0
- package/lib/shared/cmsQueryParser.ts +11 -7
- package/lib/shared/colorConversions.ts +9 -9
- package/lib/shared/colorVariableUtils.test.ts +22 -0
- package/lib/shared/colorVariableUtils.ts +19 -5
- package/lib/shared/constants.ts +4 -0
- package/lib/shared/cssGeneration.test.ts +23 -0
- package/lib/shared/cssGeneration.ts +40 -27
- package/lib/shared/cssProperties.test.ts +64 -64
- package/lib/shared/cssProperties.ts +2 -2
- package/lib/shared/elementClassName.ts +2 -2
- package/lib/shared/errorLogger.test.ts +12 -12
- package/lib/shared/expressionEvaluator.test.ts +27 -1
- package/lib/shared/expressionEvaluator.ts +9 -0
- package/lib/shared/fontLoader.test.ts +13 -13
- package/lib/shared/fontLoader.ts +2 -2
- package/lib/shared/gradientUtils.test.ts +2 -2
- package/lib/shared/gradientUtils.ts +15 -8
- package/lib/shared/hrefRefs.test.ts +2 -2
- package/lib/shared/hrefRefs.ts +1 -1
- package/lib/shared/i18n.test.ts +3 -3
- package/lib/shared/i18n.ts +5 -4
- package/lib/shared/index.ts +23 -0
- package/lib/shared/inlineSvgStyleRules.ts +12 -9
- package/lib/shared/interactiveStyleMappings.test.ts +21 -21
- package/lib/shared/interactiveStyleMappings.ts +2 -8
- package/lib/shared/itemTemplateUtils.test.ts +5 -5
- package/lib/shared/itemTemplateUtils.ts +9 -5
- package/lib/shared/jsonRepair.ts +8 -8
- package/lib/shared/libraryLoader.test.ts +6 -6
- package/lib/shared/linkUtils.ts +1 -1
- package/lib/shared/logger.test.ts +66 -0
- package/lib/shared/logger.ts +94 -0
- package/lib/shared/markdown.ts +25 -0
- package/lib/shared/nodeUtils.test.ts +13 -9
- package/lib/shared/nodeUtils.ts +27 -4
- package/lib/shared/pathSecurity.ts +1 -1
- package/lib/shared/pathUtils.ts +1 -2
- package/lib/shared/paths/PathConverter.ts +3 -3
- package/lib/shared/paths/PathUtils.ts +4 -3
- package/lib/shared/paths/PathValidator.ts +1 -1
- package/lib/shared/permissions.test.ts +168 -0
- package/lib/shared/permissions.ts +162 -0
- package/lib/shared/propResolver.test.ts +1 -1
- package/lib/shared/propResolver.ts +1 -1
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +5 -3
- package/lib/shared/registry/ClientRegistry.ts +0 -1
- package/lib/shared/registry/ComponentRegistry.test.ts +3 -3
- package/lib/shared/registry/ComponentRegistry.ts +6 -1
- package/lib/shared/registry/NodeTypeDefinition.ts +2 -2
- package/lib/shared/registry/SSRRegistry.ts +0 -1
- package/lib/shared/registry/fieldPresets.ts +3 -1
- package/lib/shared/registry/nodeTypes/CustomNodeType.ts +103 -0
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +1 -1
- package/lib/shared/registry/nodeTypes/IslandNodeType.ts +129 -0
- package/lib/shared/registry/nodeTypes/ListNodeType.ts +43 -7
- package/lib/shared/registry/nodeTypes/MarkdownNodeType.ts +78 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +6 -1
- package/lib/shared/registry/nodeTypes/index.ts +16 -1
- package/lib/shared/responsiveScaling.test.ts +1 -3
- package/lib/shared/responsiveScaling.ts +6 -5
- package/lib/shared/responsiveStyleUtils.test.ts +0 -1
- package/lib/shared/richtext/htmlToTiptap.test.ts +116 -116
- package/lib/shared/richtext/htmlToTiptap.ts +1 -1
- package/lib/shared/styleNodeUtils.ts +17 -14
- package/lib/shared/themeDefaults.test.ts +0 -3
- package/lib/shared/tree/PathBuilder.test.ts +1 -7
- package/lib/shared/tree/PathBuilder.ts +15 -13
- package/lib/shared/treePathUtils.test.ts +49 -2
- package/lib/shared/treePathUtils.ts +40 -4
- package/lib/shared/types/api.ts +105 -7
- package/lib/shared/types/cms.test.ts +34 -0
- package/lib/shared/types/cms.ts +65 -13
- package/lib/shared/types/components.ts +24 -0
- package/lib/shared/types/errors.test.ts +1 -1
- package/lib/shared/types/index.ts +23 -0
- package/lib/shared/types/permissions.ts +139 -0
- package/lib/shared/types/variables.ts +3 -3
- package/lib/shared/utilityClassConfig.ts +103 -0
- package/lib/shared/utilityClassMapper.durability.test.ts +117 -0
- package/lib/shared/utilityClassMapper.test.ts +253 -26
- package/lib/shared/utilityClassMapper.ts +160 -29
- package/lib/shared/utilityClassNames.test.ts +38 -0
- package/lib/shared/utilityClassNames.ts +155 -23
- package/lib/shared/validation/index.ts +1 -0
- package/lib/shared/validation/permissionsValidators.test.ts +49 -0
- package/lib/shared/validation/permissionsValidators.ts +49 -0
- package/lib/shared/validation/propValidator.test.ts +1 -1
- package/lib/shared/validation/propValidator.ts +1 -1
- package/lib/shared/validation/schemas.test.ts +41 -0
- package/lib/shared/validation/schemas.ts +159 -13
- package/lib/shared/validation/validators.test.ts +2 -2
- package/lib/shared/viewportUnits.test.ts +13 -0
- package/lib/shared/viewportUnits.ts +12 -5
- package/lib/test-utils/factories/ConsoleMockFactory.ts +7 -7
- package/lib/test-utils/factories/DomMockFactory.ts +10 -4
- package/lib/test-utils/factories/ServerMockFactory.ts +1 -1
- package/lib/test-utils/factories/StoreMockFactory.ts +5 -5
- package/lib/test-utils/mockFactories.ts +1 -1
- package/lib/test-utils/mocks.ts +3 -3
- package/package.json +3 -5
- package/scripts/build-for-publish.mjs +2 -4
- package/scripts/build-meno-filter.ts +3 -3
- package/tsconfig.json +1 -0
- package/vite.config.ts +1 -1
- package/build-next.ts +0 -1374
- package/build-static.test.ts +0 -453
- package/build-static.ts +0 -1072
- package/dist/build-static.js +0 -37
- package/dist/chunks/chunk-2FN4UOVO.js +0 -6465
- package/dist/chunks/chunk-2FN4UOVO.js.map +0 -7
- package/dist/chunks/chunk-5ETZFREW.js.map +0 -7
- package/dist/chunks/chunk-7E4IF5L7.js +0 -245
- package/dist/chunks/chunk-7E4IF5L7.js.map +0 -7
- package/dist/chunks/chunk-7HWQUVTU.js.map +0 -7
- package/dist/chunks/chunk-AE3QK5QW.js.map +0 -7
- package/dist/chunks/chunk-F6KTJYGV.js +0 -322
- package/dist/chunks/chunk-F6KTJYGV.js.map +0 -7
- package/dist/chunks/chunk-FZITJSSS.js.map +0 -7
- package/dist/chunks/chunk-GSYYA5GX.js +0 -77
- package/dist/chunks/chunk-GSYYA5GX.js.map +0 -7
- package/dist/chunks/chunk-HIZMY3EP.js.map +0 -7
- package/dist/chunks/chunk-I2WEGYA7.js +0 -424
- package/dist/chunks/chunk-I2WEGYA7.js.map +0 -7
- package/dist/chunks/chunk-JNO3CNLJ.js +0 -464
- package/dist/chunks/chunk-JNO3CNLJ.js.map +0 -7
- package/dist/chunks/chunk-NVRBTSQG.js.map +0 -7
- package/dist/chunks/chunk-Q4OBWKXG.js +0 -1143
- package/dist/chunks/chunk-Q4OBWKXG.js.map +0 -7
- package/dist/chunks/chunk-QTE32Y53.js +0 -3829
- package/dist/chunks/chunk-QTE32Y53.js.map +0 -7
- package/dist/chunks/configService-PRJZF7Y6.js +0 -14
- package/dist/chunks/configService-PRJZF7Y6.js.map +0 -7
- package/dist/chunks/constants-KIQEYMAM.js +0 -46
- package/dist/chunks/constants-KIQEYMAM.js.map +0 -7
- package/dist/chunks/fs-ZI5JEU7V.js.map +0 -7
- package/dist/entries/server-router.js +0 -62
- package/dist/entries/server-router.js.map +0 -7
- package/entries/server-router.tsx +0 -75
- package/lib/server/providers/fileSystemPageProvider.test.ts +0 -84
- package/lib/server/providers/fileSystemPageProvider.ts +0 -179
- package/lib/server/webflow/buildWebflow.ts +0 -623
- package/lib/server/webflow/index.ts +0 -23
- package/lib/server/webflow/nodeToWebflow.test.ts +0 -3102
- package/lib/server/webflow/nodeToWebflow.ts +0 -2372
- package/lib/server/webflow/styleMapper.test.ts +0 -348
- package/lib/server/webflow/styleMapper.ts +0 -687
- package/lib/server/webflow/templateWrapper.ts +0 -49
- package/lib/server/webflow/types.ts +0 -389
- /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
|
|
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]
|
|
94
|
-
expect(result.content[0]
|
|
95
|
-
expect(result.content[0]
|
|
96
|
-
expect(result.content[0]
|
|
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]
|
|
108
|
-
expect(result.content[0]
|
|
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]
|
|
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]
|
|
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]
|
|
159
|
-
expect(list.content![1]
|
|
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]
|
|
178
|
+
const listItem = result.content[0]!.content![0]!;
|
|
179
179
|
expect(listItem.type).toBe('listItem');
|
|
180
|
-
expect(listItem.content![0]
|
|
181
|
-
expect(listItem.content![0]
|
|
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]
|
|
186
|
+
const listItem = result.content[0]!.content![0]!;
|
|
187
187
|
expect(listItem.type).toBe('listItem');
|
|
188
|
-
expect(listItem.content![0]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
441
|
-
expect(headerRow.content![1]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
502
|
-
expect(result.content[0]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
547
|
-
expect(result.content[0]
|
|
548
|
-
expect(result.content[1]
|
|
549
|
-
expect(result.content[1]
|
|
550
|
-
expect(result.content[2]
|
|
551
|
-
expect(result.content[2]
|
|
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]
|
|
558
|
+
expect(result.content[0]!.type).toBe('paragraph');
|
|
559
559
|
// Should have multiple text nodes inside
|
|
560
|
-
expect(result.content[0]
|
|
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]
|
|
572
|
-
expect(result.content[1]
|
|
573
|
-
expect(result.content[2]
|
|
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]
|
|
582
|
-
expect(p.content![0]
|
|
583
|
-
expect(p.content![1]
|
|
584
|
-
expect(p.content![1]
|
|
585
|
-
expect(p.content![2]
|
|
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]
|
|
601
|
-
expect(result.content[1]
|
|
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]
|
|
609
|
-
expect(bq.content![0]
|
|
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]
|
|
621
|
-
expect(result.content[0]
|
|
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]
|
|
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]
|
|
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]
|
|
723
|
-
expect(result.content[1]
|
|
724
|
-
expect(result.content[2]
|
|
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]
|
|
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]
|
|
766
|
+
expect(result.content[0]!.type).toBe('paragraph');
|
|
767
767
|
// Should strip HTML tags and return plain text
|
|
768
|
-
expect(result.content[0]
|
|
769
|
-
expect(result.content[0]
|
|
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]
|
|
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]
|
|
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
|
|
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]
|
|
954
|
+
expect(listItem!.content![0]!.type).toBe('paragraph');
|
|
955
955
|
});
|
|
956
956
|
});
|
|
957
957
|
});
|