mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 16:02:55 +00:00
Cover sidebar worker log activation
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { act } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { DashboardAgentRow as DashboardAgentRowData } from '@/components/dashboard/useDashboardData'
|
||||
import { makePaneKey } from '../../../../shared/stable-pane-id'
|
||||
|
||||
const LEAF_A = '11111111-1111-4111-8111-111111111111'
|
||||
const LEAF_B = '22222222-2222-4222-8222-222222222222'
|
||||
|
||||
type MockAgentOptions = {
|
||||
paneKey: string
|
||||
tabId: string
|
||||
agentType: string
|
||||
prompt: string
|
||||
worktreeId: string
|
||||
startedAt?: number
|
||||
}
|
||||
|
||||
function mockAgent({
|
||||
paneKey,
|
||||
tabId,
|
||||
agentType,
|
||||
prompt,
|
||||
worktreeId,
|
||||
startedAt = 1000
|
||||
}: MockAgentOptions): DashboardAgentRowData {
|
||||
return {
|
||||
paneKey,
|
||||
tab: { id: tabId },
|
||||
agentType,
|
||||
rowSource: 'live',
|
||||
state: 'working',
|
||||
startedAt,
|
||||
entry: {
|
||||
prompt,
|
||||
state: 'working',
|
||||
paneKey,
|
||||
updatedAt: startedAt,
|
||||
stateStartedAt: startedAt,
|
||||
stateHistory: [],
|
||||
worktreeId
|
||||
}
|
||||
} as unknown as DashboardAgentRowData
|
||||
}
|
||||
|
||||
let mockAgents: DashboardAgentRowData[] = []
|
||||
let mockAgentActivityDisplayMode: 'compact' | 'full' | undefined
|
||||
let mockTabsByWorktree: Record<string, { id: string }[]> = {}
|
||||
let mockAgentStatusByPaneKey: Record<string, { worktreeId?: string }> = {}
|
||||
let capturedRowActivations: {
|
||||
paneKey: string
|
||||
onActivate: (tabId: string, paneKey: string) => void
|
||||
}[] = []
|
||||
|
||||
function buildMockStoreState(): Record<string, unknown> {
|
||||
return {
|
||||
agentActivityDisplayMode: mockAgentActivityDisplayMode,
|
||||
acknowledgedAgentsByPaneKey: {},
|
||||
cacheTimerByKey: {},
|
||||
dropAgentStatus: vi.fn(),
|
||||
dismissRetainedAgent: vi.fn(),
|
||||
acknowledgeAgents: vi.fn(),
|
||||
agentSendPopoverTargetMode: null,
|
||||
agentStatusByPaneKey: mockAgentStatusByPaneKey,
|
||||
agentStatusEpoch: 0,
|
||||
tabsByWorktree: mockTabsByWorktree,
|
||||
terminalLayoutsByTabId: {},
|
||||
ptyIdsByTabId: {},
|
||||
runtimePaneTitlesByTabId: {},
|
||||
sendPromptToSidebarAgentTarget: vi.fn(),
|
||||
settings: {
|
||||
promptCacheTimerEnabled: true,
|
||||
promptCacheTtlMs: 60_000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const activationMocks = vi.hoisted(() => ({
|
||||
activateAndRevealWorktree: vi.fn(),
|
||||
activateTabAndFocusPane: vi.fn()
|
||||
}))
|
||||
|
||||
const staleAgentRowMocks = vi.hoisted(() => ({
|
||||
dismissStaleAgentRowByKey: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: Object.assign(
|
||||
(selector: (state: unknown) => unknown) => selector(buildMockStoreState()),
|
||||
{
|
||||
getState: () => buildMockStoreState()
|
||||
}
|
||||
)
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/worktree-activation', () => ({
|
||||
activateAndRevealWorktree: activationMocks.activateAndRevealWorktree
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/activate-tab-and-focus-pane', () => ({
|
||||
activateTabAndFocusPane: activationMocks.activateTabAndFocusPane
|
||||
}))
|
||||
|
||||
vi.mock('../terminal-pane/stale-agent-row', () => ({
|
||||
dismissStaleAgentRowByKey: staleAgentRowMocks.dismissStaleAgentRowByKey
|
||||
}))
|
||||
|
||||
vi.mock('./useWorktreeAgentRows', () => ({
|
||||
useWorktreeAgentRows: vi.fn(() => mockAgents)
|
||||
}))
|
||||
|
||||
vi.mock('@/components/dashboard/useNow', () => ({
|
||||
useNow: vi.fn(() => 2000)
|
||||
}))
|
||||
|
||||
vi.mock('@/components/dashboard/DashboardAgentRow', () => ({
|
||||
default: ({
|
||||
agent,
|
||||
onActivate
|
||||
}: {
|
||||
agent: DashboardAgentRowData
|
||||
onActivate: (tabId: string, paneKey: string) => void
|
||||
}) => {
|
||||
capturedRowActivations.push({ paneKey: agent.paneKey, onActivate })
|
||||
return <div data-testid="agent-row" data-pane-key={agent.paneKey} />
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('./focused-agent-row-highlight', () => ({
|
||||
useFocusedAgentPaneKey: vi.fn(() => null)
|
||||
}))
|
||||
|
||||
describe('WorktreeCardAgents activation', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
activationMocks.activateAndRevealWorktree.mockImplementation(() => undefined)
|
||||
mockAgents = []
|
||||
mockAgentActivityDisplayMode = undefined
|
||||
mockTabsByWorktree = {}
|
||||
mockAgentStatusByPaneKey = {}
|
||||
capturedRowActivations = []
|
||||
})
|
||||
|
||||
it('reveals the worktree and focuses an automation worker row hydrated during reveal', async () => {
|
||||
mockAgentActivityDisplayMode = 'full'
|
||||
const tabId = 'worker-tab'
|
||||
const paneKey = makePaneKey(tabId, LEAF_A)
|
||||
mockAgents = [
|
||||
mockAgent({
|
||||
paneKey,
|
||||
tabId,
|
||||
agentType: 'codex',
|
||||
prompt: 'Run automation worker',
|
||||
worktreeId: 'wt-1'
|
||||
})
|
||||
]
|
||||
mockAgentStatusByPaneKey = { [paneKey]: { worktreeId: 'wt-1' } }
|
||||
// Why: activation must use the post-reveal store snapshot, matching tab
|
||||
// hydration that arrives while a background worker is being opened.
|
||||
activationMocks.activateAndRevealWorktree.mockImplementation(() => {
|
||||
mockTabsByWorktree = { 'wt-1': [{ id: tabId }] }
|
||||
})
|
||||
const { default: WorktreeCardAgents } = await import('./WorktreeCardAgents')
|
||||
|
||||
renderToStaticMarkup(<WorktreeCardAgents worktreeId="wt-1" />)
|
||||
expect(capturedRowActivations).toHaveLength(1)
|
||||
capturedRowActivations[0].onActivate(tabId, paneKey)
|
||||
|
||||
expect(activationMocks.activateAndRevealWorktree).toHaveBeenCalledWith('wt-1')
|
||||
expect(activationMocks.activateTabAndFocusPane).toHaveBeenCalledWith(tabId, LEAF_A, {
|
||||
ackPaneKeyOnSuccess: paneKey,
|
||||
flashFocusedPane: true,
|
||||
scrollToBottomIfOutputSinceLastView: true
|
||||
})
|
||||
expect(staleAgentRowMocks.dismissStaleAgentRowByKey).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps a live worktree-attributed row visible while its tab is hydrating', async () => {
|
||||
mockAgentActivityDisplayMode = 'full'
|
||||
const tabId = 'hydrating-worker-tab'
|
||||
const paneKey = makePaneKey(tabId, LEAF_B)
|
||||
mockAgents = [
|
||||
mockAgent({
|
||||
paneKey,
|
||||
tabId,
|
||||
agentType: 'claude',
|
||||
prompt: 'Hydrating worker',
|
||||
worktreeId: 'wt-1'
|
||||
})
|
||||
]
|
||||
mockAgentStatusByPaneKey = { [paneKey]: { worktreeId: 'wt-1' } }
|
||||
const { default: WorktreeCardAgents } = await import('./WorktreeCardAgents')
|
||||
|
||||
renderToStaticMarkup(<WorktreeCardAgents worktreeId="wt-1" />)
|
||||
expect(capturedRowActivations).toHaveLength(1)
|
||||
capturedRowActivations[0].onActivate(tabId, paneKey)
|
||||
|
||||
expect(activationMocks.activateAndRevealWorktree).toHaveBeenCalledWith('wt-1')
|
||||
expect(activationMocks.activateTabAndFocusPane).not.toHaveBeenCalled()
|
||||
expect(staleAgentRowMocks.dismissStaleAgentRowByKey).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('reveals the worktree and focuses a compact automation worker row hydrated during reveal', async () => {
|
||||
mockAgentActivityDisplayMode = 'compact'
|
||||
const tabId = 'compact-worker-tab'
|
||||
const paneKey = makePaneKey(tabId, LEAF_A)
|
||||
mockAgents = [
|
||||
mockAgent({
|
||||
paneKey,
|
||||
tabId,
|
||||
agentType: 'gemini',
|
||||
prompt: 'Compact worker',
|
||||
worktreeId: 'wt-1'
|
||||
})
|
||||
]
|
||||
mockAgentStatusByPaneKey = { [paneKey]: { worktreeId: 'wt-1' } }
|
||||
// Why: compact rows share the same activation contract as full rows, so
|
||||
// this keeps the test pinned to reveal-time tab hydration.
|
||||
activationMocks.activateAndRevealWorktree.mockImplementation(() => {
|
||||
mockTabsByWorktree = { 'wt-1': [{ id: tabId }] }
|
||||
})
|
||||
const host = document.createElement('div')
|
||||
document.body.append(host)
|
||||
const root: Root = createRoot(host)
|
||||
const { default: WorktreeCardAgents } = await import('./WorktreeCardAgents')
|
||||
|
||||
await act(async () => {
|
||||
root.render(<WorktreeCardAgents worktreeId="wt-1" />)
|
||||
})
|
||||
const row = host.querySelector('.compact-agent-row')
|
||||
expect(row).toBeInstanceOf(HTMLElement)
|
||||
|
||||
await act(async () => {
|
||||
row?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
})
|
||||
|
||||
expect(activationMocks.activateAndRevealWorktree).toHaveBeenCalledWith('wt-1')
|
||||
expect(activationMocks.activateTabAndFocusPane).toHaveBeenCalledWith(tabId, LEAF_A, {
|
||||
ackPaneKeyOnSuccess: paneKey,
|
||||
flashFocusedPane: true,
|
||||
scrollToBottomIfOutputSinceLastView: true
|
||||
})
|
||||
act(() => root.unmount())
|
||||
host.remove()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user