test: remove 75 low-value renderer store tests (#37658)

This commit is contained in:
OrcaWin
2026-11-02 20:41:35 -08:00
committed by GitHub
parent 18f674e69f
commit 01ac76d626
22 changed files with 9 additions and 1539 deletions
-8
View File
@@ -11478,7 +11478,6 @@
"src/renderer/src/store/slices/worktrees/teardown/remove-worktree-shared-records.test.ts": 2086,
"src/renderer/src/store/slices/worktrees/teardown/removed-workspace-unsaved-files.test.ts": 2481,
"src/renderer/src/store/slices/worktrees/teardown/worktree-delete-state.test.ts": 1160,
"src/renderer/src/store/slices/wsl-hosts.test.ts": 1908,
"src/renderer/src/store/slices/wsl-moved-project-chat-hydration.test.ts": 1658,
"src/renderer/src/store/slices/wsl-moved-project-chat-purge.test.ts": 1222,
"src/renderer/src/store/store-identity-churn-probe.test.ts": 67,
@@ -11486,17 +11485,11 @@
"src/renderer/src/store/terminals/restored-relay-session-identity.test.ts": 2196,
"src/renderer/src/store/terminals/restored-terminal-rebind-restarts.test.ts": 1886,
"src/renderer/src/store/terminals/structured-chat-tab-rename.test.ts": 3154,
"src/renderer/src/store/terminals/terminal-chat-pane-layout-owner.test.ts": 1753,
"src/renderer/src/store/terminals/terminal-exited-pane-liveness.test.ts": 1721,
"src/renderer/src/store/terminals/terminal-no-op-subscriber.test.ts": 1763,
"src/renderer/src/store/terminals/terminal-pane-expansion-write-bailout.test.tsx": 1927,
"src/renderer/src/store/terminals/terminal-shutdown-guards-identity.test.ts": 47,
"src/renderer/src/store/terminals/terminal-sleep-layout-owner.test.ts": 58,
"src/renderer/src/store/terminals/terminal-startup-queue-restart.test.ts": 1895,
"src/renderer/src/store/terminals/terminal-tab-close-intent.test.ts": 1945,
"src/renderer/src/store/terminals/terminal-tab-create-through-layout-owner.test.ts": 1863,
"src/renderer/src/store/terminals/terminal-tab-mirrored-id.test.ts": 1662,
"src/renderer/src/store/terminals/terminal-workspace-routing.scale.test.ts": 104,
"src/renderer/src/store/terminals/workspace-terminal-placeholders.test.ts": 302,
"src/renderer/src/store/use-runtime-status-view.test.tsx": 1979,
"src/renderer/src/store/workspace-layout-cache.test.ts": 63,
@@ -11504,7 +11497,6 @@
"src/renderer/src/store/workspace-record-selectors.test.ts": 93,
"src/renderer/src/store/worktree-diff-comments-selector.test.ts": 346,
"src/renderer/src/store/worktree-host-collision-index.test.ts": 1875,
"src/renderer/src/store/worktree-visibility-defaults-by-host.test.ts": 45,
"src/renderer/src/web/preload-api/web-agent-session-attachment-upload.test.ts": 1588,
"src/renderer/src/web/preload-api/web-ai-vault-search-compat.test.ts": 306,
"src/renderer/src/web/preload-api/web-chat-permission-normalization.test.ts": 293,
@@ -106,18 +106,6 @@ describe('confirmed discards apply only to the confirmed removal', () => {
expect(shownContents()).toEqual(['typed after confirming'])
})
it('reaches the teardown through removeWorktree', async () => {
vi.stubGlobal('window', { api: createStoreCascadesMockApi() })
const store = dirtyStore([GONE])
const confirmed = confirmUnsavedDiscard(store.getState, [{ id: GONE, hostId: 'local' }])
await store.getState().removeWorktree({ id: GONE, executionHostId: 'local' }, false, {
confirmedDiscards: confirmed
})
expect(store.getState().openFiles).toEqual([])
await settle()
expect(show).not.toHaveBeenCalled()
})
it.each(purgePaths)('%s still offers text a dialog confirmed', async (_, purge, host) => {
const store = dirtyStore([GONE], host)
confirmUnsavedDiscard(store.getState, [{ id: GONE, hostId: host ?? 'local' }])
@@ -156,19 +144,6 @@ describe('delayed text is committed before a purge', () => {
panes.forEach((unregister) => unregister())
}
})
it('leaves a surviving workspace’s CSV cell editing', () => {
const survivor = `${TEST_REPO.id}::/survivor`
const store = dirtyStore([GONE, survivor])
const cell = vi.fn()
const unregister = registerPendingEditorFlush(`${survivor}/file.ts`, cell, () => true)
try {
store.getState().purgeWorktreeTerminalState([GONE])
expect(cell).not.toHaveBeenCalled()
} finally {
unregister()
}
})
})
describe('the closed-files notice', () => {
@@ -237,14 +212,4 @@ describe('the closed-files notice', () => {
unregister()
}
})
it('falls back to a sharing tab’s draft when the dirty tab has none', async () => {
const store = dirtyStore([GONE])
const tab = (id: string) =>
makeOpenFile({ id, filePath: '/w/shared.ts', worktreeId: GONE, isDirty: true })
store.setState({ openFiles: [tab('first'), tab('second')], editorDrafts: { second: 'kept' } })
store.getState().purgeWorktreeTerminalState([GONE])
await vi.waitFor(() => expect(show).toHaveBeenCalledTimes(1))
expect(shownContents()).toEqual(['kept'])
})
})
@@ -1,25 +0,0 @@
import { expect, it } from 'vitest'
import type { WslHostStatus } from '../../../../shared/wsl-host-types'
import { createTestStore } from './store-test-helpers'
it('publishes a deferral-only change for an already-ready WSL host', () => {
const store = createTestStore()
const ready: WslHostStatus = {
environmentId: 'wsl-ubuntu',
distro: 'Ubuntu',
state: 'ready',
detail: null
}
store.getState().applyWslHostStatus(ready)
const deferred: WslHostStatus = {
...ready,
deferredUpdate: {
candidateVersion: 'next',
code: 'live-terminals',
reason: 'Terminals are still running.',
forceable: true
}
}
store.getState().applyWslHostStatus(deferred)
expect(store.getState().wslHostStatuses[ready.environmentId]).toEqual(deferred)
})
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { create, type StoreApi } from 'zustand'
import { withReactCommitCascadeWriteProbe } from './react-commit-cascade-write-probe'
import { beforeEach, describe, expect, it } from 'vitest'
import { create } from 'zustand'
import './react-commit-cascade-write-probe'
import {
armStoreIdentityChurnProbe,
disarmStoreIdentityChurnProbe,
@@ -41,53 +41,6 @@ describe('store identity churn probe', () => {
disarmStoreIdentityChurnProbe()
})
it('flags a refresh that rebuilds an array with unchanged contents', () => {
const store = createProbeStore()
armStoreIdentityChurnProbe()
store.getState().refresh([{ id: 'a', label: 'A' }])
expect(churnFor('rows')).toBe(1)
expect(readStoreIdentityChurnReport()[0]).toMatchObject({
field: 'rows',
churnedWrites: 1,
replacedWrites: 1,
sites: []
})
})
it('flags a keyed update that rewrites an entry with the same value', () => {
const store = createProbeStore()
armStoreIdentityChurnProbe()
store.getState().touch('a', 'idle')
expect(churnFor('entries')).toBe(1)
})
it('does not flag writes that change the value', () => {
const store = createProbeStore()
armStoreIdentityChurnProbe()
store.getState().refresh([{ id: 'a', label: 'B' }])
store.getState().touch('a', 'running')
store.getState().bump()
expect(readStoreIdentityChurnReport()).toEqual([])
})
it('names the write site when capture is requested', () => {
const store = createProbeStore()
armStoreIdentityChurnProbe({ captureSites: true })
store.getState().refresh([{ id: 'a', label: 'A' }])
const [row] = readStoreIdentityChurnReport()
expect(row.sites).toHaveLength(1)
expect(row.sites[0]).toMatchObject({ churnedWrites: 1 })
expect(row.sites[0].site).toContain('store-identity-churn-probe.test')
})
it('leaves a functional updater to zustand: called once, with the live state', () => {
const store = createProbeStore()
const seen: unknown[] = []
@@ -107,81 +60,4 @@ describe('store identity churn probe', () => {
expect(store.getState().counter).toBe(1)
expect(churnFor('rows')).toBe(1)
})
it('passes disarmed writes straight through without reading state', () => {
const innerSet = vi.fn()
const innerGet = vi.fn(() => ({ counter: 0 }))
const api = { setState: innerSet, getState: innerGet } as unknown as StoreApi<{
counter: number
}>
const creator = withStoreIdentityChurnProbe<{ counter: number }>(() => ({ counter: 0 }))
creator(innerSet, innerGet, api)
const updater = (state: { counter: number }) => ({ counter: state.counter + 1 })
api.setState(updater, true)
// The disarmed path forwards the exact arguments and never calls get().
expect(innerSet).toHaveBeenCalledTimes(1)
expect(innerSet.mock.calls[0]).toEqual([updater, true])
expect(innerGet).not.toHaveBeenCalled()
})
it('composes with the cascade probe without dropping or doubling a write', () => {
// Mirrors store/index.ts: churn probe outermost, cascade probe inside it.
const store = create<ProbeState>()(
withStoreIdentityChurnProbe(
withReactCommitCascadeWriteProbe((set) => ({
rows: [{ id: 'a', label: 'A' }],
entries: { a: { status: 'idle' } },
counter: 0,
refresh: (rows) => set({ rows }),
touch: (id, status) =>
set((state) => ({ entries: { ...state.entries, [id]: { status } } })),
bump: () => set((state) => ({ counter: state.counter + 1 }))
}))
)
)
let updaterCalls = 0
armStoreIdentityChurnProbe({ captureSites: true })
store.getState().bump()
store.setState((state) => {
updaterCalls += 1
return { counter: state.counter + 10 }
})
store.getState().refresh([{ id: 'a', label: 'A' }])
store.setState({ ...store.getState(), counter: 100 }, true)
expect(updaterCalls).toBe(1)
expect(store.getState().counter).toBe(100)
expect(store.getState().rows).toEqual([{ id: 'a', label: 'A' }])
// The named site is this test, not the sibling probe's wrapper frame.
const [row] = readStoreIdentityChurnReport()
expect(row).toMatchObject({ field: 'rows', churnedWrites: 1 })
expect(row.sites[0].site).toContain('store-identity-churn-probe.test')
})
it('still sees churn on a replace write', () => {
const store = createProbeStore()
const rows = store.getState().rows
armStoreIdentityChurnProbe()
store.setState({ ...store.getState(), rows: [{ ...rows[0] }] }, true)
expect(churnFor('rows')).toBe(1)
})
it('treats distinct class instances as changed rather than equal', () => {
const store = create<{ value: unknown; put: (value: unknown) => void }>()(
withStoreIdentityChurnProbe((set) => ({
value: new Map([['a', 1]]),
put: (value) => set({ value })
}))
)
armStoreIdentityChurnProbe()
store.getState().put(new Map([['a', 1]]))
expect(readStoreIdentityChurnReport()).toEqual([])
})
})
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { WorkspaceSessionState } from '../../../../shared/workspace-session-state-types'
import { buildWorkspaceSessionPayload } from '@/lib/workspace-session'
import { getOrphanTerminalIds } from '../slices/terminal-orphan-helpers'
import '../slices/terminal-orphan-helpers'
import { createTestStore, makeTab, makeWorktree } from '../slices/store-test-helpers'
const layoutOwner = vi.hoisted(() => ({ isMain: false }))
@@ -94,57 +94,5 @@ describe.each([false, true])(
expect(payload.activeWorktreeIdsOnShutdown).toContain(WORKTREE_ID)
expect(payload.activeConnectionIdsAtShutdown).toEqual([TARGET_ID])
})
it('rebinds the restored tab to its live relay PTY and keeps publishing that id', async () => {
const store = seedStore()
store.getState().hydrateWorkspaceSession(persistedSession())
await store.getState().reconnectPersistedTerminals()
expect(store.getState().tabsByWorktree[WORKTREE_ID][0].ptyId).toBe(RELAY_PTY_ID)
expect(store.getState().ptyIdsByTabId[TAB_ID]).toEqual([RELAY_PTY_ID])
expect(buildWorkspaceSessionPayload(store.getState()).remoteSessionIdsByTabId).toEqual({
[TAB_ID]: RELAY_PTY_ID
})
})
it('keeps a deferred relay session id while the host is disconnected', async () => {
const store = seedStore('disconnected')
store.getState().hydrateWorkspaceSession(persistedSession())
await store.getState().reconnectPersistedTerminals()
expect(store.getState().deferredSshSessionIdsByTabId[TAB_ID]).toBe(RELAY_PTY_ID)
// Loss of contact is not evidence the remote PTY exited, so the handle survives into the next write.
expect(buildWorkspaceSessionPayload(store.getState()).remoteSessionIdsByTabId).toEqual({
[TAB_ID]: RELAY_PTY_ID
})
})
it('leaves a tab whose relay handle is unverifiable alone instead of retiring it', async () => {
const store = seedStore('disconnected')
store.getState().hydrateWorkspaceSession(persistedSession())
await store.getState().reconnectPersistedTerminals()
// No live PTY, no row binding, host unreachable — `unverifiable`, never `exited`.
expect([...getOrphanTerminalIds(store.getState(), WORKTREE_ID)]).toEqual([])
expect(store.getState().tabsByWorktree[WORKTREE_ID].map((tab) => tab.id)).toEqual([TAB_ID])
})
it('does not invent a session id for a tab that never had one', () => {
const store = seedStore()
const session = persistedSession()
session.tabsByWorktree[WORKTREE_ID] = [
makeTab({ id: TAB_ID, worktreeId: WORKTREE_ID, ptyId: null })
]
delete session.remoteSessionIdsByTabId
delete session.activeConnectionIdsAtShutdown
session.activeWorktreeIdsOnShutdown = []
store.getState().hydrateWorkspaceSession(session)
const payload = buildWorkspaceSessionPayload(store.getState())
expect(payload.remoteSessionIdsByTabId).toBeUndefined()
expect(payload.activeWorktreeIdsOnShutdown).toEqual([])
})
}
)
@@ -59,24 +59,6 @@ function structuredTab(): Tab {
}
}
const TERMINAL_TAB_ID = 'terminal-1'
const TERMINAL_UNIFIED_ID = 'unified-terminal-1'
function terminalTab(): Tab {
return {
id: TERMINAL_UNIFIED_ID,
entityId: TERMINAL_TAB_ID,
groupId: 'group-1',
worktreeId: WORKTREE,
contentType: 'terminal',
label: 'Terminal',
customLabel: null,
color: null,
sortOrder: 1,
createdAt: 2
}
}
function storeWithStructuredTab(): ReturnType<typeof createTestStore> {
const store = createTestStore()
seedStore(store, {
@@ -112,43 +94,6 @@ function expectChange(windowShows: () => void, mainGets: Record<string, unknown>
}
}
describe('renaming a terminal tab still resolves', () => {
it('routes a terminal rename through its entityId, not the unified id', async () => {
const store = createTestStore()
seedStore(store, {
repos: [{ id: 'local-repo', path: '/tmp/app', name: 'app' }] as never,
worktreesByRepo: {
'local-repo': [makeWorktree({ id: WORKTREE, repoId: 'local-repo', path: '/tmp/app' })]
},
unifiedTabsByWorktree: { [WORKTREE]: [terminalTab(), structuredTab()] }
})
// Keyed by the TERMINAL's entityId — the structured tab must not absorb it.
store.getState().setTabCustomTitle(TERMINAL_TAB_ID, 'Build logs')
await layoutApplyTestTransport.settle()
const tabs = store.getState().unifiedTabsByWorktree[WORKTREE] ?? []
expectChange(
() => expect(tabs.find((t) => t.id === TERMINAL_UNIFIED_ID)?.customLabel).toBe('Build logs'),
{ type: 'renameTab', tabId: TERMINAL_UNIFIED_ID, title: 'Build logs' }
)
expect(tabs.find((t) => t.id === STRUCTURED_TAB_ID)?.customLabel).toBeNull()
})
})
describe('recoloring a structured chat tab', () => {
it('writes the color onto the agent-session tab', async () => {
const store = storeWithStructuredTab()
store.getState().setTabColor(STRUCTURED_TAB_ID, 'red')
await layoutApplyTestTransport.settle()
expectChange(() => expect(colorOf(store)).toBe('red'), {
type: 'setTabProps',
tabId: STRUCTURED_TAB_ID,
color: 'red'
})
})
})
describe('renaming a structured chat tab', () => {
beforeEach(() => {
vi.mocked(toast.error).mockClear()
@@ -1,117 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as AgentStatusModule from '@/lib/agent-status'
import { createTestStore, makeWorktree, seedStore } from '../slices/store-test-helpers'
import { createStoreCascadesMockApi } from '../slices/store-cascades-test-harness'
import { _resetLayoutOwnerPaneIntentsForTests } from '@/runtime/layout-owner-pane-intents'
import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
import { LayoutApplyParams } from '../../../../shared/rpc-contract/workspace-layout-params'
import { applyLayoutCommand } from '../../../../shared/workspace-layout/workspace-layout-commands'
import { layoutCommandsOf } from '@/runtime/layout-apply-test-double'
import type {
LayoutTerminalTab,
WorkspaceLayoutModel
} from '../../../../shared/workspace-layout/workspace-layout-model'
const mocks = vi.hoisted(() => ({
layoutOwnerIsMain: true,
callRuntimeRpc: vi.fn(async (_target: unknown, _method: string, _params: unknown) => ({
ok: true,
result: {}
}))
}))
vi.mock('../../../../shared/layout-owner-switch', () => ({
get ORCA_LAYOUT_OWNER_IS_MAIN() {
return mocks.layoutOwnerIsMain
}
}))
vi.mock('@/runtime/runtime-rpc-client', () => ({ callRuntimeRpc: mocks.callRuntimeRpc }))
vi.mock('sonner', () => ({
toast: { info: vi.fn(), success: vi.fn(), error: vi.fn(), warning: vi.fn() }
}))
vi.mock('@/lib/agent-status', async (importOriginal) => {
const actual = await importOriginal<typeof AgentStatusModule>()
return { ...actual, detectAgentStatusFromTitle: vi.fn().mockReturnValue(null) }
})
createStoreCascadesMockApi()
const WT = 'repo1::/path/wt1'
const LEAF = '11111111-1111-4111-8111-111111111111'
let store: ReturnType<typeof createTestStore>
/** Main's copy; it changes only through what the window sends. */
let main: WorkspaceLayoutModel
beforeEach(() => {
mocks.layoutOwnerIsMain = true
mocks.callRuntimeRpc.mockClear()
_resetLayoutOwnerPaneIntentsForTests()
store = createTestStore()
seedStore(store, {
worktreesByRepo: { repo1: [makeWorktree({ id: WT, repoId: 'repo1', path: '/path/wt1' })] },
activeWorktreeId: WT
})
main = {
hostId: LOCAL_EXECUTION_HOST_ID,
workspaces: {},
records: {},
legacy: { terminalRowOwners: {} }
}
})
/** Applies what the window sent to main's copy, as `layout.apply` does; any refusal fails. */
function applySent() {
const commands = mocks.callRuntimeRpc.mock.calls.flatMap(([, , params]) =>
layoutCommandsOf(LayoutApplyParams.parse(params).command)
)
mocks.callRuntimeRpc.mockClear()
let id = 0
for (const command of commands) {
const applied = applyLayoutCommand(main, command, {
mintId: () => `main-${++id}`,
mintLeafId: () => `leaf-${++id}`,
now: () => 1
})
if (!applied.ok) {
throw new Error(`${command.type} refused: ${applied.code}`)
}
main = applied.model
}
return commands
}
function mainTab(tabId: string): LayoutTerminalTab {
const tab = main.workspaces[WT]!.tabs.find((entry) => entry.id === tabId)
if (tab?.kind !== 'terminal') {
throw new Error('main holds no such terminal tab')
}
return tab
}
/** A one-pane tab main holds, as the window made it. */
function heldTab(): string {
const tab = store.getState().createTab(WT, undefined, undefined, { initialLeafId: LEAF })
applySent()
return tab.id
}
function setChatLeaf(tabId: string, chatLeafId: string | null): void {
const { chatLeafId: _old, ...layout } = store.getState().terminalLayoutsByTabId[tabId]!
store.getState().setTabLayout(tabId, chatLeafId ? { ...layout, chatLeafId } : layout)
}
describe('the chat pane in a layout-owner build', () => {
it('records each chat pane the window picks in main, so its save changes none', () => {
const tabId = heldTab()
setChatLeaf(tabId, LEAF)
expect(applySent()).toEqual([{ type: 'setChatPane', workspace: WT, tabId, leafId: LEAF }])
expect(mainTab(tabId).panes.chatLeafId).toBe(LEAF)
setChatLeaf(tabId, null)
applySent()
expect(mainTab(tabId).panes.chatLeafId).toBeUndefined()
})
})
@@ -3,42 +3,11 @@ import {
flushTerminalInputActivity,
resetTerminalInputActivityCoalescingForTests
} from '@/lib/terminal-input-activity-coalescing'
import { createTestStore, makeLayout } from '../slices/store-test-helpers'
import { createTestStore } from '../slices/store-test-helpers'
afterEach(resetTerminalInputActivityCoalescingForTests)
describe('terminal no-op subscriber budget', () => {
it('publishes real mutations once and keeps repeated actions silent', () => {
const store = createTestStore()
const draft = { tabId: 'tab', agent: 'codex', text: 'draft', createdAt: 1 } as const
store.getState().seedNativeChatLaunchPrompt(draft)
store.getState().seedNativeChatLaunchDraft(draft)
store.getState().setTabLayout('tab', makeLayout())
const listener = vi.fn()
store.subscribe(listener)
const actions = [
() => store.getState().markDefaultTerminalTabsApplied('folder-workspace'),
() => store.getState().markUnverifiedPtyLoss('tab'),
() => store.getState().markPtySourceDisowned('pty'),
() => store.getState().markNativeChatLaunchPromptFailed('tab'),
() => store.getState().markNativeChatLaunchDraftAdopted('tab'),
() => store.getState().resolveNativeChatLaunchDraft('tab', draft),
() => store.getState().replaceTerminalLayoutPanePtyId('tab', 'leaf', 'pty'),
() => store.getState().clearNativeChatLaunchPrompt('tab'),
() => store.getState().clearNativeChatLaunchDraft('tab')
]
for (const action of actions) {
listener.mockClear()
action()
expect(listener).toHaveBeenCalledTimes(1)
const before = store.getState()
action()
expect(listener).toHaveBeenCalledTimes(1)
expect(store.getState()).toBe(before)
}
})
it('retains draft generations when stale resolutions arrive without notifying', () => {
const store = createTestStore()
const draft = { tabId: 'tab', agent: 'codex', text: 'new draft', createdAt: 2 } as const
@@ -1,152 +0,0 @@
// @vitest-environment happy-dom
import { Profiler } from 'react'
import { act, cleanup, render } from '@testing-library/react'
import { afterEach, describe, expect, it } from 'vitest'
import { createTestStore } from '../slices/store-test-helpers'
afterEach(cleanup)
type TestStore = ReturnType<typeof createTestStore>
const TAB_ID = 'tab-1'
const NO_OP_WRITES = 25
function recordPublishedMapKeys(store: TestStore): string[] {
const published: string[] = []
store.subscribe((next, previous) => {
if (next.expandedPaneByTabId !== previous.expandedPaneByTabId) {
published.push('expandedPaneByTabId')
}
if (next.canExpandPaneByTabId !== previous.canExpandPaneByTabId) {
published.push('canExpandPaneByTabId')
}
})
return published
}
// Mirrors use-terminal-workspace-store-bindings.ts:17, which subscribes to the raw map.
function ExpandedPaneSubscriber({ store }: { store: TestStore }): React.JSX.Element {
const expandedPaneByTabId = store((s) => s.expandedPaneByTabId)
return <span>{String(expandedPaneByTabId[TAB_ID] === true)}</span>
}
function CanExpandPaneSubscriber({ store }: { store: TestStore }): React.JSX.Element {
const canExpandPaneByTabId = store((s) => s.canExpandPaneByTabId)
return <span>{String(canExpandPaneByTabId[TAB_ID] === true)}</span>
}
function renderCommitCounter(subscriber: React.JSX.Element): () => number {
let commits = 0
render(
<Profiler
id="terminal-pane-expansion"
onRender={() => {
commits += 1
}}
>
{subscriber}
</Profiler>
)
const mountCommits = commits
return () => commits - mountCommits
}
describe('setTabPaneExpanded', () => {
it('publishes the first write for an unseen tab and a real toggle', () => {
const store = createTestStore()
const published = recordPublishedMapKeys(store)
store.getState().setTabPaneExpanded(TAB_ID, false)
expect(published).toEqual(['expandedPaneByTabId'])
expect(store.getState().expandedPaneByTabId[TAB_ID]).toBe(false)
store.getState().setTabPaneExpanded(TAB_ID, true)
expect(published).toEqual(['expandedPaneByTabId', 'expandedPaneByTabId'])
expect(store.getState().expandedPaneByTabId[TAB_ID]).toBe(true)
})
it('bails out when the value is unchanged', () => {
const store = createTestStore()
store.getState().setTabPaneExpanded(TAB_ID, false)
const before = store.getState().expandedPaneByTabId
// Root identity too: returning `{}` keeps the map but allocates a new root, so zustand still walks every listener.
const rootBefore = store.getState()
const published = recordPublishedMapKeys(store)
for (let i = 0; i < NO_OP_WRITES; i += 1) {
store.getState().setTabPaneExpanded(TAB_ID, false)
}
expect(published).toEqual([])
expect(store.getState().expandedPaneByTabId).toBe(before)
expect(store.getState()).toBe(rootBefore)
})
it('costs no React commit in a map subscriber when the value is unchanged', () => {
const store = createTestStore()
store.getState().setTabPaneExpanded(TAB_ID, false)
const commitsSinceMount = renderCommitCounter(<ExpandedPaneSubscriber store={store} />)
for (let i = 0; i < NO_OP_WRITES; i += 1) {
act(() => {
store.getState().setTabPaneExpanded(TAB_ID, false)
})
}
expect(commitsSinceMount()).toBe(0)
act(() => {
store.getState().setTabPaneExpanded(TAB_ID, true)
})
expect(commitsSinceMount()).toBe(1)
})
})
describe('setTabCanExpandPane', () => {
it('publishes the first write for an unseen tab and a real toggle', () => {
const store = createTestStore()
const published = recordPublishedMapKeys(store)
store.getState().setTabCanExpandPane(TAB_ID, false)
expect(published).toEqual(['canExpandPaneByTabId'])
expect(store.getState().canExpandPaneByTabId[TAB_ID]).toBe(false)
store.getState().setTabCanExpandPane(TAB_ID, true)
expect(published).toEqual(['canExpandPaneByTabId', 'canExpandPaneByTabId'])
expect(store.getState().canExpandPaneByTabId[TAB_ID]).toBe(true)
})
it('bails out when the value is unchanged', () => {
const store = createTestStore()
store.getState().setTabCanExpandPane(TAB_ID, false)
const before = store.getState().canExpandPaneByTabId
const rootBefore = store.getState()
const published = recordPublishedMapKeys(store)
for (let i = 0; i < NO_OP_WRITES; i += 1) {
store.getState().setTabCanExpandPane(TAB_ID, false)
}
expect(published).toEqual([])
expect(store.getState().canExpandPaneByTabId).toBe(before)
expect(store.getState()).toBe(rootBefore)
})
it('costs no React commit in a map subscriber when the value is unchanged', () => {
const store = createTestStore()
store.getState().setTabCanExpandPane(TAB_ID, false)
const commitsSinceMount = renderCommitCounter(<CanExpandPaneSubscriber store={store} />)
for (let i = 0; i < NO_OP_WRITES; i += 1) {
act(() => {
store.getState().setTabCanExpandPane(TAB_ID, false)
})
}
expect(commitsSinceMount()).toBe(0)
act(() => {
store.getState().setTabCanExpandPane(TAB_ID, true)
})
expect(commitsSinceMount()).toBe(1)
})
})
@@ -1,72 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import type { AppState } from '../types'
import { createTerminalShutdownGuardController } from './terminal-shutdown-guards'
vi.mock('@/components/terminal-pane/pty-dispatcher', () => ({
restorePtyDataHandlersAfterFailedShutdown: vi.fn(),
unregisterPtyDataHandlers: vi.fn(() => [])
}))
vi.mock('@/components/terminal-pane/terminal-parked-watcher-registry', () => ({
disposeParkedTerminalWatchersForPtyIds: vi.fn()
}))
vi.mock('@/components/terminal-pane/pty-shutdown-exit-deferral', () => ({
clearCommittedPtyShutdownSettlements: vi.fn(),
hasCommittedPtyShutdownSettlement: vi.fn(() => false),
markCommittedPtyShutdowns: vi.fn(),
noteCommittedPtyShutdownSettlements: vi.fn(),
settleDeferredPtyShutdownExits: vi.fn()
}))
function harness(initial: Partial<AppState>, exitGuardPtyIds: readonly string[]) {
let current = initial as AppState
const set = vi.fn((update: unknown) => {
const patch =
typeof update === 'function' ? (update as (s: AppState) => object)(current) : update
current = { ...current, ...(patch as object) }
})
const guards = createTerminalShutdownGuardController({
exitGuardPtyIds,
get: (() => current) as never,
keepIdentifiers: false,
rendererShutdownPtyIds: exitGuardPtyIds,
runtimeOwnsRemotePtys: false,
set: set as never,
tabs: []
})
return { guards, set, state: () => current }
}
describe('markShutdownPending identity', () => {
it('does not write the store when there is nothing to guard', () => {
const { guards, set } = harness({ suppressedPtyExitIds: {}, pendingPtyShutdownIds: {} }, [])
guards.markShutdownPending()
expect(set).not.toHaveBeenCalled()
})
it('still counts a pending owner when every id is already suppressed', () => {
const suppressedPtyExitIds: Record<string, true> = { 'pty-1': true }
const { guards, state } = harness(
{ suppressedPtyExitIds, pendingPtyShutdownIds: { 'pty-1': 1 } },
['pty-1']
)
guards.markShutdownPending()
expect(state().suppressedPtyExitIds).toBe(suppressedPtyExitIds)
expect(state().pendingPtyShutdownIds).toEqual({ 'pty-1': 2 })
})
it('suppresses the ids that were not yet suppressed', () => {
const { guards, state } = harness(
{ suppressedPtyExitIds: { 'pty-1': true }, pendingPtyShutdownIds: {} },
['pty-1', 'pty-2']
)
guards.markShutdownPending()
expect(state().suppressedPtyExitIds).toEqual({ 'pty-1': true, 'pty-2': true })
expect(state().pendingPtyShutdownIds).toEqual({ 'pty-1': 1, 'pty-2': 1 })
})
})
@@ -1,114 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { SleepingAgentSessionRecord } from '../../../../shared/agent-session/lifecycle/agent-session-resume'
import type { TerminalTab } from '../../../../shared/terminal-tab-types'
import type { recordLayoutApply } from '@/runtime/layout-apply-send'
import {
recordPaneSleepThroughLayoutOwner,
recordPaneWakeThroughLayoutOwner,
recordSleepingRecordChangesThroughLayoutOwner
} from './terminal-sleep-layout-owner'
const mocks = vi.hoisted(() => ({ send: vi.fn<typeof recordLayoutApply>() }))
vi.mock('@/runtime/layout-apply-send', () => ({ recordLayoutApply: mocks.send }))
const WT = 'repo1::/path/wt1'
const STATE = {}
const A1 = 'tab-a:11111111-1111-4111-8111-111111111111'
const A2 = 'tab-a:22222222-2222-4222-8222-222222222222'
const B1 = 'tab-b:33333333-3333-4333-8333-333333333333'
function record(paneKey: string): SleepingAgentSessionRecord {
return {
paneKey,
worktreeId: WT,
agent: 'codex',
providerSession: { key: 'session_id', id: paneKey },
prompt: '',
state: 'done',
capturedAt: 1,
updatedAt: 1
}
}
/** A window holding these terminal tabs in WT. */
function holding(tabIds: string[]): { tabsByWorktree: Record<string, TerminalTab[]> } {
return {
tabsByWorktree: {
[WT]: tabIds.map((id) => ({
id,
ptyId: null,
worktreeId: WT,
title: 'Terminal',
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 1
}))
}
}
}
describe('sleep and wake through the layout owner', () => {
beforeEach(() => {
mocks.send.mockReset()
})
it('sends one sleep per tab naming only the panes it stopped', () => {
const records = {
[A1]: record(A1),
[A2]: record(A2),
[B1]: record(B1),
// A legacy pane key names no pane main holds.
'tab-a:1': record('tab-a:1')
}
recordPaneSleepThroughLayoutOwner(STATE, WT, records)
expect(mocks.send.mock.calls.map((call) => call[2])).toEqual([
{ type: 'sleep', tabId: 'tab-a', paneKeys: [A1, A2], records: [record(A1), record(A2)] },
{ type: 'sleep', tabId: 'tab-b', paneKeys: [B1], records: [record(B1)] }
])
})
it('sends nothing when nothing slept, as a worktree removal', () => {
recordPaneSleepThroughLayoutOwner(STATE, WT, {})
expect(mocks.send).not.toHaveBeenCalled()
})
it('records the wake of the one pane the window resumed, and has main start nothing', () => {
recordPaneWakeThroughLayoutOwner(STATE, WT, A2)
expect(mocks.send).toHaveBeenCalledWith(STATE, WT, {
type: 'wake',
tabId: 'tab-a',
paneKeys: [A2],
recordOnly: true
})
})
it('records a live capture and a dropped record without stopping or starting anything', () => {
const live = { ...record(A1), state: 'working' as const, origin: 'live' as const }
const kept = record(B1)
recordSleepingRecordChangesThroughLayoutOwner(
holding(['tab-a', 'tab-b']),
{ [A2]: record(A2), [B1]: kept },
{ [A1]: live, [B1]: kept }
)
expect(mocks.send.mock.calls.map((call) => call[2])).toEqual([
{ type: 'sleep', tabId: 'tab-a', paneKeys: [A1], records: [live], recordOnly: true },
{ type: 'wake', tabId: 'tab-a', paneKeys: [A2], recordOnly: true }
])
})
it('sends nothing for a tab the window closed: main dropped its records with the tab', () => {
recordSleepingRecordChangesThroughLayoutOwner(holding(['tab-b']), { [A2]: record(A2) }, {})
// A periodic capture from a stale agent status can name the closed tab again.
recordSleepingRecordChangesThroughLayoutOwner(holding(['tab-b']), {}, { [A1]: record(A1) })
expect(mocks.send).not.toHaveBeenCalled()
})
})
@@ -89,19 +89,6 @@ vi.mock('@/lib/agent-status', async (importOriginal) => ({
mockApi.worktrees.updateMeta.mockResolvedValue({})
})
it('commits the close in main even when the SSH shutdown throws', async () => {
mockApi.pty.kill.mockRejectedValue(new Error('relay shutdown failed'))
const store = storeWithSshTab()
store.getState().closeTab('ssh-tab')
await Promise.resolve()
expect(mockApi.pty.kill).toHaveBeenCalledWith(SSH_PTY)
expect(sentCloses(closeTerminalSurface)).toEqual([
{ worktreeId: SSH_WORKTREE, tabId: 'ssh-tab', reason: 'user' }
])
})
it('removes the tab and kills it without waiting for main to write the close', async () => {
closeTerminalSurface.mockReturnValue(new Promise<void>(() => {}))
layoutApplyTestTransport.answerWith('pending')
@@ -114,27 +101,6 @@ vi.mock('@/lib/agent-status', async (importOriginal) => ({
await Promise.resolve()
expect(mockApi.pty.kill).toHaveBeenCalledWith(SSH_PTY)
})
it.each([['user'], ['cleanup']] as const)('sends the intent for a %s close', (reason) => {
storeWithSshTab().getState().closeTab('ssh-tab', { reason })
expect(sentCloses(closeTerminalSurface)).toEqual([
{ worktreeId: SSH_WORKTREE, tabId: 'ssh-tab', reason }
])
})
// Why: exits still retire through main's own exit handling until main decides exits itself.
it('sends nothing for a pty-exit close', () => {
storeWithSshTab().getState().closeTab('ssh-tab', { reason: 'pty-exit' })
expect(sentCloses(closeTerminalSurface)).toEqual([])
})
it('sends nothing when a paired host owns the close', () => {
storeWithSshTab().getState().closeTab('ssh-tab', { remoteCloseOwnedByHost: true })
expect(sentCloses(closeTerminalSurface)).toEqual([])
})
})
}
@@ -144,30 +110,6 @@ vi.mock('@/lib/agent-status', async (importOriginal) => ({
const WORKTREE = 'repo::/tmp/app'
/** Maps a closing tab has no entry in; closing must not give them a new reference. */
const UNTOUCHED_FIELDS = [
'terminalLayoutsByTabId',
'ptyIdsByTabId',
'runtimePaneTitlesByTabId',
'lastKnownRelayPtyIdByTabId',
'deferredSshSessionIdsByTabId',
'pendingReconnectPtyIdByTabId',
'pendingStartupByTabId',
'automaticAgentResumeClaimsByTabId',
'nativeChatLaunchPromptByTabId',
'nativeChatLaunchDraftByTabId',
'pendingInitialCwdByTabId',
'pendingIssueCommandSplitByTabId',
'expandedPaneByTabId',
'canExpandPaneByTabId',
'cacheTimerByKey',
'lastTerminalInputAtByPaneKey',
'unreadTerminalTabs',
'unreadTerminalPanes',
'unreadAgentCompletionPanes',
'tabBarOrderByWorktree'
] as const
function storeWithTwoTabs(): ReturnType<typeof createTestStore> {
const store = createTestStore()
seedStore(store, {
@@ -195,23 +137,6 @@ vi.mock('@/lib/agent-status', async (importOriginal) => ({
mockApi.worktrees.updateMeta.mockResolvedValue({})
})
it('keeps the reference of every per-tab map the closing tab had no entry in', () => {
const store = storeWithTwoTabs()
const before = store.getState()
const snapshot = Object.fromEntries(
UNTOUCHED_FIELDS.map((field) => [field, before[field]])
) as Record<string, unknown>
store.getState().closeTab('tab-a')
const after = store.getState()
// The tab really closed — otherwise the identity assertions below are vacuous.
expect(after.tabsByWorktree[WORKTREE].map((tab) => tab.id)).toEqual(['tab-b'])
for (const field of UNTOUCHED_FIELDS) {
expect(after[field], field).toBe(snapshot[field])
}
})
it('still drops the closing tab from a map that did hold it', () => {
const store = storeWithTwoTabs()
store.setState({
@@ -233,88 +158,3 @@ vi.mock('@/lib/agent-status', async (importOriginal) => ({
})
})
}
{
const mockApi = createStoreCascadesMockApi()
const mockWindow = globalThis.window
const REMOTE_WORKTREE = 'remote-repo::/srv/app'
const LOCAL_WORKTREE = 'local-repo::/tmp/app'
function storeWithBothWorktrees(): ReturnType<typeof createTestStore> {
const store = createTestStore()
seedStore(store, {
repos: [
{ id: 'remote-repo', path: '/srv/app', name: 'app', connectionId: 'ssh-1' },
{ id: 'local-repo', path: '/tmp/app', name: 'app' }
] as never,
worktreesByRepo: {
'remote-repo': [
makeWorktree({ id: REMOTE_WORKTREE, repoId: 'remote-repo', path: '/srv/app' })
],
'local-repo': [makeWorktree({ id: LOCAL_WORKTREE, repoId: 'local-repo', path: '/tmp/app' })]
},
tabsByWorktree: {
[REMOTE_WORKTREE]: [makeTab({ id: 'remote-tab', worktreeId: REMOTE_WORKTREE })],
[LOCAL_WORKTREE]: [makeTab({ id: 'local-tab', worktreeId: LOCAL_WORKTREE })]
}
})
return store
}
const closeTerminalSurface = vi.fn()
Object.assign(mockApi, { session: { closeTerminalSurface } })
describe('closeTab close-record mirror', () => {
beforeEach(() => {
globalThis.window = mockWindow
})
beforeEach(() => {
vi.clearAllMocks()
layoutApplyTestTransport.reset()
closeTerminalSurface.mockResolvedValue(undefined)
mockApi.worktrees.updateMeta.mockResolvedValue({})
})
// Cover every workspace kind: close mirror suppresses re-seeding before main’s saved copy hydrates.
it.each([
['remote-tab', REMOTE_WORKTREE],
['local-tab', LOCAL_WORKTREE]
])('mirrors the record main is told to write for %s', (tabId, worktreeId) => {
const store = storeWithBothWorktrees()
store.getState().closeTab(tabId)
expect(store.getState().closedTerminalTabTombstonesByTabId[tabId]).toEqual({
closedAt: expect.any(Number),
worktreeId,
reason: 'user'
})
expect(sentCloses(closeTerminalSurface)).toEqual([{ worktreeId, tabId, reason: 'user' }])
})
it('mirrors a cleanup close with its reason', () => {
const store = storeWithBothWorktrees()
store.getState().closeTab('remote-tab', { reason: 'cleanup' })
expect(store.getState().closedTerminalTabTombstonesByTabId['remote-tab']?.reason).toBe(
'cleanup'
)
})
// Main alone closes for a process exit; a paired host owns its own records.
it.each([
['a pty-exit close', { reason: 'pty-exit' as const }],
['a host-owned close', { remoteCloseOwnedByHost: true }]
])('mirrors nothing for %s', (_label, opts) => {
const store = storeWithBothWorktrees()
store.getState().closeTab('remote-tab', opts)
expect(store.getState().closedTerminalTabTombstonesByTabId).toEqual({})
expect(sentCloses(closeTerminalSurface)).toEqual([])
})
})
}
@@ -1,209 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { AppState } from '../types'
import type { LayoutApplyReply } from '../../../../shared/workspace-layout/workspace-layout-command-types'
import {
_resetLayoutFramePositionForTests,
beginLayoutFramePositions,
endLayoutFramePositions,
noteLayoutFramePosition
} from '@/runtime/layout-frame-position'
import {
_resetLayoutOwnerPaneIntentsForTests,
takeTabRowFields
} from '@/runtime/layout-owner-pane-intents'
import { createTestStore, makeTab } from '../slices/store-test-helpers'
import {
createInitialTerminalThroughLayoutOwner,
recordCreatedTerminalTabInLayoutOwner
} from './terminal-tab-create-through-layout-owner'
const mocks = vi.hoisted(() => {
const held: {
pairedServer: string | null
twin: boolean
reply: ((value: LayoutApplyReply) => void) | null
fail: ((error: Error) => void) | null
} = { pairedServer: null, twin: false, reply: null, fail: null }
return Object.assign(held, { callRuntimeRpc: vi.fn() })
})
vi.mock('@/runtime/runtime-rpc-client', () => ({ callRuntimeRpc: mocks.callRuntimeRpc }))
vi.mock('@/lib/resolve-owner', () => ({
runtimeEnvironmentIdUnlessTwin: () => mocks.pairedServer,
runtimeEnvironmentIdForWorkspaceOwner: () =>
mocks.twin ? { kind: 'ambiguous' } : { kind: 'resolved', environmentId: null }
}))
const WORKTREE = 'repo1::/path/wt'
function stateWith(tabIds: string[] = [], activeWorktreeId = WORKTREE): AppState {
const store = createTestStore()
store.setState({
activeWorktreeId,
tabsByWorktree: { [WORKTREE]: tabIds.map((id) => makeTab({ id, worktreeId: WORKTREE })) }
})
return store.getState()
}
function start(state: () => AppState = () => stateWith()) {
const create = vi.fn()
const routed = createInitialTerminalThroughLayoutOwner(state, WORKTREE, create)
const command = mocks.callRuntimeRpc.mock.calls[0]?.[2]?.command
return { create, routed, command }
}
const settle = () => new Promise((resolve) => setTimeout(resolve, 0))
beforeEach(() => {
_resetLayoutOwnerPaneIntentsForTests()
endLayoutFramePositions()
mocks.pairedServer = null
mocks.twin = false
mocks.callRuntimeRpc.mockReset()
mocks.callRuntimeRpc.mockImplementation(
() =>
new Promise<LayoutApplyReply>((resolve, reject) => {
mocks.reply = resolve
mocks.fail = reject
})
)
})
describe('createInitialTerminalThroughLayoutOwner', () => {
it('asks main for the first tab and leaves drawing it, with the window’s row fields, to main’s frame', async () => {
const { create, routed, command } = start()
expect(routed).toBe(true)
expect(command).toMatchObject({
type: 'createTerminalTab',
workspace: WORKTREE,
initial: 'activation'
})
noteLayoutFramePosition(1)
mocks.reply?.({ ok: true, result: { tabId: command.tabId, leafId: command.leafId } })
await settle()
expect(create).not.toHaveBeenCalled()
// Kept for the frame, however late an owed write lets it in.
expect(takeTabRowFields(command.tabId)).toEqual({ pendingActivationSpawn: true })
})
it('draws nothing itself when no frame is coming: the reopened stream’s snapshot brings it', async () => {
const { create, command } = start()
mocks.reply?.({ ok: true, result: { tabId: command.tabId, leafId: command.leafId } })
await settle()
expect(create).not.toHaveBeenCalled()
expect(takeTabRowFields(command.tabId)).toEqual({ pendingActivationSpawn: true })
})
it('draws nothing itself when the stream ends before main’s frame', async () => {
_resetLayoutFramePositionForTests()
beginLayoutFramePositions()
const { create, command } = start()
mocks.reply?.({
ok: true,
result: { tabId: command.tabId, leafId: command.leafId },
frameSeq: 1
})
await settle()
endLayoutFramePositions()
expect(create).not.toHaveBeenCalled()
expect(takeTabRowFields(command.tabId)).toEqual({ pendingActivationSpawn: true })
})
it('draws nothing itself when a frame showed the tab before the stream ended', async () => {
let state = stateWith()
const { create, command } = start(() => state)
state = stateWith([command.tabId])
mocks.reply?.({ ok: true, result: { tabId: command.tabId, leafId: command.leafId } })
await settle()
expect(create).not.toHaveBeenCalled()
})
it('draws nothing when main’s copy already has a tab there', async () => {
const { create, command } = start()
mocks.reply?.({ ok: true, result: {} })
await settle()
expect(create).not.toHaveBeenCalled()
expect(takeTabRowFields(command.tabId)).toBeNull()
})
it('opens the terminal as today when main holds no partition for the workspace, or fails', async () => {
const unowned = start()
mocks.reply?.({ ok: false, code: 'host_not_owned' })
await settle()
expect(unowned.create).toHaveBeenCalledWith()
mocks.callRuntimeRpc.mockClear()
const failed = start()
mocks.fail?.(new Error('layout_store_unavailable'))
await settle()
expect(failed.create).toHaveBeenCalledWith()
expect(takeTabRowFields(failed.command.tabId)).toBeNull()
})
it('opens nothing as today once the user has left the workspace', async () => {
let state = stateWith()
const { create } = start(() => state)
state = stateWith([], 'repo1::/path/other')
mocks.reply?.({ ok: false, code: 'host_not_owned' })
await settle()
expect(create).not.toHaveBeenCalled()
})
it('opens nothing as today once the workspace got a tab meanwhile', async () => {
let state = stateWith()
const { create } = start(() => state)
state = stateWith(['tab-opened-meanwhile'])
mocks.fail?.(new Error('layout_store_unavailable'))
await settle()
expect(create).not.toHaveBeenCalled()
})
it('leaves a paired server’s workspace to the caller', () => {
mocks.pairedServer = 'env-1'
const { routed } = start()
expect(routed).toBe(false)
expect(mocks.callRuntimeRpc).not.toHaveBeenCalled()
})
})
describe('recordCreatedTerminalTabInLayoutOwner', () => {
const command = {
type: 'createTerminalTab' as const,
workspace: WORKTREE,
tabId: 'tab-drawn',
leafId: 'leaf-drawn'
}
it.each([
[false, 1],
[true, 0]
])(
'on host_not_owned (twin: %s), closes the drawn tab unless a paired server may hold it',
async (twin, closes) => {
vi.spyOn(console, 'warn').mockImplementation(() => {})
mocks.twin = twin
const state = stateWith(['tab-drawn'])
const closeTab = vi.fn()
recordCreatedTerminalTabInLayoutOwner({ ...state, closeTab }, command)
mocks.reply?.({ ok: false, code: 'host_not_owned' })
await settle()
expect(closeTab).toHaveBeenCalledTimes(closes)
}
)
})
@@ -26,15 +26,4 @@ describe('createTab with a mirrored host tab id', () => {
store.getState().createTab(WT, undefined, undefined, { id: MIRRORED, mirroredHostTab: true })
).toThrow(/already exists/)
})
it('still ignores a mirrored id from a caller that did not opt in', () => {
const tab = seeded().getState().createTab(WT, undefined, undefined, { id: MIRRORED })
expect(tab.id).not.toBe(MIRRORED)
})
it('ignores an opted-in id that is not a mirror of a valid host tab id', () => {
const tab = seeded().getState().createTab(WT, undefined, undefined, {
id: 'plain-tab',
mirroredHostTab: true
})
expect(tab.id).not.toBe('plain-tab')
})
})
@@ -1,75 +0,0 @@
import { describe, expect, it } from 'vitest'
import type { AppState } from '../types'
import {
getRemoteConnectionIdForWorktree,
worktreeUsesRemoteConnection,
worktreeUsesWslPath
} from './terminal-workspace-routing'
import { rightSidebarShowsPullRequestData } from '@/lib/right-sidebar-visibility'
const REPO_COUNT = 10
const WORKTREE_COUNT = 400
/** Counts every `id` read so a rescan shows up as a multiple of the row count. */
function buildCountingState(): {
state: AppState
reads: () => number
worktreeId: string
} {
let idReads = 0
const repos = Array.from({ length: REPO_COUNT }, (_, index) => ({
id: `repo-${index}`,
name: `repo-${index}`,
path: `/repos/repo-${index}`,
connectionId: null
}))
const worktreesByRepo: Record<string, unknown[]> = {}
const perRepo = WORKTREE_COUNT / REPO_COUNT
for (let repoIndex = 0; repoIndex < REPO_COUNT; repoIndex++) {
worktreesByRepo[`repo-${repoIndex}`] = Array.from({ length: perRepo }, (_, index) => {
const id = `repo-${repoIndex}::/repos/repo-${repoIndex}/wt-${index}`
return {
get id() {
idReads++
return id
},
repoId: `repo-${repoIndex}`,
path: `/repos/repo-${repoIndex}/wt-${index}`,
branch: `branch-${index}`,
hostId: 'local'
}
})
}
return {
state: {
repos,
worktreesByRepo,
folderWorkspaces: [],
projectGroups: [],
activeView: 'worktrees',
activeWorktreeId: 'repo-9::/repos/repo-9/wt-39',
rightSidebarOpen: true,
rightSidebarTab: 'checks'
} as unknown as AppState,
reads: () => idReads,
worktreeId: 'repo-9::/repos/repo-9/wt-39'
}
}
describe('terminal workspace routing scales with tab count, not workspace count', () => {
it('answers repeated owner lookups without rescanning every worktree', () => {
const { state, reads, worktreeId } = buildCountingState()
const CALLS = 200
for (let call = 0; call < CALLS; call++) {
worktreeUsesRemoteConnection(state, worktreeId)
getRemoteConnectionIdForWorktree(state, worktreeId)
worktreeUsesWslPath(state, worktreeId)
rightSidebarShowsPullRequestData(state)
}
// One index build plus O(1) hits; rescanning costs CALLS × WORKTREE_COUNT ID reads.
expect(reads()).toBeLessThanOrEqual(WORKTREE_COUNT * 2)
expect(reads()).toBeLessThan(CALLS * WORKTREE_COUNT)
})
})
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import type { Repo } from '../../../../shared/repo-types'
import type { Worktree } from '../../../../shared/worktree/types'
import { DEFAULT_REPO_BADGE_COLOR } from '../../../../shared/constants'
import { buildRuntimeSessionPlaceholders } from './workspace-terminal-placeholders'
import { addHydratedSshWorktreePlaceholders } from './workspace-terminal-ssh-placeholders'
@@ -16,131 +16,10 @@ const repo: Repo = {
executionHostId: 'local'
}
const worktree: Worktree = {
id: 'repo-1::/repos/one',
repoId: 'repo-1',
hostId: 'local',
displayName: 'main',
comment: '',
linkedIssue: null,
linkedPR: null,
linkedLinearIssue: null,
linkedGitLabMR: null,
linkedGitLabIssue: null,
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0,
path: '/repos/one',
head: '',
branch: '',
isBare: false,
isMainWorktree: true
}
describe('buildRuntimeSessionPlaceholders', () => {
it('returns the original repos array when no placeholder repo is needed', () => {
const repos = [repo]
const worktreesByRepo = { 'repo-1': [worktree] }
const result = buildRuntimeSessionPlaceholders({
repos,
runtimeHostIdByWorkspaceSessionKey: {},
worktreesByRepo
})
// Hydration writes these straight to the store; a fresh array would rerender
// every component selecting the whole repo list for no data change.
expect(result.repos).toBe(repos)
expect(result.worktreesByRepo).toBe(worktreesByRepo)
})
it('keeps the original repos array when the session only references known repos', () => {
const repos = [repo]
const result = buildRuntimeSessionPlaceholders({
repos,
runtimeHostIdByWorkspaceSessionKey: { 'repo-1::/repos/one': 'runtime:host-1' },
worktreesByRepo: { 'repo-1': [worktree] }
})
expect(result.repos).toBe(repos)
})
it('still appends a placeholder repo for an unknown runtime workspace', () => {
const repos = [repo]
const result = buildRuntimeSessionPlaceholders({
repos,
runtimeHostIdByWorkspaceSessionKey: { 'repo-2::/repos/two': 'runtime:host-1' },
worktreesByRepo: { 'repo-1': [worktree] }
})
expect(result.repos).not.toBe(repos)
expect(result.repos.map((entry) => entry.id)).toEqual(['repo-1', 'repo-2'])
expect(repos).toHaveLength(1)
})
})
describe('addHydratedSshWorktreePlaceholders', () => {
it('returns the original map when no SSH placeholder is needed', () => {
const worktreesByRepo = { 'repo-1': [worktree] }
const result = addHydratedSshWorktreePlaceholders([repo], worktreesByRepo, {
'repo-1::/repos/one': []
})
expect(result).toBe(worktreesByRepo)
})
it('returns the original map when the SSH worktree is already present', () => {
const sshRepo: Repo = { ...repo, id: 'ssh-repo', connectionId: 'ssh-1' }
const sshWorktree: Worktree = { ...worktree, id: 'ssh-repo::/repos/ssh', repoId: 'ssh-repo' }
const worktreesByRepo = { 'ssh-repo': [sshWorktree] }
const result = addHydratedSshWorktreePlaceholders([sshRepo], worktreesByRepo, {
'ssh-repo::/repos/ssh': []
})
expect(result).toBe(worktreesByRepo)
})
it('still adds a placeholder for an SSH worktree with no row, without mutating the caller', () => {
const sshRepo: Repo = { ...repo, id: 'ssh-repo', connectionId: 'ssh-1' }
const worktreesByRepo = { 'ssh-repo': [] as Worktree[] }
const result = addHydratedSshWorktreePlaceholders([sshRepo], worktreesByRepo, {
'ssh-repo::/repos/ssh': []
})
expect(result).not.toBe(worktreesByRepo)
expect(result['ssh-repo'].map((entry) => entry.id)).toEqual(['ssh-repo::/repos/ssh'])
expect(worktreesByRepo['ssh-repo']).toHaveLength(0)
})
const placeholderFor = (repos: Repo[]) =>
addHydratedSshWorktreePlaceholders(repos, {}, { 'ssh-repo::/repos/ssh': [] })['ssh-repo'][0]
it('stamps a direct SSH placeholder with its target, as a listing would', () => {
const sshRepo: Repo = { ...repo, id: 'ssh-repo', connectionId: 'box', executionHostId: null }
expect(placeholderFor([sshRepo])).toMatchObject({ hostId: 'ssh:box' })
expect(placeholderFor([sshRepo]).runtimeOwnerEnvironmentId).toBeUndefined()
})
it('stamps a server-nested SSH placeholder with both the target and the server', () => {
const hubRepo: Repo = {
...repo,
id: 'ssh-repo',
connectionId: 'box',
executionHostId: 'runtime:hub'
}
expect(placeholderFor([hubRepo])).toMatchObject({
hostId: 'ssh:box',
runtimeOwnerEnvironmentId: 'hub'
})
})
it('routes a restored, selected server-nested SSH tab through its server, not a direct dial', () => {
const hubRepo: Repo = {
...repo,
@@ -165,14 +44,6 @@ describe('addHydratedSshWorktreePlaceholders', () => {
owner: { endpoint: { kind: 'environment', environmentId: 'hub' }, at: 'ssh:box' }
})
})
it('leaves the placeholder unstamped when the repo id lives on several hosts', () => {
const local: Repo = { ...repo, id: 'ssh-repo', connectionId: null, executionHostId: 'local' }
const hub: Repo = { ...local, connectionId: 'box', executionHostId: 'runtime:hub' }
const placeholder = placeholderFor([local, hub])
expect(placeholder.hostId).toBeUndefined()
expect(placeholder.runtimeOwnerEnvironmentId).toBeUndefined()
})
})
describe('buildRuntimeSessionPlaceholders for a server repo that nests SSH', () => {
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import type { PublishedWorkspaceLayout } from '../../../shared/workspace-layout/workspace-layout-published'
import { applyLayoutFrame, EMPTY_WORKSPACE_LAYOUT_CACHE } from './workspace-layout-cache'
import { applyLayoutFrame } from './workspace-layout-cache'
function layout(worktreeId: string, tabIds: string[] = []): PublishedWorkspaceLayout {
return {
@@ -13,31 +13,6 @@ function layout(worktreeId: string, tabIds: string[] = []): PublishedWorkspaceLa
}
describe('applyLayoutFrame', () => {
it('a workspace frame replaces one workspace and leaves the rest untouched', () => {
const b = layout('b')
const cache = { a: layout('a'), b }
const next = applyLayoutFrame(cache, {
type: 'workspace',
key: 'a',
layout: layout('a', ['t1'])
})
expect(next).toEqual({ a: layout('a', ['t1']), b })
expect(next.b).toBe(b)
expect(cache).toEqual({ a: layout('a'), b })
expect(
applyLayoutFrame(EMPTY_WORKSPACE_LAYOUT_CACHE, { type: 'workspace', key: 'c', layout: b })
).toEqual({ c: b })
})
it('a removed frame drops one workspace; an absent one keeps the cache', () => {
const b = layout('b')
const cache = { a: layout('a'), b }
const next = applyLayoutFrame(cache, { type: 'removed', key: 'a' })
expect(next).toEqual({ b })
expect(next.b).toBe(b)
expect(applyLayoutFrame(next, { type: 'removed', key: 'a' })).toBe(next)
})
it('a snapshot replaces the whole cache: absent workspaces go, nothing is merged', () => {
const b = layout('b')
const cache = { a: layout('a', ['t1', 't2']), b, gone: layout('gone') }
@@ -53,22 +28,4 @@ describe('applyLayoutFrame', () => {
// An equal entry keeps its object, so its readers do not re-render.
expect(next.b).toBe(b)
})
it('a snapshot equal by value keeps the cache object', () => {
const cache = { a: layout('a', ['t1']), b: layout('b') }
const equal = applyLayoutFrame(cache, {
type: 'snapshot',
workspaces: [
{ key: 'a', layout: layout('a', ['t1']) },
{ key: 'b', layout: layout('b') }
]
})
expect(equal).toBe(cache)
const subset = applyLayoutFrame(cache, {
type: 'snapshot',
workspaces: [{ key: 'a', layout: layout('a', ['t1']) }]
})
expect(subset).toEqual({ a: cache.a })
expect(subset.a).toBe(cache.a)
})
})
@@ -82,14 +82,6 @@ function withoutTab(frame: PublishedWorkspaceLayout, tabId: string): PublishedWo
}
describe('laying frame slices over the store', () => {
it('drops a terminal tab main closed, with its pane layout', async () => {
const { state, session, frame } = await restored()
const patch = patchFor(state, session, withoutTab(frame, 'tab-shell'))
expect(patch.tabsByWorktree?.[GIT_KEY]?.map((tab) => tab.id)).not.toContain('tab-shell')
expect(patch.terminalLayoutsByTabId).not.toHaveProperty('tab-shell')
expect(patch.unifiedTabsByWorktree?.[GIT_KEY]?.map((tab) => tab.id)).not.toContain('tab-shell')
})
it('keeps the pane layout of a tab the window’s own pane command holds, until main drops the tab', async () => {
const { state, session, frame } = await restored()
const newRatios: PublishedWorkspaceLayout = {
@@ -109,33 +101,6 @@ describe('laying frame slices over the store', () => {
).not.toHaveProperty('tab-shell')
})
it('keeps an open file’s editor state and place, and drops a file main closed', async () => {
const { state, session, frame } = await restored()
const file = state.openFiles.find((entry) => entry.worktreeId === GIT_KEY)!
const edited = { ...state, openFiles: [{ ...file, isDirty: true }] }
expect(patchFor(edited, session, frame).openFiles).toBeUndefined()
const closed = patchFor(edited, session, {
...withoutTab(frame, 'editor:src/app.ts'),
editorFiles: []
})
expect(closed.openFiles).toEqual([])
})
it('keeps a browser tab’s page state, and drops the pages of a tab main closed', async () => {
const { state, session, frame } = await restored()
const browser = state.browserTabsByWorktree[GIT_KEY]![0]!
const browsing = {
...state,
browserTabsByWorktree: { [GIT_KEY]: [{ ...browser, url: 'https://example.com/next' }] }
}
expect(patchFor(browsing, session, frame).browserTabsByWorktree).toBeUndefined()
const closed = patchFor(browsing, session, {
...withoutTab(frame, 'browser-1'),
browserTabs: []
})
expect(closed.browserPagesByWorkspace).not.toHaveProperty('browser-1')
})
it('keeps a client-hosted browser tab this desktop created, with its page and handle, at its place (L274)', async () => {
const { state, session, frame } = await restored()
const page = state.browserPagesByWorkspace['browser-1']![0]!
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { hostWorktreeTarget } from '../../../shared/worktree/host-qualified-identity'
import { folderWorkspaceKey } from '../../../shared/workspace-scope'
import '../../../shared/workspace-scope'
import type { AppState } from './types'
import { makeFolderWorkspace, makeWorktree } from './slices/worktrees-slice-test-fixtures'
import { selectWorkspaceByHostTarget } from './workspace-record-selectors'
@@ -51,25 +51,4 @@ describe('selectWorkspaceByHostTarget', () => {
)
).toBe(hubRow)
})
it('matches a folder workspace on its host only', () => {
const state = makeState()
const key = folderWorkspaceKey('folder-1')
expect(
selectWorkspaceByHostTarget(state, hostWorktreeTarget({ id: key, hostId: 'local' }))?.path
).toBe(folder.folderPath)
expect(
selectWorkspaceByHostTarget(state, hostWorktreeTarget({ id: key, hostId: 'ssh:build-box' }))
).toBe(undefined)
})
})
describe('selectWorkspaceByHostTarget bucket keys', () => {
it('finds a row filed under a bucket that is not its repo id', () => {
const row = makeWorktree({ id: 'wt-9', repoId: 'repo-9', hostId: 'ssh:remote' })
const state = makeState({ worktreesByRepo: { remote: [row] } })
expect(
selectWorkspaceByHostTarget(state, hostWorktreeTarget({ id: 'wt-9', hostId: 'ssh:remote' }))
).toBe(row)
})
})
@@ -3,10 +3,7 @@ import type { DiffComment } from '../../../shared/diff-comment-types'
import type { AppState } from './types'
import { makeWorktree } from './slices/worktrees-slice-test-fixtures'
import {
selectWorktreeDiffComments,
selectWorktreeDiffCommentsOrEmpty
} from './worktree-diff-comments-selector'
import './worktree-diff-comments-selector'
import { createDiffCommentViewSelector, sourceParts } from './diff-comment-owner'
const TARGET = 'repo-9::/w/target'
@@ -94,23 +91,3 @@ describe('note owner view selectors', () => {
expect(large.catalogWriteReads).toBeLessThanOrEqual(8 * 1000 + 9 * 1000)
})
})
describe('selectWorktreeDiffComments', () => {
it('reads a replacement worktree snapshot and handles an absent id', () => {
const firstComments = [makeComment('comment-1')]
const nextComments = [makeComment('comment-2')]
const first = {
'repo-9': [makeWorktree({ id: TARGET, repoId: 'repo-9', diffComments: firstComments })]
}
const next = {
'repo-9': [makeWorktree({ id: TARGET, repoId: 'repo-9', diffComments: nextComments })]
}
expect(selectWorktreeDiffComments({ worktreesByRepo: first }, TARGET)).toBe(firstComments)
expect(selectWorktreeDiffComments({ worktreesByRepo: next }, TARGET)).toBe(nextComments)
expect(selectWorktreeDiffComments({ worktreesByRepo: next }, null)).toBeUndefined()
expect(selectWorktreeDiffCommentsOrEmpty({ worktreesByRepo: next }, null)).toBe(
selectWorktreeDiffCommentsOrEmpty({ worktreesByRepo: next }, undefined)
)
})
})
@@ -50,14 +50,6 @@ describe('id-keyed worktree projections keep distinct hosts distinct', () => {
// Last wins: fetchWorktrees replaces, so the later entry is the current one.
expect(all[0]?.head).toBe('fresh')
})
it('treats an unqualified row as its own bucket rather than inventing a host', () => {
const unqualified: Worktree = { ...baseWorktree, displayName: 'unqualified orca' }
const all = getIndexedAllWorktrees(byRepo(unqualified, sshRow))
expect(all).toHaveLength(2)
})
})
describe('getWorktreeOnHostFromState refuses a lookup that still matches two copies', () => {
@@ -1,31 +0,0 @@
import { describe, expect, it } from 'vitest'
import { getRepoOwnerWorktreeVisibilityDefaults } from './worktree-visibility-defaults-by-host'
describe('getRepoOwnerWorktreeVisibilityDefaults', () => {
it('resolves defaults from each repository owner', () => {
const settings = { worktreeVisibilityDefaults: { external: 'hide' as const } }
const defaultsByHost = {
local: { external: 'hide' as const },
'runtime:show-host': { external: 'show' as const },
'runtime:legacy-host': null
}
expect(
getRepoOwnerWorktreeVisibilityDefaults(
{ executionHostId: 'runtime:show-host' },
settings,
defaultsByHost
)
).toEqual({ external: 'show' })
expect(
getRepoOwnerWorktreeVisibilityDefaults({ connectionId: 'ssh-box' }, settings, defaultsByHost)
).toEqual({ external: 'hide' })
expect(
getRepoOwnerWorktreeVisibilityDefaults(
{ executionHostId: 'runtime:legacy-host' },
settings,
defaultsByHost
)
).toBeUndefined()
})
})