import { describe, expect, it } from 'vitest' import { resolveParsedNodes } from '../packages/markstream-angular/src/components/shared/node-helpers' import { clearGlobalCustomComponents, getCustomNodeComponents, removeCustomComponents, setCustomComponents, } from '../packages/markstream-angular/src/customComponents' import { disableKatex, enableKatex, isKatexEnabled, setKatexLoader, } from '../packages/markstream-angular/src/optional/katex' import { disableMermaid, enableMermaid, isMermaidEnabled, setMermaidLoader, } from '../packages/markstream-angular/src/optional/mermaid' import { buildKaTeXCDNWorkerSource } from '../packages/markstream-angular/src/workers/katexCdnWorker' import { buildMermaidCDNWorkerSource } from '../packages/markstream-angular/src/workers/mermaidCdnWorker' describe('markstream-angular api parity helpers', () => { it('merges global and scoped custom components like react/vue2', () => { const GlobalCode = class {} const ScopedThinking = class {} clearGlobalCustomComponents() setCustomComponents({ code_block: GlobalCode as any }) setCustomComponents('scope-a', { thinking: ScopedThinking as any }) expect(getCustomNodeComponents()).toMatchObject({ code_block: GlobalCode, }) expect(getCustomNodeComponents('scope-a')).toMatchObject({ code_block: GlobalCode, thinking: ScopedThinking, }) removeCustomComponents('scope-a') clearGlobalCustomComponents() expect(getCustomNodeComponents('scope-a')).toEqual({}) }) it('supports toggling katex and mermaid loaders', () => { const katexLoader = () => ({ renderToString: () => '' }) const mermaidLoader = () => ({ render: async () => ({ svg: '' }), initialize() {}, }) disableKatex() disableMermaid() expect(isKatexEnabled()).toBe(false) expect(isMermaidEnabled()).toBe(false) enableKatex(katexLoader) enableMermaid(mermaidLoader) expect(isKatexEnabled()).toBe(true) expect(isMermaidEnabled()).toBe(true) setKatexLoader(null) setMermaidLoader(null) expect(isKatexEnabled()).toBe(false) expect(isMermaidEnabled()).toBe(false) enableKatex() enableMermaid() }) it('passes customHtmlTags through top-level streaming parsing', () => { const nodes = resolveParsedNodes({ content: '我们需要给出一个全面的回答', final: false, customHtmlTags: ['thinking'], }) expect(nodes).toHaveLength(1) expect((nodes[0] as any)?.type).toBe('thinking') expect((nodes[0] as any)?.loading).toBe(true) expect(String((nodes[0] as any)?.content || '')).toContain('全面的回答') }) it('keeps streaming thinking content grouped across mixed markdown prefixes', () => { const source = ` 我们需要给出一个全面的回答,可能包括不同的实现方法及其分析。 $x = a$ - 递归 - 动态规划 ` for (const size of [16, 36, 72, source.length]) { const nodes = resolveParsedNodes({ content: source.slice(0, size), final: false, customHtmlTags: ['thinking'], }) expect(nodes).toHaveLength(1) expect((nodes[0] as any)?.type).toBe('thinking') expect(String((nodes[0] as any)?.content || '')).not.toContain('') } }) it('exposes worker helper source builders for katex and mermaid parity', () => { const katexSource = buildKaTeXCDNWorkerSource({ katexUrl: 'https://cdn.example.com/katex.mjs', mode: 'module', }) const mermaidSource = buildMermaidCDNWorkerSource({ mermaidUrl: 'https://cdn.example.com/mermaid.mjs', mode: 'module', }) expect(katexSource).toContain('renderToString') expect(katexSource).toContain('postMessage') expect(mermaidSource).toContain('findPrefix') expect(mermaidSource).toContain('canParse') }) })