|
1 | 1 | import { describe, it, expect } from 'vitest' |
2 | | -import { contentFromMarkdownDocument, documentFromMarkdownContent } from '../../src/runtime/utils/document' |
| 2 | +import { areDocumentsEqual, contentFromMarkdownDocument, documentFromMarkdownContent, ensureComarkBody, isDocumentMatchingContent } from '../../src/runtime/utils/document' |
| 3 | +import { markdownRootFromComarkTree } from '../../src/runtime/utils/document/legacy' |
| 4 | +import type { ComarkTree } from 'comark' |
| 5 | +import type { DatabaseItem } from 'nuxt-studio/app' |
| 6 | + |
| 7 | +// Downgrades a comark document to the minimark body older Studio builds persisted. |
| 8 | +async function legacyBodyOf(id: string, content: string): Promise<{ comark: DatabaseItem, legacy: DatabaseItem }> { |
| 9 | + const comark = await documentFromMarkdownContent(id, content) as DatabaseItem |
| 10 | + const legacy = { ...comark, body: markdownRootFromComarkTree(comark.body as unknown as ComarkTree) as unknown } as DatabaseItem |
| 11 | + return { comark, legacy } |
| 12 | +} |
3 | 13 |
|
4 | 14 | describe('Document - Markdown roundtrip Integration Tests', () => { |
5 | 15 | describe('code block with component inside named slot', () => { |
@@ -38,4 +48,103 @@ hello |
38 | 48 | expect(markdown!.trim()).toBe(content.trim()) |
39 | 49 | }) |
40 | 50 | }) |
| 51 | + |
| 52 | + describe('non-comark stored bodies (drafts persisted by older Studio builds)', () => { |
| 53 | + const content = `# Hello |
| 54 | +
|
| 55 | +A paragraph with **bold** text. |
| 56 | +` |
| 57 | + |
| 58 | + it('renders a legacy minimark body (no nodes) without throwing', async () => { |
| 59 | + const { legacy } = await legacyBodyOf('content:legacy.md', content) |
| 60 | + |
| 61 | + const markdown = await contentFromMarkdownDocument(legacy) |
| 62 | + |
| 63 | + expect(markdown!.trim()).toBe(content.trim()) |
| 64 | + }) |
| 65 | + |
| 66 | + it('renders a raw MarkdownRoot body ({type:"root", children}, no nodes) without throwing', async () => { |
| 67 | + const markdownRootBody = { |
| 68 | + type: 'root', |
| 69 | + children: [ |
| 70 | + { type: 'element', tag: 'h1', props: {}, children: [{ type: 'text', value: 'Hello' }] }, |
| 71 | + { type: 'element', tag: 'p', props: {}, children: [ |
| 72 | + { type: 'text', value: 'A paragraph with ' }, |
| 73 | + { type: 'element', tag: 'strong', props: {}, children: [{ type: 'text', value: 'bold' }] }, |
| 74 | + { type: 'text', value: ' text.' }, |
| 75 | + ] }, |
| 76 | + ], |
| 77 | + } |
| 78 | + const document = { id: 'content:legacy.md', extension: 'md', stem: 'legacy', meta: {}, body: markdownRootBody } as unknown as DatabaseItem |
| 79 | + |
| 80 | + const markdown = await contentFromMarkdownDocument(document) |
| 81 | + |
| 82 | + expect(markdown!.trim()).toBe(content.trim()) |
| 83 | + }) |
| 84 | + |
| 85 | + it('renders a legacy body containing an MDC component without throwing', async () => { |
| 86 | + const componentContent = '::alert{type="info"}\nhello\n::\n' |
| 87 | + const { legacy } = await legacyBodyOf('content:legacy.md', componentContent) |
| 88 | + |
| 89 | + const markdown = await contentFromMarkdownDocument(legacy) |
| 90 | + |
| 91 | + expect(markdown!.trim()).toBe(componentContent.trim()) |
| 92 | + }) |
| 93 | + |
| 94 | + it('returns an empty document for a markdown body that is missing entirely', async () => { |
| 95 | + const document = { id: 'content:empty.md', extension: 'md', stem: 'empty', meta: {} } as DatabaseItem |
| 96 | + |
| 97 | + const markdown = await contentFromMarkdownDocument(document) |
| 98 | + |
| 99 | + expect(markdown).toBe('\n') |
| 100 | + }) |
| 101 | + |
| 102 | + it('treats a legacy body and its comark equivalent as equal', async () => { |
| 103 | + const { comark, legacy } = await legacyBodyOf('content:page.md', content) |
| 104 | + |
| 105 | + expect(await areDocumentsEqual(legacy, comark)).toBe(true) |
| 106 | + }) |
| 107 | + |
| 108 | + it('matches a legacy body against its own raw markdown content', async () => { |
| 109 | + const { legacy } = await legacyBodyOf('content:legacy.md', content) |
| 110 | + |
| 111 | + expect(await isDocumentMatchingContent(content, legacy)).toBe(true) |
| 112 | + }) |
| 113 | + }) |
| 114 | + |
| 115 | + describe('ensureComarkBody', () => { |
| 116 | + it('upgrades a legacy body to a ComarkTree (adds .nodes)', async () => { |
| 117 | + const { legacy } = await legacyBodyOf('content:legacy.md', '# Hello\n') |
| 118 | + |
| 119 | + const upgraded = ensureComarkBody(legacy) |
| 120 | + |
| 121 | + expect(Array.isArray((upgraded.body as ComarkTree).nodes)).toBe(true) |
| 122 | + }) |
| 123 | + |
| 124 | + it('returns an already-comark document untouched (no re-parse)', async () => { |
| 125 | + const comark = await documentFromMarkdownContent('content:comark.md', '# Hello\n') as DatabaseItem |
| 126 | + |
| 127 | + expect(ensureComarkBody(comark)).toBe(comark) |
| 128 | + }) |
| 129 | + |
| 130 | + it('returns a non-markdown document untouched', () => { |
| 131 | + const yamlDoc = { id: 'content:data.yml', extension: 'yml', body: { some: 'value' } } as unknown as DatabaseItem |
| 132 | + |
| 133 | + expect(ensureComarkBody(yamlDoc)).toBe(yamlDoc) |
| 134 | + }) |
| 135 | + |
| 136 | + it('returns a bodyless document untouched', () => { |
| 137 | + const document = { id: 'content:empty.md', extension: 'md' } as DatabaseItem |
| 138 | + |
| 139 | + expect(ensureComarkBody(document)).toBe(document) |
| 140 | + }) |
| 141 | + |
| 142 | + it('is idempotent', async () => { |
| 143 | + const { legacy } = await legacyBodyOf('content:legacy.md', '# Hello\n') |
| 144 | + |
| 145 | + const once = ensureComarkBody(legacy) |
| 146 | + |
| 147 | + expect(ensureComarkBody(once)).toBe(once) |
| 148 | + }) |
| 149 | + }) |
41 | 150 | }) |
0 commit comments