import { describe, it, expect } from 'vitest' import { render, screen } from '@testing-library/react' import { MarkdownBody } from '../../chat/markdown' describe('MarkdownBody', () => { // ============================================================ // 基础渲染测试 // ============================================================ describe('基础渲染', () => { it('应正确渲染组件', () => { const { container } = render(Hello World) expect(container.firstChild).toBeInTheDocument() }) it('应设置正确的 displayName', () => { expect(MarkdownBody.displayName).toBe('MarkdownBody') }) it('应渲染普通文本', () => { render(Simple text content) expect(screen.getByText('Simple text content')).toBeInTheDocument() }) }) // ============================================================ // 段落渲染测试 // ============================================================ describe('段落 (p)', () => { it('应渲染段落为 p 元素', () => { const { container } = render(A paragraph) expect(container.querySelector('p')).toBeInTheDocument() }) it('段落应有正确的样式类', () => { const { container } = render(Styled paragraph) const p = container.querySelector('p') expect(p).toHaveClass('text-sm') expect(p).toHaveClass('text-text') }) }) // ============================================================ // 标题渲染测试 // ============================================================ describe('标题 (h1-h6)', () => { it('应渲染 h1 标题', () => { const { container } = render(# Heading 1) const h1 = container.querySelector('h1') expect(h1).toBeInTheDocument() expect(h1).toHaveTextContent('Heading 1') }) it('应渲染 h2 标题', () => { const { container } = render(## Heading 2) const h2 = container.querySelector('h2') expect(h2).toBeInTheDocument() expect(h2).toHaveTextContent('Heading 2') }) it('应渲染 h3 标题', () => { const { container } = render(### Heading 3) const h3 = container.querySelector('h3') expect(h3).toBeInTheDocument() }) it('h1 应有正确的样式', () => { const { container } = render(# Title) const h1 = container.querySelector('h1') expect(h1).toHaveClass('text-4xl') expect(h1).toHaveClass('font-medium') }) it('h2 应有边框样式', () => { const { container } = render(## Section) const h2 = container.querySelector('h2') expect(h2).toHaveClass('border-b') }) }) // ============================================================ // 列表渲染测试 // ============================================================ describe('列表 (ul/ol)', () => { it('应渲染无序列表', () => { const { container } = render( {`- Item 1\n- Item 2\n- Item 3`} ) const ul = container.querySelector('ul') expect(ul).toBeInTheDocument() expect(ul).toHaveClass('list-disc') }) it('应渲染有序列表', () => { const { container } = render( {`1. First\n2. Second\n3. Third`} ) const ol = container.querySelector('ol') expect(ol).toBeInTheDocument() expect(ol).toHaveClass('list-decimal') }) it('列表项应正确渲染', () => { const { container } = render( {`- Apple\n- Banana`} ) const items = container.querySelectorAll('li') expect(items).toHaveLength(2) }) }) // ============================================================ // 代码渲染测试 // ============================================================ describe('代码 (code/pre)', () => { it('应渲染行内代码', () => { const { container } = render( Use `npm install` to install ) const code = container.querySelector('code') expect(code).toBeInTheDocument() expect(code).toHaveTextContent('npm install') }) it('行内代码应有背景样式', () => { const { container } = render( Run `test` command ) const code = container.querySelector('code') expect(code).toHaveClass('bg-fill-tertiary') expect(code).toHaveClass('rounded') }) it('应渲染代码块', () => { const { container } = render( {`\`\`\`javascript\nconst x = 1;\n\`\`\``} ) const pre = container.querySelector('pre') expect(pre).toBeInTheDocument() }) it('代码块应包含在滚动容器中', () => { const { container } = render( {`\`\`\`js\ncode\n\`\`\``} ) // Scrollbar 组件包裹 const scrollContainer = container.querySelector('[class*="overflow"]') expect(scrollContainer).toBeInTheDocument() }) }) // ============================================================ // 引用渲染测试 // ============================================================ describe('引用 (blockquote)', () => { it('应渲染引用块', () => { const { container } = render( {'> This is a quote'} ) const blockquote = container.querySelector('blockquote') expect(blockquote).toBeInTheDocument() }) it('引用块应有边框样式', () => { const { container } = render( {'> Quote text'} ) const blockquote = container.querySelector('blockquote') expect(blockquote).toHaveClass('border-l-2') expect(blockquote).toHaveClass('italic') }) }) // ============================================================ // 链接渲染测试 // ============================================================ describe('链接 (a)', () => { it('应渲染链接', () => { render([Click me](https://example.com)) const link = screen.getByRole('link') expect(link).toBeInTheDocument() expect(link).toHaveTextContent('Click me') }) it('链接应在新标签页打开', () => { render([Link](https://example.com)) const link = screen.getByRole('link') expect(link).toHaveAttribute('target', '_blank') expect(link).toHaveAttribute('rel', 'noopener noreferrer') }) it('链接应有正确的 href', () => { render([Test](https://test.com)) const link = screen.getByRole('link') expect(link).toHaveAttribute('href', 'https://test.com') }) }) // ============================================================ // 强调渲染测试 // ============================================================ describe('强调 (strong/em)', () => { it('应渲染粗体', () => { const { container } = render( **Bold text** ) const strong = container.querySelector('strong') expect(strong).toBeInTheDocument() expect(strong).toHaveTextContent('Bold text') }) it('应渲染斜体', () => { const { container } = render( *Italic text* ) const em = container.querySelector('em') expect(em).toBeInTheDocument() expect(em).toHaveTextContent('Italic text') }) it('粗体应有正确样式', () => { const { container } = render( **Strong** ) const strong = container.querySelector('strong') expect(strong).toHaveClass('font-semibold') }) it('斜体应有正确样式', () => { const { container } = render( *Emphasis* ) const em = container.querySelector('em') expect(em).toHaveClass('italic') }) }) // ============================================================ // 水平线渲染测试 // ============================================================ describe('水平线 (hr)', () => { it('应渲染水平线', () => { const { container } = render( {`Above\n\n---\n\nBelow`} ) const hr = container.querySelector('hr') expect(hr).toBeInTheDocument() }) }) // ============================================================ // 表格渲染测试 (GFM) // ============================================================ describe('表格 (GFM)', () => { it('应渲染表格', () => { const tableMarkdown = ` | Name | Age | |------|-----| | Alice | 25 | | Bob | 30 | ` const { container } = render({tableMarkdown}) expect(container.querySelector('table')).toBeInTheDocument() }) it('应渲染表头', () => { const tableMarkdown = ` | Header1 | Header2 | |---------|---------| | Cell1 | Cell2 | ` const { container } = render({tableMarkdown}) const thead = container.querySelector('thead') expect(thead).toBeInTheDocument() }) it('应渲染表体', () => { const tableMarkdown = ` | A | B | |---|---| | 1 | 2 | ` const { container } = render({tableMarkdown}) const tbody = container.querySelector('tbody') expect(tbody).toBeInTheDocument() }) it('表格应在滚动容器中', () => { const tableMarkdown = ` | Col1 | Col2 | |------|------| | Data | Data | ` const { container } = render({tableMarkdown}) // Scrollbar 组件包裹 const scrollContainer = container.querySelector('[class*="border"]') expect(scrollContainer).toBeInTheDocument() }) }) // ============================================================ // 任务列表测试 (GFM) // ============================================================ describe('任务列表 (GFM)', () => { it('应渲染任务列表', () => { const taskList = ` - [ ] Unchecked - [x] Checked ` const { container } = render({taskList}) const checkboxes = container.querySelectorAll('input[type="checkbox"]') expect(checkboxes.length).toBeGreaterThan(0) }) it('任务列表应无圆点样式', () => { const taskList = ` - [ ] Task 1 - [x] Task 2 ` const { container } = render({taskList}) const ul = container.querySelector('ul') expect(ul).toHaveClass('list-none') }) }) // ============================================================ // className 测试 // ============================================================ describe('className', () => { it('应合并自定义 className', () => { const { container } = render( Content ) expect(container.firstChild).toHaveClass('custom-markdown') }) it('根元素应有 markdown-body 类', () => { const { container } = render( Content ) expect(container.firstChild).toHaveClass('markdown-body') }) }) // ============================================================ // 复杂内容测试 // ============================================================ describe('复杂内容', () => { it('应正确渲染混合内容', () => { const complexMarkdown = ` # Title A paragraph with **bold** and *italic* text. - List item 1 - List item 2 \`\`\`js const x = 1; \`\`\` > A quote [A link](https://example.com) ` const { container } = render({complexMarkdown}) expect(container.querySelector('h1')).toBeInTheDocument() expect(container.querySelector('p')).toBeInTheDocument() expect(container.querySelector('strong')).toBeInTheDocument() expect(container.querySelector('em')).toBeInTheDocument() expect(container.querySelector('ul')).toBeInTheDocument() expect(container.querySelector('pre')).toBeInTheDocument() expect(container.querySelector('blockquote')).toBeInTheDocument() expect(container.querySelector('a')).toBeInTheDocument() }) it('应处理空字符串', () => { const { container } = render({''}) expect(container.firstChild).toBeInTheDocument() }) it('应处理纯空白字符串', () => { const { container } = render({' '}) expect(container.firstChild).toBeInTheDocument() }) }) })