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
268 lines
8.1 KiB
TypeScript
268 lines
8.1 KiB
TypeScript
import { Blockquote } from '@tiptap/extension-blockquote'
|
|
import { Document } from '@tiptap/extension-document'
|
|
import { Heading } from '@tiptap/extension-heading'
|
|
import { BulletList, ListItem, OrderedList } from '@tiptap/extension-list'
|
|
import { Paragraph } from '@tiptap/extension-paragraph'
|
|
import { Text } from '@tiptap/extension-text'
|
|
import { MarkdownManager } from '@tiptap/markdown'
|
|
import { beforeEach, describe, expect, it } from 'vite-plus/test'
|
|
|
|
describe('Paragraph Markdown Rendering', () => {
|
|
let markdownManager: MarkdownManager
|
|
|
|
beforeEach(() => {
|
|
markdownManager = new MarkdownManager({
|
|
extensions: [
|
|
Document,
|
|
Paragraph,
|
|
Text,
|
|
Heading,
|
|
Blockquote,
|
|
BulletList,
|
|
OrderedList,
|
|
ListItem,
|
|
],
|
|
})
|
|
})
|
|
|
|
describe('empty paragraphs at doc level', () => {
|
|
it('should omit a lone empty paragraph at doc root', () => {
|
|
const doc = {
|
|
type: 'doc',
|
|
content: [{ type: 'paragraph', content: [] }],
|
|
}
|
|
|
|
const raw = markdownManager.renderNodes(doc)
|
|
expect(raw).toBe('')
|
|
})
|
|
|
|
it('should preserve a single empty paragraph between two content paragraphs as blank markdown lines', () => {
|
|
const doc = {
|
|
type: 'doc',
|
|
content: [
|
|
{ type: 'paragraph', content: [{ type: 'text', text: 'First' }] },
|
|
{ type: 'paragraph', content: [] },
|
|
{ type: 'paragraph', content: [{ type: 'text', text: 'Second' }] },
|
|
],
|
|
}
|
|
|
|
const markdown = markdownManager.serialize(doc)
|
|
expect(markdown).toBe('First\n\n\n\nSecond')
|
|
})
|
|
|
|
it('should use to preserve additional blank paragraphs between two content paragraphs', () => {
|
|
const doc = {
|
|
type: 'doc',
|
|
content: [
|
|
{ type: 'paragraph', content: [{ type: 'text', text: 'First' }] },
|
|
{ type: 'paragraph', content: [] },
|
|
{ type: 'paragraph', content: [] },
|
|
{ type: 'paragraph', content: [{ type: 'text', text: 'Second' }] },
|
|
],
|
|
}
|
|
|
|
const markdown = markdownManager.serialize(doc)
|
|
expect(markdown).toBe('First\n\n\n\n \n\nSecond')
|
|
})
|
|
|
|
it('should keep a preserved blank paragraph marker after the first blank paragraph', () => {
|
|
const input = 'First\n\n \n\nSecond'
|
|
const json = markdownManager.parse(input)
|
|
const output = markdownManager.serialize(json)
|
|
expect(output.trim()).toBe('First\n\n\n\nSecond')
|
|
})
|
|
|
|
it('should preserve a trailing empty paragraph as trailing markdown spacing', () => {
|
|
const doc = {
|
|
type: 'doc',
|
|
content: [
|
|
{
|
|
type: 'heading',
|
|
attrs: { level: 1 },
|
|
content: [{ type: 'text', text: 'Markdown Test' }],
|
|
},
|
|
{
|
|
type: 'paragraph',
|
|
content: [
|
|
{ type: 'text', text: 'Click "Parse Markdown" to load content from the left panel.' },
|
|
],
|
|
},
|
|
{ type: 'paragraph', content: [] },
|
|
],
|
|
}
|
|
|
|
const markdown = markdownManager.serialize(doc)
|
|
expect(markdown).toBe(
|
|
'# Markdown Test\n\nClick "Parse Markdown" to load content from the left panel.\n\n',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('empty paragraphs inside nested nodes', () => {
|
|
it('should not emit for an empty paragraph inside a bullet list item', () => {
|
|
const doc = {
|
|
type: 'doc',
|
|
content: [
|
|
{
|
|
type: 'bulletList',
|
|
content: [
|
|
{
|
|
type: 'listItem',
|
|
content: [{ type: 'paragraph', content: [] }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
|
|
const markdown = markdownManager.serialize(doc)
|
|
expect(markdown).toBe('- ')
|
|
})
|
|
|
|
it('should not emit for an empty paragraph inside an ordered list item', () => {
|
|
const doc = {
|
|
type: 'doc',
|
|
content: [
|
|
{
|
|
type: 'orderedList',
|
|
content: [
|
|
{
|
|
type: 'listItem',
|
|
content: [{ type: 'paragraph', content: [] }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
|
|
const markdown = markdownManager.serialize(doc)
|
|
expect(markdown).toBe('1. ')
|
|
})
|
|
|
|
it('should keep the first empty paragraph inside a blockquote empty', () => {
|
|
const doc = {
|
|
type: 'doc',
|
|
content: [
|
|
{
|
|
type: 'blockquote',
|
|
content: [{ type: 'paragraph', content: [] }],
|
|
},
|
|
],
|
|
}
|
|
|
|
const markdown = markdownManager.serialize(doc)
|
|
expect(markdown).toBe('>')
|
|
})
|
|
|
|
it('should preserve multiple empty paragraphs inside a blockquote with after the first', () => {
|
|
const doc = {
|
|
type: 'doc',
|
|
content: [
|
|
{
|
|
type: 'blockquote',
|
|
content: [
|
|
{ type: 'paragraph', content: [{ type: 'text', text: 'First' }] },
|
|
{ type: 'paragraph', content: [] },
|
|
{ type: 'paragraph', content: [] },
|
|
{ type: 'paragraph', content: [{ type: 'text', text: 'Second' }] },
|
|
],
|
|
},
|
|
],
|
|
}
|
|
|
|
const markdown = markdownManager.serialize(doc)
|
|
|
|
expect(markdown).toContain('> First')
|
|
expect(markdown).toContain('> Second')
|
|
expect(markdown).toContain('> ')
|
|
})
|
|
|
|
it('should roundtrip multiple empty paragraphs inside a blockquote without losing any', () => {
|
|
const doc = {
|
|
type: 'doc',
|
|
content: [
|
|
{
|
|
type: 'blockquote',
|
|
content: [
|
|
{ type: 'paragraph', content: [{ type: 'text', text: 'First' }] },
|
|
{ type: 'paragraph', content: [] },
|
|
{ type: 'paragraph', content: [] },
|
|
{ type: 'paragraph', content: [] },
|
|
{ type: 'paragraph', content: [{ type: 'text', text: 'Second' }] },
|
|
],
|
|
},
|
|
],
|
|
}
|
|
|
|
const markdown = markdownManager.serialize(doc)
|
|
const parsed = markdownManager.parse(markdown)
|
|
const blockquote = parsed.content![0]
|
|
|
|
expect(markdown).toBe('> First\n>\n>\n>\n> \n>\n> \n>\n> Second')
|
|
expect(blockquote.type).toBe('blockquote')
|
|
expect(blockquote.content).toHaveLength(5)
|
|
expect(
|
|
blockquote
|
|
.content!.slice(1, 4)
|
|
.every(node => node.type === 'paragraph' && node.content?.length === 0),
|
|
).toBe(true)
|
|
})
|
|
|
|
it('should render content paragraphs inside bullet list items normally', () => {
|
|
const doc = {
|
|
type: 'doc',
|
|
content: [
|
|
{
|
|
type: 'bulletList',
|
|
content: [
|
|
{
|
|
type: 'listItem',
|
|
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] }],
|
|
},
|
|
{
|
|
type: 'listItem',
|
|
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'World' }] }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
|
|
const markdown = markdownManager.serialize(doc)
|
|
expect(markdown).toBe('- Hello\n- World')
|
|
})
|
|
|
|
it('should roundtrip consecutive empty paragraphs inside a bullet list item', () => {
|
|
const doc = {
|
|
type: 'doc',
|
|
content: [
|
|
{
|
|
type: 'bulletList',
|
|
content: [
|
|
{
|
|
type: 'listItem',
|
|
content: [
|
|
{ type: 'paragraph', content: [{ type: 'text', text: 'First' }] },
|
|
{ type: 'paragraph', content: [] },
|
|
{ type: 'paragraph', content: [] },
|
|
{ type: 'paragraph', content: [{ type: 'text', text: 'Second' }] },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
|
|
const markdown = markdownManager.serialize(doc)
|
|
const parsed = markdownManager.parse(markdown)
|
|
const listItem = parsed.content![0].content![0]
|
|
|
|
expect(markdown).toBe('- First\n\n \n\n \n\n Second')
|
|
expect(listItem.type).toBe('listItem')
|
|
expect(listItem.content).toHaveLength(4)
|
|
expect(listItem.content![1].content).toEqual([])
|
|
expect(listItem.content![2].content).toEqual([])
|
|
})
|
|
})
|
|
})
|