feat(web): add plan mode controls
This commit is contained in:
@@ -82,6 +82,7 @@ describe('apply wiring', () => {
|
||||
// Declaring is claiming: the chat entry's registration put the hole on
|
||||
// the ledger with the contract's kind/scope.
|
||||
expect(b.slots.spec('conversation.chat.toolview')).toEqual({ kind: 'keyed', scope: 'session' })
|
||||
expect(b.slots.spec('conversation.composer.controls')).toEqual({ kind: 'list', scope: 'session' })
|
||||
})
|
||||
|
||||
it('occupies the three slots + the ring; session entries share one store handle, empty declares none', async () => {
|
||||
@@ -122,6 +123,7 @@ describe('apply wiring', () => {
|
||||
expect(b.slots.entries('conversation.view')).toHaveLength(0)
|
||||
expect(b.slots.entries('conversation.chat.toolview')).toHaveLength(0)
|
||||
expect(b.slots.spec('conversation.chat.toolview')).toBeUndefined()
|
||||
expect(b.slots.spec('conversation.composer.controls')).toBeUndefined()
|
||||
expect(b.slots.entries('details')).toHaveLength(0)
|
||||
expect(b.slots.entries('conversation.empty')).toHaveLength(0)
|
||||
expect(b.ctx.get('conversation')).toBeUndefined()
|
||||
|
||||
@@ -128,4 +128,11 @@ describe('error strip and variants', () => {
|
||||
expect(view.getByTestId('acc')).toBeTruthy()
|
||||
expect(view.container.querySelector('[class*="hero"]')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('renders additive controls left of the primary action', () => {
|
||||
const { view, button } = setup({ controls: <span data-testid="mode-control">计划</span> })
|
||||
const control = view.getByTestId('mode-control')
|
||||
expect(control).toBeTruthy()
|
||||
expect(control.compareDocumentPosition(button) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -58,7 +58,9 @@ function listHook(rows: { id: string; title: string; cwd?: string; parentId?: st
|
||||
describe('ConversationRoot branches', () => {
|
||||
const chatTab: ViewTab = { id: 'chat', label: 'Chat' }
|
||||
/** renderSlot stub in the outlet's baked shape (ring key + only filter marker). */
|
||||
const stubRenderSlot = (() => <div data-testid="view-body" />) as unknown as ConversationRootProps['renderSlot']
|
||||
const stubRenderSlot = ((key: string) => key === 'conversation.composer.controls'
|
||||
? null
|
||||
: <div data-testid="view-body" />) as unknown as ConversationRootProps['renderSlot']
|
||||
/** SessionProvider seat stub (render-prop pass-through; ConversationRoot never invokes it). */
|
||||
const SessionProviderStub: ConversationRootProps['SessionProvider'] = ({ children }) => <>{children(SID)}</>
|
||||
|
||||
|
||||
@@ -119,9 +119,9 @@ describe('ConversationRoot', () => {
|
||||
// The renderSlot share as the outlet would bake it: renders a marker for
|
||||
// the ring key carrying the active-id filter (a Mock cannot satisfy the
|
||||
// generic method type directly — cast once at the prop seam).
|
||||
const renderSlot = vi.fn((key: string, _owner: object, opts?: { only?: string }) => (
|
||||
<div data-testid={`view-${opts?.only ?? '(all)'}`} data-slot={key} />
|
||||
))
|
||||
const renderSlot = vi.fn((key: string, _owner: object, opts?: { only?: string }) => key === 'conversation.composer.controls'
|
||||
? <span data-testid="composer-controls" />
|
||||
: <div data-testid={`view-${opts?.only ?? '(all)'}`} data-slot={key} />)
|
||||
const ui = render(
|
||||
<ConversationRoot
|
||||
sessionId={sid('s1')}
|
||||
@@ -173,7 +173,9 @@ describe('ConversationRoot', () => {
|
||||
const { renderSlot } = bench([tab('chat', 'Chat')])
|
||||
// No owner share: views take everything from the standard kit (contract).
|
||||
expect(renderSlot).toHaveBeenCalledWith('conversation.view', {}, { only: 'chat' })
|
||||
expect(renderSlot).toHaveBeenCalledWith('conversation.composer.controls', {})
|
||||
expect(screen.getByTestId('view-chat').getAttribute('data-slot')).toBe('conversation.view')
|
||||
expect(screen.getByTestId('composer-controls')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('hides the tab strip with a single view; composer writes the store draft and sends it', () => {
|
||||
@@ -195,6 +197,7 @@ describe('ConversationRoot', () => {
|
||||
bench([tab('chat', 'Chat')], undefined, { pending: [wait] }, renderSlotChain)
|
||||
expect(screen.getByText('question takeover')).toBeTruthy()
|
||||
expect(screen.queryByPlaceholderText(/输入消息/)).toBeNull()
|
||||
expect(screen.queryByTestId('composer-controls')).toBeNull()
|
||||
// The owner dispatches the raw pending list (chain currency); routing
|
||||
// lives in entry selectors, not here.
|
||||
expect(renderSlotChain).toHaveBeenCalledWith(
|
||||
|
||||
Reference in New Issue
Block a user