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.
256 lines
8.4 KiB
TypeScript
256 lines
8.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { getMarkdown, parseMarkdownToStructure } from '../src'
|
|
|
|
describe('vmr_container fallback', () => {
|
|
it('preserves args after container name as attrs', () => {
|
|
const md = getMarkdown('vmr_container_args')
|
|
const markdown = [
|
|
':::test 1944565882923520000;tag;2',
|
|
':::',
|
|
].join('\n')
|
|
|
|
const nodes = parseMarkdownToStructure(markdown, md) as any[]
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.name).toBe('test')
|
|
expect(nodes[0]?.attrs?.args).toBe('1944565882923520000;tag;2')
|
|
})
|
|
|
|
it('parses fenced code blocks inside ::: containers', () => {
|
|
const md = getMarkdown('vmr_container_fence')
|
|
const markdown = [
|
|
'::: viewcode:topo-test-001 {"devId":"f318206374eb4ac7a3fb3b4b042fd01d"}',
|
|
'```ts',
|
|
'console.log("hello")',
|
|
'```',
|
|
':::',
|
|
].join('\n')
|
|
|
|
const nodes = parseMarkdownToStructure(markdown, md) as any[]
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.name).toBe('viewcode:topo-test-001')
|
|
expect(nodes[0]?.attrs?.devId).toBe('f318206374eb4ac7a3fb3b4b042fd01d')
|
|
|
|
const children = nodes[0]?.children as any[]
|
|
expect(Array.isArray(children)).toBe(true)
|
|
|
|
const code = children.find(n => n?.type === 'code_block')
|
|
expect(code).toBeDefined()
|
|
expect(code.language).toBe('ts')
|
|
expect(String(code.code ?? '')).toContain('console.log')
|
|
})
|
|
|
|
it('parses plain text inside ::: containers', () => {
|
|
const md = getMarkdown('vmr_container_text')
|
|
const cases = ['::: viewcode:plain-text', ':::viewcode:plain-text']
|
|
|
|
for (const openLine of cases) {
|
|
const markdown = [
|
|
openLine,
|
|
'just some plain text',
|
|
':::',
|
|
].join('\n')
|
|
const nodes = parseMarkdownToStructure(markdown, md) as any[]
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.name).toBe('viewcode:plain-text')
|
|
const children = nodes[0]?.children as any[]
|
|
expect(children.length).toBe(1)
|
|
expect(children[0]?.type).toBe('paragraph')
|
|
expect(String(children[0]?.children?.[0]?.content ?? '')).toContain('plain text')
|
|
}
|
|
})
|
|
|
|
it('parses multiple blocks inside ::: containers', () => {
|
|
const md = getMarkdown('vmr_container_blocks')
|
|
const markdown = [
|
|
'::: viewcode:multi-block',
|
|
'First paragraph.',
|
|
'',
|
|
'```js',
|
|
'alert(123)',
|
|
'```',
|
|
'',
|
|
'- list item',
|
|
':::',
|
|
].join('\n')
|
|
const nodes = parseMarkdownToStructure(markdown, md) as any[]
|
|
// debug output
|
|
|
|
console.log('multi-block AST:', JSON.stringify(nodes, null, 2))
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.name).toBe('viewcode:multi-block')
|
|
const children = nodes[0]?.children as any[]
|
|
expect(children.some(n => n.type === 'paragraph')).toBe(true)
|
|
expect(children.some(n => n.type === 'code_block')).toBe(true)
|
|
expect(children.some(n => n.type === 'list')).toBe(true)
|
|
})
|
|
|
|
it('parses empty or whitespace-only containers', () => {
|
|
const md = getMarkdown('vmr_container_empty')
|
|
const markdown = [
|
|
'::: viewcode:empty',
|
|
' ',
|
|
':::',
|
|
].join('\n')
|
|
const nodes = parseMarkdownToStructure(markdown, md) as any[]
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.name).toBe('viewcode:empty')
|
|
expect(Array.isArray(nodes[0]?.children)).toBe(true)
|
|
expect(nodes[0]?.children.length).toBe(0)
|
|
})
|
|
|
|
it('parses empty or whitespace-only containers -1', () => {
|
|
const md = getMarkdown('vmr_container_empty')
|
|
const markdown = [
|
|
':::viewcode:empty',
|
|
' ',
|
|
':::',
|
|
].join('\n')
|
|
const nodes = parseMarkdownToStructure(markdown, md) as any[]
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.name).toBe('viewcode:empty')
|
|
expect(Array.isArray(nodes[0]?.children)).toBe(true)
|
|
expect(nodes[0]?.children.length).toBe(0)
|
|
})
|
|
|
|
it('keeps content immediately above ::: as a separate node', () => {
|
|
const md = getMarkdown('vmr_container_adjacent_above')
|
|
const cases = ['::: viewcode:plain-text', ':::viewcode:plain-text']
|
|
|
|
for (const openLine of cases) {
|
|
const markdown = [
|
|
'Above text',
|
|
openLine,
|
|
'Inside text',
|
|
':::',
|
|
].join('\n')
|
|
|
|
const nodes = parseMarkdownToStructure(markdown, md) as any[]
|
|
expect(nodes[0]?.type).toBe('paragraph')
|
|
expect(String(nodes[0]?.children?.[0]?.content ?? '')).toContain('Above text')
|
|
|
|
expect(nodes[1]?.type).toBe('vmr_container')
|
|
expect(nodes[1]?.name).toBe('viewcode:plain-text')
|
|
|
|
const children = nodes[1]?.children as any[]
|
|
expect(children.length).toBe(1)
|
|
expect(children[0]?.type).toBe('paragraph')
|
|
expect(String(children[0]?.children?.[0]?.content ?? '')).toContain('Inside text')
|
|
}
|
|
})
|
|
|
|
it('preserves complex JSON attrs (nested object/array)', () => {
|
|
const md = getMarkdown('vmr_container_complex_attrs')
|
|
const markdown = [
|
|
'::: viewcode:complex {"devId":"abc","meta":{"foo":1,"bar":{"baz":[1,2,{"x":3}]}},"arr":[1,"2",{"k":true}]}',
|
|
'Inside',
|
|
':::',
|
|
].join('\n')
|
|
|
|
const nodes = parseMarkdownToStructure(markdown, md) as any[]
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.name).toBe('viewcode:complex')
|
|
expect(nodes[0]?.attrs?.devId).toBe('abc')
|
|
expect(nodes[0]?.attrs?.meta).toMatchInlineSnapshot(`
|
|
{
|
|
"bar": {
|
|
"baz": [
|
|
1,
|
|
2,
|
|
{
|
|
"x": 3,
|
|
},
|
|
],
|
|
},
|
|
"foo": 1,
|
|
}
|
|
`)
|
|
expect(nodes[0]?.attrs?.arr).toMatchInlineSnapshot(`
|
|
[
|
|
1,
|
|
"2",
|
|
{
|
|
"k": true,
|
|
},
|
|
]
|
|
`)
|
|
})
|
|
|
|
it('emits loading=true when ::: container is not closed (streaming mid-state)', () => {
|
|
const md = getMarkdown('vmr_container_loading_mid')
|
|
const markdown = [
|
|
'::: viewcode:stream',
|
|
'partial line',
|
|
].join('\n')
|
|
|
|
const nodes = parseMarkdownToStructure(markdown, md, { final: false }) as any[]
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.name).toBe('viewcode:stream')
|
|
expect(nodes[0]?.loading).toBe(true)
|
|
})
|
|
|
|
it('sets loading=false once closing ::: is present', () => {
|
|
const md = getMarkdown('vmr_container_loading_closed')
|
|
const markdown = [
|
|
'::: viewcode:stream',
|
|
'content',
|
|
':::',
|
|
].join('\n')
|
|
|
|
const nodes = parseMarkdownToStructure(markdown, md, { final: false }) as any[]
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.loading).toBe(false)
|
|
})
|
|
|
|
it('forces loading=false when final=true even if closing ::: is missing', () => {
|
|
const md = getMarkdown('vmr_container_loading_final')
|
|
const markdown = [
|
|
'::: viewcode:stream',
|
|
'content',
|
|
].join('\n')
|
|
|
|
const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[]
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.loading).toBe(false)
|
|
})
|
|
|
|
it('parses attrs args payload in streaming mid-state (no closing :::)', () => {
|
|
const md = getMarkdown('vmr_container_attrs_args_payload')
|
|
const markdown = [
|
|
'::: viewcode:stream xxx;yyy;ddd',
|
|
].join('\n')
|
|
|
|
const nodes = parseMarkdownToStructure(markdown, md, { final: false }) as any[]
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.name).toBe('viewcode:stream')
|
|
expect(nodes[0]?.attrs?.args).toBe('xxx;yyy;ddd')
|
|
expect(nodes[0]?.loading).toBe(true)
|
|
})
|
|
|
|
it('parses loose attrs payload in streaming mid-state (no closing :::)', () => {
|
|
const md = getMarkdown('vmr_container_attrs_loose_object')
|
|
const markdown = [
|
|
'::: viewcode:stream {xxx:yyy}',
|
|
].join('\n')
|
|
|
|
const nodes = parseMarkdownToStructure(markdown, md, { final: false }) as any[]
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.name).toBe('viewcode:stream')
|
|
expect(nodes[0]?.attrs?.xxx).toBe('yyy')
|
|
expect(nodes[0]?.loading).toBe(true)
|
|
})
|
|
|
|
it('downgrades incomplete attrs payload to args in streaming mid-state attr', () => {
|
|
const md = getMarkdown('vmr_container_attrs_loose_object')
|
|
const markdown = [
|
|
'::: viewcode:stream {xxx:yyy',
|
|
].join('\n')
|
|
|
|
const nodes = parseMarkdownToStructure(markdown, md, { final: false }) as any[]
|
|
|
|
expect(nodes[0]?.type).toBe('vmr_container')
|
|
expect(nodes[0]?.name).toBe('viewcode:stream')
|
|
expect(nodes[0]?.attrs?.args).toBe('{xxx:yyy')
|
|
expect(nodes[0]?.loading).toBe(true)
|
|
})
|
|
})
|