Files
tolaria/src/utils/compact-markdown.test.ts
Test e823243a3b fix: prevent BlockNote from altering list markers and inserting HTML entities
BlockNote's serializer outputs `*` for bullet lists and inserts ` `
HTML entities around inline code within bold text. Both cause meaningless
git diffs. Fix by post-processing in compactMarkdown:
- Normalize `*` bullet markers to `-` (standard convention)
- Decode HTML entities like ` ` back to literal characters
- Skip normalization inside fenced code blocks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 15:49:28 +02:00

123 lines
5.0 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { compactMarkdown } from './compact-markdown'
describe('compactMarkdown', () => {
it('collapses blank lines between bullet list items (tight list)', () => {
const input = '* Item one\n\n* Item two\n\n* Item three\n'
expect(compactMarkdown(input)).toBe('- Item one\n- Item two\n- Item three\n')
})
it('collapses blank lines between dash list items', () => {
const input = '- Item one\n\n- Item two\n\n- Item three\n'
expect(compactMarkdown(input)).toBe('- Item one\n- Item two\n- Item three\n')
})
it('collapses blank lines between numbered list items', () => {
const input = '1. First\n\n2. Second\n\n3. Third\n'
expect(compactMarkdown(input)).toBe('1. First\n2. Second\n3. Third\n')
})
it('removes extra blank line after heading', () => {
const input = '## Personal\n\n* Back on track\n\n* Good health\n'
expect(compactMarkdown(input)).toBe('## Personal\n\n- Back on track\n- Good health\n')
})
it('preserves single blank line between heading and content', () => {
const input = '## Title\n\nSome paragraph text.\n'
expect(compactMarkdown(input)).toBe('## Title\n\nSome paragraph text.\n')
})
it('collapses multiple blank lines after heading to one', () => {
const input = '## Title\n\n\n\nSome paragraph text.\n'
expect(compactMarkdown(input)).toBe('## Title\n\nSome paragraph text.\n')
})
it('preserves blank lines between paragraphs', () => {
const input = 'First paragraph.\n\nSecond paragraph.\n'
expect(compactMarkdown(input)).toBe('First paragraph.\n\nSecond paragraph.\n')
})
it('preserves blank lines inside fenced code blocks', () => {
const input = '```\nline one\n\nline two\n\nline three\n```\n'
expect(compactMarkdown(input)).toBe('```\nline one\n\nline two\n\nline three\n```\n')
})
it('preserves blank lines inside fenced code blocks with language', () => {
const input = '```js\nconst a = 1\n\nconst b = 2\n```\n'
expect(compactMarkdown(input)).toBe('```js\nconst a = 1\n\nconst b = 2\n```\n')
})
it('does not add trailing blank lines', () => {
const input = '## Title\n\nText.\n\n\n'
expect(compactMarkdown(input)).toBe('## Title\n\nText.\n')
})
it('handles the exact bug scenario from the issue', () => {
const input = '## Personal\n\n* Back on track with Flavia\n\n* Good health vitals in place\n\n'
expect(compactMarkdown(input)).toBe('## Personal\n\n- Back on track with Flavia\n- Good health vitals in place\n')
})
it('handles heading followed immediately by list (no blank line)', () => {
const input = '## Title\n* Item one\n\n* Item two\n'
expect(compactMarkdown(input)).toBe('## Title\n- Item one\n- Item two\n')
})
it('handles mixed content: heading, paragraph, list', () => {
const input = '# Title\n\nSome intro.\n\n* Item one\n\n* Item two\n\nConclusion.\n'
expect(compactMarkdown(input)).toBe('# Title\n\nSome intro.\n\n- Item one\n- Item two\n\nConclusion.\n')
})
it('preserves empty input', () => {
expect(compactMarkdown('')).toBe('')
})
it('preserves single line', () => {
expect(compactMarkdown('Hello\n')).toBe('Hello\n')
})
it('collapses 3+ consecutive blank lines to one blank line', () => {
const input = 'Paragraph one.\n\n\n\nParagraph two.\n'
expect(compactMarkdown(input)).toBe('Paragraph one.\n\nParagraph two.\n')
})
it('handles list after code block', () => {
const input = '```\ncode\n```\n\n* Item one\n\n* Item two\n'
expect(compactMarkdown(input)).toBe('```\ncode\n```\n\n- Item one\n- Item two\n')
})
it('handles nested list items', () => {
const input = '* Parent\n\n * Child one\n\n * Child two\n'
expect(compactMarkdown(input)).toBe('- Parent\n - Child one\n - Child two\n')
})
it('handles checklist items', () => {
const input = '* [ ] Todo one\n\n* [ ] Todo two\n\n* [x] Done\n'
expect(compactMarkdown(input)).toBe('- [ ] Todo one\n- [ ] Todo two\n- [x] Done\n')
})
it('handles blockquotes normally', () => {
const input = '> Quote line one\n\n> Quote line two\n'
expect(compactMarkdown(input)).toBe('> Quote line one\n\n> Quote line two\n')
})
it('does not normalize * inside code blocks', () => {
const input = '```\n* not a list\n```\n'
expect(compactMarkdown(input)).toBe('```\n* not a list\n```\n')
})
it('decodes &#x20; HTML entities from BlockNote bold+code output', () => {
const input = '**Remove&#x20;**`NoteWindow`**&#x20;and render the full&#x20;**`App`**&#x20;component.**\n'
expect(compactMarkdown(input)).toBe('**Remove **`NoteWindow`** and render the full **`App`** component.**\n')
})
it('decodes multiple HTML entity types', () => {
const input = 'Use &#x26; for ampersand and &#x3C; for less-than.\n'
expect(compactMarkdown(input)).toBe('Use & for ampersand and < for less-than.\n')
})
it('does not decode HTML entities inside code blocks', () => {
const input = '```\n&#x20; should stay\n```\n'
expect(compactMarkdown(input)).toBe('```\n&#x20; should stay\n```\n')
})
})