- Segment streaming Markdown so completed blocks are reused and only the growing tail is re-parsed; fall back to whole-document parsing for document-wide constructs - Memoize conversation sidebar rows and cache per-conversation derived activity, ordering and pending approvals - Activity page loads on entry, refreshes every 15 s while visible, and has a single header refresh button - Include pending settings, model download source, device sharing and knowledge gateway changes in the working tree - Add Electron streaming input benchmark and regression tests
53 lines
2.7 KiB
TypeScript
53 lines
2.7 KiB
TypeScript
// @vitest-environment node
|
|
import { spawn } from 'node:child_process'
|
|
import { createRequire } from 'node:module'
|
|
import { copyFile, mkdtemp, readFile, rm } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join, resolve } from 'node:path'
|
|
import { createServer } from 'vite'
|
|
import react from '@vitejs/plugin-react'
|
|
import { expect, it } from 'vitest'
|
|
|
|
it.each([
|
|
{ mode: 'layout', name: 'keeps workbar add reachable and model protocols readable at narrow container widths' },
|
|
{ mode: 'source', name: 'aligns the model download source button and supports native dialog confirm and cancel' }
|
|
])('$name', async ({ mode }) => {
|
|
const directory = await mkdtemp(join(tmpdir(), 'goodbuddy-responsive-controls-'))
|
|
const server = await createServer({
|
|
configFile: false, root: resolve('.'), cacheDir: join(directory, 'vite'),
|
|
plugins: [react(), { name: 'responsive-controls', configureServer(server) {
|
|
server.middlewares.use('/responsive.html', async (_request, response) => {
|
|
response.setHeader('Content-Type', 'text/html')
|
|
response.end(await server.transformIndexHtml('/responsive.html',
|
|
'<html><body><div id="root"></div><script type="module" src="/tests/support/workbar-settings-layout.tsx"></script></body></html>'))
|
|
})
|
|
} }],
|
|
optimizeDeps: { entries: ['tests/support/workbar-settings-layout.tsx'] },
|
|
server: { host: '127.0.0.1', port: 0, watch: null }
|
|
})
|
|
try {
|
|
await server.listen()
|
|
const driver = join(directory, 'driver.mjs')
|
|
await copyFile(resolve('tests/support/workbar-settings-layout-driver.mjs'), driver)
|
|
const env: NodeJS.ProcessEnv = { ...process.env, GB_LAYOUT_DIRECTORY: directory, GB_LAYOUT_MODE: mode,
|
|
GB_LAYOUT_URL: server.resolvedUrls!.local[0] + 'responsive.html' }
|
|
delete env.ELECTRON_RUN_AS_NODE
|
|
const child = spawn(createRequire(import.meta.url)('electron'), [driver], { env, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
let output = ''
|
|
child.stdout.on('data', data => { output += data })
|
|
child.stderr.on('data', data => { output += data })
|
|
const timeout = setTimeout(() => child.kill(), 90000)
|
|
try {
|
|
const code = await new Promise((resolve, reject) => { child.once('exit', resolve); child.once('error', reject) })
|
|
const result = await readFile(join(directory, 'result.json'), 'utf8').catch(() => output)
|
|
expect(code, result).toBe(0)
|
|
const parsed = JSON.parse(result)
|
|
expect(parsed.passed).toBe(true)
|
|
process.stdout.write(`Responsive controls: ${parsed.observations.length} observations; Chinese/English, light/dark, native mouse and keyboard; model calls: 0\n`)
|
|
} finally { clearTimeout(timeout) }
|
|
} finally {
|
|
await server.close()
|
|
await rm(directory, { recursive: true, force: true })
|
|
}
|
|
}, 120000)
|