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: ``,
}
}),
})),
}))
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: ``,
}
}
}),
}))
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 = ``
}
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
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 = `
`
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 = `
`
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 = `
`
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
})
})