/**
* @vitest-environment node
*/
import { Code } from '@tiptap/extension-code'
import { Document } from '@tiptap/extension-document'
import { Heading } from '@tiptap/extension-heading'
import { Paragraph } from '@tiptap/extension-paragraph'
import { Strike } from '@tiptap/extension-strike'
import { Text } from '@tiptap/extension-text'
import { MarkdownManager } from '@tiptap/markdown'
import { beforeEach, describe, expect, it } from 'vite-plus/test'
describe('MarkdownManager Server-side Parsing', () => {
let manager: MarkdownManager
const basicExtensions = [Document, Paragraph, Text, Strike, Code, Heading]
beforeEach(() => {
manager = new MarkdownManager({ extensions: basicExtensions })
})
it('parses inline code with HTML-like tags and strike marks without error', () => {
const md = '`
`~``'
const doc = manager.parse(md)
expect(doc.type).toBe('doc')
expect(doc.content).toBeDefined()
const paragraph = doc.content![0]
expect(paragraph.type).toBe('paragraph')
expect(paragraph.content).toBeDefined()
// Find text nodes with code marks (inline code)
const codeTextNodes = paragraph.content!.filter(
(n: any) => n.type === 'text' && n.marks?.some((m: any) => m.type === 'code'),
)
expect(codeTextNodes.length).toBeGreaterThan(0)
// At least one code segment should contain an HTML-like tag
const hasHtmlTag = codeTextNodes.some(
(n: any) => n.text && (n.text.includes('') || n.text.includes('')),
)
expect(hasHtmlTag).toBe(true)
// All text content combined should include both HTML tags and the tilde
const allTextNodes = paragraph.content!.filter((n: any) => n.type === 'text')
const allText = allTextNodes.map((n: any) => n.text).join('')
expect(allText).toContain('h1')
expect(allText).toContain('h6')
expect(allText).toMatch(/~/)
})
it('parses inline code with HTML-like tags and double strike marks without error', () => {
const md = '``~~``'
const doc = manager.parse(md)
expect(doc.type).toBe('doc')
expect(doc.content).toBeDefined()
const paragraph = doc.content![0]
expect(paragraph.type).toBe('paragraph')
// Find text nodes with code marks
const codeTextNodes = paragraph.content!.filter(
(n: any) => n.type === 'text' && n.marks?.some((m: any) => m.type === 'code'),
)
expect(codeTextNodes.length).toBeGreaterThan(0)
// At least one code segment should contain an HTML-like tag
const hasHtmlTag = codeTextNodes.some(
(n: any) => n.text && (n.text.includes('') || n.text.includes('')),
)
expect(hasHtmlTag).toBe(true)
// All text should contain both HTML tags
const allTextNodes = paragraph.content!.filter((n: any) => n.type === 'text')
const allText = allTextNodes.map((n: any) => n.text).join('')
expect(allText).toContain('h1')
expect(allText).toContain('h6')
})
it('parses strike marks in normal text', () => {
const md = 'a~b'
const doc = manager.parse(md)
expect(doc.type).toBe('doc')
expect(Array.isArray(doc.content)).toBe(true)
})
it('parses HTML heading tags with strike marks', () => {
const md = '~~test~~
'
const doc = manager.parse(md)
expect(doc.type).toBe('doc')
expect(doc.content).toBeDefined()
const paragraph = doc.content![0]
expect(paragraph.type).toBe('paragraph')
expect(paragraph.content).toBeDefined()
// In Node.js environment, HTML tags should be treated as literal text
const allText = paragraph
.content!.map((n: any) =>
n.text ? n.text : n.content?.map((c: any) => c.text).join('') || '',
)
.join('')
// Should contain the HTML tags as literal text
expect(allText).toContain('')
expect(allText).toContain('
')
// Should contain 'test' text
expect(allText).toContain('test')
})
it('parses multiple inline code segments with HTML-like content and strike', () => {
const md = 'text `` more ~~text~~ and ``'
const doc = manager.parse(md)
expect(doc.type).toBe('doc')
expect(doc.content).toBeDefined()
const paragraph = doc.content![0]
expect(paragraph.type).toBe('paragraph')
// Find text nodes with code marks (inline code)
const codeTextNodes = paragraph.content!.filter(
(n: any) => n.type === 'text' && n.marks?.some((m: any) => m.type === 'code'),
)
expect(codeTextNodes.length).toBeGreaterThanOrEqual(2)
// Code text should contain literal HTML-like tags
expect(codeTextNodes[0].text).toBe('')
expect(codeTextNodes[1].text).toBe('')
// Find text nodes with strike marks
const textNodes = paragraph.content!.filter((n: any) => n.type === 'text')
const strikeTextNode = textNodes.find(
(n: any) =>
n.text && n.text.includes('text') && n.marks?.some((m: any) => m.type === 'strike'),
)
expect(strikeTextNode).toBeDefined()
})
it('preserves unknown angle-bracket placeholders as literal text in server environment', () => {
const md = '\n\nother text after'
const doc = manager.parse(md)
expect(doc.type).toBe('doc')
expect(Array.isArray(doc.content)).toBe(true)
expect(doc.content).toHaveLength(2)
// angle-bracket placeholder preserved as literal text
const firstPara = doc.content![0]
expect(firstPara.type).toBe('paragraph')
expect(firstPara.content).toHaveLength(1)
expect(firstPara.content![0].type).toBe('text')
expect(firstPara.content![0].text).toBe('')
// trailing text after the blank line
const secondPara = doc.content![1]
expect(secondPara.type).toBe('paragraph')
expect(secondPara.content).toHaveLength(1)
expect(secondPara.content![0].type).toBe('text')
expect(secondPara.content![0].text).toBe('other text after')
})
it('parses HTML tags as plain text in server environment', () => {
const md = 'Hello
'
const doc = manager.parse(md)
expect(doc.type).toBe('doc')
expect(doc.content).toBeDefined()
expect(Array.isArray(doc.content)).toBe(true)
// The document should have a paragraph
const paragraph = doc.content![0]
expect(paragraph.type).toBe('paragraph')
expect(paragraph.content).toBeDefined()
// The HTML should be preserved as literal text (not parsed)
const textContent = paragraph.content!.map((n: any) => n.text).join('')
expect(textContent).toContain('Hello
')
})
it('still parses markdown syntax correctly alongside HTML', () => {
const md = 'test
~~world~~'
const doc = manager.parse(md)
expect(doc.type).toBe('doc')
expect(doc.content).toBeDefined()
// Find all text nodes
const textNodes = doc.content!.flatMap((node: any) =>
node.content ? node.content.filter((n: any) => n.type === 'text') : [],
)
// Should have HTML as literal text
const hasHtmlText = textNodes.some((n: any) => n.text && n.text.includes('test
'))
expect(hasHtmlText).toBe(true)
// Should have 'world' text (with or without strike mark depending on parsing)
const hasWorldText = textNodes.some((n: any) => n.text && n.text.includes('world'))
expect(hasWorldText).toBe(true)
})
})