test(desktop): cover /btw prompt.btw dispatch and btw.complete
Pin the RPC route (not slash.exec), bare-/btw usage, older-gateway fallback, and originating-session answer rendering. Co-authored-by: SsSs <w-kwan@hotmail.com> Co-authored-by: kokhlo <konstantin.khlopkov93@gmail.com> Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
This commit is contained in:
committed by
brooklyn!
parent
5994b833b0
commit
38e4de48d8
@@ -0,0 +1,66 @@
|
||||
import { act, cleanup } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { RpcEvent } from '@/types/hermes'
|
||||
|
||||
import { type MessageStreamHarness, renderMessageStream } from './test-harness'
|
||||
|
||||
const SID = 'session-1'
|
||||
const OTHER_SID = 'session-2'
|
||||
|
||||
let stream: MessageStreamHarness
|
||||
|
||||
function mountStream() {
|
||||
stream = renderMessageStream(SID)
|
||||
}
|
||||
|
||||
function emit(type: RpcEvent['type'], payload: RpcEvent['payload'] = {}, sessionId = SID) {
|
||||
act(() => stream.handleEvent({ payload, session_id: sessionId, type }))
|
||||
}
|
||||
|
||||
function lastMessage(id = SID) {
|
||||
return stream.state(id).messages.at(-1)
|
||||
}
|
||||
|
||||
describe('btw.complete event', () => {
|
||||
beforeEach(() => {
|
||||
mountStream()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
// #99065: prompt.btw delivers the answer here. The slash-worker route
|
||||
// printed it after the stdout capture window closed, so only the ack rendered.
|
||||
it('appends the answer to the originating session as a system message', () => {
|
||||
emit('btw.complete', { task_id: 'btw_ab12cd', question: 'which file was that error in?', text: 'src/main.ts' })
|
||||
|
||||
const message = lastMessage()
|
||||
|
||||
expect(message?.role).toBe('system')
|
||||
expect(message?.id).toBe('btw-complete-btw_ab12cd')
|
||||
expect(stream.text()).toBe('[btw "which file was that error in?" (btw_ab12cd)]\nsrc/main.ts')
|
||||
})
|
||||
|
||||
it('keeps another session untouched when the event targets this one', () => {
|
||||
emit('btw.complete', { task_id: 'btw_1', text: 'for the other chat' }, OTHER_SID)
|
||||
|
||||
expect(lastMessage()).toBeUndefined()
|
||||
expect(stream.text(OTHER_SID)).toBe('[btw (btw_1)]\nfor the other chat')
|
||||
})
|
||||
|
||||
it('omits the question and task-id header bits when the backend omits them', () => {
|
||||
emit('btw.complete', { text: 'the answer' })
|
||||
|
||||
expect(stream.text()).toBe('[btw]\nthe answer')
|
||||
})
|
||||
|
||||
it('drops an empty completion instead of appending a blank line', () => {
|
||||
emit('btw.complete', { task_id: 'btw_1', question: 'q', text: ' ' })
|
||||
|
||||
expect(lastMessage()).toBeUndefined()
|
||||
})
|
||||
})
|
||||
)
|
||||
@@ -1002,6 +1002,99 @@ describe('usePromptActions /compress', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('usePromptActions /btw', () => {
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
// #99065: slash.exec only captured the ack; the answer prints after the
|
||||
// worker's stdout window. prompt.btw + btw.complete is the TUI path.
|
||||
it('routes through prompt.btw (not slash.exec) and renders the start notice', async () => {
|
||||
const seeds: Record<string, unknown>[] = []
|
||||
|
||||
const requestGateway = vi.fn(async (method: string) => {
|
||||
if (method === 'prompt.btw') {
|
||||
return { task_id: 'btw_ab12cd' } as never
|
||||
}
|
||||
|
||||
return {} as never
|
||||
})
|
||||
|
||||
let handle: HarnessHandle | null = null
|
||||
await actRender(
|
||||
<Harness
|
||||
onReady={h => (handle = h)}
|
||||
onSeedState={s => seeds.push(s)}
|
||||
refreshSessions={async () => undefined}
|
||||
requestGateway={requestGateway}
|
||||
/>
|
||||
)
|
||||
|
||||
await handle!.submitText('/btw which file was that error in?')
|
||||
|
||||
expect(requestGateway).toHaveBeenCalledWith('prompt.btw', {
|
||||
session_id: RUNTIME_SESSION_ID,
|
||||
text: 'which file was that error in?'
|
||||
})
|
||||
expect(requestGateway).not.toHaveBeenCalledWith('slash.exec', expect.anything())
|
||||
expect(requestGateway).not.toHaveBeenCalledWith('command.dispatch', expect.anything())
|
||||
expect(renderedSeedTexts(seeds).some(text => text.includes('btw_ab12cd'))).toBe(true)
|
||||
})
|
||||
|
||||
it('shows usage when no question is typed', async () => {
|
||||
const seeds: Record<string, unknown>[] = []
|
||||
|
||||
const requestGateway = vi.fn(async () => ({} as never))
|
||||
|
||||
let handle: HarnessHandle | null = null
|
||||
await actRender(
|
||||
<Harness
|
||||
onReady={h => (handle = h)}
|
||||
onSeedState={s => seeds.push(s)}
|
||||
refreshSessions={async () => undefined}
|
||||
requestGateway={requestGateway}
|
||||
/>
|
||||
)
|
||||
|
||||
await handle!.submitText('/btw')
|
||||
|
||||
expect(requestGateway).not.toHaveBeenCalled()
|
||||
expect(renderedSeedTexts(seeds).some(text => text.includes('Usage: /btw'))).toBe(true)
|
||||
})
|
||||
|
||||
it('falls back to the slash worker when an older gateway lacks prompt.btw', async () => {
|
||||
const seeds: Record<string, unknown>[] = []
|
||||
|
||||
const requestGateway = vi.fn(async (method: string) => {
|
||||
if (method === 'prompt.btw') {
|
||||
throw new Error('method not found: prompt.btw')
|
||||
}
|
||||
|
||||
if (method === 'slash.exec') {
|
||||
return { output: 'Side question started by legacy gateway' } as never
|
||||
}
|
||||
|
||||
throw new Error(`unexpected method: ${method}`)
|
||||
})
|
||||
|
||||
let handle: HarnessHandle | null = null
|
||||
await actRender(
|
||||
<Harness
|
||||
onReady={h => (handle = h)}
|
||||
onSeedState={s => seeds.push(s)}
|
||||
refreshSessions={async () => undefined}
|
||||
requestGateway={requestGateway}
|
||||
/>
|
||||
)
|
||||
|
||||
await handle!.submitText('/btw anything')
|
||||
|
||||
expect(requestGateway).toHaveBeenCalledWith('slash.exec', expect.objectContaining({ command: 'btw anything' }))
|
||||
expect(renderedSeedTexts(seeds).some(text => text.includes('legacy gateway'))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('usePromptActions exec fallback error reporting', () => {
|
||||
beforeEach(() => {
|
||||
setSessions(() => [sessionInfo()])
|
||||
|
||||
@@ -232,9 +232,9 @@ describe('desktop slash command curation', () => {
|
||||
})
|
||||
|
||||
it('still routes commands without dedicated RPCs through exec()', () => {
|
||||
// /btw is an action (prompt.btw) — the slash-worker print never reached Desktop.
|
||||
const execNames = [
|
||||
'/bg',
|
||||
'/btw',
|
||||
'/debug',
|
||||
'/goal',
|
||||
'/personality',
|
||||
@@ -251,6 +251,13 @@ describe('desktop slash command curation', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('routes /btw to the prompt.btw side-question action', () => {
|
||||
expect(resolveDesktopCommand('/btw')?.surface).toEqual({ kind: 'action', action: 'btw' })
|
||||
expect(isDesktopSlashCommand('/btw')).toBe(true)
|
||||
expect(isDesktopSlashSuggestion('/btw')).toBe(true)
|
||||
expect(desktopSlashUnavailableMessage('/btw')).toBeNull()
|
||||
})
|
||||
|
||||
it('distinguishes free prose from finite slash option lists', () => {
|
||||
expect(desktopSlashCommandArgumentMode('/goal')).toBe('mixed')
|
||||
expect(desktopSlashCommandArgumentMode('/steer')).toBe('text')
|
||||
|
||||
Reference in New Issue
Block a user