import { render, screen } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { Markdown } from './markdown'; import { RevealContext } from '../reveal-context'; describe('Markdown', () => { afterEach(() => { vi.restoreAllMocks(); }); it('renders markdown as slide HTML and normalizes common indentation', () => { const { container } = render( {` ## Hello A paragraph with **markdown**. `} ); const section = container.querySelector('section'); expect(section?.querySelector('h2')).toHaveTextContent('Hello'); expect(section?.querySelector('p')).toHaveTextContent('A paragraph with markdown.'); }); it('renders new slide HTML when the markdown source changes', () => { const { container, rerender } = render(); expect(container.querySelector('h2')).toHaveTextContent('Before'); rerender(); expect(container.querySelector('h2')).not.toBeInTheDocument(); expect(container.querySelector('h3')).toHaveTextContent('After'); expect(container.querySelector('p')).toHaveTextContent('Updated paragraph'); }); it('splits horizontal and vertical slides using custom separators', () => { const { container } = render( {` ## Slide 1.1 -- ## Slide 1.2 --- ## Slide 2 `} ); const topLevelSections = Array.from(container.children).filter( (element): element is HTMLElement => element.tagName === 'SECTION' ); expect(topLevelSections).toHaveLength(2); expect(topLevelSections[0].querySelectorAll(':scope > section')).toHaveLength(2); expect(topLevelSections[1]).toHaveTextContent('Slide 2'); }); it('creates speaker notes using the configured notes separator', () => { const { container } = render( {` ## Slide Visible content Note: Remember this detail `} ); expect(container.querySelector('section')).toHaveTextContent('Visible content'); expect(container.querySelector('aside.notes')).toHaveTextContent('Remember this detail'); }); it('applies .slide attributes to the generated section', () => { const { container } = render( {` ## Intro `} ); const section = container.querySelector('section'); expect(section).toHaveAttribute('id', 'intro'); expect(section).toHaveAttribute('data-background', '#ff0000'); }); it('applies .element attributes to the preceding markdown element', () => { const { container } = render( {` - Item 1 - Item 2 `} ); const items = container.querySelectorAll('li'); expect(items[0]).toHaveClass('fragment'); expect(items[0]).toHaveAttribute('data-fragment-index', '2'); expect(items[1]).not.toHaveClass('fragment'); }); it('supports custom slide and element attribute separators', () => { const { container } = render( {` ## Title `} ); expect(container.querySelector('section')).toHaveAttribute('data-background', '#123456'); expect(container.querySelector('h2')).toHaveClass('fragment', 'highlight-red'); }); it('matches the core plugin code block line highlight syntax', () => { const { container } = render( {` \`\`\`javascript [456: 3,4,5] console.log('hello'); \`\`\` `} ); const code = container.querySelector('code'); expect(code).toHaveClass('javascript'); expect(code).toHaveAttribute('data-line-numbers', '3,4,5'); expect(code).toHaveAttribute('data-ln-start-from', '456'); }); it('runs the Reveal highlight plugin when the deck becomes available later', () => { const highlightBlock = vi.fn((block: HTMLElement) => { block.setAttribute('data-highlighted', 'yes'); }); const deck = { getPlugin: vi.fn().mockReturnValue({ highlightBlock }), syncFragments: vi.fn(), } as any; const { container, rerender } = render( ); expect(highlightBlock).not.toHaveBeenCalled(); rerender( ); const section = container.querySelector('section'); const code = container.querySelector('code'); expect(code).toBeInTheDocument(); expect(highlightBlock).toHaveBeenCalledWith(code); expect(code).toHaveAttribute('data-highlighted', 'yes'); expect(deck.syncFragments).toHaveBeenCalledWith(section); }); it('rehighlights markdown code blocks when the same slide rerenders', () => { const highlightBlock = vi.fn((block: HTMLElement) => { block.setAttribute('data-highlighted', 'yes'); }); const deck = { getPlugin: vi.fn().mockReturnValue({ highlightBlock }), syncFragments: vi.fn(), } as any; const { container, rerender } = render( ); const code = container.querySelector('code'); expect(code).toHaveAttribute('data-highlighted', 'yes'); expect(highlightBlock).toHaveBeenCalledTimes(1); code?.removeAttribute('data-highlighted'); rerender( ); const rerenderedCode = container.querySelector('code'); expect(highlightBlock).toHaveBeenCalledTimes(2); expect(rerenderedCode).toHaveAttribute('data-highlighted', 'yes'); }); it('can load markdown from an external source', async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, text: vi.fn().mockResolvedValue('## Remote slide'), }); vi.stubGlobal('fetch', fetchMock); render(); expect(await screen.findByText('Remote slide')).toBeInTheDocument(); expect(fetchMock).toHaveBeenCalledWith('/slides.md', expect.any(Object)); }); });