/** * @vitest-environment node */ import katex from 'katex' import { afterEach, describe, expect, it } from 'vitest' import { createSSRApp, defineComponent, h } from 'vue' import { renderToString } from 'vue/server-renderer' import { disableD2, enableD2 } from '../src/components/D2BlockNode/d2' import HtmlBlockNode from '../src/components/HtmlBlockNode/HtmlBlockNode.vue' import { setInfographicLoader } from '../src/components/InfographicBlockNode/infographic' import MarkdownCodeBlockNode from '../src/components/MarkdownCodeBlockNode' import MathBlockNode from '../src/components/MathBlockNode' import MathInlineNode from '../src/components/MathInlineNode' import { disableKatex, enableKatex, setKatexLoader } from '../src/components/MathInlineNode/katex' import { disableMermaid, enableMermaid } from '../src/components/MermaidBlockNode/mermaid' import MarkdownRender from '../src/components/NodeRenderer' import { clearGlobalCustomComponents, setCustomComponents } from '../src/utils/nodeComponents' const BASIC_SCOPE = 'ssr-render-basic' const defaultInfographicLoader = () => import('@antv/infographic') async function renderComponent(component: any, props: Record) { const app = createSSRApp({ render: () => h(component, props), }) return renderToString(app) } async function renderMarkdown(content: string, props: Record = {}) { return renderComponent(MarkdownRender, { content, final: true, ...props, }) } function textNode(content: string) { return { type: 'text', content, raw: content, } } function paragraphNode(children: any[], raw?: string) { return { type: 'paragraph', children, raw: raw ?? children.map(child => String(child?.raw ?? child?.content ?? '')).join(''), } } function inlineWrapper(type: string, content: string, raw: string) { return { type, children: [textNode(content)], raw, } } function linkNode(content: string, href: string) { return { type: 'link', href, title: href, text: content, raw: `[${content}](${href})`, children: [textNode(content)], } } describe('ssr renderToString coverage', () => { afterEach(() => { clearGlobalCustomComponents() enableKatex() enableMermaid() enableD2() setInfographicLoader(defaultInfographicLoader) }) it('renders basic markdown, HTML, and custom HTML tags on the server', async () => { setCustomComponents(BASIC_SCOPE, { 'ssr-badge': defineComponent({ name: 'SsrBadgeNode', setup(_, { slots }) { return () => h('span', { 'data-ssr-custom': 'badge' }, slots.default?.() ?? []) }, }), }) const html = await renderMarkdown(` # SSR Basics Inline HTML: inline html Trusted custom tag: server custom tag - [x] done - [ ] pending | Name | Role | | --- | --- | | Markstream | Renderer | Visit [Vue](https://vuejs.org). ![Markstream icon](/vue-markdown-icon.svg "Markstream icon")
HTML block is present.
Footnotes are server-rendered.[^1] [^1]: Footnote body `.trim(), { customHtmlTags: ['ssr-badge'], customId: BASIC_SCOPE, }) expect(html).toContain(' { const html = await renderMarkdown(` - alpha - beta `) expect(html).toContain(' { const html = await renderMarkdown(`
- alpha
`) expect(html.match(/
    { const html = await renderComponent(HtmlBlockNode, { node: { type: 'html_block', tag: 'details', content: '
    展开看一段 HTML

    body

    ', attrs: [['open', '']], children: [ { type: 'html_block', tag: 'summary', content: '展开看一段 HTML', children: [ { type: 'paragraph', raw: '展开看一段 HTML', children: [{ type: 'text', content: '展开看一段 HTML', raw: '展开看一段 HTML' }], }, ], }, { type: 'paragraph', raw: 'body', children: [{ type: 'text', content: 'body', raw: 'body' }], }, ], }, }) expect(html).toMatch(/]*class="html-block-node"[^>]*>(?:|)*]*>/) expect(html).not.toContain('class="markstream-vue markdown-renderer"') expect(html).not.toContain('node-slot') expect(html).not.toContain('data-node-index=') expect(html).not.toContain('