Files
BoosAPI/desktop/libs/markstream-vue/packages/markdown-parser/test/issue-386-inline-math-superscript-reference.test.ts
boos e0a5f57793 feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
2026-06-22 11:32:57 +08:00

137 lines
4.3 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { getMarkdown, parseMarkdownToStructure } from '../src'
const REAL_WORLD_MULTILINE_INPUT = `$2.897771955 times 10^{-3}text{m·K}$^[1]^
测试<sup>[3]</sup>。
$x$^[1]^
$x$ ^[1]^
测试^[1]^
$2.897771955 \\times 10^{-3}\\text{m·K}$^[1]^
<sup>[1]</sup>
测试<sup>[12]</sup>结束
A<sup>[3]</sup>B
$x$^[1]^
测试^[1]^
<sup>[3]</sup>
测试<sup>[12]</sup>结束`
function parse(markdown: string, final = true) {
const md = getMarkdown('issue-386')
return parseMarkdownToStructure(markdown, md, { final }) as any[]
}
function collectByType(value: any, targetType: string): any[] {
const out: any[] = []
const walk = (node: any) => {
if (!node)
return
if (Array.isArray(node)) {
node.forEach(walk)
return
}
if (node.type === targetType)
out.push(node)
if (Array.isArray(node.children))
node.children.forEach(walk)
if (Array.isArray(node.items))
node.items.forEach(walk)
}
walk(value)
return out
}
describe('issue #386 inline math suffix regressions', () => {
it.each([true, false])('keeps superscript syntax immediately after inline math when final=%s', (final) => {
const nodes = parse('$2.897771955 times 10^{-3}text{m·K}$^[1]^', final)
const paragraph = nodes[0] as any
expect(paragraph?.type).toBe('paragraph')
expect(paragraph?.children?.map((child: any) => child.type)).toEqual([
'math_inline',
'superscript',
])
expect(paragraph?.children?.[1]?.children?.map((child: any) => child.content).join('')).toBe('[1]')
const textNodes = collectByType(paragraph, 'text')
expect(textNodes.some((node: any) => node.content === '^[1]^')).toBe(false)
})
it.each([true, false])('keeps footnote references immediately after inline math when final=%s', (final) => {
const nodes = parse('$x$[^1]\n\n[^1]: note', final)
const paragraph = nodes[0] as any
expect(paragraph?.type).toBe('paragraph')
expect(paragraph?.children?.map((child: any) => child.type)).toEqual([
'math_inline',
'footnote_reference',
])
expect(paragraph?.children?.[1]?.id).toBe('1')
const footnotes = collectByType(nodes, 'footnote')
expect(footnotes).toHaveLength(1)
expect(footnotes[0]?.id).toBe('1')
expect(collectByType(footnotes[0], 'text').some((node: any) => node.content === 'note')).toBe(true)
})
it.each([true, false])('preserves brackets inside sup html content when final=%s', (final) => {
const nodes = parse('测试<sup>[3]</sup>。', final)
const paragraph = nodes[0] as any
const htmlInline = collectByType(paragraph, 'html_inline')[0] as any
expect(htmlInline).toBeDefined()
expect(htmlInline.content).toBe('<sup>[3]</sup>')
expect(htmlInline.children).toEqual([
{
type: 'text',
content: '[3]',
raw: '[3]',
},
])
})
it.each([true, false])('preserves brackets inside generic inline html content when final=%s', (final) => {
const nodes = parse('<span>[3]</span>', final)
const paragraph = nodes[0] as any
const htmlInline = collectByType(paragraph, 'html_inline')[0] as any
expect(htmlInline).toBeDefined()
expect(htmlInline.content).toBe('<span>[3]</span>')
expect(htmlInline.children).toEqual([
{
type: 'text',
content: '[3]',
raw: '[3]',
},
])
})
it.each([true, false])('parses the real multiline issue-386 input without leaking raw superscript syntax when final=%s', (final) => {
const nodes = parse(REAL_WORLD_MULTILINE_INPUT, final)
const paragraph = nodes[0] as any
const superscripts = collectByType(paragraph, 'superscript')
const htmlInlineNodes = collectByType(paragraph, 'html_inline')
const textNodes = collectByType(paragraph, 'text')
expect(paragraph?.type).toBe('paragraph')
expect(superscripts).toHaveLength(7)
expect(superscripts.map((node: any) => node.children?.map((child: any) => child.content).join(''))).toEqual([
'[1]',
'[1]',
'[1]',
'[1]',
'[1]',
'[1]',
'[1]',
])
expect(textNodes.some((node: any) => String(node.content).includes('^[1]^'))).toBe(false)
expect(htmlInlineNodes.map((node: any) => node.children?.map((child: any) => child.content).join(''))).toEqual([
'[3]',
'[1]',
'[12]',
'[3]',
'[3]',
'[12]',
])
})
})