diff --git a/config/scripts/ci/sharding/ci-shard-timings.json b/config/scripts/ci/sharding/ci-shard-timings.json index 3fd8fdf26ed..4763dd10065 100644 --- a/config/scripts/ci/sharding/ci-shard-timings.json +++ b/config/scripts/ci/sharding/ci-shard-timings.json @@ -6901,7 +6901,6 @@ "src/renderer/src/store/slices/editor/git/editor-remote-branch-actions.test.ts": 935, "src/renderer/src/store/slices/editor/hydration/editor-session-duplicate-restore.test.ts": 1931, "src/renderer/src/store/slices/editor/focus/editor-set-active-file-guard.test.ts": 895, - "src/renderer/src/store/slices/editor/tabs/editor-state-worktree-purge-leak.test.ts": 1740, "src/renderer/src/store/slices/editor/tabs/editor-tab-layout-owner.test.ts": 1883, "src/renderer/src/store/slices/editor/tabs/editor-tab-placement.test.ts": 1837, "src/renderer/src/store/slices/editor/tabs/editor-tab-shared-project-owner.test.ts": 954, @@ -7014,7 +7013,6 @@ "src/renderer/src/store/slices/tabs/tabs-unread-and-focus.test.ts": 1801, "src/renderer/src/store/slices/tabs/structured-chat-close-owner.test.ts": 1866, "src/renderer/src/store/slices/tabs/tabs-reconciliation-empty-groups.test.ts": 1703, - "src/renderer/src/store/terminals/terminal-input-activity-store-write.test.ts": 1726, "src/renderer/src/store/terminals/terminal-layout-pty-ownership.test.ts": 1734, "src/renderer/src/store/terminals/terminal-pane-detach-agent-identity.test.ts": 1627, "src/renderer/src/store/terminals/terminal-pane-detach-agent-retention.test.ts": 2846, @@ -7078,7 +7076,6 @@ "src/renderer/src/store/slices/worktrees/session/worktrees-terminal-reference-origins.test.ts": 1218, "src/renderer/src/store/slices/worktrees/session/worktrees-terminal-reference-source-race.test.ts": 1063, "src/renderer/src/store/slices/workspace-attention/worktrees-unread-state.test.ts": 1704, - "src/renderer/src/store/slices/worktrees/session/worktrees-workspace-selection-state.test.ts": 1170, "src/renderer/src/store/slices/worktrees/host-scoped-row-merges.test.ts": 1805, "src/renderer/src/store/slices/worktrees/listing/detected-worktree-meta.test.ts": 318, "src/renderer/src/store/slices/worktrees/listing/stale-listing-purges-created-worktree.test.ts": 1828, diff --git a/src/renderer/src/runtime/rpc/subscriptions/runtime-client-events.test.ts b/src/renderer/src/runtime/rpc/subscriptions/runtime-client-events.test.ts index 409dfaaabf6..fd98f924e25 100644 --- a/src/renderer/src/runtime/rpc/subscriptions/runtime-client-events.test.ts +++ b/src/renderer/src/runtime/rpc/subscriptions/runtime-client-events.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it } from 'vitest' import { subscribeRuntimeClientEvents } from './runtime-client-events' import { @@ -34,57 +34,4 @@ describe('subscribeRuntimeClientEvents', () => { expect(observed).toEqual(['error']) }) - - describe('when the server asks this desktop to host its browser pages', () => { - async function ask(environment: Record) { - const { subscribe, respond } = recordRuntimeSubscription({ subscriptionId: 'sub-1' }) - const answered = Promise.withResolvers() - const call = vi.fn(async () => { - answered.resolve() - return { ok: true, result: { accepted: true } } - }) - const prepareBrowserClientHostPlacement = vi - .fn() - .mockResolvedValue({ kind: 'client', browserHostClientId: 'host-a' }) - stubRuntimeEnvironmentBridge({ - subscribe, - call, - prepareBrowserClientHostPlacement, - list: vi.fn().mockResolvedValue([{ id: 'env-1', ...environment }]) - }) - await subscribeRuntimeClientEvents('env-1', vi.fn(), undefined, undefined, { - hostsBrowserPages: true - }) - expect(subscribe.mock.calls[0]?.[0]).toMatchObject({ params: { browserHostRequests: true } }) - respond({ ok: true, result: { type: 'browserHostRequested', requestId: 'request-1' } }) - await answered.promise - return { call, prepareBrowserClientHostPlacement } - } - - it('starts hosting and names the lease it now holds', async () => { - const { call, prepareBrowserClientHostPlacement } = await ask({}) - - expect(prepareBrowserClientHostPlacement).toHaveBeenCalledWith( - expect.objectContaining({ selector: 'env-1', preference: 'auto' }) - ) - expect(call).toHaveBeenCalledWith( - expect.objectContaining({ - method: 'browser.clientHost.requestResult', - params: { requestId: 'request-1', browserHostClientId: 'host-a' } - }) - ) - }) - - // Managed SSH hosts use the desktop browser bridge, so their open-url must not land here. - it('declines for a host that became managed over SSH without starting a lease', async () => { - const { call, prepareBrowserClientHostPlacement } = await ask({ - orcadDeployment: { sshTargetId: 'target-1' } - }) - - expect(prepareBrowserClientHostPlacement).not.toHaveBeenCalled() - expect(call).toHaveBeenCalledWith( - expect.objectContaining({ params: { requestId: 'request-1' } }) - ) - }) - }) }) diff --git a/src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-tabs-sync-editor-ownership.test.ts b/src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-tabs-sync-editor-ownership.test.ts index c19333a3d65..3c8f5bda295 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-tabs-sync-editor-ownership.test.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-tabs-sync-editor-ownership.test.ts @@ -1,7 +1,6 @@ import { beforeEach, describe, expect, it } from 'vitest' import type { RuntimeMobileSessionFileTab } from '../../../../../shared/runtime-mobile-session-tab-contracts' import type { OpenFile } from '../../../store/slices/editor/types/open-file' -import { buildOwnedEditorFileId } from '../../../store/slices/editor/file-ids/editor-file-ids' import { applyWebSessionTabsSnapshot } from '../snapshot-api' import { ENV, @@ -90,7 +89,6 @@ describe('host editor file ownership', () => { expect(patch?.openFiles).toHaveLength(2) expect(patch?.openFiles?.[0]).toBe(other) expect(patch?.openFiles?.[1]).toMatchObject({ - id: buildOwnedEditorFileId(filePath, WT, ENV), runtimeEnvironmentId: ENV }) expect(patch?.openFiles?.[1]?.lastKnownDiskSignature).toBeUndefined() diff --git a/src/renderer/src/store/slices/editor/tabs/editor-state-worktree-purge-leak.test.ts b/src/renderer/src/store/slices/editor/tabs/editor-state-worktree-purge-leak.test.ts deleted file mode 100644 index 3da246c43e4..00000000000 --- a/src/renderer/src/store/slices/editor/tabs/editor-state-worktree-purge-leak.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** Both single/bulk worktree removal must evict editorCursorLine/editorViewMode by file and gitStatusHugeByWorktree; missed keys leak per removed file/worktree for renderer lifetime. */ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import type * as AgentStatusModule from '@/lib/agent-status/aggregation/worktree-agent-status' - -vi.mock('sonner', () => ({ - toast: { info: vi.fn(), success: vi.fn(), error: vi.fn(), warning: vi.fn() } -})) - -vi.mock('@/components/terminal-pane/pty-transport/delivery/pty-dispatcher', () => ({})) - -vi.mock('@/components/terminal-pane/pty-transport/shutdown/pty-shutdown-data-suspension', () => ({ - restorePtyDataHandlersAfterFailedShutdown: vi.fn(), - unregisterPtyDataHandlers: vi.fn() -})) - -vi.mock('@/lib/agent-status/aggregation/worktree-agent-status', async (importOriginal) => { - const actual = await importOriginal() - return { ...actual, detectAgentStatusFromTitle: vi.fn().mockReturnValue(null) } -}) - -const mockApi = { - worktrees: { - remove: vi.fn().mockResolvedValue(undefined), - forceDeletePreservedBranch: vi.fn().mockResolvedValue({ deleted: true }), - updateMeta: vi.fn().mockResolvedValue({}) - }, - pty: { kill: vi.fn().mockResolvedValue(undefined) }, - runtimeEnvironments: { call: vi.fn().mockResolvedValue({ ok: true, result: {} }) } -} - -// @ts-expect-error -- minimal window.api stub for the store under test -globalThis.window = { api: mockApi } - -import { createTestStore, seedStore } from '../../../testing/app-store-test-fixture' -import { makeWorktree } from '../../worktrees/worktree.test-fixture' -import { makeOpenFile } from '../types/open-file.test-fixture' - -const WT = 'repo1::/path/wt1' -const FILE_A = '/path/wt1/a.ts' -const FILE_B = '/path/wt1/b.ts' - -function seedEditorState(store: ReturnType): void { - seedStore(store, { - worktreesByRepo: { - repo1: [makeWorktree({ id: WT, repoId: 'repo1', path: '/path/wt1' })] - }, - openFiles: [ - makeOpenFile({ id: FILE_A, worktreeId: WT }), - makeOpenFile({ id: FILE_B, worktreeId: WT }) - ], - editorCursorLine: { [FILE_A]: 12, [FILE_B]: 4 }, - editorViewMode: { [FILE_A]: 'changes', [FILE_B]: 'edit' }, - gitStatusHugeByWorktree: { [WT]: { limit: 1000 } } - }) -} - -describe('worktree removal purges per-file editor + git-huge state (leak regression)', () => { - beforeEach(() => { - vi.clearAllMocks() - mockApi.worktrees.remove.mockResolvedValue(undefined) - }) - - it('single removeWorktree drops editor + git-huge state for the removed worktree', async () => { - const store = createTestStore() - seedEditorState(store) - - const result = await store.getState().removeWorktree({ id: WT, executionHostId: null }) - expect(result).toEqual({ ok: true }) - - const s = store.getState() - expect(s.editorCursorLine[FILE_A]).toBeUndefined() - expect(s.editorCursorLine[FILE_B]).toBeUndefined() - expect(s.editorViewMode[FILE_A]).toBeUndefined() - expect(s.gitStatusHugeByWorktree[WT]).toBeUndefined() - }) -}) diff --git a/src/renderer/src/store/slices/tabs/tabs-hydration-group-validation.test.ts b/src/renderer/src/store/slices/tabs/tabs-hydration-group-validation.test.ts index 73329650910..9a18c255f8d 100644 --- a/src/renderer/src/store/slices/tabs/tabs-hydration-group-validation.test.ts +++ b/src/renderer/src/store/slices/tabs/tabs-hydration-group-validation.test.ts @@ -57,40 +57,4 @@ describe('buildHydratedTabState referential repair', () => { groups[0].id ]) }) - - it('collapses a persisted layout that repeats one group across leaves', () => { - const result = buildHydratedTabState( - { - ...makeBaseSession(), - unifiedTabs: { w1: [tab('t1', 'g1', 0), tab('t2', 'g2', 1)] }, - tabGroups: { - w1: [ - { id: 'g1', worktreeId: 'w1', activeTabId: 't1', tabOrder: ['t1'] }, - { id: 'g2', worktreeId: 'w1', activeTabId: 't2', tabOrder: ['t2'] } - ] - }, - tabGroupLayouts: { - w1: { - type: 'split', - direction: 'horizontal', - first: { type: 'leaf', groupId: 'g1' }, - second: { - type: 'split', - direction: 'horizontal', - first: { type: 'leaf', groupId: 'g1' }, - second: { - type: 'split', - direction: 'horizontal', - first: { type: 'leaf', groupId: 'g2' }, - second: { type: 'leaf', groupId: 'g1' } - } - } - } - } - }, - new Set(['w1']) - ) - - expect(leafGroupIds(result.layoutByWorktree.w1)).toEqual(['g1', 'g2']) - }) }) diff --git a/src/renderer/src/store/slices/worktrees/create/sparse-presets.test.ts b/src/renderer/src/store/slices/worktrees/create/sparse-presets.test.ts index d737766da9d..08e3ce01c72 100644 --- a/src/renderer/src/store/slices/worktrees/create/sparse-presets.test.ts +++ b/src/renderer/src/store/slices/worktrees/create/sparse-presets.test.ts @@ -75,25 +75,4 @@ describe('createSparsePresetsSlice', () => { ]) expect(store.getState().sparsePresetsLoadStatusByRepo['repo-1']).toBe('loaded') }) - - it('does not save or synthesize a repo bucket when presets fail to load first', async () => { - const store = createTestStore() - const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) - mockApi.sparsePresets.list.mockRejectedValueOnce(new Error('disk failed')) - - try { - const saved = await store.getState().saveSparsePreset({ - repoId: 'repo-1', - name: 'Api', - directories: ['packages/api'] - }) - - expect(saved).toBeNull() - expect(mockApi.sparsePresets.save).not.toHaveBeenCalled() - expect(store.getState().sparsePresetsByRepo['repo-1']).toBeUndefined() - expect(store.getState().sparsePresetsLoadStatusByRepo['repo-1']).toBe('error') - } finally { - consoleError.mockRestore() - } - }) }) diff --git a/src/renderer/src/store/slices/worktrees/session/worktrees-workspace-selection-state.test.ts b/src/renderer/src/store/slices/worktrees/session/worktrees-workspace-selection-state.test.ts deleted file mode 100644 index cecace91afd..00000000000 --- a/src/renderer/src/store/slices/worktrees/session/worktrees-workspace-selection-state.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { AppState } from '../../../types' -import { - registerPersistentWebview, - unregisterPersistentWebview -} from '../../../../components/browser-pane/host-guest/webview-registry' -import '../../../../../../shared/workspace-scope' - -import { createWebview } from '../../browser/browser-webview.test-fixture' -import { makeWorktree } from '../worktree.test-fixture' -import { - createTestStore, - mockApi, - resetRemoteRuntimeMocks, - resetWorktreeSliceModuleMemory -} from '../worktrees-slice-test-harness' - -const requestWorktreeBaseFallbackNotice = vi.hoisted(() => vi.fn()) - -vi.mock('sonner', () => ({ - toast: { - warning: vi.fn(), - info: vi.fn(), - success: vi.fn(), - error: vi.fn(), - dismiss: vi.fn() - } -})) - -vi.mock('@/components/worktree-base-fallback-notice', () => ({ - requestWorktreeBaseFallbackNotice -})) - -beforeEach(resetWorktreeSliceModuleMemory) - -describe('setActiveWorktree focus handling', () => { - beforeEach(() => { - vi.clearAllMocks() - resetRemoteRuntimeMocks() - mockApi.ephemeralVm.cancelProvision.mockResolvedValue({ cancelled: true }) - mockApi.ephemeralVm.cleanup.mockResolvedValue({}) - }) - - it('moves focus out of a registered webview before switching worktrees', () => { - const store = createTestStore() - const current = makeWorktree({ id: 'repo1::/path/current', repoId: 'repo1' }) - const next = makeWorktree({ id: 'repo1::/path/next', repoId: 'repo1' }) - const webview = createWebview() - const focusRenderer = vi.fn(() => { - expect(store.getState().activeWorktreeId).toBe(current.id) - }) - const previousDocument = Object.getOwnPropertyDescriptor(globalThis, 'document') - const testWindow = globalThis.window as unknown as { focus?: () => void } - const previousFocus = testWindow.focus - - Object.defineProperty(globalThis, 'document', { - configurable: true, - value: { activeElement: webview } - }) - testWindow.focus = focusRenderer - registerPersistentWebview('page-1', webview) - - try { - store.setState({ - worktreesByRepo: { repo1: [current, next] }, - activeWorktreeId: current.id, - reconcileWorktreeTabModel: vi.fn(() => ({ - activeRenderableTabId: null, - renderableTabCount: 0 - })), - refreshGitHubForWorktreeIfStale: vi.fn() - } as unknown as Partial) - - store.getState().setActiveWorktree(next.id) - - expect(webview.blur).toHaveBeenCalledTimes(1) - expect(focusRenderer).toHaveBeenCalledTimes(1) - expect(store.getState().activeWorktreeId).toBe(next.id) - } finally { - unregisterPersistentWebview('page-1') - if (previousDocument) { - Object.defineProperty(globalThis, 'document', previousDocument) - } else { - delete (globalThis as unknown as { document?: unknown }).document - } - if (previousFocus) { - testWindow.focus = previousFocus - } else { - delete testWindow.focus - } - } - }) -}) diff --git a/src/renderer/src/store/terminals/runtime-pane-title-sort-epoch.test.ts b/src/renderer/src/store/terminals/runtime-pane-title-sort-epoch.test.ts index 94d82c90782..16c7fae2e04 100644 --- a/src/renderer/src/store/terminals/runtime-pane-title-sort-epoch.test.ts +++ b/src/renderer/src/store/terminals/runtime-pane-title-sort-epoch.test.ts @@ -44,46 +44,4 @@ describe('runtimePaneTitle → sortEpoch', () => { expect(store.getState().tabsByWorktree['wt-bg']?.[0]?.title).toBe('⠂ Claude Code') expect(store.getState().runtimePaneTitlesByTabId['tab-1']?.[1]).toBe('⠂ Claude Code') }) - - it('updates titles in their corresponding worktrees', () => { - const store = createTestStore() - const tabCount = 2 - const worktrees = Array.from({ length: tabCount }, (_, index) => - makeWorktree({ id: `wt-${index}`, repoId: 'repo1', path: `/path/wt-${index}` }) - ) - seedStore(store, { - worktreesByRepo: { repo1: worktrees }, - tabsByWorktree: Object.fromEntries( - worktrees.map((worktree, index) => [ - worktree.id, - [makeTab({ id: `tab-${index}`, worktreeId: worktree.id })] - ]) - ) - }) - store.getState().updateTabTitles( - worktrees.map((_, index) => ({ - tabId: `tab-${index}`, - title: `Codex ready ${index}` - })) - ) - - expect(store.getState().tabsByWorktree['wt-0']?.[0]?.title).toBe('Codex ready 0') - expect(store.getState().tabsByWorktree['wt-1']?.[0]?.title).toBe('Codex ready 1') - }) - - it('clears an unclassified runtime pane title', () => { - const store = createTestStore() - seedStore(store, { - worktreesByRepo: { - repo1: [makeWorktree({ id: 'wt-bg', repoId: 'repo1', path: '/path/wt-bg' })] - }, - tabsByWorktree: { - 'wt-bg': [makeTab({ id: 'tab-1', worktreeId: 'wt-bg' })] - } - }) - store.getState().setRuntimePaneTitle('tab-1', 1, 'shell prompt') - store.getState().clearRuntimePaneTitle('tab-1', 1) - - expect(store.getState().runtimePaneTitlesByTabId['tab-1']).toBeUndefined() - }) }) diff --git a/src/renderer/src/store/terminals/terminal-input-activity-store-write.test.ts b/src/renderer/src/store/terminals/terminal-input-activity-store-write.test.ts deleted file mode 100644 index 276e79f5519..00000000000 --- a/src/renderer/src/store/terminals/terminal-input-activity-store-write.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * `recordTerminalInput` used to run one `set()` per keystroke, waking every zustand - * subscriber in the store. It is now coalesced: leading-edge write, then one trailing - * flush per window. These tests pin the properties that make that safe. - */ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as AgentStatusModule from '@/lib/agent-status/aggregation/worktree-agent-status' - -vi.mock('sonner', () => ({ - toast: { info: vi.fn(), success: vi.fn(), error: vi.fn(), warning: vi.fn() } -})) - -vi.mock('@/components/terminal-pane/pty-transport/delivery/pty-dispatcher', () => ({})) - -vi.mock('@/components/terminal-pane/pty-transport/shutdown/pty-shutdown-data-suspension', () => ({ - restorePtyDataHandlersAfterFailedShutdown: vi.fn(), - unregisterPtyDataHandlers: vi.fn() -})) - -vi.mock('@/lib/agent-status/aggregation/worktree-agent-status', async (importOriginal) => { - const actual = await importOriginal() - return { ...actual, detectAgentStatusFromTitle: vi.fn().mockReturnValue(null) } -}) - -// @ts-expect-error -- minimal window.api stub for the store under test -globalThis.window = { api: { pty: { kill: vi.fn().mockResolvedValue(undefined) } } } - -import { - TERMINAL_INPUT_ACTIVITY_WRITE_INTERVAL_MS, - flushTerminalInputActivity, - mergePendingTerminalInputActivity, - readLastTerminalInputAt, - resetTerminalInputActivityCoalescingForTests -} from '@/lib/terminal-input-activity-coalescing' -import { createTestStore } from '../testing/app-store-test-fixture' - -const PANE = 'tab-1:leaf-a' - -describe('recordTerminalInput store writes are coalesced', () => { - beforeEach(() => { - vi.useFakeTimers() - resetTerminalInputActivityCoalescingForTests() - }) - - it('keeps the freshest stamp visible to imperative readers before the flush', () => { - const store = createTestStore() - - store.getState().recordTerminalInput(PANE, 1_000) - store.getState().recordTerminalInput(PANE, 1_200) - - const stored = store.getState().lastTerminalInputAtByPaneKey - expect(stored[PANE]).toBe(1_000) - expect(readLastTerminalInputAt(stored, PANE)).toBe(1_200) - expect(mergePendingTerminalInputActivity(stored)[PANE]).toBe(1_200) - - vi.advanceTimersByTime(TERMINAL_INPUT_ACTIVITY_WRITE_INTERVAL_MS + 1) - expect(store.getState().lastTerminalInputAtByPaneKey[PANE]).toBe(1_200) - }) - - it('does not resurrect a pane key a purge deleted', () => { - const store = createTestStore() - - store.getState().recordTerminalInput(PANE, 1_000) - store.getState().recordTerminalInput(PANE, 1_200) - - // Teardown path (worktree purge / pane close) rewrites the map without the key. - store.setState({ lastTerminalInputAtByPaneKey: {} }) - flushTerminalInputActivity() - - const stored = store.getState().lastTerminalInputAtByPaneKey - expect(stored[PANE]).toBeUndefined() - expect(readLastTerminalInputAt(stored, PANE)).toBeUndefined() - expect(mergePendingTerminalInputActivity(stored)[PANE]).toBeUndefined() - }) -})