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.
300 lines
9.0 KiB
TypeScript
300 lines
9.0 KiB
TypeScript
import { mount } from '@vue/test-utils'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { defineComponent, h } from 'vue'
|
|
import NodeRenderer from '../packages/markstream-vue2/src/components/NodeRenderer/NodeRenderer.vue'
|
|
import { removeCustomComponents, setCustomComponents } from '../packages/markstream-vue2/src/utils/nodeComponents'
|
|
import { flushAll } from './setup/flush-all'
|
|
|
|
const customId = 'vue2-heavy-props-test'
|
|
|
|
const CustomMermaidProbe = defineComponent({
|
|
name: 'CustomMermaidProbe',
|
|
props: {
|
|
node: { type: Object, required: true },
|
|
showHeader: Boolean,
|
|
showZoomControls: Boolean,
|
|
renderDebounceMs: Number,
|
|
previewPollDelayMs: Number,
|
|
estimatedPreviewHeightPx: Number,
|
|
},
|
|
render() {
|
|
return h('div', {
|
|
'class': 'custom-mermaid-probe',
|
|
'data-language': String((this as any).node?.language ?? ''),
|
|
'data-show-header': String(this.showHeader),
|
|
'data-show-zoom-controls': String(this.showZoomControls),
|
|
'data-render-debounce-ms': String(this.renderDebounceMs),
|
|
'data-preview-poll-delay-ms': String(this.previewPollDelayMs),
|
|
'data-estimated-preview-height': String(this.estimatedPreviewHeightPx ?? ''),
|
|
})
|
|
},
|
|
})
|
|
|
|
const EstimatedPreviewProbe = defineComponent({
|
|
name: 'EstimatedPreviewProbe',
|
|
props: {
|
|
node: { type: Object, required: true },
|
|
estimatedPreviewHeightPx: Number,
|
|
},
|
|
render() {
|
|
return h('div', {
|
|
'class': 'estimated-preview-probe',
|
|
'data-language': String((this as any).node?.language ?? ''),
|
|
'data-estimated-preview-height': String((this as any).estimatedPreviewHeightPx ?? ''),
|
|
})
|
|
},
|
|
})
|
|
|
|
const ExactLanguageProbe = defineComponent({
|
|
name: 'ExactLanguageProbe',
|
|
props: {
|
|
node: { type: Object, required: true },
|
|
showHeader: Boolean,
|
|
},
|
|
render() {
|
|
return h('div', {
|
|
'class': 'exact-language-probe',
|
|
'data-language': String((this as any).node?.language ?? ''),
|
|
'data-show-header': String((this as any).showHeader),
|
|
})
|
|
},
|
|
})
|
|
|
|
const GenericCodeBlockProbe = defineComponent({
|
|
name: 'GenericCodeBlockProbe',
|
|
props: {
|
|
node: { type: Object, required: true },
|
|
showHeader: Boolean,
|
|
},
|
|
render() {
|
|
return h('div', {
|
|
'class': 'generic-code-block-probe',
|
|
'data-language': String((this as any).node?.language ?? ''),
|
|
'data-show-header': String((this as any).showHeader),
|
|
})
|
|
},
|
|
})
|
|
|
|
const CustomD2Probe = defineComponent({
|
|
name: 'CustomD2Probe',
|
|
props: {
|
|
node: { type: Object, required: true },
|
|
themeId: Number,
|
|
},
|
|
render() {
|
|
return h('div', {
|
|
'class': 'custom-d2-probe',
|
|
'data-language': String((this as any).node?.language ?? ''),
|
|
'data-theme-id': String((this as any).themeId),
|
|
})
|
|
},
|
|
})
|
|
|
|
const CustomD2LangProbe = defineComponent({
|
|
name: 'CustomD2LangProbe',
|
|
props: {
|
|
node: { type: Object, required: true },
|
|
themeId: Number,
|
|
},
|
|
render() {
|
|
return h('div', {
|
|
'class': 'custom-d2lang-probe',
|
|
'data-language': String((this as any).node?.language ?? ''),
|
|
'data-theme-id': String((this as any).themeId),
|
|
})
|
|
},
|
|
})
|
|
|
|
describe('markstream-vue2 heavy-node prop forwarding', () => {
|
|
afterEach(() => {
|
|
removeCustomComponents(customId)
|
|
})
|
|
|
|
it('renders a reserved Mermaid shell before the async component resolves', () => {
|
|
const wrapper = mount(NodeRenderer as any, {
|
|
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.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 as any, {
|
|
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.get('.infographic-preview').element as HTMLElement).style.height).toBe('500px')
|
|
})
|
|
|
|
it('injects stable preview height estimates for Mermaid and Infographic custom renderers', async () => {
|
|
setCustomComponents(customId, {
|
|
mermaid: EstimatedPreviewProbe as any,
|
|
infographic: EstimatedPreviewProbe as any,
|
|
})
|
|
|
|
const wrapper = mount(NodeRenderer as any, {
|
|
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 as any,
|
|
code_block: GenericCodeBlockProbe as any,
|
|
})
|
|
|
|
const wrapper = mount(NodeRenderer as any, {
|
|
props: {
|
|
customId,
|
|
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(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 as any,
|
|
d2lang: CustomD2LangProbe as any,
|
|
})
|
|
|
|
const wrapper = mount(NodeRenderer as any, {
|
|
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 custom mermaid renderers', async () => {
|
|
setCustomComponents(customId, { mermaid: CustomMermaidProbe })
|
|
|
|
const wrapper = mount(NodeRenderer as any, {
|
|
props: {
|
|
customId,
|
|
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 probe = wrapper.get('.custom-mermaid-probe')
|
|
expect(probe.attributes('data-show-header')).toBe('false')
|
|
expect(probe.attributes('data-show-zoom-controls')).toBe('false')
|
|
expect(probe.attributes('data-render-debounce-ms')).toBe('180')
|
|
expect(probe.attributes('data-preview-poll-delay-ms')).toBe('500')
|
|
expect(probe.attributes('data-estimated-preview-height')).toBe('360')
|
|
})
|
|
})
|