test: remove mock-only editor, IPC status and startup suites

This commit is contained in:
OrcaWin
2026-11-13 19:00:36 -08:00
parent 4ab90e56f3
commit d3f1cc2dc7
17 changed files with 14 additions and 3708 deletions
-1
View File
@@ -23,7 +23,6 @@
1 test-no-in-test-import-actual:src/main/workspaces/removal/local-worktree-removal-recovery.test.ts
1 test-no-in-test-import-actual:src/renderer/src/components/terminal-pane/pty-connection/notifications/pty-connection-hook-idle-arbitration.test.ts
3 test-no-in-test-import-actual:src/renderer/src/components/terminal-pane/snapshots/remote-snapshot-alt-screen-host-chain.test.ts
1 test-no-in-test-import-actual:src/renderer/src/hooks/useIpcEvents-agent-status-pane-teardown.test.ts
1 test-no-polling-loop:src/main/browser/cookies/browser-cookie-import-concurrency.test.ts
1 test-no-polling-loop:src/main/codex/hooks/reconciliation/codex-hook-toggle-off-wins.test.ts
1 test-no-polling-loop:src/main/codex/structured/launch/codex-structured-real-cli-command-env.test.ts
File diff suppressed because one or more lines are too long
@@ -1,398 +0,0 @@
import type * as React from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { connectPanePty } from '../connect-pane-pty'
import { makePaneKey } from '../../../../../../shared/workspace-layout/stable-pane-id'
import { flushAsyncTicks } from '../test-fixtures/pty-connection-test-async'
import {
createManager,
createMockTransport,
createPane,
leafIdForPane,
type MockTransport
} from '../test-fixtures/pty-connection-test-pane-fixtures'
import {
buildPaneConnectionDeps,
type PtyConnectionAgentTitleMocks,
bindPaneAgentTitleMocks
} from '../test-fixtures/pty-connection-test-deps'
import {
installTerminalTestGlobals,
restoreTerminalTestGlobals
} from '../test-fixtures/pty-connection-test-environment'
import { createInitialStoreState } from '../test-fixtures/pty-connection-test-store-fixtures'
import type { StoreState } from '../test-fixtures/pty-connection-test-store-state'
import type * as TerminalWorktreeRouteModule from '@/lib/workspace/ownership/terminal-worktree-route'
const agentTitleMocks = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const {
notifyCodexPaneBoundForStaleSweep,
scheduleRuntimeGraphSync,
shouldSeedCacheTimerOnInitialTitle,
toastInfo,
waitForAgentDraftInputReady,
sendAgentDraftPasteContent,
onPasteTimeout,
pasteTimeoutNotice
} = vi.hoisted(() => ({
waitForAgentDraftInputReady: vi.fn(),
sendAgentDraftPasteContent: vi.fn(async () => true),
onPasteTimeout: vi.fn(),
pasteTimeoutNotice: vi.fn(),
notifyCodexPaneBoundForStaleSweep: vi.fn(),
scheduleRuntimeGraphSync: vi.fn(),
shouldSeedCacheTimerOnInitialTitle: vi.fn(() => false),
toastInfo: vi.fn()
}))
let mockStoreState: StoreState
let transportFactoryQueue: MockTransport[] = []
let createdTransportOptions: Record<string, unknown>[] = []
let storeSubscribers: ((state: StoreState) => void)[] = []
const layoutOwner = vi.hoisted(() => ({ isMain: false }))
vi.mock('../../../../../../shared/layout-owner-switch', () => ({
get ORCA_LAYOUT_OWNER_IS_MAIN() {
return layoutOwner.isMain
}
}))
vi.mock('@/runtime/sync-runtime-graph/graph-sync-scheduler', () => ({ scheduleRuntimeGraphSync }))
vi.mock('@/lib/agent-session/messaging/paste/agent-draft-readiness', () => ({
waitForAgentDraftInputReady
}))
const { requestTerminalPaneRecovery } = vi.hoisted(() => ({
requestTerminalPaneRecovery: vi.fn(async () => undefined)
}))
vi.mock('../../render-health/recovery/terminal-pane-recovery', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
requestTerminalPaneRecovery
}))
const pairedRoute = vi.hoisted(() => {
const route: { runtimeEnvironmentId: string | null } = { runtimeEnvironmentId: null }
return route
})
vi.mock('@/lib/workspace/ownership/terminal-worktree-route', async (importOriginal) => {
const actual = await importOriginal<typeof TerminalWorktreeRouteModule>()
return {
...actual,
resolveTerminalWorktreeRoute: (
...args: Parameters<typeof actual.resolveTerminalWorktreeRoute>
) =>
pairedRoute.runtimeEnvironmentId
? { runtimeEnvironmentId: pairedRoute.runtimeEnvironmentId }
: actual.resolveTerminalWorktreeRoute(...args)
}
})
vi.mock('../../pty-transport/remote-runtime/remote-runtime-pty-transport', () => ({
createRemoteRuntimePtyTransport: vi.fn(
(_environmentId: string, options: Record<string, unknown>) => {
createdTransportOptions.push(options)
const nextTransport = transportFactoryQueue.shift()
if (!nextTransport) {
throw new Error('No mock transport queued')
}
return nextTransport
}
)
}))
vi.mock('@/lib/agent-session/messaging/paste/agent-draft-paste-content', () => ({
sendAgentDraftPasteContent
}))
vi.mock('@/lib/agent-session/launch/prompt/launch-agent-paste-timeout-notice', () => ({
createPasteReadinessTimeoutNotice: (args: unknown) => {
pasteTimeoutNotice(args)
return { onTimeout: onPasteTimeout, wasNotified: () => false }
}
}))
vi.mock('@/store/index', () => ({
useAppStore: {
getState: () => mockStoreState,
subscribe: (listener: (state: StoreState) => void) => {
storeSubscribers.push(listener)
return () => {
storeSubscribers = storeSubscribers.filter((candidate) => candidate !== listener)
}
}
}
}))
vi.mock('@/lib/agent-status/aggregation/worktree-agent-status', async (importOriginal) => {
const { buildAgentStatusModuleMock } =
await import('../test-fixtures/pty-connection-test-environment')
return buildAgentStatusModuleMock(await importOriginal<Record<string, unknown>>())
})
vi.mock('../../titles/cache-timer-seeding', () => ({ shouldSeedCacheTimerOnInitialTitle }))
vi.mock('sonner', () => ({ toast: { info: toastInfo } }))
vi.mock('@/lib/agent-session/accounts/codex/codex-stale-pane-sweep', () => ({
notifyCodexPaneBoundForStaleSweep
}))
vi.mock('react', async (importOriginal) => {
const actual = await importOriginal<typeof React>()
return {
...actual,
useCallback: <T extends (...args: unknown[]) => unknown>(fn: T): T => fn
}
})
vi.mock('../../pty-transport/ipc/pty-transport', () => ({
createIpcPtyTransport: vi.fn((options: Record<string, unknown>) => {
createdTransportOptions.push(options)
const nextTransport = transportFactoryQueue.shift()
if (!nextTransport) {
throw new Error('No mock transport queued')
}
return nextTransport
})
}))
type ConnectedAgentPane = {
paneKey: string
transport: MockTransport
type: (data: string) => void
bind: (ptyId: string) => void
unbind: () => void
resolveConnect: (result: unknown) => void
dispose: () => void
}
function connectAgentPane(overrides: Record<string, unknown> = {}): ConnectedAgentPane {
const transport = createMockTransport()
let ptyId: string | null = null
transport.getPtyId.mockImplementation(() => ptyId)
transport.sendInput.mockImplementation(() => ptyId !== null)
let resolveConnect: (result: unknown) => void = () => {}
transport.connect.mockImplementation(() => new Promise((resolve) => (resolveConnect = resolve)))
transportFactoryQueue.push(transport)
const pane = createPane(1)
const userInputListeners: (() => void)[] = []
Object.assign(pane.terminal, {
_core: {
coreService: {
onUserInput: (listener: () => void) => {
userInputListeners.push(listener)
return { dispose: vi.fn() }
}
}
}
})
const connectArgs = [
pane,
createManager(1),
buildPaneConnectionDeps(() => mockStoreState, {
startup: { command: 'claude', launchAgent: 'claude' },
...overrides
})
]
bindPaneAgentTitleMocks(connectArgs[2], agentTitleMocks)
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the shared pane fixtures implement the members connectPanePty touches.
const binding = connectPanePty(...(connectArgs as unknown as Parameters<typeof connectPanePty>))
return {
paneKey: makePaneKey('tab-1', leafIdForPane(1)),
transport,
type: (data) => {
for (const listener of userInputListeners) {
listener()
}
for (const [listener] of pane.terminal.onData.mock.calls) {
listener(data)
}
},
bind: (id) => {
ptyId = id
const onPtySpawn = createdTransportOptions[0]?.onPtySpawn
if (typeof onPtySpawn === 'function') {
onPtySpawn(id)
}
},
unbind: () => {
ptyId = null
},
resolveConnect: (result) => {
if (result && typeof result === 'object' && 'id' in result && typeof result.id === 'string') {
ptyId = result.id
}
resolveConnect(result)
},
dispose: () => binding.dispose()
}
}
describe('agent pane keys typed before its PTY binds', () => {
beforeEach(async () => {
vi.clearAllMocks()
transportFactoryQueue = []
createdTransportOptions = []
storeSubscribers = []
mockStoreState = createInitialStoreState(() => mockStoreState)
await installTerminalTestGlobals()
})
afterEach(async () => {
pairedRoute.runtimeEnvironmentId = null
await restoreTerminalTestGlobals()
})
it('pastes them as an unsubmitted draft once the agent is ready', async () => {
waitForAgentDraftInputReady.mockResolvedValue(true)
const pane = connectAgentPane()
await flushAsyncTicks(20)
pane.type('make the tests pass')
pane.type('\r')
expect(pane.transport.sendInput).not.toHaveBeenCalled()
pane.bind('pty-1')
await flushAsyncTicks(20)
expect(waitForAgentDraftInputReady).toHaveBeenCalledWith(
'pty-1',
expect.any(Number),
expect.any(String)
)
expect(sendAgentDraftPasteContent).toHaveBeenCalledWith(
'pty-1',
'make the tests pass',
'driving',
expect.any(Function)
)
pane.dispose()
})
it('drops them with a notice when the agent never reports ready', async () => {
waitForAgentDraftInputReady.mockResolvedValue(false)
const pane = connectAgentPane()
await flushAsyncTicks(20)
pane.type('hello')
pane.bind('pty-1')
await flushAsyncTicks(20)
expect(sendAgentDraftPasteContent).not.toHaveBeenCalled()
expect(onPasteTimeout).toHaveBeenCalledOnce()
// The typed text exists nowhere else, so the notice must carry it back to the user.
expect(pasteTimeoutNotice).toHaveBeenCalledWith(
expect.objectContaining({ droppedText: 'hello' })
)
pane.dispose()
})
it('sends keys typed after the bind live and drops the draft rather than pasting over them', async () => {
let ready!: (value: boolean) => void
waitForAgentDraftInputReady.mockReturnValue(
new Promise<boolean>((resolve) => (ready = resolve))
)
const pane = connectAgentPane()
await flushAsyncTicks(20)
pane.type('fix it')
pane.bind('pty-1')
pane.type('y')
expect(pane.transport.sendInput).toHaveBeenCalledWith('y', 'driving')
ready(true)
await flushAsyncTicks(20)
expect(sendAgentDraftPasteContent).not.toHaveBeenCalled()
expect(onPasteTimeout).toHaveBeenCalledOnce()
pane.dispose()
})
it('drops the held draft when the pane closes before the bind', async () => {
const pane = connectAgentPane()
await flushAsyncTicks(20)
pane.type('lost on close')
pane.dispose()
pane.bind('pty-1')
await flushAsyncTicks(20)
expect(waitForAgentDraftInputReady).not.toHaveBeenCalled()
expect(sendAgentDraftPasteContent).not.toHaveBeenCalled()
})
it('discards the draft when the first bind is a reattach result, then sends keys live', async () => {
const pane = connectAgentPane()
await flushAsyncTicks(20)
waitForAgentDraftInputReady.mockResolvedValue(true)
pane.type('held')
pane.resolveConnect({ id: 'pty-1', isReattach: true })
await flushAsyncTicks(40)
// A later fresh spawn (the cold-restore resume) must not receive the old draft either.
pane.unbind()
pane.bind('pty-2')
await flushAsyncTicks(20)
pane.type('z')
expect(waitForAgentDraftInputReady).not.toHaveBeenCalled()
expect(sendAgentDraftPasteContent).not.toHaveBeenCalled()
expect(pane.transport.sendInput).toHaveBeenCalledWith('z', 'driving')
pane.dispose()
})
// Why: the paired-server transport holds and delivers type-ahead itself, Ctrl+C included.
it('leaves a paired-server agent pane resuming a live entry on its own transport', async () => {
pairedRoute.runtimeEnvironmentId = 'env-1'
const paneKey = makePaneKey('tab-1', leafIdForPane(1))
mockStoreState.agentStatusByPaneKey[paneKey] = {
state: 'working',
prompt: '',
agentType: 'claude',
paneKey,
updatedAt: 1_000,
stateStartedAt: 1_000,
stateHistory: [],
providerSession: { key: 'session_id', id: 'session-1' }
}
const pane = connectAgentPane({ startup: undefined })
await flushAsyncTicks(20)
pane.type('before reattach')
pane.type('\x03')
expect(pane.transport.sendInput).toHaveBeenCalledWith('before reattach', 'driving')
// Ctrl+C takes the acknowledged-write path.
expect(pane.transport.sendInputAccepted).toHaveBeenCalledWith('\x03', 'driving')
pane.bind('pty-1')
await flushAsyncTicks(20)
expect(waitForAgentDraftInputReady).not.toHaveBeenCalled()
expect(sendAgentDraftPasteContent).not.toHaveBeenCalled()
pane.dispose()
})
// Why: a first connect hung past its grace must still let typing arm recovery.
it('still requests recovery for keys held while the first connect hangs past its grace', async () => {
const pane = connectAgentPane()
await flushAsyncTicks(20)
pane.type('a')
expect(requestTerminalPaneRecovery).not.toHaveBeenCalled()
const now = Date.now()
const clock = vi.spyOn(Date, 'now').mockReturnValue(now + 61_000)
try {
pane.type('b')
expect(requestTerminalPaneRecovery).toHaveBeenCalledTimes(1)
} finally {
clock.mockRestore()
}
pane.dispose()
})
})
@@ -1,199 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { TerminalPaneResumeOutcome } from '@/store/terminals/terminal-state'
import type { TerminalLayoutSnapshot } from '../../../../../../shared/workspace-layout/terminal-tab-types'
import type { ConnectPanePtySession } from '../connect-pane-pty-session'
import type { showMainPaneResumeOutcome as ShowOutcome } from '../spawn/main-pane-resume-banner'
type Outcomes = Record<string, TerminalPaneResumeOutcome>
type StoreState = {
terminalPaneResumeOutcomesByPaneKey: Outcomes
terminalLayoutsByTabId: Record<string, TerminalLayoutSnapshot>
}
const store = vi.hoisted(() => {
let state: StoreState = { terminalPaneResumeOutcomesByPaneKey: {}, terminalLayoutsByTabId: {} }
const listeners = new Set<(next: StoreState) => void>()
return {
getState: () => state,
subscribe: (listener: (next: StoreState) => void) => {
listeners.add(listener)
return () => listeners.delete(listener)
},
publish: (patch: Partial<StoreState>) => {
state = { ...state, ...patch }
for (const listener of listeners) {
listener(state)
}
},
reset: () => {
state = { terminalPaneResumeOutcomesByPaneKey: {}, terminalLayoutsByTabId: {} }
listeners.clear()
}
}
})
vi.mock('@/store/index', () => ({ useAppStore: store }))
vi.mock('../spawn/main-starts-pane', () => ({ mainStartsPane: () => true }))
const PANE_KEY = 'tab-1:leaf-1'
function attachedView() {
const view = {
cacheKey: PANE_KEY,
disposed: false,
deps: { tabId: 'tab-1' },
pane: { leafId: 'leaf-1' },
waitTeardowns: new Array<() => void>(),
showSessionRestoredBanner: vi.fn()
}
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the banner reads only these session fields; mainStartsPane is mocked.
return { view, session: view as unknown as ConnectPanePtySession }
}
/** A fresh module, as a renderer reload loads it; session storage survives the reload. */
async function loadBannerModule(): Promise<typeof ShowOutcome> {
vi.resetModules()
const { showMainPaneResumeOutcome, acceptMainPaneResumeStart } =
await import('../spawn/main-pane-resume-banner')
const { admitTerminalNoticeLifetime } =
await import('../../provider-notices/terminal-notice-lifetime')
return (session, ptyId) => {
admitTerminalNoticeLifetime(ptyId)
acceptMainPaneResumeStart(session, ptyId)
showMainPaneResumeOutcome(session, ptyId)
}
}
describe("the window's banner for main's start of a pane", () => {
let storage: Map<string, string>
beforeEach(() => {
store.reset()
storage = new Map()
vi.stubGlobal('sessionStorage', {
getItem: (key: string) => storage.get(key) ?? null,
setItem: (key: string, value: string) => storage.set(key, value)
})
})
afterEach(() => {
vi.unstubAllGlobals()
})
it('shows once per start: a later start under the same terminal id shows again', async () => {
const showMainPaneResumeOutcome = await loadBannerModule()
const publish = (ptyGeneration: number, outcome: TerminalPaneResumeOutcome['outcome']) =>
store.publish({
terminalPaneResumeOutcomesByPaneKey: {
[PANE_KEY]: { ptyId: 'pty-1', ptyGeneration, outcome }
}
})
publish(1, 'restored')
const first = attachedView()
showMainPaneResumeOutcome(first.session, 'pty-1')
// A respawn kept the daemon id; its bind raised the generation, so this is a new start.
publish(2, 'resume-unavailable')
const respawned = attachedView()
showMainPaneResumeOutcome(respawned.session, 'pty-1')
expect(respawned.view.showSessionRestoredBanner).toHaveBeenCalledExactlyOnceWith(
'resume-unavailable'
)
const again = attachedView()
showMainPaneResumeOutcome(again.session, 'pty-1')
expect(again.view.showSessionRestoredBanner).not.toHaveBeenCalled()
})
it('shows nothing again after a renderer reload reattaches the same start', async () => {
store.publish({
terminalPaneResumeOutcomesByPaneKey: {
[PANE_KEY]: { ptyId: 'pty-1', ptyGeneration: 1, outcome: 'resume-unavailable' }
}
})
const before = attachedView()
;(await loadBannerModule())(before.session, 'pty-1')
expect(before.view.showSessionRestoredBanner).toHaveBeenCalledOnce()
const reloaded = attachedView()
;(await loadBannerModule())(reloaded.session, 'pty-1')
expect(reloaded.view.showSessionRestoredBanner).not.toHaveBeenCalled()
})
it('retains a shown start across a publication gap', async () => {
const attach = await loadBannerModule()
const publish = (ptyId: string, ptyGeneration: number) =>
store.publish({
terminalPaneResumeOutcomesByPaneKey: {
[PANE_KEY]: { ptyId, ptyGeneration, outcome: 'restored' }
}
})
publish('pty-a', 3)
const first = attachedView()
attach(first.session, 'pty-a')
expect(first.view.showSessionRestoredBanner).toHaveBeenCalledExactlyOnceWith('restored')
store.publish({ terminalPaneResumeOutcomesByPaneKey: {} })
publish('pty-b', 4)
attach(attachedView().session, 'pty-b')
publish('pty-a', 3)
const remounted = attachedView()
attach(remounted.session, 'pty-a')
expect(remounted.view.showSessionRestoredBanner).not.toHaveBeenCalled()
// A late older fact cannot replace the accepted start or reset suppression.
publish('pty-a', 2)
attach(remounted.session, 'pty-a')
publish('pty-a', 3)
attach(remounted.session, 'pty-a')
expect(remounted.view.showSessionRestoredBanner).not.toHaveBeenCalled()
})
it('retains a shown start across a publication gap without session storage', async () => {
vi.stubGlobal('sessionStorage', {
getItem: () => {
throw new Error('storage unavailable')
},
setItem: () => {
throw new Error('storage unavailable')
}
})
const attach = await loadBannerModule()
const publish = (ptyId: string, ptyGeneration: number) =>
store.publish({
terminalPaneResumeOutcomesByPaneKey: {
[PANE_KEY]: { ptyId, ptyGeneration, outcome: 'restored' }
}
})
publish('pty-a', 3)
const first = attachedView()
attach(first.session, 'pty-a')
expect(first.view.showSessionRestoredBanner).toHaveBeenCalledExactlyOnceWith('restored')
store.publish({ terminalPaneResumeOutcomesByPaneKey: {} })
publish('pty-b', 4)
attach(attachedView().session, 'pty-b')
publish('pty-a', 3)
const remounted = attachedView()
attach(remounted.session, 'pty-a')
expect(remounted.view.showSessionRestoredBanner).not.toHaveBeenCalled()
// A late older fact cannot replace the accepted start or reset suppression.
publish('pty-a', 2)
attach(remounted.session, 'pty-a')
publish('pty-a', 3)
attach(remounted.session, 'pty-a')
expect(remounted.view.showSessionRestoredBanner).not.toHaveBeenCalled()
})
it('shows nothing for an outcome naming another terminal', async () => {
const showMainPaneResumeOutcome = await loadBannerModule()
store.publish({
terminalPaneResumeOutcomesByPaneKey: {
[PANE_KEY]: { ptyId: 'pty-old', ptyGeneration: 1, outcome: 'restored' }
}
})
const { view, session } = attachedView()
showMainPaneResumeOutcome(session, 'pty-1')
store.publish({
terminalPaneResumeOutcomesByPaneKey: {
[PANE_KEY]: { ptyId: 'pty-old', ptyGeneration: 2, outcome: 'resume-unavailable' }
}
})
expect(view.showSessionRestoredBanner).not.toHaveBeenCalled()
})
})
@@ -1,66 +0,0 @@
import { vi } from 'vitest'
import { buildStoreState } from './ipc-events-agent-status-store-test-fixtures'
import type { StoreLike } from './ipc-events-agent-status-store-test-fixtures'
import { buildWindowApi } from './ipc-events-agent-status-window-test-fixtures'
export function buildSshAuthorityReconciliationHarness(args: {
partialAuthority: { providerEpoch?: string; connectionGeneration?: number }
latestAuthority: { providerEpoch: string; connectionGeneration: number }
}): {
emitPartialState: () => void
getState: ReturnType<typeof vi.fn>
setSshConnectionState: ReturnType<typeof vi.fn>
storeState: StoreLike
storedState: () => Record<string, unknown> | undefined
} {
const targetId = 'target-reconciliation'
const baseState = {
targetId,
status: 'connected' as const,
error: null,
reconnectAttempt: 0
}
const partialState = { ...baseState, ...args.partialAuthority }
const latestState = { ...baseState, ...args.latestAuthority }
const sshConnectionStates = new Map<string, Record<string, unknown>>()
let sshStateListener: ((data: { targetId: string; state: unknown }) => void) | undefined
const getState = vi.fn(() => Promise.resolve(latestState))
const setSshConnectionState = vi.fn((nextTargetId: string, state: Record<string, unknown>) => {
sshConnectionStates.set(nextTargetId, state)
})
const storeState = buildStoreState({
sshTargetLabels: new Map([[targetId, 'Reconciliation Target']]),
sshConnectionStates,
setSshConnectionState,
setSshTargetsMetadata: vi.fn(),
setRemovedSshTargetLabels: vi.fn(),
clearRemoteDetectedAgents: vi.fn(),
clearRemovedSshTargetState: vi.fn()
})
vi.stubGlobal(
'window',
buildWindowApi({
onSet: () => () => {},
ssh: {
getState,
onStateChanged: (listener: (data: { targetId: string; state: unknown }) => void) => {
sshStateListener = listener
return () => {}
}
}
})
)
return {
emitPartialState: () => {
if (!sshStateListener) {
throw new Error('Expected SSH state listener')
}
sshStateListener({ targetId, state: partialState })
},
getState,
setSshConnectionState,
storeState,
storedState: () => sshConnectionStates.get(targetId)
}
}
@@ -1,326 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type * as EditorAutosaveModule from '@/components/editor/autosave/editor-autosave'
import type { FsChangedPayload } from '../../../shared/file-watch/filesystem-change-event'
vi.mock('@/store', () => ({
useAppStore: { getState: vi.fn() }
}))
vi.mock('@/components/editor/autosave/editor-autosave', async (importOriginal) => {
const actual = await importOriginal<typeof EditorAutosaveModule>()
return {
...actual,
notifyEditorExternalFileChange: vi.fn(),
getOpenFilesForExternalFileChange: vi.fn(() => [])
}
})
import {
buildEditorExternalWatchEventHandler as createExternalWatchEventHandler,
EXTERNAL_MUTATION_DEBOUNCE_MS
} from './editor-external-watch-event-reconciliation'
import { verifyLatchedMoveDestinations } from './useEditorExternalWatch'
import { useAppStore } from '@/store'
import type { OpenFile } from '@/store/slices/editor/types/open-file'
import { createTestStore } from '@/store/testing/app-store-test-fixture'
import { getOpenFilesForExternalFileChange } from '@/components/editor/autosave/editor-autosave'
import { __clearSelfWriteRegistryForTests } from '@/components/editor/external-changes/editor-self-write-registry'
import {
__clearEditorPathMovesForTests,
beginEditorPathMove,
settleEditorPathMove
} from '@/components/editor/editor-path-move-inflight'
import { getDiskBaselineSignature } from '../../../shared/file-write/disk-baseline-signature'
const findTarget = (worktreePath: string, runtimeEnvironmentId: string | null = null) =>
worktreePath === '/repo'
? { worktreeId: 'wt-1', worktreePath: '/repo', connectionId: undefined, runtimeEnvironmentId }
: undefined
function payload(events: FsChangedPayload['events']): FsChangedPayload {
return { worktreePath: '/repo', events }
}
describe('self-move source suppression (in-flight)', () => {
const setExternalMutation = vi.fn()
const fileNotes = {
id: 'file-notes',
worktreeId: 'wt-1',
worktreePath: '/repo',
filePath: '/repo/notes.md',
relativePath: 'notes.md',
language: 'markdown',
mode: 'edit' as const,
isDirty: false
}
beforeEach(() => {
vi.clearAllMocks()
vi.useFakeTimers()
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [fileNotes],
setExternalMutation
})
})
afterEach(() => {
vi.useRealTimers()
vi.unstubAllGlobals()
__clearSelfWriteRegistryForTests()
__clearEditorPathMovesForTests()
})
function beginMove(): void {
beginEditorPathMove({
operationId: 'op-1',
worktreeId: 'wt-1',
runtimeEnvironmentId: null,
sourcePaths: ['/repo/notes.md']
})
}
it('does not tombstone the source of an in-flight move (tab still at old path)', () => {
beginMove()
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'delete', absolutePath: '/repo/notes.md' }]))
vi.advanceTimersByTime(EXTERNAL_MUTATION_DEBOUNCE_MS)
expect(setExternalMutation).not.toHaveBeenCalledWith('file-notes', 'deleted')
expect(setExternalMutation).not.toHaveBeenCalledWith('file-notes', 'renamed')
dispose()
})
it('does not tombstone a tab opened UNDER a moving directory mid-rename', () => {
// Root move provenance must cover files opened during the move, even absent from its original tab list.
const lateTab = {
id: 'file-late',
worktreeId: 'wt-1',
worktreePath: '/repo',
filePath: '/repo/src/late.md',
relativePath: 'src/late.md',
language: 'markdown',
mode: 'edit' as const,
isDirty: true
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [lateTab],
setExternalMutation
})
beginEditorPathMove({
operationId: 'op-dir',
worktreeId: 'wt-1',
runtimeEnvironmentId: null,
sourcePaths: ['/repo/src']
})
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(
payload([
{ kind: 'delete', absolutePath: '/repo/src/late.md' },
{ kind: 'create', absolutePath: '/repo/dst/late.md' }
])
)
vi.advanceTimersByTime(EXTERNAL_MUTATION_DEBOUNCE_MS)
expect(setExternalMutation).not.toHaveBeenCalledWith('file-late', 'renamed')
expect(setExternalMutation).not.toHaveBeenCalledWith('file-late', 'deleted')
dispose()
})
it('tombstones a genuine delete once the move has settled', () => {
beginMove()
settleEditorPathMove('op-1')
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'delete', absolutePath: '/repo/notes.md' }]))
vi.advanceTimersByTime(EXTERNAL_MUTATION_DEBOUNCE_MS)
expect(setExternalMutation).toHaveBeenCalledWith('file-notes', 'deleted')
dispose()
})
})
describe('self-move echo verification (content identity)', () => {
const setExternalMutation = vi.fn()
const clearSelfMoveEcho = vi.fn()
const BASELINE_CONTENT = 'the file on disk\n'
const baselineSignature = getDiskBaselineSignature(BASELINE_CONTENT)
const TARGET_PATH = '/repo/subdir/notes.md'
function movedDirtyTab(overrides: Partial<OpenFile> = {}): OpenFile {
return {
id: 'file-notes',
worktreeId: 'wt-1',
filePath: TARGET_PATH,
relativePath: 'subdir/notes.md',
language: 'markdown',
mode: 'edit' as const,
isDirty: true,
lastKnownDiskSignature: baselineSignature,
pendingSelfMoveEcho: { operationId: 'op-1', targetPath: TARGET_PATH },
...overrides
}
}
function mockState(file: OpenFile): void {
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [file],
setExternalMutation,
clearSelfMoveEcho
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([file])
}
function payloadFor(): FsChangedPayload {
return { worktreePath: '/repo', events: [{ kind: 'update', absolutePath: TARGET_PATH }] }
}
function stubDiskRead(result: unknown): void {
vi.stubGlobal('window', { api: { fs: { readFile: vi.fn().mockResolvedValue(result) } } })
}
function stubDiskReadError(error: Error): void {
vi.stubGlobal('window', { api: { fs: { readFile: vi.fn().mockRejectedValue(error) } } })
}
beforeEach(() => {
vi.clearAllMocks()
vi.useFakeTimers()
__clearSelfWriteRegistryForTests()
__clearEditorPathMovesForTests()
})
afterEach(() => {
vi.useRealTimers()
vi.unstubAllGlobals()
})
it('suppresses when disk matches the baseline', async () => {
mockState(movedDirtyTab())
stubDiskRead({ isBinary: false, content: BASELINE_CONTENT })
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payloadFor())
await vi.advanceTimersByTimeAsync(100)
expect(setExternalMutation).not.toHaveBeenCalledWith('file-notes', 'changed')
expect(clearSelfMoveEcho).toHaveBeenCalledWith('file-notes')
dispose()
})
it('marks changed when a genuine external write differs from the baseline', async () => {
mockState(movedDirtyTab())
stubDiskRead({ isBinary: false, content: 'an agent rewrote this file\n' })
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payloadFor())
await vi.advanceTimersByTimeAsync(100)
expect(setExternalMutation).toHaveBeenCalledWith('file-notes', 'changed')
dispose()
})
it('fails closed (marks changed) when the tab has no disk baseline', async () => {
mockState(movedDirtyTab({ lastKnownDiskSignature: undefined }))
stubDiskRead({ isBinary: false, content: BASELINE_CONTENT })
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payloadFor())
await vi.advanceTimersByTimeAsync(100)
expect(setExternalMutation).toHaveBeenCalledWith('file-notes', 'changed')
dispose()
})
it('fails closed when the destination read errors', async () => {
mockState(movedDirtyTab())
stubDiskReadError(new Error('EACCES'))
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payloadFor())
await vi.advanceTimersByTimeAsync(100)
expect(setExternalMutation).toHaveBeenCalledWith('file-notes', 'changed')
dispose()
})
it('fails closed when the destination is binary', async () => {
mockState(movedDirtyTab())
stubDiskRead({ isBinary: true, content: '' })
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payloadFor())
await vi.advanceTimersByTimeAsync(100)
expect(setExternalMutation).toHaveBeenCalledWith('file-notes', 'changed')
dispose()
})
it('does not verify an event with no move provenance (normal changed-on-disk path)', async () => {
mockState(movedDirtyTab({ pendingSelfMoveEcho: undefined }))
stubDiskRead({ isBinary: false, content: BASELINE_CONTENT })
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payloadFor())
await vi.advanceTimersByTimeAsync(100)
expect(setExternalMutation).toHaveBeenCalledWith('file-notes', 'changed')
dispose()
})
})
describe('verifyLatchedMoveDestinations (proactive, cross-worktree path)', () => {
const clearSelfMoveEcho = vi.fn()
const setExternalMutation = vi.fn()
const CONTENT = 'the echoed file on disk\n'
const baseline = getDiskBaselineSignature(CONTENT)
// Floating tabs use their own root-relative paths, never the initiating workspace root.
const FLOATING_FILE_PATH = '/elsewhere/notes/readme.md'
const floatingTab = {
id: 'file-x',
worktreeId: 'floating',
worktreePath: '/elsewhere',
filePath: FLOATING_FILE_PATH,
relativePath: '../notes/readme.md',
runtimeEnvironmentId: null,
language: 'markdown',
mode: 'edit' as const,
isDirty: true,
lastKnownDiskSignature: baseline,
pendingSelfMoveEcho: { operationId: 'op-1', targetPath: FLOATING_FILE_PATH }
}
let readFile: ReturnType<typeof vi.fn>
beforeEach(() => {
vi.clearAllMocks()
vi.useFakeTimers()
readFile = vi.fn().mockResolvedValue({ isBinary: false, content: CONTENT })
vi.stubGlobal('window', { api: { fs: { readFile } } })
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [floatingTab],
clearSelfMoveEcho,
setExternalMutation
})
})
afterEach(() => {
vi.useRealTimers()
vi.unstubAllGlobals()
})
it('reads the tab own filePath, not the initiating worktree joined with its relativePath', async () => {
verifyLatchedMoveDestinations('/initiating-worktree', undefined, ['file-x'])
await vi.advanceTimersByTimeAsync(50)
expect(readFile).toHaveBeenCalledWith(expect.objectContaining({ filePath: FLOATING_FILE_PATH }))
expect(setExternalMutation).not.toHaveBeenCalledWith('file-x', 'changed')
// Proactive verification must retain provenance for later watcher echoes.
expect(clearSelfMoveEcho).not.toHaveBeenCalled()
})
})
@@ -1,203 +0,0 @@
// @vitest-environment happy-dom
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { act, createElement } from 'react'
import { createRoot, type Root } from 'react-dom/client'
type TestWatchTarget = {
worktreeId: string
worktreePath: string
connectionId: string | undefined
runtimeEnvironmentId: string | null
allowLocalWindowsWslAliases?: true
}
const subscriptionState = vi.hoisted(() => ({
snapshot: { targets: [] as TestWatchTarget[], targetsKey: '' },
subscribeRuntimeFileChanges: vi.fn(),
disposeEventHandler: vi.fn(),
handleFsChanged: vi.fn(),
contacts: new Map<string, () => void>(),
unsubscribeContact: vi.fn()
}))
vi.mock('@/store', () => ({
useAppStore: Object.assign(
(selector: (state: unknown) => unknown) => selector({ openFiles: [] }),
{
getState: () => ({ openFiles: [] })
}
)
}))
vi.mock('@/runtime/hosts/connections/runtime-host-contact-regained', () => ({
subscribeRuntimeHostContactRegained: (id: string, listener: () => void) => {
subscriptionState.contacts.set(id, listener)
return subscriptionState.unsubscribeContact
}
}))
vi.mock('@/runtime/files/watching/runtime-file-watch-client', () => ({
subscribeRuntimeFileChanges: subscriptionState.subscribeRuntimeFileChanges
}))
vi.mock('./editor-external-watch-targets', () => ({
selectEditorExternalWatchTargets: () => ({
unwatchedTargetKeys: new Set<string>(),
unwatchedTargetsKey: '',
...subscriptionState.snapshot
}),
getEditorExternalWatchTargetKey: (target: TestWatchTarget) =>
[
target.worktreeId,
target.worktreePath,
target.connectionId ?? 'local',
target.runtimeEnvironmentId ?? 'client',
target.allowLocalWindowsWslAliases ? 'wsl-aliases' : 'literal'
].join('::')
}))
vi.mock('./editor-external-watch-event-reconciliation', () => ({
buildEditorExternalWatchEventHandler: vi.fn(() => ({
handleFsChanged: subscriptionState.handleFsChanged,
dispose: subscriptionState.disposeEventHandler
})),
collectOverflowEditorExternalReloadTargets: vi.fn()
}))
vi.mock('./editor-external-watch-disk-verification', () => ({
verifyLatchedEditorMoveDestinations: vi.fn()
}))
import { useEditorExternalWatch } from './useEditorExternalWatch'
function WatchProbe(): null {
useEditorExternalWatch()
return null
}
function runtimeTarget(): TestWatchTarget {
return {
worktreeId: 'wt-runtime',
worktreePath: '/runtime/repo',
connectionId: 'nested-ssh',
runtimeEnvironmentId: 'runtime-1'
}
}
function deferredRuntimeSubscription(): {
promise: Promise<() => void>
resolve: (unsubscribe: () => void) => void
} {
let resolve!: (unsubscribe: () => void) => void
const promise = new Promise<() => void>((settle) => {
resolve = settle
})
return { promise, resolve }
}
describe('useEditorExternalWatch subscriptions', () => {
let previousApi: unknown
let container: HTMLDivElement
let root: Root
let watchWorktree: ReturnType<typeof vi.fn>
let unwatchWorktree: ReturnType<typeof vi.fn>
let unsubscribeFsEvents: ReturnType<typeof vi.fn>
beforeEach(() => {
vi.clearAllMocks()
subscriptionState.contacts.clear()
subscriptionState.subscribeRuntimeFileChanges.mockReset()
subscriptionState.snapshot = { targets: [], targetsKey: '' }
watchWorktree = vi.fn().mockResolvedValue(undefined)
unwatchWorktree = vi.fn().mockResolvedValue(undefined)
unsubscribeFsEvents = vi.fn()
previousApi = (window as unknown as { api?: unknown }).api
;(window as unknown as { api: unknown }).api = {
fs: {
watchWorktree,
unwatchWorktree,
onFsChanged: vi.fn(() => unsubscribeFsEvents)
}
}
container = document.body.appendChild(document.createElement('div'))
root = createRoot(container)
})
afterEach(async () => {
await act(async () => root.unmount())
container.remove()
;(window as unknown as { api?: unknown }).api = previousApi
})
it('cannot let an old runtime subscribe resolution replace a re-added watch', async () => {
const stalePending = deferredRuntimeSubscription()
const currentPending = deferredRuntimeSubscription()
const unsubscribeStale = vi.fn()
const unsubscribeCurrent = vi.fn()
subscriptionState.subscribeRuntimeFileChanges
.mockReturnValueOnce(stalePending.promise)
.mockReturnValueOnce(currentPending.promise)
subscriptionState.snapshot = {
targets: [runtimeTarget()],
targetsKey: 'runtime-watch-1'
}
await act(async () => root.render(createElement(WatchProbe)))
subscriptionState.snapshot = { targets: [], targetsKey: 'no-runtime-watch' }
await act(async () => root.render(createElement(WatchProbe)))
subscriptionState.snapshot = {
targets: [runtimeTarget()],
targetsKey: 'runtime-watch-2'
}
await act(async () => root.render(createElement(WatchProbe)))
currentPending.resolve(unsubscribeCurrent)
await act(async () => currentPending.promise)
stalePending.resolve(unsubscribeStale)
await act(async () => stalePending.promise)
expect(unsubscribeStale).toHaveBeenCalledTimes(1)
expect(unsubscribeCurrent).not.toHaveBeenCalled()
await act(async () => root.unmount())
expect(unsubscribeCurrent).toHaveBeenCalledTimes(1)
})
it('retries a failed initial watch when the same host answers again', async () => {
const warning = vi.spyOn(console, 'warn').mockImplementation(() => {})
subscriptionState.subscribeRuntimeFileChanges
.mockRejectedValueOnce(new Error('connection lost'))
.mockResolvedValueOnce(vi.fn())
subscriptionState.snapshot = { targets: [runtimeTarget()], targetsKey: 'recovering-watch' }
try {
await act(async () => root.render(createElement(WatchProbe)))
await act(async () => subscriptionState.contacts.get('runtime-1')?.())
expect(subscriptionState.subscribeRuntimeFileChanges).toHaveBeenCalledTimes(2)
expect(subscriptionState.handleFsChanged).toHaveBeenCalledWith(
{
worktreePath: '/runtime/repo',
events: [{ kind: 'overflow', absolutePath: '/runtime/repo' }],
connectionId: 'nested-ssh',
resync: true
},
'runtime-1',
runtimeTarget()
)
} finally {
warning.mockRestore()
}
})
it('replaces a lost watch and stops contact recovery when the editor unmounts', async () => {
const firstStop = vi.fn()
const secondStop = vi.fn()
subscriptionState.subscribeRuntimeFileChanges
.mockResolvedValueOnce(firstStop)
.mockResolvedValueOnce(secondStop)
subscriptionState.snapshot = { targets: [runtimeTarget()], targetsKey: 'live-watch' }
await act(async () => root.render(createElement(WatchProbe)))
await act(async () => subscriptionState.contacts.get('runtime-1')?.())
expect(firstStop).toHaveBeenCalledOnce()
expect(subscriptionState.subscribeRuntimeFileChanges).toHaveBeenCalledTimes(2)
await act(async () => root.unmount())
expect(secondStop).toHaveBeenCalledOnce()
expect(subscriptionState.unsubscribeContact).toHaveBeenCalledOnce()
subscriptionState.contacts.get('runtime-1')?.()
expect(subscriptionState.subscribeRuntimeFileChanges).toHaveBeenCalledTimes(2)
})
})
@@ -1,590 +0,0 @@
import { beforeEach, describe, expect, it, vi, afterEach } from 'vitest'
import type * as EditorAutosaveModule from '@/components/editor/autosave/editor-autosave'
import type { FsChangedPayload } from '../../../shared/file-watch/filesystem-change-event'
vi.mock('@/store', () => ({
useAppStore: {
getState: vi.fn()
}
}))
// Stub autosave’s window-event exports so the handler runs in headless Node.
vi.mock('@/components/editor/autosave/editor-autosave', async (importOriginal) => {
const actual = await importOriginal<typeof EditorAutosaveModule>()
return {
...actual,
notifyEditorExternalFileChange: vi.fn(),
getOpenFilesForExternalFileChange: vi.fn(() => [])
}
})
import {
buildEditorExternalWatchEventHandler as createExternalWatchEventHandler,
EXTERNAL_MUTATION_DEBOUNCE_MS
} from './editor-external-watch-event-reconciliation'
import { useAppStore } from '@/store'
import { createTestStore } from '@/store/testing/app-store-test-fixture'
import {
getOpenFilesForExternalFileChange,
notifyEditorExternalFileChange
} from '@/components/editor/autosave/editor-autosave'
import {
__clearSelfWriteRegistryForTests,
recordSelfWrite
} from '@/components/editor/external-changes/editor-self-write-registry'
import { __clearEditorPathMovesForTests } from '@/components/editor/editor-path-move-inflight'
describe('createExternalWatchEventHandler tombstone coalescing', () => {
const setExternalMutation = vi.fn()
const findTarget = (
worktreePath: string,
runtimeEnvironmentId: string | null = null
):
| {
worktreeId: string
worktreePath: string
connectionId: string | undefined
runtimeEnvironmentId: string | null
}
| undefined =>
worktreePath === '/repo'
? {
worktreeId: 'wt-1',
worktreePath: '/repo',
connectionId: undefined,
runtimeEnvironmentId
}
: undefined
const fileNotes = {
id: 'file-notes',
worktreeId: 'wt-1',
worktreePath: '/repo',
filePath: '/repo/notes.md',
relativePath: 'notes.md',
language: 'markdown',
mode: 'edit' as const,
isDirty: false
}
beforeEach(() => {
vi.clearAllMocks()
vi.useFakeTimers()
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [fileNotes],
setExternalMutation
})
})
afterEach(() => {
vi.useRealTimers()
vi.unstubAllGlobals()
__clearSelfWriteRegistryForTests()
__clearEditorPathMovesForTests()
})
function payload(events: FsChangedPayload['events']): FsChangedPayload {
return { worktreePath: '/repo', events }
}
it('does not flash deleted when a create at the same path arrives in the next payload', () => {
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'delete', absolutePath: '/repo/notes.md' }]))
vi.advanceTimersByTime(10)
expect(setExternalMutation).not.toHaveBeenCalledWith('file-notes', 'deleted')
handleFsChanged(payload([{ kind: 'create', absolutePath: '/repo/notes.md' }]))
vi.advanceTimersByTime(500)
expect(setExternalMutation).not.toHaveBeenCalledWith('file-notes', 'deleted')
dispose()
})
it('still sets deleted after the debounce window for a naked delete', () => {
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'delete', absolutePath: '/repo/notes.md' }]))
expect(setExternalMutation).not.toHaveBeenCalled()
vi.advanceTimersByTime(EXTERNAL_MUTATION_DEBOUNCE_MS)
expect(setExternalMutation).toHaveBeenCalledWith('file-notes', 'deleted')
dispose()
})
it('resolves batched delete+create in a single payload synchronously as renamed', () => {
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(
payload([
{ kind: 'delete', absolutePath: '/repo/notes.md' },
{ kind: 'create', absolutePath: '/repo/subdir/notes.md' }
])
)
// Same-payload delete/create already proves recreation; reconcile without deferral.
expect(setExternalMutation).toHaveBeenCalledWith('file-notes', 'renamed')
dispose()
})
it('reloads Windows editor tabs when watcher event casing differs', () => {
const file = {
id: 'file-win',
worktreeId: 'wt-win',
worktreePath: 'C:\\Repo',
filePath: 'C:\\Repo\\notes.md',
relativePath: 'notes.md',
language: 'markdown',
mode: 'edit' as const,
isDirty: false,
runtimeEnvironmentId: 'env-1'
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [file],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([file] as never)
const { handleFsChanged, dispose } = createExternalWatchEventHandler(() => ({
worktreeId: 'wt-win',
worktreePath: 'C:\\Repo',
connectionId: undefined,
runtimeEnvironmentId: 'env-1'
}))
handleFsChanged({
worktreePath: 'c:\\repo',
events: [{ kind: 'update', absolutePath: 'c:\\repo\\notes.md', isDirectory: false }]
})
vi.advanceTimersByTime(100)
expect(notifyEditorExternalFileChange).toHaveBeenCalledWith({
worktreeId: 'wt-win',
worktreePath: 'C:\\Repo',
relativePath: 'notes.md',
runtimeEnvironmentId: 'env-1'
})
dispose()
})
it('reloads UNC editor tabs without collapsing the share root', () => {
const file = {
id: 'file-unc',
worktreeId: 'wt-unc',
worktreePath: '//Server/Share/Repo',
filePath: '//Server/Share/Repo/notes.md',
relativePath: 'notes.md',
language: 'markdown',
mode: 'edit' as const,
isDirty: false,
runtimeEnvironmentId: 'env-1'
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [file],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([file] as never)
const { handleFsChanged, dispose } = createExternalWatchEventHandler(() => ({
worktreeId: 'wt-unc',
worktreePath: '//Server/Share/Repo',
connectionId: undefined,
runtimeEnvironmentId: 'env-1'
}))
handleFsChanged({
worktreePath: '//server/share/repo',
events: [{ kind: 'update', absolutePath: '//server/share/repo/notes.md', isDirectory: false }]
})
vi.advanceTimersByTime(100)
expect(notifyEditorExternalFileChange).toHaveBeenCalledWith({
worktreeId: 'wt-unc',
worktreePath: '//Server/Share/Repo',
relativePath: 'notes.md',
runtimeEnvironmentId: 'env-1'
})
dispose()
})
it('does not drop external edits that arrive inside the self-write TTL', async () => {
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [fileNotes],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([fileNotes] as never)
const readFile = vi.fn().mockResolvedValue({ content: 'agent edit', isBinary: false })
vi.stubGlobal('window', { api: { fs: { readFile } } })
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
recordSelfWrite('/repo/notes.md', 'orca save')
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]))
await vi.advanceTimersByTimeAsync(100)
expect(notifyEditorExternalFileChange).toHaveBeenCalledWith({
worktreeId: 'wt-1',
worktreePath: '/repo',
relativePath: 'notes.md',
runtimeEnvironmentId: null
})
dispose()
})
it('still suppresses the watcher echo from Orca self-writes', async () => {
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [fileNotes],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([fileNotes] as never)
const readFile = vi.fn().mockResolvedValue({ content: 'orca save', isBinary: false })
vi.stubGlobal('window', { api: { fs: { readFile } } })
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
recordSelfWrite('/repo/notes.md', 'orca save')
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]))
await vi.advanceTimersByTimeAsync(100)
expect(notifyEditorExternalFileChange).not.toHaveBeenCalled()
dispose()
})
it('does not let a local self-write stamp suppress a runtime owner update', async () => {
const runtimeFile = {
...fileNotes,
runtimeEnvironmentId: 'env-1'
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [runtimeFile],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([runtimeFile] as never)
const readFile = vi.fn().mockResolvedValue({ content: 'local save', isBinary: false })
vi.stubGlobal('window', { api: { fs: { readFile } } })
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
recordSelfWrite('/repo/notes.md', 'local save', null)
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]), 'env-1')
await vi.advanceTimersByTimeAsync(100)
expect(notifyEditorExternalFileChange).toHaveBeenCalledWith({
worktreeId: 'wt-1',
worktreePath: '/repo',
relativePath: 'notes.md',
runtimeEnvironmentId: 'env-1'
})
dispose()
})
it('routes local and runtime watch events to the matching editor owner', () => {
const localFile = fileNotes
const runtimeFile = {
...fileNotes,
id: 'runtime-notes',
runtimeEnvironmentId: 'env-1'
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [localFile, runtimeFile],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockImplementation((_files, notification) =>
notification.runtimeEnvironmentId === 'env-1'
? ([runtimeFile] as never)
: ([localFile] as never)
)
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]), null)
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]), 'env-1')
vi.advanceTimersByTime(100)
expect(notifyEditorExternalFileChange).toHaveBeenCalledWith({
worktreeId: 'wt-1',
worktreePath: '/repo',
relativePath: 'notes.md',
runtimeEnvironmentId: null
})
expect(notifyEditorExternalFileChange).toHaveBeenCalledWith({
worktreeId: 'wt-1',
worktreePath: '/repo',
relativePath: 'notes.md',
runtimeEnvironmentId: 'env-1'
})
dispose()
})
it('reloads a combined Changes tab even though no single-file tab matches', () => {
const combinedDiffTab = {
id: 'wt-1::all-diffs::uncommitted',
worktreeId: 'wt-1',
worktreePath: '/repo',
filePath: '/repo',
relativePath: 'Changes',
language: 'markdown',
mode: 'diff' as const,
diffSource: 'combined-uncommitted' as const,
isDirty: false
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [combinedDiffTab],
setExternalMutation
})
// Why: combined tabs match no single path, so the per-path lookup stays empty.
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([])
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/src/foo.ts' }]))
vi.advanceTimersByTime(100)
expect(notifyEditorExternalFileChange).toHaveBeenCalledWith({
worktreeId: 'wt-1',
worktreePath: '/repo',
relativePath: 'src/foo.ts',
runtimeEnvironmentId: null
})
dispose()
})
it('reloads a combined Changes tab when the matching single-file tab is dirty', () => {
const dirtyFile = {
...fileNotes,
isDirty: true
}
const combinedDiffTab = {
id: 'wt-1::all-diffs::uncommitted',
worktreeId: 'wt-1',
worktreePath: '/repo',
filePath: '/repo',
relativePath: 'Changes',
language: 'markdown',
mode: 'diff' as const,
diffSource: 'combined-uncommitted' as const,
isDirty: false
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [dirtyFile, combinedDiffTab],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([dirtyFile] as never)
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]))
vi.advanceTimersByTime(100)
expect(notifyEditorExternalFileChange).toHaveBeenCalledWith({
worktreeId: 'wt-1',
worktreePath: '/repo',
relativePath: 'notes.md',
runtimeEnvironmentId: null
})
dispose()
})
it('marks a dirty edit tab changed-on-disk instead of reloading it', () => {
const dirtyFile = {
...fileNotes,
isDirty: true
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [dirtyFile],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([dirtyFile] as never)
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]))
vi.advanceTimersByTime(EXTERNAL_MUTATION_DEBOUNCE_MS)
expect(setExternalMutation).toHaveBeenCalledWith('file-notes', 'changed')
expect(notifyEditorExternalFileChange).not.toHaveBeenCalled()
dispose()
})
it('still reloads a clean sibling diff tab when the edit tab for the path is dirty', () => {
const dirtyFile = {
...fileNotes,
isDirty: true
}
const cleanDiffTab = {
id: 'diff-notes',
worktreeId: 'wt-1',
worktreePath: '/repo',
filePath: '/repo/notes.md',
relativePath: 'notes.md',
language: 'markdown',
mode: 'diff' as const,
diffSource: 'unstaged' as const,
isDirty: false
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [dirtyFile, cleanDiffTab],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([dirtyFile, cleanDiffTab] as never)
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]))
vi.advanceTimersByTime(EXTERNAL_MUTATION_DEBOUNCE_MS)
expect(setExternalMutation).toHaveBeenCalledWith('file-notes', 'changed')
expect(notifyEditorExternalFileChange).toHaveBeenCalledWith({
worktreeId: 'wt-1',
worktreePath: '/repo',
relativePath: 'notes.md',
runtimeEnvironmentId: null
})
dispose()
})
it('does not mark a dirty tab for the echo of Orca’s own save', async () => {
const dirtyFile = {
...fileNotes,
isDirty: true
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [dirtyFile],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([dirtyFile] as never)
const readFile = vi.fn().mockResolvedValue({ content: 'orca save', isBinary: false })
vi.stubGlobal('window', { api: { fs: { readFile } } })
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
recordSelfWrite('/repo/notes.md', 'orca save')
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]))
await vi.advanceTimersByTimeAsync(100)
expect(setExternalMutation).not.toHaveBeenCalledWith('file-notes', 'changed')
dispose()
})
it('marks a dirty tab when a genuine external write lands inside the self-write TTL', async () => {
const dirtyFile = {
...fileNotes,
isDirty: true
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [dirtyFile],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([dirtyFile] as never)
const readFile = vi.fn().mockResolvedValue({ content: 'agent content', isBinary: false })
vi.stubGlobal('window', { api: { fs: { readFile } } })
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
recordSelfWrite('/repo/notes.md', 'orca save')
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]))
await vi.advanceTimersByTimeAsync(100)
expect(setExternalMutation).toHaveBeenCalledWith('file-notes', 'changed')
dispose()
})
it('marks a lone dirty unstaged-diff tab changed-on-disk', () => {
const dirtyDiffTab = {
id: 'diff-notes',
worktreeId: 'wt-1',
worktreePath: '/repo',
filePath: '/repo/notes.md',
relativePath: 'notes.md',
language: 'markdown',
mode: 'diff' as const,
diffSource: 'unstaged' as const,
isDirty: true
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [dirtyDiffTab],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([dirtyDiffTab] as never)
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]))
vi.advanceTimersByTime(EXTERNAL_MUTATION_DEBOUNCE_MS)
expect(setExternalMutation).toHaveBeenCalledWith('diff-notes', 'changed')
expect(notifyEditorExternalFileChange).not.toHaveBeenCalled()
dispose()
})
it('does not let an update event clear a changed-on-disk mark while the tab stays dirty', () => {
const dirtyChangedFile = {
...fileNotes,
isDirty: true,
externalMutation: 'changed' as const
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [dirtyChangedFile],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([dirtyChangedFile] as never)
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/notes.md' }]))
vi.advanceTimersByTime(EXTERNAL_MUTATION_DEBOUNCE_MS)
expect(setExternalMutation).not.toHaveBeenCalledWith('file-notes', null)
dispose()
})
it('does not reload a branch-compare combined diff for working-tree changes', () => {
const branchDiffTab = {
id: 'wt-1::all-diffs::branch',
worktreeId: 'wt-1',
worktreePath: '/repo',
filePath: '/repo',
relativePath: 'Branch Changes',
language: 'markdown',
mode: 'diff' as const,
diffSource: 'combined-branch' as const,
isDirty: false
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [branchDiffTab],
setExternalMutation
})
vi.mocked(getOpenFilesForExternalFileChange).mockReturnValue([])
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'update', absolutePath: '/repo/src/foo.ts' }]))
vi.advanceTimersByTime(100)
expect(notifyEditorExternalFileChange).not.toHaveBeenCalled()
dispose()
})
it('tombstones only the matching owner for same-path delete events', () => {
const localFile = fileNotes
const runtimeFile = {
...fileNotes,
id: 'runtime-notes',
runtimeEnvironmentId: 'env-1'
}
vi.mocked(useAppStore.getState).mockReturnValue({
...createTestStore().getState(),
openFiles: [localFile, runtimeFile],
setExternalMutation
})
const { handleFsChanged, dispose } = createExternalWatchEventHandler(findTarget)
handleFsChanged(payload([{ kind: 'delete', absolutePath: '/repo/notes.md' }]), 'env-1')
vi.advanceTimersByTime(EXTERNAL_MUTATION_DEBOUNCE_MS)
expect(setExternalMutation).toHaveBeenCalledWith('runtime-notes', 'deleted')
expect(setExternalMutation).not.toHaveBeenCalledWith('file-notes', 'deleted')
dispose()
})
})
@@ -1,480 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
buildStoreState,
expectWorktreeRouting,
FUTURE_LEAF_ID,
ORPHAN_LEAF_ID,
TAB_1_LEAF_ID,
STALE_PANE_KEY,
ORPHAN_PANE_KEY,
TAB_1_PANE_KEY,
type AgentStatusSetData,
type StoreLike,
type StoreSubscribeListener
} from './ipc-events-agent-status-store-test-fixtures'
import {
buildWindowApi,
stubReactSyncEffect,
stubAuxiliaryModules
} from './ipc-events-agent-status-window-test-fixtures'
describe('useIpcEvents agent status snapshot integration', () => {
beforeEach(() => {
vi.resetModules()
vi.unstubAllGlobals()
})
it('pulls the snapshot once workspace session is ready even before settings load', async () => {
const setAgentStatus = vi.fn()
const getSnapshot = vi.fn(() => Promise.resolve([]))
const subscribeListenerRef: { current: StoreSubscribeListener | null } = { current: null }
const storeState: StoreLike = buildStoreState({
setAgentStatus,
tabsByWorktree: {},
workspaceSessionReady: true,
settings: null
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn((listener: StoreSubscribeListener) => {
subscribeListenerRef.current = listener
return () => {
subscribeListenerRef.current = null
}
}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
getSnapshot,
onSet: () => () => {}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
expect(getSnapshot).toHaveBeenCalledTimes(1)
expect(setAgentStatus).not.toHaveBeenCalled()
})
it('silently discards snapshot entries whose tabs are still unknown', async () => {
const setAgentStatus = vi.fn()
const getSnapshot = vi.fn(() =>
Promise.resolve([
{
paneKey: ORPHAN_PANE_KEY,
state: 'done' as const,
prompt: 'p',
agentType: 'claude',
receivedAt: 1_700_000_000_000,
stateStartedAt: 1_699_999_999_000
}
])
)
const storeState: StoreLike = buildStoreState({
setAgentStatus,
tabsByWorktree: {
'wt-1': [{ id: 'tab-other', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'Other' }]
},
terminalLayoutsByTabId: {
'tab-orphan': {
root: { type: 'leaf', leafId: ORPHAN_LEAF_ID },
activeLeafId: ORPHAN_LEAF_ID,
expandedLeafId: null
}
},
workspaceSessionReady: true
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
getSnapshot,
onSet: () => () => {}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
await Promise.resolve()
expect(setAgentStatus).not.toHaveBeenCalled()
})
it('silently discards stale worktree-attributed snapshots for unknown panes', async () => {
const setAgentStatus = vi.fn()
const getSnapshot = vi.fn(() =>
Promise.resolve([
{
paneKey: ORPHAN_PANE_KEY,
state: 'done' as const,
prompt: 'old copilot turn',
agentType: 'copilot',
worktreeId: 'wt-1',
receivedAt: 1_700_000_000_000,
stateStartedAt: 1_699_999_999_000
}
])
)
const storeState: StoreLike = buildStoreState({
setAgentStatus,
repos: [{ id: 'repo-1', connectionId: null }],
worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] },
tabsByWorktree: {
'wt-1': [{ id: 'tab-future', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'Copilot' }]
},
terminalLayoutsByTabId: {
'tab-future': {
root: { type: 'leaf', leafId: FUTURE_LEAF_ID },
activeLeafId: FUTURE_LEAF_ID,
expandedLeafId: null
}
},
workspaceSessionReady: true
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
getSnapshot,
onSet: () => () => {}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
await Promise.resolve()
expect(setAgentStatus).not.toHaveBeenCalled()
})
it('applies worktree-attributed child snapshots when runtime identity is present', async () => {
const setAgentStatus = vi.fn()
const getSnapshot = vi.fn(() =>
Promise.resolve([
{
paneKey: ORPHAN_PANE_KEY,
state: 'working' as const,
prompt: 'child task',
agentType: 'codex',
worktreeId: 'wt-1',
terminalHandle: 'term-child',
orchestration: {
taskId: 'task-child',
dispatchId: 'dispatch-child',
parentTerminalHandle: 'term-parent'
},
receivedAt: 1_700_000_000_000,
stateStartedAt: 1_699_999_999_000
}
])
)
const storeState: StoreLike = buildStoreState({
setAgentStatus,
repos: [{ id: 'repo-1', connectionId: null }],
worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] },
tabsByWorktree: {
'wt-1': [{ id: 'tab-future', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'Codex' }]
},
terminalLayoutsByTabId: {
'tab-future': {
root: { type: 'leaf', leafId: FUTURE_LEAF_ID },
activeLeafId: FUTURE_LEAF_ID,
expandedLeafId: null
}
},
workspaceSessionReady: true
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
getSnapshot,
onSet: () => () => {}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
await Promise.resolve()
expect(setAgentStatus).toHaveBeenCalledTimes(1)
expect(setAgentStatus).toHaveBeenCalledWith(
ORPHAN_PANE_KEY,
expect.objectContaining({
state: 'working',
prompt: 'child task',
agentType: 'codex',
orchestration: expect.objectContaining({ taskId: 'task-child' })
}),
undefined,
{ updatedAt: 1_700_000_000_000, stateStartedAt: 1_699_999_999_000 },
expect.objectContaining({ worktreeId: 'wt-1', terminalHandle: 'term-child' }),
undefined
)
})
it('silently discards valid paneKeys whose leaf is not in the current layout', async () => {
const setAgentStatus = vi.fn()
const getSnapshot = vi.fn(() =>
Promise.resolve([
{
paneKey: STALE_PANE_KEY,
state: 'done' as const,
prompt: 'p',
agentType: 'claude',
receivedAt: 1_700_000_000_000,
stateStartedAt: 1_699_999_999_000
}
])
)
const storeState: StoreLike = buildStoreState({
setAgentStatus,
tabsByWorktree: {
'wt-1': [{ id: 'tab-future', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'Future Tab' }]
},
terminalLayoutsByTabId: {
'tab-future': {
root: { type: 'leaf', leafId: FUTURE_LEAF_ID },
activeLeafId: FUTURE_LEAF_ID,
expandedLeafId: null
}
},
workspaceSessionReady: true
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
getSnapshot,
onSet: () => () => {}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
await Promise.resolve()
expect(setAgentStatus).not.toHaveBeenCalled()
})
it('forwards events whose connectionId matches the live repo connection', async () => {
const setAgentStatus = vi.fn()
const onSetListenerRef: { current: ((data: AgentStatusSetData) => void) | null } = {
current: null
}
const storeState: StoreLike = buildStoreState({
setAgentStatus,
workspaceSessionReady: true,
repos: [{ id: 'repo-1', connectionId: 'conn-1' }],
worktreesByRepo: {
'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }]
},
tabsByWorktree: {
'wt-1': [{ id: 'tab-1', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'Terminal 1' }]
},
terminalLayoutsByTabId: {
'tab-1': {
root: { type: 'leaf', leafId: TAB_1_LEAF_ID },
activeLeafId: TAB_1_LEAF_ID,
expandedLeafId: null
}
}
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: { subscribe: vi.fn(() => () => {}), getState: () => storeState }
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: (cb) => {
onSetListenerRef.current = cb
return () => {}
}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
onSetListenerRef.current?.({
paneKey: TAB_1_PANE_KEY,
connectionId: 'conn-1',
state: 'working',
receivedAt: 1_700_000_000_100,
stateStartedAt: 1_699_999_999_100
})
expect(setAgentStatus).toHaveBeenCalledWith(
TAB_1_PANE_KEY,
expect.objectContaining({ state: 'working' }),
'Terminal 1',
{ updatedAt: 1_700_000_000_100, stateStartedAt: 1_699_999_999_100 },
expectWorktreeRouting('wt-1'),
undefined
)
})
it('drops events whose connectionId no longer matches the live local repo', async () => {
const setAgentStatus = vi.fn()
const onSetListenerRef: { current: ((data: AgentStatusSetData) => void) | null } = {
current: null
}
const storeState: StoreLike = buildStoreState({
setAgentStatus,
workspaceSessionReady: true,
repos: [{ id: 'repo-1', connectionId: null }],
worktreesByRepo: {
'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }]
},
tabsByWorktree: {
'wt-1': [{ id: 'tab-1', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'Terminal 1' }]
},
terminalLayoutsByTabId: {
'tab-1': {
root: { type: 'leaf', leafId: TAB_1_LEAF_ID },
activeLeafId: TAB_1_LEAF_ID,
expandedLeafId: null
}
}
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: { subscribe: vi.fn(() => () => {}), getState: () => storeState }
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: (cb) => {
onSetListenerRef.current = cb
return () => {}
}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
onSetListenerRef.current?.({
paneKey: TAB_1_PANE_KEY,
connectionId: 'conn-stale',
state: 'working',
receivedAt: 1_700_000_000_100,
stateStartedAt: 1_699_999_999_100
})
expect(setAgentStatus).not.toHaveBeenCalled()
})
it('accepts events without a stamped connectionId for preload compatibility', async () => {
const setAgentStatus = vi.fn()
const onSetListenerRef: { current: ((data: AgentStatusSetData) => void) | null } = {
current: null
}
const storeState: StoreLike = buildStoreState({
setAgentStatus,
workspaceSessionReady: true,
repos: [{ id: 'repo-1', connectionId: 'conn-1' }],
worktreesByRepo: {
'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }]
},
tabsByWorktree: {
'wt-1': [{ id: 'tab-1', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'Terminal 1' }]
},
terminalLayoutsByTabId: {
'tab-1': {
root: { type: 'leaf', leafId: TAB_1_LEAF_ID },
activeLeafId: TAB_1_LEAF_ID,
expandedLeafId: null
}
}
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: { subscribe: vi.fn(() => () => {}), getState: () => storeState }
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: (cb) => {
onSetListenerRef.current = cb
return () => {}
}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
onSetListenerRef.current?.({
paneKey: TAB_1_PANE_KEY,
state: 'working',
receivedAt: 1_700_000_000_100,
stateStartedAt: 1_699_999_999_100
})
expect(setAgentStatus).toHaveBeenCalledTimes(1)
})
})
@@ -1,444 +0,0 @@
import type * as ReactModule from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { AgentStatusClearIpcPayload } from '../../../shared/agent-status/contracts/agent-status-ipc-payload'
import {
buildStoreState,
expectWorktreeRouting,
FUTURE_LEAF_ID,
FUTURE_PANE_KEY,
type AgentStatusSetData,
type StoreLike
} from './ipc-events-agent-status-store-test-fixtures'
import {
buildWindowApi,
stubReactSyncEffect,
stubAuxiliaryModules
} from './ipc-events-agent-status-window-test-fixtures'
describe('useIpcEvents agent status snapshot integration', () => {
beforeEach(() => {
vi.resetModules()
vi.unstubAllGlobals()
})
it('does not send an out-of-order hook event to completion lifecycle tracking', async () => {
const setAgentStatus = vi.fn()
const updateTabTitle = vi.fn()
const observeAgentHookCompletionForNotification = vi.fn()
const onSetListenerRef: { current: ((data: AgentStatusSetData) => void) | null } = {
current: null
}
const storeState: StoreLike = buildStoreState({
setAgentStatus,
updateTabTitle,
workspaceSessionReady: true,
settings: { terminalFontSize: 13, notifications: { enabled: true, agentTaskComplete: true } },
tabsByWorktree: {
'wt-1': [{ id: 'tab-future', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'Claude' }]
},
agentStatusByPaneKey: {
[FUTURE_PANE_KEY]: {
state: 'working',
prompt: 'newer turn',
agentType: 'claude',
updatedAt: 1_700_000_000_500,
stateStartedAt: 1_700_000_000_400
}
}
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
vi.doMock('./agent-hook-completion-notifications', () => ({
observeAgentHookCompletionForNotification,
resetAgentHookCompletionNotificationCoordinators: vi.fn(),
syncAgentHookCompletionNotificationSettings: vi.fn()
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: (cb) => {
onSetListenerRef.current = cb
return () => {}
}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
if (typeof onSetListenerRef.current !== 'function') {
throw new Error('Expected agentStatus.onSet listener to be registered')
}
onSetListenerRef.current({
paneKey: FUTURE_PANE_KEY,
tabId: 'tab-future',
worktreeId: 'wt-1',
state: 'done',
prompt: 'older turn',
agentType: 'claude',
receivedAt: 1_700_000_000_300,
stateStartedAt: 1_700_000_000_200
})
expect(setAgentStatus).not.toHaveBeenCalled()
expect(updateTabTitle).not.toHaveBeenCalled()
expect(observeAgentHookCompletionForNotification).not.toHaveBeenCalled()
})
it('keeps missing-tab runtime attribution for tabs that were never explicitly closed', async () => {
const setAgentStatus = vi.fn()
const onSetListenerRef: { current: ((data: AgentStatusSetData) => void) | null } = {
current: null
}
const storeState: StoreLike = buildStoreState({
setAgentStatus,
workspaceSessionReady: true,
recentlyClosedAgentStatusTabIds: {},
settings: { terminalFontSize: 13, notifications: { enabled: false } },
repos: [{ id: 'repo-1', connectionId: null }],
worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] },
tabsByWorktree: { 'wt-1': [] },
terminalLayoutsByTabId: {}
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: (cb) => {
onSetListenerRef.current = cb
return () => {}
}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
if (typeof onSetListenerRef.current !== 'function') {
throw new Error('Expected agentStatus.onSet listener to be registered')
}
onSetListenerRef.current({
paneKey: FUTURE_PANE_KEY,
state: 'working',
prompt: 'runtime child',
agentType: 'codex',
worktreeId: 'wt-1',
receivedAt: 1_700_000_000_200,
stateStartedAt: 1_700_000_000_000,
orchestration: {
parentPaneKey: 'parent-tab:11111111-1111-4111-8111-111111111111'
}
})
expect(setAgentStatus).toHaveBeenCalledTimes(1)
expect(setAgentStatus).toHaveBeenCalledWith(
FUTURE_PANE_KEY,
expect.objectContaining({ state: 'working', prompt: 'runtime child', agentType: 'codex' }),
undefined,
{ updatedAt: 1_700_000_000_200, stateStartedAt: 1_700_000_000_000 },
expectWorktreeRouting('wt-1'),
undefined
)
})
it('clears a worktree-attributed live row when main reports pane teardown', async () => {
const setAgentStatus = vi.fn()
const removeAgentStatus = vi.fn()
const onSetListenerRef: { current: ((data: AgentStatusSetData) => void) | null } = {
current: null
}
const onClearListenerRef: {
current: ((data: AgentStatusClearIpcPayload) => void) | null
} = {
current: null
}
const storeState: StoreLike = buildStoreState({
setAgentStatus,
removeAgentStatus,
workspaceSessionReady: true,
settings: { terminalFontSize: 13, notifications: { enabled: false } },
repos: [{ id: 'repo-1', connectionId: null }],
worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] },
tabsByWorktree: { 'wt-1': [] },
terminalLayoutsByTabId: {},
agentStatusByPaneKey: {
[FUTURE_PANE_KEY]: {
state: 'working',
prompt: 'hidden worker',
agentType: 'codex',
updatedAt: 1_700_000_000_200,
stateStartedAt: 1_700_000_000_000,
paneKey: FUTURE_PANE_KEY,
worktreeId: 'wt-1',
stateHistory: []
}
}
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: (cb) => {
onSetListenerRef.current = cb
return () => {}
},
onClear: (cb) => {
onClearListenerRef.current = cb
return () => {}
}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
if (typeof onSetListenerRef.current !== 'function') {
throw new Error('Expected agentStatus.onSet listener to be registered')
}
if (typeof onClearListenerRef.current !== 'function') {
throw new Error('Expected agentStatus.onClear listener to be registered')
}
onSetListenerRef.current({
paneKey: FUTURE_PANE_KEY,
state: 'working',
prompt: 'hidden worker',
agentType: 'codex',
worktreeId: 'wt-1',
receivedAt: 1_700_000_000_200,
stateStartedAt: 1_700_000_000_000,
orchestration: {
parentPaneKey: 'parent-tab:11111111-1111-4111-8111-111111111111'
}
})
expect(setAgentStatus).toHaveBeenCalledTimes(1)
expect(setAgentStatus).toHaveBeenCalledWith(
FUTURE_PANE_KEY,
expect.objectContaining({ state: 'working', prompt: 'hidden worker', agentType: 'codex' }),
undefined,
{ updatedAt: 1_700_000_000_200, stateStartedAt: 1_700_000_000_000 },
expectWorktreeRouting('wt-1'),
undefined
)
onClearListenerRef.current({ paneKey: FUTURE_PANE_KEY })
expect(removeAgentStatus).toHaveBeenCalledTimes(1)
expect(removeAgentStatus).toHaveBeenCalledWith(FUTURE_PANE_KEY)
})
it('blocks cleared snapshots across remount and accepts newer reconnect replay', async () => {
let resolveOldSnapshot!: (entries: AgentStatusSetData[]) => void
let resolveCurrentSnapshot!: (entries: AgentStatusSetData[]) => void
const oldSnapshot = new Promise<AgentStatusSetData[]>((resolve) => {
resolveOldSnapshot = resolve
})
const currentSnapshot = new Promise<AgentStatusSetData[]>((resolve) => {
resolveCurrentSnapshot = resolve
})
const effectCleanups: (() => void)[] = []
const setAgentStatus = vi.fn()
const clearTransientAgentStatuses = vi.fn()
const onSetListenerRef: { current: ((data: AgentStatusSetData) => void) | null } = {
current: null
}
const onClearListenerRef: {
current: ((data: AgentStatusClearIpcPayload) => void) | null
} = { current: null }
const storeState: StoreLike = buildStoreState({
setAgentStatus,
clearTransientAgentStatuses,
workspaceSessionReady: true,
settings: { terminalFontSize: 13, notifications: { enabled: false } },
repos: [{ id: 'repo-1', connectionId: 'ssh-a' }],
worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] },
tabsByWorktree: {
'wt-1': [{ id: 'tab-future', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'Remote agent' }]
},
terminalLayoutsByTabId: {
'tab-future': { root: { type: 'leaf', leafId: FUTURE_LEAF_ID } }
}
})
vi.doMock('react', async () => {
const actual = await vi.importActual<typeof ReactModule>('react')
return {
...actual,
useEffect: (effect: () => void | (() => void)) => {
const cleanup = effect()
effectCleanups.push(typeof cleanup === 'function' ? cleanup : () => {})
}
}
})
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: (callback) => {
onSetListenerRef.current = callback
return () => {}
},
onClear: (callback) => {
onClearListenerRef.current = callback
return () => {}
},
getSnapshot: vi.fn().mockReturnValueOnce(oldSnapshot).mockReturnValueOnce(currentSnapshot)
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
effectCleanups[0]?.()
useIpcEvents()
await Promise.resolve()
if (!onSetListenerRef.current || !onClearListenerRef.current) {
throw new Error('Expected agent status listeners to be registered')
}
expect(() =>
onClearListenerRef.current?.(null as unknown as AgentStatusClearIpcPayload)
).not.toThrow()
expect(clearTransientAgentStatuses).not.toHaveBeenCalled()
onClearListenerRef.current({
transient: true,
connectionId: 'ssh-a',
clearedAt: 100
})
const staleEntry: AgentStatusSetData = {
paneKey: FUTURE_PANE_KEY,
state: 'working',
prompt: 'stale snapshot',
agentType: 'codex',
worktreeId: 'wt-1',
connectionId: 'ssh-a',
receivedAt: 100,
stateStartedAt: 90
}
resolveOldSnapshot([staleEntry])
resolveCurrentSnapshot([staleEntry])
await Promise.resolve()
await Promise.resolve()
expect(clearTransientAgentStatuses).toHaveBeenCalledWith('ssh-a', 100)
expect(setAgentStatus).not.toHaveBeenCalled()
onSetListenerRef.current({
paneKey: FUTURE_PANE_KEY,
state: 'working',
prompt: 'replayed',
agentType: 'codex',
worktreeId: 'wt-1',
connectionId: 'ssh-a',
receivedAt: 101,
stateStartedAt: 101
})
expect(setAgentStatus).toHaveBeenCalledOnce()
expect(setAgentStatus).toHaveBeenCalledWith(
FUTURE_PANE_KEY,
expect.objectContaining({ prompt: 'replayed' }),
'Remote agent',
{ updatedAt: 101, stateStartedAt: 101 },
expect.objectContaining({ worktreeId: 'wt-1', connectionId: 'ssh-a' }),
undefined
)
})
it('keeps a completed worktree-attributed row when main reports pane teardown', async () => {
const removeAgentStatus = vi.fn()
const onClearListenerRef: {
current: ((data: AgentStatusClearIpcPayload) => void) | null
} = {
current: null
}
const storeState: StoreLike = buildStoreState({
removeAgentStatus,
workspaceSessionReady: true,
agentStatusByPaneKey: {
[FUTURE_PANE_KEY]: {
state: 'done',
prompt: 'hidden worker',
agentType: 'codex',
updatedAt: 1_700_000_000_200,
stateStartedAt: 1_700_000_000_000,
paneKey: FUTURE_PANE_KEY,
worktreeId: 'wt-1',
stateHistory: []
}
}
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: () => () => {},
onClear: (cb) => {
onClearListenerRef.current = cb
return () => {}
}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
if (typeof onClearListenerRef.current !== 'function') {
throw new Error('Expected agentStatus.onClear listener to be registered')
}
onClearListenerRef.current({ paneKey: FUTURE_PANE_KEY })
expect(removeAgentStatus).not.toHaveBeenCalled()
})
})
@@ -1,254 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { AgentStatusUpdate } from '../store/slices/agent-status/agent-status-contract'
import {
buildStoreState,
FUTURE_LEAF_ID,
FUTURE_PANE_KEY,
type AgentStatusSetData,
type StoreLike,
type StoreSubscribeListener
} from './ipc-events-agent-status-store-test-fixtures'
import {
buildWindowApi,
stubReactSyncEffect,
stubAuxiliaryModules
} from './ipc-events-agent-status-window-test-fixtures'
describe('useIpcEvents agent status snapshot integration', () => {
beforeEach(() => {
vi.resetModules()
vi.unstubAllGlobals()
})
afterEach(() => {
vi.useRealTimers()
})
it('preserves queued set-clear order for working removal and done retention', async () => {
vi.useFakeTimers()
let storeState: StoreLike
const applyStatusUpdate = (
paneKey: string,
payload: { state: string },
timing: { updatedAt?: number } | undefined
): void => {
storeState.agentStatusByPaneKey = {
...(storeState.agentStatusByPaneKey as Record<string, unknown>),
[paneKey]: { ...payload, updatedAt: timing?.updatedAt }
}
}
const setAgentStatus = vi.fn(
(
paneKey: string,
payload: { state: string },
_title: unknown,
timing: { updatedAt?: number } | undefined
) => {
applyStatusUpdate(paneKey, payload, timing)
}
)
const setAgentStatuses = vi.fn((updates: readonly AgentStatusUpdate[]) => {
for (const update of updates) {
applyStatusUpdate(update.paneKey, update.payload, update.timing)
}
return updates.map(() => true)
})
const removeAgentStatus = vi.fn((paneKey: string) => {
const next = { ...(storeState.agentStatusByPaneKey as Record<string, unknown>) }
delete next[paneKey]
storeState.agentStatusByPaneKey = next
})
const onSetListenerRef: { current: ((data: AgentStatusSetData) => void) | null } = {
current: null
}
const onClearListenerRef: { current: ((data: { paneKey: string }) => void) | null } = {
current: null
}
storeState = buildStoreState({
setAgentStatus,
setAgentStatuses,
removeAgentStatus,
workspaceSessionReady: true,
tabsByWorktree: {
'wt-1': [{ id: 'tab-future', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'Future Tab' }]
},
terminalLayoutsByTabId: {
'tab-future': {
root: { type: 'leaf', leafId: FUTURE_LEAF_ID },
activeLeafId: FUTURE_LEAF_ID,
expandedLeafId: null
}
}
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: (cb) => {
onSetListenerRef.current = cb
return () => {}
},
onClear: (cb) => {
onClearListenerRef.current = cb as (data: { paneKey: string }) => void
return () => {}
}
})
)
try {
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
if (
typeof onSetListenerRef.current !== 'function' ||
typeof onClearListenerRef.current !== 'function'
) {
throw new Error('Expected agentStatus listeners to be registered')
}
const emit = (
receivedAt: number,
prompt: string,
state: AgentStatusSetData['state'] = 'working'
): void => {
onSetListenerRef.current!({
paneKey: FUTURE_PANE_KEY,
state,
prompt,
agentType: 'claude',
receivedAt,
stateStartedAt: receivedAt
})
}
emit(1_700_000_000_000, 'leading')
emit(1_700_000_000_001, 'queued')
expect(setAgentStatus).toHaveBeenCalledTimes(1)
onClearListenerRef.current({ paneKey: FUTURE_PANE_KEY })
expect(removeAgentStatus).toHaveBeenCalledWith(FUTURE_PANE_KEY)
expect(storeState.agentStatusByPaneKey).toEqual({})
vi.advanceTimersByTime(40)
expect(setAgentStatus).toHaveBeenCalledTimes(1)
expect(setAgentStatuses).toHaveBeenCalledTimes(1)
expect(storeState.agentStatusByPaneKey).toEqual({})
emit(1_700_000_000_002, 'task')
emit(1_700_000_000_003, 'task', 'done')
expect(setAgentStatus).toHaveBeenCalledTimes(2)
onClearListenerRef.current({ paneKey: FUTURE_PANE_KEY })
expect(setAgentStatus).toHaveBeenCalledTimes(2)
expect(setAgentStatuses).toHaveBeenCalledTimes(2)
expect(setAgentStatuses.mock.calls[1][0][0]).toEqual(
expect.objectContaining({ payload: expect.objectContaining({ state: 'done' }) })
)
expect(removeAgentStatus).toHaveBeenCalledTimes(1)
expect(storeState.agentStatusByPaneKey).toEqual({
[FUTURE_PANE_KEY]: expect.objectContaining({ state: 'done' })
})
vi.advanceTimersByTime(40)
expect(setAgentStatus).toHaveBeenCalledTimes(2)
expect(setAgentStatuses).toHaveBeenCalledTimes(2)
} finally {
vi.useRealTimers()
}
})
it('does not recurse when flushing a pending status re-enters via the store subscriber', async () => {
// Model synchronous Zustand notifications: pending flush once reentered itself with the same queued event until stack overflow (crash 9fc89529).
const subscribeListenerRef: { current: StoreSubscribeListener | null } = { current: null }
const onSetListenerRef: { current: ((data: AgentStatusSetData) => void) | null } = {
current: null
}
let setAgentStatusCalls = 0
const notify = (previousState: StoreLike = storeState): void => {
const listener = subscribeListenerRef.current
if (listener) {
listener(storeState, previousState)
}
}
const storeState: StoreLike = buildStoreState({
setAgentStatus: (paneKey: string, entry: unknown) => {
setAgentStatusCalls += 1
storeState.agentStatusByPaneKey = {
...(storeState.agentStatusByPaneKey as Record<string, unknown>),
[paneKey]: entry
}
notify()
},
workspaceSessionReady: true,
settings: { terminalFontSize: 13, notifications: { enabled: false } },
tabsByWorktree: {},
terminalLayoutsByTabId: {}
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn((listener: StoreSubscribeListener) => {
subscribeListenerRef.current = listener
return () => {
subscribeListenerRef.current = null
}
}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: (cb) => {
onSetListenerRef.current = cb
return () => {}
}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
if (typeof onSetListenerRef.current !== 'function') {
throw new Error('Expected agentStatus.onSet listener to be registered')
}
if (typeof subscribeListenerRef.current !== 'function') {
throw new Error('Expected useAppStore.subscribe listener to be registered')
}
onSetListenerRef.current({
paneKey: FUTURE_PANE_KEY,
state: 'working',
prompt: 'p',
agentType: 'claude',
receivedAt: 1_700_000_000_100,
stateStartedAt: 1_699_999_999_100
})
expect(setAgentStatusCalls).toBe(0)
// Hydration retries synchronously; the re-entry guard must apply this event once without recursion.
const beforeHydration = { ...storeState }
storeState.tabsByWorktree = {
'wt-1': [{ id: 'tab-future', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'Future Tab' }]
}
storeState.terminalLayoutsByTabId = {
'tab-future': {
root: { type: 'leaf', leafId: FUTURE_LEAF_ID },
activeLeafId: FUTURE_LEAF_ID,
expandedLeafId: null
}
}
expect(() => notify(beforeHydration)).not.toThrow()
expect(setAgentStatusCalls).toBe(1)
})
})
@@ -1,364 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
buildStoreState,
expectWorktreeRouting,
FUTURE_LEAF_ID,
FUTURE_PANE_KEY,
type AgentStatusSetData,
type StoreLike,
type StoreSubscribeListener
} from './ipc-events-agent-status-store-test-fixtures'
import {
buildWindowApi,
stubReactSyncEffect,
stubAuxiliaryModules
} from './ipc-events-agent-status-window-test-fixtures'
describe('useIpcEvents agent status snapshot integration', () => {
beforeEach(() => {
vi.resetModules()
vi.unstubAllGlobals()
})
it('queues replayed startup snapshots until the pane hydrates', async () => {
const setAgentStatus = vi.fn()
const subscribeListenerRef: { current: StoreSubscribeListener | null } = { current: null }
let resolveSnapshot!: (entries: AgentStatusSetData[]) => void
const getSnapshot = vi.fn(
() =>
new Promise<AgentStatusSetData[]>((resolve) => {
resolveSnapshot = resolve
})
)
const storeState: StoreLike = buildStoreState({
setAgentStatus,
workspaceSessionReady: true,
tabsByWorktree: {},
terminalLayoutsByTabId: {},
repos: [],
worktreesByRepo: {}
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn((listener: StoreSubscribeListener) => {
subscribeListenerRef.current = listener
return () => {
subscribeListenerRef.current = null
}
}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
getSnapshot,
onSet: () => () => {}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
resolveSnapshot([
{
paneKey: FUTURE_PANE_KEY,
tabId: 'tab-future',
worktreeId: 'wt-1',
connectionId: 'ssh-1',
state: 'working',
prompt: 'PreToolUse: Bash',
agentType: 'codex',
toolName: 'Bash',
toolInput: 'pnpm test',
terminalHandle: 'term-future',
receivedAt: 1_700_000_000_000,
stateStartedAt: 1_699_999_999_000,
// A reload starts with no state history; the host's turn start must arrive with the row.
turnStartedAt: 1_699_999_990_000
}
])
await Promise.resolve()
await Promise.resolve()
expect(setAgentStatus).not.toHaveBeenCalled()
const beforeHydration = { ...storeState }
Object.assign(storeState, {
tabsByWorktree: {
'wt-1': [{ id: 'tab-future', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'SSH Tab' }]
},
terminalLayoutsByTabId: {
'tab-future': {
root: { type: 'leaf', leafId: FUTURE_LEAF_ID },
activeLeafId: FUTURE_LEAF_ID,
expandedLeafId: null
}
}
})
subscribeListenerRef.current?.(storeState, beforeHydration)
expect(setAgentStatus).toHaveBeenCalledTimes(1)
expect(setAgentStatus).toHaveBeenCalledWith(
FUTURE_PANE_KEY,
expect.objectContaining({
state: 'working',
prompt: 'PreToolUse: Bash',
agentType: 'codex',
toolName: 'Bash',
toolInput: 'pnpm test'
}),
'SSH Tab',
{
updatedAt: 1_700_000_000_000,
stateStartedAt: 1_699_999_999_000,
turnStartedAt: 1_699_999_990_000
},
expectWorktreeRouting('wt-1'),
undefined
)
})
it('suppresses notifications for replayed done snapshots after pane hydration', async () => {
const setAgentStatus = vi.fn()
const observeAgentHookCompletionForNotification = vi.fn()
const subscribeListenerRef: { current: StoreSubscribeListener | null } = { current: null }
let resolveSnapshot!: (entries: AgentStatusSetData[]) => void
const getSnapshot = vi.fn(
() =>
new Promise<AgentStatusSetData[]>((resolve) => {
resolveSnapshot = resolve
})
)
const storeState: StoreLike = buildStoreState({
setAgentStatus,
workspaceSessionReady: true,
settings: { terminalFontSize: 13, notifications: { enabled: true, agentTaskComplete: true } },
tabsByWorktree: {},
terminalLayoutsByTabId: {},
repos: [],
worktreesByRepo: {}
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn((listener: StoreSubscribeListener) => {
subscribeListenerRef.current = listener
return () => {
subscribeListenerRef.current = null
}
}),
getState: () => storeState
}
}))
vi.doMock('./agent-hook-completion-notifications', () => ({
observeAgentHookCompletionForNotification,
resetAgentHookCompletionNotificationCoordinators: vi.fn(),
syncAgentHookCompletionNotificationSettings: vi.fn(),
syncAgentHookCompletionNotificationsForStoreUpdate: vi.fn()
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
getSnapshot,
onSet: () => () => {}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
resolveSnapshot([
{
paneKey: FUTURE_PANE_KEY,
tabId: 'tab-future',
worktreeId: 'wt-1',
connectionId: 'ssh-1',
state: 'done',
prompt: 'remote completion',
agentType: 'codex',
lastAssistantMessage: 'queued completion',
terminalHandle: 'term-future',
receivedAt: 1_700_000_000_000,
stateStartedAt: 1_699_999_999_000
}
])
await Promise.resolve()
await Promise.resolve()
expect(setAgentStatus).not.toHaveBeenCalled()
const beforeHydration = { ...storeState }
Object.assign(storeState, {
tabsByWorktree: {
'wt-1': [{ id: 'tab-future', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'SSH Tab' }]
},
terminalLayoutsByTabId: {
'tab-future': {
root: { type: 'leaf', leafId: FUTURE_LEAF_ID },
activeLeafId: FUTURE_LEAF_ID,
expandedLeafId: null
}
}
})
subscribeListenerRef.current?.(storeState, beforeHydration)
expect(setAgentStatus).toHaveBeenCalledWith(
FUTURE_PANE_KEY,
expect.objectContaining({
state: 'done',
prompt: 'remote completion',
agentType: 'codex',
lastAssistantMessage: 'queued completion'
}),
'SSH Tab',
{ updatedAt: 1_700_000_000_000, stateStartedAt: 1_699_999_999_000 },
expectWorktreeRouting('wt-1'),
undefined
)
expect(observeAgentHookCompletionForNotification).not.toHaveBeenCalled()
})
it('accepts WSL-relayed status events for a local repo (wsl:* is transport provenance, not ownership)', async () => {
const setAgentStatus = vi.fn()
const onSetListenerRef: { current: ((data: AgentStatusSetData) => void) | null } = {
current: null
}
const storeState: StoreLike = buildStoreState({
setAgentStatus,
workspaceSessionReady: true,
tabsByWorktree: {
'wt-1': [{ id: 'tab-future', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'WSL Tab' }]
},
terminalLayoutsByTabId: {
'tab-future': {
root: { type: 'leaf', leafId: FUTURE_LEAF_ID },
activeLeafId: FUTURE_LEAF_ID,
expandedLeafId: null
}
},
repos: [{ id: 'repo-1', connectionId: null }],
worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] }
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: (cb) => {
onSetListenerRef.current = cb
return () => {}
}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
if (typeof onSetListenerRef.current !== 'function') {
throw new Error('Expected agentStatus.onSet listener to be registered')
}
onSetListenerRef.current({
paneKey: FUTURE_PANE_KEY,
state: 'working',
prompt: 'wsl p',
agentType: 'claude',
worktreeId: 'wt-1',
connectionId: 'wsl:Ubuntu',
receivedAt: 1_700_000_000_000,
stateStartedAt: 1_699_999_999_000
})
expect(setAgentStatus).toHaveBeenCalledTimes(1)
expect(setAgentStatus).toHaveBeenCalledWith(
FUTURE_PANE_KEY,
expect.objectContaining({ state: 'working', prompt: 'wsl p', agentType: 'claude' }),
'WSL Tab',
{ updatedAt: 1_700_000_000_000, stateStartedAt: 1_699_999_999_000 },
expectWorktreeRouting('wt-1'),
undefined
)
})
it('still rejects WSL-relayed status events against an SSH-owned repo', async () => {
const setAgentStatus = vi.fn()
const onSetListenerRef: { current: ((data: AgentStatusSetData) => void) | null } = {
current: null
}
const storeState: StoreLike = buildStoreState({
setAgentStatus,
workspaceSessionReady: true,
tabsByWorktree: {
'wt-1': [{ id: 'tab-future', ptyId: 'pty-1', worktreeId: 'wt-1', title: 'SSH Tab' }]
},
terminalLayoutsByTabId: {
'tab-future': {
root: { type: 'leaf', leafId: FUTURE_LEAF_ID },
activeLeafId: FUTURE_LEAF_ID,
expandedLeafId: null
}
},
repos: [{ id: 'repo-1', connectionId: 'ssh-1' }],
worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] }
})
stubReactSyncEffect()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => storeState
}
}))
stubAuxiliaryModules()
vi.stubGlobal(
'window',
buildWindowApi({
onSet: (cb) => {
onSetListenerRef.current = cb
return () => {}
}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await Promise.resolve()
if (typeof onSetListenerRef.current !== 'function') {
throw new Error('Expected agentStatus.onSet listener to be registered')
}
onSetListenerRef.current({
paneKey: FUTURE_PANE_KEY,
state: 'working',
prompt: 'wsl p',
agentType: 'claude',
worktreeId: 'wt-1',
connectionId: 'wsl:Ubuntu',
receivedAt: 1_700_000_000_000,
stateStartedAt: 1_699_999_999_000
})
expect(setAgentStatus).not.toHaveBeenCalled()
})
})
@@ -1,352 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { buildStoreState } from './ipc-events-agent-status-store-test-fixtures'
import type { StoreLike } from './ipc-events-agent-status-store-test-fixtures'
import {
buildWindowApi,
stubReactSyncEffect,
stubAuxiliaryModules
} from './ipc-events-agent-status-window-test-fixtures'
import { buildSshAuthorityReconciliationHarness } from './ipc-events-ssh-authority-test-fixtures'
function stubDirectSshModules(args: { storeState: StoreLike }): void {
stubReactSyncEffect()
stubAuxiliaryModules()
vi.doMock('../store', () => ({
useAppStore: {
subscribe: vi.fn(() => () => {}),
getState: () => args.storeState
}
}))
}
// Pull main’s durable status snapshot through the real hook only after tabs are ready, recovering startup pushes received before local state.
describe('useIpcEvents agent status snapshot integration', () => {
beforeEach(() => {
vi.resetModules()
vi.unstubAllGlobals()
})
it.each([
{ partialAuthority: { providerEpoch: 'epoch-current' } },
{ partialAuthority: { connectionGeneration: 7 } }
])('fills a same-watermark partial SSH authority', async ({ partialAuthority }) => {
const harness = buildSshAuthorityReconciliationHarness({
partialAuthority,
latestAuthority: {
providerEpoch: 'epoch-current',
connectionGeneration: 7
}
})
stubDirectSshModules({ storeState: harness.storeState })
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
harness.emitPartialState()
await vi.waitFor(() => {
expect(harness.setSshConnectionState).toHaveBeenCalledTimes(2)
})
expect(harness.getState).toHaveBeenCalledOnce()
expect(harness.storedState()).toEqual({
targetId: 'target-reconciliation',
status: 'connected',
error: null,
reconnectAttempt: 0,
providerEpoch: 'epoch-current',
connectionGeneration: 7
})
})
it.each([
{ partialAuthority: { providerEpoch: 'epoch-conflict' } },
{ partialAuthority: { connectionGeneration: 6 } }
])(
'rejects a reconciliation reply that conflicts with present authority',
async ({ partialAuthority }) => {
const harness = buildSshAuthorityReconciliationHarness({
partialAuthority,
latestAuthority: {
providerEpoch: 'epoch-current',
connectionGeneration: 7
}
})
stubDirectSshModules({ storeState: harness.storeState })
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
harness.emitPartialState()
await vi.waitFor(() => {
expect(harness.getState).toHaveBeenCalledOnce()
})
await Promise.resolve()
expect(harness.setSshConnectionState).toHaveBeenCalledOnce()
expect(harness.storedState()).toEqual(
expect.objectContaining({
targetId: 'target-reconciliation',
...partialAuthority
})
)
}
)
it('does not let initial SSH port snapshots overwrite newer push events', async () => {
const targetId = 'target-ports'
const secondTargetId = 'target-ports-second'
const rejectingTargetId = 'target-ports-rejecting'
const partialTargetId = 'target-ports-partial'
const connectedState = {
targetId,
status: 'connected' as const,
error: null,
reconnectAttempt: 0,
providerEpoch: 'epoch-ports',
connectionGeneration: 3
}
const secondConnectedState = {
...connectedState,
targetId: secondTargetId,
providerEpoch: 'epoch-ports-second'
}
const rejectingConnectedState = {
...connectedState,
targetId: rejectingTargetId,
providerEpoch: 'epoch-ports-rejecting'
}
const partialConnectedState = {
...connectedState,
targetId: partialTargetId,
providerEpoch: null,
connectionGeneration: undefined
}
const reconciledPartialState = {
...connectedState,
targetId: partialTargetId,
providerEpoch: 'epoch-ports-partial',
connectionGeneration: 4
}
const liveForward = {
id: 'forward-live',
targetId,
localPort: 17860,
remoteHost: '127.0.0.1',
remotePort: 7860,
status: 'active' as const
}
const secondForward = {
...liveForward,
id: 'forward-second',
targetId: secondTargetId,
localPort: 17861,
remotePort: 7861
}
const rejectingTargetForward = {
...liveForward,
id: 'forward-rejecting-target',
targetId: rejectingTargetId,
localPort: 17862,
remotePort: 7862
}
const partialTargetForward = {
...liveForward,
id: 'forward-partial-target',
targetId: partialTargetId,
localPort: 17863,
remotePort: 7863
}
const detectedPort = {
port: 7860,
pid: 42,
processName: 'python',
command: 'python -m http.server 7860'
}
const secondDetectedPort = {
...detectedPort,
port: 7861,
pid: 43,
command: 'python -m http.server 7861'
}
let resolveForwards: (value: []) => void = () => {}
let resolveDetected: (value: (typeof detectedPort)[]) => void = () => {}
let resolveSecondForwards: (value: (typeof secondForward)[]) => void = () => {}
let resolveSecondDetected: (value: []) => void = () => {}
const forwardsSnapshot = new Promise<[]>((resolve) => {
resolveForwards = resolve
})
const detectedSnapshot = new Promise<(typeof detectedPort)[]>((resolve) => {
resolveDetected = resolve
})
const secondForwardsSnapshot = new Promise<(typeof secondForward)[]>((resolve) => {
resolveSecondForwards = resolve
})
const secondDetectedSnapshot = new Promise<[]>((resolve) => {
resolveSecondDetected = resolve
})
const listPortForwards = vi.fn(({ targetId: requestedTargetId }: { targetId: string }) => {
if (requestedTargetId === rejectingTargetId) {
return Promise.resolve([rejectingTargetForward])
}
if (requestedTargetId === partialTargetId) {
return Promise.resolve([partialTargetForward])
}
return requestedTargetId === targetId ? forwardsSnapshot : secondForwardsSnapshot
})
const listDetectedPorts = vi.fn(({ targetId: requestedTargetId }: { targetId: string }) => {
if (requestedTargetId === rejectingTargetId) {
return Promise.reject(new Error('detected snapshot unavailable'))
}
if (requestedTargetId === partialTargetId) {
return Promise.resolve([])
}
return requestedTargetId === targetId ? detectedSnapshot : secondDetectedSnapshot
})
let forwardListener:
| ((data: { targetId: string; forwards: (typeof liveForward)[] }) => void)
| undefined
let detectedListener:
| ((data: { targetId: string; ports: (typeof detectedPort)[] }) => void)
| undefined
const setPortForwards = vi.fn()
const setDetectedPorts = vi.fn()
const sshConnectionStates = new Map<
string,
| typeof connectedState
| typeof secondConnectedState
| typeof rejectingConnectedState
| typeof partialConnectedState
| typeof reconciledPartialState
>()
const storeState = buildStoreState({
sshTargetLabels: new Map([
[targetId, 'Ports Target'],
[secondTargetId, 'Second Ports Target'],
[rejectingTargetId, 'Rejecting Ports Target'],
[partialTargetId, 'Partial Ports Target']
]),
sshConnectionStates,
setSshConnectionState: (
nextTargetId: string,
state:
| typeof connectedState
| typeof secondConnectedState
| typeof rejectingConnectedState
| typeof partialConnectedState
| typeof reconciledPartialState
) => {
sshConnectionStates.set(nextTargetId, state)
},
setPortForwards,
setUnrestoredPortForwards: vi.fn(),
setDetectedPorts,
setSshTargetsMetadata: vi.fn(),
setRemovedSshTargetLabels: vi.fn(),
fetchRuntimeEnvironmentRepos: vi.fn(async () => []),
fetchWorktreeLineage: vi.fn(async () => undefined),
clearRemoteDetectedAgents: vi.fn(),
clearRemovedSshTargetState: vi.fn()
})
let partialTargetStateCalls = 0
stubDirectSshModules({ storeState })
vi.stubGlobal(
'window',
buildWindowApi({
onSet: () => () => {},
ssh: {
listTargets: () =>
Promise.resolve([
{ id: rejectingTargetId, label: 'Rejecting Ports Target' },
{ id: partialTargetId, label: 'Partial Ports Target' },
{ id: targetId, label: 'Ports Target' },
{ id: secondTargetId, label: 'Second Ports Target' }
]),
listRemovedTargetLabels: () => Promise.resolve({}),
getState: ({ targetId: requestedTargetId }: { targetId: string }) => {
if (requestedTargetId === partialTargetId) {
partialTargetStateCalls += 1
return Promise.resolve(
partialTargetStateCalls === 1 ? partialConnectedState : reconciledPartialState
)
}
return Promise.resolve(
requestedTargetId === rejectingTargetId
? rejectingConnectedState
: requestedTargetId === targetId
? connectedState
: secondConnectedState
)
},
listPortForwards,
listUnrestoredPortForwards: () => Promise.resolve([]),
listDetectedPorts,
onPortForwardsChanged: (
listener: (data: { targetId: string; forwards: (typeof liveForward)[] }) => void
) => {
forwardListener = listener
return () => {}
},
onDetectedPortsChanged: (
listener: (data: { targetId: string; ports: (typeof detectedPort)[] }) => void
) => {
detectedListener = listener
return () => {}
},
onTargetRemoved: () => () => {}
}
})
)
const { useIpcEvents } = await import('./useIpcEvents')
useIpcEvents()
await vi.waitFor(() => {
expect(forwardListener).toBeTypeOf('function')
expect(detectedListener).toBeTypeOf('function')
expect(partialTargetStateCalls).toBe(2)
expect(sshConnectionStates.get(partialTargetId)).toEqual(reconciledPartialState)
expect(listPortForwards).toHaveBeenCalledTimes(4)
expect(listDetectedPorts).toHaveBeenCalledTimes(4)
expect(
setPortForwards.mock.calls.filter(
([requestedTargetId]) => requestedTargetId === rejectingTargetId
)
).toEqual([[rejectingTargetId, [rejectingTargetForward]]])
expect(
setPortForwards.mock.calls.filter(
([requestedTargetId]) => requestedTargetId === partialTargetId
)
).toEqual([[partialTargetId, [partialTargetForward]]])
})
forwardListener?.({ targetId, forwards: [liveForward] })
resolveForwards([])
resolveDetected([detectedPort])
await vi.waitFor(() => {
expect(
setPortForwards.mock.calls.filter(([requestedTargetId]) => requestedTargetId === targetId)
).toEqual([[targetId, [liveForward]]])
expect(
setDetectedPorts.mock.calls.filter(([requestedTargetId]) => requestedTargetId === targetId)
).toEqual([[targetId, [detectedPort]]])
expect(listPortForwards).toHaveBeenCalledTimes(4)
expect(listDetectedPorts).toHaveBeenCalledTimes(4)
})
forwardListener?.({ targetId, forwards: [liveForward] })
detectedListener?.({ targetId: secondTargetId, ports: [secondDetectedPort] })
resolveSecondForwards([secondForward])
resolveSecondDetected([])
await vi.waitFor(() => {
expect(
setPortForwards.mock.calls.filter(
([requestedTargetId]) => requestedTargetId === secondTargetId
)
).toEqual([[secondTargetId, [secondForward]]])
expect(
setDetectedPorts.mock.calls.filter(
([requestedTargetId]) => requestedTargetId === secondTargetId
)
).toEqual([[secondTargetId, [secondDetectedPort]]])
})
})
})
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { agentLaunchSettingsMutationUpdates } from './agent-launch-settings'
import { applyAgentPermissionMode, AUTO_TUI_AGENT_ARGS } from '../permissions/tui-agent-permissions'
import { applyAgentPermissionMode } from '../permissions/tui-agent-permissions'
describe('agent launch permission projection compatibility', () => {
it('changes only agents Auto does not cover', () => {
@@ -13,7 +13,6 @@ describe('agent launch permission projection compatibility', () => {
{ type: 'permissions', mode: 'manual', exceptAuto: true },
'darwin'
)
expect(updates.agentDefaultArgs).toMatchObject(AUTO_TUI_AGENT_ARGS)
expect(updates.agentDefaultArgs?.gemini).toBe('')
expect(() =>
agentLaunchSettingsMutationUpdates(
@@ -33,7 +33,6 @@ windowsIt('preserves the startup query and spaced custom args in native Windows
})
const encodedCommand = plan?.command.match(/-EncodedCommand\s+(\S+)/)?.[1]
expect(encodedCommand).toBeDefined()
execFileSync('powershell.exe', ['-NoProfile', '-EncodedCommand', encodedCommand!], {
cwd: tempDir,
env: { ...process.env, ...plan?.env }
@@ -56,13 +56,9 @@ it.each(
env: { ...process.env, ...plan.env, ORCA_OMP_FRESH_CONFIG: config, CAPTURE: calls }
})
expect(result.code, result.stderr).toBe(present ? 17 : 1)
expect(result.stderr).toContain(
present ? 'agent-stderr' : 'fresh OMP settings are unavailable'
)
if (present) {
expect(result.stderr).toBe('agent-stderr')
}
expect(result.stderr).not.toContain('command substitutions not allowed')
expect(await readFile(calls, 'utf8')).toBe('task with spaces\n')
}
} finally {
@@ -34,7 +34,6 @@ it.each(shells)(
platform: 'linux',
isRemote: true
})
expect(plan).not.toBeNull()
const define =
shell === 'fish'
? 'function omp; printf "%s\\n" $argv >> "$CAPTURE"; return 17; end; '