Files
deepseek-harness/packages/ui/desktop/tests/renderer-regressions.spec.ts
NI0317 c4584b5c19 feat(ui): rebuild desktop renderer on a shared trace graph and design tokens
Replace the full-innerHTML render loop with a static shell plus
per-region updates, so composer drafts, fold state, focus, and scroll
survive streaming turns. Fold session events into one trace graph
consumed by Chat, Trajectory, Waterfall, and the shared inspector
drawer, with live ACP updates patched into a keyed live-turn region.

Align the visual system with a tokenized design spec: a 4px spacing
base with fixed control/row height steps, foreground-derived text
tiers and borders (color-mix), neutral interaction overlays, tiered
motion durations with a reduced-motion collapse, hover-revealed
scrollbars, and drawer-aware layout elasticity. Localize trajectory
role chips and row previews.

The renderer entry (app.ts) joins the coverage exclude list as a
self-executing DOM bootstrap: jsdom lifecycle specs exercise its
behavior, and extractable logic lives in covered modules
(trace-graph.ts, renderer-content.ts).
2026-07-19 01:37:04 +08:00

70 lines
3.3 KiB
TypeScript

import { readFile } from 'node:fs/promises'
import { describe, expect, it } from 'vitest'
import { assistantText, contentBlocks, contentText, reasoningText } from '../src/renderer-content.ts'
describe('desktop live content', () => {
it('renders single ACP update blocks before the persisted message exists', () => {
expect(contentText({ type: 'text', text: '你' })).toBe('你')
expect(contentText({ type: 'reasoning', text: '想' })).toBe('想')
})
it('accepts plain strings, arrays, and empty values', () => {
const blocks = [{ type: 'text', text: 'a' }]
expect(contentBlocks(blocks)).toBe(blocks)
expect(contentBlocks(null)).toEqual([])
expect(contentText('plain')).toBe('plain')
expect(assistantText('plain')).toBe('plain')
})
it('keeps persisted content arrays split by visible role', () => {
const content = [
{ type: 'reasoning', text: '先想' },
{ type: 'text', text: '再答' },
]
expect(reasoningText(content)).toBe('先想')
expect(assistantText(content)).toBe('再答')
})
it('renders tool, resource, and unknown blocks without object coercion', () => {
expect(contentText({ type: 'tool-call', name: 'bash', arguments: { command: 'pwd' } }))
.toBe('[tool-call bash] {"command":"pwd"}')
expect(contentText({ type: 'resource_link', name: 'notes', uri: 'file:///notes' }))
.toBe('[resource notes] file:///notes')
expect(contentText({ type: 'custom', value: 1 })).toBe('{"type":"custom","value":1}')
expect(contentText({ type: 'text', text: 1 })).toBe('')
expect(contentText({ type: 'tool-call', name: 1, arguments: 'pwd' })).toBe('[tool-call ] pwd')
expect(contentText({ type: 'tool-call', name: 'bash' })).toBe('[tool-call bash] ')
expect(contentText({ type: 'resource_link' })).toBe('[resource ] ')
expect(reasoningText([{ type: 'reasoning', text: 1 }])).toBe('')
expect(contentText(undefined)).toBe('')
})
})
describe('desktop shell layout', () => {
it('pins the composer to its intrinsic bottom row', async () => {
const css = await readFile(new URL('../src/styles.css', import.meta.url), 'utf8')
expect(css).toMatch(/\.session-canvas,\s*\.module-canvas\s*{\s*grid-row: 3;/)
expect(css).toMatch(/\.composer\s*{\s*grid-row: 4;/)
})
it('does not launch Electron after a strict-port Vite failure', async () => {
const script = await readFile(new URL('../scripts/dev.mjs', import.meta.url), 'utf8')
expect(script).toContain("'--strictPort'")
expect(script).not.toContain('setTimeout(startElectron')
})
it('suppresses persisted ACP replay updates before forwarding a new prompt', async () => {
const main = await readFile(new URL('../src/main.mjs', import.meta.url), 'utf8')
expect(main).toContain('replayingSessions.has(String(params.sessionId))')
expect(main).toContain('replayingSessions.add(sessionId)')
expect(main).toContain('replayingSessions.delete(sessionId)')
})
it('switches from Develop to Sessions and patches artifact detail in place', async () => {
const app = await readFile(new URL('../src/app.ts', import.meta.url), 'utf8')
expect(app).toMatch(/if \(sessionButton !== null\) \{\s*showModule\('sessions'\)\s*await loadTrace/)
expect(app).toContain("selectDevArtifact(devArtifact.dataset.devArtifact ?? '')")
expect(app).toContain('detail.innerHTML = renderDevArtifactDetail(selected)')
})
})