import { describe, it, expect } from 'vitest' import { render } from '@testing-library/react' import { MarkdownContent } from './markdown-content' describe('', () => { it('renders markdown with the safe defaults (no html, no images)', () => { const { container } = render( raw' } />, ) // Image must NOT render — disallowedElements blocks it. expect(container.querySelector('img')).toBeNull() // Raw HTML escapes to text — span never gets parsed as an element. expect(container.querySelector('span')).toBeNull() // Heading still renders normally. expect(container.textContent).toContain('Title') }) it('renders images when allowImages=true', () => { const { container } = render( , ) const img = container.querySelector('img') expect(img).not.toBeNull() expect(img?.getAttribute('alt')).toBe('alt') }) it('parses GFM tables only when gfm=true', () => { const table = '| a | b |\n| --- | --- |\n| 1 | 2 |' const { container: plain } = render() // Without the flag, GFM syntax stays CommonMark text — no table element. expect(plain.querySelector('table')).toBeNull() const { container } = render() expect(container.querySelector('table')).not.toBeNull() expect(container.querySelector('td')?.textContent).toBe('1') }) it('parses raw HTML when allowHtml=true', () => { const { container } = render( raw html'} allowHtml />, ) // Both inline tags survive sanitization; the text content is preserved. expect(container.querySelector('span')).not.toBeNull() expect(container.querySelector('em')?.textContent).toBe('html') expect(container.textContent).toContain('raw html') }) describe('XSS hardening', () => { it('drops safe'} allowHtml allowImages />, ) expect(container.querySelector('script')).toBeNull() expect(container.textContent).toContain('safe') }) it('strips event handlers from ', () => { const { container } = render( '} allowHtml allowImages />, ) const img = container.querySelector('img') expect(img).not.toBeNull() expect(img?.getAttribute('onerror')).toBeNull() }) it('drops '} allowHtml allowImages />, ) expect(container.querySelector('iframe')).toBeNull() }) it('drops with onload', () => { const { container } = render( '} allowHtml allowImages />, ) const svg = container.querySelector('svg') // defaultSchema doesn't allowlist , so the element should be gone. // Even if a future schema variant allows it, `onload` must not survive. expect(svg?.getAttribute('onload') ?? null).toBeNull() }) it('drops tags that could rewrite relative URLs', () => { const { container } = render( link'} allowHtml />, ) expect(container.querySelector('base')).toBeNull() }) it('strips href for javascript: URLs in markdown links', () => { const { container } = render( , ) const a = container.querySelector('a') // Either the anchor renders without `href` or with empty `href` — both // are safe (no navigation). Concretely we assert no `javascript:` value. expect(a?.getAttribute('href') ?? '').not.toMatch(/^javascript:/i) }) it('strips href for data: URLs in markdown links (Note path)', () => { const { container } = render( alert(1))'} />, ) const a = container.querySelector('a') expect(a?.getAttribute('href') ?? '').not.toMatch(/^data:/i) }) it('strips data: URLs from on the rich path', () => { const { container } = render( )'} allowImages />, ) const img = container.querySelector('img') expect(img?.getAttribute('src') ?? '').not.toMatch(/^data:/i) }) it('preserves safe URLs (https, mailto, relative)', () => { const { container } = render( , ) const links = Array.from(container.querySelectorAll('a')).map((a) => a.getAttribute('href'), ) expect(links).toEqual([ 'https://example.com', 'mailto:a@b.co', './page', '#x', ]) }) }) })