test: remove 11 low-value suites and 157 individual cases (#36703)

* test: remove low-value suites and cases

* test: preserve historical reliability gate evidence
This commit is contained in:
OrcaWin
2026-10-31 15:27:20 +00:00
committed by GitHub
parent 2487a258fe
commit fe4595d6d6
82 changed files with 43 additions and 4044 deletions
-5
View File
@@ -27,7 +27,6 @@
1 test-no-in-test-import-actual:src/main/local-worktree-removal-recovery.test.ts
2 test-no-in-test-import-actual:src/main/managed-server/activation/orcad-activation-crash-recovery.test.ts
1 test-no-in-test-import-actual:src/main/managed-server/host-state/orcad-state-mutation-timeout.test.ts
1 test-no-in-test-import-actual:src/main/node-runtime/managed-runtime-installation.test.ts
1 test-no-in-test-import-actual:src/main/node-runtime/pinned-runtime-materializer.test.ts
1 test-no-in-test-import-actual:src/main/omp/omp-session-cwd-lookup.test.ts
6 test-no-in-test-import-actual:src/main/opencode/hook-service-legacy-acl.test.ts
@@ -340,7 +339,6 @@
1 test-no-real-time-wait:src/main/ipc/pty/pty-agent-launch-pane-attach.test.ts
1 test-no-real-time-wait:src/main/ipc/pty/pty-daemon-controller-teardown.test.ts
1 test-no-real-time-wait:src/main/ipc/pty/pty-launch-parity.test.ts
2 test-no-real-time-wait:src/main/ipc/pty/pty-spawn-cwd-fallback-hung-folder.test.ts
1 test-no-real-time-wait:src/main/ipc/rebinding-file-watch-noise.test.ts
1 test-no-real-time-wait:src/main/ipc/rebinding-file-watch.test.ts
3 test-no-real-time-wait:src/main/ipc/runtime-environments/runtime-environment-preference.test.ts
@@ -382,7 +380,6 @@
1 test-no-real-time-wait:src/main/native-chat/transcript-watch-error.test.ts
2 test-no-real-time-wait:src/main/native-chat/transcript-watch-liveness.test.ts
11 test-no-real-time-wait:src/main/native-chat/transcript-watch.test.ts
1 test-no-real-time-wait:src/main/node-runtime/managed-runtime-installation.test.ts
1 test-no-real-time-wait:src/main/opencode/hook-plugin-legacy-tui-ownership.test.ts
6 test-no-real-time-wait:src/main/opencode/hook-plugin-module-contract.test.ts
1 test-no-real-time-wait:src/main/opencode/hook-plugin-opencode2-tui-ownership.test.ts
@@ -497,7 +494,6 @@
1 test-no-real-time-wait:src/renderer/src/components/native-chat/use-structured-agent-session-legacy-recovery.test.tsx
1 test-no-real-time-wait:src/renderer/src/components/native-chat/use-structured-agent-session-options.learned-default.test.tsx
1 test-no-real-time-wait:src/renderer/src/components/native-chat/use-structured-agent-session-read.test.tsx
1 test-no-real-time-wait:src/renderer/src/components/repo/CreateFromPicker.test.tsx
1 test-no-real-time-wait:src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-generation.test.tsx
1 test-no-real-time-wait:src/renderer/src/components/right-sidebar/file-explorer-watch-refresh-scheduler.test.ts
1 test-no-real-time-wait:src/renderer/src/components/right-sidebar/parent-pr-checks-refresh.test.ts
@@ -1107,7 +1103,6 @@
5 test-no-wall-clock:src/renderer/src/components/status-bar/inline-usage-bars.test.tsx
4 test-no-wall-clock:src/renderer/src/components/status-bar/provider-segment-balance.test.tsx
2 test-no-wall-clock:src/renderer/src/components/status-bar/status-bar-antigravity-segment.test.tsx
3 test-no-wall-clock:src/renderer/src/components/status-bar/status-bar-provider-menu-focus.test.tsx
1 test-no-wall-clock:src/renderer/src/components/status-bar/workspace-space-worktree-row.test.tsx
1 test-no-wall-clock:src/renderer/src/components/tab-bar/TabBarCreateEntry.history.test.tsx
2 test-no-wall-clock:src/renderer/src/components/tab-bar/cli-chat-resume-run.test.ts
+6 -13
View File
@@ -9051,17 +9051,16 @@
"providers": ["local", "daemon", "ssh", "wsl", "remote-runtime", "mobile-relay"],
"coveredPlatforms": ["macos"],
"coveredProviders": ["local", "daemon", "ssh"],
"coverageNotes": "Local macOS deterministic evidence covers the renderer snapshot/coordinator, exact local and SSH-shaped PTY request settlement, active-last and pinned terminal-tab routing, component-unmount continuation, and the existing current/legacy daemon management contract. Windows Electron process absence, live SSH/WSL behavior, and remote-runtime/mobile host completion remain explicit gaps.",
"coverageNotes": "Local macOS deterministic evidence covers the renderer snapshot/coordinator, exact local and SSH-shaped PTY request settlement, active-last and pinned terminal-tab routing, component-unmount continuation, and the existing current/legacy daemon management contract. Windows Electron process absence, live SSH/WSL behavior, and remote-runtime/mobile host completion remain explicit gaps. The audit removed the isolated terminal-tab close routing suite. Forced-pin close-call shape, editor-before-browser fallback, browser fallback, empty-worktree deactivation and no replacement-terminal routing lose their supplied-store spy assertions. Remaining lifecycle/coordinator suites are not claimed to reproduce those isolated routing alarms.",
"motivatingLinks": ["https://github.com/stablyai/orca/issues/8001"],
"invariant": "Every terminal surface confirmed in the invoking renderer is force-closed exactly once after daemon management settles, later-created surfaces and non-terminal tabs survive, and exact shutdown requests are limited to deduplicated current non-runtime PTY bindings of the confirmed surfaces.",
"oracle": "Snapshot terminal entity IDs before the first await; mutate ownership, active selection, bindings, and tab presence while daemon management is pending and between bounded close batches; then assert only the immutable targets disappear from both terminal stores, active targets close last with valid editor/browser/deactivated post-state, every captured exact PTY promise settles before callbacks, and no provider inventory sweep or late-tab kill occurs.",
"commands": [
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/shared/kill-all-terminal-surfaces.test.ts src/renderer/src/components/shared/useDaemonActions.test.tsx src/renderer/src/components/terminal/terminal-tab-actions-kill-all.test.ts src/main/ipc/pty/pty-management.test.ts"
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/shared/kill-all-terminal-surfaces.test.ts src/renderer/src/components/shared/useDaemonActions.test.tsx src/main/ipc/pty/pty-management.test.ts"
],
"testFiles": [
"src/renderer/src/components/shared/kill-all-terminal-surfaces.test.ts",
"src/renderer/src/components/shared/useDaemonActions.test.tsx",
"src/renderer/src/components/terminal/terminal-tab-actions-kill-all.test.ts",
"src/main/ipc/pty/pty-management.test.ts"
],
"assertionRefs": [
@@ -9085,13 +9084,6 @@
"closed terminal tabs report success instead of the no-sessions informational state when daemon management reports zero"
]
},
{
"file": "src/renderer/src/components/terminal/terminal-tab-actions-kill-all.test.ts",
"assertions": [
"force closes pinned terminals without a second confirmation",
"closing the last active terminal preserves and activates editor or browser content, otherwise deactivates without auto-spawn"
]
},
{
"file": "src/main/ipc/pty/pty-management.test.ts",
"assertions": [
@@ -9107,10 +9099,10 @@
"runner": "local",
"platform": "macos",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/shared/kill-all-terminal-surfaces.test.ts src/renderer/src/components/shared/useDaemonActions.test.tsx src/renderer/src/components/terminal/terminal-tab-actions-kill-all.test.ts src/main/ipc/pty-management.test.ts",
"currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/shared/kill-all-terminal-surfaces.test.ts src/renderer/src/components/shared/useDaemonActions.test.tsx src/renderer/src/components/terminal/terminal-tab-actions-kill-all.test.ts src/main/ipc/pty/pty-management.test.ts",
"currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/shared/kill-all-terminal-surfaces.test.ts src/renderer/src/components/shared/useDaemonActions.test.tsx src/main/ipc/pty/pty-management.test.ts",
"result": "passed",
"durationSeconds": 1.18,
"summary": "4 test files and 27 tests passed locally, including the existing daemon handler contract and a real 100-terminal Zustand cleanup fixture."
"summary": "4 test files and 27 tests passed locally, including the existing daemon handler contract and a real 100-terminal Zustand cleanup fixture. Historical four-file record only: its date, result, duration and 27-test count describe the original command. currentCommand maps the three surviving roots; that three-file invocation was not rerun here, and this record does not restore the removed routing alarms."
}
],
"runtimeBudget": {
@@ -9139,7 +9131,8 @@
"Windows Electron screenshots and live process/xterm absence evidence were not produced by this local macOS run.",
"Live SSH and WSL process absence, Linux local/daemon behavior, and mobile/relay shutdown remain unproved.",
"Runtime-host terminal close is best-effort because closeTerminalTab still discards the existing async host result and its close-intent lifetime is shorter than the possible RPC flow.",
"Daemon adapter listing failures remain suppressed by the existing management API, so reported daemon counts are not authoritative verification of every process."
"Daemon adapter listing failures remain suppressed by the existing management API, so reported daemon counts are not authoritative verification of every process.",
"The audit removed the isolated terminal-tab close routing suite. Forced-pin close-call shape, editor-before-browser fallback, browser fallback, empty-worktree deactivation and no replacement-terminal routing lose their supplied-store spy assertions. Remaining lifecycle/coordinator suites are not claimed to reproduce those isolated routing alarms."
],
"demotionRule": "Keep experimental or demote to protection none if the gate flakes, permits a late-created tab or unrelated PTY to close, duplicates provider shutdown, or performs more than one ownership replan per bounded yield."
},
-11
View File
@@ -2654,7 +2654,6 @@
"src/main/github/work-item-search-semantics.test.ts": 473,
"src/main/gitlab/client-mr-auth-rate-limit.test.ts": 355,
"src/main/gitlab/client-mr-branch-lookup.test.ts": 435,
"src/main/gitlab/client-mr-job-ci.test.ts": 420,
"src/main/gitlab/client-mr-listing.test.ts": 388,
"src/main/gitlab/client-mr-review-actions.test.ts": 404,
"src/main/gitlab/client-work-items.test.ts": 539,
@@ -6170,7 +6169,6 @@
"src/main/ssh/orcad-retained-fence-takeover.test.ts": 211,
"src/main/ssh/orcad-retained-source-changes.test.ts": 1858,
"src/main/ssh/orcad-retained-source-lists.test.ts": 566,
"src/main/ssh/orcad-running-version-adoption.test.ts": 564,
"src/main/ssh/orcad-runtime-maintenance.test.ts": 595,
"src/main/ssh/orcad-server-project-groups.test.ts": 18,
"src/main/ssh/orcad-snapshot-room.test.ts": 312,
@@ -7054,9 +7052,7 @@
"src/renderer/src/components/contextual-tours/contextual-tour-floating-position.test.ts": 2004,
"src/renderer/src/components/contextual-tours/contextual-tour-gate.test.ts": 47,
"src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.test.ts": 258,
"src/renderer/src/components/contextual-tours/contextual-tour-step-actions.test.ts": 55,
"src/renderer/src/components/contextual-tours/request-contextual-tour-when-ready.test.ts": 1380,
"src/renderer/src/components/contextual-tours/workspace-creation-tour-handoff.test.ts": 1390,
"src/renderer/src/components/crash-report/crash-report-submit-notice.test.ts": 124,
"src/renderer/src/components/crash-report/use-crash-report-copy.test.tsx": 358,
"src/renderer/src/components/dashboard-popout/AgentKanbanBoard.test.tsx": 740,
@@ -8014,7 +8010,6 @@
"src/renderer/src/components/quick-open-recent-validation.test.ts": 152,
"src/renderer/src/components/quick-open-search.test.ts": 163,
"src/renderer/src/components/quick-open-selection-owner.react.test.tsx": 1686,
"src/renderer/src/components/repo/CreateFromPicker.test.tsx": 642,
"src/renderer/src/components/repo/NestedRepoChecklist.test.tsx": 182,
"src/renderer/src/components/repo/NestedRepoScanLimitNotice.test.ts": 192,
"src/renderer/src/components/repo/repo-icon.test.tsx": 303,
@@ -8774,7 +8769,6 @@
"src/renderer/src/components/sidebar/worktree-list/rows/host-header-detail.test.ts": 128,
"src/renderer/src/components/sidebar/worktree-list/rows/option-dom-host-collision.test.ts": 125,
"src/renderer/src/components/sidebar/worktree-list/rows/project-header-host-context.test.ts": 103,
"src/renderer/src/components/sidebar/worktree-list/rows/use-project-group-dialogs-owner-host.test.tsx": 732,
"src/renderer/src/components/sidebar/worktree-list/viewport/hard-scroll-up.test.ts": 48,
"src/renderer/src/components/sidebar/worktree-list/viewport/scroll-adjustment.test.ts": 1810,
"src/renderer/src/components/sidebar/worktree-list/viewport/sticky-headers.test.ts": 195,
@@ -8876,7 +8870,6 @@
"src/renderer/src/components/status-bar/status-bar-copy-localization.test.tsx": 885,
"src/renderer/src/components/status-bar/status-bar-density.test.ts": 57,
"src/renderer/src/components/status-bar/status-bar-pool-countdown.test.tsx": 389,
"src/renderer/src/components/status-bar/status-bar-provider-menu-focus.test.tsx": 941,
"src/renderer/src/components/status-bar/status-bar-provider-visibility.test.ts": 42,
"src/renderer/src/components/status-bar/status-bar-runtime-groups.test.ts": 237,
"src/renderer/src/components/status-bar/status-bar-usage-collapse.test.ts": 159,
@@ -9599,7 +9592,6 @@
"src/renderer/src/components/terminal-pane/use-terminal-scroll-visibility-memory.test.ts": 56,
"src/renderer/src/components/terminal-pane/use-terminal-tab-cold-parking.test.ts": 461,
"src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts": 333,
"src/renderer/src/components/terminal-pane/useTerminalFontZoom.test.ts": 197,
"src/renderer/src/components/terminal-pane/wrapped-terminal-http-link-limits.test.ts": 166,
"src/renderer/src/components/terminal-pane/wrapped-terminal-http-link-provider.test.ts": 396,
"src/renderer/src/components/terminal-pane/wrapped-terminal-link-ranges.test.ts": 42,
@@ -9636,7 +9628,6 @@
"src/renderer/src/components/terminal/terminal-provider-snapshot-bound-pty-ids.test.ts": 1570,
"src/renderer/src/components/terminal/terminal-provider-snapshot-capability-resettlement.test.ts": 377,
"src/renderer/src/components/terminal/terminal-provider-snapshot-capability.test.ts": 43,
"src/renderer/src/components/terminal/terminal-tab-actions-kill-all.test.ts": 602,
"src/renderer/src/components/terminal/terminal-tab-actions-pinned-server-close.test.ts": 269,
"src/renderer/src/components/terminal/terminal-tab-actions-unified-close.test.ts": 1809,
"src/renderer/src/components/terminal/terminal-tab-actions-unrouted-close.test.ts": 474,
@@ -10273,7 +10264,6 @@
"src/renderer/src/lib/pane-manager/terminal-kitty-file-reader.test.ts": 68,
"src/renderer/src/lib/pane-manager/terminal-ligatures-addon.test.ts": 38,
"src/renderer/src/lib/pane-manager/terminal-link-provider-guard.test.ts": 52,
"src/renderer/src/lib/pane-manager/terminal-linkifier-hover-reset-on-mouseleave.test.ts": 34,
"src/renderer/src/lib/pane-manager/terminal-linkifier-hover-reset-on-write.test.ts": 57,
"src/renderer/src/lib/pane-manager/terminal-mouse-encoding-tracker.test.ts": 71,
"src/renderer/src/lib/pane-manager/terminal-render-pause-release-parked-resize.test.ts": 50,
@@ -11384,7 +11374,6 @@
"src/renderer/src/web/preload-api/web-ui-compact-notice.test.ts": 569,
"src/renderer/src/web/preload-api/web-ui-explorer-root.test.ts": 614,
"src/renderer/src/web/preload-api/web-worktree-attachment-mutations.test.ts": 130,
"src/renderer/src/web/web-clipboard-copy-fallback.test.ts": 34,
"src/renderer/src/web/web-clipboard-copy-terminal-selection.test.ts": 152,
"src/renderer/src/web/web-clipboard-image-presence.test.ts": 933,
"src/renderer/src/web/web-device-key.test.ts": 86,
@@ -107,41 +107,4 @@ describe('the WSL hook relay idle stop', () => {
await vi.advanceTimersByTimeAsync(1_000)
expect(states.has('ubuntu')).toBe(false)
})
it('brings a stopped relay back with its config, and a quit forgets it', async () => {
const state = running()
states.set('ubuntu', state)
idle.update(state)
await vi.advanceTimersByTimeAsync(1_000)
idle.restoreParked('Ubuntu')
expect(states.get('ubuntu')).toMatchObject({
phase: 'failed',
cooldownUntil: 0,
codexHomePath: '/home/u/.codex'
})
states.clear()
const again = running()
states.set('ubuntu', again)
idle.update(again)
await vi.advanceTimersByTimeAsync(1_000)
idle.dispose(true)
idle.restoreParked('Ubuntu')
expect(states.has('ubuntu')).toBe(false)
})
it('never stops a relay that is not running, or one a newer state replaced', async () => {
const starting = { ...running(), phase: 'starting' as const }
states.set('ubuntu', starting)
idle.update(starting)
await vi.advanceTimersByTimeAsync(5_000)
expect(states.get('ubuntu')).toBe(starting)
const old = running()
idle.update(old)
await vi.advanceTimersByTimeAsync(1_000)
expect(states.get('ubuntu')).toBe(starting)
expect(old.child?.kill).not.toHaveBeenCalled()
})
})
@@ -98,19 +98,6 @@ describe('BrowserNetworkTunnelSession stream-scoped failures', () => {
expect(tunnel.sockets[0]!.destroyed).toBe(false)
})
it('fails only the pending stream when the destination ends before connecting', async () => {
const tunnel = createPairedTunnel()
const healthy = await openConnectedStream(tunnel, 'healthy.internal')
const pending = tunnel.client.open({ host: 'never-connects.internal', port: 443 })
tunnel.sockets[1]!.emit('end')
await expect(pending).rejects.toThrow('destination_closed_before_connect')
expect(tunnel.closures).toEqual([])
expect(healthy.destroyed).toBe(false)
expect(tunnel.sockets[0]!.destroyed).toBe(false)
})
it('fails only the receiving stream when destination bytes exhaust the retained budget', async () => {
const tunnel = createPairedTunnel(() => null)
const healthy = await openConnectedStream(tunnel, 'healthy.internal')
@@ -44,8 +44,7 @@ vi.mock('./browser-extension-window-model', () => ({
getBrowserExtensionMainWindow: () => ({ id: 1 })
}))
const { openBrowserExtensionTab, recentWorktreeIdForBrowserProfile, recordActiveBrowserPage } =
await import('./browser-extension-tab-model')
const { openBrowserExtensionTab } = await import('./browser-extension-tab-model')
const { watchBrowserMainWindowReadiness } = await import('../browser-main-window-readiness')
/** Orca's main window; its renderer reports readiness on the per-window IPC channel. */
@@ -66,43 +65,6 @@ beforeEach(() => {
mainWindow().rendererReady()
})
describe('where an extension tab goes when no workspace is active', () => {
it("prefers the active page's workspace when that page is in the profile", () => {
mocks.pages.set('a', { id: 11, session: mocks.workSession, worktreeId: 'wt-a' })
mocks.pages.set('b', { id: 12, session: mocks.workSession, worktreeId: 'wt-b' })
recordActiveBrowserPage('b')
expect(recentWorktreeIdForBrowserProfile('work')).toBe('wt-b')
})
it("otherwise uses any open tab in the profile, never another profile's workspace", () => {
mocks.pages.set('a', { id: 11, session: mocks.otherSession, worktreeId: 'wt-other' })
mocks.pages.set('b', { id: 12, session: mocks.workSession, worktreeId: 'wt-work' })
recordActiveBrowserPage('a')
expect(recentWorktreeIdForBrowserProfile('work')).toBe('wt-work')
mocks.pages.delete('b')
expect(recentWorktreeIdForBrowserProfile('work')).toBeUndefined()
})
it('passes that workspace to the renderer as the fallback', async () => {
mocks.pages.set('a', { id: 11, session: mocks.workSession, worktreeId: 'wt-a' })
mocks.createTab.mockImplementation(async () => {
mocks.pages.set('new', { id: 13, session: mocks.workSession, worktreeId: 'wt-a' })
return { browserPageId: 'new' }
})
expect(await openBrowserExtensionTab('work', 'https://welcome.test', true)).toBe(13)
expect(mocks.createTab).toHaveBeenCalledWith(
expect.anything(),
'https://welcome.test',
undefined,
'work',
'persist:work',
true,
undefined,
'wt-a'
)
})
})
describe('an extension tab opened while Orca starts', () => {
afterEach(() => {
vi.useRealTimers()
@@ -128,14 +90,4 @@ describe('an extension tab opened while Orca starts', () => {
window.rendererReady()
await expect(reopening).resolves.toBe(21)
})
it('fails cleanly when the window never becomes ready', async () => {
vi.useFakeTimers()
mainWindow()
const opening = openBrowserExtensionTab('work', 'https://welcome.test', true)
const failed = expect(opening).rejects.toThrow('Orca has no window to open a tab in')
await vi.advanceTimersByTimeAsync(30_000)
await failed
expect(mocks.createTab).not.toHaveBeenCalled()
})
})
@@ -1,4 +1,5 @@
import { existsSync, mkdtempSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'
import { mkdtempSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
@@ -67,31 +68,6 @@ describe('runClaudeLoginSession', () => {
expect(openWslSignInLink).not.toHaveBeenCalled()
})
it('leaves a local login to open its own browser', async () => {
const config = folder('host')
const openWslSignInLink = vi.fn()
const runCommand = vi.fn<RunCommand>(async (_args, _config, _ms, options) => {
expect(options?.browser).toBeUndefined()
expect(existsSync(join(config.windowsPath, '.orca-sign-in-browser'))).toBe(false)
return ''
})
await runClaudeLoginSession(config, { runCommand, setCancel: vi.fn(), openWslSignInLink })
expect(runCommand).toHaveBeenCalledTimes(1)
expect(openWslSignInLink).not.toHaveBeenCalled()
})
it('changes nothing for a WSL login where nothing can open a link', async () => {
const config = folder('wsl')
const runCommand = vi.fn<RunCommand>(async () => '')
await runClaudeLoginSession(config, { runCommand, setCancel: vi.fn() })
expect(runCommand.mock.calls[0]).toEqual([
['auth', 'login', '--claudeai'],
config,
expect.any(Number),
expect.objectContaining({ browser: undefined })
])
})
it('never opens a link that is not a Claude sign-in, and keeps the login going', async () => {
const config = folder('wsl')
const openWslSignInLink = vi.fn()
@@ -129,19 +105,4 @@ describe('runClaudeLoginSession', () => {
).rejects.toThrow('cancelled')
expect(openWslSignInLink).not.toHaveBeenCalled()
})
it('runs a WSL login unchanged when the stand-in cannot be written', async () => {
const config = { ...folder('wsl'), windowsPath: join(tmpdir(), 'missing-claude-folder', 'x') }
const openWslSignInLink = vi.fn()
const runCommand = vi.fn<RunCommand>(async () => '')
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
await runClaudeLoginSession(config, { runCommand, setCancel: vi.fn(), openWslSignInLink })
warn.mockRestore()
expect(runCommand.mock.calls[0]).toEqual([
['auth', 'login', '--claudeai'],
config,
expect.any(Number),
expect.objectContaining({ browser: undefined })
])
})
})
@@ -91,19 +91,6 @@ describe('DaemonServer attachment lifecycle', () => {
expect(daemon.attachments.tokenBySessionId.has('transport-owned-session')).toBe(false)
})
it('parks only the requesting client attachment on explicit detach', async () => {
await createSession('explicitly-detached-session')
const daemon = server as unknown as DaemonAttachmentPrivate
const detach = vi.spyOn(daemon.host, 'detach')
await client.request('detach', { sessionId: 'explicitly-detached-session' })
expect(detach).toHaveBeenCalledWith('explicitly-detached-session', expect.any(Symbol))
expect(daemon.host.sessions.get('explicitly-detached-session')?.hasAttachedClients).toBe(false)
expect(daemon.attachments.clientIdBySessionId.has('explicitly-detached-session')).toBe(false)
expect(daemon.attachments.tokenBySessionId.has('explicitly-detached-session')).toBe(false)
})
it('parks an attachment completed after its transport already closed', async () => {
const daemon = server as unknown as DaemonAttachmentPrivate
const originalCreateOrAttach = daemon.host.createOrAttach.bind(daemon.host)
+1 -79
View File
@@ -4,7 +4,7 @@ import {
_resetRunningWslDistroCacheForTests,
resolveRunningWslDistros
} from '../wsl-running-distro-cache'
import { checkWslBridgeDistro, classifyWslBridgeExit } from './daemon-wsl-bridge-exit'
import { checkWslBridgeDistro } from './daemon-wsl-bridge-exit'
afterEach(() => {
_resetRunningWslDistroCacheForTests()
@@ -37,52 +37,6 @@ describe('checkWslBridgeDistro', () => {
expect(probe).toHaveBeenCalledOnce()
})
it('matches the distro case-insensitively, and a running distro is not lost', async () => {
await expect(
checkWslBridgeDistro('ubuntu', 0, async () => ({ status: 'confirmed', distros: ['Ubuntu'] }))
).resolves.toBe('running')
})
it('treats a hung check as unanswered, so the exit stays an exit', async () => {
await expect(
checkWslBridgeDistro('Ubuntu', 0, () => new Promise(() => {}), { firstMs: 10, totalMs: 30 })
).resolves.toBe('unavailable')
})
// 5e hardening (b): a slow or failing first read is asked once more before the exit stays an exit.
it('retries an unanswered read once, past the failure backoff, within the total budget', async () => {
const reads: { bypassFailureBackoff?: boolean }[] = []
const answers: WslRunningDistrosRead[] = [
{ status: 'unavailable' },
{ status: 'confirmed', distros: [] }
]
await expect(
checkWslBridgeDistro('Ubuntu', 0, async (options) => {
reads.push({ bypassFailureBackoff: options.bypassFailureBackoff })
return answers.shift() ?? { status: 'unavailable' }
})
).resolves.toBe('lost')
expect(reads).toEqual([{ bypassFailureBackoff: undefined }, { bypassFailureBackoff: true }])
})
it('gives the retry only what is left of the total budget after a hung first read', async () => {
const reads = vi.fn(() => new Promise<WslRunningDistrosRead>(() => {}))
const startedAt = performance.now()
await expect(
checkWslBridgeDistro('Ubuntu', 0, reads, { firstMs: 20, totalMs: 60 })
).resolves.toBe('unavailable')
expect(reads).toHaveBeenCalledTimes(2)
expect(performance.now() - startedAt).toBeLessThan(500)
})
it('treats a failed check as unanswered', async () => {
await expect(
checkWslBridgeDistro('Ubuntu', 0, async () => {
throw new Error('wsl.exe missing')
})
).resolves.toBe('unavailable')
})
it('never trusts a probe already in flight before the exit: one fresh probe follows it', async () => {
// A background probe started before the pane's shell booted the distro, so it lacks it.
const answers: ((distros: string[]) => void)[] = []
@@ -105,35 +59,3 @@ describe('checkWslBridgeDistro', () => {
await expect(Promise.all(checks)).resolves.toEqual(['running', 'running'])
})
})
describe('classifyWslBridgeExit', () => {
const running = async (): Promise<WslRunningDistrosRead> => ({
status: 'confirmed',
distros: ['Ubuntu']
})
it('calls a running distro that booted again since the spawn rebooted, not an exit', async () => {
await expect(classifyWslBridgeExit('Ubuntu', 0, running, async () => true)).resolves.toBe(
'rebooted'
)
await expect(classifyWslBridgeExit('Ubuntu', 0, running, async () => false)).resolves.toBe(
'running'
)
})
it('asks about the boot id only when the running list says running', async () => {
const rebooted = vi.fn(async () => true)
await expect(
classifyWslBridgeExit(
'Ubuntu',
0,
async () => ({ status: 'confirmed', distros: [] }),
rebooted
)
).resolves.toBe('lost')
await expect(
classifyWslBridgeExit('Ubuntu', 0, async () => ({ status: 'unavailable' }), rebooted)
).resolves.toBe('unavailable')
expect(rebooted).not.toHaveBeenCalled()
})
})
-141
View File
@@ -1,141 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as GlUtils from './gl-utils'
const {
glabExecFileAsyncMock,
glabApiWithHeadersMock,
getGlabKnownHostsMock,
getProjectRefMock,
resolveIssueSourceMock,
acquireMock,
releaseMock,
gitExecFileAsyncMock
} = vi.hoisted(() => ({
glabExecFileAsyncMock: vi.fn(),
glabApiWithHeadersMock: vi.fn(),
getGlabKnownHostsMock: vi.fn(),
getProjectRefMock: vi.fn(),
resolveIssueSourceMock: vi.fn(),
acquireMock: vi.fn(),
releaseMock: vi.fn(),
gitExecFileAsyncMock: vi.fn()
}))
// Why: the #9171 default-branch guard resolves the repo default branch via
// git; keep those probes hermetic instead of spawning real git processes.
vi.mock('../git/runner', () => ({
gitExecFileAsync: gitExecFileAsyncMock
}))
vi.mock('./gl-utils', async () => {
const actual = await vi.importActual<typeof GlUtils>('./gl-utils')
return {
...actual,
glabExecFileAsync: glabExecFileAsyncMock,
glabApiWithHeaders: glabApiWithHeadersMock,
getGlabKnownHosts: getGlabKnownHostsMock,
getProjectRef: getProjectRefMock,
resolveIssueSource: resolveIssueSourceMock,
acquire: acquireMock,
release: releaseMock
}
})
import { getJobTrace, retryJob } from './client'
import { resetGitLabMrMocks } from './client-mr-test-harness'
describe('gitlab client — MR operations', () => {
beforeEach(() => {
resetGitLabMrMocks({
glabExecFileAsyncMock,
glabApiWithHeadersMock,
getGlabKnownHostsMock,
getProjectRefMock,
resolveIssueSourceMock,
acquireMock,
releaseMock,
gitExecFileAsyncMock
})
})
describe('job CI operations', () => {
beforeEach(() => {
resolveIssueSourceMock.mockImplementation(async () => ({
source: { host: 'git.internal', path: 'g/p' },
fellBack: false
}))
})
it('fetches a job trace through the selected SSH GitLab host', async () => {
glabExecFileAsyncMock.mockResolvedValueOnce({ stdout: 'trace output' })
await expect(getJobTrace('/repo', 99, 'upstream', 'conn-1')).resolves.toEqual({
ok: true,
trace: 'trace output'
})
expect(glabExecFileAsyncMock).toHaveBeenCalledWith(
['api', '--hostname', 'git.internal', 'projects/g%2Fp/jobs/99/trace'],
{ timeout: 60_000 }
)
})
// Canceled-before-start jobs have no trace; show an empty log rather than issue-edit error copy.
it('reports a 404 trace as an empty log rather than an error', async () => {
glabExecFileAsyncMock.mockRejectedValueOnce(new Error('HTTP 404 Not Found'))
await expect(getJobTrace('/repo', 99, 'upstream', 'conn-1')).resolves.toEqual({
ok: true,
trace: ''
})
})
it('keeps a missing project and a scope failure as errors, in job-log wording', async () => {
glabExecFileAsyncMock.mockRejectedValueOnce(new Error('HTTP 404: Project Not Found'))
await expect(getJobTrace('/repo', 99, 'upstream', 'conn-1')).resolves.toMatchObject({
ok: false,
error: "Could not find this job's GitLab project."
})
glabExecFileAsyncMock.mockRejectedValueOnce(new Error('HTTP 403 insufficient_scope'))
const forbidden = await getJobTrace('/repo', 99, 'upstream', 'conn-1')
expect(forbidden).toEqual({
ok: false,
error: "You don't have permission to read this job's log. Check your GitLab token scopes.",
errorCode: 'gitlab.jobLogPermissionDenied'
})
})
it('retries a job through the selected SSH GitLab host', async () => {
glabExecFileAsyncMock.mockResolvedValueOnce({
stdout: JSON.stringify({
id: 100,
pipeline: { id: 50 },
name: 'test',
stage: 'test',
status: 'pending',
web_url: 'https://git.internal/g/p/-/jobs/100',
duration: null
})
})
await expect(retryJob('/repo', 99, 'upstream', 'conn-1')).resolves.toEqual({
ok: true,
job: {
id: 100,
pipelineId: 50,
name: 'test',
stage: 'test',
status: 'pending',
webUrl: 'https://git.internal/g/p/-/jobs/100',
duration: null
}
})
expect(glabExecFileAsyncMock).toHaveBeenCalledWith(
['api', '--hostname', 'git.internal', '-X', 'POST', 'projects/g%2Fp/jobs/99/retry'],
{}
)
})
})
})
@@ -64,12 +64,6 @@ describe('getLocalRepoForRegisteredWorktree', () => {
expect(counter.splitCalls).toBe(rowCount)
})
it('never touches the meta table when a repo path matches directly', () => {
const { store, metaScans } = makeStore([{ id: 'repo-a', path: '/repos/a' }], [])
expect(getLocalRepoForRegisteredWorktree(store, '/repos/a', '/repos/a')?.id).toBe('repo-a')
expect(metaScans()).toBe(0)
})
describe('equivalence with the per-repo scan', () => {
const repos: TestRepo[] = [
{ id: 'first', path: '/repos/first' },
@@ -77,21 +71,6 @@ describe('getLocalRepoForRegisteredWorktree', () => {
{ id: 'last', path: '/repos/last' }
]
it('finds a worktree owned by the first repo', () => {
const { store } = makeStore(repos, [worktreeId('first', '/wt/one')])
expect(getLocalRepoForRegisteredWorktree(store, '/wt/one', '/wt/one')?.id).toBe('first')
})
it('finds a worktree owned by the last repo', () => {
const { store } = makeStore(repos, [worktreeId('last', '/wt/one')])
expect(getLocalRepoForRegisteredWorktree(store, '/wt/one', '/wt/one')?.id).toBe('last')
})
it('returns undefined when no repo owns the worktree', () => {
const { store } = makeStore(repos, [worktreeId('other', '/wt/elsewhere')])
expect(getLocalRepoForRegisteredWorktree(store, '/wt/one', '/wt/one')).toBeUndefined()
})
it('keeps getRepos precedence when two repos both own the path', () => {
const { store } = makeStore(repos, [
worktreeId('last', '/wt/shared'),
@@ -123,19 +102,5 @@ describe('getLocalRepoForRegisteredWorktree', () => {
const { store } = makeStore(repos, [worktreeId('middle', '/wt/one')])
expect(getLocalRepoForRegisteredWorktree(store, '/wt/other', '/wt/one')?.id).toBe('middle')
})
it('returns undefined for a folder workspace that is not a registered worktree', () => {
const { store } = makeStore(repos, [worktreeId('middle', '/wt/one')])
expect(
getLocalRepoForRegisteredWorktree(store, '/folders/notes', '/folders/notes')
).toBeUndefined()
})
it('tolerates a store without getRepos or getAllWorktreeMeta', () => {
expect(getLocalRepoForRegisteredWorktree({} as never, '/wt/one', '/wt/one')).toBeUndefined()
expect(
getLocalRepoForRegisteredWorktree({ getRepos: () => repos } as never, '/wt/one', '/wt/one')
).toBeUndefined()
})
})
})
@@ -1,6 +1,7 @@
import type * as FsPromises from 'node:fs/promises'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { statSyncMock, spawnMock } from './pty-ipc-mock-registry'
import { spawnMock } from './pty-ipc-mock-registry'
import { setupPtyIpcSuite } from './pty-ipc-test-harness'
import { registerPtyHandlers } from './register-handlers'
@@ -57,13 +58,8 @@ vi.mock('../../codex/codex-state-db-backfill-recovery', () =>
)
const WORKTREE = '/Volumes/nas/repo'
const SAVED_CWD = `${WORKTREE}/packages/app`
const directoryStats = { isDirectory: () => true }
function statAnsweringAfter(ms: number): Promise<DirectoryStats> {
return new Promise((resolve) => setTimeout(() => resolve(directoryStats), ms))
}
function spawnWithWorktreeFallback(cwd: string): Promise<unknown> {
return Promise.resolve(
handlers.get('pty:spawn')!(null, {
@@ -85,31 +81,6 @@ beforeEach(() => {
})
describe('pty:spawn with the worktree-folder fallback on a mount that stopped answering', () => {
it('waits on a hung saved folder without blocking main or calling statSync', async () => {
statMock.mockReturnValue(new Promise<DirectoryStats>(() => {}))
registerPtyHandlers(mainWindow as never)
const settled = vi.fn()
void spawnWithWorktreeFallback(SAVED_CWD).then(settled, settled)
const timerFired = await new Promise<boolean>((resolve) => setTimeout(() => resolve(true), 50))
expect(timerFired).toBe(true)
expect(settled).not.toHaveBeenCalled()
expect(spawnMock).not.toHaveBeenCalled()
expect(statSyncMock).not.toHaveBeenCalledWith(SAVED_CWD)
})
it('starts in a slow saved folder once it answers', async () => {
statMock.mockImplementation(() => statAnsweringAfter(50))
registerPtyHandlers(mainWindow as never)
const result = await spawnWithWorktreeFallback(SAVED_CWD)
const [, , options] = spawnMock.mock.calls.at(-1) as [string, string[], { cwd: string }]
expect(options.cwd).toBe(SAVED_CWD)
expect(result).toEqual(expect.not.objectContaining({ startupCwdFallback: expect.anything() }))
})
it('never asks the worktree folder once the saved folder answers', async () => {
const outsideCwd = '/Users/alice/scratch'
statMock.mockImplementation(async (path) =>
@@ -123,19 +94,4 @@ describe('pty:spawn with the worktree-folder fallback on a mount that stopped an
const [, , options] = spawnMock.mock.calls.at(-1) as [string, string[], { cwd: string }]
expect(options.cwd).toBe(outsideCwd)
})
it('still falls back to the worktree folder when the saved one is gone', async () => {
statMock.mockImplementation(async (path) => {
if (path === SAVED_CWD) {
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' })
}
return directoryStats
})
registerPtyHandlers(mainWindow as never)
const result = await spawnWithWorktreeFallback(SAVED_CWD)
expect(statMock.mock.calls.map(([path]) => path)).toEqual([SAVED_CWD, WORKTREE])
expect(result).toMatchObject({ startupCwdFallback: { kind: 'worktree', cwd: WORKTREE } })
})
})
@@ -72,7 +72,7 @@ const WSL_RUNTIME = {
// E9: a restore the user did not ask for must not start a stopped distro, whatever provider runs.
describe('a passive WSL restore with a stopped distro', () => {
const { handlers, mainWindow, installDaemonTestProvider } = setupPtyIpcSuite()
const { handlers, mainWindow } = setupPtyIpcSuite()
const originalPlatform = process.platform
beforeEach(() => {
@@ -108,15 +108,6 @@ describe('a passive WSL restore with a stopped distro', () => {
expect(spawnMock).not.toHaveBeenCalled()
})
it('holds with the daemon provider too, before its cwd probe', async () => {
const providerSpawn = vi.fn().mockResolvedValue({ id: 'pty-daemon' })
installDaemonTestProvider({ spawn: providerSpawn })
register()
await expect(restore()).rejects.toThrow()
expect(wslUncDirectoryExistsAsyncMock).not.toHaveBeenCalled()
expect(providerSpawn).not.toHaveBeenCalled()
})
it('restores once the distro runs, and never holds a restore the user asked for', async () => {
register()
runningStateMock.mockResolvedValue('running')
@@ -129,18 +120,4 @@ describe('a passive WSL restore with a stopped distro', () => {
expect(runningStateMock).not.toHaveBeenCalled()
expect(spawnMock).toHaveBeenCalledTimes(2)
})
it("hands the provider this spawn's read anchor, so its hold reuses only that read", async () => {
const providerSpawn = vi.fn().mockResolvedValue({ id: 'pty-daemon' })
installDaemonTestProvider({ spawn: providerSpawn })
register()
runningStateMock.mockResolvedValue('running')
await restore()
const anchor = runningStateMock.mock.calls[0]?.[1]?.startedAfter
expect(anchor).toEqual(expect.any(Number))
expect(providerSpawn).toHaveBeenCalledWith(
expect.objectContaining({ wslHoldReadAnchor: anchor })
)
})
})
@@ -51,21 +51,6 @@ describe('required Jira transition fields', () => {
getClients.mockReturnValue([entry])
})
it('loads required resolution metadata rather than hiding it from the form', async () => {
jiraRequest.mockResolvedValue({ transitions: [transition] })
const { listTransitions } = await import('./jira-transition-queries')
expect(await listTransitions('APP-1', 'site-1')).toMatchObject([
{
id: '31',
fields: [{ key: 'resolution', required: true, allowedValues: [{ id: '1', name: 'Fixed' }] }]
}
])
expect(jiraRequest).toHaveBeenCalledWith(
entry,
'/rest/api/3/issue/APP-1/transitions?expand=transitions.fields'
)
})
it('posts resolution and comment in the same request Jira validates', async () => {
jiraRequest.mockImplementation(async (_entry, _path, options) => {
const payload = JSON.parse(options.body)
@@ -107,23 +92,6 @@ describe('required Jira transition fields', () => {
expect(clearToken).not.toHaveBeenCalled()
})
it('preserves the legacy empty list on operational failures without strict reads', async () => {
jiraRequest.mockRejectedValue(new JiraApiError('Service unavailable', 503))
const { listTransitions } = await import('./jira-transition-queries')
expect(await listTransitions('APP-1', 'site-1')).toEqual([])
expect(jiraRequest).toHaveBeenCalledTimes(1)
expect(release).toHaveBeenCalledOnce()
expect(clearToken).not.toHaveBeenCalled()
})
it('does not retry expired credentials', async () => {
jiraRequest.mockRejectedValue(new JiraApiError('Unauthorized', 401))
const { listTransitions } = await import('./jira-transition-queries')
await expect(listTransitions('APP-1')).rejects.toThrow('Unauthorized')
expect(jiraRequest).toHaveBeenCalledTimes(1)
expect(clearToken).toHaveBeenCalledWith('site-1')
})
it('keeps old-client bare transitions and Server comments compatible', async () => {
jiraRequest.mockResolvedValue(undefined)
const { updateIssue } = await import('./jira-issue-mutations')
@@ -131,16 +131,4 @@ describe("a managed tunnel follows another desktop's restart of the server", ()
})
expect([...state.forwardedTo.values()]).toEqual([6_768])
})
it('keeps the tunnel when the host still names the forwarded port', async () => {
const state = setup()
const environment = createEnvironment()
await state.manager.ensure(environment)
state.ensureServing.mockResolvedValueOnce({ state: 'unverifiable', detail: 'not answering' })
await expect(state.manager.verifyServing(environment)).resolves.toMatchObject({
state: 'unverifiable'
})
expect([...state.forwardedTo.values()]).toEqual([6_768])
})
})
@@ -9,12 +9,9 @@ vi.mock('../ipc/runtime-environments/runtime-environment-registry-root', () => (
}))
import {
REMOTE_PREVIEW_UPDATE_REQUIRED_MESSAGE,
RemotePreviewPairingChangedError,
assertRemoteChunkReadsSupported,
readRemotePreviewRange,
readRemotePreviewChunk,
statRemotePreviewFile
readRemotePreviewChunk
} from './remote-file-preview-reader'
const address = {
@@ -24,38 +21,11 @@ const address = {
expectedEnvironmentPairingRevision: 7
}
const ok = (result: unknown) => ({ ok: true, result })
const fail = (code: string, message: string) => ({ ok: false, error: { code, message } })
describe('remote file preview reader', () => {
const signal = new AbortController().signal
beforeEach(() => callRuntimeEnvironment.mockReset())
it('stats the file through its pinned pairing', async () => {
callRuntimeEnvironment.mockResolvedValue(
ok({ size: 6, isDirectory: false, mtime: 1, ctime: 2 })
)
await expect(statRemotePreviewFile(address, signal)).resolves.toEqual({
size: 6,
mtime: 1,
ctime: 2
})
expect(callRuntimeEnvironment).toHaveBeenCalledWith(
'/user-data',
'env-1',
'files.stat',
{ worktree: 'id:wt-1', relativePath: 'out/clip.mp4' },
15_000,
7,
undefined,
{ signal }
)
})
it('refuses a directory', async () => {
callRuntimeEnvironment.mockResolvedValue(ok({ size: 0, isDirectory: true, mtime: 1 }))
await expect(statRemotePreviewFile(address, signal)).rejects.toThrow('directory')
})
it('decodes a chunk and refuses one whose length disagrees', async () => {
callRuntimeEnvironment.mockResolvedValueOnce(
ok({ contentBase64: 'AAECAw==', bytesRead: 4, eof: false })
@@ -69,20 +39,6 @@ describe('remote file preview reader', () => {
await expect(readRemotePreviewChunk(address, 0, 4, signal)).rejects.toThrow('corrupted')
})
it('tells the user to update a host without range reads', async () => {
callRuntimeEnvironment.mockResolvedValue(fail('method_not_found', 'x'))
await expect(statRemotePreviewFile(address, signal)).rejects.toThrow(
REMOTE_PREVIEW_UPDATE_REQUIRED_MESSAGE
)
})
it('reports a re-paired environment as a dead pairing, not a transient failure', async () => {
callRuntimeEnvironment.mockResolvedValue(fail('runtime_environment_changed', 'changed'))
await expect(readRemotePreviewChunk(address, 0, 1, signal)).rejects.toBeInstanceOf(
RemotePreviewPairingChangedError
)
})
it('proves chunk reads with one byte once per pairing, and re-proves after a re-pair', async () => {
callRuntimeEnvironment.mockResolvedValue(
ok({ contentBase64: 'AA==', bytesRead: 1, eof: false })
@@ -108,23 +64,6 @@ describe('remote file preview reader', () => {
expect(callRuntimeEnvironment).toHaveBeenCalledTimes(2)
})
it('never caches a verdict no pairing revision pins', async () => {
callRuntimeEnvironment.mockResolvedValue(
ok({ contentBase64: 'AA==', bytesRead: 1, eof: false })
)
const unpinned = { environmentId: 'env-unpinned', worktree: 'id:wt-1', relativePath: 'a.mp4' }
await assertRemoteChunkReadsSupported(unpinned, signal)
await assertRemoteChunkReadsSupported(unpinned, signal)
expect(callRuntimeEnvironment).toHaveBeenCalledTimes(2)
})
it('tells the user to update a host that answers files.stat but not files.readChunk', async () => {
callRuntimeEnvironment.mockResolvedValue(fail('method_not_found', 'x'))
await expect(
assertRemoteChunkReadsSupported({ ...address, environmentId: 'env-old' }, signal)
).rejects.toThrow(REMOTE_PREVIEW_UPDATE_REQUIRED_MESSAGE)
})
it('reads a range in host-sized chunks, clipped to the file and stopping at EOF', async () => {
callRuntimeEnvironment
.mockResolvedValueOnce(
@@ -5,11 +5,7 @@ import type { AgentSessionAccountHome } from '../../../shared/agent-session-acco
import type { AgentSessionModelOption } from '../../../shared/agent-session-wire'
import { agentModelCatalogFingerprintForRecord } from './agent-model-catalog-fingerprint'
import { createAgentModelCatalogService } from './agent-model-catalog-service'
import {
AGENT_MODEL_CATALOG_FRESH_MS,
AgentModelCatalogStore,
type AgentModelCatalogSuccess
} from './agent-model-catalog-store'
import { AgentModelCatalogStore, type AgentModelCatalogSuccess } from './agent-model-catalog-store'
const HOME: AgentSessionAccountHome = { variable: 'GROK_HOME', path: '/homes/grok' }
@@ -105,15 +101,6 @@ describe('a live listing beside the account-level discovery', () => {
expect(read?.models[0]?.defaultEffort).toBe('high')
})
it('names the default on a cold account whose first listing came from a live chat', async () => {
const run = harness(async () => discovery('grok-4'))
run.saveLive()
// A live listing alone names no default and does not count as fresh, so explicit demand lists the account.
await run.catalog.demand({ agent: 'grok' })
await vi.waitFor(() => expect(run.probes).toHaveBeenCalledTimes(1))
await vi.waitFor(async () => expect(await run.defaultOf()).toBe('grok-4'))
})
it('still refreshes the account listing after ten minutes of live saves', async () => {
let configured = 'grok-4'
const run = harness(async () => discovery(configured))
@@ -130,13 +117,4 @@ describe('a live listing beside the account-level discovery', () => {
await vi.waitFor(() => expect(run.probes).toHaveBeenCalledTimes(2))
await vi.waitFor(async () => expect(await run.defaultOf()).toBe('grok-3-mini'))
})
it('does not refresh while the discovery is younger than its freshness window', async () => {
const run = harness(async () => discovery('grok-4'))
await run.read()
run.advance(AGENT_MODEL_CATALOG_FRESH_MS - 1)
run.saveLive()
await run.read()
expect(run.probes).toHaveBeenCalledTimes(1)
})
})
@@ -97,17 +97,6 @@ describe('resolved revisions', () => {
expect(rowText()).toBe('abc')
})
it('skips a revision that resolves to nothing', async () => {
const deferred = createDeferredStructuredAgentSessionEventSink(testEventSinkLogging())
deferred.sink.tryReviseResolvedItem?.(1_000, () => null, {
turnScope: AGENT_JOURNAL_THREAD_SCOPE
})
const before = journal.cursor()
deferred.bind(journalTarget())
await expect(deferred.drained()).resolves.toEqual({ ok: true })
expect(journal.cursor()).toEqual(before)
})
it('publishes a revision in the operation that writes it, within the same reservation', async () => {
const deferred = createDeferredStructuredAgentSessionEventSink(testEventSinkLogging())
const bytes = estimateStructuredAgentSessionItemBytes(ROW, text('a'))
@@ -35,30 +35,6 @@ function createSession() {
describe('default-session proxy request guard', () => {
beforeEach(() => vi.clearAllMocks())
it('holds renderer requests until a delayed proxy transition settles', async () => {
const { proxySession, request } = createSession()
let finishWrite: (() => void) | undefined
proxySession.setProxy.mockImplementationOnce(
() => new Promise<void>((resolve) => (finishWrite = resolve))
)
resetSessionProxyApplicationForTests(proxySession)
installElectronProxyRequestGuard(proxySession as never)
const applying = applyProxySettingsToSession(
proxySession,
{ httpProxyUrl: 'http://proxy.example:8080' },
{ env: {} }
)
const callback = vi.fn()
request(callback)
expect(callback).not.toHaveBeenCalled()
await vi.waitFor(() => expect(proxySession.setProxy).toHaveBeenCalledOnce())
finishWrite?.()
await applying
await vi.waitFor(() => expect(callback).toHaveBeenCalledWith({}))
})
it('keeps renderer requests blocked across a transient retry', async () => {
const { proxySession, request } = createSession()
let finishRetry: (() => void) | undefined
@@ -1,11 +1,12 @@
import { createHash } from 'node:crypto'
import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'
import { mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { strToU8, zipSync } from 'fflate'
import { afterEach, expect, it, vi } from 'vitest'
import type * as ReleaseModule from './managed-runtime-release'
import { reclaimRuntimeReleases, RUNTIME_RELEASE_RECEIPT } from './managed-runtime-release'
import { RUNTIME_RELEASE_RECEIPT } from './managed-runtime-release'
import { ManagedRuntimeInstallation } from './managed-runtime-installation'
vi.mock('./managed-runtime-release', async (importOriginal) => {
@@ -90,34 +91,6 @@ it('installs only when asked, with progress, cancel, retry, and a status read fr
expect(await installation.status()).toMatchObject({ installedCurrent: true })
})
it('publishes an update only once the old releases were reclaimed', async () => {
const { root, host, phase } = await rig(['whole'])
for (const version of ['0.8', '0.9']) {
await mkdir(join(root, version), { recursive: true })
await writeFile(join(root, version, 'server'), 'old server', { mode: 0o755 })
const members = [{ path: 'server', bytes: 10 }]
const receipt = { releaseId: sha256(version), version, members }
await writeFile(
join(root, version, RUNTIME_RELEASE_RECEIPT),
JSON.stringify({ ...receipt, platform: process.platform, arch: process.arch })
)
}
let resume = (): void => {}
const reclaim = vi.mocked(reclaimRuntimeReleases)
const paused = new Promise<void>((resolve) => (resume = resolve))
const { reclaimRuntimeReleases: reclaimNow } = await vi.importActual<typeof ReleaseModule>(
'./managed-runtime-release'
)
reclaim.mockImplementationOnce(async (...args) => paused.then(() => reclaimNow(...args)))
const installation = host()
installation.install()
await new Promise((resolve) => setTimeout(resolve, 100))
expect((await installation.status()).operation).toMatchObject({ downloadedBytes: 0 })
resume()
await vi.waitFor(async () => expect(await phase(installation)).toBe('succeeded'))
expect((await readdir(root)).sort()).toEqual(['0.9', '1.0'])
})
it('repairs a damaged copy when Install is clicked again', async () => {
const { root, host, phase } = await rig(['whole', 'whole'])
const installation = host()
@@ -43,9 +43,7 @@ vi.mock('./profile-cloud-client', () => ({
import {
connectCurrentOrcaProfile,
createCloudLinkedOrcaProfile,
getCurrentOrcaProfileAuthStatus,
selectCurrentOrcaProfileOrg,
signOutCurrentOrcaProfile
} from './profile-cloud-service'
@@ -73,59 +71,6 @@ describe('Orca cloud dev auth service', () => {
vi.unstubAllEnvs()
})
it('connects the active profile without PKCE or cloud endpoints', async () => {
expect(getCurrentOrcaProfileAuthStatus(userDataPath)).toMatchObject({
configured: true,
state: 'local'
})
const result = await connectCurrentOrcaProfile(userDataPath)
expect(result.status).toBe('connected')
expect(beginOrcaCloudPkceFlowMock).not.toHaveBeenCalled()
expect(exchangeOrcaCloudAuthCodeMock).not.toHaveBeenCalled()
expect(getCurrentOrcaProfileAuthStatus(userDataPath)).toMatchObject({
configured: true,
state: 'connected',
persistence: 'encrypted',
cloud: {
cloudProfileId: 'dev-cloud-local-default',
email: 'dev@orca.local'
},
capabilities: {
flags: expect.objectContaining({ 'share.create': true })
}
})
expect(getCurrentOrcaProfileAuthStatus(userDataPath).organizations).toHaveLength(2)
})
it('selects dev organizations and creates org-scoped cloud profiles locally', async () => {
await connectCurrentOrcaProfile(userDataPath)
const selected = await selectCurrentOrcaProfileOrg(userDataPath, 'dev-acme')
const created = await createCloudLinkedOrcaProfile(userDataPath, {
orgId: 'dev-acme',
name: 'Acme Dev'
})
expect(selected.status).toBe('selected')
expect(getCurrentOrcaProfileAuthStatus(userDataPath).cloud).toMatchObject({
activeOrgId: 'dev-acme',
activeOrgName: 'Acme Dev'
})
expect(created.status).toBe('created')
if (created.status === 'created') {
expect(created.profile).toMatchObject({
name: 'Acme Dev',
kind: 'cloud-linked',
cloud: expect.objectContaining({
activeOrgId: 'dev-acme',
activeOrgName: 'Acme Dev'
})
})
}
})
it('signs out locally without calling the cloud logout endpoint', async () => {
await connectCurrentOrcaProfile(userDataPath)
const regionCache = join(userDataPath, 'orca-relay-region-preference.json')
@@ -92,20 +92,6 @@ afterEach(async () => {
})
describe('resolveOrcadBrowserProvider Electron preference', () => {
it('uses the installed Electron app even when a Chromium executable is configured', async () => {
provider = await resolveOrcadBrowserProvider({
userDataPath: join(harnessRoot, 'state'),
environment: { ORCA_BROWSER_EXECUTABLE: chromiumExecutable },
resolveInstalledElectronExecutable: async () => INSTALLED_EXECUTABLE,
resolveAgentBrowserBinary: () => '/agent-browser'
})
expect(provider?.kind).toBe('electron')
expect(provider?.isAvailable()).toBe(true)
expect(spawnProcessMock.mock.calls[0][0].program).toBe(INSTALLED_EXECUTABLE)
expect(runProcessMock).not.toHaveBeenCalled()
})
it('falls back to the operator Chromium when the installed Electron app cannot start', async () => {
sidecarMode = 'exit-before-ready'
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
@@ -124,19 +110,4 @@ describe('resolveOrcadBrowserProvider Electron preference', () => {
expect.anything()
)
})
it('returns no provider when Electron fails and no Chromium is configured', async () => {
sidecarMode = 'exit-before-ready'
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
provider = await resolveOrcadBrowserProvider({
userDataPath: join(harnessRoot, 'state'),
environment: {},
resolveInstalledElectronExecutable: async () => INSTALLED_EXECUTABLE,
resolveAgentBrowserBinary: () => '/agent-browser'
})
expect(provider).toBeNull()
expect(runProcessMock).not.toHaveBeenCalled()
})
})
@@ -76,16 +76,6 @@ describe('daemon descendant walk under Windows pid reuse', () => {
expect(await teardown(root, [daemon, ...system], system)).toBeNull()
})
it('still fails on a child the daemon started and left running', async ({ onTestFinished }) => {
const daemonPid = await exitedPid()
const daemon = { pid: daemonPid, ppid: 1, name: 'node.exe', creationTimeMs: 5_000 }
const child = { pid: 77, ppid: daemonPid, name: 'OpenConsole.exe', creationTimeMs: 6_000 }
const root = profile(daemonPid, onTestFinished)
expect(await teardown(root, [daemon, child], [child])).toMatchObject(
leaked(`OpenConsole.exe 77 (ppid ${daemonPid}, created 6000)`)
)
})
it('still fails on a grandchild chain created after the daemon', async ({ onTestFinished }) => {
const daemonPid = await exitedPid()
const daemon = { pid: daemonPid, ppid: 1, name: 'node.exe', creationTimeMs: 5_000 }
@@ -97,17 +87,6 @@ describe('daemon descendant walk under Windows pid reuse', () => {
)
})
it('ignores an older process whose reused parent pid belongs to an exited daemon child', async ({
onTestFinished
}) => {
const daemonPid = await exitedPid()
const daemon = { pid: daemonPid, ppid: 1, name: 'node.exe', creationTimeMs: 5_000 }
const child = { pid: 2_000, ppid: daemonPid, name: 'OpenConsole.exe', creationTimeMs: 6_000 }
const telemetry = { pid: 2_904, ppid: 2_000, name: 'vctip.exe', creationTimeMs: 1_000 }
const root = profile(daemonPid, onTestFinished)
expect(await teardown(root, [daemon, child, telemetry], [telemetry])).toBeNull()
})
it('ignores children of a newer process that reused the exited daemon pid', async ({
onTestFinished
}) => {
@@ -121,25 +100,4 @@ describe('daemon descendant walk under Windows pid reuse', () => {
leaked(`OpenConsole.exe 77 (ppid ${daemonPid}, created 6000)`)
)
})
it.for([
{ daemonCreated: undefined, childCreated: 10 },
{ daemonCreated: 5_000, childCreated: undefined }
])(
'keeps a child when a creation time is unknown (%o)',
async ({ daemonCreated, childCreated }, { onTestFinished }) => {
const daemonPid = await exitedPid()
const daemon = { pid: daemonPid, ppid: 1, name: 'node.exe', creationTimeMs: daemonCreated }
const child = {
pid: 77,
ppid: daemonPid,
name: 'OpenConsole.exe',
creationTimeMs: childCreated
}
const root = profile(daemonPid, onTestFinished)
expect(await teardown(root, [daemon, child], [child])).toMatchObject(
leaked(`OpenConsole.exe 77 (ppid ${daemonPid}, created ${childCreated})`)
)
}
)
})
@@ -45,16 +45,6 @@ function failRegistration(): void {
}
describe('PluginServiceHousekeeping', () => {
it('checks bindings and reaps healthy workers without refreshing or restarting watchers', async () => {
const { reapIdle, refresh } = sync()
await vi.advanceTimersByTimeAsync(180_000)
expect(reapIdle).toHaveBeenCalledTimes(3)
expect(refresh).not.toHaveBeenCalled()
expect(watcher().start).toHaveBeenCalledOnce()
expect(watcher().checkRootBindings).toHaveBeenCalledTimes(3)
expect(vi.getTimerCount()).toBe(1)
})
it('retries failed registration using the existing maintenance interval', async () => {
const { reapIdle, refresh } = sync()
failRegistration()
@@ -70,15 +60,6 @@ describe('PluginServiceHousekeeping', () => {
expect(vi.getTimerCount()).toBe(1)
})
it('retains a synchronous startup failure for the next bounded retry', async () => {
vi.mocked(watcher().start).mockImplementation((_paths, _refresh, onWatcherError) => {
onWatcherError?.()
})
const { refresh } = sync()
await vi.advanceTimersByTimeAsync(60_000)
expect(refresh).toHaveBeenCalledOnce()
})
it('keeps unchanged missing roots idle and refreshes when their binding appears', async () => {
const { refresh, reapIdle } = sync()
const onWatcherError = vi.mocked(watcher().start).mock.calls[0]?.[2]
@@ -125,20 +106,4 @@ describe('PluginServiceHousekeeping', () => {
await Promise.resolve()
expect(refresh).toHaveBeenCalledOnce()
})
it('stops retries and releases subscriptions when disabled or disposed', () => {
const { reapIdle, refresh } = sync()
failRegistration()
housekeeping.sync({ enabled: false, devPaths: ['plugin'], reapIdle, refresh })
vi.advanceTimersByTime(120_000)
expect(reapIdle).not.toHaveBeenCalled()
expect(refresh).not.toHaveBeenCalled()
expect(vi.getTimerCount()).toBe(0)
sync()
failRegistration()
housekeeping.dispose()
vi.advanceTimersByTime(120_000)
expect(refresh).not.toHaveBeenCalled()
expect(vi.getTimerCount()).toBe(0)
})
})
@@ -123,19 +123,4 @@ describe('codex rate-limit spawn runtime pairing', () => {
}
}
})
it('leaves PATH alone when the CLI resolves to a bare command name', async () => {
resolveCodexCommandMock.mockReturnValue('codex')
const rpcChild = makeRpcChild()
childSpawnMock.mockReturnValue(rpcChild)
const resultPromise = fetchCodexRateLimits()
await vi.advanceTimersByTimeAsync(0)
const spawnEnv = childSpawnMock.mock.calls[0]?.[2]?.env as NodeJS.ProcessEnv
expect(spawnEnv.PATH).toBe(process.env.PATH)
rpcChild.emit('close')
await resultPromise
})
})
@@ -49,19 +49,6 @@ describe('RuntimeFileCommands', () => {
return dir
}
it('stays inside the transport ceiling once base64-inflated', () => {
const result = {
content: Buffer.alloc(RUNTIME_PREVIEWABLE_BINARY_MAX_BYTES).toString('base64'),
isBinary: true,
isImage: true,
mimeType: 'image/png'
}
expect(Buffer.byteLength(JSON.stringify(result), 'utf8')).toBeLessThanOrEqual(
REMOTE_RPC_MAX_CONTENT_BYTES
)
})
it('rejects a previewable image one byte above the cap', async () => {
const dir = await previewFixture(RUNTIME_PREVIEWABLE_BINARY_MAX_BYTES + 1)
const { commands } = createRuntimeFileCommands({ path: dir })
@@ -72,21 +59,6 @@ describe('RuntimeFileCommands', () => {
).rejects.toThrow('file_too_large')
})
it('returns full base64 for a previewable image at the cap', async () => {
const dir = await previewFixture(RUNTIME_PREVIEWABLE_BINARY_MAX_BYTES)
const { commands } = createRuntimeFileCommands({ path: dir })
resolveAuthorizedPathMock.mockImplementation(async (p: string) => p)
await expect(
commands.readFileExplorerPreview('id:wt-1', 'logo.png', REMOTE_RPC_MAX_CONTENT_BYTES)
).resolves.toEqual({
content: Buffer.alloc(RUNTIME_PREVIEWABLE_BINARY_MAX_BYTES, 0x61).toString('base64'),
isBinary: true,
isImage: true,
mimeType: 'image/png'
})
})
it('keeps local previews above the remote cap available without a request budget', async () => {
const size = RUNTIME_PREVIEWABLE_BINARY_MAX_BYTES + 1
const dir = await previewFixture(size)
@@ -85,60 +85,4 @@ describe('a moved shell history that waited on live shells', () => {
expect(options.worktreeHasLiveShells(TEST_WORKTREE_ID)).toBe(true)
expect(options.worktreeHasLiveShells('repo-1::/srv/other')).toBe(false)
})
it('waits when the terminal inventory does not answer', async () => {
const installPending = vi.fn()
const runtime = new OrcaRuntimeService({
...store,
installPendingOrcadMigrationReceivedBlobs: installPending,
hasPendingOrcadMigrationShellHistory: () => true
})
runtime.setPtyController({
write: () => true,
kill: () => true,
getForegroundProcess: async () => null,
listProcesses: vi.fn(async () => {
throw new Error('daemon unreachable')
})
})
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
await runtime.installPendingOrcadMigrationShellHistory()
expect(installPending).not.toHaveBeenCalled()
warn.mockRestore()
})
it('costs a start with nothing pending no inventory and keeps restored receipts', async () => {
const installPending = vi.fn()
const runtime = new OrcaRuntimeService({
...store,
installPendingOrcadMigrationReceivedBlobs: installPending,
hasPendingOrcadMigrationShellHistory: () => false
})
const listProcesses = vi.fn(async () => [{ id: 'daemon-pty', cwd: '/elsewhere', title: 'zsh' }])
runtime.setPtyController({
write: () => true,
kill: () => true,
getForegroundProcess: async () => null,
listProcesses
})
const receipt = {
ptyId: 'daemon-pty',
worktreeId: TEST_WORKTREE_ID,
terminalHandle: 'term_daemon',
paneKey: 'tab-1:1',
processIncarnation: 'daemon-pty:inc-1',
hostScope: { kind: 'local' }
}
internalsOf(runtime).restoredOrchestrationAuthorityByPtyId.set('daemon-pty', receipt)
await runtime.installPendingOrcadMigrationShellHistory()
expect(listProcesses).not.toHaveBeenCalled()
expect(installPending).not.toHaveBeenCalled()
expect(internalsOf(runtime).restoredOrchestrationAuthorityByPtyId.get('daemon-pty')).toBe(
receipt
)
})
})
@@ -47,54 +47,6 @@ afterEach(() => {
})
describe('startup fork-upstream backfill', () => {
it('keeps a renamed fork own owner avatar while recording the upstream', async () => {
const runtime = new OrcaRuntimeService()
const updateRepo = attachStore(runtime, [makeRepo()])
getRepoUpstream.mockResolvedValue({ owner: 'upstream-org', repo: 'rocket' })
getRepoSlug.mockResolvedValue({ owner: 'acme', repo: 'rocket-pro' })
await (runtime as unknown as BackfillInternals).repositoryForkBackfill.run()
expect(updateRepo).toHaveBeenCalledExactlyOnceWith(repoTarget('repo-1'), {
upstream: { owner: 'upstream-org', repo: 'rocket' },
repoIcon: {
type: 'image',
src: 'https://github.com/acme.png?size=64',
source: 'github',
label: 'acme/rocket-pro'
}
})
})
it('migrates a same-name fork to the upstream owner avatar', async () => {
const runtime = new OrcaRuntimeService()
const updateRepo = attachStore(runtime, [
makeRepo({
path: '/workspace/rocket',
repoIcon: {
type: 'image',
src: 'https://github.com/acme.png?size=64',
source: 'github',
label: 'acme/rocket'
}
})
])
getRepoUpstream.mockResolvedValue({ owner: 'upstream-org', repo: 'rocket' })
getRepoSlug.mockResolvedValue({ owner: 'acme', repo: 'rocket' })
await (runtime as unknown as BackfillInternals).repositoryForkBackfill.run()
expect(updateRepo).toHaveBeenCalledExactlyOnceWith(repoTarget('repo-1'), {
upstream: { owner: 'upstream-org', repo: 'rocket' },
repoIcon: {
type: 'image',
src: 'https://github.com/upstream-org.png?size=64',
source: 'github',
label: 'upstream-org/rocket'
}
})
})
it('skips every row whose files sit on an SSH host', async () => {
// Unified remote ownership, including nested runtime targets, must not read this client's same-path files.
const runtime = new OrcaRuntimeService()
@@ -1,9 +1,10 @@
import { describe, expect, it, vi } from 'vitest'
import type { OrcaRuntimeService } from '../../../../orca-runtime'
import type { OrchestrationDb } from '../../../../orchestration/db'
import { exposeWorker, inspectWorkerTerminal } from './worker-observation'
import { inspectWorkerTerminal } from './worker-observation'
import { exposeDispatchContext } from './dispatch-context-exposure'
import type { DispatchContextRow, WorkerDispatchRow } from '../../../../orchestration/types'
import type { DispatchContextRow } from '../../../../orchestration/types'
const DISPATCH_ID = 'ctx-worker'
const TERMINAL_HANDLE = 'term-worker'
@@ -28,18 +29,6 @@ function createHarness(args: {
}
describe('inspectWorkerTerminal missing liveness verdict', () => {
it('keeps a connected local worker live', async () => {
const { runtime, db } = createHarness({
connected: true,
hostScope: { kind: 'local', hostId: 'local' }
})
await expect(inspectWorkerTerminal(runtime, db, DISPATCH_ID)).resolves.toMatchObject({
exact: true,
status: 'live'
})
})
it('keeps a disconnected local worker exited', async () => {
const { runtime, db } = createHarness({
connected: false,
@@ -67,40 +56,6 @@ describe('inspectWorkerTerminal missing liveness verdict', () => {
})
describe('worker-show receipt shape', () => {
it('parses the JSON columns once and emits one casing', () => {
const exposed = exposeWorker({
dispatch_id: DISPATCH_ID,
runtime_epoch: 'epoch-1',
state: 'ready',
stage: 'input_accepted',
worktree_id: 'repo::/tmp/wt',
agent_terminal_handle: TERMINAL_HANDLE,
setup_state: 'ran',
effects: '[{"kind":"setup"}]',
residual_resources: '["res-1"]',
start_options: '{"agent":"codex"}',
last_error: null,
created_at: 'now',
updated_at: 'now'
} as WorkerDispatchRow)
expect(exposed).toEqual({
dispatchId: DISPATCH_ID,
runtimeEpoch: 'epoch-1',
state: 'ready',
stage: 'input_accepted',
worktreeId: 'repo::/tmp/wt',
agentTerminalHandle: TERMINAL_HANDLE,
setupState: 'ran',
effects: [{ kind: 'setup' }],
residualResources: ['res-1'],
startOptions: { agent: 'codex' },
lastError: null,
createdAt: 'now',
updatedAt: 'now'
})
})
it('parses host_scope and withholds authority hashes from the dispatch row', () => {
const exposed = exposeDispatchContext({
id: DISPATCH_ID,
@@ -123,25 +78,4 @@ describe('worker-show receipt shape', () => {
// Retain only the legacy dispatch spelling older paired CLIs still print beside camelCase worker.
expect(Object.keys(exposed).filter((key) => key.includes('_'))).toEqual(['task_id'])
})
// Mixed-version CLIs still print these legacy fields (cli/handlers/orchestration/worker-observation-handlers.ts).
it('keeps every field an older paired CLI prints', () => {
const dispatch = exposeDispatchContext({
id: DISPATCH_ID,
run_id: 'run-1',
task_id: 'task-1',
status: 'dispatched'
} as DispatchContextRow)
expect(dispatch).toMatchObject({ id: DISPATCH_ID, task_id: 'task-1', status: 'dispatched' })
expect(
exposeWorker({
state: 'ready',
stage: 'input_accepted',
effects: '[]',
residual_resources: '[]',
start_options: '{}'
} as WorkerDispatchRow)
).toMatchObject({ state: 'ready', stage: 'input_accepted' })
})
})
@@ -3,67 +3,6 @@ import { eraseRpcMethods, type RpcContext } from '../core'
import { TERMINAL_METHODS } from './terminal'
describe('terminal.create RPC idempotency', () => {
it('scopes the mutation key to the authenticated paired device and worktree', async () => {
const terminal = { handle: 'terminal-1', worktreeId: 'worktree-1', title: null }
const createTerminal = vi.fn(async () => terminal)
const dedupeTerminalCreate = vi.fn(
async (
_clientIdentity: string,
_worktree: string | undefined,
_mutationId: string | undefined,
_reconcileExisting: boolean,
run: (worktree: string | undefined, handle: string | undefined) => Promise<typeof terminal>
) => run('id:worktree-1', 'term_stable')
)
const method = eraseRpcMethods(TERMINAL_METHODS).find(
(candidate) => candidate.name === 'terminal.create'
)
if (!method) {
throw new Error('terminal.create method missing')
}
const result = await method.handler(
{
worktree: 'id:worktree-1',
clientMutationId: 'mutation-1',
command: 'pwsh',
resumeProviderSession: {
key: 'session_id',
id: 'session-1',
transcriptPath: 'C:\\Users\\example\\.codex\\sessions\\rollout.jsonl'
}
},
{
runtime: { createTerminal, dedupeTerminalCreate },
pairedDeviceId: 'device-a',
clientId: 'bearer-token'
} as unknown as RpcContext,
vi.fn()
)
expect(dedupeTerminalCreate).toHaveBeenCalledWith(
'device-a',
'id:worktree-1',
'mutation-1',
false,
expect.any(Function),
{ wslUserAct: true }
)
expect(createTerminal).toHaveBeenCalledWith(
'id:worktree-1',
expect.objectContaining({
command: 'pwsh',
preAllocatedHandle: 'term_stable',
resumeProviderSession: {
key: 'session_id',
id: 'session-1',
transcriptPath: 'C:\\Users\\example\\.codex\\sessions\\rollout.jsonl'
}
})
)
expect(result).toEqual({ terminal })
})
it('does not let a paired focused create navigate the host by default', async () => {
const terminal = { handle: 'terminal-focused', worktreeId: 'worktree-1', title: null }
const createTerminal = vi.fn(async () => terminal)
@@ -55,22 +55,6 @@ const notFound = (): never => {
// Why: only the desktop that owns a run can verify its dispatch token (#34433).
describe('worktree.create for another owner’s automation run', () => {
it('creates a plain workspace for a dispatch token it cannot verify', async () => {
const { response, createManagedWorktree } = createOnHost(notFound, {
automationProvenanceRequest: {
automationId: 'desktop-automation',
automationRunId: 'desktop-run',
dispatchToken: 'desktop-token',
createRequestId: 'create-request-desktop'
}
})
await expect(response).resolves.toMatchObject({ ok: true })
expect(createManagedWorktree).toHaveBeenCalledWith(
expect.not.objectContaining({ automationProvenance: expect.anything() })
)
})
// Invariant: a client-origin label never grants ownership, sleep authority or create policy here.
it('records the owner’s run as a client-origin label without automation create policy', async () => {
const { response, createManagedWorktree } = createOnHost(notFound, {
@@ -103,32 +87,6 @@ describe('worktree.create for another owner’s automation run', () => {
)
})
it('drops an oversized label and still creates the workspace', async () => {
// Why fresh modules: the warning is once per process, so this create must be the first to drop one.
vi.resetModules()
const { WORKTREE_METHODS: freshMethods } = await import('./worktree')
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
const { response, createManagedWorktree } = createOnHost(
notFound,
{
clientAutomationProvenance: {
...CLIENT_PROVENANCE,
automationNameSnapshot: 'x'.repeat(1025)
}
},
freshMethods
)
await expect(response).resolves.toMatchObject({ ok: true })
expect(createManagedWorktree).toHaveBeenCalledWith(
expect.not.objectContaining({ automationProvenance: expect.anything() })
)
expect(warn).toHaveBeenCalledWith(
expect.stringContaining('dropped a malformed client automation label')
)
warn.mockRestore()
})
it('surfaces an automation store failure', async () => {
const { response, createManagedWorktree } = createOnHost(
() => {
@@ -10,7 +10,7 @@ import {
TERMINAL_MULTIPLEX_MAX_PENDING_PTY_WAITS_PER_CONNECTION,
TERMINAL_MULTIPLEX_MAX_QUEUED_PTY_WAITS_PER_CONNECTION
} from '../../../shared/terminal-multiplex-flow-control'
import { createMultiplexPtyWaitAdmission } from './methods/terminal/terminal-multiplex-pty-wait-admission'
import { startDesktopMultiplexSubscribe } from './terminal-multiplex-test-harness'
function startPendingPtyHarness() {
@@ -122,20 +122,3 @@ describe('terminal multiplex PTY wait queue', () => {
await harness.dispatchPromise
})
})
describe('createMultiplexPtyWaitAdmission', () => {
it('admits FIFO, skips aborted waiters, and reports room against both bounds', async () => {
const admission = createMultiplexPtyWaitAdmission(1, 2)
const first = await admission.acquire(new AbortController().signal)
expect(first).toBe(true)
const aborted = new AbortController()
const second = admission.acquire(aborted.signal)
const third = admission.acquire(new AbortController().signal)
expect(admission.hasRoom()).toBe(false)
aborted.abort()
await expect(second).resolves.toBe(false)
expect(admission.hasRoom()).toBe(true)
admission.release()
await expect(third).resolves.toBe(true)
})
})
@@ -96,22 +96,6 @@ describe.skipIf(process.platform === 'win32')(
expect((await launchWith(settings)).command).toBe(join(privateBin, 'opencode'))
})
it('keeps a refused release first on PATH on the terminal chat when nothing points elsewhere', async () => {
const settings = { ...base, agentDefaultEnv: {} }
expect(
await structuredAgentSupportsLaunch('opencode', workspace, runtimeWith(settings))
).toBe(false)
})
it('runs a stable 2.x first on PATH in the structured chat', async () => {
await fakeOpencode(shellBin, '2.0.21')
const settings = { ...base, agentDefaultEnv: {} }
expect(
await structuredAgentSupportsLaunch('opencode', workspace, runtimeWith(settings))
).toBe(true)
expect((await launchWith(settings)).command).toBe(join(shellBin, 'opencode'))
})
it('checks and launches the binary the Command setting names', async () => {
const settings = {
...base,
@@ -62,25 +62,6 @@ it('shares one root read across six concurrent name filters', async () => {
})
})
it('reuses the raw snapshot for a new name and invalidates it on mutation', async () => {
await fixture(async (root) => {
const args = { homeDir: root, repos: [], includeCwd: false, sourceKinds: ['home' as const] }
observed.opens = 0
await repair.discoverSkills({ ...args, names: ['skill-0'] })
const next = await repair.discoverSkills({ ...args, names: ['skill-47'] })
expect(next.skills.map((skill) => skill.name)).toEqual(['skill-47'])
expect(observed.opens).toBe(48)
await writeFile(
join(root, '.agents', 'skills', 'skill-47', 'SKILL.md'),
'---\nname: renamed\n---\n'
)
repair.clearSkillRootScanCache()
const updated = await repair.discoverSkills({ ...args, names: ['renamed'] })
expect(updated.skills.map((skill) => skill.name)).toEqual(['renamed'])
expect(observed.opens).toBe(96)
})
})
it('retains a newly requested name when its previously observed root becomes unavailable', async () => {
await fixture(async (root) => {
const args = { homeDir: root, repos: [], includeCwd: false, sourceKinds: ['home' as const] }
@@ -108,21 +89,6 @@ it('retains a newly requested name when its previously observed root becomes una
})
})
it('keeps simultaneous forced refreshes independent', async () => {
await fixture(async (root) => {
const args = { homeDir: root, repos: [], includeCwd: false, sourceKinds: ['home' as const] }
await repair.discoverSkills({ ...args, names: ['skill-0'] })
observed.opens = 0
const results = await Promise.all(
[0, 1].map((index) =>
repair.discoverSkills({ ...args, names: [`skill-${index}`], refresh: true })
)
)
expect(results.map((result) => result.skills[0]?.name)).toEqual(['skill-0', 'skill-1'])
expect(observed.opens).toBe(96)
})
})
it('filters aliases before deduplication so excluded bundled roots cannot own home results', async () => {
await fixture(async (root) => {
const bundled = join(root, '.codex', 'skills', '.system', 'bundle')
@@ -1,139 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { OrcadActivationRecord } from '../managed-server/activation/orcad-activation-record'
import type { OrcadSlotOptions } from './orcad-recovery-slot'
import { getRemoteHostPlatform } from './ssh-remote-platform'
const mocks = vi.hoisted(() => ({
wake: vi.fn(),
readRecord: vi.fn(),
probe: vi.fn(),
launch: vi.fn(),
fenceRefusal: vi.fn()
}))
vi.mock('../managed-server/activation/orcad-activation-fence-hold', () => ({
orcadActivationFenceRefusal: mocks.fenceRefusal
}))
vi.mock('../managed-server/lifecycle/orcad-managed-wake', () => ({
wakeStoppedManagedOrcad: mocks.wake
}))
vi.mock('../managed-server/activation/orcad-activation-record-store', () => ({
readOrcadActivationRecord: mocks.readRecord
}))
vi.mock('../managed-server/install/orcad-remote-build-hash', () => ({
readRemoteOrcadBuildHash: async () => 'hash'
}))
vi.mock('./orcad-active-readiness', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
probeActiveOrcadReadiness: mocks.probe,
launchOrcadSlotAndAwaitReadiness: mocks.launch
}))
const { adoptRunningOrcadVersion } = await import('./orcad-running-version-adoption')
const LOCAL = '1.5.0+new'
const RUNNING = '1.4.0+old'
const READINESS = { runtimeId: 'r' }
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every remote step is mocked, so only the paths are read.
const slot = {
host: getRemoteHostPlatform('linux-x64'),
remoteHome: '/home/u',
userDataDir: '/home/u/.orca'
} as OrcadSlotOptions
const record = (active: string | null): OrcadActivationRecord => ({
schemaVersion: 1,
active,
previous: null,
activatedAt: null,
snapshot: null
})
beforeEach(() => {
vi.resetAllMocks()
mocks.readRecord.mockResolvedValue(record(RUNNING))
mocks.probe.mockResolvedValue(READINESS)
})
describe('adoptRunningOrcadVersion', () => {
it('leaves an empty host, or one on this build, to the deploy', async () => {
await expect(adoptRunningOrcadVersion(slot, record(null), LOCAL)).resolves.toBeNull()
await expect(adoptRunningOrcadVersion(slot, record(LOCAL), LOCAL)).resolves.toBeNull()
expect(mocks.wake).not.toHaveBeenCalled()
})
it('adopts the serving build, proven against its own installed bytes', async () => {
mocks.wake.mockResolvedValue({ outcome: 'serving' })
await expect(adoptRunningOrcadVersion(slot, record(RUNNING), LOCAL)).resolves.toMatchObject({
version: RUNNING,
readiness: READINESS
})
expect(mocks.probe).toHaveBeenCalledWith(
expect.objectContaining({ remoteInstallDir: expect.stringContaining(RUNNING) }),
{ buildHash: 'hash', fullVersion: RUNNING }
)
expect(mocks.launch).not.toHaveBeenCalled()
})
it('adopts a stopped build the wake restarted', async () => {
mocks.wake.mockResolvedValue({ outcome: 'started', readiness: READINESS })
await expect(adoptRunningOrcadVersion(slot, record(RUNNING), LOCAL)).resolves.toMatchObject({
version: RUNNING
})
})
it('defers to the deploy when the host was stopped, or moved to this build meanwhile', async () => {
mocks.wake.mockResolvedValueOnce({ outcome: 'not-activated' })
await expect(adoptRunningOrcadVersion(slot, record(RUNNING), LOCAL)).resolves.toBeNull()
mocks.wake.mockResolvedValueOnce({ outcome: 'serving' })
mocks.readRecord.mockResolvedValueOnce(record(LOCAL))
await expect(adoptRunningOrcadVersion(slot, record(RUNNING), LOCAL)).resolves.toBeNull()
expect(mocks.probe).not.toHaveBeenCalled()
})
// #28833: peer startup must defer adoption; an unpaired deploy cannot census the running host.
it('waits on a fence another run holds instead of deploying over an uncounted host', async () => {
mocks.wake.mockResolvedValueOnce({ outcome: 'fenced' })
mocks.fenceRefusal.mockResolvedValueOnce({
code: 'orcad_activation_fence_busy',
reason: 'busy'
})
await expect(adoptRunningOrcadVersion(slot, record(RUNNING), LOCAL)).resolves.toEqual({
outcome: 'deferred',
candidateVersion: LOCAL,
code: 'orcad_activation_fence_busy',
reason: 'busy',
forceable: false
})
expect(mocks.probe).not.toHaveBeenCalled()
})
it('retries the wake once after clearing an abandoned fence', async () => {
mocks.wake.mockResolvedValueOnce({ outcome: 'fenced' })
mocks.fenceRefusal.mockResolvedValueOnce({ code: 'c', reason: 'cleared', cleared: true })
mocks.wake.mockResolvedValueOnce({ outcome: 'started', readiness: READINESS })
await expect(adoptRunningOrcadVersion(slot, record(RUNNING), LOCAL)).resolves.toMatchObject({
version: RUNNING
})
mocks.wake.mockResolvedValue({ outcome: 'fenced' })
mocks.fenceRefusal.mockResolvedValue({ code: 'c', reason: 'cleared', cleared: true })
await expect(adoptRunningOrcadVersion(slot, record(RUNNING), LOCAL)).resolves.toMatchObject({
outcome: 'deferred'
})
expect(mocks.wake).toHaveBeenCalledTimes(4)
})
it('reports an unverifiable or unrecoverable host without touching it', async () => {
mocks.wake.mockResolvedValueOnce({ outcome: 'unverifiable' })
await expect(adoptRunningOrcadVersion(slot, record(RUNNING), LOCAL)).rejects.toMatchObject({
verdict: 'unverifiable'
})
mocks.wake.mockResolvedValueOnce({ outcome: 'recovery-refused', reason: 'Recover it first.' })
await expect(adoptRunningOrcadVersion(slot, record(RUNNING), LOCAL)).rejects.toThrow(
'Recover it first.'
)
expect(mocks.launch).not.toHaveBeenCalled()
})
})
-58
View File
@@ -31,23 +31,6 @@ function fakeBase(width: number, height: number): FakeImage {
const AMBER = { b: 0x0b, g: 0x9e, r: 0xf5 }
describe('composeTrayAttentionIcon', () => {
it('returns the base unchanged when it has no pixels', () => {
const base = { getSize: () => ({ width: 0, height: 0 }), toBitmap: () => Buffer.alloc(0) }
expect(composeTrayAttentionIcon(base as never)).toBe(base)
expect(createFromBitmapMock).not.toHaveBeenCalled()
})
it('builds a new image of the same size', () => {
createFromBitmapMock.mockClear()
const result = composeTrayAttentionIcon(fakeBase(16, 16) as never)
expect(createFromBitmapMock).toHaveBeenCalledTimes(1)
const [, options] = createFromBitmapMock.mock.calls[0]
expect(options).toEqual({ width: 16, height: 16 })
expect(result).toMatchObject({ __image: true, width: 16, height: 16 })
})
it('paints an amber dot in the top-right corner and leaves the rest untouched', () => {
createFromBitmapMock.mockClear()
const width = 16
@@ -86,33 +69,6 @@ describe('composeTrayAttentionIcon', () => {
})
describe('tintTrayTemplateForAttention', () => {
it('preserves alpha while selecting literal black pixels for a light menu bar', () => {
createFromBitmapMock.mockClear()
const bitmap = Buffer.from([0x22, 0x33, 0x44, 0x80, 0xaa, 0xbb, 0xcc, 0x00])
const base = {
getSize: () => ({ width: 2, height: 1 }),
toBitmap: () => bitmap
}
tintTrayTemplateForAttention(base as never, false)
expect(createFromBitmapMock.mock.calls[0][0]).toEqual(
Buffer.from([0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00])
)
})
it('selects literal white pixels for a dark menu bar', () => {
createFromBitmapMock.mockClear()
const base = {
getSize: () => ({ width: 1, height: 1 }),
toBitmap: () => Buffer.from([0x00, 0x00, 0x00, 0xff])
}
tintTrayTemplateForAttention(base as never, true)
expect(createFromBitmapMock.mock.calls[0][0]).toEqual(Buffer.from([0xff, 0xff, 0xff, 0xff]))
})
it('keeps white edge pixels premultiplied-valid at partial alpha', () => {
createFromBitmapMock.mockClear()
const base = {
@@ -125,18 +81,4 @@ describe('tintTrayTemplateForAttention', () => {
// Premultiplied alpha requires 0x80 channels for half-covered white, not 0xff.
expect(createFromBitmapMock.mock.calls[0][0]).toEqual(Buffer.from([0x80, 0x80, 0x80, 0x80]))
})
it('reads and sizes the requested scale factor', () => {
createFromBitmapMock.mockClear()
const toBitmap = vi.fn(() => Buffer.from([0x00, 0x00, 0x00, 0xff]))
const base = {
getSize: () => ({ width: 1, height: 1 }),
toBitmap
}
tintTrayTemplateForAttention(base as never, true, 2)
expect(toBitmap).toHaveBeenCalledWith({ scaleFactor: 2 })
expect(createFromBitmapMock.mock.calls[0][1]).toEqual({ width: 2, height: 2 })
})
})
@@ -8,7 +8,6 @@ const {
autoUpdaterMock,
getLinuxRootPackageTypeMock,
isExternallyManagedLinuxInstallMock,
recordUpdaterLifecycleMock,
fetchNewerReleaseTagsMock,
moduleFactories,
resetUpdaterMocks
@@ -64,29 +63,6 @@ describe('updater externally managed Linux installs', () => {
return send.mock.calls.findLast(([channel]) => channel === 'updater:status')?.[1]
}
it('still reports the available release so the user can update through their distribution', async () => {
const { send, updater } = await startUpdater({ packageType: 'deb', externallyManaged: true })
updater.checkForUpdatesFromMenu()
await vi.advanceTimersByTimeAsync(0)
expect(lastStatus(send)).toEqual({
state: 'available',
version: '1.0.61',
changelog: null,
externallyManaged: true
})
})
it('does not flag a real deb host', async () => {
const { send, updater } = await startUpdater({ packageType: 'deb', externallyManaged: false })
updater.checkForUpdatesFromMenu()
await vi.advanceTimersByTimeAsync(0)
const status = lastStatus(send)
expect(status).toEqual({ state: 'available', version: '1.0.61', changelog: null })
expect(status && 'externallyManaged' in status).toBe(false)
})
it('refuses the download instead of spending it on a package it can never install', async () => {
const { send, updater } = await startUpdater({ packageType: 'deb', externallyManaged: true })
updater.checkForUpdatesFromMenu()
@@ -105,19 +81,6 @@ describe('updater externally managed Linux installs', () => {
})
})
it('records the blocked download for field diagnosis', async () => {
const { updater } = await startUpdater({ packageType: 'deb', externallyManaged: true })
updater.checkForUpdatesFromMenu()
await vi.advanceTimersByTimeAsync(0)
updater.downloadUpdate()
await vi.advanceTimersByTimeAsync(0)
expect(recordUpdaterLifecycleMock).toHaveBeenCalledWith(
'linux_package_externally_managed_download_blocked',
{ version: '1.0.61' }
)
})
it('leaves an ordinary deb host able to download', async () => {
const { updater } = await startUpdater({ packageType: 'deb', externallyManaged: false })
updater.checkForUpdatesFromMenu()
@@ -129,16 +92,4 @@ describe('updater externally managed Linux installs', () => {
expect(autoUpdaterMock.downloadUpdate).toHaveBeenCalled()
})
it('leaves an AppImage host able to download', async () => {
const { updater } = await startUpdater({ packageType: null, externallyManaged: false })
updater.checkForUpdatesFromMenu()
await vi.advanceTimersByTimeAsync(0)
autoUpdaterMock.downloadUpdate.mockResolvedValue([])
updater.downloadUpdate()
await vi.advanceTimersByTimeAsync(0)
expect(autoUpdaterMock.downloadUpdate).toHaveBeenCalled()
})
})
@@ -60,25 +60,6 @@ describe('requestTerminalTabCloseFromRenderer', () => {
await expect(pending).resolves.toBeUndefined()
})
it('propagates renderer cancellation instead of reporting success', async () => {
const { requestTerminalTabCloseFromRenderer } =
await import('./terminal-tab-close-request-relay')
const webContents = { isDestroyed: () => false, send: vi.fn() }
const pending = requestTerminalTabCloseFromRenderer(
{ isDestroyed: () => false, webContents } as never,
'tab-pinned'
)
const request = webContents.send.mock.calls[0]?.[1] as { requestId: string }
ipcEmitter.emit(
'ui:terminalTabCloseResponse',
{ sender: webContents },
{ requestId: request.requestId, error: 'terminal_tab_pinned' }
)
await expect(pending).rejects.toThrow('terminal_tab_pinned')
})
it('rejects and cleans up when the BrowserWindow closes and webContents becomes unavailable', async () => {
const { requestTerminalTabCloseFromRenderer } =
await import('./terminal-tab-close-request-relay')
@@ -73,35 +73,6 @@ describe('the guest process table', () => {
expect(readWslGuestPaneShellCwd(twoRoots ?? [], KEY)).toBeNull()
})
it('never starts a distro that is not running', async () => {
isWslDistroRunning.mockResolvedValue(false)
expect(await readWslGuestPaneProcesses('Ubuntu', 3_000)).toBeNull()
expect(runWslProcess).not.toHaveBeenCalled()
})
it('skips the running check for a live pane whose distro was last seen running, settled', async () => {
wasWslDistroLastSeenRunningSettled.mockReturnValue(true)
runWslProcess.mockResolvedValue({
environmentResolved: true,
code: 0,
stdout: fenced(),
stderr: '',
timedOut: false
})
expect(await readWslGuestPaneProcesses('Ubuntu', 3_000, { running: true })).toEqual([])
expect(wasWslDistroLastSeenRunningSettled).toHaveBeenCalledWith('Ubuntu')
expect(isWslDistroRunning).not.toHaveBeenCalled()
})
// Review of #33398: a session the client still holds as live may be in a distro terminated since.
it('still checks a live pane whose distro was not last seen running after any stop settled', async () => {
wasWslDistroLastSeenRunningSettled.mockReturnValue(false)
isWslDistroRunning.mockResolvedValue(false)
expect(await readWslGuestPaneProcesses('Ubuntu', 3_000, { running: true })).toBeNull()
expect(isWslDistroRunning).toHaveBeenCalledWith('Ubuntu')
expect(runWslProcess).not.toHaveBeenCalled()
})
it('runs one fixed script with no login shell, shared by callers asking at once', async () => {
runWslProcess.mockResolvedValue({
environmentResolved: true,
@@ -125,17 +96,4 @@ describe('the guest process table', () => {
})
)
})
it('answers null when the read fails or times out', async () => {
runWslProcess.mockResolvedValue({
environmentResolved: true,
code: null,
stdout: '',
stderr: '',
timedOut: true
})
expect(await readWslGuestPaneProcesses('Ubuntu', 3_000)).toBeNull()
runWslProcess.mockRejectedValue(new Error('wsl.exe missing'))
expect(await readWslGuestPaneProcesses('Ubuntu', 3_000)).toBeNull()
})
})
@@ -1,12 +1,12 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ORCAD_HOST_NEWER_DEFERRAL_CODE } from '../../shared/wsl-host-types'
import type { OrcadActivationRecord } from '../managed-server/activation/orcad-activation-record'
import { recordOrcadUpdateFailedThisLaunch } from '../managed-server/lifecycle/orcad-managed-update-failure-memory'
import type { ServeReadiness } from '../server/serve-readiness'
import { ORCAD_ACTIVATION_POLICY_REFUSED_CODE } from '../ssh/orcad-installed-activation'
import type { OrcadSlotOptions } from '../ssh/orcad-recovery-slot'
import { getRemoteHostPlatform } from '../ssh/ssh-remote-platform'
import { admitWslOrcadUpdate, wslOrcadDeferralCode } from './wsl-orcad-update-admission'
import { admitWslOrcadUpdate } from './wsl-orcad-update-admission'
const { adoptRunningOrcadVersion, readOrcadActivationRecord } = vi.hoisted(() => ({
adoptRunningOrcadVersion: vi.fn(),
@@ -86,38 +86,11 @@ describe('WSL orcad admission across desktop and host versions', () => {
}
expect(adoptRunningOrcadVersion).toHaveBeenCalledWith(slot, running(NEWER, '1.6.0'), OLDER)
})
it('deploys over it only when the user forces the update', async () => {
expect(await admit(running(NEWER, '1.6.0'), olderDesktop, { intent: 'force' })).toEqual({
action: 'deploy',
record: running(NEWER, '1.6.0')
})
expect(adoptRunningOrcadVersion).not.toHaveBeenCalled()
})
it('names a policy refusal over the newer host, which Update cannot pass', async () => {
readOrcadActivationRecord.mockResolvedValue(running(NEWER, '1.6.0'))
expect(
await wslOrcadDeferralCode(
slot,
{ code: ORCAD_ACTIVATION_POLICY_REFUSED_CODE, fullVersion: OLDER },
'1.5.0'
)
).toBe(ORCAD_HOST_NEWER_DEFERRAL_CODE)
})
})
describe('a newer desktop meets the orcad an older desktop deployed', () => {
const newerDesktop = { candidateVersion: NEWER, appVersion: '1.6.0' }
it('updates it on connect', async () => {
expect(await admit(running(OLDER, '1.5.0'), newerDesktop)).toEqual({
action: 'deploy',
record: running(OLDER, '1.5.0')
})
expect(adoptRunningOrcadVersion).not.toHaveBeenCalled()
})
it('keeps the older orcad serving after a failed update, and offers Force', async () => {
const host = { kind: 'wsl' as const, id: 'Ubuntu-failed' }
recordOrcadUpdateFailedThisLaunch(host, { code: 'orcad_activation_failed', reason: 'boom' })
@@ -134,10 +107,5 @@ describe('WSL orcad admission across desktop and host versions', () => {
}
})
})
it('treats an orcad from before app provenance as updatable, not as newer', async () => {
const { activeAppVersion: _omitted, ...legacy } = running(OLDER, '1.5.0')
expect(await admit(legacy, newerDesktop)).toEqual({ action: 'deploy', record: legacy })
})
})
})
@@ -64,77 +64,6 @@ describe('installBrowserExtensionInertApis', () => {
}
})
it('leaves storage alone when the extension has no storage permission', () => {
const realm = { chrome: { runtime: { getManifest: () => ({}) } } }
runInNewContext(`(${installBrowserExtensionInertApis.toString()})('page')`, {
globalThis: realm,
Promise
})
expect(member(realm.chrome, 'storage')).toBeUndefined()
})
it('gives content scripts chrome.extension.inIncognitoContext and dom.openOrClosedShadowRoot', () => {
const { chrome } = install('content-script')
expect(member(chrome, 'extension.inIncognitoContext')).toBe(false)
const shadowRoot = { mode: 'open' }
expect(call(chrome, 'dom.openOrClosedShadowRoot', { shadowRoot })).toBe(shadowRoot)
expect(call(chrome, 'dom.openOrClosedShadowRoot', { shadowRoot: null })).toBeNull()
expect(member(chrome, 'extension.getViews')).toBeUndefined()
expect(member(chrome, 'runtime.onPerformanceWarning')).toBeUndefined()
})
it('adds getViews to pages only, and inert worker and page APIs on chrome and browser', async () => {
const page = install('page', {}, { extension: { getBackgroundPage: () => null } })
expect(call(page.chrome, 'extension.getViews', { type: 'popup' })).toEqual([])
expect(typeof member(page.chrome, 'extension.getBackgroundPage')).toBe('function')
const { chrome, browser } = install('worker', {}, { permissions: {} })
expect(member(chrome, 'extension.getViews')).toBeUndefined()
expect(member(chrome, 'dom')).toBeUndefined()
for (const api of [chrome, browser]) {
const onWarning = member(api, 'runtime.onPerformanceWarning')
const listener = vi.fn()
call(api, 'runtime.onPerformanceWarning.addListener', listener)
expect(call(api, 'runtime.onPerformanceWarning.hasListener', listener)).toBe(true)
expect(onWarning).toBe(member(chrome, 'runtime.onPerformanceWarning'))
await expect(call(api, 'permissions.addHostAccessRequest', { tabId: 1 })).resolves.toBe(
undefined
)
await expect(call(api, 'permissions.removeHostAccessRequest', { tabId: 1 })).resolves.toBe(
undefined
)
}
})
it('installs omnibox for a manifest omnibox key and fontSettings for its permission', async () => {
const without = install('worker')
expect(without.chrome.omnibox).toBeUndefined()
expect(without.chrome.fontSettings).toBeUndefined()
const { chrome } = install('page', {
omnibox: { keyword: 'lp' },
permissions: ['fontSettings']
})
expect(typeof member(chrome, 'omnibox.onInputEntered.addListener')).toBe('function')
expect(member(chrome, 'omnibox.OnInputEnteredDisposition.NEW_FOREGROUND_TAB')).toBe(
'newForegroundTab'
)
const suggested = await new Promise((resolve) =>
call(chrome, 'omnibox.setDefaultSuggestion', { description: 'Search' }, () =>
resolve('called')
)
)
expect(suggested).toBe('called')
const fonts = await new Promise((resolve) => call(chrome, 'fontSettings.getFontList', resolve))
expect(fonts).toContainEqual({ fontId: 'sans-serif', displayName: 'sans-serif' })
await expect(
call(chrome, 'fontSettings.getFont', { genericFamily: 'standard' })
).resolves.toEqual({ fontId: '', levelOfControl: 'not_controllable' })
await expect(
call(chrome, 'fontSettings.setFont', { genericFamily: 'standard', fontId: 'serif' })
).resolves.toBeUndefined()
})
it('keeps members Electron already provides', () => {
const native = vi.fn()
const { chrome } = install('page', {}, { extension: { getViews: native } })
@@ -1,142 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { performContextualTourStepAction } from './contextual-tour-step-actions'
describe('performContextualTourStepAction', () => {
it('opens Tasks after detaching the terminal-owned tour source', () => {
const finishTour = vi.fn()
const advanceContextualTour = vi.fn()
const detachContextualTourSource = vi.fn()
const openTaskPage = vi.fn()
performContextualTourStepAction({
action: { kind: 'open-tasks', label: 'Show tasks' },
activeTabId: 'tab-1',
isLastStep: false,
finishTour,
advanceContextualTour,
detachContextualTourSource,
setSidebarOpen: vi.fn(),
openTaskPage,
openModal: vi.fn(),
openClientHostedBrowserSettings: vi.fn(),
openWorkspaceComposer: vi.fn(),
dispatchTerminalPaneSplit: vi.fn(),
schedule: vi.fn()
})
expect(detachContextualTourSource).toHaveBeenCalledTimes(1)
expect(openTaskPage).toHaveBeenCalledTimes(1)
expect(advanceContextualTour).toHaveBeenCalledTimes(1)
expect(finishTour).not.toHaveBeenCalled()
})
it('dispatches the terminal-pane split action against the active tab', () => {
const dispatchTerminalPaneSplit = vi.fn()
performContextualTourStepAction({
action: { kind: 'split-terminal-pane', label: 'Split terminal' },
activeTabId: 'tab-1',
isLastStep: false,
finishTour: vi.fn(),
advanceContextualTour: vi.fn(),
detachContextualTourSource: vi.fn(),
setSidebarOpen: vi.fn(),
openTaskPage: vi.fn(),
openModal: vi.fn(),
openClientHostedBrowserSettings: vi.fn(),
openWorkspaceComposer: vi.fn(),
dispatchTerminalPaneSplit,
schedule: vi.fn()
})
expect(dispatchTerminalPaneSplit).toHaveBeenCalledWith({
tabId: 'tab-1',
direction: 'vertical'
})
})
it('opens the workspace composer after detaching, without advancing the tour itself', () => {
const detachContextualTourSource = vi.fn()
const openWorkspaceComposer = vi.fn()
const advanceContextualTour = vi.fn()
const finishTour = vi.fn()
performContextualTourStepAction({
action: { kind: 'create-worktree', label: 'Create worktree' },
activeTabId: 'tab-1',
isLastStep: true,
finishTour,
advanceContextualTour,
detachContextualTourSource,
setSidebarOpen: vi.fn(),
openTaskPage: vi.fn(),
openModal: vi.fn(),
openClientHostedBrowserSettings: vi.fn(),
openWorkspaceComposer,
dispatchTerminalPaneSplit: vi.fn(),
schedule: vi.fn()
})
// Opening the composer cancels this tour and hands off, so we neither
// advance nor finish it here.
expect(detachContextualTourSource).toHaveBeenCalledTimes(1)
expect(openWorkspaceComposer).toHaveBeenCalledTimes(1)
expect(advanceContextualTour).not.toHaveBeenCalled()
expect(finishTour).not.toHaveBeenCalled()
})
it('opens the workspace composer with no projects so the first one can be added there', () => {
const detachContextualTourSource = vi.fn()
const openWorkspaceComposer = vi.fn()
performContextualTourStepAction({
action: { kind: 'create-worktree', label: 'Create worktree' },
activeTabId: 'tab-1',
isLastStep: true,
finishTour: vi.fn(),
advanceContextualTour: vi.fn(),
detachContextualTourSource,
setSidebarOpen: vi.fn(),
openTaskPage: vi.fn(),
openModal: vi.fn(),
openClientHostedBrowserSettings: vi.fn(),
openWorkspaceComposer,
dispatchTerminalPaneSplit: vi.fn(),
schedule: vi.fn()
})
expect(detachContextualTourSource).toHaveBeenCalledTimes(1)
expect(openWorkspaceComposer).toHaveBeenCalledTimes(1)
})
it('finishes the tour before opening the client-hosted browser settings', () => {
const finishTour = vi.fn()
const openClientHostedBrowserSettings = vi.fn()
const scheduled: (() => void)[] = []
performContextualTourStepAction({
action: { kind: 'open-client-hosted-browser-settings', label: 'Browser settings' },
activeTabId: 'tab-1',
isLastStep: true,
finishTour,
advanceContextualTour: vi.fn(),
detachContextualTourSource: vi.fn(),
setSidebarOpen: vi.fn(),
openTaskPage: vi.fn(),
openModal: vi.fn(),
openClientHostedBrowserSettings,
openWorkspaceComposer: vi.fn(),
dispatchTerminalPaneSplit: vi.fn(),
schedule: (callback) => {
scheduled.push(callback)
}
})
expect(finishTour).toHaveBeenCalledTimes(1)
expect(openClientHostedBrowserSettings).not.toHaveBeenCalled()
for (const callback of scheduled) {
callback()
}
expect(openClientHostedBrowserSettings).toHaveBeenCalledTimes(1)
})
})
@@ -1,146 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { useAppStore } from '@/store'
import { openWorkspaceCreationComposerWithTourHandoff } from './workspace-creation-tour-handoff'
import { requestContextualTourWhenReady } from './request-contextual-tour-when-ready'
vi.mock('./request-contextual-tour-when-ready', () => ({
requestContextualTourWhenReady: vi.fn()
}))
describe('openWorkspaceCreationComposerWithTourHandoff', () => {
afterEach(() => {
vi.clearAllMocks()
vi.restoreAllMocks()
})
it('opens the workspace composer without a tour handoff outside the agent sessions tour', () => {
const openModal = vi.fn()
const detachContextualTourSource = vi.fn()
vi.spyOn(useAppStore, 'getState').mockImplementation(
() =>
({
activeContextualTourId: null,
activeContextualTourStepIndex: 0,
activeContextualTourSource: null,
activeModal: 'none',
contextualToursSeenIds: [],
repos: [{ id: 'repo-1' }],
detachContextualTourSource,
openModal
}) as unknown as ReturnType<typeof useAppStore.getState>
)
openWorkspaceCreationComposerWithTourHandoff()
expect(openModal).toHaveBeenCalledWith('new-workspace-composer', {
telemetrySource: 'sidebar'
})
expect(detachContextualTourSource).not.toHaveBeenCalled()
expect(requestContextualTourWhenReady).not.toHaveBeenCalled()
})
it('hands off from the agent sessions create-worktree step to the workspace-creation tour', () => {
const openModal = vi.fn()
const detachContextualTourSource = vi.fn()
const completeContextualTour = vi.fn()
let activeModal: ReturnType<typeof useAppStore.getState>['activeModal'] = 'none'
vi.spyOn(useAppStore, 'getState').mockImplementation(
() =>
({
activeContextualTourId: 'workspace-agent-sessions',
activeContextualTourStepIndex: 1,
activeContextualTourSource: 'setup_guide_parallel_work',
activeModal,
contextualToursSeenIds: [],
repos: [{ id: 'repo-1' }],
detachContextualTourSource,
completeContextualTour,
openModal
}) as unknown as ReturnType<typeof useAppStore.getState>
)
openWorkspaceCreationComposerWithTourHandoff()
expect(detachContextualTourSource).toHaveBeenCalledWith(
'workspace-agent-sessions',
'setup_guide_parallel_work'
)
expect(completeContextualTour).toHaveBeenCalledWith('workspace-agent-sessions')
expect(openModal).toHaveBeenCalledWith('new-workspace-composer', {
telemetrySource: 'sidebar',
contextualTourSource: 'workspace_creation_modal'
})
expect(requestContextualTourWhenReady).toHaveBeenCalledWith({
id: 'workspace-creation',
source: 'workspace_creation_modal',
wasFeaturePreviouslyInteracted: false,
waitForActiveTourToClear: true,
shouldContinue: expect.any(Function)
})
const [{ shouldContinue }] = vi.mocked(requestContextualTourWhenReady).mock.calls[0]
activeModal = 'new-workspace-composer'
expect(shouldContinue?.()).toBe(true)
activeModal = 'none'
expect(shouldContinue?.()).toBe(false)
})
it('opens the composer without re-showing workspace creation when that tour was already seen', () => {
const openModal = vi.fn()
const detachContextualTourSource = vi.fn()
const completeContextualTour = vi.fn()
vi.spyOn(useAppStore, 'getState').mockImplementation(
() =>
({
activeContextualTourId: 'workspace-agent-sessions',
activeContextualTourStepIndex: 1,
activeContextualTourSource: 'setup_guide_parallel_work',
activeModal: 'none',
contextualToursSeenIds: ['workspace-creation'],
repos: [{ id: 'repo-1' }],
detachContextualTourSource,
completeContextualTour,
openModal
}) as unknown as ReturnType<typeof useAppStore.getState>
)
openWorkspaceCreationComposerWithTourHandoff()
expect(detachContextualTourSource).toHaveBeenCalledWith(
'workspace-agent-sessions',
'setup_guide_parallel_work'
)
expect(completeContextualTour).toHaveBeenCalledWith('workspace-agent-sessions')
expect(openModal).toHaveBeenCalledWith('new-workspace-composer', {
telemetrySource: 'sidebar',
contextualTourSource: 'workspace_creation_modal'
})
expect(requestContextualTourWhenReady).not.toHaveBeenCalled()
})
it('opens the composer without a project so the empty form can guide setup', () => {
const openModal = vi.fn()
const detachContextualTourSource = vi.fn()
vi.spyOn(useAppStore, 'getState').mockImplementation(
() =>
({
activeContextualTourId: 'workspace-agent-sessions',
activeContextualTourStepIndex: 1,
activeContextualTourSource: 'setup_guide_parallel_work',
activeModal: 'none',
contextualToursSeenIds: [],
repos: [],
detachContextualTourSource,
openModal
}) as unknown as ReturnType<typeof useAppStore.getState>
)
openWorkspaceCreationComposerWithTourHandoff()
expect(detachContextualTourSource).not.toHaveBeenCalled()
expect(openModal).toHaveBeenCalledWith('new-workspace-composer', {
telemetrySource: 'sidebar'
})
expect(requestContextualTourWhenReady).not.toHaveBeenCalled()
})
})
@@ -3,7 +3,7 @@
import React, { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { VIRTUALIZED_LIST_MIN_ROWS } from '@/components/virtualized-list'
import type { GitBranchChangeEntry } from '../../../../../../shared/git-diff-compare-types'
import type { CombinedDiffFileTreeRow as CombinedDiffFileTreeRowComponent } from './combined-diff-file-tree-row'
@@ -117,18 +117,6 @@ function renderTree(entries: readonly GitBranchChangeEntry[]): void {
}
describe('combined diff file tree row windowing', () => {
it('mounts every row below the virtualize threshold', () => {
const fileCount = VIRTUALIZED_LIST_MIN_ROWS - 10
const directoryCount = 4
renderTree(buildEntries(fileCount, directoryCount))
const totalRows = 1 + directoryCount + fileCount
expect(totalRows).toBeLessThan(VIRTUALIZED_LIST_MIN_ROWS)
expect(mountedRows.count).toBe(totalRows)
expect(host.querySelector('[data-testid="virtualized-list"]')).toBeNull()
expect(host.querySelectorAll('[data-index]').length).toBe(0)
})
it('mounts only a window of rows for a large review', () => {
const fileCount = 900
const directoryCount = 30
@@ -7,7 +7,6 @@ import { createRichMarkdownExtensions } from './rich-markdown-extensions'
import { createRichMarkdownEditorCodec } from './rich-markdown-source-transport'
import { useLocalImagePick } from './useLocalImagePick'
import { handleRichMarkdownImagePaste } from './rich-markdown-paste-image'
import { runSlashCommand, slashCommands } from './rich-markdown-slash-commands'
vi.mock('@/runtime/runtime-file-import-client', () => ({
importExternalPathsToRuntime: vi.fn().mockResolvedValue({
@@ -102,20 +101,6 @@ describe('inserting an image while the cursor is inside a fenced code block', ()
expectFencedSplit(editor)
})
it('keeps both halves fenced when the slash command inserts the image', async () => {
const imageCommand = slashCommands.find((command) => command.id === 'image')
expect(imageCommand).toBeDefined()
const { result } = renderHook(() => useLocalImagePick(editor as never, '/repo/note.md', 'wt-1'))
const from = editor.state.selection.from
runSlashCommand(editor as never, { from, to: from }, imageCommand!, () => {
void result.current()
})
await flushPromises()
expectFencedSplit(editor)
})
it('keeps both halves fenced when a clipboard screenshot is pasted', async () => {
const handled = handleRichMarkdownImagePaste({
editor: editor as never,
@@ -131,16 +116,4 @@ describe('inserting an image while the cursor is inside a fenced code block', ()
expect(handled).toBe(true)
expectFencedSplit(editor)
})
it('still inserts the image inline when the cursor is in ordinary prose', async () => {
editor.destroy()
editor = mountRichMarkdownEditor('Install the extension\n')
editor.commands.setTextSelection(13)
const { result } = renderHook(() => useLocalImagePick(editor as never, '/repo/note.md', 'wt-1'))
await result.current()
await flushPromises()
expect(editor.getMarkdown().trimEnd()).toBe('Install the ![](shot.png)extension')
})
})
@@ -52,54 +52,6 @@ function diffEditorFixture(
}
describe('useDiffViewerLargeDiffLifecycle', () => {
it('keeps repeated content rotations bounded to the current Monaco models', async () => {
const modelKey = 'diff-tab'
const originalModelKey = 'original-v1'
const onEnterFallback = vi.fn()
const paths = ['modified-v1', 'modified-v2', 'modified-v3'].map((modifiedModelKey) =>
getDiffViewerMonacoModelPaths({
modelKey,
originalModelKey,
modifiedModelKey,
generationSuffix: ''
})
)
const firstModel = detachedModel()
const secondModel = detachedModel()
const currentModel = detachedModel()
const originalModel = detachedModel()
monacoFixture.models.set(paths[0].originalModelPath, originalModel)
monacoFixture.models.set(paths[0].modifiedModelPath, firstModel)
monacoFixture.models.set(paths[1].modifiedModelPath, secondModel)
monacoFixture.models.set(paths[2].modifiedModelPath, currentModel)
const retainedEditor = diffEditorFixture(originalModel, firstModel)
const hook = renderHook(
({ modifiedModelKey }) =>
useDiffViewerLargeDiffLifecycle({
limited: false,
modelKey,
originalModelKey,
modifiedModelKey,
diffEditorRef: retainedEditor,
onEnterFallback
}),
{ initialProps: { modifiedModelKey: 'modified-v1' } }
)
hook.rerender({ modifiedModelKey: 'modified-v2' })
await act(() => Promise.resolve())
hook.rerender({ modifiedModelKey: 'modified-v3' })
await act(() => Promise.resolve())
expect(firstModel.dispose).toHaveBeenCalledOnce()
expect(secondModel.dispose).toHaveBeenCalledOnce()
expect(currentModel.dispose).not.toHaveBeenCalled()
expect(retainedEditor.setModel).toHaveBeenCalledTimes(2)
expect(hook.result.current).toEqual(paths[2])
expect(onEnterFallback).not.toHaveBeenCalled()
})
it('resets the owning diff widget before disposing a superseded model', async () => {
const modelKey = 'diff-tab'
const paths = ['modified-v1', 'modified-v2'].map((modifiedModelKey) =>
@@ -61,24 +61,6 @@ describe('native chat launch-default adoption', () => {
expect(resolveNativeChatSessionOptionDefaults(defaults.read(), 'claude')).toBeUndefined()
})
it('does not adopt an unlisted launch-flag model before discovery either', async () => {
// A tracked flag proves emission, not that a catalog recognizes the model.
seedNativeChatAppliedSessionOptions('pty-1', 'claude', { model: 'claude-opus-5' })
const defaults = claudeLaunchDefaults()
const surface = createNativeChatPtySessionOptions({
agent: 'claude',
scopeKey: 'pty-1',
mode: 'live',
modelLabel: 'Model',
dispatchCommand: vi.fn().mockResolvedValue({ outcome: 'applied' }),
persistSelection: defaults.persistSelection
})!
await surface.setOption('model', 'claude-opus-5')
expect(defaults.read().claude?.model).toBeUndefined()
})
it('still adopts a model only the host catalog lists', async () => {
seedNativeChatAppliedSessionOptions('pty-1', 'claude', { model: 'sonnet' })
const defaults = claudeLaunchDefaults()
@@ -119,21 +101,4 @@ describe('native chat launch-default adoption', () => {
effort: 'high'
})
})
it('still adopts a tracked seed model before any discovery', async () => {
seedNativeChatAppliedSessionOptions('pty-1', 'claude', { model: 'opus', effort: 'xhigh' })
const defaults = claudeLaunchDefaults()
const surface = createNativeChatPtySessionOptions({
agent: 'claude',
scopeKey: 'pty-1',
mode: 'live',
modelLabel: 'Model',
dispatchCommand: vi.fn(),
persistSelection: defaults.persistSelection
})!
await surface.setOption('effort', 'high')
expect(defaults.read().claude?.model).toBe('opus')
})
})
@@ -26,29 +26,6 @@ beforeEach(() => {
})
describe('a mounted structured chat', () => {
it('holds the session while it is on screen and releases it on unmount', async () => {
const { unmount } = renderHook(() =>
useStructuredAgentSessionHold({
sessionId: 'session-alpha',
target: LOCAL_TARGET,
surface: 'desktop-chat'
})
)
await waitFor(() => expect(callsTo('agentSession.hold')).toHaveLength(1))
const held = callsTo('agentSession.hold')[0] as { sessionId: string; holderId: string }
expect(held.sessionId).toBe('session-alpha')
expect(callsTo('agentSession.release')).toHaveLength(0)
unmount()
await waitFor(() =>
expect(callsTo('agentSession.release')).toEqual([
{ sessionId: 'session-alpha', holderId: held.holderId }
])
)
})
it('does not release a hold that has not landed yet', async () => {
let settleHold = (): void => {}
mocks.call.mockImplementation((_target: unknown, method: string) =>
@@ -76,26 +53,6 @@ describe('a mounted structured chat', () => {
await waitFor(() => expect(callsTo('agentSession.release')).toHaveLength(1))
})
it('reports a hold the host refused and still releases on unmount', async () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const refused = new Error('structured_agent_session_unsupported')
mocks.call.mockImplementation((_target: unknown, method: string) =>
method === 'agentSession.hold' ? Promise.reject(refused) : Promise.resolve()
)
const { unmount } = renderHook(() =>
useStructuredAgentSessionHold({
sessionId: 'session-alpha',
target: LOCAL_TARGET,
surface: 'desktop-chat'
})
)
await waitFor(() => expect(warn).toHaveBeenCalledWith(expect.any(String), refused))
unmount()
await waitFor(() => expect(callsTo('agentSession.release')).toHaveLength(1))
warn.mockRestore()
})
it('keeps one hold across re-renders that rebuild the target object', async () => {
const { rerender, unmount } = renderHook(
(props: { sessionId: string }) =>
@@ -12,46 +12,6 @@ vi.mock('@/runtime/runtime-rpc-client', () => ({
callRuntimeRpc: permission.settings
}))
it('reveals before launch Retry, but waits for a successful queue Resume', async () => {
const order: string[] = []
const controller = {
capturePresentation: () => () => true,
respond: vi.fn(async () => null),
queuedMessages: {
queueCapable: true,
clearCapable: true,
cards: [],
pause: null,
clear: vi.fn(async () => true),
queuePending: null,
pending: () => null,
resume: vi.fn(async () => {
order.push('resume')
return true
}),
steer: vi.fn(async () => {}),
remove: vi.fn(async () => {}),
edit: vi.fn(async () => {}),
editCapable: false,
editor: undefined,
steerHead: vi.fn(() => false),
isWorking: false
}
}
const retryLaunch = (): number => order.push('launch')
const { result } = renderHook(() => useStructuredNativeChatSubmitReveal(controller, retryLaunch))
result.current.messageListRef.current = {
revealLatest: () => order.push('reveal'),
revealFindMatch: () => {},
messageCopyText: () => null
}
act(() => result.current.retryLaunch())
await act(() => result.current.queuedMessages.resume())
expect(order).toEqual(['reveal', 'launch', 'resume', 'reveal'])
})
// STA-10671: an older host held for its permission mode would run a queued card unprompted.
it('sends no queued card and retries no start on a host held for its permission mode', async () => {
const queuedMessages = {
@@ -1,141 +0,0 @@
// @vitest-environment happy-dom
import React, { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { Repo } from '../../../../shared/repo-types'
import { CreateFromPicker } from './CreateFromPicker'
import { TooltipProvider } from '@/components/ui/tooltip'
import {
getRuntimeRepoBaseRefDefault,
searchRuntimeRepoBaseRefs
} from '@/runtime/runtime-repo-client'
vi.mock('@/components/ui/popover', () => ({
// Why: the real popover owns open state; expose onOpenChange so a test can open the list.
Popover: ({
children,
onOpenChange
}: {
children: React.ReactNode
onOpenChange?: (open: boolean) => void
}) => (
<div>
<button type="button" data-testid="open-popover" onClick={() => onOpenChange?.(true)} />
{children}
</div>
),
PopoverContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
PopoverTrigger: ({ children }: { children: React.ReactNode }) => <>{children}</>
}))
vi.mock('@/components/ui/command', () => ({
Command: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
CommandEmpty: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
CommandInput: () => <input />,
CommandItem: ({ children }: { children: React.ReactNode }) => <button>{children}</button>,
CommandList: ({ children }: { children: React.ReactNode }) => <div>{children}</div>
}))
const storeState = {
settings: { activeRuntimeEnvironmentId: 'focused-runtime' },
repos: [] as Pick<Repo, 'id' | 'connectionId' | 'executionHostId'>[]
}
vi.mock('@/store', () => ({
useAppStore: (selector: (state: typeof storeState) => unknown) => selector(storeState)
}))
vi.mock('@/runtime/runtime-repo-client', () => ({
getRuntimeRepoBaseRefDefault: vi.fn().mockResolvedValue({
defaultBaseRef: 'main',
remoteCount: 1
}),
searchRuntimeRepoBaseRefs: vi.fn().mockResolvedValue([])
}))
let container: HTMLDivElement
let root: Root
function repoMapFor(repo: Repo): Map<string, Repo> {
return new Map([[repo.id, repo]])
}
function makeRepo(overrides: Partial<Repo>): Repo {
return {
id: 'repo-1',
path: '/repo',
displayName: 'Repo',
badgeColor: '#000000',
addedAt: 1,
...overrides
}
}
async function renderPicker(repo: Repo): Promise<void> {
await act(async () => {
root.render(
<TooltipProvider>
<CreateFromPicker
repoId={repo.id}
repoMap={repoMapFor(repo)}
worktrees={[]}
value=""
onValueChange={vi.fn()}
/>
</TooltipProvider>
)
})
}
describe('CreateFromPicker host routing', () => {
beforeEach(() => {
container = document.createElement('div')
document.body.appendChild(container)
root = createRoot(container)
vi.clearAllMocks()
})
afterEach(() => {
act(() => {
root.unmount()
})
container.remove()
storeState.repos = []
})
it('uses the selected runtime-owned repo host instead of the focused runtime', async () => {
const repo = makeRepo({ executionHostId: 'runtime:owner-runtime' })
storeState.repos = [repo]
await renderPicker(repo)
expect(getRuntimeRepoBaseRefDefault).toHaveBeenCalledWith('runtime:owner-runtime', repo.id)
})
// Empty queries must list all repo branches instead of only defaults/worktree branches.
it('lists the repo branches as soon as it opens, with no query typed', async () => {
const repo = makeRepo({ executionHostId: 'local' })
storeState.repos = [repo]
await renderPicker(repo)
await act(async () => {
container.querySelector<HTMLButtonElement>('[data-testid="open-popover"]')?.click()
})
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 250))
})
expect(searchRuntimeRepoBaseRefs).toHaveBeenCalledWith('local', repo.id, '', 30)
})
it('keeps an explicit local repo on the local client even when a runtime is focused', async () => {
const repo = makeRepo({ executionHostId: 'local' })
storeState.repos = [repo]
await renderPicker(repo)
expect(getRuntimeRepoBaseRefDefault).toHaveBeenCalledWith('local', repo.id)
expect(searchRuntimeRepoBaseRefs).not.toHaveBeenCalled()
})
})
@@ -7,7 +7,6 @@ import type { Repo } from '../../../../shared/repo-types'
import { makeWorktree } from '@/store/slices/worktrees-slice-test-fixtures'
import { useAppStore } from '@/store'
import { McpConfigSection } from './McpConfigSection'
import { toast } from 'sonner'
const runtimeFiles = vi.hoisted(() => ({
readRuntimeDirectory: vi.fn(),
@@ -105,24 +104,6 @@ it('inspects and creates a managed-host repo config on that host, never the desk
expect(desktopFs.writeFile).not.toHaveBeenCalled()
})
it('keeps a desktop repo on the desktop while a managed host is focused', async () => {
const desktopRepo: Repo = { ...repo, executionHostId: 'local' }
useAppStore.setState({
settings: { ...getDefaultSettings('/tmp'), activeRuntimeEnvironmentId: 'host-a' },
repos: [desktopRepo],
worktreesByRepo: {
repo: [makeWorktree({ id: worktreeId, repoId: 'repo', path: root, isMainWorktree: true })]
}
})
render(<McpConfigSection repo={desktopRepo} />)
await waitFor(() => expect(runtimeFiles.readRuntimeDirectory).toHaveBeenCalled())
expect(runtimeFiles.readRuntimeDirectory).toHaveBeenCalledWith(
expect.objectContaining({ target: { kind: 'local' } }),
root
)
})
it('does not offer the starter over a listed config the host refuses to read', async () => {
runtimeFiles.readRuntimeDirectory.mockResolvedValue([{ name: '.mcp.json', isDirectory: false }])
runtimeFiles.readRuntimeFileContent.mockRejectedValue(new Error('Remote file is too large'))
@@ -131,14 +112,3 @@ it('does not offer the starter over a listed config the host refuses to read', a
await screen.findByText('Remote file is too large')
expect(screen.queryByRole('button', { name: /Add MCP config/ })).toBeNull()
})
it('does not replace a config another process created after the listing', async () => {
runtimeFiles.writeRuntimeFile.mockResolvedValue({ ok: false, diskConflict: 'changed' })
render(<McpConfigSection repo={repo} />)
fireEvent.click(await screen.findByRole('button', { name: /Add MCP config/ }))
fireEvent.click(await screen.findByRole('button', { name: /Create empty config/ }))
await waitFor(() => expect(toast.error).toHaveBeenCalled())
expect(toast.success).not.toHaveBeenCalled()
})
@@ -70,23 +70,6 @@ describe('settings orca.yaml inspection', () => {
// An unmounted hook must drop its retry listeners, or they fire into the next case.
afterEach(cleanup)
it('asks in the background and keeps a stopped distro checking, not "missing"', async () => {
checkRuntimeHooksMock.mockResolvedValue({
status: 'error',
hasHooks: false,
hooks: null,
mayNeedUpdate: false,
unavailable: 'wsl-distro-stopped'
})
const { map } = renderEffects()
await waitFor(() => expect(checkRuntimeHooksMock).toHaveBeenCalled())
// Let the resolved check settle into state before asserting nothing was recorded.
await act(async () => {})
expect(checkRuntimeHooksMock).toHaveBeenCalledWith('local', 'repo-1', { background: true })
expect(map()).toEqual({})
})
it('re-asks when the window comes back and records the distro once it runs', async () => {
const hooks = { scripts: { setup: 'pnpm i' } }
checkRuntimeHooksMock
@@ -134,11 +117,4 @@ describe('settings orca.yaml inspection', () => {
vi.useRealTimers()
}
})
it('records the inspection once the distro answers', async () => {
checkRuntimeHooksMock.mockResolvedValue({ hasHooks: true, hooks: null, mayNeedUpdate: false })
const { map } = renderEffects()
await waitFor(() => expect(Object.keys(map())).toHaveLength(1))
})
})
@@ -36,25 +36,6 @@ function makeSession(
}
describe('commitProjectHeaderDragDrop', () => {
it('commits whole-repo reordering when project groups are absent', () => {
const onCommitRepoOrder = vi.fn()
const repos = [makeRepo('a'), makeRepo('b'), makeRepo('c')]
const repoById = new Map(repos.map((repo) => [repo.id, repo]))
commitProjectHeaderDragDrop({
session: makeSession(makeRepo('c'), ['a', 'b', 'c']),
sidebarDropIndex: 0,
orderedRepoIds: ['a', 'b', 'c'],
repoById,
liveRepos: repos,
usesProjectGroupOrdering: false,
onCommitRepoOrder,
onCommitProjectGroupOrder: vi.fn()
})
expect(onCommitRepoOrder).toHaveBeenCalledWith(['c', 'a', 'b'])
})
it('moves a merged paired-host header upward as one stable block', () => {
const onCommitRepoOrder = vi.fn()
const repos = [makeRepo('b'), makeRepo('same'), makeRepo('c')]
@@ -74,25 +55,6 @@ describe('commitProjectHeaderDragDrop', () => {
expect(onCommitRepoOrder).toHaveBeenCalledWith(['same', 'same', 'b', 'c'])
})
it('does not reorder host occurrences when a merged header stays in place', () => {
const onCommitRepoOrder = vi.fn()
const repos = [makeRepo('b'), makeRepo('same'), makeRepo('c')]
const repoById = new Map(repos.map((repo) => [repo.id, repo]))
commitProjectHeaderDragDrop({
session: makeSession(makeRepo('same'), ['b', 'same', 'c']),
sidebarDropIndex: 2,
orderedRepoIds: ['b', 'same', 'c', 'same'],
repoById,
liveRepos: repos,
usesProjectGroupOrdering: false,
onCommitRepoOrder,
onCommitProjectGroupOrder: vi.fn()
})
expect(onCommitRepoOrder).not.toHaveBeenCalled()
})
it('commits projectGroupOrder for the dragged row, not the id-keyed twin on another host', () => {
const onCommitProjectGroupOrder = vi.fn()
const repos = [
@@ -1,142 +0,0 @@
// @vitest-environment happy-dom
import { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useProjectGroupDialogs, type ProjectGroupDialogs } from './use-project-group-dialogs'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
const mocks = vi.hoisted(() => ({
moveProjectToGroup: vi.fn(),
createProjectGroup: vi.fn(),
updateProjectGroup: vi.fn(),
deleteProjectGroupWithContainedProjects: vi.fn(),
confirmUnsavedDiscard: vi.fn(),
toastError: vi.fn()
}))
vi.mock('@/store', () => ({
useAppStore: (selector: (state: Record<string, unknown>) => unknown) =>
selector({
moveProjectToGroup: mocks.moveProjectToGroup,
createProjectGroup: mocks.createProjectGroup,
updateProjectGroup: mocks.updateProjectGroup,
deleteProjectGroupWithContainedProjects: mocks.deleteProjectGroupWithContainedProjects
})
}))
vi.mock('@/store/slices/worktrees/teardown/removed-workspace-unsaved-files', () => ({
confirmUnsavedDiscard: mocks.confirmUnsavedDiscard
}))
vi.mock('sonner', () => ({
toast: { error: mocks.toastError }
}))
const remoteGroup: ProjectGroup = {
id: 'group-1',
name: 'Projects',
parentPath: '/srv/projects',
parentGroupId: null,
createdFrom: 'folder-scan',
executionHostId: 'runtime:env-1',
tabOrder: 0,
isCollapsed: false,
color: null,
createdAt: 1,
updatedAt: 1
}
let latest: ProjectGroupDialogs | null = null
const roots: Root[] = []
function HookProbe(): null {
latest = useProjectGroupDialogs({
repos: [],
repoMap: new Map(),
projectGroups: [remoteGroup]
})
return null
}
async function renderHookProbe(): Promise<void> {
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
roots.push(root)
await act(async () => {
root.render(<HookProbe />)
})
}
beforeEach(() => {
vi.clearAllMocks()
mocks.updateProjectGroup.mockResolvedValue(true)
mocks.deleteProjectGroupWithContainedProjects.mockResolvedValue({
status: 'deleted-group',
groupId: remoteGroup.id,
requestedProjectIds: [],
removedProjectIds: [],
failedProjectRemovals: []
})
})
afterEach(async () => {
await act(async () => {
for (const root of roots.splice(0)) {
root.unmount()
}
})
latest = null
})
describe('project group dialogs carry the owner host', () => {
it('renames through the host that owns the group row', async () => {
await renderHookProbe()
await act(async () => {
latest!.handleRenameProjectGroup(remoteGroup.id, remoteGroup.name, 'runtime:env-1')
})
await act(async () => {
await latest!.handleSubmitProjectGroupName('Renamed')
})
expect(mocks.updateProjectGroup).toHaveBeenCalledWith(
remoteGroup.id,
{ name: 'Renamed' },
{ hostId: 'runtime:env-1' }
)
expect(mocks.toastError).not.toHaveBeenCalled()
})
it('surfaces an unconfirmed-rename toast when the owner host does not answer', async () => {
mocks.updateProjectGroup.mockResolvedValue(false)
await renderHookProbe()
await act(async () => {
latest!.handleRenameProjectGroup(remoteGroup.id, remoteGroup.name, 'runtime:env-1')
})
await act(async () => {
await latest!.handleSubmitProjectGroupName('Renamed')
})
expect(mocks.toastError).toHaveBeenCalledWith('Failed to rename group', {
description:
"Orca could not confirm the new name with the group's host. Recheck the group after reconnecting."
})
})
it('deletes through the host that owns the group row', async () => {
await renderHookProbe()
await act(async () => {
latest!.handleDeleteProjectGroup(remoteGroup.id, remoteGroup.name, 'runtime:env-1')
})
await act(async () => {
await latest!.handleConfirmDeleteProjectGroup()
})
expect(mocks.deleteProjectGroupWithContainedProjects).toHaveBeenCalledWith(remoteGroup.id, {
removeContainedProjects: false,
hostId: 'runtime:env-1'
})
expect(mocks.confirmUnsavedDiscard).toHaveBeenCalledTimes(1)
})
})
@@ -1,42 +1,9 @@
import type { VirtualItem } from '@tanstack/react-virtual'
import type { WorktreeItemRow } from '../listing/renderable-rows'
import { makePRRefreshWorktree } from '@/store/slices/github-slice-test-harness'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
installWorktreeVisibleRefreshVisibilityListener,
visibleReviewCardIds,
installVisibleReviewCardScrollListener
} from './use-visible-review-refresh'
import { describe, expect, it } from 'vitest'
describe('installWorktreeVisibleRefreshVisibilityListener', () => {
beforeEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
})
it('subscribes to document visibility changes so visible PR refresh can rerun on return', () => {
const listeners = new Map<string, () => void>()
const onChange = vi.fn()
const addEventListener = vi.fn((event: string, listener: () => void) => {
listeners.set(event, listener)
})
const removeEventListener = vi.fn()
vi.stubGlobal('document', {
addEventListener,
removeEventListener
})
const cleanup = installWorktreeVisibleRefreshVisibilityListener(onChange)
expect(addEventListener).toHaveBeenCalledWith('visibilitychange', onChange)
listeners.get('visibilitychange')?.()
expect(onChange).toHaveBeenCalledTimes(1)
cleanup()
expect(removeEventListener).toHaveBeenCalledWith('visibilitychange', onChange)
})
})
import { visibleReviewCardIds } from './use-visible-review-refresh'
function row(id: string): WorktreeItemRow {
return {
@@ -74,65 +41,4 @@ describe('actual visible review cards', () => {
})
).toEqual(['visible'])
})
it('includes visible lineage children but excludes offscreen members', () => {
expect(
visibleReviewCardIds({
enabled: true,
renderRows: [{ type: 'lineage-group', key: 'family', rows: [row('parent'), row('child')] }],
virtualItems: [virtualItem(0)],
viewportTop: 0,
viewportHeight: 100,
isOnScreen: (id) => id === 'child'
})
).toEqual(['child'])
})
it('excludes rows when their review decoration is disabled', () => {
expect(
visibleReviewCardIds({
enabled: false,
renderRows: [row('visible')],
virtualItems: [virtualItem(0)],
viewportTop: 0,
viewportHeight: 100
})
).toEqual([])
})
})
describe('scrolling within one virtual lineage row', () => {
it('updates child visibility on scroll without changing virtual indexes and cancels pending frames', () => {
const listeners = new Map<string, EventListenerOrEventListenerObject>()
let frame: FrameRequestCallback = () => {}
const update = vi.fn()
const scroll = {
addEventListener: vi.fn((type: string, callback: EventListenerOrEventListenerObject) => {
listeners.set(type, callback)
}),
removeEventListener: vi.fn()
}
vi.stubGlobal(
'requestAnimationFrame',
vi.fn((callback: FrameRequestCallback) => {
frame = callback
return 1
})
)
const cancel = vi.fn()
vi.stubGlobal('cancelAnimationFrame', cancel)
const cleanup = installVisibleReviewCardScrollListener(scroll, update)
const listener = listeners.get('scroll')
if (typeof listener === 'function') {
listener(new Event('scroll'))
}
expect(update).not.toHaveBeenCalled()
frame(0)
expect(update).toHaveBeenCalledOnce()
if (typeof listener === 'function') {
listener(new Event('scroll'))
}
cleanup()
expect(cancel).toHaveBeenCalledWith(1)
expect(scroll.removeEventListener).toHaveBeenCalledWith('scroll', listener)
vi.unstubAllGlobals()
})
})
@@ -118,24 +118,4 @@ describe('sidebar manual-order drop', () => {
expect(updateMeta).not.toHaveBeenCalled()
expect(toastInfo).not.toHaveBeenCalled()
})
it('reorders silently when Manual is already the sort', async () => {
const worktrees = seedManualOrderedRows(3)
useAppStore.setState({ sortBy: 'manual' })
const ids = worktrees.map((worktree) => worktree.id)
const keys = worktrees.map(getWorktreeHostIdentity)
const reorder = renderReorder(worktrees)
await act(async () => {
reorder.current.reorderWorktrees({
groups: [{ key: GROUP_KEY, worktreeIds: keys }],
sourceGroupKey: GROUP_KEY,
draggedKeys: [keys[0]!],
dropIndex: 2
})
})
expect(manualOrderedIds()).toEqual([ids[1], ids[0], ids[2]])
expect(toastInfo).not.toHaveBeenCalled()
})
})
@@ -1,142 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
const recordFeatureInteractionMock = vi.fn()
const usagePercentageDisplayMock = 'used'
vi.mock(import('react'), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
memo: function memo<T>(component: T): T {
return component
},
useRef: function useRef<T>(current: T): { current: T } {
return { current }
}
}
})
vi.mock('../../store', () => ({
useAppStore: (
selector: (state: {
recordFeatureInteraction: typeof recordFeatureInteractionMock
usagePercentageDisplay: 'used'
}) => unknown
) =>
selector({
recordFeatureInteraction: recordFeatureInteractionMock,
usagePercentageDisplay: usagePercentageDisplayMock
})
}))
vi.mock('./tooltip', () => ({
ProviderIcon: function ProviderIcon(props: Record<string, unknown>) {
return { type: 'ProviderIcon', props }
},
ProviderPanel: function ProviderPanel(props: Record<string, unknown>) {
return { type: 'ProviderPanel', props }
},
barColor: () => 'bg-green-500',
clampUsedPercent: (n: number) => Math.max(0, Math.min(100, Math.round(n)))
}))
vi.mock('@/components/ui/dropdown-menu', () => ({
DropdownMenu: function DropdownMenu(props: Record<string, unknown>) {
return { type: 'DropdownMenu', props }
},
DropdownMenuContent: function DropdownMenuContent(props: Record<string, unknown>) {
return { type: 'DropdownMenuContent', props }
},
DropdownMenuSeparator: function DropdownMenuSeparator() {
return { type: 'DropdownMenuSeparator', props: {} }
},
DropdownMenuTrigger: function DropdownMenuTrigger(props: Record<string, unknown>) {
return { type: 'DropdownMenuTrigger', props }
}
}))
type ReactElementLike = {
type: unknown
props: Record<string, unknown>
}
function findChildByType(node: unknown, typeName: string): ReactElementLike {
const stack = [node]
while (stack.length > 0) {
const current = stack.pop()
if (current == null || typeof current === 'string' || typeof current === 'number') {
continue
}
if (Array.isArray(current)) {
stack.push(...current)
continue
}
const el = current as ReactElementLike
const type = el.type as { name?: string } | string | undefined
const matchedName = typeof type === 'string' ? type : type?.name
if (matchedName === typeName) {
return el
}
if (el.props && 'children' in el.props) {
stack.push(el.props.children)
}
}
throw new Error(`Could not find ${typeName}`)
}
async function renderProviderDetailsMenu(): Promise<unknown> {
const { ProviderDetailsMenu } = await import('./StatusBar')
return ProviderDetailsMenu({
provider: {
provider: 'codex',
status: 'ok',
error: null,
updatedAt: Date.now(),
session: {
usedPercent: 1,
resetsAt: Date.now() + 1_000,
resetDescription: '5h',
windowMinutes: 300
},
weekly: {
usedPercent: 3,
resetsAt: Date.now() + 1_000,
resetDescription: 'wk',
windowMinutes: 10_080
}
},
compact: false,
iconOnly: false,
ariaLabel: 'Open Codex usage details'
})
}
describe('ProviderDetailsMenu focus handoff', () => {
it('lets pointer-outside closes keep focus on the clicked surface', async () => {
const element = await renderProviderDetailsMenu()
const dropdown = findChildByType(element, 'DropdownMenu')
const content = findChildByType(element, 'DropdownMenuContent')
const providerPanel = findChildByType(element, 'ProviderPanel')
expect(dropdown.props.modal).toBe(false)
expect(providerPanel.props.usagePercentageDisplay).toBe('used')
const preventDefault = vi.fn()
;(content.props.onCloseAutoFocus as (event: { preventDefault: () => void }) => void)({
preventDefault
})
expect(preventDefault).not.toHaveBeenCalled()
;(content.props.onPointerDownOutside as () => void)()
;(content.props.onCloseAutoFocus as (event: { preventDefault: () => void }) => void)({
preventDefault
})
expect(preventDefault).toHaveBeenCalledOnce()
preventDefault.mockClear()
;(content.props.onCloseAutoFocus as (event: { preventDefault: () => void }) => void)({
preventDefault
})
expect(preventDefault).not.toHaveBeenCalled()
})
})
@@ -89,24 +89,6 @@ async function createGatedTransport(options: Record<string, unknown> = {}) {
// The user is told when keys typed into a starting remote pane were dropped for safety.
describe('remote pre-bind typing discard notice', () => {
it('tells the pane when startup work made it drop typing from before the terminal bound', async () => {
const onTypingDiscardedBeforeBind = vi.fn()
const { transport, created, waitForCreate } = await createGatedTransport({
keepTypeAheadForNewTerminal: () => true,
releasePreconnectInput: () => false,
onTypingDiscardedBeforeBind
})
expect(transport.sendInput('make the tests pass\r', 'driving')).toBe(true)
const connecting = transport.connect({ url: '', callbacks: {} })
await waitForCreate()
created.resolve({ handle: 'terminal-new' })
await connecting
await completeAttach()
expect(onTypingDiscardedBeforeBind).toHaveBeenCalledTimes(1)
transport.destroy?.()
})
it('says nothing when the typing reaches the terminal it was typed for', async () => {
const onTypingDiscardedBeforeBind = vi.fn()
const { transport, created, waitForCreate } = await createGatedTransport({
@@ -20,23 +20,17 @@ afterEach(() => {
let subscriptionCallbacks: MultiplexSubscriptionCallbacks = null
let resolvedPaneHandle = 'terminal-1'
const {
emitSnapshot,
runtimeCall,
latestSubscribePayload,
subscribeFrameCount,
subscribedTerminalHandles,
resetRemoteRuntimeTransport
} = createRemoteRuntimeTransportMocks({
getCallbacks: () => subscriptionCallbacks,
setCallbacks: (callbacks) => {
subscriptionCallbacks = callbacks
},
getResolvedPaneHandle: () => resolvedPaneHandle,
setResolvedPaneHandle: (handle) => {
resolvedPaneHandle = handle
}
})
const { latestSubscribePayload, subscribeFrameCount, resetRemoteRuntimeTransport } =
createRemoteRuntimeTransportMocks({
getCallbacks: () => subscriptionCallbacks,
setCallbacks: (callbacks) => {
subscriptionCallbacks = callbacks
},
getResolvedPaneHandle: () => resolvedPaneHandle,
setResolvedPaneHandle: (handle) => {
resolvedPaneHandle = handle
}
})
// What a host sends when its window did not mount the pane within its 10 s wait.
function answerUnmountedHostPane(streamId: number): void {
@@ -65,44 +59,6 @@ describe('remote runtime PTY whose host pane is not mounted yet', () => {
resetRemoteRuntimeTransport()
})
it('keeps the pane and reattaches once the host mounts it', async () => {
const onPtyExit = vi.fn()
const { transport, onExit } = await connectPane(onPtyExit)
answerUnmountedHostPane(latestSubscribePayload().streamId)
expect(onPtyExit).not.toHaveBeenCalled()
expect(onExit).not.toHaveBeenCalled()
await vi.waitFor(() => expect(subscribeFrameCount()).toBe(2))
expect(subscribedTerminalHandles()).toEqual(['terminal-1', 'terminal-1'])
emitSnapshot(latestSubscribePayload().streamId, 'host mounted the pane')
await vi.waitFor(() => expect(transport.getRecoveryState?.().phase).toBe('connected'))
expect(transport.getPtyId()).toBe('remote:env-1@@terminal-1')
expect(onPtyExit).not.toHaveBeenCalled()
transport.destroy?.()
})
it('retires the pane as lost, not exited, when the host stops listing it', async () => {
const onPtyExit = vi.fn()
const { transport, onExit } = await connectPane(onPtyExit)
const defaultCall = runtimeCall.getMockImplementation()
runtimeCall.mockImplementation(async (args: { method: string }) => {
if (args.method === 'terminal.resolvePane') {
return { ok: false, error: { code: 'runtime_error', message: 'terminal_not_found' } }
}
return defaultCall?.(args)
})
answerUnmountedHostPane(latestSubscribePayload().streamId)
// Why -1: the host-loss sentinel; a pane the host stopped listing is not proof of exit.
await vi.waitFor(() => expect(onPtyExit).toHaveBeenCalledWith('remote:env-1@@terminal-1', -1))
expect(onPtyExit).toHaveBeenCalledOnce()
expect(onExit).not.toHaveBeenCalled()
expect(subscribeFrameCount()).toBe(1)
transport.destroy?.()
})
it('backs off and settles disconnected, not exited, when the host never mounts it', async () => {
vi.useFakeTimers()
try {
@@ -17,30 +17,6 @@ describe('refreshTerminalImeInputContext', () => {
return helper
}
it('blurs then refocuses the helper on macOS', () => {
const helper = appendHelper()
const blur = vi.spyOn(helper, 'blur')
const focus = vi.spyOn(helper, 'focus')
helper.focus()
focus.mockClear()
const scheduled: (() => void)[] = []
const refreshed = refreshTerminalImeInputContext(helper, {
isMac: true,
scheduleRefocus: (callback) => scheduled.push(callback)
})
expect(refreshed).toBe(true)
expect(blur).toHaveBeenCalledOnce()
expect(focus).not.toHaveBeenCalled()
for (const run of scheduled) {
run()
}
expect(focus).toHaveBeenCalledOnce()
expect(document.activeElement).toBe(helper)
})
it('marks only the synchronous refresh blur so focus ownership can stay latched', () => {
const helper = appendHelper()
let refreshingDuringBlur = false
@@ -80,27 +56,6 @@ describe('refreshTerminalImeInputContext', () => {
expect(document.activeElement).toBe(outside)
})
it('reports when a newer focus owner wins the refocus guard', () => {
const helper = appendHelper()
const outside = document.createElement('input')
document.body.appendChild(outside)
const onRefocusSkipped = vi.fn()
const scheduled: (() => void)[] = []
helper.focus()
refreshTerminalImeInputContext(helper, {
isMac: true,
onRefocusSkipped,
scheduleRefocus: (callback) => scheduled.push(callback)
})
outside.focus()
for (const run of scheduled) {
run()
}
expect(onRefocusSkipped).toHaveBeenCalledWith(outside)
})
it('reports when a connected helper cannot accept the scheduled focus', () => {
const helper = appendHelper()
const onRefocusSkipped = vi.fn()
@@ -120,19 +75,4 @@ describe('refreshTerminalImeInputContext', () => {
expect(document.activeElement).toBe(document.body)
expect(onRefocusSkipped).toHaveBeenCalledWith(document.body)
})
it('skips non-macOS platforms', () => {
const helper = appendHelper()
const blur = vi.spyOn(helper, 'blur')
const refreshed = refreshTerminalImeInputContext(helper, {
isMac: false,
scheduleRefocus: () => {
throw new Error('should not schedule')
}
})
expect(refreshed).toBe(false)
expect(blur).not.toHaveBeenCalled()
})
})
@@ -1,27 +1,12 @@
// useShallow skips renders, not selectors; memoize host resolution to avoid per-tab traversal and discarded objects on every publication.
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as ConnectionContext from '@/lib/connection-context'
import { beforeEach, describe, expect, it } from 'vitest'
import type { AppState } from '@/store/types'
import {
resetTerminalPaneHostStateMemoForTests,
selectTerminalPaneHostState
} from './terminal-pane-host-state'
const connectionIdCalls = vi.hoisted(() => ({ count: 0 }))
vi.mock('@/lib/connection-context', async (importOriginal) => {
const actual = await importOriginal<typeof ConnectionContext>()
return {
...actual,
getConnectionIdFromState: (state: AppState, worktreeId: string | null) => {
connectionIdCalls.count += 1
return actual.getConnectionIdFromState(state, worktreeId)
}
}
})
const LOCAL_WORKTREE = 'repo-1::/repo/worktrees/local'
const OTHER_WORKTREE = 'repo-2::/repo/worktrees/other'
/** Only the fields the resolver touches; the memo key is the whole object. */
function makeState(overrides: Partial<Record<string, unknown>> = {}): AppState {
@@ -46,7 +31,6 @@ function makeState(overrides: Partial<Record<string, unknown>> = {}): AppState {
beforeEach(() => {
resetTerminalPaneHostStateMemoForTests()
connectionIdCalls.count = 0
})
describe('selectTerminalPaneHostState memo', () => {
@@ -77,21 +61,6 @@ describe('selectTerminalPaneHostState memo', () => {
expect(after).toBe(before)
})
it('resolves the host once per published state, not once per mounted tab', () => {
const state = makeState()
selectTerminalPaneHostState(state, LOCAL_WORKTREE)
const afterFirstTab = connectionIdCalls.count
expect(afterFirstTab).toBe(1)
for (let index = 0; index < 4; index += 1) {
selectTerminalPaneHostState(state, LOCAL_WORKTREE)
}
expect(connectionIdCalls.count).toBe(afterFirstTab)
selectTerminalPaneHostState(state, OTHER_WORKTREE)
expect(connectionIdCalls.count).toBe(afterFirstTab + 1)
})
it('re-resolves when the published state changes, so a host change is never stale', () => {
const local = makeState()
const before = selectTerminalPaneHostState(local, LOCAL_WORKTREE)
@@ -108,31 +77,4 @@ describe('selectTerminalPaneHostState memo', () => {
expect(after).not.toBe(before)
expect(after.sshReconnectTargetId).toBe('ssh-host-a')
})
it('allocates nothing across 1,000 publications at 6 worktrees x 5 tabs', () => {
const worktreeIds = Array.from({ length: 6 }, (_, index) => `repo-${index}::/repo/wt-${index}`)
const firstState = makeState()
const firstByWorktree = new Map(
worktreeIds.map((worktreeId) => [
worktreeId,
selectTerminalPaneHostState(firstState, worktreeId)
])
)
connectionIdCalls.count = 0
let allocations = 0
for (let publication = 0; publication < 1_000; publication += 1) {
const state = makeState({ agentStatusEpoch: publication })
for (const worktreeId of worktreeIds) {
for (let tab = 0; tab < 5; tab += 1) {
if (selectTerminalPaneHostState(state, worktreeId) !== firstByWorktree.get(worktreeId)) {
allocations += 1
}
}
}
}
expect(allocations).toBe(0)
expect(connectionIdCalls.count).toBe(6 * 1_000)
})
})
@@ -6,7 +6,6 @@ import { BACKGROUND_WORKTREE_MEASURE_WINDOW_MS } from '../terminal/background-te
import {
_resetTerminalPaneSplitRequestRoutingForTests,
cancelQueuedTerminalPaneSplitRequests,
dispatchTerminalPaneSplitRequest,
hasTerminalPaneSplitMountLease,
queueTerminalPaneSplitRequest,
registerTerminalPaneSplitRequestHandler,
@@ -37,16 +36,6 @@ afterEach(() => {
})
describe('parked terminal split request routing', () => {
it('demonstrates that the legacy fire-and-forget event is lost before a parked pane mounts', () => {
const handler = vi.fn()
dispatchTerminalPaneSplitRequest(splitRequest('tab-parked'))
const unregister = registerTerminalPaneSplitRequestHandler('tab-parked', undefined, handler)
expect(handler).not.toHaveBeenCalled()
unregister()
})
it('replays a parked-tab request as soon as that exact pane lifecycle registers', () => {
const request = splitRequest('tab-parked', 91)
const splitPane = vi.fn()
@@ -74,10 +63,6 @@ describe('parked terminal split request routing', () => {
unregister()
})
it('fails closed when a remount no longer contains the stable source leaf', () => {
expect(resolveTerminalPaneSplitSourceId(splitRequest('tab-parked', 91), () => null)).toBe(-1)
})
it('keeps the mount lease through replay, then releases it at the existing measure bound', () => {
queueTerminalPaneSplitRequest(splitRequest('tab-parked'))
takeQueuedTerminalPaneSplitRequests('tab-parked')
@@ -1,140 +0,0 @@
// @vitest-environment happy-dom
import type * as ReactModule from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useTerminalFontZoom } from './useTerminalFontZoom'
const mocks = vi.hoisted(() => ({
captureScrollState: vi.fn(() => ({ wasAtBottom: true })),
restoreScrollState: vi.fn(),
safeFit: vi.fn(),
dispatchZoomLevelChanged: vi.fn()
}))
vi.mock('react', async (importOriginal) => {
const actual = await importOriginal<typeof ReactModule>()
return {
...actual,
useEffect: (effect: () => void | (() => void)) => effect()
}
})
vi.mock('@/lib/pane-manager/pane-tree-ops', () => ({
captureScrollState: mocks.captureScrollState,
restoreScrollState: mocks.restoreScrollState,
safeFit: mocks.safeFit
}))
vi.mock('@/lib/zoom-events', () => ({
dispatchZoomLevelChanged: mocks.dispatchZoomLevelChanged
}))
describe('useTerminalFontZoom', () => {
let terminalZoomListeners: ((direction: 'in' | 'out' | 'reset') => void)[]
beforeEach(() => {
terminalZoomListeners = []
document.body.replaceChildren()
vi.clearAllMocks()
vi.stubGlobal('window', {
api: {
ui: {
onTerminalZoom: vi.fn((listener: (direction: 'in' | 'out' | 'reset') => void) => {
terminalZoomListeners.push(listener)
return () => {}
})
}
}
})
})
function useMountedTerminalFontZoom(activeElement: HTMLElement): {
terminal: { options: { fontSize?: number } }
listener: (direction: 'in' | 'out' | 'reset') => void
} {
const container = document.createElement('div')
document.body.appendChild(container)
container.appendChild(activeElement)
activeElement.focus()
const terminal = { options: { fontSize: 14 } }
useTerminalFontZoom({
isActive: true,
containerRef: { current: container },
managerRef: {
current: {
getActivePane: () => ({ id: 1, terminal })
}
} as never,
paneFontSizesRef: { current: new Map() },
settingsRef: { current: { terminalFontSize: 14 } }
})
const listener = terminalZoomListeners.at(-1)
expect(listener).toBeTypeOf('function')
return { terminal, listener: listener as (direction: 'in' | 'out' | 'reset') => void }
}
it('ignores zoom events when terminal input no longer owns focus', () => {
const button = document.createElement('button')
const { listener, terminal } = useMountedTerminalFontZoom(button)
listener('in')
expect(terminal.options.fontSize).toBe(14)
expect(mocks.safeFit).not.toHaveBeenCalled()
expect(mocks.dispatchZoomLevelChanged).not.toHaveBeenCalled()
})
it('applies terminal font zoom while the xterm helper textarea owns focus', () => {
const helper = document.createElement('textarea')
helper.className = 'xterm-helper-textarea'
const { listener, terminal } = useMountedTerminalFontZoom(helper)
listener('in')
expect(terminal.options.fontSize).toBe(15)
expect(mocks.safeFit).toHaveBeenCalledTimes(1)
expect(mocks.dispatchZoomLevelChanged).toHaveBeenCalledWith('terminal', 107)
})
it('only lets the pane owning the focused helper apply terminal font zoom', () => {
const inactiveContainer = document.createElement('div')
const activeContainer = document.createElement('div')
const focusedHelper = document.createElement('textarea')
focusedHelper.className = 'xterm-helper-textarea'
document.body.append(inactiveContainer, activeContainer)
activeContainer.appendChild(focusedHelper)
focusedHelper.focus()
const inactiveTerminal = { options: { fontSize: 14 } }
const activeTerminal = { options: { fontSize: 14 } }
useTerminalFontZoom({
isActive: true,
containerRef: { current: inactiveContainer },
managerRef: {
current: {
getActivePane: () => ({ id: 1, terminal: inactiveTerminal })
}
} as never,
paneFontSizesRef: { current: new Map() },
settingsRef: { current: { terminalFontSize: 14 } }
})
useTerminalFontZoom({
isActive: true,
containerRef: { current: activeContainer },
managerRef: {
current: {
getActivePane: () => ({ id: 2, terminal: activeTerminal })
}
} as never,
paneFontSizesRef: { current: new Map() },
settingsRef: { current: { terminalFontSize: 14 } }
})
for (const listener of terminalZoomListeners) {
listener('in')
}
expect(inactiveTerminal.options.fontSize).toBe(14)
expect(activeTerminal.options.fontSize).toBe(15)
expect(mocks.safeFit).toHaveBeenCalledTimes(1)
})
})
@@ -1,141 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { getStateMock, closeWebRuntimeSessionTabMock, isWebRuntimeSessionActiveMock } = vi.hoisted(
() => ({
getStateMock: vi.fn(),
closeWebRuntimeSessionTabMock: vi.fn(),
isWebRuntimeSessionActiveMock: vi.fn(() => false)
})
)
vi.mock('@/store', () => ({
useAppStore: { getState: getStateMock }
}))
vi.mock('@/runtime/web-runtime-session-tab-lifecycle', () => ({
activateWebRuntimeSessionTab: vi.fn(),
closeWebRuntimeSessionTab: closeWebRuntimeSessionTabMock
}))
vi.mock('@/runtime/web-runtime-terminal-creation', () => ({
createWebRuntimeSessionTerminal: vi.fn()
}))
vi.mock('@/runtime/web-runtime-session-environment', () => ({
isWebRuntimeSessionActive: isWebRuntimeSessionActiveMock
}))
vi.mock('../../../../shared/terminal-surface-id', () => ({
toHostSessionTabId: vi.fn((tabId: string) => tabId)
}))
vi.mock('@/runtime/web-session-tabs-sync/tracking-mappings', () => ({
resolveHostSessionTabIdForWebSessionTab: vi.fn(() => null)
}))
import { closeTerminalTab } from './terminal-tab-actions'
function baseState(overrides: Record<string, unknown> = {}): Record<string, unknown> {
return {
settings: { activeRuntimeEnvironmentId: null, confirmClosePinnedTab: true },
repos: [{ id: 'repo', executionHostId: 'local', connectionId: null }],
worktreesByRepo: { repo: [{ id: 'wt', repoId: 'repo' }] },
tabsByWorktree: { wt: [{ id: 'terminal-1' }] },
unifiedTabsByWorktree: {},
activeWorktreeId: 'wt',
activeTabId: 'terminal-1',
openFiles: [],
browserTabsByWorktree: {},
reconcileWorktreeTabModel: vi.fn(() => ({ renderableTabCount: 0 })),
closeTab: vi.fn(),
closeUnifiedTab: vi.fn(),
setActiveFile: vi.fn(),
setActiveBrowserTab: vi.fn(),
setActiveTabType: vi.fn(),
setActiveTab: vi.fn(),
setActiveWorktree: vi.fn(),
requestPinnedTabCloseConfirm: vi.fn(),
createTab: vi.fn(),
closeFile: vi.fn(),
closeBrowserTab: vi.fn(),
...overrides
}
}
describe('closeTerminalTab kill-all routing', () => {
beforeEach(() => {
vi.clearAllMocks()
isWebRuntimeSessionActiveMock.mockReturnValue(false)
})
it('force-closes a pinned terminal without opening a second confirmation', () => {
const closeTab = vi.fn()
const closeUnifiedTab = vi.fn()
const requestPinnedTabCloseConfirm = vi.fn()
getStateMock.mockReturnValue(
baseState({
tabsByWorktree: {},
unifiedTabsByWorktree: {
wt: [
{
id: 'visible-pinned',
entityId: 'terminal-1',
contentType: 'terminal',
isPinned: true
}
]
},
closeTab,
closeUnifiedTab,
requestPinnedTabCloseConfirm
})
)
closeTerminalTab('terminal-1', { force: true })
expect(requestPinnedTabCloseConfirm).not.toHaveBeenCalled()
expect(closeTab).toHaveBeenCalledWith('terminal-1', { reason: undefined })
expect(closeUnifiedTab).not.toHaveBeenCalled()
})
it('routes the last active terminal to an existing editor without closing it', () => {
const state = baseState({
openFiles: [{ id: 'editor-1', worktreeId: 'wt' }]
})
getStateMock.mockReturnValue(state)
closeTerminalTab('terminal-1', { force: true })
expect(state.closeTab).toHaveBeenCalledWith('terminal-1')
expect(state.setActiveFile).toHaveBeenCalledWith('editor-1')
expect(state.setActiveTabType).toHaveBeenCalledWith('editor', 'wt')
expect(state.closeFile).not.toHaveBeenCalled()
expect(state.closeBrowserTab).not.toHaveBeenCalled()
expect(state.setActiveWorktree).not.toHaveBeenCalled()
expect(state.createTab).not.toHaveBeenCalled()
})
it('routes the last active terminal to an existing browser when no editor exists', () => {
const state = baseState({
browserTabsByWorktree: { wt: [{ id: 'browser-1' }] }
})
getStateMock.mockReturnValue(state)
closeTerminalTab('terminal-1', { force: true })
expect(state.setActiveBrowserTab).toHaveBeenCalledWith('browser-1')
expect(state.setActiveTabType).toHaveBeenCalledWith('browser', 'wt')
expect(state.closeBrowserTab).not.toHaveBeenCalled()
expect(state.setActiveWorktree).not.toHaveBeenCalled()
expect(state.createTab).not.toHaveBeenCalled()
})
it('deactivates after the last active terminal when no other content exists', () => {
const state = baseState()
getStateMock.mockReturnValue(state)
closeTerminalTab('terminal-1', { force: true })
expect(state.setActiveWorktree).toHaveBeenCalledWith(null)
expect(state.setActiveFile).not.toHaveBeenCalled()
expect(state.setActiveBrowserTab).not.toHaveBeenCalled()
expect(state.createTab).not.toHaveBeenCalled()
})
})
@@ -84,40 +84,6 @@ describe('unsaved-changes prompt for a closing file', () => {
})
afterEach(() => cleanup())
it('reveals a floating note in the floating panel without moving the main window', () => {
store.openFiles = [dirtyFile('note', FLOATING_TERMINAL_WORKTREE_ID)]
const { result } = mountCloseQueue()
act(() => requestEditorFileClose('note'))
expect(result.current.saveDialogFileId).toBe('note')
expect(revealFloatingWorkspacePanel).toHaveBeenCalledOnce()
expect(setActiveWorktree).not.toHaveBeenCalled()
expect(setActiveTabType).toHaveBeenCalledWith('editor', FLOATING_TERMINAL_WORKTREE_ID)
})
it('still switches to a background worktree to show its file', () => {
store.openFiles = [dirtyFile('readme', 'wt-other')]
mountCloseQueue()
act(() => requestEditorFileClose('readme'))
expect(setActiveWorktree).toHaveBeenCalledWith('wt-other')
expect(revealFloatingWorkspacePanel).not.toHaveBeenCalled()
})
it('runs the close reaction once the file is discarded', async () => {
store.openFiles = [dirtyFile('note', FLOATING_TERMINAL_WORKTREE_ID)]
const onClosed = vi.fn()
const { result } = mountCloseQueue()
act(() => requestEditorFileClose('note', { onClosed }))
await act(() => result.current.handleSaveDialogDiscard())
expect(closeFile).toHaveBeenCalledWith('note')
expect(onClosed).toHaveBeenCalledOnce()
})
// Why a later close of the same file: a cancelled reaction left behind would fire on it.
it('drops the close reaction when the prompt is cancelled', async () => {
vi.useFakeTimers()
@@ -73,19 +73,6 @@ describe('createClientWorkspaceViewReporter', () => {
])
})
it('refreshes the host’s copy at most every ten seconds of activity', () => {
const { sent, report } = reporter()
report.update(STATE, ['local-wt'], [])
vi.setSystemTime(Date.now() + 9_000)
report.renewPresence()
expect(sent).toHaveLength(1)
vi.setSystemTime(Date.now() + 1_000)
report.renewPresence()
expect(sent).toHaveLength(2)
expect(sent[1].view.presentForMs).toBe(190_000)
})
it('reports an away user’s screen with no presence, and renews as soon as input returns', () => {
const { sent, report } = reporter()
presence.remaining = 0
@@ -98,16 +85,6 @@ describe('createClientWorkspaceViewReporter', () => {
expect(sent).toHaveLength(2)
})
it('refreshes where presence is not tracked too, so a reconnected host learns the view', () => {
const { sent, report } = reporter()
presence.remaining = null
report.update(STATE, ['local-wt'], [])
vi.setSystemTime(Date.now() + 10_000)
report.renewPresence()
expect(sent).toHaveLength(2)
expect(sent[1].view.presentForMs).toBeNull()
})
it('retries a failed send, empty or not, no sooner than a refresh', async () => {
const sent: ClientWorkspaceView[] = []
let fail = true
@@ -121,24 +121,4 @@ describe('live agent-status replay notifications', () => {
1_700_000_004_000
)
})
it('seeds a stamped working replay when this renderer holds no row', () => {
const apply = createApplicator()
apply(status({ state: 'working', isReplay: true, turnCompletedAt: 1_700_000_004_000 }))
expect(mocks.observe.mock.calls[0][0]).toHaveProperty('seedOnly', true)
})
it('seeds a replayed working row, and a later real done still notifies', () => {
const apply = createApplicator()
apply(status({ state: 'working', isReplay: true }))
apply(status({ state: 'done', receivedAt: 1_700_000_006_000 }))
expect(mocks.observe).toHaveBeenCalledTimes(2)
expect(mocks.observe.mock.calls[0][0]).toHaveProperty('seedOnly', true)
expect(mocks.observe.mock.calls[1][0]).not.toHaveProperty('seedOnly')
expect(mocks.observe.mock.calls[1][0].payload).toHaveProperty('state', 'done')
})
})
@@ -123,15 +123,6 @@ describe('useReadOnScreenAttention — clock-skewed execution host', () => {
expect(calls).toEqual([])
})
it('still acks a normally stamped turn once', () => {
seedFutureStampedTurn(NOW - 5_000)
const calls = instrumentAcknowledgeAgents()
renderHook(() => useReadOnScreenAttention())
expect(calls).toEqual([[PANE_KEY]])
})
it('keeps an explicitly marked-unread visible turn unread', () => {
seedFutureStampedTurn(NOW - 5_000)
useAppStore.getState().acknowledgeAgents([PANE_KEY])
@@ -1,141 +0,0 @@
import type { Terminal } from '@xterm/xterm'
import { describe, expect, it, vi } from 'vitest'
import {
installTerminalLinkifierHoverResetOnMouseLeave,
installTerminalLinkifierHoverResetOnWindowBlur
} from './terminal-linkifier-hover-reset-on-mouseleave'
type FakeLinkifier = {
_lastBufferCell?: unknown
_activeLine?: number
_clearCurrentLink?: () => void
_currentLink?: unknown
}
function createHarness(hasScreen = true) {
let mouseLeaveHandler: (() => void) | null = null
let blurHandler: (() => void) | null = null
const addEventListener = vi.fn((_event: string, handler: () => void) => {
if (_event === 'mouseleave') {
mouseLeaveHandler = handler
}
})
const removeEventListener = vi.fn((_event: string, handler: () => void) => {
if (_event === 'mouseleave' && mouseLeaveHandler === handler) {
mouseLeaveHandler = null
}
})
const screen = { addEventListener, removeEventListener, classList: { remove: vi.fn() } }
const querySelector = vi.fn(() => (hasScreen ? screen : null))
const rendererWindow = {
addEventListener: vi.fn((_event: string, handler: () => void) => {
blurHandler = handler
}),
removeEventListener: vi.fn((_event: string, handler: () => void) => {
if (blurHandler === handler) {
blurHandler = null
}
})
}
const linkifier: FakeLinkifier = {
_lastBufferCell: { x: 2, y: 3 },
_activeLine: 3,
_clearCurrentLink: vi.fn(),
_currentLink: { link: 'https://example.com' }
}
const terminal = {
element: { querySelector, ownerDocument: { defaultView: rendererWindow } },
_core: { linkifier }
} as unknown as Terminal
const linkTooltip = {
ownerDocument: { defaultView: rendererWindow },
style: { display: '' }
} as unknown as HTMLElement
return {
terminal,
linkifier,
linkTooltip,
rendererWindow,
querySelector,
addEventListener,
removeEventListener,
dispatchMouseLeave: () => mouseLeaveHandler?.(),
dispatchBlur: () => blurHandler?.()
}
}
describe('installTerminalLinkifierHoverResetOnMouseLeave', () => {
it('resets the hover cache when the terminal surface loses the pointer', () => {
const harness = createHarness()
installTerminalLinkifierHoverResetOnMouseLeave(harness.terminal, harness.linkTooltip)
expect(harness.querySelector).toHaveBeenCalledWith('.xterm-screen')
expect(harness.addEventListener).toHaveBeenCalledWith('mouseleave', expect.any(Function))
harness.dispatchMouseLeave()
expect(harness.linkifier._lastBufferCell).toBeUndefined()
expect(harness.linkifier._activeLine).toBe(-1)
expect(harness.linkifier._clearCurrentLink).toHaveBeenCalledTimes(1)
expect(harness.linkifier._currentLink).toBeUndefined()
expect(harness.linkTooltip.style.display).toBe('none')
})
it('removes the listener on dispose', () => {
const harness = createHarness()
const disposable = installTerminalLinkifierHoverResetOnMouseLeave(harness.terminal)
const mouseLeaveHandler = harness.addEventListener.mock.calls.find(
([eventName]) => eventName === 'mouseleave'
)?.[1]
expect(mouseLeaveHandler).toBeTypeOf('function')
disposable.dispose()
expect(harness.removeEventListener).toHaveBeenCalledWith('mouseleave', mouseLeaveHandler)
harness.dispatchMouseLeave()
expect(harness.linkifier._lastBufferCell).toEqual({ x: 2, y: 3 })
expect(harness.linkifier._activeLine).toBe(3)
})
it('degrades to a no-op when the screen is unavailable', () => {
const harness = createHarness(false)
expect(() =>
installTerminalLinkifierHoverResetOnMouseLeave(harness.terminal).dispose()
).not.toThrow()
})
it('does not throw when xterm linkifier internals are unavailable', () => {
const harness = createHarness()
const terminal = harness.terminal as unknown as { _core?: unknown }
terminal._core = undefined
installTerminalLinkifierHoverResetOnMouseLeave(harness.terminal)
expect(harness.dispatchMouseLeave).not.toThrow()
})
it('clears the active link and tooltip when the renderer window blurs', () => {
const harness = createHarness()
const disposable = installTerminalLinkifierHoverResetOnWindowBlur(
harness.terminal,
harness.linkTooltip
)
harness.dispatchBlur()
expect(harness.rendererWindow.addEventListener).toHaveBeenCalledWith(
'blur',
expect.any(Function)
)
expect(harness.linkifier._clearCurrentLink).toHaveBeenCalledTimes(1)
expect(harness.linkifier._currentLink).toBeUndefined()
expect(harness.linkifier._lastBufferCell).toBeUndefined()
expect(harness.linkifier._activeLine).toBe(-1)
expect(harness.linkTooltip.style.display).toBe('none')
disposable.dispose()
expect(harness.rendererWindow.removeEventListener).toHaveBeenCalledWith(
'blur',
expect.any(Function)
)
})
})
@@ -33,10 +33,7 @@ vi.mock('@/lib/ensure-hooks-confirmed', () => ({
vi.mock('sonner', () => ({ toast: { error: vi.fn() } }))
import { ensureHooksConfirmed } from './ensure-hooks-confirmed'
import {
runBackgroundWorktreeCreation,
retryBackgroundWorktreeCreation
} from './worktree-creation-flow'
import { runBackgroundWorktreeCreation } from './worktree-creation-flow'
function makeRequest(overrides: Partial<WorktreeCreationRequest> = {}): WorktreeCreationRequest {
return {
@@ -106,34 +103,4 @@ describe('background script preparation', () => {
await flushAsyncWorktreeCreation()
expect(store.createWorktree).not.toHaveBeenCalled()
})
it('keeps refused scripts disabled when creating the worktree', async () => {
vi.mocked(ensureHooksConfirmed).mockResolvedValueOnce('skip')
runBackgroundWorktreeCreation(
makeRequest({
setupDecision: 'run',
hookPreparation: { executionHostId: 'local' }
})
)
await vi.waitFor(() => expect(store.createWorktree).toHaveBeenCalledOnce())
expect(store.pendingWorktreeCreations['creation-1'].request.setupDecision).toBe('skip')
})
it('retries failed preparation on the captured host', async () => {
vi.mocked(ensureHooksConfirmed).mockRejectedValueOnce(new Error('inspection failed'))
runBackgroundWorktreeCreation(
makeRequest({ hookPreparation: { executionHostId: 'runtime:host-1' } })
)
await vi.waitFor(() =>
expect(store.pendingWorktreeCreations['creation-1'].status).toBe('error')
)
expect(store.createWorktree).not.toHaveBeenCalled()
const trust = Promise.withResolvers<'run' | 'skip'>()
vi.mocked(ensureHooksConfirmed).mockReturnValueOnce(trust.promise)
retryBackgroundWorktreeCreation('creation-1')
expect(store.pendingWorktreeCreations['creation-1'].phase).toBe('preparing')
trust.resolve('run')
await vi.waitFor(() => expect(store.createWorktree).toHaveBeenCalledOnce())
})
})
@@ -1,7 +1,6 @@
import { describe, expect, it, vi } from 'vitest'
import type { RuntimeMobileSessionTabsResult } from '../../../shared/runtime-types'
import {
getWebSessionTerminalHandleSubscriberCountForTests,
queueAcceptedWebSessionTerminalSnapshot,
subscribeAcceptedWebSessionTerminalHandle
} from './web-session-terminal-handle-events'
@@ -22,42 +21,6 @@ function snapshot(
}
describe('accepted web-session terminal handle events', () => {
it('notifies only the matching runtime/worktree/pane and releases the listener', async () => {
const listener = vi.fn()
const unsubscribe = subscribeAcceptedWebSessionTerminalHandle(
{ environmentId: 'env-1', worktreeId: 'wt-1', hostTabId: 'tab-1', leafId: 'leaf-1' },
listener
)
queueAcceptedWebSessionTerminalSnapshot(snapshot([]), 'env-2')
queueAcceptedWebSessionTerminalSnapshot(snapshot([], 'wt-2'), 'env-1')
queueAcceptedWebSessionTerminalSnapshot(
snapshot([
{
type: 'terminal',
id: 'tab-1::leaf-1',
parentTabId: 'tab-1',
leafId: 'leaf-1',
title: 'Claude Code',
isActive: true,
status: 'ready',
terminal: 'terminal-replacement'
}
]),
'env-1'
)
await Promise.resolve()
expect(listener).toHaveBeenCalledOnce()
expect(listener).toHaveBeenCalledWith({
surfacePresent: true,
terminalHandle: 'terminal-replacement'
})
expect(getWebSessionTerminalHandleSubscriberCountForTests()).toBe(1)
unsubscribe()
expect(getWebSessionTerminalHandleSubscriberCountForTests()).toBe(0)
})
it('distinguishes a pending handle from an explicitly removed surface', async () => {
const listener = vi.fn()
const unsubscribe = subscribeAcceptedWebSessionTerminalHandle(
@@ -91,50 +91,3 @@ it('drops the previous icons when the next selected theme fails to load', async
await waitFor(() => expect(document.getElementById('orca-plugin-icon-theme')).toBeNull())
expect(store.useActivePluginIconTheme.getState().theme).toBeNull()
})
it('reloads the active icon theme when a same-metadata update changes its content', async () => {
const metadata = { id: 'plugin:samples.icons/minimal', pluginKey: 'samples.icons', label: 'A' }
let listed: { id: string; pluginKey: string; label: string; revision?: string }[] = [
{ ...metadata, revision: 'r1' }
]
const listeners: ((event: PluginChangeEvent) => void)[] = []
const loadIconTheme = vi.fn(async () => ({
...listed[0],
svgs: ['<svg xmlns="http://www.w3.org/2000/svg"><path fill="currentColor"/></svg>'],
icons: { file: 0 },
fileNames: {},
fileExtensions: {},
folderNames: {},
folderNamesExpanded: {}
}))
vi.stubGlobal('api', {
plugins: {
listIconThemes: async () => listed,
loadIconTheme,
onChanged: (listener: (event: PluginChangeEvent) => void) => {
listeners.push(listener)
return () => {}
}
}
})
const store = await import('./plugin-icon-themes')
renderHook(() => store.usePluginIconThemeActivation(metadata.id))
await waitFor(() => expect(loadIconTheme).toHaveBeenCalledTimes(1))
const refresh = async () => {
act(() => listeners.forEach((listener) => listener({ contentPacksChanged: true })))
await act(async () => {
await store.usePluginIconThemeStore.getState().fetchEntries()
})
}
listed = [
{ ...metadata, revision: 'r1' },
{ ...metadata, id: 'plugin:samples.icons/other' }
]
await refresh()
expect(loadIconTheme).toHaveBeenCalledTimes(1)
listed = [{ ...metadata, revision: 'r2' }]
await refresh()
await waitFor(() => expect(loadIconTheme).toHaveBeenCalledTimes(2))
})
@@ -97,24 +97,6 @@ describe('a fetched catalog that drops a repo registration', () => {
])
})
it('drops the pre-host rows too once no host registers the repo id', async () => {
const store = createTestStore()
seedStore(store, {
repos: [serverRepo('old')],
worktreesByRepo: {
old: [
serverRow('old'),
makeWorktree({ id: 'old::/legacy', repoId: 'old', path: '/legacy' })
]
}
})
serverRepos = [serverRepo('copied')]
await store.getState().fetchRuntimeEnvironmentRepos('env-1')
expect(store.getState().worktreesByRepo.old).toEqual([])
})
it('keeps the rows of a repo added while the catalog was in flight', async () => {
const store = createTestStore()
seedStore(store, {
@@ -68,27 +68,6 @@ describe('tab view mode', () => {
seedTwoLeafTabs(store)
})
it('toggles a tab to chat and back to terminal', async () => {
expect(findTab(store, 'left-tab')?.viewMode).toBeUndefined()
store.getState().toggleTabViewMode('left-tab')
await layoutApplyTestTransport.settle()
expect(findTab(store, 'left-tab')?.viewMode).toBe('chat')
store.getState().toggleTabViewMode('left-tab')
await layoutApplyTestTransport.settle()
expect(findTab(store, 'left-tab')?.viewMode).toBe('terminal')
})
it('setTabViewMode sets an explicit mode', async () => {
store.getState().setTabViewMode('left-tab', 'chat')
await layoutApplyTestTransport.settle()
expect(findTab(store, 'left-tab')?.viewMode).toBe('chat')
store.getState().setTabViewMode('left-tab', 'terminal')
await layoutApplyTestTransport.settle()
expect(findTab(store, 'left-tab')?.viewMode).toBe('terminal')
})
it('mutates only the targeted tab, leaving a split sibling unchanged', async () => {
store.getState().toggleTabViewMode('left-tab')
await layoutApplyTestTransport.settle()
@@ -97,12 +76,6 @@ describe('tab view mode', () => {
expect(findTab(store, 'right-tab')?.viewMode).toBeUndefined()
})
it('is a no-op for an unknown tab id', () => {
const before = store.getState().unifiedTabsByWorktree[WT]
store.getState().toggleTabViewMode('missing-tab')
expect(store.getState().unifiedTabsByWorktree[WT]).toBe(before)
})
// Local toggles must mirror viewMode onto terminal rows used by recovery, matching host sync.
describe('mirrors onto the terminal row', () => {
function terminalRow(tabId: string) {
@@ -52,24 +52,6 @@ describe('row writers touch only the copy on their own host', () => {
expect(rows).toHaveLength(2)
})
it('a server create leaves the local twin alone', () => {
const store = createTestStore()
const { local } = seedTwins(store)
applyCreatedWorktree(
store.setState,
REPO_ID,
{
worktree: makeWorktree({ id: WORKTREE_ID, repoId: REPO_ID, displayName: 'fresh remote' })
} satisfies CreateWorktreeResult,
'runtime:hub'
)
const rows = store.getState().worktreesByRepo[REPO_ID]!
expect(rows.find((row) => !row.runtimeOwnerEnvironmentId)).toEqual(local)
expect(rows.find((row) => row.runtimeOwnerEnvironmentId === 'hub')?.displayName).toBe(
'fresh remote'
)
})
it("a server's lineage reply updates its own copy, never the focused local twin", () => {
const store = createTestStore()
const { local } = seedTwins(store)
@@ -114,14 +114,6 @@ it("keeps only a moved project's chat tab through hydration and persistence", ()
expect(persisted.unifiedTabs?.[MOVED]?.map((entry) => entry.id)).toEqual([CHAT])
})
it("never treats a paired host's partition as the moved local workspace", () => {
const session = movedSession()
expect(
collectWslMovedWorkspaceKeysFromSession(session, projects, { [MOVED]: 'runtime:wsl-ubuntu' })
).toEqual([])
expect(collectWslMovedWorkspaceKeysFromSession(session, [])).toEqual([])
})
it("lists the moved chats under their project, and never the host's listed worktree", () => {
const store = createTestStore()
hydrate(store, movedSession())
@@ -2,10 +2,8 @@ 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,
isTabPaneTreeHeld
} from '@/runtime/layout-owner-pane-intents'
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'
@@ -116,24 +114,4 @@ describe('the chat pane in a layout-owner build', () => {
applySent()
expect(mainTab(tabId).panes.chatLeafId).toBeUndefined()
})
it('holds frames off the tab until main answers, so a frame cannot undo the pick', () => {
const tabId = heldTab()
mocks.callRuntimeRpc.mockImplementationOnce(() => new Promise(() => {}))
setChatLeaf(tabId, LEAF)
expect(isTabPaneTreeHeld(tabId)).toBe(true)
})
it('sends nothing for a write that keeps the chat pane, or with the switch off', () => {
const tabId = heldTab()
const layout = store.getState().terminalLayoutsByTabId[tabId]!
store.getState().setTabLayout(tabId, { ...layout, expandedLeafId: LEAF })
expect(mocks.callRuntimeRpc).not.toHaveBeenCalled()
mocks.layoutOwnerIsMain = false
setChatLeaf(tabId, LEAF)
expect(mocks.callRuntimeRpc).not.toHaveBeenCalled()
})
})
@@ -8,32 +8,6 @@ import { createTestStore, makeLayout } from '../slices/store-test-helpers'
afterEach(resetTerminalInputActivityCoalescingForTests)
describe('terminal no-op subscriber budget', () => {
it('does not publish missing-entry cleanup and restart actions', () => {
const store = createTestStore()
const before = store.getState()
const listener = vi.fn()
store.subscribe(listener)
for (let i = 0; i < 25; i += 1) {
const s = store.getState()
s.replaceTerminalLayoutPanePtyId('missing', 'leaf', 'pty')
expect(s.consumeSuppressedPtyExit('missing')).toBe(false)
expect(s.consumePendingPaneAccountRestart('missing')).toBeNull()
s.clearCodexRestartNotice('missing')
s.dismissCodexRestartNotices(['missing'])
s.reopenCodexRestartPrompt('missing')
s.markNativeChatLaunchPromptFailed('missing')
s.clearNativeChatLaunchPrompt('missing')
s.markNativeChatLaunchDraftAdopted('missing')
s.resolveNativeChatLaunchDraft('missing', { text: 'draft', createdAt: 1 })
s.clearNativeChatLaunchDraft('missing')
s.removeDeferredSshSessionId('missing')
}
expect(listener).not.toHaveBeenCalled()
expect(store.getState()).toBe(before)
})
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
@@ -81,23 +55,6 @@ describe('terminal no-op subscriber budget', () => {
expect(store.getState().nativeChatLaunchDraftByTabId.tab).toBe(draft)
})
it('consumes real restart entries and leaves repeated consumes silent', () => {
const store = createTestStore()
store.getState().suppressPtyExit('pty')
store.getState().queuePaneAccountRestarts(['pty'], 'codex')
const listener = vi.fn()
store.subscribe(listener)
expect(store.getState().consumeSuppressedPtyExit('pty')).toBe(true)
expect(store.getState().consumePendingPaneAccountRestart('pty')).toBe('codex')
expect(listener).toHaveBeenCalledTimes(2)
const before = store.getState()
expect(store.getState().consumeSuppressedPtyExit('pty')).toBe(false)
expect(store.getState().consumePendingPaneAccountRestart('pty')).toBeNull()
expect(listener).toHaveBeenCalledTimes(2)
expect(store.getState()).toBe(before)
})
it('drops a trailing input flush after pane teardown without publishing', () => {
const store = createTestStore()
store.getState().recordTerminalInput('tab:leaf', 1000)
@@ -113,34 +70,4 @@ describe('terminal no-op subscriber budget', () => {
expect(store.getState()).toBe(before)
expect(store.getState().lastTerminalInputAtByPaneKey['tab:leaf']).toBeUndefined()
})
it('dismisses, reopens and clears restart notices without replaying no-op notifications', () => {
const store = createTestStore()
store
.getState()
.markCodexRestartNotices([
{ ptyId: 'pty', previousAccountLabel: 'old', nextAccountLabel: 'new' }
])
const listener = vi.fn()
store.subscribe(listener)
const actions = [
() => store.getState().dismissCodexRestartNotices(['pty']),
() => store.getState().reopenCodexRestartPrompt('pty'),
() => store.getState().clearCodexRestartNotice('pty')
]
for (const [index, action] of actions.entries()) {
if (index === 1) {
store.getState().queuePaneAccountRestarts(['pty'], 'codex')
}
listener.mockClear()
action()
expect(listener).toHaveBeenCalledTimes(1)
const before = store.getState()
action()
expect(listener).toHaveBeenCalledTimes(1)
expect(store.getState()).toBe(before)
}
expect(store.getState().codexRestartNoticeByPtyId.pty).toBeUndefined()
expect(store.getState().pendingPaneAccountRestarts.pty).toBeUndefined()
})
})
@@ -50,11 +50,6 @@ vi.mock('../../runtime/runtime-rpc-client', () => ({
assertRuntimeEnvironmentCapability: vi.fn(),
callRuntimeRpc: rpc
}))
const savedState = {
filters: [{ id: 'mine', name: 'My work', jql: 'project = APP' }],
activeFilterId: 'mine'
}
const quickState = { statuses: ['In Progress'] }
const BOTH = [JIRA_SAVED_FILTER_RUNTIME_CAPABILITY, JIRA_STATUS_FILTER_RUNTIME_CAPABILITY]
@@ -69,26 +64,6 @@ beforeEach(() => {
})
afterEach(() => vi.unstubAllGlobals())
it('reads and writes Jira filter state on the paired host from the local target', async () => {
runtime.status.mockResolvedValue({ capabilities: BOTH })
runtime.call.mockImplementation(async (method: string) =>
method.includes('Saved') ? savedState : quickState
)
expect(await readJiraSavedFilterState({ kind: 'local' })).toEqual(savedState)
await mutateJiraSavedFilters({ kind: 'local' }, { kind: 'select', id: 'mine' })
await readJiraQuickFilterState({ kind: 'local' })
await writeJiraQuickFilterState({ kind: 'local' }, quickState)
expect(runtime.call.mock.calls).toEqual([
['ui.getJiraSavedFilters', undefined, 15_000],
['ui.mutateJiraSavedFilters', { kind: 'select', id: 'mine' }, 15_000],
['ui.getJiraQuickFilters', undefined, 15_000],
['ui.setJiraQuickFilters', quickState, 15_000]
])
expect(rpc).not.toHaveBeenCalled()
})
it('reports update-required for an older host and never sends the new methods', async () => {
runtime.status.mockResolvedValue({ capabilities: [] })
@@ -107,17 +82,6 @@ it('reports update-required for an older host and never sends the new methods',
expect(runtime.call).not.toHaveBeenCalled()
})
it('rejects host failures instead of returning empty or unsaved state', async () => {
runtime.status.mockRejectedValueOnce(new Error('Connection lost'))
await expect(readJiraSavedFilterState({ kind: 'local' })).rejects.toThrow('Connection lost')
runtime.status.mockResolvedValue({ capabilities: BOTH })
runtime.call.mockRejectedValue(new Error('Host write failed'))
await expect(writeJiraQuickFilterState({ kind: 'local' }, quickState)).rejects.toThrow(
'Host write failed'
)
})
it('does not send to a host paired in while the capability probe was in flight', async () => {
runtime.status.mockResolvedValue({ capabilities: BOTH })
runtime.idAfterProbe = 'host-b'
@@ -127,17 +91,3 @@ it('does not send to a host paired in while the capability probe was in flight',
).rejects.toThrow('Pairing changed')
expect(runtime.call).not.toHaveBeenCalled()
})
it('keeps explicit environment targets on the environment RPC path', async () => {
rpc.mockResolvedValue(savedState)
await readJiraSavedFilterState({ kind: 'environment', environmentId: 'host-c' })
expect(rpc).toHaveBeenCalledWith(
{ kind: 'environment', environmentId: 'host-c' },
'ui.getJiraSavedFilters',
undefined,
{ timeoutMs: 15_000 }
)
expect(runtime.call).not.toHaveBeenCalled()
})
@@ -1,140 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { copyClipboardTextViaExecCommand } from './web-clipboard-copy-fallback'
type FakeDocOptions = {
dispatchesCopyEvent?: boolean
execCommandResult?: boolean
execCommandThrows?: boolean
withClipboardData?: boolean
}
function createFakeDocument(options?: FakeDocOptions) {
const listeners: ((event: unknown) => void)[] = []
const clipboardData = { setData: vi.fn() }
const stopImmediatePropagation = vi.fn()
const createElement = vi.fn()
const appendChild = vi.fn()
const execCommand = vi.fn((command: string) => {
if (options?.execCommandThrows) {
throw new Error('execCommand denied')
}
if (command !== 'copy') {
return false
}
if (options?.dispatchesCopyEvent ?? true) {
for (const listener of listeners.slice()) {
listener({
clipboardData: (options?.withClipboardData ?? true) ? clipboardData : undefined,
preventDefault: vi.fn(),
stopImmediatePropagation
})
}
}
return options?.execCommandResult ?? true
})
const doc = {
activeElement: { focus: vi.fn() },
createElement,
execCommand,
addEventListener: vi.fn((type: string, listener: (event: unknown) => void) => {
if (type === 'copy') {
listeners.push(listener)
}
}),
removeEventListener: vi.fn((type: string, listener: (event: unknown) => void) => {
if (type === 'copy') {
const index = listeners.indexOf(listener)
if (index !== -1) {
listeners.splice(index, 1)
}
}
}),
body: { appendChild }
} as unknown as Document
return {
doc,
clipboardData,
stopImmediatePropagation,
createElement,
appendChild,
execCommand,
listeners
}
}
describe('copyClipboardTextViaExecCommand', () => {
it('serves the text from the copy event without touching the DOM', () => {
const { doc, clipboardData, createElement, appendChild, execCommand } = createFakeDocument()
expect(copyClipboardTextViaExecCommand('terminal selection', doc)).toBe(true)
expect(clipboardData.setData).toHaveBeenCalledWith('text/plain', 'terminal selection')
expect(execCommand).toHaveBeenCalledWith('copy')
expect(createElement).not.toHaveBeenCalled()
expect(appendChild).not.toHaveBeenCalled()
})
it('removes the copy listener after a successful copy', () => {
const { doc, listeners } = createFakeDocument()
copyClipboardTextViaExecCommand('copy me', doc)
expect(listeners).toHaveLength(0)
})
it('removes the copy listener when execCommand throws', () => {
const { doc, listeners } = createFakeDocument({ execCommandThrows: true })
expect(copyClipboardTextViaExecCommand('copy me', doc)).toBe(false)
expect(listeners).toHaveLength(0)
})
it('reports failure when no copy event is dispatched', () => {
const { doc, createElement, appendChild } = createFakeDocument({ dispatchesCopyEvent: false })
expect(copyClipboardTextViaExecCommand('copy me', doc)).toBe(false)
expect(createElement).not.toHaveBeenCalled()
expect(appendChild).not.toHaveBeenCalled()
})
it('reports failure when the copy event has no clipboardData', () => {
const { doc } = createFakeDocument({ withClipboardData: false })
expect(copyClipboardTextViaExecCommand('copy me', doc)).toBe(false)
})
it('reports failure when execCommand reports failure', () => {
const { doc } = createFakeDocument({ execCommandResult: false })
expect(copyClipboardTextViaExecCommand('copy me', doc)).toBe(false)
})
it('returns false when the document has no execCommand', () => {
const doc = { addEventListener: vi.fn() } as unknown as Document
expect(copyClipboardTextViaExecCommand('copy me', doc)).toBe(false)
})
it('returns false when the document has no event listener API', () => {
const doc = { execCommand: vi.fn() } as unknown as Document
expect(copyClipboardTextViaExecCommand('copy me', doc)).toBe(false)
})
it('leaves focus and the page selection untouched', () => {
const { doc } = createFakeDocument()
const focus = vi.fn()
const selection = {
rangeCount: 1,
getRangeAt: vi.fn(),
removeAllRanges: vi.fn(),
addRange: vi.fn()
}
;(doc as unknown as { activeElement: unknown }).activeElement = { focus }
;(doc as unknown as { getSelection: () => unknown }).getSelection = () => selection
expect(copyClipboardTextViaExecCommand('copy me', doc)).toBe(true)
expect(selection.removeAllRanges).not.toHaveBeenCalled()
expect(focus).not.toHaveBeenCalled()
})
})
@@ -5,23 +5,10 @@ import {
} from '../../agent-session-journal-producer'
describe('isRootAgentJournalItem', () => {
it("reads a row carrying no agent id as the session's own", () => {
expect(isRootAgentJournalItem({})).toBe(true)
})
it('reads a row carrying one as a subagent’s', () => {
expect(isRootAgentJournalItem({ agentId: 'task-1' })).toBe(false)
})
it('reads an id that failed to resolve as a subagent’s, not as root', () => {
// Presence, not truthiness: an unresolved child id must never attribute its content to the parent.
expect(isRootAgentJournalItem({ agentId: '' })).toBe(false)
})
it('reads a missing item as root rather than throwing', () => {
// Every caller walks a list backwards with an index that can fall off it.
expect(isRootAgentJournalItem(undefined)).toBe(true)
})
})
describe('agentJournalLinkageFields', () => {
@@ -31,15 +18,4 @@ describe('agentJournalLinkageFields', () => {
expect(Object.keys(agentJournalLinkageFields({ agentId: 'task-1' }))).toEqual(['agentId'])
expect(agentJournalLinkageFields(undefined)).toEqual({})
})
it('carries every member of the bundle through', () => {
const linkage = {
agentId: 'task-1',
parentAgentId: 'task-parent',
providerParentRef: 'toolu_1',
producerKind: 'background' as const,
attempt: 3
}
expect(agentJournalLinkageFields(linkage)).toEqual(linkage)
})
})
@@ -29,46 +29,6 @@ describe('RelayDispatcher silent-client reaper', () => {
expect(detachListener).toHaveBeenCalledWith(clientId, 'local')
})
it('keeps a quiet but answering client attached', () => {
const detachListener = vi.fn()
dispatcher = new RelayDispatcher(() => true)
dispatcher.onClientDetached(detachListener)
const clientId = dispatcher.attachClient(() => true)
for (let tick = 0; tick < 10; tick += 1) {
vi.advanceTimersByTime(KEEPALIVE_SEND_MS)
dispatcher.feedClient(clientId, encodeKeepAliveFrame(tick + 1, 0))
}
// Assert this client specifically; the unfed primary in this harness proves nothing about the socket under test.
expect(detachListener).not.toHaveBeenCalledWith(clientId, expect.anything())
})
it('does not reap every client on the first tick after the host slept', () => {
const detachListener = vi.fn()
dispatcher = new RelayDispatcher(() => true)
dispatcher.onClientDetached(detachListener)
dispatcher.attachClient(() => true)
// One tick fires far late because the process was paused, not because the peers went away.
vi.setSystemTime(10 * 60_000)
vi.advanceTimersByTime(KEEPALIVE_SEND_MS)
expect(detachListener).not.toHaveBeenCalled()
})
it('does not judge a client that has not spoken yet by the silence window', () => {
// A slow initial handshake can outlast the silence window; give never-speaking clients a wider bound.
const detachListener = vi.fn()
dispatcher = new RelayDispatcher(() => true)
dispatcher.onClientDetached(detachListener)
const clientId = dispatcher.attachClient(() => true)
vi.advanceTimersByTime(TIMEOUT_MS * 5)
expect(detachListener).not.toHaveBeenCalledWith(clientId, expect.anything())
})
it('still bounds a client that never speaks at all', () => {
// Bound attach age because a never-speaking client has no lastReceivedAt to expire.
const detachListener = vi.fn()