Merge branch 'master' into worktree/web-session-titles
This commit is contained in:
@@ -13,7 +13,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react'
|
||||
import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { UseSession } from '@deepseek-ai/dsh-client-web-react'
|
||||
import type { ConversationSnapshot, SessionId, SessionListState } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { ConversationSnapshot, PendingInteraction, SessionId, SessionListState } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { PendingWait } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { RpcId } from '@deepseek-ai/dsh-client-connection/client'
|
||||
import type { SelectionTarget, ViewTab } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import type { ConversationRootProps } from '../src/client/skeleton/ConversationRoot.tsx'
|
||||
// Export discipline: packages/client/AGENTS.md.
|
||||
@@ -36,11 +38,12 @@ interface FakeSnapshot {
|
||||
running: boolean
|
||||
removed: boolean
|
||||
promptError: { op: 'send' | 'stop'; error: { message: string; code: string } } | null
|
||||
pending: readonly PendingInteraction[]
|
||||
}
|
||||
|
||||
function fakeSession(init: Partial<FakeSnapshot> = {}) {
|
||||
const store = createSnapshotStore<FakeSnapshot>({
|
||||
nodes: [], runningCalls: [], running: false, removed: false, promptError: null, ...init,
|
||||
nodes: [], runningCalls: [], running: false, removed: false, promptError: null, pending: [], ...init,
|
||||
})
|
||||
return { store, useSession: bindSnapshotSelector(store) as unknown as UseSession<ConversationSnapshot> }
|
||||
}
|
||||
@@ -99,8 +102,11 @@ describe('EmptyState', () => {
|
||||
})
|
||||
|
||||
describe('ConversationRoot', () => {
|
||||
function bench(tabs: ViewTab[], activeView?: string) {
|
||||
const { useSession } = fakeSession({ nodes: [{ kind: 'user' }, { kind: 'user' }] })
|
||||
function bench(
|
||||
tabs: ViewTab[], activeView?: string, init: Partial<FakeSnapshot> = {},
|
||||
renderSlotChain?: ConversationRootProps['renderSlotChain'],
|
||||
) {
|
||||
const { useSession } = fakeSession({ nodes: [{ kind: 'user' }, { kind: 'user' }], ...init })
|
||||
const { useSessions } = fakeSessions([
|
||||
{ id: 'root', title: 'proj' },
|
||||
{ id: 's1', title: 'child', parentId: 'root' },
|
||||
@@ -124,6 +130,7 @@ describe('ConversationRoot', () => {
|
||||
useStore={bindSnapshotSelector(chat)}
|
||||
actions={chat.actions}
|
||||
renderSlot={renderSlot as unknown as ConversationRootProps['renderSlot']}
|
||||
renderSlotChain={renderSlotChain ?? ((_key, _owner, opts) => opts?.fallback ?? null)}
|
||||
SessionProvider={SessionProviderStub}
|
||||
views={{
|
||||
list: () => tabs,
|
||||
@@ -179,6 +186,30 @@ describe('ConversationRoot', () => {
|
||||
fireEvent.keyDown(box, { key: 'Enter' })
|
||||
expect(send).toHaveBeenCalledWith('hi', 'queue')
|
||||
})
|
||||
|
||||
it('dispatches the pending list to the composer chain; all-decline falls back to InputBar', () => {
|
||||
const wait = new PendingWait('question', RpcId('rq'), sid('s1'),
|
||||
{ questions: [{ id: 'mode', question: 'Choose?', options: [{ label: 'Fast' }] }] } as PendingWait<'question'>['payload'], vi.fn())
|
||||
// A matching entry takes the composer over.
|
||||
const renderSlotChain = vi.fn(() => <div>question takeover</div>) as unknown as ConversationRootProps['renderSlotChain']
|
||||
bench([tab('chat', 'Chat')], undefined, { pending: [wait] }, renderSlotChain)
|
||||
expect(screen.getByText('question takeover')).toBeTruthy()
|
||||
expect(screen.queryByPlaceholderText(/输入消息/)).toBeNull()
|
||||
// The owner dispatches the raw pending list (chain currency); routing
|
||||
// lives in entry selectors, not here.
|
||||
expect(renderSlotChain).toHaveBeenCalledWith(
|
||||
'conversation.composer',
|
||||
expect.objectContaining({
|
||||
interactions: expect.arrayContaining([expect.objectContaining({ key: 'q:rq' })]),
|
||||
}),
|
||||
expect.objectContaining({ fallback: expect.anything() }),
|
||||
)
|
||||
cleanup()
|
||||
// Zero registered entries (default all-decline stub): the fallback IS the
|
||||
// default InputBar — behavior equals the pre-chain composer.
|
||||
bench([tab('chat', 'Chat')], undefined, { pending: [wait] })
|
||||
expect(screen.getByPlaceholderText(/输入消息/)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('DetailsPanel', () => {
|
||||
|
||||
Reference in New Issue
Block a user