Cover sidebar worker log activation

This commit is contained in:
Neil
2026-06-24 21:18:52 -07:00
parent d9d870d93f
commit f34b8ef70f
@@ -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()
})
})