e0a5f57793
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.
259 lines
8.7 KiB
TypeScript
259 lines
8.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { textIncludes } from '../../../test/utils/midstate-utils'
|
|
import { getMarkdown, parseMarkdownToStructure } from '../src'
|
|
|
|
const md = getMarkdown('test')
|
|
|
|
function collectByType(nodes: any[], type: string) {
|
|
const out: any[] = []
|
|
const walk = (n: any) => {
|
|
if (!n)
|
|
return
|
|
if (n.type === type)
|
|
out.push(n)
|
|
if (Array.isArray(n.children))
|
|
n.children.forEach(walk)
|
|
if (Array.isArray(n.items))
|
|
n.items.forEach(walk)
|
|
}
|
|
nodes.forEach(walk)
|
|
return out
|
|
}
|
|
|
|
describe('special link cases', () => {
|
|
it('parses link with numeric brackets in link text: [[8]](url)', () => {
|
|
const markdown = '建议采用以下接口方案:[[8]](https://markstream-vue-docs.simonhe.me/)'
|
|
const nodes = parseMarkdownToStructure(markdown, md)
|
|
|
|
// Collect all link nodes
|
|
const links: any[] = []
|
|
const walk = (n: any) => {
|
|
if (!n)
|
|
return
|
|
if (n.type === 'link')
|
|
links.push(n)
|
|
if (Array.isArray(n.children))
|
|
n.children.forEach(walk)
|
|
if (Array.isArray(n.items))
|
|
n.items.forEach(walk)
|
|
}
|
|
nodes.forEach(walk)
|
|
|
|
// Should have exactly one link
|
|
expect(links.length).toBe(1)
|
|
const link = links[0]
|
|
|
|
// Verify link properties
|
|
expect(link.href).toBe('https://markstream-vue-docs.simonhe.me/')
|
|
expect(link.loading).toBe(false)
|
|
|
|
// Verify link text contains [8]
|
|
// The link text should be "[8]" (the brackets are part of the link text)
|
|
const linkText = link.text || ''
|
|
const childText = (link.children || [])
|
|
.map((c: any) => (c.content ?? c.text ?? ''))
|
|
.join('')
|
|
const fullText = linkText || childText
|
|
|
|
expect(fullText).toBe('[8]')
|
|
|
|
// Verify the paragraph contains both the prefix text and the link
|
|
const para = nodes.find((n: any) => n.type === 'paragraph') as any
|
|
expect(para).toBeTruthy()
|
|
expect(textIncludes(para, '建议采用以下接口方案:')).toBe(true)
|
|
expect(para.children.some((c: any) => c.type === 'link')).toBe(true)
|
|
})
|
|
|
|
it('parses link with emphasis markers in link text: [DR **(Danmarks Radio)](url)', () => {
|
|
const markdown = '[DR **(Danmarks Radio)](https://www.dr.dk/nyheder)'
|
|
const nodes = parseMarkdownToStructure(markdown, md)
|
|
|
|
// Collect all link nodes
|
|
const links: any[] = []
|
|
const walk = (n: any) => {
|
|
if (!n)
|
|
return
|
|
if (n.type === 'link')
|
|
links.push(n)
|
|
if (Array.isArray(n.children))
|
|
n.children.forEach(walk)
|
|
if (Array.isArray(n.items))
|
|
n.items.forEach(walk)
|
|
}
|
|
nodes.forEach(walk)
|
|
|
|
// Should have exactly one link
|
|
expect(links.length).toBe(1)
|
|
const link = links[0]
|
|
|
|
// Verify link properties
|
|
expect(link.href).toBe('https://www.dr.dk/nyheder')
|
|
expect(link.loading).toBe(false)
|
|
|
|
// Should contain "DR" and "Danmarks Radio"
|
|
expect(textIncludes(link, 'DR')).toBe(true)
|
|
expect(textIncludes(link, 'Danmarks Radio')).toBe(true)
|
|
expect(textIncludes(link, '(Danmarks Radio)')).toBe(true)
|
|
|
|
// Verify the paragraph contains the link
|
|
const para = nodes.find((n: any) => n.type === 'paragraph') as any
|
|
expect(para).toBeTruthy()
|
|
expect(para.children.some((c: any) => c.type === 'link')).toBe(true)
|
|
})
|
|
|
|
it('parses multiple links with special characters in same paragraph', () => {
|
|
const markdown = '建议采用以下接口方案:[[8]](https://markstream-vue-docs.simonhe.me/)\n\n[DR **(Danmarks Radio)](https://www.dr.dk/nyheder)'
|
|
const nodes = parseMarkdownToStructure(markdown, md)
|
|
|
|
// Collect all link nodes
|
|
const links: any[] = []
|
|
const walk = (n: any) => {
|
|
if (!n)
|
|
return
|
|
if (n.type === 'link')
|
|
links.push(n)
|
|
if (Array.isArray(n.children))
|
|
n.children.forEach(walk)
|
|
if (Array.isArray(n.items))
|
|
n.items.forEach(walk)
|
|
}
|
|
nodes.forEach(walk)
|
|
|
|
// Should have exactly two links
|
|
expect(links.length).toBe(2)
|
|
|
|
// First link: [[8]](url)
|
|
const firstLink = links.find(l => l.href === 'https://markstream-vue-docs.simonhe.me/')
|
|
expect(firstLink).toBeDefined()
|
|
expect(firstLink?.loading).toBe(false)
|
|
const firstLinkText = firstLink?.text || (firstLink?.children || []).map((c: any) => c.content || '').join('')
|
|
expect(firstLinkText).toBe('[8]')
|
|
|
|
// Second link: [DR **(Danmarks Radio)](url)
|
|
const secondLink = links.find(l => l.href === 'https://www.dr.dk/nyheder')
|
|
expect(secondLink).toBeDefined()
|
|
expect(secondLink?.loading).toBe(false)
|
|
expect(textIncludes(secondLink, 'DR')).toBe(true)
|
|
expect(textIncludes(secondLink, 'Danmarks Radio')).toBe(true)
|
|
})
|
|
|
|
it('parses standalone numeric bracket link without prefix text', () => {
|
|
const markdown = '[[8]](https://example.com)'
|
|
const nodes = parseMarkdownToStructure(markdown, md)
|
|
|
|
const links: any[] = []
|
|
const walk = (n: any) => {
|
|
if (!n)
|
|
return
|
|
if (n.type === 'link')
|
|
links.push(n)
|
|
if (Array.isArray(n.children))
|
|
n.children.forEach(walk)
|
|
}
|
|
nodes.forEach(walk)
|
|
|
|
expect(links.length).toBe(1)
|
|
expect(links[0].href).toBe('https://example.com')
|
|
expect(links[0].text || links[0].children?.map((c: any) => c.content || '').join('')).toBe('[8]')
|
|
})
|
|
|
|
it('distinguishes between reference [8] and link [[8]](url)', () => {
|
|
// Standalone [8] should be a reference
|
|
const referenceMarkdown = '请参考 [8] 了解更多信息'
|
|
const referenceNodes = parseMarkdownToStructure(referenceMarkdown, md)
|
|
|
|
const references: any[] = []
|
|
const walkRef = (n: any) => {
|
|
if (!n)
|
|
return
|
|
if (n.type === 'reference')
|
|
references.push(n)
|
|
if (Array.isArray(n.children))
|
|
n.children.forEach(walkRef)
|
|
}
|
|
referenceNodes.forEach(walkRef)
|
|
|
|
// [8] as standalone should be parsed as reference (if reference rule matches)
|
|
// But if it's followed by ](, it should be a link
|
|
const linkMarkdown = '请参考 [[8]](https://example.com) 了解更多信息'
|
|
const linkNodes = parseMarkdownToStructure(linkMarkdown, md)
|
|
|
|
const links: any[] = []
|
|
const walkLink = (n: any) => {
|
|
if (!n)
|
|
return
|
|
if (n.type === 'link')
|
|
links.push(n)
|
|
if (Array.isArray(n.children))
|
|
n.children.forEach(walkLink)
|
|
}
|
|
linkNodes.forEach(walkLink)
|
|
|
|
// The link version should have a link node
|
|
expect(links.length).toBeGreaterThan(0)
|
|
const link = links.find(l => l.href === 'https://example.com')
|
|
expect(link).toBeDefined()
|
|
expect(link?.text || link?.children?.map((c: any) => c.content || '').join('')).toBe('[8]')
|
|
})
|
|
|
|
it('preserves normal reference-style links when the label is not numeric', () => {
|
|
const markdown = `请看 [1][source]
|
|
|
|
[source]: https://example.com`
|
|
const nodes = parseMarkdownToStructure(markdown, md)
|
|
const links = collectByType(nodes, 'link')
|
|
const references = collectByType(nodes, 'reference')
|
|
|
|
expect(links).toHaveLength(1)
|
|
expect(links[0].href).toBe('https://example.com')
|
|
expect(links[0].text || links[0].children?.map((c: any) => c.content || '').join('')).toBe('1')
|
|
expect(references).toHaveLength(0)
|
|
})
|
|
|
|
it('parses numeric references when they are adjacent to surrounding text', () => {
|
|
const cases = [
|
|
'随便输入一些文字[1]',
|
|
'随便输入一些文字[1]后面还有文字',
|
|
'随便输入一些文字[1] ',
|
|
'随便输入一些文字[1] (空格+文字)',
|
|
]
|
|
|
|
for (const markdown of cases) {
|
|
const nodes = parseMarkdownToStructure(markdown, md)
|
|
const references = collectByType(nodes, 'reference')
|
|
|
|
expect(references, markdown).toHaveLength(1)
|
|
expect(references[0].id).toBe('1')
|
|
expect(textIncludes(nodes, '随便输入一些文字')).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('parses a numeric reference immediately followed by a normal inline link', () => {
|
|
const cases = [
|
|
'随便输入一些文字 [1][百度](www.baidu.com)',
|
|
'随便输入一些文字 [1] [百度](www.baidu.com)',
|
|
]
|
|
|
|
for (const markdown of cases) {
|
|
const nodes = parseMarkdownToStructure(markdown, md)
|
|
const references = collectByType(nodes, 'reference')
|
|
const links = collectByType(nodes, 'link')
|
|
|
|
expect(references, markdown).toHaveLength(1)
|
|
expect(references[0].id).toBe('1')
|
|
expect(links, markdown).toHaveLength(1)
|
|
expect(links[0].href).toBe('www.baidu.com')
|
|
expect(textIncludes(links[0], '百度')).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('parses chained numeric references without requiring spaces', () => {
|
|
const nodes = parseMarkdownToStructure('随便输入一些文字[1][2][3]', md)
|
|
const references = collectByType(nodes, 'reference')
|
|
|
|
expect(references).toHaveLength(3)
|
|
expect(references.map(ref => ref.id)).toEqual(['1', '2', '3'])
|
|
expect(textIncludes(nodes, '随便输入一些文字')).toBe(true)
|
|
})
|
|
})
|