import { describe, expect, it } from 'vitest' import { getMarkdown, parseMarkdownToStructure } from '../src' function firstHtml(nodes: any[]) { return nodes.find(n => n.type === 'html_block') } describe('html_block parser', () => { it('marks unclosed
', md)
const html = firstHtml(nodes) as any
expect(html.loading).toBe(false)
expect(String(html.content)).toContain('首先。。。。
') expect(summary?.tag).toBe('summary') expect(html?.children?.[1]?.type).toBe('paragraph') expect(html?.children?.[2]?.type).toBe('paragraph') }) it('keeps nested details grouped under the outer details container', () => { const markdown = `inner body
') } }) it('keeps complex markdown blocks inside details and preserves the closing newline', () => { const markdown = `') expect(String(html?.content ?? '')).toContain('data-lang="js"') expect(String(html?.content ?? '')).toContain('tail
') expect(html?.children?.map((child: any) => child?.type === 'html_block' ? `${child?.type}:${child?.tag}` : child?.type)) .toEqual(['html_block:summary', 'list', 'blockquote', 'code_block', 'paragraph']) }) it('parses markdown inside summary into summary children', () => { const markdown = `` const md = getMarkdown('html-block-details-summary-markdown') const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[] const html = firstHtml(nodes) as any const summary = html?.children?.[0] const summaryParagraph = summary?.children?.[0] expect(nodes).toHaveLength(1) expect(summary?.tag).toBe('summary') expect(html?.attrs).toEqual([['open', '']]) expect(summaryParagraph?.children?.[0]?.type).toBe('strong') expect(summaryParagraph?.children?.[0]?.children?.[0]?.content).toBe('sum') expect(summaryParagraph?.children?.[2]?.type).toBe('emphasis') expect(summaryParagraph?.children?.[2]?.children?.[0]?.content).toBe('x') expect(html?.children?.[1]?.children?.[0]?.content).toBe('body') }) it('keeps opener content before and after summary as structured children', () => { const markdown = `**sum** _x_
bodylead` const md = getMarkdown('html-block-details-opener-mixed') const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[] const html = firstHtml(nodes) as any expect(nodes).toHaveLength(1) expect(html?.attrs).toEqual([['open', '']]) expect(html?.children?.map((child: any) => child?.type === 'html_block' ? `${child?.type}:${child?.tag}` : child?.type)) .toEqual(['paragraph', 'html_block:summary', 'paragraph', 'html_block:div', 'paragraph']) expect(html?.children?.[0]?.children?.[0]?.content).toBe('lead') expect(html?.children?.[1]?.children?.[0]?.children?.[0]?.content).toBe('sum') expect(html?.children?.[2]?.children?.[0]?.content).toBe('text') expect(html?.children?.[3]?.tag).toBe('div') expect(html?.children?.[4]?.children?.[0]?.content).toBe('para') }) it('does not absorb content after a self-contained details block', () => { const markdown = `sum
textinnerpara\n\n我是通义千问,阿里巴巴集团旗下的超大规模语言模型。我能够帮助你回答问题、创作文字(如写故事、写公文、写邮件、写剧本等)、进行逻辑推理、编程等任务,并支持多语言交流。\n` const md = getMarkdown('html-block-details-self-contained') const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[] expect(nodes).toHaveLength(2) expect(nodes[0]?.type).toBe('html_block') expect(nodes[0]?.tag).toBe('details') expect(nodes[1]?.type).toBe('paragraph') }) it('self-contained details with no trailing content produces a single node', () => { const markdown = `Thinking...
好的,我现在需要处理用户的问题:"你是谁"。首先,我要按照用户设定的规则来回答。用户要求先检查提供的上下文(context)中的信息,如果在里面找不到答案,再用 \n\n` const md = getMarkdown('html-block-self-contained-no-trailing') const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[] expect(nodes).toHaveLength(1) expect(nodes[0]?.type).toBe('html_block') expect(nodes[0]?.tag).toBe('details') expect((nodes[0] as any)?.loading).toBe(false) }) it('self-contained details followed by a heading keeps them separate', () => { const markdown = `S
body \n\n\n# Heading\n` const md = getMarkdown('html-block-self-contained-heading') const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[] expect(nodes).toHaveLength(2) expect(nodes[0]?.type).toBe('html_block') expect(nodes[0]?.tag).toBe('details') expect(nodes[1]?.type).toBe('heading') }) it('two consecutive self-contained details blocks stay separate', () => { const markdown = `S
body \n\n\nA
aaa \n\n` const md = getMarkdown('html-block-self-contained-consecutive') const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[] expect(nodes).toHaveLength(2) expect(nodes[0]?.type).toBe('html_block') expect(nodes[0]?.tag).toBe('details') expect(nodes[1]?.type).toBe('html_block') expect(nodes[1]?.tag).toBe('details') }) it('self-contained details followed by a multi-line details block stay separate', () => { const markdown = `B
bbb \n\n\nA
aaa \n\n\n` const md = getMarkdown('html-block-self-contained-then-multiline') const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[] expect(nodes).toHaveLength(2) expect(nodes[0]?.type).toBe('html_block') expect(nodes[0]?.tag).toBe('details') expect(nodes[1]?.type).toBe('html_block') expect(nodes[1]?.tag).toBe('details') expect(String(nodes[1]?.content ?? '')).toContain('B
\n\nbbb\nbbb
') }) it('self-contained details followed by a code block stay separate', () => { const markdown = '\n\n```js\nconsole.log(1)\n```\n' const md = getMarkdown('html-block-self-contained-code') const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[] expect(nodes).toHaveLength(2) expect(nodes[0]?.type).toBe('html_block') expect(nodes[0]?.tag).toBe('details') expect(nodes[1]?.type).toBe('code_block') }) it('self-contained details in streaming mode does not absorb following content', () => { const markdown = `S
body \n\n\n我是通义千问\n` for (const final of [false, true]) { const md = getMarkdown(`html-block-self-contained-stream-${final}`) const nodes = parseMarkdownToStructure(markdown, md, { final }) as any[] expect(nodes).toHaveLength(2) expect(nodes[0]?.type).toBe('html_block') expect(nodes[0]?.tag).toBe('details') expect((nodes[0] as any)?.loading).toBe(false) expect(nodes[1]?.type).toBe('paragraph') } }) it('self-contained details preserves attributes correctly', () => { const markdown = `S
body \n\n\npara\n` const md = getMarkdown('html-block-self-contained-attrs') const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[] expect(nodes).toHaveLength(2) const details = nodes[0] as any expect(details?.tag).toBe('details') expect(details?.attrs).toEqual([['style', 'background:#f8f8f8'], ['open', '']]) expect(details?.children?.find((c: any) => c?.tag === 'summary')).toBeDefined() expect(nodes[1]?.type).toBe('paragraph') }) it('self-contained details preserves summary children and body text', () => { const markdown = `S
body \n\n\n我是通义千问\n` const md = getMarkdown('html-block-self-contained-children') const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[] expect(nodes).toHaveLength(2) const details = nodes[0] as any expect(details?.tag).toBe('details') expect(details?.loading).toBe(false) const summary = details?.children?.find((c: any) => c?.tag === 'summary') expect(summary).toBeDefined() expect(nodes[1]?.type).toBe('paragraph') }) it('self-contained details followed by multiple paragraphs stays separate from all', () => { const markdown = `Thinking...
好的 \n\n\npara1\n\npara2\n` const md = getMarkdown('html-block-self-contained-multi-para') const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[] expect(nodes.length).toBeGreaterThanOrEqual(2) expect(nodes[0]?.type).toBe('html_block') expect(nodes[0]?.tag).toBe('details') for (let k = 1; k < nodes.length; k++) { expect(nodes[k]?.type).not.toBe('html_block') } }) })S
body \n