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.
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user