mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
# Conflicts: # docs/architecture.i18n.yaml # docs/architecture.md # docs/architecture.zh.md # docs/config-catalog.md # docs/core-data-structures/core.i18n.yaml # docs/core-data-structures/llm-streaming.i18n.yaml # docs/module-graph.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/README.i18n.yaml # packages/client/connection/src/client/fixture.ts # packages/client/connection/src/index.ts # packages/client/runtime/README.i18n.yaml # packages/client/runtime/README.md # packages/client/runtime/README.zh.md # packages/client/runtime/src/client/sessions/conversation.ts # packages/client/ui-conversation/README.i18n.yaml # packages/client/ui-conversation/src/client/apply.ts # packages/client/ui-conversation/src/client/chat/ChatView.tsx # packages/client/ui-conversation/src/client/chat/MessageItem.tsx # packages/client/ui-conversation/src/client/contract/slots.ts # packages/client/ui-trajectory/tests/views.spec.tsx # packages/compact/compact-basic/README.i18n.yaml # packages/cordis/tool-cordis/src/api-catalog.ts # packages/host/apiproxy/src/api-proxy.ts # packages/host/apiproxy/src/api/index.ts # packages/host/apiproxy/src/api/sessions.ts # packages/host/apiproxy/src/index.ts # packages/host/apiproxy/tests/fetch-carrier.spec.ts # packages/llm/llm-deepseek/src/adapter.ts # packages/llm/llm-deepseek/tests/adapter.spec.ts # packages/llm/llm-deepseek/tests/serialize.spec.ts # packages/llm/llm-pi-ai/README.i18n.yaml # packages/llm/llm-pi-ai/src/adapter.ts # packages/llm/llm-pi-ai/src/index.ts # packages/llm/llm-pi-ai/tests/adapter.spec.ts # packages/llm/llm/src/types.ts # packages/ui/tui/README.i18n.yaml # packages/ui/tui/src/index.ts # packages/ui/tui/tests/tui.spec.ts
100 lines
4.1 KiB
TypeScript
100 lines
4.1 KiB
TypeScript
// @vitest-environment jsdom
|
|
/**
|
|
* QueueDock rendering (web input-triggers queue cut 1): empty queue renders
|
|
* nothing, rows render one preview line each keyed by rpcId, and the strip
|
|
* follows queue changes through the useSession selector.
|
|
*/
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { act, cleanup, render } from '@testing-library/react'
|
|
import { useSyncExternalStore } from 'react'
|
|
import type { ConversationSnapshot, QueuedMessage, SessionId, SessionListState } from '@deepseek-ai/dsh-client-runtime/client'
|
|
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'
|
|
import type { InputState } from '../src/client/input/contract.ts'
|
|
import { QueueDock, queueDockEntry } from '../src/client/queue/QueueDock.tsx'
|
|
|
|
afterEach(cleanup)
|
|
|
|
const SID = 's1' as SessionId
|
|
|
|
function snapshotWith(queue: QueuedMessage[]): ConversationSnapshot {
|
|
return {
|
|
sessionId: SID, nodes: [], foldDegraded: false, partial: null, runningCalls: [], codeDispatches: new Map(),
|
|
pending: [], queue, todos: [], running: true, composerPhase: 'active', removed: false, openState: 'open', openError: null,
|
|
hasMore: false, loadingOlder: false, promptError: null, blank: false, lastAgentError: null,
|
|
}
|
|
}
|
|
|
|
/** Minimal live source backing the useSession stub (queue swaps notify subscribers). */
|
|
function liveSession(initial: ConversationSnapshot) {
|
|
let snapshot = initial
|
|
const listeners = new Set<() => void>()
|
|
const useSession: SnapshotSelectorHook<ConversationSnapshot> = sel =>
|
|
useSyncExternalStore(
|
|
(fn) => {
|
|
listeners.add(fn)
|
|
return () => listeners.delete(fn)
|
|
},
|
|
() => sel(snapshot),
|
|
)
|
|
return {
|
|
useSession,
|
|
push(next: ConversationSnapshot): void {
|
|
snapshot = next
|
|
for (const fn of [...listeners]) fn()
|
|
},
|
|
}
|
|
}
|
|
|
|
/** InputZone owner stub (the dock reads useSession only; the zone fields satisfy the owner share). */
|
|
const INPUT_STATE: InputState = { draft: '', imageIds: [], draftRev: 0, phase: 'plain', occurrences: [], queue: [] }
|
|
|
|
function kitFor(snapshot: ConversationSnapshot) {
|
|
return {
|
|
sessionId: SID,
|
|
useSessions: (() => { throw new Error('unused') }) as unknown as SnapshotSelectorHook<SessionListState>,
|
|
useWorkspaces: (() => { throw new Error('unused') }) as never,
|
|
useInput: (() => { throw new Error('unused') }) as never,
|
|
inputActions: { setDraft: () => {}, submit: () => {} } as never,
|
|
session: snapshot,
|
|
input: INPUT_STATE,
|
|
}
|
|
}
|
|
|
|
describe('QueueDock', () => {
|
|
it('renders null while the queue is empty', () => {
|
|
const snap = snapshotWith([])
|
|
const source = liveSession(snap)
|
|
const { container } = render(<QueueDock {...kitFor(snap)} useSession={source.useSession} />)
|
|
expect(container.innerHTML).toBe('')
|
|
})
|
|
|
|
it('renders one preview row per queued message with the count strip', () => {
|
|
const snap = snapshotWith([
|
|
{ key: 'p-1', preview: '第一条排队消息' },
|
|
{ key: 'p-2', preview: 'second queued line' },
|
|
])
|
|
const source = liveSession(snap)
|
|
const { container } = render(<QueueDock {...kitFor(snap)} useSession={source.useSession} />)
|
|
expect(container.textContent).toContain('已排队 2 条')
|
|
const rows = [...container.querySelectorAll('li')]
|
|
expect(rows.map(r => r.textContent)).toEqual(['第一条排队消息', 'second queued line'])
|
|
})
|
|
|
|
it('follows queue changes: retirement empties the strip back to null', () => {
|
|
const snap = snapshotWith([{ key: 'p-1', preview: '在场' }])
|
|
const source = liveSession(snap)
|
|
const { container } = render(<QueueDock {...kitFor(snap)} useSession={source.useSession} />)
|
|
expect(container.textContent).toContain('在场')
|
|
act(() => { source.push(snapshotWith([])) })
|
|
expect(container.innerHTML).toBe('')
|
|
})
|
|
|
|
it('ships the registrant plugin shape (list entry into conversation.input.dock)', () => {
|
|
// Registration itself runs under T5's slot declaration; here we pin the
|
|
// frozen registration surface so the wiring layer can mount it verbatim.
|
|
expect(queueDockEntry.name).toBe('conversation-queue-dock')
|
|
expect(queueDockEntry.inject).toEqual(['slots', 'conversation'])
|
|
expect(typeof queueDockEntry.apply).toBe('function')
|
|
})
|
|
})
|