import { describe, expect, it } from 'vitest' import { getMarkdown, parseMarkdownToStructure } from '../src' describe('issue 380 html wrapper markdown regression', () => { it('keeps markdown nested in a span wrapper as structured children without leaking duplicate top-level nodes', () => { const markdown = ` 🗺️【环境状态】 - 地点:石溪村,李东的茅屋 - 时间:4/12 周四 上午07:00 - 环境:阳光明媚,空气清新,茅屋内简陋但整洁,远处山峦笼罩在薄雾中,偶尔传来鸟鸣声 📊【角色状态】 - 生理:健康(轻微饥饿) - 物品:锄头(自家)、简易弓箭(自家)、5个铜币(自家)、一些干粮(自家) - 技能:农耕(基础)、陷阱设置(基础)、生存知识(基础) - 关系:与顾客关系良好(+) 📖【目标与线索】 - 已知线索:无 - 短期目标:无 - 长期目标:无 *** 🎯【选项】 1. 去田里劳作,争取多收成些粮食卖钱 2. 上山检查之前设置的陷阱,看有没有捕到猎 3. 直接去清风镇看看情况,虽然路途遥远 4. …… ` const md = getMarkdown('issue-380-structured') 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('span') expect(nodes[0]?.attrs).toEqual([['style', 'font-size: 12px;']]) expect(nodes[0]?.raw).toBe(markdown) expect(nodes[0]?.children?.map((child: any) => child?.type)).toEqual([ 'paragraph', 'list', 'paragraph', 'list', 'paragraph', 'list', 'thematic_break', 'paragraph', 'list', ]) expect(nodes[0]?.children?.[0]?.children?.[0]?.content).toBe('🗺️【环境状态】') expect(nodes[0]?.children?.[1]?.items).toHaveLength(3) expect(nodes[0]?.children?.[8]?.ordered).toBe(true) expect(nodes[0]?.children?.[8]?.items).toHaveLength(4) }) it('structures simple block markdown inside span and removes leaked list duplicates', () => { const markdown = ` - a - b ` const md = getMarkdown('issue-380-simple') 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('span') expect(nodes[0]?.children?.map((child: any) => child?.type)).toEqual(['list']) expect(nodes[0]?.children?.[0]?.items).toHaveLength(2) expect(nodes[0]?.children?.[0]?.items?.[0]?.children?.[0]?.children?.[0]?.content).toBe('a') expect(nodes[0]?.children?.[0]?.items?.[1]?.children?.[0]?.children?.[0]?.content).toBe('b') }) it('keeps pure html wrappers unstructured when they do not actually contain markdown blocks', () => { const markdown = '
Block HTML
' const md = getMarkdown('issue-380-pure-html') 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('div') expect(nodes[0]?.children).toBeUndefined() }) it('does not structure blocked tags even when their inner content looks like markdown blocks', () => { const markdown = `` const md = getMarkdown('issue-380-blocked-tag') 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('script') expect(nodes[0]?.children).toBeUndefined() expect(nodes[0]?.raw).toBe(markdown) }) it('keeps literal-content tags unstructured even when their inner content looks like markdown blocks', () => { const samples = ['pre', 'style', 'textarea'] as const for (const tag of samples) { const markdown = `<${tag}> - a - b ` const md = getMarkdown(`issue-380-literal-${tag}`) 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(tag) expect(nodes[0]?.children).toBeUndefined() expect(nodes[0]?.raw).toBe(markdown) } }) it('structures nested html wrappers when the inner wrapper already contains markdown blocks', () => { const markdown = `
- a
` const md = getMarkdown('issue-380-nested') 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('div') expect(nodes[0]?.children?.map((child: any) => child?.type)).toEqual(['html_block']) expect(nodes[0]?.children?.[0]?.tag).toBe('div') expect(nodes[0]?.children?.[0]?.children?.map((child: any) => child?.type)).toEqual(['list']) expect(nodes[0]?.children?.[0]?.children?.[0]?.items).toHaveLength(1) expect(nodes[0]?.children?.[0]?.children?.[0]?.items?.[0]?.children?.[0]?.children?.[0]?.content).toBe('a') }) it('keeps streaming span wrappers stable before the closing tag arrives', () => { const markdown = ` - a - b` const md = getMarkdown('issue-380-streaming') const nodes = parseMarkdownToStructure(markdown, md, { final: false }) as any[] expect(nodes).toHaveLength(1) expect(nodes[0]?.type).toBe('html_block') expect(nodes[0]?.tag).toBe('span') expect(nodes[0]?.loading).toBe(true) expect(nodes[0]?.children?.map((child: any) => child?.type)).toEqual(['list']) expect(nodes[0]?.children?.[0]?.items).toHaveLength(2) }) })