Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped
213 lines
7.2 KiB
TypeScript
213 lines
7.2 KiB
TypeScript
/**
|
|
* @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 = '`<h1>`~`<h6>`'
|
|
|
|
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('<h1>') || n.text.includes('<h6>')),
|
|
)
|
|
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 = '`<h1>`~~`<h6>`'
|
|
|
|
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('<h1>') || n.text.includes('<h6>')),
|
|
)
|
|
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 = '<h1>~~test~~</h1>'
|
|
|
|
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('<h1>')
|
|
expect(allText).toContain('</h1>')
|
|
|
|
// Should contain 'test' text
|
|
expect(allText).toContain('test')
|
|
})
|
|
|
|
it('parses multiple inline code segments with HTML-like content and strike', () => {
|
|
const md = 'text `<h1>` more ~~text~~ and `<h2>`'
|
|
|
|
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('<h1>')
|
|
expect(codeTextNodes[1].text).toBe('<h2>')
|
|
|
|
// 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 = '<enter existing CID here if available>\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('<enter existing CID here if available>')
|
|
|
|
// 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 = '<p>Hello</p>'
|
|
|
|
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('<p>Hello</p>')
|
|
})
|
|
|
|
it('still parses markdown syntax correctly alongside HTML', () => {
|
|
const md = '<div>test</div> ~~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('<div>test</div>'))
|
|
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)
|
|
})
|
|
})
|