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()
})
})
})