import { describe, expect, it, vi } from 'vitest' import { enhanceRenderedHtml } from '../packages/markstream-angular/src/enhanceRenderedHtml' const { monacoCleanup, canParseOffthread, findPrefixOffthread, mermaidState, } = vi.hoisted(() => ({ monacoCleanup: vi.fn(), canParseOffthread: vi.fn(async () => true), findPrefixOffthread: vi.fn(async () => null), mermaidState: { failOnBToC: false }, })) vi.mock('../packages/markstream-angular/src/optional/katex', () => ({ isKatexEnabled: vi.fn(() => true), getKatex: vi.fn(async () => ({ renderToString(source: string, options?: { displayMode?: boolean }) { return options?.displayMode ? `${source}` : `${source}` }, })), })) vi.mock('../packages/markstream-angular/src/optional/mermaid', () => ({ isMermaidEnabled: vi.fn(() => true), getMermaid: vi.fn(async () => ({ render: vi.fn(async (_id: string, source: string) => { if (mermaidState.failOnBToC && source.includes('B-->C')) throw new Error('Incomplete mermaid graph') return { svg: `${source}`, } }), })), })) vi.mock('../packages/markstream-angular/src/workers/mermaidWorkerClient', () => ({ canParseOffthread, findPrefixOffthread, })) vi.mock('../packages/markstream-angular/src/optional/d2', () => ({ getD2: vi.fn(async () => class MockD2 { async compile(code: string) { return { diagram: { code }, renderOptions: {} } } async render(diagram: { code: string }) { return { svg: `${diagram.code}`, } } }), })) vi.mock('../packages/markstream-angular/src/optional/infographic', () => ({ getInfographic: vi.fn(async () => class MockInfographic { container: HTMLElement constructor(options: { container: HTMLElement }) { this.container = options.container } render(source: string) { this.container.innerHTML = `${source}` } destroy() { this.container.dataset.infographicDestroyed = '1' } }), })) vi.mock('../packages/markstream-angular/src/optional/monaco', () => ({ getUseMonaco: vi.fn(async () => ({ useMonaco: vi.fn(() => ({ async createEditor(container: HTMLElement, code: string, language: string) { container.innerHTML = `
${code}
` }, async setTheme() {}, cleanupEditor: monacoCleanup, })), })), })) describe('markstream-angular enhanceRenderedHtml', () => { it('hydrates math, mermaid, monaco, infographic, and d2 blocks in place', async () => { monacoCleanup.mockReset() canParseOffthread.mockReset() findPrefixOffthread.mockReset() canParseOffthread.mockImplementation(async () => true) findPrefixOffthread.mockImplementation(async () => null) mermaidState.failOnBToC = false const onCopy = vi.fn() const root = document.createElement('div') root.innerHTML = `
E = mc^2
\\int_0^1 x^2 dx
graph TD; A-->B;
const value = 1
-const value = 1
+const value: number = 1
infographic list-row-simple-horizontal-arrow
a -> b
` const shell = root.querySelector('.markstream-angular') as HTMLElement const handle = await enhanceRenderedHtml(shell, { final: true, isDark: true, onCopy, showTooltips: true }) expect(shell.innerHTML).toContain('class="katex"') expect(shell.innerHTML).toContain('class="katex-display"') expect(shell.innerHTML).toContain('data-mermaid="1"') expect(shell.innerHTML).toContain('markstream-angular-mermaid') expect(shell.innerHTML).toContain('data-monaco="1"') expect(shell.innerHTML).toContain('data-markstream-monaco-diff="1"') expect(shell.innerHTML).toContain('data-infographic="1"') expect(shell.innerHTML).toContain('data-d2="1"') expect(shell.innerHTML).toContain('markstream-angular-enhanced-block__action') const copyButton = shell.querySelector('.markstream-angular-enhanced-block__action') copyButton?.click() expect(onCopy).toHaveBeenCalled() handle.dispose() expect(monacoCleanup).toHaveBeenCalledTimes(2) }) it('skips heavy code/diagram upgrades while content is still streaming', async () => { monacoCleanup.mockReset() canParseOffthread.mockReset() findPrefixOffthread.mockReset() canParseOffthread.mockImplementation(async () => true) findPrefixOffthread.mockImplementation(async () => null) mermaidState.failOnBToC = false const root = document.createElement('div') root.innerHTML = `
a+b
const value = 1
a -> b
` const shell = root.querySelector('.markstream-angular') as HTMLElement await enhanceRenderedHtml(shell, { final: false }) expect(shell.innerHTML).toContain('class="katex"') expect(shell.innerHTML).toContain('language-ts') expect(shell.innerHTML).not.toContain('data-monaco="1"') expect(shell.innerHTML).not.toContain('data-d2="1"') expect(monacoCleanup).not.toHaveBeenCalled() }) it('does not re-render KaTeX from already-rendered output', async () => { canParseOffthread.mockReset() findPrefixOffthread.mockReset() canParseOffthread.mockImplementation(async () => true) findPrefixOffthread.mockImplementation(async () => null) mermaidState.failOnBToC = false const root = document.createElement('div') root.innerHTML = `
x = a
f(x) = x^2
` const shell = root.querySelector('.markstream-angular') as HTMLElement await enhanceRenderedHtml(shell, { final: false }) const firstPass = shell.innerHTML await enhanceRenderedHtml(shell, { final: false }) expect(shell.innerHTML).toBe(firstPass) expect(shell.innerHTML).toContain('class="katex"') expect(shell.innerHTML).toContain('class="katex-display"') }) it('re-renders KaTeX when the source text changes during streaming', async () => { canParseOffthread.mockReset() findPrefixOffthread.mockReset() canParseOffthread.mockImplementation(async () => true) findPrefixOffthread.mockImplementation(async () => null) mermaidState.failOnBToC = false const root = document.createElement('div') root.innerHTML = `
x = a
f(x) = x^2
` const shell = root.querySelector('.markstream-angular') as HTMLElement await enhanceRenderedHtml(shell, { final: false }) expect(shell.innerHTML).toContain('x = a') expect(shell.innerHTML).toContain('f(x) = x^2') const inlineSource = shell.querySelector('.markstream-nested-math__source') const blockSource = shell.querySelector('.markstream-nested-math-block__source code') inlineSource!.textContent = 'x = a + b' blockSource!.textContent = 'f(x) = x^3' await enhanceRenderedHtml(shell, { final: false }) expect(shell.innerHTML).toContain('x = a + b') expect(shell.innerHTML).toContain('f(x) = x^3') expect(shell.querySelector('.markstream-nested-math')?.getAttribute('data-markstream-katex-source')).toBe('x = a + b') expect(shell.querySelector('.markstream-nested-math-block')?.getAttribute('data-markstream-katex-source')).toBe('f(x) = x^3') }) it('renders a mermaid prefix preview while streaming when the full diagram is not yet valid', async () => { canParseOffthread.mockReset() findPrefixOffthread.mockReset() canParseOffthread.mockImplementation(async (source: string) => !source.includes('B-->C')) findPrefixOffthread.mockImplementation(async () => 'graph LR\nA-->B\n') mermaidState.failOnBToC = true const root = document.createElement('div') root.innerHTML = `
graph LR
A-->B
B-->C
` const shell = root.querySelector('.markstream-angular') as HTMLElement await enhanceRenderedHtml(shell, { final: false }) expect(canParseOffthread).toHaveBeenCalled() expect(findPrefixOffthread).toHaveBeenCalled() expect(shell.innerHTML).toContain('data-mermaid="1"') expect(shell.innerHTML).toContain('markstream-angular-mermaid') const previewHost = shell.querySelector('.markstream-angular-mermaid') expect(previewHost?.innerHTML).toContain('A-->B') expect(previewHost?.innerHTML).not.toContain('B-->C') mermaidState.failOnBToC = false }) })