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.
176 lines
6.1 KiB
TypeScript
176 lines
6.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { hasCompleteHtmlTagContent } from '../packages/markstream-angular/src/components/shared/node-helpers'
|
|
import {
|
|
coerceBuiltinHtmlNode,
|
|
coerceCustomHtmlNode,
|
|
resolveNodeOutletCodeMode,
|
|
resolveNodeOutletCustomComponent,
|
|
resolveNodeOutletCustomInputs,
|
|
} from '../packages/markstream-angular/src/components/shared/node-outlet-helpers'
|
|
|
|
class ExactLanguageComponent {}
|
|
class GenericCodeBlockComponent {}
|
|
class MermaidComponent {}
|
|
class D2Component {}
|
|
class D2LangComponent {}
|
|
|
|
describe('markstream-angular NodeOutlet', () => {
|
|
it('coerces custom html tags into tag-typed nodes for custom components', () => {
|
|
const node = {
|
|
type: 'html_block',
|
|
tag: 'thinking',
|
|
content: '<thinking>inner body</thinking>',
|
|
raw: '<thinking>inner body</thinking>',
|
|
} as any
|
|
|
|
expect((coerceCustomHtmlNode(node) as any).type).toBe('thinking')
|
|
expect((coerceCustomHtmlNode(node) as any).tag).toBe('thinking')
|
|
expect((coerceCustomHtmlNode(node) as any).content).toBe('inner body')
|
|
expect((coerceBuiltinHtmlNode(node, 'html_block') as any).type).toBe('html_block')
|
|
expect((coerceBuiltinHtmlNode(node, 'html_block') as any).content).toBe('<thinking>inner body</thinking>')
|
|
})
|
|
|
|
it('preserves standard html wrappers for builtin html nodes', () => {
|
|
const node = {
|
|
type: 'html_block',
|
|
tag: 'details',
|
|
content: '<details><summary>More</summary><p>Body</p></details>',
|
|
raw: '<details><summary>More</summary><p>Body</p></details>',
|
|
} as any
|
|
|
|
expect((coerceBuiltinHtmlNode(node, 'html_block') as any).content).toBe('<details><summary>More</summary><p>Body</p></details>')
|
|
})
|
|
|
|
it('only escapes malformed unknown html tags', () => {
|
|
expect(hasCompleteHtmlTagContent('<question>ok</question>', 'question')).toBe(true)
|
|
expect(hasCompleteHtmlTagContent('<question>ok', 'question')).toBe(false)
|
|
})
|
|
|
|
it('routes heavy node props by code block mode', () => {
|
|
const context = {
|
|
codeBlockProps: { showHeader: false },
|
|
mermaidProps: { renderDebounceMs: 180 },
|
|
d2Props: { themeId: 7 },
|
|
infographicProps: { showHeader: false },
|
|
events: {},
|
|
}
|
|
|
|
let node = {
|
|
type: 'code_block',
|
|
language: 'mermaid',
|
|
code: 'graph TD\nA-->B\n',
|
|
} as any
|
|
expect(resolveNodeOutletCodeMode(node, context as any)).toBe('mermaid')
|
|
expect(resolveNodeOutletCustomInputs(node, context as any)).toEqual({ renderDebounceMs: 180, estimatedPreviewHeightPx: 360 })
|
|
|
|
node = {
|
|
type: 'code_block',
|
|
language: 'd2',
|
|
code: 'a -> b',
|
|
} as any
|
|
expect(resolveNodeOutletCodeMode(node, context as any)).toBe('d2')
|
|
expect(resolveNodeOutletCustomInputs(node, context as any)).toEqual({ themeId: 7 })
|
|
|
|
node = {
|
|
type: 'code_block',
|
|
language: 'infographic',
|
|
code: 'infographic list-row-simple-horizontal-arrow',
|
|
} as any
|
|
expect(resolveNodeOutletCodeMode(node, context as any)).toBe('infographic')
|
|
expect(resolveNodeOutletCustomInputs(node, context as any)).toEqual({ showHeader: false, estimatedPreviewHeightPx: 360 })
|
|
|
|
node = {
|
|
type: 'code_block',
|
|
language: 'ts',
|
|
code: 'const value = 1',
|
|
} as any
|
|
expect(resolveNodeOutletCodeMode(node, context as any)).toBe('code')
|
|
expect(resolveNodeOutletCustomInputs(node, context as any)).toEqual({ showHeader: false })
|
|
})
|
|
|
|
it('prefers exact language custom components over code_block fallback', () => {
|
|
const context = {
|
|
customComponents: {
|
|
echarts: ExactLanguageComponent as any,
|
|
code_block: GenericCodeBlockComponent as any,
|
|
},
|
|
codeBlockProps: { showHeader: false },
|
|
events: {},
|
|
} as any
|
|
|
|
let node = {
|
|
type: 'code_block',
|
|
language: 'echarts',
|
|
code: 'option = {}',
|
|
} as any
|
|
expect(resolveNodeOutletCustomComponent(node, context)).toBe(ExactLanguageComponent)
|
|
expect(resolveNodeOutletCustomInputs(node, context)).toEqual({ showHeader: false })
|
|
|
|
node = {
|
|
type: 'code_block',
|
|
language: 'ts',
|
|
code: 'const value = 1',
|
|
} as any
|
|
expect(resolveNodeOutletCustomComponent(node, context)).toBe(GenericCodeBlockComponent)
|
|
expect(resolveNodeOutletCustomInputs(node, context)).toEqual({ showHeader: false })
|
|
})
|
|
|
|
it('keeps specialized mermaid routing ahead of code_block fallback', () => {
|
|
const context = {
|
|
customComponents: {
|
|
mermaid: MermaidComponent as any,
|
|
code_block: GenericCodeBlockComponent as any,
|
|
},
|
|
mermaidProps: { renderDebounceMs: 180 },
|
|
events: {},
|
|
} as any
|
|
const node = {
|
|
type: 'code_block',
|
|
language: 'mermaid',
|
|
code: 'graph TD\nA-->B\n',
|
|
} as any
|
|
|
|
expect(resolveNodeOutletCustomComponent(node, context)).toBe(MermaidComponent)
|
|
expect(resolveNodeOutletCustomInputs(node, context)).toEqual({ renderDebounceMs: 180, estimatedPreviewHeightPx: 360 })
|
|
})
|
|
|
|
it('injects stable preview height estimates for angular mermaid and infographic custom inputs', () => {
|
|
const context = {
|
|
events: {},
|
|
} as any
|
|
|
|
let node = {
|
|
type: 'code_block',
|
|
language: 'mermaid',
|
|
code: 'flowchart TD\nA-->B\nB-->C\nC-->D\nD-->E\nE-->F\nF-->G\nG-->H\nH-->I\nI-->J\nJ-->K\nK-->L\n',
|
|
} as any
|
|
expect(resolveNodeOutletCustomInputs(node, context)).toEqual({ estimatedPreviewHeightPx: 500 })
|
|
|
|
node = {
|
|
type: 'code_block',
|
|
language: 'infographic',
|
|
code: ['# Release progress', '- Plan: complete', '- Build: active', '- Verify: pending'].join('\n'),
|
|
} as any
|
|
expect(resolveNodeOutletCustomInputs(node, context)).toEqual({ estimatedPreviewHeightPx: 500 })
|
|
})
|
|
|
|
it('lets d2lang exact overrides beat d2 fallback while keeping d2 inputs', () => {
|
|
const context = {
|
|
customComponents: {
|
|
d2: D2Component as any,
|
|
d2lang: D2LangComponent as any,
|
|
},
|
|
d2Props: { themeId: 7 },
|
|
events: {},
|
|
} as any
|
|
const node = {
|
|
type: 'code_block',
|
|
language: 'd2lang',
|
|
code: 'a -> b',
|
|
} as any
|
|
|
|
expect(resolveNodeOutletCustomComponent(node, context)).toBe(D2LangComponent)
|
|
expect(resolveNodeOutletCustomInputs(node, context)).toEqual({ themeId: 7 })
|
|
})
|
|
})
|