// @vitest-environment jsdom
import { expect, it } from 'vitest'
import { renderMarkdown } from './markdown'
it('renders CommonMark and GFM formats, escaped literals and safe HTML', async () => {
const source = ['# H1','## H2','### H3','#### H4','##### H5','###### H6',
'**bold** *italic* ~~deleted~~ `inline` ``a`b``', '> quote', '- item\n - nested',
'1. first\n2. second', '- [x] done\n- [ ] todo', '[link][ref]\n\n[ref]: https://example.com',
'', '| A | B |\n| --- | --- |\n| x | y |', '---',
'line \nbreak', '\\`literal\\`', '
',
'```unknown-language\n\n```'].join('\n\n')
const root = document.createElement('div')
root.innerHTML = await renderMarkdown(source)
for (const selector of ['h1','h2','h3','h4','h5','h6','strong','em','del','blockquote','ul','ol','table','hr','br','a','img','input[type=checkbox]','pre code']) expect(root.querySelector(selector), selector).not.toBeNull()
expect(root.querySelector('code')?.textContent).toBe('inline')
expect(root.textContent).toContain('`literal`')
expect(root.querySelector('pre code')?.textContent).toContain('')
expect(root.querySelector('script,[onerror]')).toBeNull()
})
it('renders inline, display and editor LaTeX fences while leaving code literals alone', async () => {
const root = document.createElement('div')
root.innerHTML = await renderMarkdown('$x^2$\n\n$$\nx^2\n$$\n\n```LaTeX\nx^2\n```\n\n`$literal$`\n\n```text\n$literal$\n```')
expect(root.querySelectorAll('.katex')).toHaveLength(3)
expect(root.querySelector('code')?.textContent).toBe('$literal$')
expect(root.querySelector('pre code')?.textContent).toContain('$literal$')
})
it('renders numeric and legacy citations as numbered buttons without altering code', async () => {
const root = document.createElement('div')
root.innerHTML = await renderMarkdown('正文 [1][2] [cit_blk_a] `[1]` [3] [1](https://example.com)', { citationNumbers: [1, 2], citationAliases: { cit_blk_a: 2 } })
expect([...root.querySelectorAll('.inline-citation')].map(c => c.textContent)).toEqual(['[1]', '[2]', '[2]'])
expect(root.querySelector('code')?.textContent).toBe('[1]')
expect(root.querySelector('a')?.getAttribute('href')).toBe('https://example.com')
})
it('shows code language and preserves exact source for copying', async () => {
const root = document.createElement('div')
root.innerHTML = await renderMarkdown('```python\nprint("hello")\n```')
expect(root.querySelector('.markdown-code-toolbar')?.textContent).toContain('python')
expect(root.querySelector('[data-code-action="copy"]')).not.toBeNull()
expect(root.querySelector('.markdown-code-source')?.textContent).toBe('print("hello")\n')
})
it('keeps code toolbar inside the themed frame and avoids extra rendered newline rows', async () => {
const root = document.createElement('div')
root.innerHTML = await renderMarkdown('```markdown\n# First\n\n## Second\n```')
const frame = root.querySelector('.markdown-code-block')!
expect(frame.getAttribute('data-language-label')).toBe('markdown')
expect(frame.querySelector(':scope > .markdown-code-toolbar')).not.toBeNull()
const code = frame.querySelector('.shiki code')!
expect([...code.childNodes].filter(n => n.nodeType === Node.TEXT_NODE && n.textContent?.includes('\n'))).toHaveLength(0)
expect(code.querySelectorAll('.line')).toHaveLength(4)
expect(frame.querySelector('.markdown-code-source')?.textContent).toBe('# First\n\n## Second\n')
})