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.
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mathNode = {
|
||||
type: 'math_block',
|
||||
content: '\\sum_{n=1}^{3} n = 6',
|
||||
raw: '$$\\sum_{n=1}^{3} n = 6$$',
|
||||
markup: '$$',
|
||||
loading: false,
|
||||
} as const
|
||||
|
||||
const katexRenderer = {
|
||||
renderToString(content: string, options?: { displayMode?: boolean }) {
|
||||
return options?.displayMode
|
||||
? `<span class="katex-display">${content}</span>`
|
||||
: `<span class="katex">${content}</span>`
|
||||
},
|
||||
}
|
||||
|
||||
const browserKeys = [
|
||||
'window',
|
||||
'document',
|
||||
'navigator',
|
||||
'self',
|
||||
'Element',
|
||||
'HTMLElement',
|
||||
'SVGElement',
|
||||
'Node',
|
||||
'Text',
|
||||
'Comment',
|
||||
'Document',
|
||||
'MutationObserver',
|
||||
'Event',
|
||||
'CustomEvent',
|
||||
'getComputedStyle',
|
||||
'requestAnimationFrame',
|
||||
'cancelAnimationFrame',
|
||||
] as const
|
||||
|
||||
async function withGlobalOverrides<T>(values: Partial<Record<(typeof browserKeys)[number], unknown>>, run: () => Promise<T>) {
|
||||
const previous = new Map<string, PropertyDescriptor | undefined>()
|
||||
|
||||
for (const key of browserKeys) {
|
||||
previous.set(key, Object.getOwnPropertyDescriptor(globalThis, key))
|
||||
if (!(key in values))
|
||||
continue
|
||||
Object.defineProperty(globalThis, key, {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: values[key],
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
return await run()
|
||||
}
|
||||
finally {
|
||||
for (const [key, descriptor] of previous) {
|
||||
if (descriptor) {
|
||||
Object.defineProperty(globalThis, key, descriptor)
|
||||
}
|
||||
else {
|
||||
Reflect.deleteProperty(globalThis, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function renderServerMathBlock() {
|
||||
return withGlobalOverrides(
|
||||
Object.fromEntries(browserKeys.map(key => [key, undefined])) as Partial<Record<(typeof browserKeys)[number], undefined>>,
|
||||
async () => {
|
||||
vi.resetModules()
|
||||
const vue = await import('vue')
|
||||
const { renderToString } = await import('vue/server-renderer')
|
||||
const { setKatexLoader } = await import('../src/components/MathInlineNode/katex')
|
||||
const MathBlockNode = (await import('../src/components/MathBlockNode/MathBlockNode.vue')).default
|
||||
|
||||
setKatexLoader(() => katexRenderer)
|
||||
|
||||
const app = vue.createSSRApp({
|
||||
render: () => vue.h(MathBlockNode, { node: mathNode }),
|
||||
})
|
||||
|
||||
return renderToString(app)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
describe('mathBlockNode hydration', () => {
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
vi.restoreAllMocks()
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
it('hydrates server-rendered KaTeX markup without mismatch warnings', async () => {
|
||||
const serverHtml = await renderServerMathBlock()
|
||||
|
||||
expect(serverHtml).toContain('data-markstream-mode="katex"')
|
||||
expect(serverHtml).toContain('katex-display')
|
||||
|
||||
document.body.innerHTML = `<div id="app">${serverHtml}</div>`
|
||||
|
||||
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
|
||||
vi.resetModules()
|
||||
const vue = await import('vue')
|
||||
const { setKatexLoader } = await import('../src/components/MathInlineNode/katex')
|
||||
const MathBlockNode = (await import('../src/components/MathBlockNode/MathBlockNode.vue')).default
|
||||
|
||||
setKatexLoader(() => katexRenderer)
|
||||
|
||||
const app = vue.createSSRApp({
|
||||
render: () => vue.h(MathBlockNode, { node: mathNode }),
|
||||
})
|
||||
|
||||
const container = document.querySelector('#app') as HTMLElement
|
||||
app.mount(container)
|
||||
|
||||
await vue.nextTick()
|
||||
await Promise.resolve()
|
||||
|
||||
const diagnostics = [
|
||||
...consoleError.mock.calls.flat(),
|
||||
...consoleWarn.mock.calls.flat(),
|
||||
].join(' ')
|
||||
|
||||
expect(container.innerHTML).toContain('katex-display')
|
||||
expect(diagnostics).not.toMatch(/Hydration/i)
|
||||
expect(diagnostics).not.toMatch(/mismatch/i)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user