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:
+112
@@ -0,0 +1,112 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getMarkdown, parseMarkdownToStructure } from '../src'
|
||||
|
||||
function collectByType(nodes: any[], type: string) {
|
||||
const out: any[] = []
|
||||
const walk = (node: any) => {
|
||||
if (!node)
|
||||
return
|
||||
if (Array.isArray(node)) {
|
||||
node.forEach(walk)
|
||||
return
|
||||
}
|
||||
if (node.type === type)
|
||||
out.push(node)
|
||||
if (Array.isArray(node.children))
|
||||
node.children.forEach(walk)
|
||||
if (Array.isArray(node.items))
|
||||
node.items.forEach(walk)
|
||||
}
|
||||
walk(nodes)
|
||||
return out
|
||||
}
|
||||
|
||||
function collectText(nodes: any[]) {
|
||||
return collectByType(nodes, 'text').map((node: any) => String(node.content ?? '')).join('')
|
||||
}
|
||||
|
||||
describe('numeric reference streaming stability', () => {
|
||||
it('keeps completed numeric references stable during char-by-char chained references', () => {
|
||||
const md = getMarkdown('numeric-reference-streaming-chain')
|
||||
const full = '随便输入一些文字[1][2][3]'
|
||||
const firstReady = full.indexOf('[1]') + '[1]'.length
|
||||
const secondOpens = full.indexOf('[2]')
|
||||
const secondReady = secondOpens + '[2]'.length
|
||||
const thirdReady = full.indexOf('[3]') + '[3]'.length
|
||||
|
||||
for (let i = 1; i <= full.length; i++) {
|
||||
const chunk = full.slice(0, i)
|
||||
const nodes = parseMarkdownToStructure(chunk, md, { final: false })
|
||||
const ids = collectByType(nodes, 'reference').map((node: any) => String(node.id ?? ''))
|
||||
|
||||
if (i >= firstReady)
|
||||
expect(ids[0], `prefix(${i}): ${chunk}`).toBe('1')
|
||||
if (i > secondOpens)
|
||||
expect(ids[0], `prefix(${i}): ${chunk}`).toBe('1')
|
||||
if (i >= secondReady)
|
||||
expect(ids.slice(0, 2), `prefix(${i}): ${chunk}`).toEqual(['1', '2'])
|
||||
if (i >= thirdReady)
|
||||
expect(ids.slice(0, 3), `prefix(${i}): ${chunk}`).toEqual(['1', '2', '3'])
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps the numeric reference once an adjacent inline link becomes distinguishable', () => {
|
||||
const md = getMarkdown('numeric-reference-streaming-link')
|
||||
const full = '随便输入一些文字 [1][百度](www.baidu.com)'
|
||||
const distinguishableAt = full.indexOf('[百度](') + '[百度]('.length
|
||||
|
||||
for (let i = 1; i <= full.length; i++) {
|
||||
const chunk = full.slice(0, i)
|
||||
const nodes = parseMarkdownToStructure(chunk, md, { final: false })
|
||||
const ids = collectByType(nodes, 'reference').map((node: any) => String(node.id ?? ''))
|
||||
const links = collectByType(nodes, 'link')
|
||||
|
||||
if (i >= distinguishableAt) {
|
||||
expect(ids[0], `prefix(${i}): ${chunk}`).toBe('1')
|
||||
expect(collectText(nodes), `prefix(${i}): ${chunk}`).not.toContain('[1]')
|
||||
expect(links, `prefix(${i}): ${chunk}`).toHaveLength(1)
|
||||
expect(String(links[0]?.text ?? ''), `prefix(${i}): ${chunk}`).toContain('百度')
|
||||
expect(Boolean(links[0]?.loading), `prefix(${i}): ${chunk}`).toBe(i < full.length)
|
||||
}
|
||||
}
|
||||
|
||||
const finalNodes = parseMarkdownToStructure(full, md, { final: true })
|
||||
const finalIds = collectByType(finalNodes, 'reference').map((node: any) => String(node.id ?? ''))
|
||||
const links = collectByType(finalNodes, 'link')
|
||||
|
||||
expect(finalIds).toEqual(['1'])
|
||||
expect(links).toHaveLength(1)
|
||||
expect(String(links[0]?.href ?? '')).toBe('www.baidu.com')
|
||||
})
|
||||
|
||||
it('keeps the completed numeric reference while trailing text streams in', () => {
|
||||
const md = getMarkdown('numeric-reference-streaming-suffix')
|
||||
const full = '随便输入一些文字[1]后面还有文字'
|
||||
const readyAt = full.indexOf('[1]') + '[1]'.length
|
||||
|
||||
for (let i = readyAt; i <= full.length; i++) {
|
||||
const chunk = full.slice(0, i)
|
||||
const nodes = parseMarkdownToStructure(chunk, md, { final: false })
|
||||
const ids = collectByType(nodes, 'reference').map((node: any) => String(node.id ?? ''))
|
||||
|
||||
expect(ids[0], `prefix(${i}): ${chunk}`).toBe('1')
|
||||
}
|
||||
})
|
||||
|
||||
it('still respects validateLink when the trailing markdown link is being recovered', () => {
|
||||
const md = getMarkdown('numeric-reference-streaming-validate')
|
||||
md.set?.({ validateLink: (url: string) => !/^\s*javascript:/i.test(url.trim()) })
|
||||
|
||||
const chunk = '随便输入一些文字 [1][百度](javascript:alert(1'
|
||||
const nodes = parseMarkdownToStructure(chunk, md, { final: false })
|
||||
const ids = collectByType(nodes, 'reference').map((node: any) => String(node.id ?? ''))
|
||||
const links = collectByType(nodes, 'link')
|
||||
|
||||
expect(ids).toEqual(['1'])
|
||||
expect(links).toHaveLength(1)
|
||||
expect(String(links[0]?.text ?? '')).toBe('百度')
|
||||
expect(String(links[0]?.href ?? '')).not.toMatch(/javascript:/i)
|
||||
expect(Boolean(links[0]?.loading)).toBe(true)
|
||||
expect(collectText(nodes)).toContain('百度')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user