Files
BoosAPI/desktop/libs/markstream-vue/test/node-renderer-heavy-node-props.test.ts
boos e0a5f57793 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.
2026-06-22 11:32:57 +08:00

281 lines
8.2 KiB
TypeScript

import { mount } from '@vue/test-utils'
import { afterEach, describe, expect, it } from 'vitest'
import { defineComponent, h } from 'vue'
import MermaidBlockNode from '../src/components/MermaidBlockNode'
import NodeRenderer from '../src/components/NodeRenderer'
import { removeCustomComponents, setCustomComponents } from '../src/utils/nodeComponents'
import { flushAll } from './setup/flush-all'
const customId = 'vue3-heavy-props-test'
const ExactLanguageProbe = defineComponent({
name: 'ExactLanguageProbe',
props: {
node: { type: Object, required: true },
showHeader: Boolean,
stream: Boolean,
},
setup(props) {
return () => h('div', {
'class': 'exact-language-probe',
'data-language': String((props.node as any)?.language ?? ''),
'data-show-header': String(props.showHeader),
'data-stream': String(props.stream),
})
},
})
const GenericCodeBlockProbe = defineComponent({
name: 'GenericCodeBlockProbe',
props: {
node: { type: Object, required: true },
showHeader: Boolean,
},
setup(props) {
return () => h('div', {
'class': 'generic-code-block-probe',
'data-language': String((props.node as any)?.language ?? ''),
'data-show-header': String(props.showHeader),
})
},
})
const CustomD2Probe = defineComponent({
name: 'CustomD2Probe',
props: {
node: { type: Object, required: true },
themeId: Number,
},
setup(props) {
return () => h('div', {
'class': 'custom-d2-probe',
'data-language': String((props.node as any)?.language ?? ''),
'data-theme-id': String(props.themeId),
})
},
})
const CustomD2LangProbe = defineComponent({
name: 'CustomD2LangProbe',
props: {
node: { type: Object, required: true },
themeId: Number,
},
setup(props) {
return () => h('div', {
'class': 'custom-d2lang-probe',
'data-language': String((props.node as any)?.language ?? ''),
'data-theme-id': String(props.themeId),
})
},
})
const EstimatedPreviewProbe = defineComponent({
name: 'EstimatedPreviewProbe',
props: {
node: { type: Object, required: true },
estimatedPreviewHeightPx: Number,
},
setup(props) {
return () => h('div', {
'class': 'estimated-preview-probe',
'data-language': String((props.node as any)?.language ?? ''),
'data-estimated-preview-height': String(props.estimatedPreviewHeightPx ?? ''),
})
},
})
afterEach(() => {
removeCustomComponents(customId)
})
describe('nodeRenderer heavy-node prop forwarding', () => {
it('renders a reserved Mermaid shell before the async component resolves', () => {
const wrapper = mount(NodeRenderer, {
props: {
nodes: [
{
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',
raw: '```mermaid\nflowchart TD\nA-->B\n```',
},
],
},
})
const shell = wrapper.get('[data-markstream-mermaid="1"]')
expect(shell.attributes('data-markstream-mode')).toBe('pending')
expect(shell.find('.mermaid-source-panel').exists()).toBe(false)
expect((shell.get('.mermaid-preview-area').element as HTMLElement).style.height).toBe('500px')
})
it('renders a reserved Infographic shell before the async component resolves', () => {
const wrapper = mount(NodeRenderer, {
props: {
nodes: [
{
type: 'code_block',
language: 'infographic',
code: [
'# Release progress',
'- Plan: complete',
'- Build: active',
'- Verify: pending',
].join('\n'),
raw: '```infographic\n# Release progress\n- Plan: complete\n```',
},
],
},
})
const shell = wrapper.get('[data-markstream-infographic="1"]')
expect(shell.attributes('data-markstream-mode')).toBe('pending')
expect(shell.find('.infographic-source').exists()).toBe(false)
expect((shell.get('.infographic-preview').element as HTMLElement).style.height).toBe('500px')
})
it('injects stable preview height estimates for Mermaid and Infographic blocks', async () => {
setCustomComponents(customId, {
mermaid: EstimatedPreviewProbe,
infographic: EstimatedPreviewProbe,
})
const wrapper = mount(NodeRenderer, {
props: {
customId,
nodes: [
{
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',
raw: '```mermaid\nflowchart TD\nA-->B\n```',
},
{
type: 'code_block',
language: 'infographic',
code: [
'# Release progress',
'- Plan: complete',
'- Build: active',
'- Verify: pending',
].join('\n'),
raw: '```infographic\n# Release progress\n- Plan: complete\n```',
},
],
},
})
await flushAll()
const probes = wrapper.findAll('.estimated-preview-probe')
expect(probes).toHaveLength(2)
expect(probes[0].attributes('data-language')).toBe('mermaid')
expect(probes[0].attributes('data-estimated-preview-height')).toBe('500')
expect(probes[1].attributes('data-language')).toBe('infographic')
expect(probes[1].attributes('data-estimated-preview-height')).toBe('500')
})
it('prefers exact language overrides over code_block fallback for custom languages', async () => {
setCustomComponents(customId, {
echarts: ExactLanguageProbe,
code_block: GenericCodeBlockProbe,
})
const wrapper = mount(NodeRenderer, {
props: {
customId,
codeBlockStream: false,
codeBlockProps: {
showHeader: false,
},
nodes: [
{
type: 'code_block',
language: 'echarts',
code: 'option = {}',
raw: '```echarts\noption = {}\n```',
},
{
type: 'code_block',
language: 'ts',
code: 'export const value = 1',
raw: '```ts\nexport const value = 1\n```',
},
],
},
})
await flushAll()
const exact = wrapper.get('.exact-language-probe')
const generic = wrapper.get('.generic-code-block-probe')
expect(exact.attributes('data-language')).toBe('echarts')
expect(exact.attributes('data-show-header')).toBe('false')
expect(exact.attributes('data-stream')).toBe('false')
expect(generic.attributes('data-language')).toBe('ts')
expect(generic.attributes('data-show-header')).toBe('false')
})
it('lets d2lang exact overrides beat d2 fallback while keeping d2 props', async () => {
setCustomComponents(customId, {
d2: CustomD2Probe,
d2lang: CustomD2LangProbe,
})
const wrapper = mount(NodeRenderer, {
props: {
customId,
d2Props: {
themeId: 7,
},
nodes: [
{
type: 'code_block',
language: 'd2lang',
code: 'a -> b',
raw: '```d2lang\na -> b\n```',
},
],
},
})
await flushAll()
expect(wrapper.find('.custom-d2-probe').exists()).toBe(false)
const exact = wrapper.get('.custom-d2lang-probe')
expect(exact.attributes('data-language')).toBe('d2lang')
expect(exact.attributes('data-theme-id')).toBe('7')
})
it('forwards mermaidProps to MermaidBlockNode', async () => {
const wrapper = mount(NodeRenderer, {
props: {
nodes: [
{
type: 'code_block',
language: 'mermaid',
code: 'graph LR\nA-->B\n',
raw: '```mermaid\ngraph LR\nA-->B\n```',
},
],
mermaidProps: {
showHeader: false,
showZoomControls: false,
renderDebounceMs: 180,
previewPollDelayMs: 500,
},
},
})
await flushAll()
const mermaid = wrapper.findComponent(MermaidBlockNode as any)
expect(mermaid.exists()).toBe(true)
expect(mermaid.props('showHeader')).toBe(false)
expect(mermaid.props('showZoomControls')).toBe(false)
expect(mermaid.props('renderDebounceMs')).toBe(180)
expect(mermaid.props('previewPollDelayMs')).toBe(500)
})
})