mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 00:03:52 +00:00
test(structured-chat): make the stale-binding clear and the availability guard observable
The bridge-route assertion used deep equality with an explicit undefined, which vitest cannot distinguish from an absent key, so reverting the stale-binding clear left the suite green. canUseStructuredNativeChat had no coverage at all. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mockCreateTab = vi.fn()
|
||||
const mockSetTabViewMode = vi.fn()
|
||||
const mockWaitForAgentReady = vi.fn()
|
||||
|
||||
const store = {
|
||||
activeRepoId: 'repo-1',
|
||||
activeWorktreeId: 'wt-1',
|
||||
settings: {
|
||||
agentCmdOverrides: {},
|
||||
agentDefaultArgs: {},
|
||||
agentDefaultEnv: {},
|
||||
activeRuntimeEnvironmentId: null,
|
||||
experimentalNativeChat: true,
|
||||
openAgentTabsInChatByDefault: true
|
||||
},
|
||||
projects: [{ id: 'repo-1', localWindowsRuntimePreference: { kind: 'inherit-global' as const } }],
|
||||
repos: [{ id: 'repo-1', connectionId: null as string | null, path: '/repo' }],
|
||||
sshConnectionStates: new Map(),
|
||||
transientClearedAgentStatusConnectionIds: {},
|
||||
worktreesByRepo: {
|
||||
'repo-1': [{ id: 'wt-1', repoId: 'repo-1', projectId: 'repo-1', path: '/repo/worktree' }]
|
||||
},
|
||||
detectedWorktreesByRepo: {},
|
||||
allWorktrees: vi.fn(() => store.worktreesByRepo['repo-1']),
|
||||
tabsByWorktree: { 'wt-1': [{ id: 'tab-1' }] },
|
||||
openFiles: [] as { id: string; worktreeId: string }[],
|
||||
browserTabsByWorktree: {} as Record<string, { id: string }[]>,
|
||||
tabBarOrderByWorktree: {} as Record<string, string[]>,
|
||||
terminalLayoutsByTabId: {},
|
||||
ptyIdsByTabId: {},
|
||||
createTab: mockCreateTab,
|
||||
closeTab: vi.fn(),
|
||||
queueTabStartupCommand: vi.fn(),
|
||||
setActiveTabType: vi.fn(),
|
||||
setTabViewMode: mockSetTabViewMode,
|
||||
setTabBarOrder: vi.fn(),
|
||||
setAgentStatus: vi.fn(),
|
||||
seedNativeChatLaunchPrompt: vi.fn(),
|
||||
seedNativeChatLaunchDraft: vi.fn(),
|
||||
markNativeChatLaunchPromptFailed: vi.fn()
|
||||
}
|
||||
|
||||
vi.mock('@/store', () => ({ useAppStore: { getState: () => store } }))
|
||||
vi.mock('sonner', () => ({ toast: { message: vi.fn(), error: vi.fn() } }))
|
||||
vi.mock('@/components/tab-bar/reconcile-order', () => ({ reconcileTabOrder: vi.fn(() => []) }))
|
||||
vi.mock('@/lib/agent-paste-draft', () => ({ pasteDraftWhenAgentReady: vi.fn() }))
|
||||
vi.mock('@/lib/agent-ready-wait', () => ({ waitForAgentReady: mockWaitForAgentReady }))
|
||||
vi.mock('@/lib/telemetry', () => ({
|
||||
track: vi.fn(),
|
||||
tuiAgentToAgentKind: (agent: string) => agent
|
||||
}))
|
||||
vi.mock('@/runtime/web-runtime-session', () => ({
|
||||
createWebRuntimeSessionTerminal: vi.fn(),
|
||||
createWebRuntimeAgentSessionTerminalWithLaunchDraft: vi.fn(),
|
||||
isWebRuntimeSessionActive: vi.fn(() => false),
|
||||
isWebTerminalSurfaceTabId: vi.fn(() => false)
|
||||
}))
|
||||
|
||||
/** Structured adoption creates the tab in terminal mode and flips it to chat once
|
||||
* Codex is ready; the bridge stamps `viewMode: 'chat'` on the tab up front. That
|
||||
* difference is the only observable signal that the availability guard ran. */
|
||||
describe('structured chat adoption guard on the launch path', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
store.repos = [{ id: 'repo-1', connectionId: null, path: '/repo' }]
|
||||
store.projects = [{ id: 'repo-1', localWindowsRuntimePreference: { kind: 'inherit-global' } }]
|
||||
mockCreateTab.mockReturnValue({ id: 'tab-1' })
|
||||
mockWaitForAgentReady.mockResolvedValue({ ready: true, reason: 'foreground-match' })
|
||||
})
|
||||
|
||||
it('adopts a local Codex tab into the structured stack', async () => {
|
||||
const { launchAgentInNewTab } = await import('./launch-agent-in-new-tab')
|
||||
|
||||
launchAgentInNewTab({ agent: 'codex', worktreeId: 'wt-1' })
|
||||
|
||||
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
|
||||
launchAgent: 'codex'
|
||||
})
|
||||
await vi.waitFor(() => expect(mockSetTabViewMode).toHaveBeenCalledWith('tab-1', 'chat'))
|
||||
})
|
||||
|
||||
it('keeps an SSH Codex tab on the bridge', async () => {
|
||||
store.repos = [{ id: 'repo-1', connectionId: 'ssh-a', path: '/repo' }]
|
||||
const { launchAgentInNewTab } = await import('./launch-agent-in-new-tab')
|
||||
|
||||
launchAgentInNewTab({ agent: 'codex', worktreeId: 'wt-1' })
|
||||
|
||||
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
|
||||
launchAgent: 'codex',
|
||||
viewMode: 'chat'
|
||||
})
|
||||
expect(mockWaitForAgentReady).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps a runtime-paired Codex tab on the bridge', async () => {
|
||||
store.repos = [{ id: 'repo-1', connectionId: 'runtime-ssh-a', path: '/repo' }]
|
||||
const { launchAgentInNewTab } = await import('./launch-agent-in-new-tab')
|
||||
|
||||
launchAgentInNewTab({ agent: 'codex', worktreeId: 'wt-1' })
|
||||
|
||||
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
|
||||
launchAgent: 'codex',
|
||||
viewMode: 'chat'
|
||||
})
|
||||
expect(mockWaitForAgentReady).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { AppState } from '@/store/types'
|
||||
import { canUseStructuredNativeChat } from './structured-native-chat-availability'
|
||||
|
||||
const { mockGetRendererAppPlatform } = vi.hoisted(() => ({
|
||||
mockGetRendererAppPlatform: vi.fn<() => NodeJS.Platform>(() => 'darwin')
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/renderer-app-platform', () => ({
|
||||
getRendererAppPlatform: mockGetRendererAppPlatform
|
||||
}))
|
||||
|
||||
function stateFor(input: {
|
||||
connectionId?: string | null
|
||||
windowsRuntime?: 'windows-host' | 'wsl'
|
||||
worktreePath?: string
|
||||
}): AppState {
|
||||
return {
|
||||
activeRepoId: 'repo-1',
|
||||
activeWorktreeId: 'wt-1',
|
||||
projects: [
|
||||
{
|
||||
id: 'repo-1',
|
||||
localWindowsRuntimePreference:
|
||||
input.windowsRuntime === 'wsl'
|
||||
? { kind: 'wsl', distro: 'Ubuntu' }
|
||||
: { kind: 'windows-host' }
|
||||
}
|
||||
],
|
||||
repos: [{ id: 'repo-1', connectionId: input.connectionId ?? null, path: 'C:\\repo' }],
|
||||
settings: {},
|
||||
worktreesByRepo: {
|
||||
'repo-1': [
|
||||
{
|
||||
id: 'wt-1',
|
||||
repoId: 'repo-1',
|
||||
projectId: 'repo-1',
|
||||
path: input.worktreePath ?? 'C:\\repo\\worktree'
|
||||
}
|
||||
]
|
||||
},
|
||||
detectedWorktreesByRepo: {}
|
||||
} as unknown as AppState
|
||||
}
|
||||
|
||||
describe('canUseStructuredNativeChat', () => {
|
||||
beforeEach(() => {
|
||||
mockGetRendererAppPlatform.mockReturnValue('darwin')
|
||||
})
|
||||
|
||||
it('allows the structured stack on a local worktree', () => {
|
||||
expect(canUseStructuredNativeChat(stateFor({}), 'wt-1')).toBe(true)
|
||||
})
|
||||
|
||||
it('refuses an SSH worktree so the pane stays on the bridge', () => {
|
||||
expect(canUseStructuredNativeChat(stateFor({ connectionId: 'ssh-a' }), 'wt-1')).toBe(false)
|
||||
})
|
||||
|
||||
it('refuses a runtime-paired worktree so the pane stays on the bridge', () => {
|
||||
expect(canUseStructuredNativeChat(stateFor({ connectionId: 'runtime-ssh-a' }), 'wt-1')).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('refuses a WSL project on Windows so the pane stays on the bridge', () => {
|
||||
mockGetRendererAppPlatform.mockReturnValue('win32')
|
||||
expect(canUseStructuredNativeChat(stateFor({ windowsRuntime: 'wsl' }), 'wt-1')).toBe(false)
|
||||
})
|
||||
|
||||
it('allows a Windows-host project on Windows', () => {
|
||||
mockGetRendererAppPlatform.mockReturnValue('win32')
|
||||
expect(canUseStructuredNativeChat(stateFor({ windowsRuntime: 'windows-host' }), 'wt-1')).toBe(
|
||||
true
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -229,10 +229,36 @@ describe('native chat routing', () => {
|
||||
).resolves.toBe('bridge')
|
||||
|
||||
expect(callStructuredAgentSession).not.toHaveBeenCalled()
|
||||
expect(patch).toHaveBeenCalledWith(tab.id, {
|
||||
viewMode: 'chat',
|
||||
structuredSessionId: undefined
|
||||
expect(patch).toHaveBeenCalledTimes(1)
|
||||
const [patchedTabId, bridgePatch] = patch.mock.calls[0] as [string, Record<string, unknown>]
|
||||
expect(patchedTabId).toBe(tab.id)
|
||||
expect(bridgePatch.viewMode).toBe('chat')
|
||||
// Deep equality treats an absent key as undefined, so presence is the only proof it was cleared.
|
||||
expect(Object.keys(bridgePatch)).toContain('structuredSessionId')
|
||||
expect(bridgePatch.structuredSessionId).toBeUndefined()
|
||||
})
|
||||
|
||||
it('keeps a Codex binding when the same agent falls back to the bridge', async () => {
|
||||
const { tab, state } = terminalState({
|
||||
agent: 'codex',
|
||||
structuredSessionId: 'codex_thread-codex',
|
||||
connectionId: 'ssh-1'
|
||||
})
|
||||
const patch = vi.fn()
|
||||
|
||||
await expect(
|
||||
setTerminalNativeChatMode({
|
||||
getState: () => state as never,
|
||||
patch,
|
||||
tabId: tab.id,
|
||||
mode: 'chat'
|
||||
})
|
||||
).resolves.toBe('bridge')
|
||||
|
||||
expect(callStructuredAgentSession).not.toHaveBeenCalled()
|
||||
expect(patch).toHaveBeenCalledTimes(1)
|
||||
const [, codexBridgePatch] = patch.mock.calls[0] as [string, Record<string, unknown>]
|
||||
expect(Object.keys(codexBridgePatch)).not.toContain('structuredSessionId')
|
||||
})
|
||||
|
||||
it('names the terminal direction when returning ownership fails', async () => {
|
||||
|
||||
Reference in New Issue
Block a user