import { describe, expect, it } from 'vitest' import { parseInline, parseMarkdown } from '../src/components/markdown/parse' import type { InlineSpan } from '../src/components/markdown/parse' /* Parser tests pin the shapes the Go converter emits — this renderer never sees arbitrary markdown, so these cases are the whole contract. */ function text(spans: InlineSpan[]): string { return spans.map(span => (span.kind === 'text' ? span.text : '')).join('') } describe('parseMarkdown blocks', () => { it('parses headings, paragraphs and rules', () => { const blocks = parseMarkdown('# Title\n\nBody text.\n\n---\n\n### Deep') expect(blocks).toEqual([ { kind: 'heading', level: 1, spans: [{ kind: 'text', text: 'Title' }] }, { kind: 'paragraph', spans: [{ kind: 'text', text: 'Body text.' }] }, { kind: 'hr' }, { kind: 'heading', level: 3, spans: [{ kind: 'text', text: 'Deep' }] }, ]) }) it('parses fenced code with language', () => { const blocks = parseMarkdown('```go\nfmt.Println("hi")\n```') expect(blocks).toEqual([{ kind: 'code', language: 'go', text: 'fmt.Println("hi")' }]) }) it('parses blockquotes by re-parsing their content', () => { const blocks = parseMarkdown('> quoted\n> more') expect(blocks).toEqual([ { kind: 'quote', blocks: [{ kind: 'paragraph', spans: [{ kind: 'text', text: 'quoted more' }] }], }, ]) }) it('parses flat and nested lists', () => { const blocks = parseMarkdown('- one\n- two\n 1. inner\n3. three') expect(blocks).toEqual([ { kind: 'list', ordered: false, start: 1, items: [ { spans: [{ kind: 'text', text: 'one' }] }, { spans: [{ kind: 'text', text: 'two' }], sub: { kind: 'list', ordered: true, start: 1, items: [{ spans: [{ kind: 'text', text: 'inner' }] }], }, }, ], }, { kind: 'list', ordered: true, start: 3, items: [{ spans: [{ kind: 'text', text: 'three' }] }], }, ]) }) it('parses GFM tables', () => { const blocks = parseMarkdown('| A | B |\n| --- | --- |\n| 1 | 2 |') expect(blocks).toEqual([{ kind: 'table', head: ['A', 'B'], rows: [['1', '2']] }]) }) }) describe('parseInline', () => { it('splits links, code and emphasis', () => { const spans = parseInline('see [Docs](https://x.test/d) and `code` plus **bold** *em* ~~gone~~') expect(spans).toEqual([ { kind: 'text', text: 'see ' }, { kind: 'link', href: 'https://x.test/d', spans: [{ kind: 'text', text: 'Docs' }] }, { kind: 'text', text: ' and ' }, { kind: 'code', text: 'code' }, { kind: 'text', text: ' plus ' }, { kind: 'strong', spans: [{ kind: 'text', text: 'bold' }] }, { kind: 'text', text: ' ' }, { kind: 'em', spans: [{ kind: 'text', text: 'em' }] }, { kind: 'text', text: ' ' }, { kind: 'del', spans: [{ kind: 'text', text: 'gone' }] }, ]) }) it('parses images with alt text', () => { const spans = parseInline('![Logo](https://x.test/l.png)') expect(spans).toEqual([{ kind: 'image', alt: 'Logo', src: 'https://x.test/l.png' }]) }) it('keeps link titles out of the href', () => { const spans = parseInline('[API](https://x.test "the api")') expect(spans).toEqual([ { kind: 'link', href: 'https://x.test', spans: [{ kind: 'text', text: 'API' }] }, ]) }) it('leaves plain text untouched', () => { const spans = parseInline('just words') expect(spans).toHaveLength(1) expect(text(spans)).toBe('just words') }) })