Files
BoosAPI/desktop/libs/markstream-vue/test/markdown-code-block-node.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

152 lines
4.4 KiB
TypeScript

import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import { nextTick } from 'vue'
import MarkdownCodeBlockNode from '../src/components/MarkdownCodeBlockNode/MarkdownCodeBlockNode.vue'
function makeNode(code = 'console.log(1)') {
return {
type: 'code_block' as const,
language: 'plaintext',
code,
raw: `\`\`\`txt\n${code}\n\`\`\``,
}
}
function makeScrollable(element: HTMLElement) {
Object.defineProperty(element, 'scrollHeight', {
configurable: true,
value: 1000,
})
Object.defineProperty(element, 'clientHeight', {
configurable: true,
value: 100,
})
element.scrollTop = 0
}
async function flushCodeWatchers() {
await nextTick()
await Promise.resolve()
await nextTick()
}
describe('markdownCodeBlockNode props and features', () => {
it('should support all header control props', () => {
const expectedProps = [
'showHeader',
'showCopyButton',
'showExpandButton',
'showPreviewButton',
'showFontSizeButtons',
'showTooltips',
'enableFontSizeControl',
]
// These props should now be supported on MarkdownCodeBlockNode
expectedProps.forEach((prop) => {
expect(prop).toBeTruthy()
})
})
it('should support language detection for preview', () => {
const htmlLanguages = ['html', 'svg']
const isPreviewable = (lang: string, isShowPreview: boolean) => {
return isShowPreview && htmlLanguages.includes(lang.toLowerCase())
}
expect(isPreviewable('html', true)).toBe(true)
expect(isPreviewable('svg', true)).toBe(true)
expect(isPreviewable('javascript', true)).toBe(false)
expect(isPreviewable('html', false)).toBe(false)
})
it('should support font size controls', () => {
const codeFontMin = 10
const codeFontMax = 36
const codeFontStep = 1
const defaultSize = 14
const increaseFont = (current: number) => Math.min(codeFontMax, current + codeFontStep)
const decreaseFont = (current: number) => Math.max(codeFontMin, current - codeFontStep)
const resetFont = () => defaultSize
expect(increaseFont(14)).toBe(15)
expect(increaseFont(36)).toBe(36)
expect(decreaseFont(14)).toBe(13)
expect(decreaseFont(10)).toBe(10)
expect(resetFont()).toBe(14)
})
it('should emit proper events', () => {
const expectedEvents = ['previewCode', 'copy']
expectedEvents.forEach((event) => {
expect(event).toBeTruthy()
})
})
it('should support named slots', () => {
const expectedSlots = ['header-left', 'header-right']
expectedSlots.forEach((slot) => {
expect(slot).toBeTruthy()
})
})
it('should support auto-scroll to bottom behavior', () => {
// Test the isAtBottom helper function logic with 50px threshold
const isAtBottom = (scrollHeight: number, scrollTop: number, clientHeight: number, threshold = 50): boolean => {
return scrollHeight - scrollTop - clientHeight <= threshold
}
// Test when at bottom
expect(isAtBottom(1000, 900, 100, 50)).toBe(true) // exactly at bottom
expect(isAtBottom(1000, 860, 100, 50)).toBe(true) // within threshold (40px from bottom)
// Test when not at bottom
expect(isAtBottom(1000, 840, 100, 50)).toBe(false) // beyond threshold (60px from bottom)
expect(isAtBottom(1000, 500, 100, 50)).toBe(false) // in middle
expect(isAtBottom(1000, 0, 100, 50)).toBe(false) // at top
})
it('should not auto-scroll when autoScrollOnUpdate is false', async () => {
const wrapper = mount(MarkdownCodeBlockNode, {
props: {
loading: false,
node: makeNode(),
autoScrollOnUpdate: false,
},
})
const content = wrapper.get('.code-block-content').element as HTMLElement
makeScrollable(content)
await wrapper.setProps({
node: makeNode('line\n'.repeat(80)),
})
await flushCodeWatchers()
expect(content.scrollTop).toBe(0)
wrapper.unmount()
})
it('should start with auto-scroll disabled when autoScrollInitial is false', async () => {
const wrapper = mount(MarkdownCodeBlockNode, {
props: {
loading: false,
node: makeNode(),
autoScrollInitial: false,
},
})
const content = wrapper.get('.code-block-content').element as HTMLElement
makeScrollable(content)
await wrapper.setProps({
node: makeNode('line\n'.repeat(80)),
})
await flushCodeWatchers()
expect(content.scrollTop).toBe(0)
wrapper.unmount()
})
})