From 3a983d06fd907bd3a72f8f5ea8d01366cfea0d2f Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sun, 1 Nov 2026 04:37:19 -0800 Subject: [PATCH] test: remove 278 low-value declarations and 31 suites (#37007) * test: remove 278 low-value declarations and 31 suites * test: prune timeout baseline after test removal --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- config/flaky-test-pattern-baseline.txt | 3 +- config/reliability-gates.jsonc | 25 ++-- config/scripts/ci-shard-timings.json | 29 ----- ...rowser-extension-permission-grants.test.ts | 105 +-------------- ...owser-extension-webstore-redirects.test.ts | 16 --- .../runtime-auth-service.test.ts | 30 ----- .../claude-usage-report-aggregation.test.ts | 40 ------ .../claude-subagent-task-frames.test.ts | 120 ------------------ .../codex/codex-structured-sign-in.test.ts | 14 -- .../renderer-heap-allocation-sites.test.ts | 39 ------ .../macos-daemon-bundle-retirement.test.ts | 6 - .../daemon/macos-retired-daemon-jobs.test.ts | 42 ------ .../session-ingest-throughput.bench.test.ts | 119 ----------------- .../ephemeral-vm/resume-integrity.test.ts | 67 ---------- .../gh-exec-file-deadline.test.ts | 39 ------ ...esh-coordinator-wsl-stopped-distro.test.ts | 53 -------- src/main/gitlab/mappers-workitem.test.ts | 118 ----------------- .../ipc/github-click-wsl-user-act.test.ts | 118 ----------------- ...mote-versioned-install-termination.test.ts | 117 ----------------- .../journal-dispatch-reason-bound.spec.ts | 16 --- .../queued-message-update.spec.ts | 51 -------- ...ured-agent-session-codex-held-goal.test.ts | 23 ---- ...ent-session-restart-failure-filing.test.ts | 16 --- ...tructured-agent-session-send-block.test.ts | 116 ----------------- .../local-file-sink-memory.test.ts | 51 -------- .../profile-telemetry-consent-seed.test.ts | 9 -- .../auth-filesystem-operation.test.ts | 35 ----- ...orchestration-delivery-consumption.test.ts | 26 ---- .../clipboard-image-upload-decoding.test.ts | 50 -------- .../terminal-viewer-colors-method.test.ts | 49 +------ ...space-session-controller-twin-repo.test.ts | 40 ------ .../workspace-git-identity.test.ts | 17 --- src/main/runtime/terminal-tail-crlf.test.ts | 21 --- .../runtime/windows-drive-listing.test.ts | 116 ----------------- src/main/sqlite/sqlite-read-failure.test.ts | 57 +-------- .../ssh/orcad-server-project-groups.test.ts | 112 ---------------- ...single-instance-lock-exit.electron.test.ts | 8 -- ...cli-reconciliation-startup-barrier.test.ts | 50 -------- .../updater-agent-state-rules-release.test.ts | 116 ----------------- src/main/updater.fallback.test.ts | 117 ----------------- src/main/usage/agent-token-usage.test.ts | 117 ----------------- src/main/worktree-removal-authority.test.ts | 114 ----------------- .../AutomationsPage.setup-consent.test.tsx | 116 ----------------- .../preview-terminal-snapshot-replay.test.ts | 44 ------- .../combined-diff-view-cache.test.ts | 14 -- ...wn-document-worktree-path-selector.test.ts | 115 ----------------- .../rich-markdown-lowlight-cache.test.ts | 22 ---- .../rich-markdown-mixed-task-lists.test.ts | 23 ---- .../react-185-bystander-attribution.test.tsx | 116 ----------------- .../native-chat-observed-send.test.ts | 111 ---------------- ...-chat-working-status-shared-clock.test.tsx | 38 +----- ...ed-agent-session-delivery-notices.test.tsx | 109 ---------------- .../ai-vault-session-deletability.test.ts | 114 ----------------- .../gitlab-mr-merge-state.test.ts | 114 ----------------- ...ntrol-create-review-blocked-action.test.ts | 117 ----------------- .../review/suppressed-github-pr.test.ts | 118 ----------------- .../useSourceControlSelection.test.ts | 56 -------- .../sidebar/markdown-top-level-blocks.test.ts | 38 ------ .../sleep-worktree-activation-race.test.ts | 117 ----------------- .../workspace-hover-attachments.test.ts | 30 ----- ...tree-list-groups-pending-creations.test.ts | 117 ----------------- ...terminal-ime-forwarder-space-claim.test.ts | 23 ---- ...term-windows-resumed-preedit-trace.test.ts | 112 ---------------- .../terminal-layout-leaf-detach.test.ts | 119 ----------------- ...terminal-pane-local-windows-conpty.test.ts | 118 ----------------- ...orktree-jump-palette-status-inputs.test.ts | 112 ---------------- ...talled-agent-skill-discovery-cache.test.ts | 17 --- ...cEvents-client-hosted-browser-rows.test.ts | 41 ------ ...gent-startup-creation-continuation.test.ts | 23 ---- src/renderer/src/lib/file-type-icons.test.ts | 93 +------------- .../active-workspace-ssh-targets.test.ts | 112 ---------------- .../active-terminal-chrome-selector.test.ts | 115 ----------------- ...cted-agents-environment-prune-leak.test.ts | 14 -- .../worktree-catalog-reconciliation.test.ts | 84 +----------- ...s-runtime-host-metadata-retirement.test.ts | 115 ----------------- .../agent-startup-update-suppression.test.ts | 82 ------------ 76 files changed, 15 insertions(+), 5121 deletions(-) delete mode 100644 src/main/claude/claude-subagent-task-frames.test.ts delete mode 100644 src/main/daemon/session-ingest-throughput.bench.test.ts delete mode 100644 src/main/ephemeral-vm/resume-integrity.test.ts delete mode 100644 src/main/gitlab/mappers-workitem.test.ts delete mode 100644 src/main/ipc/github-click-wsl-user-act.test.ts delete mode 100644 src/main/managed-server/install/remote-versioned-install-termination.test.ts delete mode 100644 src/main/native-chat/agent-session-wire/structured-agent-session-send-block.test.ts delete mode 100644 src/main/runtime/windows-drive-listing.test.ts delete mode 100644 src/main/ssh/orcad-server-project-groups.test.ts delete mode 100644 src/main/updater-agent-state-rules-release.test.ts delete mode 100644 src/main/updater.fallback.test.ts delete mode 100644 src/main/usage/agent-token-usage.test.ts delete mode 100644 src/main/worktree-removal-authority.test.ts delete mode 100644 src/renderer/src/components/automations/AutomationsPage.setup-consent.test.tsx delete mode 100644 src/renderer/src/components/editor/markdown-document-worktree-path-selector.test.ts delete mode 100644 src/renderer/src/components/error-boundaries/react-185-bystander-attribution.test.tsx delete mode 100644 src/renderer/src/components/native-chat/native-chat-observed-send.test.ts delete mode 100644 src/renderer/src/components/native-chat/use-structured-agent-session-delivery-notices.test.tsx delete mode 100644 src/renderer/src/components/right-sidebar/ai-vault-session-deletability.test.ts delete mode 100644 src/renderer/src/components/right-sidebar/gitlab-mr-merge-state.test.ts delete mode 100644 src/renderer/src/components/right-sidebar/source-control-create-review-blocked-action.test.ts delete mode 100644 src/renderer/src/components/right-sidebar/source-control/review/suppressed-github-pr.test.ts delete mode 100644 src/renderer/src/components/sidebar/sleep-worktree-activation-race.test.ts delete mode 100644 src/renderer/src/components/sidebar/worktree-list-groups-pending-creations.test.ts delete mode 100644 src/renderer/src/components/terminal-pane/terminal-ime-xterm-windows-resumed-preedit-trace.test.ts delete mode 100644 src/renderer/src/components/terminal-pane/terminal-layout-leaf-detach.test.ts delete mode 100644 src/renderer/src/components/terminal-pane/terminal-pane-local-windows-conpty.test.ts delete mode 100644 src/renderer/src/components/worktree-jump-palette-status-inputs.test.ts delete mode 100644 src/renderer/src/startup/active-workspace-ssh-targets.test.ts delete mode 100644 src/renderer/src/store/active-terminal-chrome-selector.test.ts delete mode 100644 src/renderer/src/store/slices/worktrees-runtime-host-metadata-retirement.test.ts diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index e8066b6bcde..4ab8c1927f8 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -1046,7 +1046,6 @@ 3 test-no-wall-clock:src/main/windows/windows-msys-job.win32.test.ts 1 test-no-wall-clock:src/main/windows/windows-process-table-native-addon.win32.test.ts 2 test-no-wall-clock:src/main/windows/windows-pty-job.win32.test.ts -3 test-no-wall-clock:src/main/worktree-removal-authority.test.ts 1 test-no-wall-clock:src/main/worktree-removal-close-records.test.ts 3 test-no-wall-clock:src/main/wsl/wsl-orcad-real-distro-lane.test.ts 4 test-no-wall-clock:src/main/wsl/wsl-runner.wsl.test.ts @@ -1319,7 +1318,7 @@ 2 test-timeout-needs-reason:src/main/ssh/ssh-credential-prompt-login-grace-wire.test.ts 1 test-timeout-needs-reason:src/main/ssh/ssh-exec-stdin-file-transfer.test.ts 2 test-timeout-needs-reason:src/main/ssh/system-transport/system-ssh-forward-process-real-openssh.test.ts -3 test-timeout-needs-reason:src/main/startup/single-instance-lock-exit.electron.test.ts +2 test-timeout-needs-reason:src/main/startup/single-instance-lock-exit.electron.test.ts 1 test-timeout-needs-reason:src/main/usage/usage-scan-worker-event-loop.test.ts 1 test-timeout-needs-reason:src/main/window/renderer-publication-throttle.electron.test.ts 1 test-timeout-needs-reason:src/main/windows/windows-administrator-restart.win32.test.ts diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index c14ae4f280b..1cfea45bd2c 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -670,21 +670,20 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime", "mobile-relay"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "daemon", "ssh", "remote-runtime", "mobile-relay"], - "coverageNotes": "Production mobile controller, parser, presentation, overlay, view, question, permission and composer run with native surfaces mocked on macOS. Desktop selector groups traverse the real observed-write queue, verified helper, IPC chunker, SSH provider and transport callbacks; paired sends traverse the real dispatcher and runtime writer into that provider. Local/daemon contracts and released-version terminal codecs are covered. Provider labels describe contracts, not physical deployments; physical WSL/Linux/Windows remain gaps.", + "coverageNotes": "Production mobile controller, parser, presentation, overlay, view, question, permission and composer run with native surfaces mocked on macOS. Desktop selector groups traverse the real observed-write queue, verified helper, IPC chunker, SSH provider and transport callbacks; paired sends traverse the real dispatcher and runtime writer into that provider. Local/daemon contracts and released-version terminal codecs are covered. Provider labels describe contracts, not physical deployments; physical WSL/Linux/Windows remain gaps. Audit885 removes the isolated observed-send mock-call suite; no replacement-equivalence claim is made.", "motivatingLinks": ["https://github.com/stablyai/orca/pull/25761"], "invariant": "A prompt occurrence leaves the input region only when an accepted reply answers it (a current host's provider acknowledgment, or an older host's whole-write acceptance as before) or the user collapses it to a strip above the composer, which writes nothing and keeps the card mounted with its partial answer; status-backed occurrences are told apart by the host wait's start, so view toggles and remounts preserve them while real clear/replacement supersedes them, and a transcript-only one is dropped when the view stops observing it; obsolete results cannot change replacements; selector groups wait for complete writes before pacing; refusal or unknown handoff never causes replay; a card retires an unsubmitted composer send without writing under it or losing its text, and the hidden composer cannot send or interrupt; Collapse or Escape folds a card without writing, never during an in-flight answer, and a new occurrence shows expanded again; the phone blocks only Send while a card is expanded and shows one card per occurrence; cancellation stops unsent groups and active dictation can stop.", "oracle": "After acknowledged phone replies with stale host status, the exact card disappears and Send is enabled; unknown, refused or unverifiable-settlement replies retain enabled choices, while an older host's accepted reply dismisses like an acknowledged one; a same-occurrence view toggle retains pending acknowledgment and real replacements discard it. A slow 32 KiB SSH answer completes all chunks before Enter; cancellation preserves issued bytes but omits remaining groups. Paired settlement invokes the actual provider and lost callbacks remain unknown; older hosts deliver ordinary messages, launch and selector groups once and their accepted answers dismiss as before. An answered card stays hidden across a view remount; a collapsed card is a strip that frees the composer and expands back; an identical question in a new wait shows again; a composer send caught by a new card keeps its bubble without submitting or clearing the line under it.", "commands": [ - "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-observed-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-composer.test.tsx src/renderer/src/components/native-chat/use-native-chat-prompt-card-presentation.test.ts src/renderer/src/components/native-chat/NativeChatComposer.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts src/main/runtime/runtime-terminal-writer.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts src/renderer/src/lib/agent-paste-draft-readiness-budget.test.ts tests/e2e/native-chat-ssh-delivery.unit.test.ts --maxWorkers=2", + "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-composer.test.tsx src/renderer/src/components/native-chat/use-native-chat-prompt-card-presentation.test.ts src/renderer/src/components/native-chat/NativeChatComposer.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts src/main/runtime/runtime-terminal-writer.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts src/renderer/src/lib/agent-paste-draft-readiness-budget.test.ts tests/e2e/native-chat-ssh-delivery.unit.test.ts --maxWorkers=2", "ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts mobile/src/session/MobileNativeChatView.stop.test.ts mobile/src/session/MobileNativeChatView.failure-banner.test.ts mobile/src/session/MobileNativeChatView.rows.test.ts mobile/src/session/MobileNativeChatView.composer-lock.test.ts mobile/src/session/MobileNativeChatView.tail-follow.test.ts mobile/src/session/MobileNativeChatView.history-paging.test.ts mobile/src/session/mobile-prompt-controller.test.ts mobile/src/session/native-chat/input/mobile-native-chat-permission-send.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts mobile/src/session/native-chat/mobile-native-chat-send.test.ts mobile/src/session/MobileNativeChatCardHeaderAction.test.tsx mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx --maxWorkers=2", "ORCA_BACKGROUND_LAUNCH=1 pnpm test tests/e2e/cross-version-wire/cross-version-terminal-wire.unit.test.ts --maxWorkers=1", - "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-observed-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts --maxWorkers=2", + "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts --maxWorkers=2", "ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts mobile/src/session/MobileNativeChatView.stop.test.ts mobile/src/session/MobileNativeChatView.failure-banner.test.ts mobile/src/session/MobileNativeChatView.rows.test.ts mobile/src/session/MobileNativeChatView.composer-lock.test.ts mobile/src/session/MobileNativeChatView.tail-follow.test.ts mobile/src/session/MobileNativeChatView.history-paging.test.ts --maxWorkers=2" ], "testFiles": [ "src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx", "src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx", - "src/renderer/src/components/native-chat/native-chat-observed-send.test.ts", "src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts", "src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts", "src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx", @@ -810,7 +809,7 @@ "runner": "local", "platform": "macos", "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-observed-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts --maxWorkers=2", - "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-observed-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts --maxWorkers=2", + "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts --maxWorkers=2", "result": "passed", "durationSeconds": 232.36, "summary": "257 tests passed across 19 explicit files, covering production cards, observed sends, existing launch/paste callers, IPC validation, SSH acknowledgment and daemon settlement recovery." @@ -830,7 +829,7 @@ "runner": "local", "platform": "macos", "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-observed-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-composer.test.tsx src/renderer/src/components/native-chat/use-native-chat-prompt-card-presentation.test.ts src/renderer/src/runtime/runtime-terminal-verified-input.test.ts src/renderer/src/components/native-chat/NativeChatComposer.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts src/main/runtime/runtime-terminal-writer.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/rpc/terminal-send-launch-draft-resolution.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts src/renderer/src/lib/agent-paste-draft-readiness-budget.test.ts tests/e2e/native-chat-ssh-delivery.unit.test.ts --maxWorkers=2", - "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-observed-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-composer.test.tsx src/renderer/src/components/native-chat/use-native-chat-prompt-card-presentation.test.ts src/renderer/src/components/native-chat/NativeChatComposer.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts src/main/runtime/runtime-terminal-writer.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts src/renderer/src/lib/agent-paste-draft-readiness-budget.test.ts tests/e2e/native-chat-ssh-delivery.unit.test.ts --maxWorkers=2", + "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-composer.test.tsx src/renderer/src/components/native-chat/use-native-chat-prompt-card-presentation.test.ts src/renderer/src/components/native-chat/NativeChatComposer.test.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts src/renderer/src/runtime/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/renderer/src/lib/agent-followup-delivery.test.ts src/main/ipc/pty/ipc/write-input-acceptance.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/daemon/daemon-pty-write-settlement-recovery.test.ts src/main/runtime/runtime-terminal-writer.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts src/renderer/src/lib/agent-paste-draft-readiness-budget.test.ts tests/e2e/native-chat-ssh-delivery.unit.test.ts --maxWorkers=2", "result": "passed", "durationSeconds": 14.53, "summary": "391 tests passed across 29 explicit files after the round-4 review fix, including older-host acceptance, the inert hidden composer, collapse that keeps a partial answer and returns focus to the composer, pane-scoped card focus, wait-start occurrence identity and the pane remount signal." @@ -3609,7 +3608,7 @@ "providers": ["orca-server", "direct-ssh"], "coveredPlatforms": ["macos", "linux", "windows"], "coveredProviders": ["orca-server", "direct-ssh"], - "coverageNotes": "A pinned exact-source harness runs the pre-#14352 reader and lifecycle service against baseline, affected-main, candidate, and fix-reverted stores. Platform-neutral tests cover store compatibility, user-cancellable cleanup, branch/ref adoption, and resume ownership integrity. The direct-SSH provisioned-root Electron journey remains the live transport and destroy proof; this desktop sidecar is not mobile-facing and changes no RPC or remote wire payload.", + "coverageNotes": "A pinned exact-source harness runs the pre-#14352 reader and lifecycle service against baseline, affected-main, candidate, and fix-reverted stores. Platform-neutral tests cover store compatibility, user-cancellable cleanup, branch/ref adoption; audit885 removes the direct resume ownership predicate alarms. The direct-SSH provisioned-root Electron journey remains the live transport and destroy proof; this desktop sidecar is not mobile-facing and changes no RPC or remote wire payload.", "motivatingLinks": [ "https://linear.app/stably/issue/STA-4274", "https://github.com/stablyai/orca/pull/14352", @@ -3619,7 +3618,7 @@ "oracle": "Write one ordinary schema-v1 runtime and one provisioned-root runtime. Require the exact pre-#14352 reader to return both, run the old destroy lifecycle against the projected provisioned record, then require the candidate to retain the cleaned state while restoring schema-v2 and checkout-mode metadata. The same old reader must reject affected-main and fix-reverted bytes. Malformed, oversized, unknown-record, and future-version feature sidecars must leave rollback-compatible records accessible and must not be overwritten during compatible lifecycle mutations. Require destroy to have no implicit deadline, stop a hung destroy through the public desktop action, require cleanup_failed plus a fresh successful retry, prove provider or project-host rollback failure retains every ownership record required to retry, and prove missing recipe context cannot leave an unremovable hidden SSH registration. Supply the source ref's captured commit to create, reject a provisioned checkout on the wrong branch/ref, and reject a resume that changes connection mode or SSH ownership.", "commands": [ "node config/scripts/run-ephemeral-vm-runtime-store-rollback-repro.mjs config/scripts/ephemeral-vm-runtime-store-cross-version.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/shared/ephemeral-vm-runtime-store.test.ts src/shared/ephemeral-vm-runtime-store-rollback.test.ts src/shared/ephemeral-vm-recipe-process.test.ts src/main/ephemeral-vm/runtime-service.test.ts src/main/ephemeral-vm/resume-integrity.test.ts src/main/ephemeral-vm/recipe-runner.test.ts src/main/ephemeral-vm/runtime-ssh-cleanup.test.ts src/main/ipc/ephemeral-vm-runtime-handler-cleanup.test.ts src/main/ipc/ephemeral-vm-provision-cancel.test.ts src/main/ipc/ephemeral-vm-provisioned-root-ref.test.ts src/main/ipc/ephemeral-vm.test.ts src/renderer/src/components/settings/EphemeralVmRuntimesSection.test.tsx src/renderer/src/lib/ephemeral-vm-failed-create-cleanup.test.ts src/renderer/src/lib/ephemeral-vm-runtime-cleanup.test.ts src/renderer/src/lib/ephemeral-vm-workspace-target.test.ts src/renderer/src/lib/provisioned-root-create-options.test.ts src/renderer/src/store/slices/repos-ephemeral-vm-cleanup-retention.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/shared/ephemeral-vm-runtime-store.test.ts src/shared/ephemeral-vm-runtime-store-rollback.test.ts src/shared/ephemeral-vm-recipe-process.test.ts src/main/ephemeral-vm/runtime-service.test.ts src/main/ephemeral-vm/recipe-runner.test.ts src/main/ephemeral-vm/runtime-ssh-cleanup.test.ts src/main/ipc/ephemeral-vm-runtime-handler-cleanup.test.ts src/main/ipc/ephemeral-vm-provision-cancel.test.ts src/main/ipc/ephemeral-vm-provisioned-root-ref.test.ts src/main/ipc/ephemeral-vm.test.ts src/renderer/src/components/settings/EphemeralVmRuntimesSection.test.tsx src/renderer/src/lib/ephemeral-vm-failed-create-cleanup.test.ts src/renderer/src/lib/ephemeral-vm-runtime-cleanup.test.ts src/renderer/src/lib/ephemeral-vm-workspace-target.test.ts src/renderer/src/lib/provisioned-root-create-options.test.ts src/renderer/src/store/slices/repos-ephemeral-vm-cleanup-retention.test.ts", "pnpm exec playwright test tests/e2e/ephemeral-vm-cleanup-retry.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1" ], "testFiles": [ @@ -3631,7 +3630,6 @@ "src/main/ephemeral-vm/runtime-service.test.ts", "src/main/ephemeral-vm/recipe-runner.test.ts", "src/main/ephemeral-vm/runtime-ssh-cleanup.test.ts", - "src/main/ephemeral-vm/resume-integrity.test.ts", "src/main/ipc/ephemeral-vm-runtime-handler-cleanup.test.ts", "src/main/ipc/ephemeral-vm-provision-cancel.test.ts", "src/main/ipc/ephemeral-vm-provisioned-root-ref.test.ts", @@ -3713,13 +3711,6 @@ "file": "src/main/ipc/ephemeral-vm-provision-cancel.test.ts", "assertions": ["cancellation during source-ref resolution aborts before provider create"] }, - { - "file": "src/main/ephemeral-vm/resume-integrity.test.ts", - "assertions": [ - "resume allows host and port rotation", - "resume rejects connection-mode and stable SSH ownership drift" - ] - }, { "file": "tests/e2e/ephemeral-vm-cleanup-retry.spec.ts", "assertions": [ @@ -3745,7 +3736,7 @@ "result": "passed", "durationSeconds": 3, "summary": "One hundred twenty-two focused store, recipe-process, lifecycle, cleanup-handler, rendered stop-action, retry-context, source-ref, adoption-integrity, resume-integrity, and provisioning-recovery tests passed.", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/shared/ephemeral-vm-runtime-store.test.ts src/shared/ephemeral-vm-runtime-store-rollback.test.ts src/shared/ephemeral-vm-recipe-process.test.ts src/main/ephemeral-vm/runtime-service.test.ts src/main/ephemeral-vm/resume-integrity.test.ts src/main/ephemeral-vm/recipe-runner.test.ts src/main/ephemeral-vm/runtime-ssh-cleanup.test.ts src/main/ipc/ephemeral-vm-runtime-handler-cleanup.test.ts src/main/ipc/ephemeral-vm-provision-cancel.test.ts src/main/ipc/ephemeral-vm-provisioned-root-ref.test.ts src/main/ipc/ephemeral-vm.test.ts src/renderer/src/components/settings/EphemeralVmRuntimesSection.test.tsx src/renderer/src/lib/ephemeral-vm-failed-create-cleanup.test.ts src/renderer/src/lib/ephemeral-vm-runtime-cleanup.test.ts src/renderer/src/lib/ephemeral-vm-workspace-target.test.ts src/renderer/src/lib/provisioned-root-create-options.test.ts src/renderer/src/store/slices/repos-ephemeral-vm-cleanup-retention.test.ts" + "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/shared/ephemeral-vm-runtime-store.test.ts src/shared/ephemeral-vm-runtime-store-rollback.test.ts src/shared/ephemeral-vm-recipe-process.test.ts src/main/ephemeral-vm/runtime-service.test.ts src/main/ephemeral-vm/recipe-runner.test.ts src/main/ephemeral-vm/runtime-ssh-cleanup.test.ts src/main/ipc/ephemeral-vm-runtime-handler-cleanup.test.ts src/main/ipc/ephemeral-vm-provision-cancel.test.ts src/main/ipc/ephemeral-vm-provisioned-root-ref.test.ts src/main/ipc/ephemeral-vm.test.ts src/renderer/src/components/settings/EphemeralVmRuntimesSection.test.tsx src/renderer/src/lib/ephemeral-vm-failed-create-cleanup.test.ts src/renderer/src/lib/ephemeral-vm-runtime-cleanup.test.ts src/renderer/src/lib/ephemeral-vm-workspace-target.test.ts src/renderer/src/lib/provisioned-root-create-options.test.ts src/renderer/src/store/slices/repos-ephemeral-vm-cleanup-retention.test.ts" } ], "runtimeBudget": { diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json index efe50bcb891..240702581fb 100644 --- a/config/scripts/ci-shard-timings.json +++ b/config/scripts/ci-shard-timings.json @@ -1583,7 +1583,6 @@ "src/main/claude/claude-subagent-id-aliases.test.ts": 40, "src/main/claude/claude-subagent-permission-request.test.ts": 2528, "src/main/claude/claude-subagent-resume-after-restart.test.ts": 722, - "src/main/claude/claude-subagent-task-frames.test.ts": 34, "src/main/claude/claude-supervised-stop-real-cli.test.ts": 425, "src/main/claude/claude-supervised-stop.integration.test.ts": 3670, "src/main/claude/claude-thread-goal-journaling.test.ts": 328, @@ -2196,7 +2195,6 @@ "src/main/daemon/serialize-grid-transcript-replay.test.ts": 15950, "src/main/daemon/serialize-grid.differential.fuzz.test.ts": 398, "src/main/daemon/session-conpty-resize-history.test.ts": 398, - "src/main/daemon/session-ingest-throughput.bench.test.ts": 124, "src/main/daemon/session-pending-output.test.ts": 207, "src/main/daemon/session-reset-input-modes.test.ts": 143, "src/main/daemon/session-shell-recovery.test.ts": 254, @@ -2292,7 +2290,6 @@ "src/main/emulator/serve-sim-runtime-materializer.test.ts": 106, "src/main/emulator/simctl-simulator-devices.test.ts": 69, "src/main/ephemeral-vm/recipe-runner.test.ts": 649, - "src/main/ephemeral-vm/resume-integrity.test.ts": 111, "src/main/ephemeral-vm/runtime-service.test.ts": 715, "src/main/ephemeral-vm/runtime-ssh-cleanup.test.ts": 154, "src/main/execution-host-workspace-trust.test.ts": 194, @@ -2625,7 +2622,6 @@ "src/main/gitlab/gitlab-known-host-retirement.test.ts": 420, "src/main/gitlab/gl-utils.test.ts": 437, "src/main/gitlab/issues.test.ts": 421, - "src/main/gitlab/mappers-workitem.test.ts": 44, "src/main/gitlab/mappers.test.ts": 59, "src/main/gitlab/merge-request-creation.test.ts": 539, "src/main/gitlab/mr-head-tracking-ref.test.ts": 43, @@ -3328,7 +3324,6 @@ "src/main/managed-server/install/remote-node-runtime-store-gc.test.ts": 1905, "src/main/managed-server/install/remote-node-runtime-store-lock-host-release.test.ts": 2217, "src/main/managed-server/install/remote-node-runtime-store-windows.test.ts": 1694, - "src/main/managed-server/install/remote-versioned-install-termination.test.ts": 156, "src/main/managed-server/install/remote-versioned-install.test.ts": 346, "src/main/managed-server/lifecycle/orcad-decommission-released-peer-recovery.test.ts": 118, "src/main/managed-server/lifecycle/orcad-decommission-transaction.test.ts": 111, @@ -3591,7 +3586,6 @@ "src/main/native-chat/agent-session-wire/structured-agent-session-rewind-provenance.test.ts": 424, "src/main/native-chat/agent-session-wire/structured-agent-session-rewind.test.ts": 1998, "src/main/native-chat/agent-session-wire/structured-agent-session-running-operation-order.test.ts": 1321, - "src/main/native-chat/agent-session-wire/structured-agent-session-send-block.test.ts": 191, "src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts": 1010, "src/main/native-chat/agent-session-wire/structured-agent-session-send-open-stale-turn.test.ts": 1588, "src/main/native-chat/agent-session-wire/structured-agent-session-send-options.test.ts": 2067, @@ -5759,7 +5753,6 @@ "src/main/runtime/windowless-main-layout-commands-layout-owner.test.ts": 2694, "src/main/runtime/windows-codex-held-prompt-resubmit.test.ts": 3365, "src/main/runtime/windows-default-route-interfaces.test.ts": 51, - "src/main/runtime/windows-drive-listing.test.ts": 31, "src/main/runtime/windows-firewall-remote-scope.test.ts": 35, "src/main/runtime/windows-mobile-firewall.test.ts": 79, "src/main/runtime/worker-terminal-archive-release.test.ts": 4360, @@ -6031,7 +6024,6 @@ "src/main/ssh/orcad-retained-source-changes.test.ts": 1858, "src/main/ssh/orcad-retained-source-lists.test.ts": 566, "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, "src/main/ssh/orcad-stdio-bridge.test.ts": 555, "src/main/ssh/orcad-terminal-census-client.test.ts": 162, @@ -6246,7 +6238,6 @@ "src/main/tray/system-tray.test.ts": 212, "src/main/tray/tray-attention-icon.test.ts": 41, "src/main/tray/tray-dev-badge.test.ts": 36, - "src/main/updater-agent-state-rules-release.test.ts": 58, "src/main/updater-changelog.test.ts": 46, "src/main/updater-events.test.ts": 231, "src/main/updater-lifecycle-diagnostics.test.ts": 26, @@ -6262,7 +6253,6 @@ "src/main/updater.check-failure.test.ts": 3024, "src/main/updater.check-preflight.test.ts": 4032, "src/main/updater.check-settlement.test.ts": 2235, - "src/main/updater.fallback.test.ts": 43, "src/main/updater.feed-attempt-lifetime.test.ts": 3428, "src/main/updater.headless-serve-install.test.ts": 364, "src/main/updater.install-failure-cause.test.ts": 1873, @@ -6280,7 +6270,6 @@ "src/main/usage-worktree-canonicalizer.test.ts": 51, "src/main/usage-worktree-metadata.test.ts": 44, "src/main/usage/agent-token-usage-reporter.test.ts": 605, - "src/main/usage/agent-token-usage.test.ts": 57, "src/main/usage/analytics-session-id-store.test.ts": 140, "src/main/usage/highest-usage-key.test.ts": 38, "src/main/usage/jsonl-file-reader.test.ts": 63, @@ -6417,7 +6406,6 @@ "src/main/worktree-preparation-discard-retry.test.ts": 309, "src/main/worktree-preparation-fetch-barrier.test.ts": 268, "src/main/worktree-preparation-tip-refresh.test.ts": 545, - "src/main/worktree-removal-authority.test.ts": 142, "src/main/worktree-removal-close-records.test.ts": 1302, "src/main/worktree-removal-home-guard.test.ts": 85, "src/main/worktree-removal-repo-owner.test.ts": 42, @@ -7118,7 +7106,6 @@ "src/renderer/src/components/editor/markdown-doc-completions.test.ts": 70, "src/renderer/src/components/editor/markdown-doc-links.test.ts": 90, "src/renderer/src/components/editor/markdown-document-list-request.test.ts": 252, - "src/renderer/src/components/editor/markdown-document-worktree-path-selector.test.ts": 1834, "src/renderer/src/components/editor/markdown-export-extract.test.ts": 287, "src/renderer/src/components/editor/markdown-export-html.test.ts": 41, "src/renderer/src/components/editor/markdown-frontmatter.test.ts": 40, @@ -7309,7 +7296,6 @@ "src/renderer/src/components/emulator-pane/use-mobile-emulator-tab-intro-actions.test.tsx": 1493, "src/renderer/src/components/error-boundaries/RecoverableRenderErrorBoundary.lazy-chunk.test.tsx": 364, "src/renderer/src/components/error-boundaries/RecoverableRenderErrorBoundary.never-landed-reload.test.tsx": 402, - "src/renderer/src/components/error-boundaries/react-185-bystander-attribution.test.tsx": 464, "src/renderer/src/components/feature-tips/NativeChatUpgradeTipDialog.test.tsx": 1501, "src/renderer/src/components/feature-tips/feature-tip-modal-state.test.ts": 139, "src/renderer/src/components/feature-tips/feature-tip-startup-gate.test.ts": 105, @@ -7564,7 +7550,6 @@ "src/renderer/src/components/native-chat/native-chat-message-list-projection.test.ts": 154, "src/renderer/src/components/native-chat/native-chat-message-rail-items.test.ts": 225, "src/renderer/src/components/native-chat/native-chat-new-rows-marker.test.tsx": 335, - "src/renderer/src/components/native-chat/native-chat-observed-send.test.ts": 95, "src/renderer/src/components/native-chat/native-chat-owner-selectors.test.tsx": 446, "src/renderer/src/components/native-chat/native-chat-pane-file-drop.test.tsx": 893, "src/renderer/src/components/native-chat/native-chat-pane-resolution.test.ts": 59, @@ -7706,7 +7691,6 @@ "src/renderer/src/components/native-chat/use-structured-agent-session-command-send-gate.test.tsx": 2263, "src/renderer/src/components/native-chat/use-structured-agent-session-command-start-failure.test.tsx": 1753, "src/renderer/src/components/native-chat/use-structured-agent-session-context-usage.test.tsx": 1808, - "src/renderer/src/components/native-chat/use-structured-agent-session-delivery-notices.test.tsx": 392, "src/renderer/src/components/native-chat/use-structured-agent-session-hold.test.tsx": 521, "src/renderer/src/components/native-chat/use-structured-agent-session-legacy-recovery.test.tsx": 1756, "src/renderer/src/components/native-chat/use-structured-agent-session-messages.test.tsx": 415, @@ -7859,7 +7843,6 @@ "src/renderer/src/components/right-sidebar/ai-vault-session-conditional-refresh-hook.test.tsx": 1736, "src/renderer/src/components/right-sidebar/ai-vault-session-conditional-refresh.test.ts": 133, "src/renderer/src/components/right-sidebar/ai-vault-session-continuation.test.ts": 51, - "src/renderer/src/components/right-sidebar/ai-vault-session-deletability.test.ts": 334, "src/renderer/src/components/right-sidebar/ai-vault-session-display.test.ts": 52, "src/renderer/src/components/right-sidebar/ai-vault-session-filters.test.ts": 72, "src/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.test.ts": 1767, @@ -7935,7 +7918,6 @@ "src/renderer/src/components/right-sidebar/git-status-twin-copy.test.ts": 1453, "src/renderer/src/components/right-sidebar/github-pr-link-modal.test.ts": 54, "src/renderer/src/components/right-sidebar/github-pr-stack-merge.test.ts": 109, - "src/renderer/src/components/right-sidebar/gitlab-mr-merge-state.test.ts": 207, "src/renderer/src/components/right-sidebar/local-workspace-port-sections.test.ts": 47, "src/renderer/src/components/right-sidebar/parent-pr-checks-projection-selector.test.ts": 557, "src/renderer/src/components/right-sidebar/parent-pr-checks-refresh.test.ts": 213, @@ -7959,7 +7941,6 @@ "src/renderer/src/components/right-sidebar/source-control-branch-context-row.test.tsx": 248, "src/renderer/src/components/right-sidebar/source-control-commit-message-rows.test.ts": 70, "src/renderer/src/components/right-sidebar/source-control-create-pr-intent-flow.test.ts": 57, - "src/renderer/src/components/right-sidebar/source-control-create-review-blocked-action.test.ts": 148, "src/renderer/src/components/right-sidebar/source-control-created-review-link.test.ts": 57, "src/renderer/src/components/right-sidebar/source-control-discard-confirmation.test.ts": 118, "src/renderer/src/components/right-sidebar/source-control-dropdown-items.create-pr-intent.test.ts": 163, @@ -7993,7 +7974,6 @@ "src/renderer/src/components/right-sidebar/source-control/notes/use-diff-comment-notes.test.tsx": 253, "src/renderer/src/components/right-sidebar/source-control/panel/use-panel-view-state.test.tsx": 367, "src/renderer/src/components/right-sidebar/source-control/review/create-pr-intent-operation-target.test.ts": 1391, - "src/renderer/src/components/right-sidebar/source-control/review/suppressed-github-pr.test.ts": 55, "src/renderer/src/components/right-sidebar/source-control/review/use-create-pr-intent-jira-title.test.tsx": 1632, "src/renderer/src/components/right-sidebar/source-control/review/use-hosted-review-creation.test.tsx": 1644, "src/renderer/src/components/right-sidebar/source-control/review/use-hosted-review-state.test.tsx": 268, @@ -8381,7 +8361,6 @@ "src/renderer/src/components/sidebar/sidebar-list-goldens.test.tsx": 3557, "src/renderer/src/components/sidebar/sidebar-list-parity.test.tsx": 5146, "src/renderer/src/components/sidebar/sidebar-project-drop.test.ts": 125, - "src/renderer/src/components/sidebar/sleep-worktree-activation-race.test.ts": 554, "src/renderer/src/components/sidebar/sleep-worktree-flow.test.ts": 562, "src/renderer/src/components/sidebar/ssh-host-remove-resolution.test.ts": 26, "src/renderer/src/components/sidebar/stale-agent-row-unverifiable.test.ts": 1488, @@ -8463,7 +8442,6 @@ "src/renderer/src/components/sidebar/worktree-list-groups-mobile-parity.test.ts": 306, "src/renderer/src/components/sidebar/worktree-list-groups-nested-project-groups.test.ts": 146, "src/renderer/src/components/sidebar/worktree-list-groups-notice-host-labels.test.ts": 215, - "src/renderer/src/components/sidebar/worktree-list-groups-pending-creations.test.ts": 246, "src/renderer/src/components/sidebar/worktree-list-groups-pinned-host-labels.test.ts": 190, "src/renderer/src/components/sidebar/worktree-list-groups-project-groups.test.ts": 177, "src/renderer/src/components/sidebar/worktree-list-groups-project-host-setups.test.ts": 149, @@ -9149,7 +9127,6 @@ "src/renderer/src/components/terminal-pane/terminal-ime-xterm-resumed-preedit-visibility.test.ts": 546, "src/renderer/src/components/terminal-pane/terminal-ime-xterm-trailing-preedit-occlusion.test.ts": 616, "src/renderer/src/components/terminal-pane/terminal-ime-xterm-transaction-events.test.ts": 516, - "src/renderer/src/components/terminal-pane/terminal-ime-xterm-windows-resumed-preedit-trace.test.ts": 645, "src/renderer/src/components/terminal-pane/terminal-inline-image-da1.test.ts": 112, "src/renderer/src/components/terminal-pane/terminal-input-host-platform.test.ts": 1435, "src/renderer/src/components/terminal-pane/terminal-input-quarantine.test.ts": 80, @@ -9161,7 +9138,6 @@ "src/renderer/src/components/terminal-pane/terminal-keyboard-native-chat-cover.test.ts": 1546, "src/renderer/src/components/terminal-pane/terminal-keyboard-protocol-pane-agent.test.ts": 37, "src/renderer/src/components/terminal-pane/terminal-kitty-csi-u-encoding.test.ts": 59, - "src/renderer/src/components/terminal-pane/terminal-layout-leaf-detach.test.ts": 37, "src/renderer/src/components/terminal-pane/terminal-layout-leaf-ids.test.ts": 44, "src/renderer/src/components/terminal-pane/terminal-layout-pty-ownership-depth.test.ts": 120, "src/renderer/src/components/terminal-pane/terminal-layout-pty-ownership.test.ts": 89, @@ -9206,7 +9182,6 @@ "src/renderer/src/components/terminal-pane/terminal-pane-host-state-memo.test.ts": 2151, "src/renderer/src/components/terminal-pane/terminal-pane-layout-owner-commands.test.ts": 222, "src/renderer/src/components/terminal-pane/terminal-pane-layout-owner-gestures.test.ts": 43, - "src/renderer/src/components/terminal-pane/terminal-pane-local-windows-conpty.test.ts": 1431, "src/renderer/src/components/terminal-pane/terminal-pane-mount-bootstrap.test.ts": 1256, "src/renderer/src/components/terminal-pane/terminal-pane-mount-events-layout-owner.test.ts": 743, "src/renderer/src/components/terminal-pane/terminal-pane-recovery-host-rebind-cap.test.ts": 521, @@ -9395,7 +9370,6 @@ "src/renderer/src/components/worktree-jump-palette-interleaved-sections.test.tsx": 2023, "src/renderer/src/components/worktree-jump-palette-quick-action-availability.test.tsx": 1441, "src/renderer/src/components/worktree-jump-palette-sleeping-filter.test.ts": 36, - "src/renderer/src/components/worktree-jump-palette-status-inputs.test.ts": 46, "src/renderer/src/hooks/agent-hook-completion-background-turn-notifications.test.ts": 482, "src/renderer/src/hooks/agent-hook-completion-fresh-working.test.ts": 221, "src/renderer/src/hooks/agent-hook-completion-grok-notifications.test.ts": 791, @@ -10559,10 +10533,8 @@ "src/renderer/src/ssh/ssh-connect-ui-timeout.test.ts": 31, "src/renderer/src/ssh/ssh-connect-verb.test.ts": 44, "src/renderer/src/ssh/ssh-host-node-download-copy.test.ts": 1426, - "src/renderer/src/startup/active-workspace-ssh-targets.test.ts": 58, "src/renderer/src/startup/startup-degraded-recovery.test.ts": 427, "src/renderer/src/startup/startup-ssh-connection-restore.test.ts": 74, - "src/renderer/src/store/active-terminal-chrome-selector.test.ts": 43, "src/renderer/src/store/adopt-from-endpoint.test.ts": 57, "src/renderer/src/store/always-mounted-selector-scan-cost.test.ts": 1574, "src/renderer/src/store/copy-on-write-record.test.ts": 56, @@ -10971,7 +10943,6 @@ "src/renderer/src/store/slices/worktrees-remote-runtime-removal.test.ts": 803, "src/renderer/src/store/slices/worktrees-removal-state-cleanup.test.ts": 734, "src/renderer/src/store/slices/worktrees-runtime-connection-generation-fence.test.ts": 1284, - "src/renderer/src/store/slices/worktrees-runtime-host-metadata-retirement.test.ts": 961, "src/renderer/src/store/slices/worktrees-terminal-pr-exact-observation.test.ts": 1021, "src/renderer/src/store/slices/worktrees-terminal-pr-url-linking.test.ts": 888, "src/renderer/src/store/slices/worktrees-terminal-reference-admission.test.ts": 1637, diff --git a/src/main/browser/extensions/browser-extension-permission-grants.test.ts b/src/main/browser/extensions/browser-extension-permission-grants.test.ts index 4344ba5ac85..64ed4123c1d 100644 --- a/src/main/browser/extensions/browser-extension-permission-grants.test.ts +++ b/src/main/browser/extensions/browser-extension-permission-grants.test.ts @@ -1,108 +1,5 @@ import { describe, expect, it } from 'vitest' -import { - containsBrowserExtensionPermissions, - effectiveBrowserExtensionPermissions, - originPatternCovers, - planBrowserExtensionPermissionRemoval, - planBrowserExtensionPermissionRequest -} from './browser-extension-permission-grants' - -// Bitwarden's shape: required APIs and hosts, optional nativeMessaging and privacy. -const manifest = { - permissions: ['tabs', 'storage'], - optional_permissions: ['nativeMessaging', 'privacy'], - host_permissions: ['https://*/*'], - optional_host_permissions: ['http://*.example.com/*'] -} -const none = { grantedPermissions: [], grantedOrigins: [] } - -describe('permission request planning', () => { - it('prompts only for what is optional and not yet granted', () => { - expect( - planBrowserExtensionPermissionRequest(manifest, none, { - permissions: ['privacy', 'tabs'] - }) - ).toEqual({ kind: 'prompt', add: { permissions: ['privacy'], origins: [] } }) - expect( - planBrowserExtensionPermissionRequest( - manifest, - { grantedPermissions: ['privacy'], grantedOrigins: [] }, - { permissions: ['privacy'] } - ) - ).toEqual({ kind: 'already-granted' }) - }) - - it('refuses anything the manifest does not list', () => { - const refused = { - kind: 'invalid', - error: 'Only permissions specified in the manifest may be requested.' - } - expect( - planBrowserExtensionPermissionRequest(manifest, none, { permissions: ['history'] }) - ).toEqual(refused) - expect( - planBrowserExtensionPermissionRequest(manifest, none, { origins: ['http://other.test/*'] }) - ).toEqual(refused) - }) - - it('treats optional host patterns as covering narrower requests', () => { - expect( - planBrowserExtensionPermissionRequest(manifest, none, { - origins: ['http://shop.example.com/cart/*', 'https://bank.test/*'] - }) - ).toEqual({ - kind: 'prompt', - add: { permissions: [], origins: ['http://shop.example.com/cart/*'] } - }) - }) - - it('never removes required permissions and removes only what was granted', () => { - expect( - planBrowserExtensionPermissionRemoval(manifest, none, { permissions: ['tabs'] }) - ).toEqual({ kind: 'invalid', error: 'You cannot remove required permissions.' }) - expect( - planBrowserExtensionPermissionRemoval( - manifest, - { grantedPermissions: ['privacy'], grantedOrigins: [] }, - { permissions: ['privacy', 'nativeMessaging'] } - ) - ).toEqual({ kind: 'remove', removed: { permissions: ['privacy'], origins: [] } }) - }) -}) - -describe('contains and getAll', () => { - it('answers from required plus granted permissions', () => { - const granted = { grantedPermissions: ['privacy'], grantedOrigins: ['http://a.example.com/*'] } - expect(effectiveBrowserExtensionPermissions(manifest, granted)).toEqual({ - permissions: ['tabs', 'storage', 'privacy'], - origins: ['https://*/*', 'http://a.example.com/*'] - }) - expect( - containsBrowserExtensionPermissions(manifest, granted, { permissions: ['privacy'] }) - ).toBe(true) - expect(containsBrowserExtensionPermissions(manifest, none, { permissions: ['privacy'] })).toBe( - false - ) - expect( - containsBrowserExtensionPermissions(manifest, none, { origins: ['https://bank.test/login'] }) - ).toBe(true) - }) - - it('reads MV2 host entries out of permissions', () => { - const mv2 = { permissions: ['tabs', ''], optional_permissions: ['https://x.test/*'] } - expect(effectiveBrowserExtensionPermissions(mv2, none)).toEqual({ - permissions: ['tabs'], - origins: [''] - }) - expect( - planBrowserExtensionPermissionRequest( - { permissions: ['tabs'], optional_permissions: ['https://x.test/*'] }, - none, - { origins: ['https://x.test/*'] } - ) - ).toEqual({ kind: 'prompt', add: { permissions: [], origins: ['https://x.test/*'] } }) - }) -}) +import { originPatternCovers } from './browser-extension-permission-grants' describe('originPatternCovers', () => { it.each([ diff --git a/src/main/browser/extensions/browser-extension-webstore-redirects.test.ts b/src/main/browser/extensions/browser-extension-webstore-redirects.test.ts index ed592ddb497..dbfa49e622e 100644 --- a/src/main/browser/extensions/browser-extension-webstore-redirects.test.ts +++ b/src/main/browser/extensions/browser-extension-webstore-redirects.test.ts @@ -79,22 +79,6 @@ describe('Web Store redirect policy', () => { } ) - it('still verifies an allowed redirected package', async () => { - const fetcher = vi - .fn() - .mockResolvedValueOnce( - Response.redirect('https://downloads.googleusercontent.com/extension.crx') - ) - .mockResolvedValueOnce(new Response('bad')) - - await expect(download('crx', fetcher)).rejects.toThrow('Not a CRX file') - expect(fetcher.mock.calls.map(([url]) => url)).toEqual([ - CRX_URL, - 'https://downloads.googleusercontent.com/extension.crx' - ]) - expect(fetcher.mock.calls.every(([, init]) => init?.signal?.aborted)).toBe(true) - }) - it.each(scenarios)( 'bounds a $kind redirect cycle and cancels every unused body', async (scenario) => { diff --git a/src/main/claude-accounts/runtime-auth-service.test.ts b/src/main/claude-accounts/runtime-auth-service.test.ts index 2b8dc727f0b..80fe277025a 100644 --- a/src/main/claude-accounts/runtime-auth-service.test.ts +++ b/src/main/claude-accounts/runtime-auth-service.test.ts @@ -60,36 +60,6 @@ afterEach(() => { }) describe('ClaudeRuntimeAuthService', () => { - it('skips a quota preparation for a stopped distro instead of booting it', async () => { - const preparation = vi.spyOn(fakes.wsl, 'preparation') - const auth = service('win32', {}, async () => false) - await expect( - auth.prepareForRateLimitFetch({ runtime: 'wsl', wslDistro: 'Ubuntu' }) - ).resolves.toMatchObject({ usageError: 'WSL distro Ubuntu is not running.' }) - expect(preparation).not.toHaveBeenCalled() - - const running = service('win32') - await expect( - running.prepareForRateLimitFetch({ runtime: 'wsl', wslDistro: 'Ubuntu' }) - ).resolves.toEqual({ runtime: 'wsl', wslDistro: 'Ubuntu' }) - }) - - it('keeps a stale WSL account setting on the host off Windows', async () => { - const auth = service('darwin', { localAccountRuntime: 'wsl', localAccountWslDistro: 'Ubuntu' }) - expect(auth.getRuntimeConfigDir()).toBe('/host/.claude') - await expect(auth.prepareForClaudeLaunch()).resolves.toEqual({ runtime: 'host' }) - }) - - it('routes a WSL target that names no distro to the default distro', async () => { - const auth = service('win32') - await expect(auth.prepareForClaudeLaunch({ runtime: 'wsl', wslDistro: null })).resolves.toEqual( - { runtime: 'wsl', wslDistro: 'Ubuntu' } - ) - expect(auth.getRuntimeConfigDir({ runtime: 'wsl', wslDistro: null })).toBe( - '/wsl/Ubuntu/home/.claude' - ) - }) - it('never fails a select over a failed guest publish, and publishes one pass at a time', async () => { const auth = service('win32') vi.spyOn(fakes.wsl, 'publish').mockRejectedValue(new Error('distro is gone')) diff --git a/src/main/claude-usage/claude-usage-report-aggregation.test.ts b/src/main/claude-usage/claude-usage-report-aggregation.test.ts index 57fa7d7b30b..cbc91658556 100644 --- a/src/main/claude-usage/claude-usage-report-aggregation.test.ts +++ b/src/main/claude-usage/claude-usage-report-aggregation.test.ts @@ -36,38 +36,6 @@ function stateWithDaily(dailyAggregates: ClaudeUsageDailyAggregate[]): ClaudeUsa } } -describe('buildSummary Claude Sonnet 5 pricing over time', () => { - // The cancelled price increase must not reprice historical usage. - it('prices an introductory-window day and a post-window day at the same $2/$10 rate', () => { - const inWindow = buildSummary( - stateWithDaily([dailyRow('2026-08-15', 'claude-sonnet-5')]), - 'all', - 'all' - ) - const afterWindow = buildSummary( - stateWithDaily([dailyRow('2026-09-15', 'claude-sonnet-5')]), - 'all', - 'all' - ) - - expect(inWindow.estimatedCostUsd).toBeCloseTo(12) - expect(afterWindow.estimatedCostUsd).toBeCloseTo(12) - }) - - it('sums both days in the All range without re-pricing history upward', () => { - const summary = buildSummary( - stateWithDaily([ - dailyRow('2026-08-15', 'claude-sonnet-5'), - dailyRow('2026-09-15', 'claude-sonnet-5') - ]), - 'all', - 'all' - ) - - expect(summary.estimatedCostUsd).toBeCloseTo(24) - }) -}) - describe('buildSummary unpriced models', () => { it('keeps the priced total and flags the rows it had to leave out', () => { const summary = buildSummary( @@ -82,14 +50,6 @@ describe('buildSummary unpriced models', () => { expect(summary.estimatedCostUsd).toBeCloseTo(12) expect(summary.hasUnpricedModels).toBe(true) }) - - it('does not flag rows that carry no model name', () => { - const row = { ...dailyRow('2026-10-15', 'claude-sonnet-5-5'), model: null } - const summary = buildSummary(stateWithDaily([row]), 'all', 'all') - - expect(summary.estimatedCostUsd).toBeNull() - expect(summary.hasUnpricedModels).toBe(false) - }) }) describe('buildSummary Haiku 5.5 long-context requests', () => { diff --git a/src/main/claude/claude-subagent-task-frames.test.ts b/src/main/claude/claude-subagent-task-frames.test.ts deleted file mode 100644 index d69628f927f..00000000000 --- a/src/main/claude/claude-subagent-task-frames.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { readClaudeSubagentTaskFrame } from './claude-subagent-task-frames' - -function system(subtype: string, fields: Record): Record { - return { type: 'system', subtype, session_id: 'claude-session', ...fields } -} - -describe('readClaudeSubagentTaskFrame', () => { - it('ignores frames that are not task frames', () => { - expect(readClaudeSubagentTaskFrame({ type: 'assistant', subtype: 'task_started' })).toBeNull() - expect(readClaudeSubagentTaskFrame(system('init', { task_id: 'task-1' }))).toBeNull() - expect(readClaudeSubagentTaskFrame(system('task_started', {}))).toBeNull() - expect(readClaudeSubagentTaskFrame(system('task_started', { task_id: '' }))).toBeNull() - }) - - describe('task_type triage', () => { - it('announces a local_agent task', () => { - const frame = readClaudeSubagentTaskFrame( - system('task_started', { - task_id: 'task-1', - tool_use_id: 'toolu_1', - task_type: 'local_agent', - subagent_type: 'code-reviewer', - description: 'Review the diff' - }) - ) - expect(frame).toMatchObject({ - taskId: 'task-1', - toolUseId: 'toolu_1', - announcesSubagent: true, - excluded: false - }) - }) - - it('announces the legacy local_subagent task type', () => { - expect( - readClaudeSubagentTaskFrame( - system('task_started', { - task_id: 'task-legacy-subagent', - tool_use_id: 'toolu_legacy', - task_type: 'local_subagent', - subagent_type: 'code-reviewer', - description: 'Review the diff' - }) - ) - ).toMatchObject({ announcesSubagent: true, excluded: false }) - }) - - it('excludes a backgrounded shell command even though it carries a tool_use_id', () => { - const frame = readClaudeSubagentTaskFrame( - system('task_started', { - task_id: 'task-bash', - tool_use_id: 'toolu_bash', - task_type: 'local_bash', - description: 'sleep 20', - is_backgrounded: true - }) - ) - expect(frame).toMatchObject({ - taskId: 'task-bash', - toolUseId: 'toolu_bash', - announcesSubagent: false, - excluded: true - }) - }) - - it('excludes workflows and monitors', () => { - for (const taskType of ['local_workflow', 'monitor']) { - expect( - readClaudeSubagentTaskFrame( - system('task_started', { task_id: `task-${taskType}`, task_type: taskType }) - ) - ).toMatchObject({ announcesSubagent: false, excluded: true }) - } - }) - - it('falls back to subagent_type only when the release sends no task_type', () => { - expect( - readClaudeSubagentTaskFrame( - system('task_started', { task_id: 'task-old', subagent_type: 'explorer' }) - ) - ).toMatchObject({ announcesSubagent: true }) - expect( - readClaudeSubagentTaskFrame(system('task_started', { task_id: 'task-bare' })) - ).toMatchObject({ announcesSubagent: false, excluded: true }) - expect( - readClaudeSubagentTaskFrame( - system('task_started', { - task_id: 'task-new', - task_type: 'local_something_new', - subagent_type: 'explorer' - }) - ) - ).toMatchObject({ announcesSubagent: false, excluded: true }) - }) - - it('excludes ambient housekeeping tasks', () => { - for (const suppression of [{ skip_transcript: true }, { ambient: true }]) { - expect( - readClaudeSubagentTaskFrame( - system('task_started', { - task_id: 'task-ambient', - task_type: 'local_agent', - subagent_type: 'watcher', - ...suppression - }) - ) - ).toMatchObject({ announcesSubagent: false, excluded: true }) - } - }) - }) - - it('reads the spawn call id from an update patch', () => { - expect( - readClaudeSubagentTaskFrame( - system('task_updated', { task_id: 'task-1', patch: { tool_use_id: 'toolu_1' } }) - ) - ).toMatchObject({ taskId: 'task-1', toolUseId: 'toolu_1', excluded: false }) - }) -}) diff --git a/src/main/codex/codex-structured-sign-in.test.ts b/src/main/codex/codex-structured-sign-in.test.ts index f831e475b1f..55911e1e3d4 100644 --- a/src/main/codex/codex-structured-sign-in.test.ts +++ b/src/main/codex/codex-structured-sign-in.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from 'vitest' import type { AgentJournalItemBody } from '../../shared/agent-session/journal/agent-session-journal-types' import { createCodexJournalTranslator } from './codex-structured-journal-translation' -import { codexAuthenticationFailure } from './codex-authentication-failure' import { codexTurnEndRejection, readCodexTurnEnd } from './codex-structured-turn-end-settlement' const errors = [ @@ -59,19 +58,6 @@ describe('Codex protocol authentication failures', () => { translator.dispose() } ) - it.each([ - 'other', - 'unauthorized request', - { other: { httpStatusCode: 401 } }, - { httpConnectionFailed: { httpStatusCode: 403 } }, - { httpConnectionFailed: { httpStatusCode: 500 } } - ])('does not guess sign-out from unrelated failures %j', (codexErrorInfo) => { - expect( - codexAuthenticationFailure({ - error: { codexErrorInfo, message: '401 unauthorized authentication required' } - }) - ).toBeNull() - }) it('keeps a retrying 401 as a retry, without asking the person to sign in yet', () => { const bodies: AgentJournalItemBody[] = [] const translator = createCodexJournalTranslator({ diff --git a/src/main/crash-reporting/renderer-heap-allocation-sites.test.ts b/src/main/crash-reporting/renderer-heap-allocation-sites.test.ts index e9dcb66cdf5..a959bed2386 100644 --- a/src/main/crash-reporting/renderer-heap-allocation-sites.test.ts +++ b/src/main/crash-reporting/renderer-heap-allocation-sites.test.ts @@ -30,34 +30,6 @@ function sitesOf(...children: ProfileNode[]): string[] { } describe('summarizeSamplingProfile', () => { - it('names frames from the window bundle by function, file name and line', () => { - const summary = summarizeSamplingProfile( - { - head: { - callFrame: frame('(root)', '', '0'), - selfSize: 0, - children: [ - { - callFrame: frame('appendChunk', `${BUNDLE}terminal-Ab12.js`), - selfSize: 0, - children: [leaf(frame('sliceOutput', `${BUNDLE}terminal-Ab12.js`, '7', 20), 3072)] - }, - leaf(frame('join', '', '0'), 1024) - ] - } - }, - BUNDLE - ) - - expect(summary).toEqual({ - sampledLiveMB: 4, - siteCount: 2, - site01: '3072KB sliceOutput@terminal-Ab12.js:21:5 < appendChunk@terminal-Ab12.js:10:5', - site02: '1024KB join' - }) - expect(JSON.stringify(summary)).not.toContain('someone') - }) - it('reduces every frame outside the bundle to a fixed label, name included', () => { expect( sitesOf( @@ -107,14 +79,3 @@ describe('site length', () => { expect(site.endsWith('.js:10:5')).toBe(true) }) }) - -describe('trustedScriptPrefixFor', () => { - it('is the assets folder next to the page', () => { - expect(trustedScriptPrefixFor(`${PAGE}?x=1#hash`)).toBe( - 'file:///Users/someone/Orca.app/Contents/Resources/app.asar/out/renderer/assets/' - ) - expect(trustedScriptPrefixFor('http://localhost:5173/')).toBe('http://localhost:5173/assets/') - expect(trustedScriptPrefixFor('about:blank')).toBeNull() - expect(trustedScriptPrefixFor('')).toBeNull() - }) -}) diff --git a/src/main/daemon/macos-daemon-bundle-retirement.test.ts b/src/main/daemon/macos-daemon-bundle-retirement.test.ts index 83e761e87e7..a1abb079ff0 100644 --- a/src/main/daemon/macos-daemon-bundle-retirement.test.ts +++ b/src/main/daemon/macos-daemon-bundle-retirement.test.ts @@ -110,9 +110,3 @@ it("retires every unused copy and staging dir except the current build's and thi expect(await exists(oldUnused)).toBe(false) expect(await exists(crashedStaging)).toBe(false) }) - -it('runs one collection at a time and tolerates a missing root', async () => { - const first = retireOtherMacDaemonCopies(join(root, 'absent'), join(root, 'absent', 'x')) - expect(retireOtherMacDaemonCopies(root, join(root, 'x'))).toBe(first) - await expect(first).resolves.toBeUndefined() -}) diff --git a/src/main/daemon/macos-retired-daemon-jobs.test.ts b/src/main/daemon/macos-retired-daemon-jobs.test.ts index faed86d9e09..683210ed790 100644 --- a/src/main/daemon/macos-retired-daemon-jobs.test.ts +++ b/src/main/daemon/macos-retired-daemon-jobs.test.ts @@ -30,36 +30,6 @@ const job = (argv = args): string => beforeEach(() => run.mockReset()) -it('discovers only exact same-profile supported labels in one bounded command', async () => { - run.mockResolvedValue({ - ...ok, - stdout: [ - `12345\t0\t${macDaemonJobLabel(profile, version)}`, - `12346\t0\t${macDaemonJobLabel(profile)}`, - `12347\t0\t${macDaemonJobLabel(profile, PROTOCOL_VERSION + 1)}`, - `12348\t0\t${macDaemonJobLabel('/profiles/other', version)}`, - `12349\t0\t${macDaemonJobLabel(profile, version)}.extra` - ].join('\n') - }) - expect(await listMacDaemonJobVersions(profile)).toEqual([version, PROTOCOL_VERSION]) - expect(run).toHaveBeenCalledOnce() - expect(run).toHaveBeenCalledWith( - expect.objectContaining({ args: ['list'], timeoutMs: 3_000, maxOutputBytes: 128 * 1024 }) - ) -}) - -it('requires exact endpoint arguments from the top-level launchd job', async () => { - run.mockResolvedValue({ ...ok, stdout: job() }) - expect(await readMacDaemonRecoveryJob(profile, runtime, version)).toEqual({ - service: `gui/${process.getuid?.()}/${macDaemonJobLabel(profile, version)}`, - protocolVersion: version, - pid: 12345, - launchNonce: 'old-nonce', - socketPath: getDaemonSocketPath(runtime, version), - pidPath: getDaemonPidPath(runtime, version) - }) -}) - it.each( [ args.filter((a) => a !== '--login-session-watch'), @@ -74,11 +44,6 @@ it.each( expect(await readMacDaemonRecoveryJob(profile, runtime, version)).toBeNull() }) -it('ignores a job that exited between list and print', async () => { - run.mockResolvedValue({ ...ok, code: 113, stderr: 'Could not find service "old"' }) - expect(await readMacDaemonRecoveryJob(profile, runtime, version)).toBeNull() -}) - it.each([ { timedOut: true }, { outputTruncated: true }, @@ -110,10 +75,3 @@ it('bounds job discovery and rejects an oversized profile inventory', async () = await expect(listMacDaemonJobVersions(profile)).rejects.toThrow('Too many') expect(run).toHaveBeenCalledOnce() }) - -it('never inspects newer or invalid protocol jobs', async () => { - for (const unsupported of [0, -1, 1.5, PROTOCOL_VERSION + 1]) { - expect(await readMacDaemonRecoveryJob(profile, runtime, unsupported)).toBeNull() - } - expect(run).not.toHaveBeenCalled() -}) diff --git a/src/main/daemon/session-ingest-throughput.bench.test.ts b/src/main/daemon/session-ingest-throughput.bench.test.ts deleted file mode 100644 index b57deb0d9dc..00000000000 --- a/src/main/daemon/session-ingest-throughput.bench.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { performance } from 'node:perf_hooks' -import { Session } from './session' -import type { SubprocessHandle } from './session-subprocess-handle' - -// Enable ORCA_TERMINAL_PERF_BENCH=1 to isolate daemon ingest cost with terminal-pipeline-bench workloads. -const benchEnabled = process.env.ORCA_TERMINAL_PERF_BENCH === '1' - -const COLS = 114 -const ROWS = 85 -const TARGET_BYTES = 10 * 1024 * 1024 -const CHUNK = 64 * 1024 - -function asciiLog(targetBytes: number): string { - const parts: string[] = [] - let bytes = 0 - let line = 0 - while (bytes < targetBytes) { - line++ - const s = `\x1b[32m[build ${String(line).padStart(6, '0')}]\x1b[0m compile transform resolve bundle emit chunk module (${line % 5000}ms)\r\n` - parts.push(s) - bytes += s.length - } - return parts.join('') -} - -function agentTui(targetBytes: number): string { - const statusRows = 10 - const parts: string[] = [] - let bytes = 0 - let frame = 0 - let painted = false - const push = (s: string): void => { - parts.push(s) - bytes += Buffer.byteLength(s, 'utf8') - } - while (bytes < targetBytes) { - frame++ - push('\x1b[?2026h') - if (painted) { - push(`\x1b[${statusRows}A\x1b[0J`) - } - push(`\x1b[2m●\x1b[0m transcript line for frame ${frame} with some words\r\n`) - for (let r = 0; r < statusRows; r++) { - push( - `\x1b[38;5;${33 + (r % 6)}m⠼ task ${frame % 100}·${r}\x1b[0m ${'▇'.repeat((frame + r) % 40)}\r\n` - ) - } - painted = true - push('\x1b[?2026l') - } - return parts.join('') -} - -function makeSubprocess(): SubprocessHandle & { emit: (data: string) => void } { - let onData: ((data: string) => void) | null = null - return { - pid: 4242, - getForegroundProcess: () => 'bench', - write: () => {}, - resize: () => {}, - kill: () => {}, - terminateOwnedTree: () => 'unavailable' as const, - forceKill: () => {}, - signal: () => {}, - onData: (cb) => { - onData = cb - }, - onExit: () => {}, - dispose: () => {}, - emit: (data: string) => onData?.(data) - } -} - -function ingest(fixture: string, drainPendingEveryChunks: number | null): number { - const subprocess = makeSubprocess() - const session = new Session({ - sessionId: 'bench', - cols: COLS, - rows: ROWS, - subprocess, - shellReadySupported: false - }) - session.attachClient({ onData: () => {}, onExit: () => {} }) - subprocess.emit(fixture.slice(0, 256 * 1024)) - session.takePendingOutput(false) - const start = performance.now() - let chunks = 0 - for (let i = 0; i < fixture.length; i += CHUNK) { - subprocess.emit(fixture.slice(i, i + CHUNK)) - chunks++ - // Drain before overflow stops record accounting, or the benchmark understates steady-state cost. - if (drainPendingEveryChunks && chunks % drainPendingEveryChunks === 0) { - session.takePendingOutput(false) - } - } - const ms = performance.now() - start - session.dispose() - return ms -} - -describe.skipIf(!benchEnabled)('daemon session ingest throughput', () => { - it('measures MB/s per workload shape', () => { - const rows: string[] = [] - for (const [name, fixture] of [ - ['ascii-log', asciiLog(TARGET_BYTES)], - ['agent-tui', agentTui(TARGET_BYTES)] - ] as const) { - const bytes = Buffer.byteLength(fixture, 'utf8') - const ms = ingest(fixture, 24) - const rate = bytes / 1024 / 1024 / (ms / 1000) - rows.push( - `${name}: ${rate.toFixed(1)} MB/s (${ms.toFixed(0)}ms for ${(bytes / 1024 / 1024).toFixed(1)}MB)` - ) - } - console.log(`\n[session-ingest] ${COLS}x${ROWS}\n ${rows.join('\n ')}`) - expect(rows.length).toBe(2) - }) -}) diff --git a/src/main/ephemeral-vm/resume-integrity.test.ts b/src/main/ephemeral-vm/resume-integrity.test.ts deleted file mode 100644 index c89e92dd40e..00000000000 --- a/src/main/ephemeral-vm/resume-integrity.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { - EphemeralVmRecipeConnection, - EphemeralVmRecipeResult -} from '../../shared/ephemeral-vm-recipes' -import { getProvisionedRootResumeIntegrityError } from './resume-integrity' - -type RecipeSshTarget = Extract['target'] - -describe('getProvisionedRootResumeIntegrityError', () => { - it('allows an SSH endpoint to rotate while preserving its ownership identity', () => { - const previous = provisionedSshResult() - const resumed = provisionedSshResult({ host: 'new-host', port: 2222, label: 'Resumed VM' }) - - expect(getProvisionedRootResumeIntegrityError(previous, resumed)).toBeNull() - }) - - it.each([ - ['config host', { configHost: 'other-alias' }], - ['username', { username: 'other-user' }], - ['identity file', { identityFile: '/keys/other' }], - ['identity agent', { identityAgent: '/agents/other' }], - ['identities-only setting', { identitiesOnly: true }], - ['proxy command', { proxyCommand: 'ssh proxy' }], - ['jump host', { jumpHost: 'bastion' }] - ])('rejects changed SSH %s ownership', (_label, target) => { - expect( - getProvisionedRootResumeIntegrityError(provisionedSshResult(), provisionedSshResult(target)) - ).toContain('SSH ownership changed') - }) - - it('rejects a resume that changes the connection type', () => { - const resumed: EphemeralVmRecipeResult = { - schemaVersion: 2, - checkoutMode: 'provisioned-root', - connection: { - type: 'orca-server', - pairingCode: 'pairing-code', - projectRoot: '/workspace/orca' - } - } - - expect(getProvisionedRootResumeIntegrityError(provisionedSshResult(), resumed)).toContain( - 'connection type changed' - ) - }) -}) - -function provisionedSshResult(target: Partial = {}): EphemeralVmRecipeResult { - return { - schemaVersion: 2, - checkoutMode: 'provisioned-root', - connection: { - type: 'ssh', - projectRoot: '/workspace/orca', - target: { - label: 'VM', - host: 'host', - port: 22, - username: 'orca', - identityFile: '/keys/orca', - identityAgent: '/agents/orca', - ...target - } - } - } -} diff --git a/src/main/git/command-runner/gh-exec-file-deadline.test.ts b/src/main/git/command-runner/gh-exec-file-deadline.test.ts index b42747a99ae..a9394f97a1d 100644 --- a/src/main/git/command-runner/gh-exec-file-deadline.test.ts +++ b/src/main/git/command-runner/gh-exec-file-deadline.test.ts @@ -64,45 +64,6 @@ describe('gh exec deadline', () => { } ) - it('spawns with hidden console and captured stdio, never an inherited or shell stdio', async () => { - const child = mockChild() - spawnMock.mockImplementation(() => { - queueMicrotask(() => settleChild(child, 'HTTP/2.0 204 No Content\r\n')) - return child - }) - - const result = await ghExecFileAsync(['api', '--include', 'user/starred/stablyai/orca'], { - timeout: 15_000 - }) - - expect(result.stdout).toContain('204 No Content') - const [command, args, options] = spawnMock.mock.calls[0] - expect(command).toBe('gh') - expect(args).toEqual(['api', '--include', 'user/starred/stablyai/orca']) - expect(options.windowsHide).toBe(true) - expect(options.stdio).toEqual(['pipe', 'pipe', 'pipe']) - expect(options.shell).toBe(false) - }) - - it('writes configured stdin through the child-process capture', async () => { - const child = mockChild() - const input = JSON.stringify({ title: 'Image issue', body: 'x'.repeat(133_596) }) - spawnMock.mockImplementation(() => { - queueMicrotask(() => settleChild(child, '{"number":1}')) - return child - }) - - await expect( - ghExecFileAsync(['api', '-X', 'POST', 'repos/stablyai/orca/issues', '--input', '-'], { - timeout: 15_000, - stdin: input - }) - ).resolves.toEqual({ stdout: '{"number":1}', stderr: '' }) - - expect(spawnMock).toHaveBeenCalledOnce() - expect(child.stdin?.end).toHaveBeenCalledWith(input) - }) - it('fails rather than returning a clipped answer when gh overruns maxBuffer', async () => { const child = mockChild() spawnMock.mockImplementation(() => { diff --git a/src/main/github/pr-refresh-coordinator-wsl-stopped-distro.test.ts b/src/main/github/pr-refresh-coordinator-wsl-stopped-distro.test.ts index a6029241c9f..8de95463914 100644 --- a/src/main/github/pr-refresh-coordinator-wsl-stopped-distro.test.ts +++ b/src/main/github/pr-refresh-coordinator-wsl-stopped-distro.test.ts @@ -48,25 +48,6 @@ describe('PR refresh for a stopped WSL distro (E9)', () => { vi.useRealTimers() }) - it('skips a queued background refresh without running gh, and refreshes once it runs', async () => { - const { reportVisiblePRRefreshCandidates } = await import('./pr-refresh-coordinator') - const candidate = makeCandidate({ - repoPath: WSL_REPO, - cacheKey: `${WSL_REPO}::feature/test`, - isSelected: true - }) - - reportVisiblePRRefreshCandidates([candidate], 1, 1) - await vi.advanceTimersByTimeAsync(0) - expect(getPRForBranchOutcomeMock).not.toHaveBeenCalled() - expect(gate.blockedWslDistroForPath).toHaveBeenCalledWith(WSL_REPO, undefined) - - gate.stopped = false - reportVisiblePRRefreshCandidates([candidate], 2, 1) - await vi.advanceTimersByTimeAsync(0) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - }) - it("drains outside a user act that scheduled it, so a stopped distro's queue is not admitted", async () => { const { reportVisiblePRRefreshCandidates } = await import('./pr-refresh-coordinator') const { isWslUserActFor, runAsWslUserAct } = await import('../wsl/wsl-user-act-scope') @@ -90,38 +71,4 @@ describe('PR refresh for a stopped WSL distro (E9)', () => { expect(drainedInsideAct).toEqual([false]) expect(getPRForBranchOutcomeMock).not.toHaveBeenCalled() }) - - it.each(['visible', 'active', 'post-push'] as const)( - 'skips an automatic %s refreshPRNow without running gh, keeping the cached PR', - async (reason) => { - const { refreshPRNow } = await import('./pr-refresh-coordinator') - const outcome = await refreshPRNow( - makeCandidate({ repoPath: WSL_REPO, cacheKey: `${WSL_REPO}::c` }), - reason - ) - expect(getPRForBranchOutcomeMock).not.toHaveBeenCalled() - expect(outcome).toMatchObject({ kind: 'upstream-error' }) - } - ) - - it('treats an unverifiable distro as blocked', async () => { - gate.blockedWslDistroForPath.mockResolvedValue({ distro: 'Ubuntu', state: 'unverifiable' }) - const { refreshPRNow } = await import('./pr-refresh-coordinator') - await refreshPRNow(makeCandidate({ repoPath: WSL_REPO, cacheKey: `${WSL_REPO}::d` }), 'visible') - expect(getPRForBranchOutcomeMock).not.toHaveBeenCalled() - }) - - it('still runs a manual refresh, which is the user asking', async () => { - const { refreshPRNow } = await import('./pr-refresh-coordinator') - await refreshPRNow(makeCandidate({ repoPath: WSL_REPO, cacheKey: `${WSL_REPO}::b` })) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - }) - - it('refreshes a host repo exactly as before, without asking the gate', async () => { - const { reportVisiblePRRefreshCandidates } = await import('./pr-refresh-coordinator') - reportVisiblePRRefreshCandidates([makeCandidate({ isSelected: true })], 1, 1) - await vi.advanceTimersByTimeAsync(0) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - expect(gate.blockedWslDistroForPath).not.toHaveBeenCalled() - }) }) diff --git a/src/main/gitlab/mappers-workitem.test.ts b/src/main/gitlab/mappers-workitem.test.ts deleted file mode 100644 index c70aa4d74ae..00000000000 --- a/src/main/gitlab/mappers-workitem.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { mapIssueToWorkItem, mapMRToWorkItem } from './mappers' - -describe('mapMRToWorkItem', () => { - it('produces a unified GitLabWorkItem with branch + author', () => { - expect( - mapMRToWorkItem( - { - id: 100, - iid: 5, - title: 'Add support', - state: 'opened', - web_url: 'https://gitlab.com/g/p/-/merge_requests/5', - updated_at: '2026-05-05T10:00:00Z', - source_branch: 'feat-x', - target_branch: 'main', - author: { username: 'alice' }, - source_project_id: 7, - target_project_id: 7, - labels: [{ name: 'bug' }, 'p1'] - }, - 'g/p' - ) - ).toEqual({ - id: 'gitlab-mr-100', - type: 'mr', - number: 5, - title: 'Add support', - state: 'opened', - url: 'https://gitlab.com/g/p/-/merge_requests/5', - labels: ['bug', 'p1'], - updatedAt: '2026-05-05T10:00:00Z', - author: 'alice', - branchName: 'feat-x', - baseRefName: 'main', - isCrossRepository: false, - repoId: 'g/p' - }) - }) - - it('flags cross-repository when source_project_id !== target_project_id', () => { - const item = mapMRToWorkItem( - { - iid: 1, - title: 't', - state: 'opened', - source_project_id: 5, - target_project_id: 7 - }, - 'g/p' - ) - expect(item.isCrossRepository).toBe(true) - }) - - it('maps GitLab mergeability when the MR payload includes merge status', () => { - expect( - mapMRToWorkItem( - { iid: 1, title: 't', state: 'opened', detailed_merge_status: 'mergeable' }, - 'g/p' - ).mergeable - ).toBe('MERGEABLE') - expect( - mapMRToWorkItem({ iid: 2, title: 't', state: 'opened', has_conflicts: true }, 'g/p').mergeable - ).toBe('CONFLICTING') - }) - - it('does not flag cross-repository when project ids are absent', () => { - const item = mapMRToWorkItem({ iid: 1, title: 't', state: 'opened' }, 'g/p') - expect(item.isCrossRepository).toBe(false) - }) - - it('infers draft from a Draft: title prefix', () => { - const item = mapMRToWorkItem({ iid: 1, title: 'Draft: WIP refactor', state: 'opened' }, 'g/p') - expect(item.state).toBe('draft') - }) - - it('falls back to a deterministic id when GitLab omits global id', () => { - // Older detail payloads can omit id; the repository/IID fallback must still distinguish records. - const item = mapMRToWorkItem({ iid: 5, title: 't', state: 'opened' }, 'g/p') - expect(item.id).toBe('gitlab-mr-g/p-5') - }) -}) - -describe('mapIssueToWorkItem', () => { - it('coerces opened/closed and produces a unified GitLabWorkItem', () => { - expect( - mapIssueToWorkItem( - { - id: 200, - iid: 9, - title: 'bug', - state: 'opened', - web_url: 'https://gitlab.com/g/p/-/issues/9', - updated_at: '2026-05-05T10:00:00Z', - author: { username: 'alice' }, - labels: ['bug'] - }, - 'g/p' - ) - ).toEqual({ - id: 'gitlab-issue-200', - type: 'issue', - number: 9, - title: 'bug', - state: 'opened', - url: 'https://gitlab.com/g/p/-/issues/9', - labels: ['bug'], - updatedAt: '2026-05-05T10:00:00Z', - author: 'alice', - repoId: 'g/p' - }) - }) - - it("collapses any non-'opened' state to 'closed'", () => { - expect(mapIssueToWorkItem({ iid: 1, title: 't', state: 'closed' }, 'g/p').state).toBe('closed') - expect(mapIssueToWorkItem({ iid: 1, title: 't', state: 'weird' }, 'g/p').state).toBe('closed') - }) -}) diff --git a/src/main/ipc/github-click-wsl-user-act.test.ts b/src/main/ipc/github-click-wsl-user-act.test.ts deleted file mode 100644 index e0a820694b5..00000000000 --- a/src/main/ipc/github-click-wsl-user-act.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -// A click on a cwd-less gh action may start the stopped default distro its gh lives in (E9); the -// same reads from mount effects on a restored page must not. -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as WslModule from '../wsl' - -const { handlers, getDefaultWslDistroMock, ranAsUserAct } = vi.hoisted(() => ({ - handlers: new Map unknown>(), - getDefaultWslDistroMock: vi.fn(), - // Each stub answers whether it ran inside a user act for the default distro. - ranAsUserAct: async (): Promise => - (await import('../wsl/wsl-user-act-scope')).isWslUserActFor('Ubuntu') -})) - -vi.mock('electron', () => ({ - ipcMain: { - handle: (channel: string, handler: (event: unknown, args?: unknown) => unknown) => - handlers.set(channel, handler) - } -})) - -vi.mock('../wsl', async (importOriginal) => ({ - ...(await importOriginal()), - getDefaultWslDistro: getDefaultWslDistroMock -})) - -vi.mock('../github/auth-diagnose', () => ({ diagnoseGhAuth: vi.fn(ranAsUserAct) })) - -vi.mock('../github/project-view', () => ({ - addIssueCommentBySlug: vi.fn(ranAsUserAct), - clearProjectItemFieldValue: vi.fn(ranAsUserAct), - deleteIssueCommentBySlug: vi.fn(ranAsUserAct), - getProjectViewTable: vi.fn(ranAsUserAct), - getWorkItemDetailsBySlug: vi.fn(ranAsUserAct), - listAccessibleProjects: vi.fn(ranAsUserAct), - listAssignableUsersBySlug: vi.fn(ranAsUserAct), - listIssueTypesBySlug: vi.fn(ranAsUserAct), - listLabelsBySlug: vi.fn(ranAsUserAct), - listProjectViews: vi.fn(ranAsUserAct), - resolveProjectRef: vi.fn(ranAsUserAct), - updateIssueBySlug: vi.fn(ranAsUserAct), - updateIssueCommentBySlug: vi.fn(ranAsUserAct), - updateIssueTypeBySlug: vi.fn(ranAsUserAct), - updateProjectItemFieldValue: vi.fn(ranAsUserAct), - updatePullRequestBySlug: vi.fn(ranAsUserAct) -})) - -import { registerGitHubProjectViewHandlers } from './github-project-view-handlers' -import { registerGitHubAccountHandlers } from './github-account-handlers' -import type { Store } from '../persistence' -import type { WakeWslDistroArg } from '../../shared/github/project-request-types' - -const originalPlatform = process.platform - -beforeEach(() => { - handlers.clear() - getDefaultWslDistroMock.mockReset().mockReturnValue('Ubuntu') - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - registerGitHubProjectViewHandlers() - registerGitHubAccountHandlers(unusedStore()) -}) - -afterEach(() => { - Object.defineProperty(process, 'platform', { configurable: true, value: originalPlatform }) -}) - -function unusedStore(): Store { - const store: Partial = {} - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the handlers invoked here never read the store. - return store as Store -} - -function invoke(channel: string, args: WakeWslDistroArg = {}): unknown { - const handler = handlers.get(channel) - if (!handler) { - throw new Error(`no handler for ${channel}`) - } - return handler({}, args) -} - -const CLICK_CHANNELS = [ - 'gh:listAccessibleProjects', - 'gh:resolveProjectRef', - 'gh:listProjectViews', - 'gh:projectWorkItemDetailsBySlug', - 'gh:updateProjectItemField', - 'gh:clearProjectItemField', - 'gh:updateIssueBySlug', - 'gh:updatePullRequestBySlug', - 'gh:addIssueCommentBySlug', - 'gh:updateIssueCommentBySlug', - 'gh:deleteIssueCommentBySlug', - 'gh:updateIssueTypeBySlug' -] - -describe('cwd-less gh calls run as a user act on the default distro only when a click says so', () => { - it.each(CLICK_CHANNELS)('%s with wakeWslDistro', async (channel) => { - await expect(invoke(channel, { wakeWslDistro: true })).resolves.toBe(true) - }) - - // E.g. useProjectViewTable lists a restored board's views from a mount effect, with no click. - it.each(CLICK_CHANNELS)('%s without it stays gated', async (channel) => { - await expect(invoke(channel)).resolves.toBe(false) - }) - - it.each([ - 'gh:getProjectViewTable', - 'gh:listLabelsBySlug', - 'gh:listAssignableUsersBySlug', - 'gh:listIssueTypesBySlug' - ])('%s never wakes, even when asked', async (channel) => { - await expect(invoke(channel, { wakeWslDistro: true })).resolves.toBe(false) - }) - - // GhAuthErrorHelp diagnoses from a mount effect. - it('the auth diagnosis never wakes', async () => { - await expect(invoke('gh:diagnoseAuth', { wakeWslDistro: true })).resolves.toBe(false) - }) -}) diff --git a/src/main/managed-server/install/remote-versioned-install-termination.test.ts b/src/main/managed-server/install/remote-versioned-install-termination.test.ts deleted file mode 100644 index fe230df931b..00000000000 --- a/src/main/managed-server/install/remote-versioned-install-termination.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type * as ExecCommandModule from '../../ssh/remote-exec/remote-exec-command' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('../../ssh/remote-exec/remote-exec-command', async (importOriginal) => ({ - ...(await importOriginal()), - execCommand: vi.fn() -})) - -import { REMOTE_INSTALL_MODELS } from './remote-install-model' -import { execCommand } from '../../ssh/remote-exec/remote-exec-command' -import { - abandonInstall, - finalizeInstall, - isRemoteInstallComplete -} from './remote-versioned-install' -import { getRemoteHostPlatform } from '../../ssh/ssh-remote-platform' -import { unusedSshConnection } from '../../ssh/unused-host-transport.test-fixture' - -const conn = unusedSshConnection() -const mockExec = vi.mocked(execCommand) - -beforeEach(() => { - mockExec.mockReset() -}) - -describe.each([ - { platform: 'linux-x64' as const, remoteDir: '/relay/version' }, - { platform: 'win32-x64' as const, remoteDir: 'C:/relay/version' } -])('install-complete probe termination on $platform', ({ platform, remoteDir }) => { - const host = getRemoteHostPlatform(platform) - - it.each([abandonInstall, finalizeInstall])( - 'preserves uncertainty from lock release by %s', - async (release) => { - const error = Object.assign(new Error('release still running'), { - sshChannelCloseConfirmed: false - }) - mockExec.mockResolvedValue('') - if (release === finalizeInstall) { - mockExec.mockResolvedValueOnce('') - } - mockExec.mockRejectedValueOnce(error) - await expect(release(conn, remoteDir, host)).rejects.toBe(error) - } - ) - - it.each([abandonInstall, finalizeInstall])( - 'keeps confirmed release failures nonfatal in %s', - async (release) => { - if (release === finalizeInstall) { - mockExec.mockResolvedValueOnce('') - } - mockExec.mockRejectedValueOnce( - Object.assign(new Error('release failed'), { - sshChannelCloseConfirmed: true - }) - ) - await expect(release(conn, remoteDir, host)).resolves.toBeFalsy() - } - ) - - describe.each(REMOTE_INSTALL_MODELS)('$id installs', (model) => { - it.each([ - { aborted: false, rethrowSessionLimitErrors: false }, - { aborted: false, rethrowSessionLimitErrors: true }, - { aborted: true, rethrowSessionLimitErrors: false }, - { aborted: true, rethrowSessionLimitErrors: true } - ])( - 'preserves uncertainty with aborted=$aborted and strict=$rethrowSessionLimitErrors', - async ({ aborted, rethrowSessionLimitErrors }) => { - const controller = new AbortController() - const error = Object.assign(new Error('SSH teardown not confirmed'), { - sshChannelCloseConfirmed: false - }) - mockExec.mockImplementationOnce(async () => { - if (aborted) { - controller.abort(new Error('deploy aborted')) - } - throw error - }) - - await expect( - isRemoteInstallComplete(conn, model, remoteDir, host, { - signal: controller.signal, - rethrowSessionLimitErrors - }) - ).rejects.toBe(error) - expect(mockExec).toHaveBeenCalledTimes(1) - } - ) - - it('keeps a confirmed command failure as an incomplete install', async () => { - mockExec.mockRejectedValueOnce( - Object.assign(new Error('SSH command failed after closing'), { - sshChannelCloseConfirmed: true - }) - ) - - await expect(isRemoteInstallComplete(conn, model, remoteDir, host)).resolves.toBe(false) - }) - - it('keeps caller cancellation after a confirmed command failure', async () => { - const controller = new AbortController() - const abortError = new Error('deploy aborted') - mockExec.mockImplementationOnce(async () => { - controller.abort(abortError) - throw Object.assign(new Error('SSH command failed after closing'), { - sshChannelCloseConfirmed: true - }) - }) - - await expect( - isRemoteInstallComplete(conn, model, remoteDir, host, { signal: controller.signal }) - ).rejects.toBe(abortError) - }) - }) -}) diff --git a/src/main/native-chat/agent-session-journal/journal-dispatch-reason-bound.spec.ts b/src/main/native-chat/agent-session-journal/journal-dispatch-reason-bound.spec.ts index adfb15a44c6..a4d63d68e25 100644 --- a/src/main/native-chat/agent-session-journal/journal-dispatch-reason-bound.spec.ts +++ b/src/main/native-chat/agent-session-journal/journal-dispatch-reason-bound.spec.ts @@ -68,22 +68,6 @@ export function registerJournalDispatchReasonBoundTests(): void { }) describe('dispatch reason bounding', () => { - it('bounds an oversized provider error before it reaches the row', async () => { - const stored = await settle(HUGE) - expect(stored).not.toBeNull() - expect(stored?.length).toBeLessThan(HUGE.length) - }) - - it('marks the clipped reason rather than truncating it silently', async () => { - const stored = await settle(HUGE) - expect(stored).toContain('[Orca: output truncated') - }) - - it('leaves a reason that already fits exactly as written', async () => { - const stored = await settle(`${DISPATCH_REJECTED_WRITE_FAILED}: broken pipe`) - expect(stored).toBe(`${DISPATCH_REJECTED_WRITE_FAILED}: broken pipe`) - }) - it('keeps a clipped transport failure classifiable', async () => { const stored = await settle(`${DISPATCH_REJECTED_WRITE_FAILED}: ${HUGE}`) expect(stored).not.toBe(`${DISPATCH_REJECTED_WRITE_FAILED}: ${HUGE}`) diff --git a/src/main/native-chat/agent-session-journal/queued-message-update.spec.ts b/src/main/native-chat/agent-session-journal/queued-message-update.spec.ts index 7d0a1926001..3d7d170f10f 100644 --- a/src/main/native-chat/agent-session-journal/queued-message-update.spec.ts +++ b/src/main/native-chat/agent-session-journal/queued-message-update.spec.ts @@ -3,7 +3,6 @@ import { queuedMessageUpdateDecision } from './queued-message-update' import type { QueuedMessageRow } from './queued-message-table' import type { AgentJournalMessageItem } from '../../../shared/agent-session/journal/agent-session-journal-types' import { agentSessionSendBodyFingerprint } from '../../../shared/agent-session/requests/structured-agent-session-send-mutation' -import { MAX_PROMPT_BYTES } from '../../../shared/rpc-contract/structured-agent-session-params' export function registerQueuedMessageUpdateTests(): void { const sessionId = 'folder-session' @@ -65,55 +64,5 @@ export function registerQueuedMessageUpdateTests(): void { expect(decide(edited, 'other', base).status).toBe('changed') expect(decide(card(), 'edit').status).toBe('ready') }) - it('refuses empty, oversized, command and unsupported bodies', () => { - expect(decide(card(), ' ').status).toBe('not-editable') - expect(decide(card(), 'é'.repeat(MAX_PROMPT_BYTES)).status).toBe('not-editable') - expect(decide(card({ ...initial, command: { name: 'compact' } }), 'edit').status).toBe( - 'not-editable' - ) - const unsupported = card({ - ...initial, - blocks: [{ type: 'tool-result', callId: 'tool', output: 'retained' }] - }) - expect(decide(unsupported, 'edit').status).toBe('not-editable') - }) - it('allows empty text with a retained attachment, and inserts text before one with none', () => { - const captioned = card({ - ...initial, - blocks: [ - { type: 'text', text: 'caption' }, - { type: 'image-ref', path: 'file.png' } - ] - }) - expect(decide(captioned, '')).toMatchObject({ - body: { - blocks: [ - { type: 'text', text: '' }, - { type: 'image-ref', path: 'file.png' } - ] - } - }) - const imageOnly = card({ ...initial, blocks: [{ type: 'image-ref', path: 'file.png' }] }) - expect(decide(imageOnly, 'added')).toMatchObject({ - body: { - blocks: [ - { type: 'text', text: 'added' }, - { type: 'image-ref', path: 'file.png' } - ] - } - }) - }) - it('a returned card stays editable; a dispatched, withdrawn or missing one answers gone', () => { - expect(decide(card(initial, 'returned'), 'edit').status).toBe('ready') - expect(decide(card(initial, 'dispatched'), 'edit')).toMatchObject({ - status: 'gone', - disposition: 'dispatched' - }) - expect(decide(card(initial, 'withdrawn'), 'edit')).toMatchObject({ - status: 'gone', - disposition: 'withdrawn' - }) - expect(decide(null, 'edit')).toMatchObject({ status: 'gone', disposition: 'missing' }) - }) }) } diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-codex-held-goal.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-codex-held-goal.test.ts index a5dff238b51..6844ac1c173 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-codex-held-goal.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-codex-held-goal.test.ts @@ -75,26 +75,3 @@ it.each([ expect((await rig.submission(held.id))?.handedOverAt).toBeUndefined() } ) - -it('releases a startup goal when a second message parks while the first turn opens', async () => { - await rig.dispose() - rig = await createQueuedMessageTestRig({ starting: true, restartable: true }) - const changeGoal = vi.fn(async () => ({ ok: true as const })) - rig.host.deps.adapter.changeThreadGoal = changeGoal - const first = rig.send('First model', undefined, { carried: { model: 'model-a' } }) - const second = rig.send('Second model', undefined, { carried: { model: 'model-b' } }) - await first.result - await second.result - const change = { kind: 'set' as const, objective: 'A startup objective' } - const goal = rig.host.changeThreadGoal(QUEUED_RIG_CALLER, { - envelope: rig.envelope({ change }, 'agentSession.threadGoal', hostTestOperationId()), - change - }) - await rig.host.collaboratorsForTests().serialize(SESSION, async () => {}) - await rig.proveStart() - await eventually(() => expect(changeGoal).toHaveBeenCalledOnce()) - expect(await goal).toMatchObject({ ok: true }) - expect(rig.dispatch).toHaveBeenCalledOnce() - await rig.settleAccepted(first.id, 'a') - await eventually(() => expect(rig.dispatch).toHaveBeenCalledTimes(2)) -}) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-restart-failure-filing.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-restart-failure-filing.test.ts index 383a7407e6f..ecc5b7686a5 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-restart-failure-filing.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-restart-failure-filing.test.ts @@ -4,7 +4,6 @@ import { StructuredAgentSessionHost } from './structured-agent-session-host' import { afterEach, expect, it, vi } from 'vitest' import { AgentSessionRecoveryCapsule } from '../../runtime/agent-session-recovery-capsule' import { - AGENT_SESSION_RESTART_CONTINUATION_NOTE, AGENT_SESSION_RESTART_CONTINUATION_REFUSED_NOTE, AGENT_SESSION_RESTART_CONTINUATION_UNCONFIRMED_NOTE } from '../../../shared/agent-session/lifecycle/agent-session-restart-continuation' @@ -83,21 +82,6 @@ it('says so in the chat when the agent cannot start for the continuation', async await restarted.flushAllStreamedEvents() }) -// Successful continuation notes follow their own message; preserve the earlier cut notice. -it("keeps the quit's notice through a continuation that carries the chat on", async () => { - const { host } = await interruptedRestart() - await host.restartResume.list() - expect(await readerNotes(host)).toEqual([QUIT_CUT_NOTICE]) - - const result = await host.restartResume.continueAfterRestart([SESSION], 'modal') - - expect(result.continued).toMatchObject([{ outcome: 'continued' }]) - expect(await readerNotes(host)).toEqual([ - QUIT_CUT_NOTICE, - { text: AGENT_SESSION_RESTART_CONTINUATION_NOTE, tone: undefined } - ]) -}) - it('keeps an unconfirmed failure while the agent the continuation started keeps running', async () => { const state = await interruptedRestart() const { host } = state diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-send-block.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-send-block.test.ts deleted file mode 100644 index de4edc3bb3b..00000000000 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-send-block.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { AgentSessionConversationCommandRecord } from '../../../shared/agent-session/agent-session-conversation-command' -import { agentSessionRecordFixture } from '../../../shared/agent-session/lifecycle/agent-session-record.test-fixture' -import { - openConversationForWrite, - structuredAgentSessionSendBlock -} from './structured-agent-session-send-preparation' -import { createStructuredAgentSessionLogger } from './structured-agent-session-logger' - -function withCommand(command: AgentSessionConversationCommandRecord) { - return { ...agentSessionRecordFixture(), conversationCommand: command } -} - -const COMMAND = { operationId: 'operation-1', callerKey: 'client-1', runtimeFence: 7 } - -describe('a send refused by the conversation command it follows', () => { - it("lets a send follow a /clear that never committed, as an older build's record leaves one", () => { - expect( - structuredAgentSessionSendBlock( - withCommand({ - ...COMMAND, - command: 'clear', - state: 'unknown', - phase: 'prepared', - replacementSessionId: 'clear-replacement-1' - }) - ) - ).toBeNull() - }) - - it('allows a send to an independently retained old-build clear source', () => { - const blocked = structuredAgentSessionSendBlock( - withCommand({ - ...COMMAND, - command: 'clear', - state: 'completed', - phase: 'committed', - replacementSessionId: 'clear-replacement-1' - }) - ) - - expect(blocked).toBeNull() - }) - - it("lets a send follow an older build's unconfirmed /compact, whose child this host no longer runs", () => { - expect( - structuredAgentSessionSendBlock( - withCommand({ ...COMMAND, command: 'compact', state: 'unknown', phase: 'prepared' }) - ) - ).toBeNull() - }) - - it('lets a send follow a /clear whose new conversation failed to start', () => { - expect( - structuredAgentSessionSendBlock( - withCommand({ ...COMMAND, command: 'clear', state: 'completed', phase: 'committed' }) - ) - ).toBeNull() - }) -}) - -describe('a write whose conversation the host could not open', () => { - afterEach(() => { - vi.restoreAllMocks() - }) - - const ENVELOPE = { - sessionId: 'session-1', - clientOperationId: 'operation-1', - expectedRuntimeFence: 1, - payloadFingerprint: '' - } - - function refusedBy(error: unknown) { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - return openConversationForWrite( - async () => { - throw error - }, - ENVELOPE, - createStructuredAgentSessionLogger() - ) - } - - it('says a corrupt history is final, in words and not the error', async () => { - const corrupt = Object.assign(new Error('/Users/me/journal.db: file is not a database'), { - code: 'ERR_SQLITE_ERROR', - errcode: 26 - }) - - expect(await refusedBy(corrupt)).toEqual({ - ok: false, - refusal: { - code: 'agent_session_journal_unreadable', - details: { reason: 'journalCorrupt' }, - message: 'Unable to load this chat.' - } - }) - }) - - it('says any other failed open can clear', async () => { - const denied = Object.assign(new Error('EACCES: permission denied, open /Users/me'), { - code: 'EACCES', - errno: -13 - }) - - expect(await refusedBy(denied)).toEqual({ - ok: false, - refusal: { - code: 'agent_session_journal_unreadable', - details: { reason: 'journalUnavailable' }, - message: "Orca couldn't open this chat's history right now. Try again." - } - }) - }) -}) diff --git a/src/main/observability/local-file-sink-memory.test.ts b/src/main/observability/local-file-sink-memory.test.ts index 9971c4dd701..8cb5684c2c6 100644 --- a/src/main/observability/local-file-sink-memory.test.ts +++ b/src/main/observability/local-file-sink-memory.test.ts @@ -31,26 +31,6 @@ function retainedHeap(): number { } describe('trace sink rejected record retention', () => { - it('keeps small-record byte scans deferred until the batch flush', async () => { - const filePath = join(directory, 'trace.ndjson') - sink = createLocalFileSink({ filePath }) - const byteLength = vi.spyOn(Buffer, 'byteLength') - let beforeFlush: number - let afterFlush: number - try { - for (let index = 0; index < 20; index++) { - sink.push({ index, text: '💡漢字' }) - } - beforeFlush = byteLength.mock.calls.length - await sink.flushAsync() - afterFlush = byteLength.mock.calls.length - } finally { - byteLength.mockRestore() - } - expect(beforeFlush).toBe(0) - expect(afterFlush).toBeLessThanOrEqual(1) - }) - it('releases oversized serialized records before the pending batch flushes', async () => { const filePath = join(directory, 'trace.ndjson') sink = createLocalFileSink({ filePath, maxBytes: 64 * 1024, batchWindowMs: 200 }) @@ -69,37 +49,6 @@ describe('trace sink rejected record retention', () => { expect(written.at(-1)).toEqual({ valid: true }) }) - it('names the dropped record in the marker instead of leaving a silent gap', async () => { - const filePath = join(directory, 'trace.ndjson') - sink = createLocalFileSink({ filePath, maxBytes: 64 * 1024 }) - sink.push({ - type: 'effect-span', - name: 'worktree.create', - traceId: 'a'.repeat(32), - payload: 'x'.repeat(1024 * 1024) - }) - await sink.flushAsync() - const [marker] = readFileSync(filePath, 'utf8').split('\n').filter(Boolean).map(parseLine) - expect(marker).toMatchObject({ - type: DROPPED_RECORD_TYPE, - reason: 'oversize', - name: 'worktree.create', - traceId: 'a'.repeat(32) - }) - expect(marker.droppedChars).toBeGreaterThan(1024 * 1024) - // The collector must be able to age dropped-record markers out of its lookback window. - expect(BigInt(marker.endTimeUnixNano as string)).toBeGreaterThan(0n) - }) - - it('omits the marker when even the marker would exceed the byte cap', async () => { - const filePath = join(directory, 'trace.ndjson') - sink = createLocalFileSink({ filePath, maxBytes: 40 }) - sink.push({ payload: 'x'.repeat(1_000) }) - sink.push({ ok: 1 }) - await sink.flushAsync() - expect(readFileSync(filePath, 'utf8')).toBe('{"ok":1}\n') - }) - it('accepts the exact UTF-8 byte cap and rotates at it', async () => { const filePath = join(directory, 'trace.ndjson') const record = { text: '💡' } diff --git a/src/main/orca-profiles/profile-telemetry-consent-seed.test.ts b/src/main/orca-profiles/profile-telemetry-consent-seed.test.ts index aad1bd9c812..e01e4edb71a 100644 --- a/src/main/orca-profiles/profile-telemetry-consent-seed.test.ts +++ b/src/main/orca-profiles/profile-telemetry-consent-seed.test.ts @@ -81,15 +81,6 @@ describe('new profile consent seeding', () => { expect(existsSync(getOrcaProfileStateDatabaseFile(profileId, root))).toBe(false) }) - it('refuses an incapable runtime before admission or profile creation', () => { - vi.spyOn(database, 'isProfileStateSqliteAvailable').mockReturnValue(false) - expect(() => seedNewOrcaProfileTelemetryConsent(profileId, telemetry, root)).toThrow( - 'bundled Orca runtime' - ) - expect(existsSync(join(root, '.profile-state-access'))).toBe(false) - expect(existsSync(join(root, 'profiles'))).toBe(false) - }) - it('refuses seeding while maintenance owns the root', () => { const maintenance = acquireProfileStateMaintenance(root) try { diff --git a/src/main/rate-limits/auth-filesystem-operation.test.ts b/src/main/rate-limits/auth-filesystem-operation.test.ts index 92099fa2dcc..68819739aae 100644 --- a/src/main/rate-limits/auth-filesystem-operation.test.ts +++ b/src/main/rate-limits/auth-filesystem-operation.test.ts @@ -39,41 +39,6 @@ describe('createAuthFilesystemOperation', () => { expect(secondRaw).not.toHaveBeenCalled() }) - it('allows different WSL distros to probe concurrently', async () => { - const ubuntuRaw = vi.fn(async () => 'ubuntu') - const debianRaw = vi.fn(async () => 'debian') - const ubuntu = createAuthFilesystemOperation( - '\\\\wsl.localhost\\Ubuntu\\home\\alice\\.codex\\auth.json', - ubuntuRaw - ) - const debian = createAuthFilesystemOperation( - '\\\\wsl.localhost\\Debian\\home\\alice\\.codex\\auth.json', - debianRaw - ) - const controller = new AbortController() - - await expect( - Promise.all([ubuntu.wait(controller.signal), debian.wait(controller.signal)]) - ).resolves.toEqual(['ubuntu', 'debian']) - expect(ubuntuRaw).toHaveBeenCalledOnce() - expect(debianRaw).toHaveBeenCalledOnce() - }) - - it('does not start an operation for an already-aborted waiter', async () => { - const raw = vi.fn(async () => 'unexpected') - const operation = createAuthFilesystemOperation( - '\\\\wsl.localhost\\Ubuntu\\home\\alice\\.codex\\auth.json', - raw - ) - const controller = new AbortController() - const abortError = new Error('already expired') - controller.abort(abortError) - - await expect(operation.wait(controller.signal)).rejects.toBe(abortError) - await expect(operation.result).rejects.toBe(abortError) - expect(raw).not.toHaveBeenCalled() - }) - it('caps cross-distro operations and never starts an expired queued probe', async () => { let resolveUbuntu!: (value: string) => void let resolveDebian!: (value: string) => void diff --git a/src/main/runtime/orchestration/orchestration-delivery-consumption.test.ts b/src/main/runtime/orchestration/orchestration-delivery-consumption.test.ts index 62bbafe1719..f7a51102e64 100644 --- a/src/main/runtime/orchestration/orchestration-delivery-consumption.test.ts +++ b/src/main/runtime/orchestration/orchestration-delivery-consumption.test.ts @@ -19,18 +19,6 @@ describe('mailbox delivery consumption', () => { return { run, params, insert } } - it('ignores a fully read batch without rewriting it', () => { - const { params, insert } = setup() - const old = insert('old') - const first = db.getOrCreateRunDelivery(params)! - db.db.prepare('UPDATE messages SET read = 1 WHERE id = ?').run(old.id) - const next = insert('next') - const current = db.getOrCreateRunDelivery(params)! - expect(current.messages.map((m) => m.id)).toEqual([next.id]) - expect(current.replayed).toBe(false) - expect(db.getDeliveryRaw(first.delivery.id)?.acknowledged_at).toBeNull() - }) - it('preserves the entire replay batch while any member is unread', () => { const { params, insert } = setup() const a = insert('a') @@ -60,20 +48,6 @@ describe('mailbox delivery consumption', () => { expect(db.getDeliveryRaw(first.delivery.id)?.acknowledged_at).toBeNull() }) - it('explains the delivery ID contract for invalid acknowledgements without consuming mail', () => { - const { params, insert } = setup() - const message = insert('pending') - const first = db.getOrCreateRunDelivery(params)! - expect(() => db.acknowledgeRunDelivery({ ...params, deliveryId: message.id })).toThrow( - `${message.id} is a message id, not a delivery id. Acknowledge the batch with the deliveryId field from the check response; process the entire batch before acknowledging.` - ) - expect(() => db.acknowledgeRunDelivery({ ...params, deliveryId: 'delivery_missing' })).toThrow( - '--ack requires a delivery_* ID returned by orchestration check; process the entire batch before acknowledging.' - ) - expect(db.getMessageById(message.id)?.read).toBe(0) - expect(db.getDeliveryRaw(first.delivery.id)?.acknowledged_at).toBeNull() - }) - it('checks the consumer generation even when the prior batch is already read', () => { const { run, params, insert } = setup() const message = insert('old') diff --git a/src/main/runtime/rpc/methods/clipboard-image-upload-decoding.test.ts b/src/main/runtime/rpc/methods/clipboard-image-upload-decoding.test.ts index 280579cf91b..395b2b616ea 100644 --- a/src/main/runtime/rpc/methods/clipboard-image-upload-decoding.test.ts +++ b/src/main/runtime/rpc/methods/clipboard-image-upload-decoding.test.ts @@ -17,56 +17,6 @@ describe('clipboard image upload decoding', () => { expect(decodeClipboardImageUpload(chunks)).toEqual(Buffer.from(chunks.join(''), 'base64')) }) - it.each([ - ['AA==', 'AAAA'], - ['AA', '==', '=='], - ['AAA', 'AA'], - ['AA=', '=='] - ])('rejects the same invalid complete payload: %j', (...chunks) => { - expect(isValidBase64(chunks.join(''))).toBe(false) - expect(() => decodeClipboardImageUpload(chunks)).toThrow( - 'Clipboard image content must be base64' - ) - }) - - it('matches whole-string validation and decoding for seeded arbitrary chunk boundaries', () => { - let seed = 941 - const next = (max: number): number => { - seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 - return seed % max - } - const alphabet = 'Az09+/' - for (let sample = 0; sample < 5000; sample += 1) { - const chunks: string[] = [''] - const count = next(16) - for (let index = 0; index < count; index += 1) { - let size = 2 + next(12) - if (size % 4 === 1) { - size += 1 - } - let chunk = '' - for (let offset = 0; offset < size; offset += 1) { - chunk += alphabet[next(alphabet.length)] - } - if (next(6) === 0) { - const padding = 1 + next(2) - chunk = chunk.slice(0, -padding) + '='.repeat(padding) - } - chunks.push(chunk) - } - chunks.push('') - expect(chunks.every(isValidBase64)).toBe(true) - const content = chunks.join('') - if (isValidBase64(content)) { - expect(decodeClipboardImageUpload(chunks)).toEqual(Buffer.from(content, 'base64')) - } else { - expect(() => decodeClipboardImageUpload(chunks)).toThrow( - 'Clipboard image content must be base64' - ) - } - } - }) - it('initializes every output byte for permissive padding and partial quartets', () => { const allocate = vi .spyOn(Buffer, 'allocUnsafe') diff --git a/src/main/runtime/rpc/methods/terminal/terminal-viewer-colors-method.test.ts b/src/main/runtime/rpc/methods/terminal/terminal-viewer-colors-method.test.ts index b8295ec7d2b..92f1bb7e334 100644 --- a/src/main/runtime/rpc/methods/terminal/terminal-viewer-colors-method.test.ts +++ b/src/main/runtime/rpc/methods/terminal/terminal-viewer-colors-method.test.ts @@ -6,9 +6,7 @@ import { TERMINAL_METHODS } from '../terminal' import { _resetTerminalViewAttributesForTest, getTerminalViewAttributes, - getTerminalViewerColors, - setTerminalViewAttributes, - setTerminalViewerColorsListener + setTerminalViewAttributes } from '../../../terminal-view-attribute-store' import type { TerminalViewAttributes, @@ -45,42 +43,6 @@ function setViewerColors(params: unknown) { describe('terminal.setViewerColors', () => { afterEach(() => _resetTerminalViewAttributesForTest()) - it("makes a paired client's colours the ones a headless host answers with", async () => { - const published: unknown[] = [] - setTerminalViewerColorsListener((colors) => published.push(colors)) - const colors = { foreground: '#2e3434', background: '#ffffff' } - - const response = await setViewerColors({ colors }) - - expect(response).toMatchObject({ ok: true, result: { applied: true } }) - expect(getTerminalViewerColors()).toEqual(colors) - expect(published).toEqual([colors]) - }) - - it('keeps the current colours when the pair cannot answer both slots', async () => { - const response = await setViewerColors({ colors: { foreground: '#ffffff' } }) - - expect(response).toMatchObject({ ok: true, result: { applied: false } }) - expect(getTerminalViewerColors()).toBeNull() - }) - - it('ignores members a newer client adds', async () => { - const colors = { foreground: '#000000', background: '#ffffff' } - - const response = await setViewerColors({ colors: { ...colors, cursor: '#ff0000' }, seq: 3 }) - - expect(response).toMatchObject({ ok: true, result: { applied: true } }) - expect(getTerminalViewerColors()).toEqual(colors) - }) - - it("gives a windowless host the paired client's palette for hidden OSC 4/12 and ?996n replies", async () => { - const attributes = appearance([0xff, 0xff, 0xff]) - - await setViewerColors({ colors: { foreground: '#2e3434', background: '#ffffff' }, attributes }) - - expect(getTerminalViewAttributes()).toEqual(attributes) - }) - it("keeps a windowed host's own palette", async () => { const own = appearance([0x28, 0x2c, 0x34]) setTerminalViewAttributes(own) @@ -93,15 +55,6 @@ describe('terminal.setViewerColors', () => { expect(getTerminalViewAttributes()).toEqual(own) }) - it('drops a malformed palette but still applies the colours', async () => { - const colors = { foreground: '#2e3434', background: '#ffffff' } - - await setViewerColors({ colors, attributes: { ...appearance([1, 2, 3]), ansi: [] } }) - - expect(getTerminalViewAttributes()).toBeNull() - expect(getTerminalViewerColors()).toEqual(colors) - }) - it("forgets an earlier client's palette when an older client pushes colours only", async () => { await setViewerColors({ colors: { foreground: '#ffffff', background: '#282c34' }, diff --git a/src/main/runtime/runtime-workspace-session-controller-twin-repo.test.ts b/src/main/runtime/runtime-workspace-session-controller-twin-repo.test.ts index 6a67978d7ae..1c77cabde9b 100644 --- a/src/main/runtime/runtime-workspace-session-controller-twin-repo.test.ts +++ b/src/main/runtime/runtime-workspace-session-controller-twin-repo.test.ts @@ -4,7 +4,6 @@ import type { ExecutionHostId } from '../../shared/execution-host' import { LOCAL_ROW, RUNTIME_ROW, - SSH_ROW, TWIN_REPO_HOST_CASES, TWIN_WORKTREE_ID, twinPartitionSessions, @@ -56,15 +55,6 @@ describe('routing a repo id registered on two hosts, in a layout-owner build', ( expect(controller.tryGetHostId(TWIN_WORKTREE_ID)).toBe(expected) }) - it.each<[string, TwinRepoRow, ExecutionHostId]>([ - ['local', LOCAL_ROW, 'local'], - ['SSH', SSH_ROW, 'ssh:box'], - ['runtime', RUNTIME_ROW, 'runtime:env-1'] - ])('leaves a %s-only repo on its own host', (_label, row, expected) => { - layoutOwner.isMain = true - expect(controllerFor({ repos: [row] }).controller.tryGetHostId(TWIN_WORKTREE_ID)).toBe(expected) - }) - it('hydrates a twin worktree from the partition the router picks, not the last repo row', () => { layoutOwner.isMain = true const { controller, sessions } = controllerFor({ @@ -76,16 +66,6 @@ describe('routing a repo id registered on two hosts, in a layout-owner build', ( ) }) - it.each(['global-floating-terminal', 'removed-repo::/work/api'])( - 'routes %s without reading the repo inventory', - (worktreeId) => { - layoutOwner.isMain = true - const { controller, getRepos } = controllerFor({ repos: [RUNTIME_ROW, LOCAL_ROW] }) - expect(controller.tryGetHostId(worktreeId)).toBe('local') - expect(getRepos).not.toHaveBeenCalled() - } - ) - it('reads the repo inventory once per hydration pass, not once per saved worktree', () => { layoutOwner.isMain = true const { controller, getRepos } = controllerFor({ @@ -96,23 +76,3 @@ describe('routing a repo id registered on two hosts, in a layout-owner build', ( expect(getRepos).toHaveBeenCalledTimes(1) }) }) - -describe('routing twins with the layout-owner switch off', () => { - it.each<[string, TwinRepoRow[], ExecutionHostId]>([ - ['local and SSH twins', [SSH_ROW, LOCAL_ROW], 'ssh:box'], - ['local and runtime twins', [RUNTIME_ROW, LOCAL_ROW], 'runtime:env-1'], - ['SSH and runtime twins', [RUNTIME_ROW, SSH_ROW], 'runtime:env-1'] - ])('keeps the first catalog row for %s', (_label, repos, expected) => { - layoutOwner.isMain = false - expect(controllerFor({ repos }).controller.tryGetHostId(TWIN_WORKTREE_ID)).toBe(expected) - }) - - it('keeps hydrating from the last repo row', () => { - layoutOwner.isMain = false - const { controller } = controllerFor({ - repos: [RUNTIME_ROW, LOCAL_ROW], - saved: ['runtime:env-1'] - }) - expect(controller.getHydrationTargets(true).has(TWIN_WORKTREE_ID)).toBe(false) - }) -}) diff --git a/src/main/runtime/sidebar-inputs/workspace-git-identity.test.ts b/src/main/runtime/sidebar-inputs/workspace-git-identity.test.ts index 3ef2df19ad5..e528eb2bd61 100644 --- a/src/main/runtime/sidebar-inputs/workspace-git-identity.test.ts +++ b/src/main/runtime/sidebar-inputs/workspace-git-identity.test.ts @@ -38,14 +38,6 @@ describe('WorkspaceGitIdentity', () => { expect(identity.observeScan([row({ linkedPR: null })], SCANNED)).toEqual([]) }) - it('keeps a link written after the last scan, which may belong to the new branch', () => { - const identity = new WorkspaceGitIdentity() - identity.observeScan([row({ linkedPR: null })], SCANNED) - expect(identity.observeScan([row({ branch: 'refs/heads/b', linkedPR: 9 })], SCANNED)).toEqual( - [] - ) - }) - it('keeps a PR linked on a new branch that a cached rescan still reported as the old one', () => { const identity = new WorkspaceGitIdentity() const cachedRun = {} @@ -58,15 +50,6 @@ describe('WorkspaceGitIdentity', () => { ) }) - it('records a link once git ran again on the same branch, so a later switch clears it', () => { - const identity = new WorkspaceGitIdentity() - identity.observeScan([row({ linkedPR: null })], SCANNED) - identity.observeScan([row({ linkedPR: 9 })], SCANNED) - expect( - identity.observeScan([row({ branch: 'refs/heads/b', linkedPR: 9 })], SCANNED) - ).toHaveLength(1) - }) - it('treats a recreated workspace, or one with no instance id, as new', () => { const identity = new WorkspaceGitIdentity() identity.observeScan([row()], SCANNED) diff --git a/src/main/runtime/terminal-tail-crlf.test.ts b/src/main/runtime/terminal-tail-crlf.test.ts index 09be61337d1..fd033ace4dd 100644 --- a/src/main/runtime/terminal-tail-crlf.test.ts +++ b/src/main/runtime/terminal-tail-crlf.test.ts @@ -48,20 +48,6 @@ describe('terminal tail CRLF chunk boundaries', () => { expect(ingestChunks([...output])).toEqual(expected) }) - it.each([ - [['old\r', '', 'new\n'], ['new']], - [['old\r', '\r', 'new\r', '\n'], ['new']], - [['old\r', '\r\n'], ['']], - [['old\r', '\x1b[', '2Knew\r', '\n'], ['new']], - [['\x1b[31', 'mQUJDRA==\r', '\n\x1b[0m'], ['QUJDRA==']], - [ - ['row one\nrow two\n', '\x1b[1A\x1b[2Kchanged\r', '\n'], - ['row one', 'changed'] - ] - ])('preserves standalone redraw and ANSI handling for %j', (chunks, expected) => { - expect(ingestChunks(chunks).lines).toEqual(expected) - }) - it('keeps partial retention bounded while a final CR is pending', () => { const normalized = normalizeTerminalChunk(`${'x'.repeat(MAX_TAIL_PARTIAL_CHARS * 2)}\r`) const partial = appendNormalizedToTailBuffer([], '', normalized.text) @@ -75,13 +61,6 @@ describe('terminal tail CRLF chunk boundaries', () => { expect(newline.pendingAnsi).toBe('') }) - it('retains the last painted content if output ends with a lone CR', () => { - const tail = ingestChunks(['last output\r', '', '']) - expect(tail.partialLine).toBe('last output') - expect(tail.pendingAnsi).toBe('\r') - expect(tail.completeLines).toBe(0) - }) - it.each([false, true])( 'updates PTY and leaf transcripts when leaf diverged=%s', async (diverged) => { diff --git a/src/main/runtime/windows-drive-listing.test.ts b/src/main/runtime/windows-drive-listing.test.ts deleted file mode 100644 index 4ceef94bb55..00000000000 --- a/src/main/runtime/windows-drive-listing.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { Stats } from 'node:fs' -import { isServerDriveListRequest, listWindowsDrives } from './windows-drive-listing' - -describe('isServerDriveListRequest', () => { - it('matches root browses only on win32', () => { - expect(isServerDriveListRequest('/', 'win32')).toBe(true) - expect(isServerDriveListRequest('\\', 'win32')).toBe(true) - expect(isServerDriveListRequest(' / ', 'win32')).toBe(true) - expect(isServerDriveListRequest('/', 'darwin')).toBe(false) - expect(isServerDriveListRequest('/', 'linux')).toBe(false) - }) - - it('does not intercept non-root paths on win32', () => { - expect(isServerDriveListRequest('C:\\', 'win32')).toBe(false) - expect(isServerDriveListRequest('/Users', 'win32')).toBe(false) - expect(isServerDriveListRequest('~', 'win32')).toBe(false) - expect(isServerDriveListRequest('', 'win32')).toBe(false) - }) -}) - -describe('listWindowsDrives', () => { - afterEach(() => { - vi.restoreAllMocks() - }) - - const statOnly = (mounted: string[]) => async (p: string) => { - if (!mounted.includes(p)) { - throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }) - } - return { isDirectory: () => true } as Stats - } - - it('returns one directory entry per mounted drive, anchored at the host root', async () => { - const result = await listWindowsDrives(statOnly(['C:\\', 'M:\\'])) - expect(result.resolvedPath).toBe('/') - expect(result.pathFlavor).toBe('win32') - expect(result.entries).toEqual([ - { name: 'C:\\', isDirectory: true, isSymlink: false }, - { name: 'M:\\', isDirectory: true, isSymlink: false } - ]) - }) - - it('skips letters whose stat fails or is not a directory', async () => { - const statPath = async (p: string): Promise => { - if (p === 'C:\\') { - return { isDirectory: () => true } as Stats - } - if (p === 'D:\\') { - return { isDirectory: () => false } as Stats - } - throw Object.assign(new Error('EPERM'), { code: 'EPERM' }) - } - const result = await listWindowsDrives(statPath) - expect(result.entries.map((e) => e.name)).toEqual(['C:\\']) - }) - - it('skips an unreadable drive with an unexpected errno and logs it', async () => { - const error = Object.assign(new Error('EIO'), { code: 'EIO' }) - const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) - const statPath = async (p: string): Promise => { - if (p === 'D:\\') { - throw error - } - throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }) - } - - const result = await listWindowsDrives(statPath) - - expect(result.entries).toEqual([]) - expect(warn).toHaveBeenCalledOnce() - expect(warn).toHaveBeenCalledWith('[windows-drive-listing] Failed to stat drive', { - root: 'D:\\', - error - }) - }) - - it('surfaces programming errors from isDirectory', async () => { - const error = new TypeError('programmer bug') - const statPath = async (p: string): Promise => { - if (p === 'D:\\') { - return { - isDirectory: () => { - throw error - } - } as unknown as Stats - } - throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }) - } - - await expect(listWindowsDrives(statPath)).rejects.toBe(error) - }) - - it('returns healthy drives among tolerated and unexpected stat failures', async () => { - const error = Object.assign(new Error('EIO'), { code: 'EIO' }) - const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) - const statPath = async (p: string): Promise => { - if (p === 'C:\\' || p === 'F:\\') { - return { isDirectory: () => true } as Stats - } - if (p === 'E:\\') { - throw error - } - throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }) - } - - const result = await listWindowsDrives(statPath) - - expect(result.entries.map((entry) => entry.name)).toEqual(['C:\\', 'F:\\']) - expect(warn).toHaveBeenCalledOnce() - expect(warn).toHaveBeenCalledWith('[windows-drive-listing] Failed to stat drive', { - root: 'E:\\', - error - }) - }) -}) diff --git a/src/main/sqlite/sqlite-read-failure.test.ts b/src/main/sqlite/sqlite-read-failure.test.ts index 14b73f6f5ac..676191a3a50 100644 --- a/src/main/sqlite/sqlite-read-failure.test.ts +++ b/src/main/sqlite/sqlite-read-failure.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import SyncDatabase from './sync-database' -import { classifySqliteReadFailure, isTransientSqliteContention } from './sqlite-read-failure' +import { isTransientSqliteContention } from './sqlite-read-failure' // Real node:sqlite codes distinguish transient contention (5) from missing read-only WAL companions (14); they require opposite recovery. @@ -54,59 +54,4 @@ describe('isTransientSqliteContention', () => { expect(thrown).toMatchObject({ errcode: 5 }) expect(isTransientSqliteContention(thrown)).toBe(true) }) - - it('recognizes a relayed message with no errcode, as the Codex heal pass sees it', () => { - expect( - isTransientSqliteContention('codex app-server thread/read failed: database is locked') - ).toBe(true) - expect(isTransientSqliteContention(new Error('SQLITE_LOCKED: table is locked'))).toBe(true) - }) - - it('reads extended result codes through their primary code', () => { - expect(isTransientSqliteContention({ errcode: 517, message: 'busy snapshot' })).toBe(true) - expect(isTransientSqliteContention({ errcode: 261, message: 'recovery' })).toBe(true) - }) - - it('does not treat an unreadable or absent database as contention', () => { - expect(isTransientSqliteContention(new Error('file is not a database'))).toBe(false) - expect( - isTransientSqliteContention({ errcode: 14, message: 'unable to open database file' }) - ).toBe(false) - }) -}) - -describe('classifySqliteReadFailure', () => { - it('classifies contention as retryable regardless of the file evidence', () => { - const error = { errcode: 5, message: 'database is locked' } - - expect(classifySqliteReadFailure({ error, databaseFileExists: true })).toBe('contended') - expect(classifySqliteReadFailure({ error, databaseFileExists: false })).toBe('contended') - }) - - it('classifies SQLITE_CANTOPEN against a present database as an unreachable wal-index', () => { - expect( - classifySqliteReadFailure({ - error: { errcode: 14, message: 'unable to open database file' }, - databaseFileExists: true - }) - ).toBe('wal-index-unavailable') - }) - - it('does not blame the wal-index when the database file itself is gone', () => { - expect( - classifySqliteReadFailure({ - error: { errcode: 14, message: 'unable to open database file' }, - databaseFileExists: false - }) - ).toBe('unreadable') - }) - - it('falls back to unreadable for anything else', () => { - expect( - classifySqliteReadFailure({ - error: { errcode: 11, message: 'database disk image is malformed' }, - databaseFileExists: true - }) - ).toBe('unreadable') - }) }) diff --git a/src/main/ssh/orcad-server-project-groups.test.ts b/src/main/ssh/orcad-server-project-groups.test.ts deleted file mode 100644 index fc939b2345e..00000000000 --- a/src/main/ssh/orcad-server-project-groups.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { ProjectGroup } from '../../shared/project-group-types' -import { projectGroupsHeldByServer, serverHeldProjectGroups } from './orcad-server-project-groups' - -function group(id: string, parentGroupId: string | null = null): ProjectGroup { - return { - id, - name: id, - parentPath: '/root', - connectionId: 'ssh-shared', - parentGroupId, - createdFrom: 'manual', - tabOrder: 0, - isCollapsed: false, - color: null, - createdAt: 1, - updatedAt: 1 - } -} - -const NOTHING_LINKED = { repositoryIds: new Set(), folderWorkspaceIds: new Set() } -const payload = (groups: ProjectGroup[]) => ({ - repositories: [], - folderWorkspaces: [], - projectGroups: groups -}) -const serverGroup = (id: string, connectionId: string | null = null) => ({ - id, - parentGroupId: null, - connectionId -}) - -describe('which project groups the server already holds', () => { - it('holds a same-id group on its own machine, though no row sits in it', () => { - expect( - projectGroupsHeldByServer([group('empty')], { projectGroups: [serverGroup('empty')] }) - ).toEqual(['empty']) - }) - - it('keeps a same-id group the server keeps for another host apart', () => { - expect( - projectGroupsHeldByServer([group('empty')], { - projectGroups: [serverGroup('empty', 'server-ssh-host')] - }) - ).toEqual([]) - }) - - it('links a held group and its ancestors', () => { - const held = serverHeldProjectGroups( - payload([group('parent'), group('child', 'parent')]), - NOTHING_LINKED, - [], - { projectGroups: [serverGroup('parent'), serverGroup('child')] } - ) - expect(held.sort()).toEqual(['child', 'parent']) - }) - - // Foreign rows/groups cannot prove local ownership or hide migration conflicts. - it('takes no evidence from a server row or group on another host', () => { - const held = serverHeldProjectGroups( - payload([group('mine'), group('foreign'), group('via-row')]), - NOTHING_LINKED, - [ - { projectGroupId: 'foreign', connectionId: 'server-ssh-host' }, - { projectGroupId: 'via-row', executionHostId: 'ssh:server-ssh-host' } - ], - { projectGroups: [serverGroup('mine'), serverGroup('foreign', 'server-ssh-host')] } - ) - expect(held).toEqual(['mine']) - }) - - it('keeps a same-id group stamped with another execution host apart', () => { - expect( - projectGroupsHeldByServer([group('empty')], { - projectGroups: [{ ...serverGroup('empty'), executionHostId: 'ssh:server-ssh-host' }] - }) - ).toEqual([]) - }) - - // Same-id foreign parents must not enter the local ancestor link set. - it('does not link a parent the server keeps for another host', () => { - const held = serverHeldProjectGroups( - payload([group('parent'), group('child', 'parent')]), - NOTHING_LINKED, - [], - { projectGroups: [serverGroup('parent', 'server-ssh-host'), serverGroup('child')] } - ) - expect(held).toEqual(['child']) - }) - - // A missing parent must migrate or later rows referencing it will fail. - it('does not link a parent the server lacks', () => { - const held = serverHeldProjectGroups( - payload([group('new-parent'), group('child', 'new-parent')]), - NOTHING_LINKED, - [{ projectGroupId: 'child' }], - { projectGroups: [serverGroup('child')] } - ) - expect(held).toEqual(['child']) - }) - - it('without the server’s group list, links the group a local server row sits in', () => { - expect( - serverHeldProjectGroups( - payload([group('g')]), - NOTHING_LINKED, - [{ projectGroupId: 'g' }], - undefined - ) - ).toEqual(['g']) - }) -}) diff --git a/src/main/startup/single-instance-lock-exit.electron.test.ts b/src/main/startup/single-instance-lock-exit.electron.test.ts index e2ebc0cf9c9..1c88a312fee 100644 --- a/src/main/startup/single-instance-lock-exit.electron.test.ts +++ b/src/main/startup/single-instance-lock-exit.electron.test.ts @@ -107,12 +107,4 @@ describe('pre-ready termination under real Electron', () => { expect(run.markers).toEqual([GATE_ENTERED]) expect(run.status).toBe(1) }, 90_000) - - it('reproduces the deferred graceful quit that let the doomed launch keep booting', () => { - const run = runPreReadyGate('app.quit()') - - // Pin Electron semantics: pre-ready quit schedules exit but does not stop startup. - expect(run.markers).toEqual([GATE_ENTERED, CONTINUED_INTO_STARTUP, REACHED_TAIL]) - expect(run.status).not.toBe(SINGLE_INSTANCE_ALREADY_RUNNING_EXIT_CODE) - }, 90_000) }) diff --git a/src/main/startup/wsl-cli-reconciliation-startup-barrier.test.ts b/src/main/startup/wsl-cli-reconciliation-startup-barrier.test.ts index 2059734c1d9..5381cf83289 100644 --- a/src/main/startup/wsl-cli-reconciliation-startup-barrier.test.ts +++ b/src/main/startup/wsl-cli-reconciliation-startup-barrier.test.ts @@ -5,31 +5,6 @@ import { } from './wsl-cli-reconciliation-startup-barrier' describe('createWslCliReconciliationStartupBarrier', () => { - it('resolves as soon as reconciliation finishes', async () => { - vi.useFakeTimers() - let resolveReconciliation!: () => void - - try { - const reconciliation = new Promise((resolve) => { - resolveReconciliation = resolve - }) - const barrier = createWslCliReconciliationStartupBarrier(reconciliation) - let barrierSettled = false - void barrier.then(() => { - barrierSettled = true - }) - - await vi.advanceTimersByTimeAsync(1) - expect(barrierSettled).toBe(false) - - resolveReconciliation() - await expect(barrier).resolves.toBeUndefined() - expect(vi.getTimerCount()).toBe(0) - } finally { - vi.useRealTimers() - } - }) - it('fails open immediately when reconciliation rejects before the budget expires', async () => { vi.useFakeTimers() let rejectReconciliation!: (error: Error) => void @@ -89,29 +64,4 @@ describe('createWslCliReconciliationStartupBarrier', () => { vi.useRealTimers() } }) - - it('preserves eventual reconciliation error reporting after the budget expires', async () => { - vi.useFakeTimers() - let rejectReconciliation!: (error: Error) => void - const reportedErrors: string[] = [] - - try { - const reconciliation = new Promise((_resolve, reject) => { - rejectReconciliation = reject - }).catch((error) => { - reportedErrors.push(error instanceof Error ? error.message : String(error)) - }) - const barrier = createWslCliReconciliationStartupBarrier(reconciliation, { timeoutMs: 10 }) - - await vi.advanceTimersByTimeAsync(10) - await expect(barrier).resolves.toBeUndefined() - expect(reportedErrors).toEqual([]) - - rejectReconciliation(new Error('WSL discovery failed')) - await reconciliation - expect(reportedErrors).toEqual(['WSL discovery failed']) - } finally { - vi.useRealTimers() - } - }) }) diff --git a/src/main/updater-agent-state-rules-release.test.ts b/src/main/updater-agent-state-rules-release.test.ts deleted file mode 100644 index 4ca6643c22d..00000000000 --- a/src/main/updater-agent-state-rules-release.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { getVersionChannel } from '../shared/release-channel' -import { installNetRequestFetchAdapter } from './updater-net-request.fixture' - -const { netFetchMock, netRequestMock } = vi.hoisted(() => ({ - netFetchMock: vi.fn(), - netRequestMock: vi.fn() -})) - -vi.mock('electron', () => ({ net: { fetch: netFetchMock, request: netRequestMock } })) -vi.mock('./updater-release-api-token', () => ({ - resolveReleaseApiToken: async () => null, - rejectReleaseApiToken: () => {} -})) -vi.mock('./git/gh-rate-limit-breaker', () => ({ - getGhRateLimitBlockedUntilMs: () => null, - recordGhPrimaryRateLimit: () => {} -})) - -const { fetchNewerReleaseTag } = await import('./updater-prerelease-feed') -const { listReleaseBuilds, resolveTargetBuild } = await import('./updater-release-builds') - -const RULES_TAGS = ['agent-state-rules-engine-1-next', 'agent-state-rules-engine-1-stable'] -const APP_TAGS = ['v1.4.3-rc.1', 'v1.4.2', 'v1.4.2-rc.4', 'v1.4.1'] - -function atomFeed(tags: readonly string[]): string { - const entries = tags - .map( - (tag) => - `${tag}` - ) - .join('') - return `${entries}` -} - -function serveFeed(tags: readonly string[]): void { - netFetchMock.mockImplementation((url: string) => { - if (url === 'https://github.com/stablyai/orca/releases.atom') { - return Promise.resolve({ ok: true, text: () => Promise.resolve(atomFeed(tags)) }) - } - const manifest = url.match(/\/releases\/download\/v([^/]+)\/latest(?:-[a-z0-9-]+)?\.yml$/) - if (manifest) { - const version = manifest[1] - return Promise.resolve({ - ok: true, - status: 200, - text: () => - Promise.resolve( - `version: ${version}\npath: Orca-${version}.zip\nfiles:\n - url: Orca-${version}.zip\n` - ) - }) - } - return Promise.resolve({ ok: true, status: 200, text: () => Promise.resolve('') }) - }) -} - -function apiRelease(tag: string, assets: readonly string[]) { - return { - tag_name: tag, - draft: false, - published_at: '2026-10-01T00:00:00Z', - html_url: `https://github.com/stablyai/orca/releases/tag/${tag}`, - assets: assets.map((name) => ({ name })) - } -} - -const APP_ASSETS = [ - 'latest-mac.yml', - 'orca-macos-arm64.dmg', - 'latest.yml', - 'orca-windows-setup.exe' -] - -beforeEach(() => { - netFetchMock.mockReset() - netRequestMock.mockReset() - installNetRequestFetchAdapter(netRequestMock, netFetchMock) -}) - -describe('agent state rules releases and the app updater', () => { - it('leave the RC prerelease feed resolving to the same app tag', async () => { - serveFeed(APP_TAGS) - const withoutRules = await fetchNewerReleaseTag('1.4.2-rc.4') - serveFeed([...RULES_TAGS, ...APP_TAGS]) - expect(await fetchNewerReleaseTag('1.4.2-rc.4')).toBe(withoutRules) - expect(withoutRules).toBe('v1.4.3-rc.1') - }) - - it('leave the stable check resolving to the same app tag', async () => { - serveFeed([...RULES_TAGS, ...APP_TAGS]) - expect(await fetchNewerReleaseTag('1.4.1', { includePrerelease: false })).toBe('v1.4.2') - }) - - it.each(['stable', 'rc'] as const)('never appear in the %s build picker', async (channel) => { - netFetchMock.mockResolvedValue({ - ok: true, - status: 200, - headers: new Headers(), - json: () => - Promise.resolve([ - ...RULES_TAGS.map((tag) => apiRelease(tag, ['agent-state-rules.json'])), - ...APP_TAGS.map((tag) => apiRelease(tag, APP_ASSETS)) - ]) - }) - const builds = await listReleaseBuilds(channel, 'darwin') - expect(builds.map((build) => build.tag).filter((tag) => RULES_TAGS.includes(tag))).toEqual([]) - expect(builds.length).toBeGreaterThan(0) - }) - - it('cannot be pinned as a build or read as any app channel', () => { - for (const tag of RULES_TAGS) { - expect(getVersionChannel(tag)).toBeNull() - expect(() => resolveTargetBuild('stable', tag)).toThrow('not a valid release tag') - } - }) -}) diff --git a/src/main/updater.fallback.test.ts b/src/main/updater.fallback.test.ts deleted file mode 100644 index f9a255eae7c..00000000000 --- a/src/main/updater.fallback.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { UpdateStatus } from '../shared/update-status-types' -import { - compareVersions, - isBenignCheckFailure, - isMissingUpdateManifestFailure, - isReleaseAssetsPublishingFailure, - isPrereleaseVersion, - statusesEqual -} from './updater-fallback' - -describe('compareVersions', () => { - it('compares prerelease and build semver strings correctly', () => { - expect(compareVersions('1.0.70-rc.1', '1.0.69')).toBeGreaterThan(0) - expect(compareVersions('1.0.70', '1.0.70-rc.1')).toBeGreaterThan(0) - expect(compareVersions('1.0.70+build.5', '1.0.70')).toBe(0) - expect(compareVersions('v1.0.70-beta.2', '1.0.70-beta.1')).toBeGreaterThan(0) - }) -}) - -describe('isPrereleaseVersion', () => { - it('recognises RC, beta, and alpha variants', () => { - expect(isPrereleaseVersion('1.3.17-rc.1')).toBe(true) - expect(isPrereleaseVersion('v1.3.17-rc.2')).toBe(true) - expect(isPrereleaseVersion('1.0.0-beta.5')).toBe(true) - expect(isPrereleaseVersion('2.1.0-alpha')).toBe(true) - }) - - it('returns false for stable releases and unparseable values', () => { - expect(isPrereleaseVersion('1.3.17')).toBe(false) - expect(isPrereleaseVersion('v1.3.17')).toBe(false) - expect(isPrereleaseVersion('1.3.17+build.5')).toBe(false) - expect(isPrereleaseVersion('not-a-version')).toBe(false) - }) -}) - -describe('isMissingUpdateManifestFailure', () => { - it('matches platform manifest 404s but not generic network failures', () => { - expect( - isMissingUpdateManifestFailure( - 'Cannot find channel "latest-mac.yml" update info: HttpError: 404' - ) - ).toBe(true) - expect(isMissingUpdateManifestFailure('net::ERR_FAILED')).toBe(false) - expect(isMissingUpdateManifestFailure('Unable to find latest version on GitHub')).toBe(false) - }) -}) - -describe('isBenignCheckFailure', () => { - it('treats in-progress release asset publication as retryable', () => { - expect(isBenignCheckFailure('Latest release assets are still publishing')).toBe(true) - }) -}) - -describe('statusesEqual', () => { - const recovery = { - kind: 'linux-package-install', - packageType: 'deb', - reason: 'authentication-agent-unavailable', - version: '1.0.61' - } as const - const withRecovery: UpdateStatus = { state: 'error', message: 'install failed', recovery } - const withoutRecovery: UpdateStatus = { state: 'error', message: 'install failed' } - - it('does not dedupe a recovery-carrying error against the same message without recovery', () => { - expect(statusesEqual(withRecovery, withoutRecovery)).toBe(false) - expect(statusesEqual(withoutRecovery, withRecovery)).toBe(false) - }) - - it('separates recovery statuses that differ only in package type, reason, or version', () => { - expect( - statusesEqual(withRecovery, { - ...withRecovery, - recovery: { ...recovery, packageType: 'rpm' } - }) - ).toBe(false) - expect( - statusesEqual(withRecovery, { - ...withRecovery, - recovery: { ...recovery, reason: 'package-install-failed' } - }) - ).toBe(false) - expect( - statusesEqual(withRecovery, { ...withRecovery, recovery: { ...recovery, version: '1.0.62' } }) - ).toBe(false) - expect(statusesEqual(withRecovery, { ...withRecovery })).toBe(true) - }) - - it('delivers a same-valued recovery recaptured for a new package cycle', () => { - expect(statusesEqual(withRecovery, { ...withRecovery, recovery: { ...recovery } })).toBe(false) - }) - - it('separates generic errors by version and retryability', () => { - const error: UpdateStatus = { - state: 'error', - message: 'package unavailable', - version: '1.0.61', - retryable: false - } - - expect(statusesEqual(error, { ...error, version: '1.0.62' })).toBe(false) - expect(statusesEqual(error, { ...error, retryable: true })).toBe(false) - expect(statusesEqual(error, { ...error, retryAction: 'install' })).toBe(false) - expect(statusesEqual(error, { ...error })).toBe(true) - }) -}) - -describe('isReleaseAssetsPublishingFailure', () => { - it('only matches the explicit release-asset publishing sentinel', () => { - expect(isReleaseAssetsPublishingFailure('Latest release assets are still publishing')).toBe( - true - ) - expect( - isReleaseAssetsPublishingFailure('Cannot find channel "latest-mac.yml" update info: 404') - ).toBe(false) - }) -}) diff --git a/src/main/usage/agent-token-usage.test.ts b/src/main/usage/agent-token-usage.test.ts deleted file mode 100644 index e18c825212a..00000000000 --- a/src/main/usage/agent-token-usage.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { ClaudeUsageSession } from '../claude-usage/types' -import type { CodexUsageSession } from '../codex-usage/types' -import type { OpenCodeUsageSession } from '../opencode-usage/types' -import { claudeTokenSessions, codexOpenCodeTokenSessions } from './agent-token-usage' - -const claudeLocation = { - locationKey: '/private/path', - projectLabel: 'private-repo', - repoId: 'repo', - worktreeId: 'folder', - turnCount: 1, - inputTokens: 100, - outputTokens: 20, - cacheReadTokens: 30, - cacheWriteTokens: 10, - cacheWrite1hTokens: 5 -} -const claude: ClaudeUsageSession = { - sessionId: 'session', - firstTimestamp: 'private-start', - lastTimestamp: 'private-end', - model: 'private-model', - lastCwd: '/private/path', - lastGitBranch: 'private-branch', - primaryWorktreeId: 'folder', - primaryRepoId: 'repo', - turnCount: 2, - totalInputTokens: 200, - totalOutputTokens: 40, - totalCacheReadTokens: 60, - totalCacheWriteTokens: 20, - totalCacheWrite1hTokens: 10, - locationBreakdown: [claudeLocation, { ...claudeLocation, worktreeId: null }] -} -const codexLocation = { - locationKey: '/private/path', - projectLabel: 'private-repo', - repoId: 'repo', - worktreeId: 'folder', - eventCount: 1, - inputTokens: 100, - cachedInputTokens: 30, - outputTokens: 20, - reasoningOutputTokens: 5, - totalTokens: 120, - hasInferredPricing: false, - longContextInputTokens: 0, - longContextCachedInputTokens: 0, - longContextOutputTokens: 0, - estimatedCostUsd: 1 -} -const codex: CodexUsageSession = { - sessionId: 'session', - firstTimestamp: 'private-start', - lastTimestamp: 'private-end', - primaryModel: 'private-model', - hasMixedModels: false, - primaryProjectLabel: 'private-repo', - hasMixedLocations: true, - primaryWorktreeId: 'folder', - primaryRepoId: 'repo', - eventCount: 2, - totalInputTokens: 200, - totalCachedInputTokens: 60, - totalOutputTokens: 40, - totalReasoningOutputTokens: 10, - totalTokens: 240, - hasInferredPricing: false, - longContextInputTokens: 0, - longContextCachedInputTokens: 0, - longContextOutputTokens: 0, - locationBreakdown: [codexLocation, { ...codexLocation, worktreeId: null }], - modelBreakdown: [], - locationModelBreakdown: [] -} -const opencode: OpenCodeUsageSession = { - ...codex, - estimatedCostUsd: 2, - locationBreakdown: [codexLocation, { ...codexLocation, worktreeId: null }], - modelBreakdown: [], - locationModelBreakdown: [] -} - -describe('Orca token projections', () => { - it('counts Claude cache writes once including the 1-hour subset and excludes outside usage', () => { - expect(claudeTokenSessions([claude])).toEqual([ - { - providerSessionId: 'session', - input_tokens: 100, - output_tokens: 20, - cached_input_tokens: 30, - cache_write_input_tokens: 10 - } - ]) - }) - it.each([codex, opencode])('separates cache reads from inclusive input counts', (session) => { - expect(codexOpenCodeTokenSessions([session])).toEqual([ - { - providerSessionId: 'session', - input_tokens: 70, - output_tokens: 20, - cached_input_tokens: 30, - cache_write_input_tokens: 0 - } - ]) - }) - it('does not fall back to unscoped session totals when attribution is missing', () => { - expect(claudeTokenSessions([{ ...claude, locationBreakdown: [] }])).toEqual([]) - expect(codexOpenCodeTokenSessions([{ ...codex, locationBreakdown: [] }])).toEqual([]) - expect( - codexOpenCodeTokenSessions([ - { ...codex, locationBreakdown: [{ ...codexLocation, worktreeId: null }] } - ]) - ).toEqual([]) - }) -}) diff --git a/src/main/worktree-removal-authority.test.ts b/src/main/worktree-removal-authority.test.ts deleted file mode 100644 index 4d8229f3f1f..00000000000 --- a/src/main/worktree-removal-authority.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - canCleanupUnregisteredOrcaWorktreeDirectory, - isWorktreePathMissing, - stripOrcaProvenanceMetaUpdates -} from './worktree-removal-safety' -import type { WorktreeMeta } from '../shared/worktree/meta-types' - -describe('isWorktreePathMissing', () => { - it('recognizes missing-path errors from local and remote stat providers', async () => { - await expect( - isWorktreePathMissing('/missing', async () => { - throw Object.assign(new Error('missing'), { code: 'ENOENT' }) - }) - ).resolves.toBe(true) - - await expect( - isWorktreePathMissing('/missing', () => Promise.reject({ code: 'ENOTDIR' })) - ).resolves.toBe(true) - }) - - it('does not classify existing paths or unrelated stat failures as missing', async () => { - await expect(isWorktreePathMissing('/exists', async () => ({}))).resolves.toBe(false) - - await expect( - isWorktreePathMissing('/unknown', async () => { - throw new Error('permission denied') - }) - ).resolves.toBe(false) - }) -}) - -describe('canCleanupUnregisteredOrcaWorktreeDirectory', () => { - it('does not treat orcaCreatedAt alone as cleanup authority', () => { - expect( - canCleanupUnregisteredOrcaWorktreeDirectory({ - meta: { orcaCreatedAt: Date.now() } - }) - ).toBe(false) - expect( - canCleanupUnregisteredOrcaWorktreeDirectory({ - meta: { - orcaCreatedAt: Date.now(), - orcaCreationSource: 'runtime' - } - }) - ).toBe(true) - }) - - it('accepts legacy Orca-created metadata before explicit provenance existed', () => { - expect( - canCleanupUnregisteredOrcaWorktreeDirectory({ - meta: { createdAt: Date.now() } - }) - ).toBe(true) - }) - - it('does not treat creation layout metadata alone as cleanup authority', () => { - const layoutOnlyMeta: WorktreeMeta = { - displayName: '', - comment: '', - linkedIssue: null, - linkedPR: null, - linkedLinearIssue: null, - linkedGitLabMR: null, - linkedGitLabIssue: null, - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 0, - workspaceStatus: 'todo', - orcaCreationWorkspaceLayout: { path: '/orca/workspaces', nestWorkspaces: true } - } - - expect( - canCleanupUnregisteredOrcaWorktreeDirectory({ - meta: layoutOnlyMeta - }) - ).toBe(false) - }) - - it('does not trust paths without provenance or legacy metadata', () => { - expect( - canCleanupUnregisteredOrcaWorktreeDirectory({ - meta: undefined - }) - ).toBe(false) - }) -}) - -describe('stripOrcaProvenanceMetaUpdates', () => { - it('removes Orca-owned provenance fields from user metadata updates', () => { - expect( - stripOrcaProvenanceMetaUpdates({ - comment: 'keep me', - orcaCreatedAt: 123, - orcaCreationSource: 'desktop', - orcaCreationWorkspaceLayout: { path: '/workspace', nestWorkspaces: false }, - automationProvenance: { - kind: 'created-by-automation', - automationId: 'automation-1', - automationNameSnapshot: 'Nightly review', - automationRunId: 'run-1', - automationRunTitleSnapshot: 'Nightly review run', - createdAt: 123, - executionTargetType: 'local', - executionTargetId: 'local', - projectId: 'repo-1' - } - }) - ).toEqual({ comment: 'keep me' }) - }) -}) diff --git a/src/renderer/src/components/automations/AutomationsPage.setup-consent.test.tsx b/src/renderer/src/components/automations/AutomationsPage.setup-consent.test.tsx deleted file mode 100644 index f9f02c20473..00000000000 --- a/src/renderer/src/components/automations/AutomationsPage.setup-consent.test.tsx +++ /dev/null @@ -1,116 +0,0 @@ -// @vitest-environment happy-dom -import { act } from 'react' -import { describe, expect, it, vi } from 'vitest' -import { - api, - mocks, - installAutomationsPageHarness, - renderPage, - settleHostQueries, - scopedList, - runtimeHost, - addRuntimeProject, - RUNTIME_REPO_ID -} from './automations-page-test-harness' -import { makeAutomation, makeStoreState } from './automations-page-fixtures' -import { listedRows } from './automations-page-listed-items' -import { ensureHooksConfirmed } from '@/lib/ensure-hooks-confirmed' - -describe('automation retarget setup consent', () => { - installAutomationsPageHarness() - - it.each([ - [false, 'skip'], - [true, 'skip'], - [false, 'run'] - ] as const)( - 'persists resolved consent on retarget (move=%s, consent=%s)', - async (move, consent) => { - const saved = makeAutomation({ setupDecision: 'run' }) - api.automations.list.mockResolvedValue([saved]) - scopedList([saved]) - vi.mocked(ensureHooksConfirmed).mockResolvedValue(consent) - mocks.state.fetchWorktrees = async () => {} - const nextRepo = { ...makeStoreState().state.repos[0]!, id: 'repo-next', path: '/next' } - if (move) { - runtimeHost([], []) - addRuntimeProject() - const previous = mocks.callRuntimeRpc.getMockImplementation() - mocks.callRuntimeRpc.mockImplementation( - async (target: unknown, method: string, params: unknown, options: unknown) => { - if (method === 'automation.create') { - return { automation: { ...saved, id: 'replacement' } } - } - return previous?.(target, method, params, options) - } - ) - } else { - mocks.state.repos = [...mocks.state.repos, nextRepo] - mocks.repoMap.set(nextRepo.id, nextRepo) - mocks.state.projectHostSetups = [ - ...mocks.state.projectHostSetups, - { - ...mocks.state.projectHostSetups[0]!, - id: 'next-setup', - projectId: 'next-project', - repoId: nextRepo.id, - path: nextRepo.path - } - ] - } - await renderPage() - await settleHostQueries() - const row = listedRows().find((entry) => entry.automation.id === saved.id) - if (!row) { - throw new Error('Missing automation') - } - await act(async () => { - mocks.listPanel?.openEditDialog(row) - }) - if (move) { - const entry = mocks.editorDialog?.editDestination?.entries.find( - (entry) => entry.stableRef.authority.kind === 'runtime' - ) - if (!entry) { - throw new Error('Missing runtime destination') - } - await act(async () => { - mocks.editorDialog?.editDestination?.onSelect(entry.stableKey) - }) - } - await act(async () => { - mocks.editorDialog?.onProjectChange(move ? RUNTIME_REPO_ID : nextRepo.id) - }) - await act(async () => { - await mocks.editorDialog?.onSave() - }) - expect(ensureHooksConfirmed).toHaveBeenCalledOnce() - expect(mocks.toastError).not.toHaveBeenCalled() - if (move) { - const create = mocks.callRuntimeRpc.mock.calls.find( - ([, method]) => method === 'automation.create' - ) - expect(create?.[2]).toMatchObject({ setupDecision: consent }) - expect(api.automations.delete).toHaveBeenCalledWith( - expect.objectContaining({ id: saved.id, expectedOwner: { selector: { kind: 'self' } } }) - ) - } else { - expect(api.automations.update).toHaveBeenCalledWith( - expect.objectContaining({ - id: saved.id, - expectedOwner: { selector: { kind: 'self' } }, - updates: expect.objectContaining( - consent === 'skip' ? { setupDecision: 'skip' } : { repo: 'id:repo-next' } - ) - }) - ) - if (consent === 'run') { - expect( - api.automations.update.mock.calls[0]?.[0].updates.setupDecision ?? saved.setupDecision - ).toBe('run') - } - expect(api.automations.create).not.toHaveBeenCalled() - } - } - ) -}) diff --git a/src/renderer/src/components/dashboard-popout/preview-terminal-snapshot-replay.test.ts b/src/renderer/src/components/dashboard-popout/preview-terminal-snapshot-replay.test.ts index bcd5c97b043..f6c0b52b3f1 100644 --- a/src/renderer/src/components/dashboard-popout/preview-terminal-snapshot-replay.test.ts +++ b/src/renderer/src/components/dashboard-popout/preview-terminal-snapshot-replay.test.ts @@ -30,22 +30,6 @@ function apply( // Snapshot ANSI omits kitty pushes; restore proven metadata in order before the IME forwarder reads it. describe('replayPreviewConnectionSnapshot', () => { - it('adopts flags the snapshot owner proved, before any live output', () => { - const { modes } = apply({ kittyKeyboardFlags: 8 }) - expect(modes.flags).toBe(8) - expect(modes.snapshotFlags).toBe(8) - }) - - it('leaves the mirror unproven and at the raw-text fallback when the field is absent', () => { - const { modes } = apply({ data: 'plain output' }) - expect(modes.flags).toBe(0) - expect(modes.snapshotFlags).toBeUndefined() - - // A later explicit negotiation still upgrades it. - modes.scan('\x1b[>8u') - expect(modes.flags).toBe(8) - }) - it('replaces an earlier 8 when a replacement snapshot proves 0', () => { const modes = new TerminalKittyKeyboardModeTracker() apply({ kittyKeyboardFlags: 8 }, [], modes) @@ -64,22 +48,6 @@ describe('replayPreviewConnectionSnapshot', () => { expect(modes.snapshotFlags).toBe(8) }) - it('carries nothing across a flagless snapshot when the mirror never proved state', () => { - // Fresh default zero is not proof of this PTY’s negotiation state. - const modes = new TerminalKittyKeyboardModeTracker() - apply({ data: 'first frame' }, [], modes) - apply({ data: 'second frame' }, [], modes) - expect(modes.flags).toBe(0) - expect(modes.snapshotFlags).toBeUndefined() - }) - - it('keeps proven flags across a replacement snapshot whose ANSI carries no kitty bytes', () => { - const modes = new TerminalKittyKeyboardModeTracker() - apply({ data: 'first frame' }, [], modes) - apply({ data: 'second frame', cols: 90, rows: 30, kittyKeyboardFlags: 8 }, [], modes) - expect(modes.flags).toBe(8) - }) - it('restores onto the screen the snapshot scan selected', () => { const { modes } = apply({ data: '\x1b[?1049h', kittyKeyboardFlags: 8 }) expect(modes.isAlternateScreen).toBe(true) @@ -101,16 +69,4 @@ describe('replayPreviewConnectionSnapshot', () => { expect(modes.flags).toBe(0) expect(modes.snapshotFlags).toBe(0) }) - - it('writes scrollback, frame, kitty restore, escape tail, then replay, in that order', () => { - const { written } = apply( - { - scrollbackAnsi: 'history', - data: 'frame', - pendingEscapeTailAnsi: '\x1b[' - }, - [{ data: 'tail', mode: 'live' }] - ) - expect(written).toEqual(['history', 'frame', '\x1b[<99u', '\x1b[', 'tail']) - }) }) diff --git a/src/renderer/src/components/editor/combined-diff/remember-view/combined-diff-view-cache.test.ts b/src/renderer/src/components/editor/combined-diff/remember-view/combined-diff-view-cache.test.ts index c65a35c6b94..caef1f0982e 100644 --- a/src/renderer/src/components/editor/combined-diff/remember-view/combined-diff-view-cache.test.ts +++ b/src/renderer/src/components/editor/combined-diff/remember-view/combined-diff-view-cache.test.ts @@ -55,20 +55,6 @@ describe('combined diff view succession', () => { expect(readCombinedDiffViewMemory(keyFor('tab-1'))).toBeUndefined() }) - it('keeps a still-open but inactive tab view out of reach of a second tab', () => { - viewThenUnmount('tab-1', 640) - - const nextKey = keyFor('tab-2') - const bothOpen = succession('tab-2', ['tab-1', 'tab-2']) - expect(readCombinedDiffViewMemory(nextKey, { worktreeId, succession: bothOpen })).toBe( - undefined - ) - claimCombinedDiffView(nextKey, worktreeId, bothOpen) - - expect(readCombinedDiffViewMemory(nextKey)).toBeUndefined() - expect(readCombinedDiffViewMemory(keyFor('tab-1'))?.scrollTop).toBe(640) - }) - it('never takes the view of a viewer that is still mounted', () => { const mounted = claimCombinedDiffView(keyFor('tab-1'), worktreeId, succession('tab-1')) rememberCombinedDiffView(mounted, { scrollTop: 320 }) diff --git a/src/renderer/src/components/editor/markdown-document-worktree-path-selector.test.ts b/src/renderer/src/components/editor/markdown-document-worktree-path-selector.test.ts deleted file mode 100644 index 0230113c026..00000000000 --- a/src/renderer/src/components/editor/markdown-document-worktree-path-selector.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { Worktree } from '../../../../shared/worktree/types' -import { findWorktreeById } from '@/store/slices/worktree-helpers' -import { selectMarkdownDocumentWorktreePath } from './markdown-document-worktree-path-selector' - -const PANEL_COUNT = 200 -const UPDATE_COUNT = 200 -const WORKTREE_COUNT = 100 -const ACTIVE_ID = `worktree-${WORKTREE_COUNT - 1}` - -type CountedFixture = { - readCount: () => number - replaceBackgroundWorktree: (worktrees: Worktree[], update: number) => Worktree[] - resetReadCount: () => void - worktrees: Worktree[] -} - -function createCountedFixture(): CountedFixture { - let reads = 0 - const makeWorktree = (index: number): Worktree => { - const worktree = { - path: `/worktrees/${index}` - } as Worktree - Object.defineProperty(worktree, 'id', { - enumerable: true, - get: () => { - reads += 1 - return `worktree-${index}` - } - }) - return worktree - } - return { - readCount: () => reads, - replaceBackgroundWorktree: (worktrees, update) => { - const next = [...worktrees] - const index = update % (WORKTREE_COUNT - 1) - next[index] = makeWorktree(index) - return next - }, - resetReadCount: () => { - reads = 0 - }, - worktrees: Array.from({ length: WORKTREE_COUNT }, (_, index) => makeWorktree(index)) - } -} - -describe('Markdown document worktree path selector', () => { - it('keeps mounted editors stable across worktree metadata replacements', () => { - const wholeMapFixture = createCountedFixture() - let wholeMap = { repo: wholeMapFixture.worktrees } - let wholeMapInvalidations = 0 - wholeMapFixture.resetReadCount() - - for (let update = 0; update < UPDATE_COUNT; update += 1) { - const previousMap = wholeMap - wholeMap = { - repo: wholeMapFixture.replaceBackgroundWorktree(wholeMap.repo, update) - } - for (let panel = 0; panel < PANEL_COUNT; panel += 1) { - wholeMapInvalidations += Number(previousMap !== wholeMap) - expect(findWorktreeById(wholeMap, ACTIVE_ID)?.path).toBe(`/worktrees/${WORKTREE_COUNT - 1}`) - } - } - - const scopedFixture = createCountedFixture() - let scopedMap = { repo: scopedFixture.worktrees } - const selectedPaths = Array.from({ length: PANEL_COUNT }, () => - selectMarkdownDocumentWorktreePath({ worktreesByRepo: scopedMap }, ACTIVE_ID) - ) - let scopedInvalidations = 0 - scopedFixture.resetReadCount() - - for (let update = 0; update < UPDATE_COUNT; update += 1) { - scopedMap = { - repo: scopedFixture.replaceBackgroundWorktree(scopedMap.repo, update) - } - for (let panel = 0; panel < PANEL_COUNT; panel += 1) { - const nextPath = selectMarkdownDocumentWorktreePath( - { worktreesByRepo: scopedMap }, - ACTIVE_ID - ) - scopedInvalidations += Number(selectedPaths[panel] !== nextPath) - selectedPaths[panel] = nextPath - } - } - - expect(wholeMapInvalidations).toBe(40_000) - expect(scopedInvalidations).toBe(0) - expect(wholeMapFixture.readCount()).toBe(4_000_000) - expect(scopedFixture.readCount()).toBe(20_000) - }) - - it('publishes path replacements and handles an absent worktree', () => { - const first = { id: 'active', path: '/worktrees/first' } as Worktree - const next = { id: 'active', path: '/worktrees/next' } as Worktree - - expect( - selectMarkdownDocumentWorktreePath({ worktreesByRepo: { repo: [first] } }, 'active') - ).toBe('/worktrees/first') - expect( - selectMarkdownDocumentWorktreePath({ worktreesByRepo: { repo: [next] } }, 'active') - ).toBe('/worktrees/next') - expect(selectMarkdownDocumentWorktreePath({ worktreesByRepo: {} }, null)).toBeNull() - }) -}) - -it('resolves Markdown document listing roots for non-git folder workspaces', () => { - expect( - selectMarkdownDocumentWorktreePath( - { worktreesByRepo: {}, folderWorkspaces: [{ id: 'folder-id', folderPath: '/notes' }] }, - 'folder:folder-id' - ) - ).toBe('/notes') -}) diff --git a/src/renderer/src/components/editor/rich-markdown-lowlight-cache.test.ts b/src/renderer/src/components/editor/rich-markdown-lowlight-cache.test.ts index fd944cc7c69..0b06fbbc7b8 100644 --- a/src/renderer/src/components/editor/rich-markdown-lowlight-cache.test.ts +++ b/src/renderer/src/components/editor/rich-markdown-lowlight-cache.test.ts @@ -5,18 +5,6 @@ import { createCachedLowlight } from './rich-markdown-lowlight-cache' const CODE = 'export function handler(input: string): string { return input.trim() }' describe('createCachedLowlight', () => { - it('reuses one exact-language result across 533 identical code blocks', () => { - const lowlight = createLowlight(common) - const highlight = vi.spyOn(lowlight, 'highlight') - const cached = createCachedLowlight(lowlight) - - for (let index = 0; index < 533; index += 1) { - cached.highlight('typescript', CODE) - } - - expect(highlight).toHaveBeenCalledOnce() - }) - it('matches lowlight for exact-language and automatic highlighting', () => { const stock = createLowlight(common) const cached = createCachedLowlight(createLowlight(common)) @@ -37,16 +25,6 @@ describe('createCachedLowlight', () => { ) }) - it('preserves language registration', () => { - const cached = createCachedLowlight(createLowlight()) - - cached.register('typescript', common.typescript) - - expect(cached.listLanguages()).toEqual(['typescript']) - expect(cached.registered('typescript')).toBe(true) - expect(cached.highlight('typescript', CODE).children).not.toHaveLength(0) - }) - it('keys explicit language, automatic mode, and supported options independently', () => { const lowlight = createLowlight(common) const highlight = vi.spyOn(lowlight, 'highlight') diff --git a/src/renderer/src/components/editor/rich-markdown-mixed-task-lists.test.ts b/src/renderer/src/components/editor/rich-markdown-mixed-task-lists.test.ts index 5ca4c1f179f..5d5565ca37e 100644 --- a/src/renderer/src/components/editor/rich-markdown-mixed-task-lists.test.ts +++ b/src/renderer/src/components/editor/rich-markdown-mixed-task-lists.test.ts @@ -90,27 +90,4 @@ describe('mixed literal checkbox markers and task lists', () => { editor.destroy() } }) - - it.each([ - ['- [ ]\n- [x] Later\n- plain', [{ text: 'Later', checked: true }]], - ['- [ ]\n- plain', []] - ])('does not invent tasks from bare markers in %j', (source, expected) => { - const editor = openMarkdown(source) - try { - editor.state.doc.check() - expect(taskItems(editor)).toEqual(expected) - } finally { - editor.destroy() - } - }) - - it('keeps a checkbox followed by a non-breaking space as a task', () => { - const editor = openMarkdown('- literal\n- [x]\u00a0Later') - try { - editor.state.doc.check() - expect(taskItems(editor)).toEqual([{ text: 'Later', checked: true }]) - } finally { - editor.destroy() - } - }) }) diff --git a/src/renderer/src/components/error-boundaries/react-185-bystander-attribution.test.tsx b/src/renderer/src/components/error-boundaries/react-185-bystander-attribution.test.tsx deleted file mode 100644 index 6c584fbeb8c..00000000000 --- a/src/renderer/src/components/error-boundaries/react-185-bystander-attribution.test.tsx +++ /dev/null @@ -1,116 +0,0 @@ -// @vitest-environment happy-dom - -import { act, type ReactElement } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -import { RecoverableRenderErrorBoundary } from './RecoverableRenderErrorBoundary' -import { RichMarkdownErrorBoundary } from '@/components/editor/RichMarkdownErrorBoundary' -import { clearReactErrorBoundaryReportingForTest } from '@/lib/react-error-boundary-reporting' - -const mocks = vi.hoisted(() => ({ recordRendererError: vi.fn() })) - -vi.mock('@/store', () => ({ - useAppStore: { getState: () => ({}) } -})) - -globalThis.IS_REACT_ACT_ENVIRONMENT = true - -const REACT_185_PRODUCTION = - 'Minified React error #185; visit https://react.dev/errors/185 for the full message.' -const UNRELATED_RENDER_ERROR = - 'Minified React error #310; visit https://react.dev/errors/310 for the full message.' - -// Every reporting boundary must stamp #185, not just the one nearest the runaway fiber. -const BOUNDARIES: { name: string; boundaryId: string; render: () => ReactElement }[] = [ - { - name: 'RecoverableRenderErrorBoundary', - boundaryId: 'page.automations', - render: () => ( - - - - ) - }, - { - name: 'RichMarkdownErrorBoundary', - boundaryId: 'editor.rich-markdown', - render: () => ( - - - - ) - } -] - -let thrownError = new Error('unset') - -function Broken(): ReactElement { - throw thrownError -} - -describe('React #185 bystander attribution', () => { - let root: Root | null = null - let container: HTMLDivElement | null = null - let consoleError: ReturnType - - beforeEach(() => { - clearReactErrorBoundaryReportingForTest() - mocks.recordRendererError.mockReset() - mocks.recordRendererError.mockResolvedValue({ ok: true, deduped: false }) - consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined) - vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) - window.api = { - crashReports: { recordRendererError: mocks.recordRendererError } - } as unknown as typeof window.api - }) - - afterEach(() => { - if (root) { - act(() => root?.unmount()) - } - container?.remove() - root = null - container = null - consoleError.mockRestore() - vi.unstubAllGlobals() - }) - - async function renderThrowing(element: ReactElement, message: string): Promise { - thrownError = new Error(message) - container = document.createElement('div') - document.body.appendChild(container) - root = createRoot(container) - await act(async () => { - root?.render(element) - }) - } - - for (const boundary of BOUNDARIES) { - it(`stamps unreliable attribution from ${boundary.name}`, async () => { - await renderThrowing(boundary.render(), REACT_185_PRODUCTION) - - expect(mocks.recordRendererError).toHaveBeenCalledWith( - expect.objectContaining({ boundaryId: boundary.boundaryId, attribution: 'unreliable' }) - ) - }) - - it(`leaves unrelated render errors unattributed from ${boundary.name}`, async () => { - await renderThrowing(boundary.render(), UNRELATED_RENDER_ERROR) - - expect(mocks.recordRendererError).toHaveBeenCalledTimes(1) - expect(mocks.recordRendererError.mock.calls[0]?.[0]).not.toHaveProperty('attribution') - }) - } - - it('stamps the development "Maximum update depth exceeded" message', async () => { - await renderThrowing( - BOUNDARIES[0]!.render(), - 'Maximum update depth exceeded. This can happen when a component repeatedly calls setState.' - ) - - expect(mocks.recordRendererError).toHaveBeenCalledWith( - expect.objectContaining({ attribution: 'unreliable' }) - ) - }) -}) diff --git a/src/renderer/src/components/native-chat/native-chat-observed-send.test.ts b/src/renderer/src/components/native-chat/native-chat-observed-send.test.ts deleted file mode 100644 index 0f9c9d02822..00000000000 --- a/src/renderer/src/components/native-chat/native-chat-observed-send.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -const io = vi.hoisted(() => ({ write: vi.fn(), verified: vi.fn() })) -vi.mock('@/runtime/runtime-terminal-inspection', () => ({ - sendRuntimePtyInput: io.write -})) -vi.mock('@/runtime/runtime-terminal-verified-input', () => ({ - sendRuntimePtyInputVerified: io.verified -})) -import { - sendNativeChatMessage, - resetNativeChatPtySendQueuesForTests -} from './native-chat-runtime-send' -import { - buildNativeChatPasteBytes, - NATIVE_CHAT_SUBMIT -} from '../../../../shared/native-chat-paste-bytes' -beforeEach(() => { - vi.useFakeTimers() - resetNativeChatPtySendQueuesForTests() - io.write.mockReset().mockReturnValue(true) - io.verified.mockReset().mockResolvedValue(true) -}) -afterEach(() => { - resetNativeChatPtySendQueuesForTests() - vi.useRealTimers() -}) -it('observes a refused write, skips Enter, and releases the queue for the next user action', async () => { - const rejected = vi.fn() - io.verified.mockResolvedValueOnce(false) - sendNativeChatMessage('pane', 'refused', { onWriteRejected: rejected }) - await vi.advanceTimersByTimeAsync(1000) - expect(rejected).toHaveBeenCalledOnce() - expect(io.verified.mock.calls.map((call) => call[1])).toEqual([ - buildNativeChatPasteBytes('refused') - ]) - sendNativeChatMessage('pane', 'next', { onWriteRejected: rejected }) - await vi.advanceTimersByTimeAsync(1000) - expect(io.verified.mock.calls.map((call) => call[1])).toEqual([ - buildNativeChatPasteBytes('refused'), - buildNativeChatPasteBytes('next'), - NATIVE_CHAT_SUBMIT - ]) -}) -it('reports a lost acknowledgment once as unconfirmed, never as rejection, and still submits', async () => { - const rejected = vi.fn() - const unconfirmed = vi.fn() - io.verified.mockRejectedValueOnce(new Error('lost acknowledgment')) - io.verified.mockRejectedValueOnce(new Error('lost acknowledgment')) - sendNativeChatMessage('pane', 'uncertain', { - onWriteRejected: rejected, - onWriteUnconfirmed: unconfirmed - }) - await vi.advanceTimersByTimeAsync(120000) - expect(rejected).not.toHaveBeenCalled() - expect(unconfirmed).toHaveBeenCalledOnce() - expect(io.verified.mock.calls.map((call) => call[1])).toEqual([ - buildNativeChatPasteBytes('uncertain'), - NATIVE_CHAT_SUBMIT - ]) -}) -it('serializes rapid sends through their acknowledged Enter and preserves the paste delay', async () => { - const rejected = vi.fn() - sendNativeChatMessage('pane', 'one', { onWriteRejected: rejected }) - sendNativeChatMessage('pane', 'two', { onWriteRejected: rejected }) - await vi.advanceTimersByTimeAsync(499) - expect(io.verified).toHaveBeenCalledOnce() - await vi.advanceTimersByTimeAsync(501) - expect(io.verified.mock.calls.map((call) => call[1])).toEqual([ - buildNativeChatPasteBytes('one'), - NATIVE_CHAT_SUBMIT, - buildNativeChatPasteBytes('two'), - NATIVE_CHAT_SUBMIT - ]) -}) - -it.each(['accepted', 'refused', 'unknown'] as const)( - 'reports a pasted answer as acknowledged only after %s settlement', - async (outcome) => { - const settled = vi.fn() - if (outcome === 'refused') { - io.verified.mockResolvedValueOnce(false) - } else if (outcome === 'unknown') { - io.verified.mockRejectedValueOnce(new Error('lost acknowledgment')) - } - sendNativeChatMessage('pane', 'answer', { onDeliverySettled: settled }) - expect(settled).not.toHaveBeenCalled() - await vi.advanceTimersByTimeAsync(1000) - expect(settled).toHaveBeenCalledExactlyOnceWith(outcome === 'accepted') - expect(io.verified.mock.calls.map((call) => call[1])).toEqual( - outcome === 'refused' - ? [buildNativeChatPasteBytes('answer')] - : [buildNativeChatPasteBytes('answer'), NATIVE_CHAT_SUBMIT] - ) - } -) - -it('ignores a pasted answer acknowledgment after cancellation', async () => { - let acknowledge: (accepted: boolean) => void = () => {} - io.verified.mockReturnValueOnce( - new Promise((resolve) => { - acknowledge = resolve - }) - ) - const settled = vi.fn() - const handle = sendNativeChatMessage('pane', 'answer', { onDeliverySettled: settled }) - handle.cancel() - acknowledge(true) - await vi.advanceTimersByTimeAsync(1000) - expect(settled).not.toHaveBeenCalled() - expect(io.verified).toHaveBeenCalledOnce() -}) diff --git a/src/renderer/src/components/native-chat/native-chat-working-status-shared-clock.test.tsx b/src/renderer/src/components/native-chat/native-chat-working-status-shared-clock.test.tsx index 6491f8ac324..3edf7425e1d 100644 --- a/src/renderer/src/components/native-chat/native-chat-working-status-shared-clock.test.tsx +++ b/src/renderer/src/components/native-chat/native-chat-working-status-shared-clock.test.tsx @@ -3,7 +3,7 @@ import { act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { formatNativeChatDuration, NativeChatWorkingStatus } from './NativeChatWorkingStatus' +import { NativeChatWorkingStatus } from './NativeChatWorkingStatus' let container: HTMLDivElement let root: Root @@ -52,35 +52,6 @@ afterEach(() => { }) describe('native chat working status elapsed clock', () => { - it.each([ - [0, '0s'], - [59, '59s'], - [60, '1m 0s'], - [69, '1m 9s'], - [3_725, '1h 2m 5s'] - ])('formats %s seconds as %s', (seconds, expected) => { - expect(formatNativeChatDuration(seconds)).toBe(expected) - }) - - it('renders the compact duration in the completed status label', () => { - act(() => { - root.render() - }) - - expect(elapsedLabels()).toEqual(['Worked for 1m 9s']) - }) - - it('announces responding only while the turn runs', () => { - const status = (): Element | null => container.querySelector('[data-native-chat-turn-status]') - renderTurns(1, 1_000_000) - expect(status()?.getAttribute('aria-label')).toBe('Agent is responding') - - act(() => { - root.render() - }) - expect(status()?.hasAttribute('aria-label')).toBe(false) - }) - it('collapses every in-flight turn onto one shared visibility-gated timer', () => { renderTurns(3, 1_000_000) @@ -106,11 +77,4 @@ describe('native chat working status elapsed clock', () => { expect(elapsedLabels()).toEqual(['Working for 1m 3s']) expect(vi.getTimerCount()).toBe(1) }) - - it('holds no timer for a completed turn', () => { - act(() => { - root.render() - }) - expect(vi.getTimerCount()).toBe(0) - }) }) diff --git a/src/renderer/src/components/native-chat/use-structured-agent-session-delivery-notices.test.tsx b/src/renderer/src/components/native-chat/use-structured-agent-session-delivery-notices.test.tsx deleted file mode 100644 index f50bfed83af..00000000000 --- a/src/renderer/src/components/native-chat/use-structured-agent-session-delivery-notices.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -// @vitest-environment happy-dom - -import { cleanup, renderHook } from '@testing-library/react' -import { afterEach, expect, it } from 'vitest' -import type { AgentJournalSubmission } from '../../../../shared/agent-session/journal/agent-session-journal-types' -import { DISPATCH_REJECTED_CANCELLED } from '../../../../shared/agent-session/requests/structured-agent-session-dispatch-rejection' -import { useStructuredAgentSessionDeliveryNotices } from './use-structured-agent-session-delivery-notices' - -afterEach(cleanup) - -const EMPTY: never[] = [] - -function rejected( - clientMessageId: string, - kind: 'hostRestarted' | 'notDelivered' -): AgentJournalSubmission { - return { - clientMessageId, - fence: 1, - payloadFingerprint: clientMessageId, - dispatchState: 'rejected', - providerItemId: null, - reason: null, - rejection: { kind }, - submittedAt: 1, - resolvedAt: 2 - } -} - -function accepted(clientMessageId: string): AgentJournalSubmission { - return { - clientMessageId, - fence: 1, - payloadFingerprint: clientMessageId, - dispatchState: 'accepted', - providerItemId: `provider-${clientMessageId}`, - reason: null, - submittedAt: 3, - resolvedAt: 4 - } -} - -function withdrawn(clientMessageId: string): AgentJournalSubmission { - return { - clientMessageId, - fence: 1, - payloadFingerprint: clientMessageId, - dispatchState: 'rejected', - providerItemId: null, - reason: DISPATCH_REJECTED_CANCELLED, - rejection: { kind: 'cancelled' }, - submittedAt: 1, - resolvedAt: 2 - } -} - -it('keeps the same notices across batches in a chat whose only rejection a Stop withdrew', () => { - const { result, rerender } = renderHook( - ({ submissions }: { submissions: readonly AgentJournalSubmission[] }) => - useStructuredAgentSessionDeliveryNotices({ - pending: EMPTY, - submissions, - journalItems: EMPTY, - agentName: 'Claude' - }), - { initialProps: { submissions: [withdrawn('stopped')] } } - ) - const first = result.current - - rerender({ submissions: [withdrawn('stopped')] }) - - expect(result.current).toBe(first) - expect(result.current.size).toBe(0) -}) - -function renderNotices(submissions: readonly AgentJournalSubmission[]) { - return renderHook( - ({ submissions: current }: { submissions: readonly AgentJournalSubmission[] }) => - useStructuredAgentSessionDeliveryNotices({ - pending: EMPTY, - submissions: current, - journalItems: EMPTY, - agentName: 'Claude' - }), - { initialProps: { submissions } } - ) -} - -it('keeps the same notices when a batch leaves every not-sent message as it was', () => { - const { result, rerender } = renderNotices([rejected('lost', 'hostRestarted')]) - const first = result.current - expect(first.size).toBe(1) - - rerender({ submissions: [rejected('lost', 'hostRestarted'), accepted('next')] }) - - expect(result.current).toBe(first) -}) - -it('keeps the notice of a row that did not change when another one does', () => { - const { result, rerender } = renderNotices([rejected('lost', 'hostRestarted')]) - const lost = result.current.get('orca:lost') - - rerender({ - submissions: [rejected('lost', 'hostRestarted'), rejected('other', 'notDelivered')] - }) - - expect(result.current.size).toBe(2) - expect(result.current.get('orca:lost')).toBe(lost) -}) diff --git a/src/renderer/src/components/right-sidebar/ai-vault-session-deletability.test.ts b/src/renderer/src/components/right-sidebar/ai-vault-session-deletability.test.ts deleted file mode 100644 index 4ba3f922029..00000000000 --- a/src/renderer/src/components/right-sidebar/ai-vault-session-deletability.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { aiVaultSessionDeleteBlockedReason } from './ai-vault-session-deletability' - -// Without a loaded catalog, translate() returns English; pin copy and gate ordering together. -const NON_LOCAL = 'Only sessions on this device can be deleted.' -const SYNTHETIC = "This session can't be deleted from Orca." -const HOST = { label: 'Build box', deleteSupported: null } - -const localGeminiSession = { - agent: 'gemini' as const, - executionHostId: 'local' as const, - filePath: '/home/user/.gemini/sessions/log.jsonl' -} - -describe('aiVaultSessionDeleteBlockedReason', () => { - it('offers Delete for a deletable agent on a local, real path', () => { - expect(aiVaultSessionDeleteBlockedReason(localGeminiSession, HOST)).toBeNull() - }) - - it('offers Delete for a directory-shaped agent (claude)', () => { - expect( - aiVaultSessionDeleteBlockedReason( - { - agent: 'claude', - executionHostId: 'local', - filePath: '/home/user/.claude/projects/-proj/sess-1.jsonl' - }, - HOST - ) - ).toBeNull() - }) - - it('offers Delete for a session on a paired or SSH-managed server, routed to that server', () => { - for (const deleteSupported of [true, null]) { - expect( - aiVaultSessionDeleteBlockedReason( - { ...localGeminiSession, executionHostId: 'runtime:gpu-box' }, - { label: 'GPU box', deleteSupported } - ) - ).toBeNull() - } - }) - - it('asks to update a server that predates remote delete, naming it', () => { - expect( - aiVaultSessionDeleteBlockedReason( - { ...localGeminiSession, executionHostId: 'runtime:gpu-box' }, - { label: 'GPU box', deleteSupported: false } - ) - ).toBe('Update GPU box to delete its sessions from here.') - }) - - it('blocks an unroutable owner such as a legacy SSH relay or a contested workspace', () => { - for (const executionHostId of ['ssh:dev-box', 'runtime:unresolved-owner'] as const) { - expect( - aiVaultSessionDeleteBlockedReason({ ...localGeminiSession, executionHostId }, HOST) - ).toBe(NON_LOCAL) - } - }) - - it('blocks a synthetic OpenCode SQLite row identity on any host', () => { - for (const executionHostId of ['local', 'runtime:gpu-box'] as const) { - expect( - aiVaultSessionDeleteBlockedReason( - { - agent: 'opencode', - executionHostId, - filePath: '/home/user/.opencode/db.sqlite#sess_123' - }, - { label: 'GPU box', deleteSupported: false } - ) - ).toBe(SYNTHETIC) - } - }) - - it('names the agent without explaining why it is unsupported', () => { - expect( - aiVaultSessionDeleteBlockedReason( - { - agent: 'opencode', - executionHostId: 'local', - filePath: '/home/user/.opencode/sessions/log.jsonl' - }, - HOST - ) - ).toBe("OpenCode sessions can't be deleted from Orca.") - }) - - it('gives a multi-cause agent (antigravity) the same single sentence', () => { - expect( - aiVaultSessionDeleteBlockedReason( - { - agent: 'antigravity', - executionHostId: 'local', - filePath: '/home/user/.antigravity/brain/conv-1/.system_generated/logs/transcript.jsonl' - }, - HOST - ) - ).toBe("Antigravity sessions can't be deleted from Orca.") - }) - - it('prioritizes the host gate over the unsupported-agent reason', () => { - expect( - aiVaultSessionDeleteBlockedReason( - { - agent: 'claude', - executionHostId: 'ssh:dev-box', - filePath: '/home/user/.claude/sessions/sess-dir/log.jsonl' - }, - HOST - ) - ).toBe(NON_LOCAL) - }) -}) diff --git a/src/renderer/src/components/right-sidebar/gitlab-mr-merge-state.test.ts b/src/renderer/src/components/right-sidebar/gitlab-mr-merge-state.test.ts deleted file mode 100644 index cccfb554eeb..00000000000 --- a/src/renderer/src/components/right-sidebar/gitlab-mr-merge-state.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { presentGitLabMRMergeState } from './gitlab-mr-merge-state' - -describe('presentGitLabMRMergeState', () => { - it('does not treat UNKNOWN as able to merge', () => { - expect( - presentGitLabMRMergeState({ - state: 'open', - status: 'success', - mergeable: 'UNKNOWN', - mergeStateStatus: 'not_approved' - }) - ).toMatchObject({ - label: 'Approval required', - directMergeAvailable: false - }) - }) - - it('labels common blocked detailed_merge_status values', () => { - expect( - presentGitLabMRMergeState({ - state: 'open', - status: 'pending', - mergeable: 'UNKNOWN', - mergeStateStatus: 'ci_still_running' - }).label - ).toBe('Checks pending') - expect( - presentGitLabMRMergeState({ - state: 'open', - status: 'success', - mergeable: 'UNKNOWN', - mergeStateStatus: 'discussions_not_resolved' - }).label - ).toBe('Unresolved threads') - }) - - // Unknown merge reasons must not hide the actionable failed pipeline behind Checking. - it('surfaces a failed pipeline when GitLab reports no usable merge reason', () => { - expect( - presentGitLabMRMergeState({ - state: 'open', - status: 'failure', - mergeable: 'UNKNOWN', - mergeStateStatus: undefined - }) - ).toMatchObject({ - label: 'Checks failed', - directMergeAvailable: false - }) - }) - - // New GitLab reason values must degrade like missing reasons instead of claiming merge readiness. - it('treats an unrecognised merge reason like an absent one', () => { - expect( - presentGitLabMRMergeState({ - state: 'open', - status: 'failure', - mergeable: 'UNKNOWN', - mergeStateStatus: 'some_future_gitlab_status' - }) - ).toMatchObject({ - label: 'Checks failed', - directMergeAvailable: false - }) - expect( - presentGitLabMRMergeState({ - state: 'open', - status: 'success', - mergeable: 'UNKNOWN', - mergeStateStatus: 'some_future_gitlab_status' - }) - ).toMatchObject({ - label: 'Checking', - directMergeAvailable: false - }) - }) - - it('still reports Checking when there is no reason and no failing pipeline', () => { - expect( - presentGitLabMRMergeState({ - state: 'open', - status: 'pending', - mergeable: 'UNKNOWN', - mergeStateStatus: undefined - }).label - ).toBe('Checking') - }) - - it('keeps an explicit GitLab reason ahead of the pipeline result', () => { - expect( - presentGitLabMRMergeState({ - state: 'open', - status: 'failure', - mergeable: 'UNKNOWN', - mergeStateStatus: 'not_approved' - }).label - ).toBe('Approval required') - }) - - it('only offers direct merge when GitLab reports MERGEABLE', () => { - expect( - presentGitLabMRMergeState({ - state: 'open', - status: 'success', - mergeable: 'MERGEABLE', - mergeStateStatus: 'mergeable' - }) - ).toMatchObject({ - label: 'Able to merge', - directMergeAvailable: true - }) - }) -}) diff --git a/src/renderer/src/components/right-sidebar/source-control-create-review-blocked-action.test.ts b/src/renderer/src/components/right-sidebar/source-control-create-review-blocked-action.test.ts deleted file mode 100644 index 5a588bfee3b..00000000000 --- a/src/renderer/src/components/right-sidebar/source-control-create-review-blocked-action.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - canClickBlockedCreateReviewReason, - resolveBlockedCreateReviewNoticeMessage -} from './source-control-create-review-blocked-action' -import type { HostedReviewCreationEligibility } from '../../../../shared/hosted-review' - -function eligibility( - overrides: Partial = {} -): HostedReviewCreationEligibility { - return { - provider: 'github', - review: null, - canCreate: false, - blockedReason: 'needs_push', - nextAction: 'push', - reviewLookupOutcome: 'not_found', - ...overrides - } -} - -describe('source-control-create-review-blocked-action', () => { - it.each(['dirty', 'default_branch', 'no_upstream', 'needs_push', 'needs_sync', 'auth_required'])( - 'allows direct Create Review clicks for %s', - (reason) => { - expect( - canClickBlockedCreateReviewReason( - reason as NonNullable - ) - ).toBe(true) - } - ) - - it.each(['detached_head', 'existing_review', 'fork_head_unsupported', 'unsupported_provider'])( - 'keeps direct Create Review clicks disabled for %s', - (reason) => { - expect( - canClickBlockedCreateReviewReason( - reason as NonNullable - ) - ).toBe(false) - } - ) - - it('returns provider-localized auth guidance for blocked direct clicks', () => { - expect( - resolveBlockedCreateReviewNoticeMessage( - eligibility({ - provider: 'gitlab', - blockedReason: 'auth_required', - nextAction: 'authenticate' - }) - ) - ).toBe( - 'Create MR failed: GitLab is not authenticated. Next step: run glab auth login in this environment.' - ) - }) - - it('returns a push-first notice for supported needs-push clicks', () => { - expect(resolveBlockedCreateReviewNoticeMessage(eligibility())).toBe( - 'Create PR failed: push this branch before creating a pull request.' - ) - }) - - it('preserves a known dirty-tree prerequisite when review lookup is unavailable', () => { - expect( - resolveBlockedCreateReviewNoticeMessage( - eligibility({ - blockedReason: 'dirty', - nextAction: 'commit', - reviewLookupOutcome: 'unavailable' - }) - ) - ).toBe('Create PR failed: commit or discard local changes before creating a pull request.') - }) - - it('preserves authentication guidance when review lookup is unavailable', () => { - expect( - resolveBlockedCreateReviewNoticeMessage( - eligibility({ - provider: 'gitlab', - blockedReason: 'auth_required', - nextAction: 'authenticate', - reviewLookupOutcome: 'unavailable' - }) - ) - ).toBe( - 'Create MR failed: GitLab is not authenticated. Next step: run glab auth login in this environment.' - ) - }) - - it('reports unavailable review lookup authority when no local blocker is known', () => { - expect( - resolveBlockedCreateReviewNoticeMessage( - eligibility({ - provider: 'gitlab', - blockedReason: null, - nextAction: null, - reviewLookupOutcome: 'unavailable' - }) - ) - ).toBe( - 'Create MR failed: Orca could not confirm whether this branch already has a merge request. Retry once the GitLab lookup succeeds.' - ) - }) - - it('returns null when the blocked reason should remain non-clickable', () => { - expect( - resolveBlockedCreateReviewNoticeMessage( - eligibility({ - blockedReason: 'existing_review', - nextAction: 'open_existing_review' - }) - ) - ).toBeNull() - }) -}) diff --git a/src/renderer/src/components/right-sidebar/source-control/review/suppressed-github-pr.test.ts b/src/renderer/src/components/right-sidebar/source-control/review/suppressed-github-pr.test.ts deleted file mode 100644 index 152f0b81e00..00000000000 --- a/src/renderer/src/components/right-sidebar/source-control/review/suppressed-github-pr.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { - HostedReviewCreationEligibility, - HostedReviewInfo -} from '../../../../../../shared/hosted-review' -import { resolveSourceControlSuppressedGitHubPRState } from './suppressed-github-pr' - -const worktree = { - linkedPR: null, - linkedGitLabMR: null, - linkedBitbucketPR: null, - linkedAzureDevOpsPR: null, - linkedGiteaPR: null, - suppressedGitHubPR: 42 -} - -const notFound: HostedReviewCreationEligibility = { - provider: 'github', - review: null, - canCreate: true, - blockedReason: null, - nextAction: null, - reviewLookupOutcome: 'not_found' -} - -const differentReview: HostedReviewInfo = { - provider: 'github', - number: 43, - title: 'Different PR', - state: 'open', - url: 'https://github.com/acme/orca/pull/43', - status: 'pending', - updatedAt: '2026-08-28T00:00:00Z', - mergeable: 'UNKNOWN' -} - -const unavailable: HostedReviewCreationEligibility = { - ...notFound, - canCreate: false, - blockedReason: 'no_upstream', - reviewLookupOutcome: 'unavailable' -} - -function resolve( - overrides: Partial[0]> = {} -) { - return resolveSourceControlSuppressedGitHubPRState({ - worktree, - isFolder: false, - provider: 'github', - hasMatchingSuppressedPR: false, - hostedReview: null, - hostedReviewCreation: null, - isHostedReviewCreationLoading: false, - hostedReviewCreationRequestFailed: false, - ...overrides - }) -} - -describe('resolveSourceControlSuppressedGitHubPRState', () => { - it('shows recovery only after cache or eligibility evidence matches the tombstone', () => { - expect(resolve({ hasMatchingSuppressedPR: true })).toEqual({ number: 42, status: 'matched' }) - expect( - resolve({ - hostedReviewCreation: { - ...notFound, - review: { number: 42, url: 'https://github.com/acme/orca/pull/42' }, - canCreate: false, - blockedReason: 'existing_review', - nextAction: 'open_existing_review', - reviewLookupOutcome: 'found' - } - }) - ).toEqual({ number: 42, status: 'matched' }) - }) - - it('suppresses transient Create PR chrome only while the marker is unresolved', () => { - expect(resolve()).toEqual({ number: 42, status: 'pending' }) - expect( - resolve({ hostedReviewCreation: notFound, isHostedReviewCreationLoading: true }) - ).toEqual({ number: 42, status: 'pending' }) - }) - - it('returns to normal chrome after stale, different, unavailable, or failed evidence', () => { - expect(resolve({ hostedReviewCreation: notFound })).toBeNull() - expect(resolve({ hostedReview: differentReview })).toBeNull() - expect(resolve({ hostedReviewCreation: unavailable })).toBeNull() - expect(resolve({ hostedReviewCreationRequestFailed: true })).toBeNull() - expect( - resolve({ - hostedReviewCreation: { - ...notFound, - review: { number: 43, url: differentReview.url }, - canCreate: false, - blockedReason: 'existing_review', - nextAction: 'open_existing_review', - reviewLookupOutcome: 'found' - } - }) - ).toBeNull() - }) - - it('does not apply GitHub recovery to folders, other providers, or invalid markers', () => { - expect(resolve({ isFolder: true })).toBeNull() - expect(resolve({ provider: 'gitlab' })).toBeNull() - expect(resolve({ worktree: { ...worktree, suppressedGitHubPR: null } })).toBeNull() - }) - - it.each([ - ['GitHub', { linkedPR: 42 }], - ['GitLab', { linkedGitLabMR: 9 }], - ['Bitbucket', { linkedBitbucketPR: 9 }], - ['Azure DevOps', { linkedAzureDevOpsPR: 9 }], - ['Gitea', { linkedGiteaPR: 9 }] - ])('keeps an explicit %s review ahead of stale GitHub suppression', (_provider, link) => { - expect(resolve({ worktree: { ...worktree, ...link } })).toBeNull() - }) -}) diff --git a/src/renderer/src/components/right-sidebar/useSourceControlSelection.test.ts b/src/renderer/src/components/right-sidebar/useSourceControlSelection.test.ts index 0078a4d5c63..897bea00c6c 100644 --- a/src/renderer/src/components/right-sidebar/useSourceControlSelection.test.ts +++ b/src/renderer/src/components/right-sidebar/useSourceControlSelection.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from 'vitest' import type { GitStatusEntry } from '../../../../shared/git-status-types' import { - getSelectionRangeKeys, reconcileSourceControlSelectionState, type FlatEntry } from './source-control/listing/use-selection' @@ -45,37 +44,6 @@ describe('reconcileSourceControlSelectionState', () => { expect(result.anchorKey).toBeNull() }) - it('still drops an anchor that is no longer visible when nothing is selected', () => { - const selectedKeys: ReadonlySet = new Set() - const result = reconcileSourceControlSelectionState({ - selectedKeys, - anchorKey: 'gone.ts', - flatEntries: [{ key: 'kept.ts' }] as unknown as Parameters< - typeof reconcileSourceControlSelectionState - >[0]['flatEntries'] - }) - - expect(result.anchorKey).toBeNull() - expect(result.selectedKeys).toBe(selectedKeys) - }) - - it('keeps selected keys and anchor identity when all keys are still visible', () => { - const flatEntries = [ - makeEntry('unstaged::a.ts', 'unstaged', 'a.ts'), - makeEntry('staged::b.ts', 'staged', 'b.ts') - ] - const selectedKeys = new Set(['unstaged::a.ts']) - - const result = reconcileSourceControlSelectionState({ - selectedKeys, - anchorKey: 'unstaged::a.ts', - flatEntries - }) - - expect(result.selectedKeys).toBe(selectedKeys) - expect(result.anchorKey).toBe('unstaged::a.ts') - }) - it('prunes stale selected keys and clears stale range anchors', () => { const flatEntries = [makeEntry('staged::b.ts', 'staged', 'b.ts')] @@ -91,27 +59,3 @@ describe('reconcileSourceControlSelectionState', () => { }) }) }) - -describe('getSelectionRangeKeys', () => { - it('selects an inclusive range across visible sections', () => { - const flatEntries = [ - makeEntry('unstaged::a.ts', 'unstaged', 'a.ts'), - makeEntry('unstaged::b.ts', 'unstaged', 'b.ts'), - makeEntry('staged::c.ts', 'staged', 'c.ts'), - makeEntry('untracked::d.ts', 'untracked', 'd.ts') - ] - - expect(getSelectionRangeKeys(flatEntries, 'unstaged::b.ts', 'untracked::d.ts')).toEqual( - new Set(['unstaged::b.ts', 'staged::c.ts', 'untracked::d.ts']) - ) - }) - - it('returns null when the anchor is no longer visible', () => { - const flatEntries = [ - makeEntry('staged::c.ts', 'staged', 'c.ts'), - makeEntry('untracked::d.ts', 'untracked', 'd.ts') - ] - - expect(getSelectionRangeKeys(flatEntries, 'unstaged::b.ts', 'untracked::d.ts')).toBeNull() - }) -}) diff --git a/src/renderer/src/components/sidebar/markdown-top-level-blocks.test.ts b/src/renderer/src/components/sidebar/markdown-top-level-blocks.test.ts index 04a9fb4fe4d..15ba686d587 100644 --- a/src/renderer/src/components/sidebar/markdown-top-level-blocks.test.ts +++ b/src/renderer/src/components/sidebar/markdown-top-level-blocks.test.ts @@ -58,44 +58,10 @@ function cut(source: string, previous: MarkdownBlockSplit | null = null): string } describe('splitMarkdownTopLevelBlocks', () => { - it('cuts at top-level blocks and loses none of the source', () => { - const blocks = cut(REPLY) - expect(blocks.join('')).toBe(REPLY) - expect(blocks.map((block) => block.trim().split('\n')[0])).toEqual([ - 'Here is the plan.', - '## Steps', - '- first item', - '1. one', - 'A loose list follows.', - '1. first', - '> a quote', - '```ts', - 'Intro to the table', - '| a | b |', - 'Setext title', - 'Closing words with `code` and **bold**.' - ]) - }) - it.each([1, 3, 7])('reaches the same cut when streamed %i characters at a time', (step) => { expect(streamed(REPLY, step)).toEqual(cut(REPLY)) }) - it.each([1, 3, 7])('numbers each block by its source line, streamed %i at a time', (step) => { - let split: MarkdownBlockSplit | null = null - for (let end = step; end < REPLY.length + step; end += step) { - split = splitMarkdownTopLevelBlocks(REPLY.slice(0, end), split) - } - for (const block of split?.blocks ?? []) { - expect(block.line).toBe(REPLY.slice(0, block.start).split('\n').length) - } - }) - - it('keeps Windows line endings inside their blocks', () => { - const source = 'one\r\n\r\ntwo\r\n\r\n- three\r\n' - expect(cut(source)).toEqual(['one\r\n\r\n', 'two\r\n\r\n', '- three\r\n']) - }) - it.each([ ['a link definition', 'See [the docs][docs].\n\nMore.\n\n[docs]: https://example.com\n'], ['a footnote', 'A claim.[^1]\n\nMore.\n\n[^1]: The source.\n'], @@ -108,10 +74,6 @@ describe('splitMarkdownTopLevelBlocks', () => { expect(streamed(source, 2)).toEqual([source]) }) - it('still cuts a document whose only HTML is a tag that holds nothing', () => { - expect(cut('| a |\n| - |\n| one
two |\n\nAfter.\n')).toHaveLength(2) - }) - it('starts over when the source is replaced rather than appended to', () => { const first = splitMarkdownTopLevelBlocks('one\n\ntwo\n', null) expect(cut('other\n\ntext\n', first)).toEqual(['other\n\n', 'text\n']) diff --git a/src/renderer/src/components/sidebar/sleep-worktree-activation-race.test.ts b/src/renderer/src/components/sidebar/sleep-worktree-activation-race.test.ts deleted file mode 100644 index c8435b80abf..00000000000 --- a/src/renderer/src/components/sidebar/sleep-worktree-activation-race.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const mocks = vi.hoisted(() => { - const state = { - activeWorktreeId: null as string | null, - setActiveWorktree: vi.fn((worktreeId: string | null) => { - state.activeWorktreeId = worktreeId - }), - shutdownWorktreeBrowsers: vi.fn().mockResolvedValue(undefined), - shutdownWorktreeTerminals: vi.fn(async (worktreeId: string) => { - for (const tab of state.tabsByWorktree[worktreeId] ?? []) { - state.ptyIdsByTabId[tab.id] = [] - } - }), - worktreesByRepo: {}, - detectedWorktreesByRepo: {}, - tabsByWorktree: {} as Record, - ptyIdsByTabId: {} as Record - } - const activateAndRevealWorktree = vi.fn() - const activateAndRevealFolderWorkspace = vi.fn() - const resumeWorkspace = vi.fn().mockResolvedValue(null) - return { - activateAndRevealFolderWorkspace, - activateAndRevealWorktree, - resumeWorkspace, - state, - toastError: vi.fn() - } -}) - -vi.mock('@/store', () => ({ - useAppStore: { - getState: () => mocks.state - } -})) - -vi.mock('@/lib/worktree-activation', () => ({ - activateAndRevealFolderWorkspace: mocks.activateAndRevealFolderWorkspace, - activateAndRevealWorktree: mocks.activateAndRevealWorktree -})) - -vi.mock('sonner', () => ({ toast: { error: mocks.toastError } })) -import { activateWorktreeFromSidebar } from '@/lib/sidebar-worktree-activation' -import { runSleepWorktrees } from './sleep-worktree-flow' -import { fakeMainSleep } from './sleep-worktree-test-main' - -describe('sleep flow vs slept-workspace activation', () => { - beforeEach(() => { - mocks.activateAndRevealWorktree.mockClear() - mocks.activateAndRevealFolderWorkspace.mockClear() - mocks.resumeWorkspace.mockClear().mockResolvedValue(null) - mocks.toastError.mockClear() - vi.stubGlobal('window', { - api: { - ephemeralVm: { - resumeWorkspace: mocks.resumeWorkspace - }, - worktrees: { sleep: fakeMainSleep().sleep } - } - }) - mocks.state.activeWorktreeId = 'wt-parent' - mocks.state.setActiveWorktree.mockClear() - mocks.state.shutdownWorktreeBrowsers.mockClear().mockResolvedValue(undefined) - mocks.state.shutdownWorktreeTerminals.mockClear().mockImplementation(async (worktreeId) => { - for (const tab of mocks.state.tabsByWorktree[worktreeId] ?? []) { - mocks.state.ptyIdsByTabId[tab.id] = [] - } - }) - mocks.state.tabsByWorktree = { - 'wt-parent': [{ id: 'tab-parent' }], - 'wt-child-1': [{ id: 'tab-child-1' }], - 'wt-child-2': [{ id: 'tab-child-2' }], - 'wt-child-3': [{ id: 'tab-child-3' }] - } - mocks.state.ptyIdsByTabId = { - 'tab-parent': ['pty-parent'], - 'tab-child-1': ['pty-child-1'], - 'tab-child-2': ['pty-child-2'], - 'tab-child-3': ['pty-child-3'] - } - }) - - it('does not leave behind a delayed parent activation after sleeping children', async () => { - await runSleepWorktrees(['wt-parent']) - - expect(mocks.state.activeWorktreeId).toBeNull() - expect(mocks.state.ptyIdsByTabId['tab-parent']).toEqual([]) - - await activateWorktreeFromSidebar('wt-parent') - expect(mocks.resumeWorkspace).toHaveBeenCalledWith({ workspaceId: 'wt-parent' }) - expect(mocks.activateAndRevealWorktree).toHaveBeenCalledTimes(1) - expect(mocks.activateAndRevealWorktree).toHaveBeenCalledWith('wt-parent', { - navigationIntent: 'user-open', - revealInSidebar: false - }) - - await runSleepWorktrees(['wt-child-1', 'wt-child-2', 'wt-child-3']) - - expect(mocks.activateAndRevealWorktree).toHaveBeenCalledTimes(1) - }) - - it('keeps the slept worktree selected when VM resume fails', async () => { - mocks.resumeWorkspace.mockRejectedValueOnce(new Error('provider unavailable')) - - await activateWorktreeFromSidebar('wt-parent') - - expect(mocks.activateAndRevealWorktree).toHaveBeenCalledWith('wt-parent', { - navigationIntent: 'user-open', - revealInSidebar: false - }) - expect(mocks.toastError).toHaveBeenCalledWith( - 'Failed to wake ephemeral VM workspace', - expect.objectContaining({ description: 'provider unavailable' }) - ) - }) -}) diff --git a/src/renderer/src/components/sidebar/workspace-hover-attachments.test.ts b/src/renderer/src/components/sidebar/workspace-hover-attachments.test.ts index df07f181e9a..a4537957cd0 100644 --- a/src/renderer/src/components/sidebar/workspace-hover-attachments.test.ts +++ b/src/renderer/src/components/sidebar/workspace-hover-attachments.test.ts @@ -54,29 +54,6 @@ describe('source-safe linked hover details', () => { expect(result.activeKey).toBeNull() expect(result.items.every((item) => !item.title)).toBe(true) }) - it('enriches legacy scalar tasks without introducing duplicate references', () => { - const result = getWorkspaceHoverAttachments( - { ...workspace, linkedPR: null, linkedIssue: 2, linkedLinearIssue: 'ENG-3' }, - { - review: null, - issue: { number: 2, title: 'GitHub task', url: 'https://github.com/acme/orca/issues/2' }, - linearIssue: { - identifier: 'ENG-3', - title: 'Linear task', - url: 'https://linear.app/acme/issue/ENG-3/title' - } - } - ) - expect(result.items).toHaveLength(2) - expect(result.items.map((item) => item.title)).toEqual(['GitHub task', 'Linear task']) - }) - it('does not attach review checks to folder workspaces', () => { - const result = getWorkspaceHoverAttachments( - { ...workspace, id: 'folder:docs', linkedItems: [local] }, - { review: { provider: 'github', number: 57, title: 'Review', url: local.url } } - ) - expect(result.activeKey).toBeNull() - }) it('does not choose between identical URLs owned by different source contexts', () => { const localHost = { ...local, @@ -102,11 +79,4 @@ describe('source-safe linked hover details', () => { ) expect(result.activeKey).toBeNull() }) - it('uses a reference label rather than a review placeholder with no fetched URL', () => { - const result = getWorkspaceHoverAttachments( - { ...workspace, linkedItems: [{ provider: 'github', type: 'pr', number: 57 }] }, - { review: { provider: 'github', number: 57, title: 'Unavailable placeholder' } } - ) - expect(result.items[0]?.title).toBeUndefined() - }) }) diff --git a/src/renderer/src/components/sidebar/worktree-list-groups-pending-creations.test.ts b/src/renderer/src/components/sidebar/worktree-list-groups-pending-creations.test.ts deleted file mode 100644 index 6590917e89e..00000000000 --- a/src/renderer/src/components/sidebar/worktree-list-groups-pending-creations.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { buildRows } from './worktree-list/grouping/build-rows.test-fixture' -import type { PendingCreationRef } from '../../../../shared/sidebar-list/row-types' -import { repo, worktree, repoMap } from './worktree-list-groups-test-fixtures' - -describe('buildRows pending creations', () => { - function makePendingCreation(creationId: string, repoId: string): PendingCreationRef { - return { creationId, repoId } - } - - it('nests a pending creation under its repo, above the repo worktrees', () => { - const rows = buildRows( - 'repo', - [worktree], - repoMap, - null, - new Set(), - undefined, - undefined, - undefined, - {}, - new Map([[worktree.id, worktree]]), - false, - undefined, - [], - new Set(), - new Map(), - new Map(), - [makePendingCreation('c1', repo.id)] - ) - - const types = rows.map((row) => row.type) - const headerIndex = types.indexOf('header') - const pendingIndex = rows.findIndex( - (row) => row.type === 'pending-creation' && row.creationId === 'c1' - ) - const itemIndex = types.indexOf('item') - expect(headerIndex).toBeGreaterThanOrEqual(0) - expect(pendingIndex).toBe(headerIndex + 1) - expect(pendingIndex).toBeLessThan(itemIndex) - }) - - it('creates a repo group for a pending creation in a repo with no worktrees yet', () => { - const rows = buildRows( - 'repo', - [], - repoMap, - null, - new Set(), - undefined, - undefined, - undefined, - {}, - new Map(), - false, - undefined, - [], - new Set(), - new Map(), - new Map(), - [makePendingCreation('c1', repo.id)] - ) - - expect(rows.map((row) => row.type)).toEqual(['header', 'pending-creation']) - }) - - it('keeps a pending creation visible when its repo metadata is temporarily missing', () => { - const rows = buildRows( - 'repo', - [], - new Map(), - null, - new Set(), - undefined, - undefined, - undefined, - {}, - new Map(), - false, - undefined, - [], - new Set(), - new Map(), - new Map(), - [makePendingCreation('c1', repo.id)] - ) - - expect(rows).toMatchObject([ - { type: 'header', key: `repo:${repo.id}`, label: 'Unknown' }, - { type: 'pending-creation', creationId: 'c1', repo: undefined } - ]) - }) - - it('surfaces pending creations at the top for non-repo groupings', () => { - const rows = buildRows( - 'none', - [worktree], - repoMap, - null, - new Set(), - undefined, - undefined, - undefined, - {}, - new Map([[worktree.id, worktree]]), - false, - undefined, - [], - new Set(), - new Map(), - new Map(), - [makePendingCreation('c1', repo.id)] - ) - - expect(rows[0]).toMatchObject({ type: 'pending-creation', creationId: 'c1' }) - }) -}) diff --git a/src/renderer/src/components/terminal-pane/terminal-ime-forwarder-space-claim.test.ts b/src/renderer/src/components/terminal-pane/terminal-ime-forwarder-space-claim.test.ts index b5bb377d827..dc1bb7f04f9 100644 --- a/src/renderer/src/components/terminal-pane/terminal-ime-forwarder-space-claim.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-ime-forwarder-space-claim.test.ts @@ -73,21 +73,6 @@ describe('#11504 suppression rests on claiming space and emptying the field', () return created } - // Space triggers the native substitution rule and must remain claimable. - it('claims a plain space keydown', () => { - const p = pane() - expect(p.forwarder.claimKeyEvent(makeKeydown({ key: ' ', code: 'Space', keyCode: 32 }))).toBe( - true - ) - }) - - it('claims a plain letter keydown', () => { - const p = pane() - expect(p.forwarder.claimKeyEvent(makeKeydown({ key: 'a', code: 'KeyA', keyCode: 65 }))).toBe( - true - ) - }) - // Empty the field so the text system cannot read a preceding word and substitute. it('leaves the helper textarea empty after a claimed commit, so no word precedes the space', () => { const p = pane() @@ -101,12 +86,4 @@ describe('#11504 suppression rests on claiming space and emptying the field', () expect(p.emitted.join('')).toBe('ab ') expect(p.textarea.value).toBe('') }) - - // Control chords remain with xterm’s encoder. - it('does not claim a control chord', () => { - const p = pane() - expect( - p.forwarder.claimKeyEvent(makeKeydown({ key: 'c', code: 'KeyC', keyCode: 67, ctrlKey: true })) - ).toBe(false) - }) }) diff --git a/src/renderer/src/components/terminal-pane/terminal-ime-xterm-windows-resumed-preedit-trace.test.ts b/src/renderer/src/components/terminal-pane/terminal-ime-xterm-windows-resumed-preedit-trace.test.ts deleted file mode 100644 index c00f3e412f6..00000000000 --- a/src/renderer/src/components/terminal-pane/terminal-ime-xterm-windows-resumed-preedit-trace.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -// @vitest-environment happy-dom -// Replay dispatch-only Windows/WSL Korean events: 37 visible updates across 11 balanced sessions. Batched frame re-logs would fabricate resume-without-start ordering; that behavior has separate synthetic coverage. -import { Terminal } from '@xterm/xterm' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import trace from './__fixtures__/windows-wsl-hangul-resumed-preedit-trace.json' - -type RecordedEvent = { - type: string - inputType?: string - data?: string - key?: string - code?: string - keyCode?: number - isComposing?: boolean - value?: string - selectionStart?: number - selectionEnd?: number -} - -function nextEventLoop(): Promise { - return new Promise((resolve) => window.setTimeout(resolve, 0)) -} - -function buildEvent(recorded: RecordedEvent): Event { - if (recorded.type === 'keydown' || recorded.type === 'keyup') { - const keyboard = new KeyboardEvent(recorded.type, { - key: recorded.key, - code: recorded.code, - isComposing: recorded.isComposing, - bubbles: true, - cancelable: true - }) - Object.defineProperty(keyboard, 'keyCode', { value: recorded.keyCode }) - return keyboard - } - if (recorded.type === 'input' || recorded.type === 'beforeinput') { - const input = new InputEvent(recorded.type, { - isComposing: recorded.isComposing, - bubbles: true - }) - // happy-dom drops these from InputEventInit; Chromium supplies them. - Object.defineProperty(input, 'inputType', { value: recorded.inputType ?? '' }) - Object.defineProperty(input, 'data', { value: recorded.data ?? null }) - Object.defineProperty(input, 'composed', { value: true }) - return input - } - const composition = new CompositionEvent(recorded.type, { bubbles: true }) - Object.defineProperty(composition, 'data', { value: recorded.data ?? '' }) - return composition -} - -describe('Windows/WSL Korean — recorded composition updates keep the preedit visible', () => { - beforeEach(() => { - vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({ - measureText: () => ({ width: 10 }) - } as unknown as CanvasRenderingContext2D) - }) - - afterEach(() => { - vi.restoreAllMocks() - document.body.replaceChildren() - }) - - it('keeps the preedit visible for every recorded update', async () => { - const container = document.createElement('div') - document.body.appendChild(container) - const terminal = new Terminal() - terminal.open(container) - const textarea = terminal.textarea - if (!textarea) { - throw new Error('xterm helper textarea was not created') - } - - const events = trace.dom as RecordedEvent[] - let compositionOpen = false - const resumed: { data: string; shown: boolean }[] = [] - const shown: { data: string; visible: boolean }[] = [] - - for (const recorded of events) { - if (recorded.type === 'keydown' || recorded.type === 'keyup') { - await nextEventLoop() - } - if (recorded.value !== undefined) { - textarea.value = recorded.value - } - if (recorded.selectionStart !== undefined && recorded.selectionEnd !== undefined) { - textarea.setSelectionRange(recorded.selectionStart, recorded.selectionEnd) - } - textarea.dispatchEvent(buildEvent(recorded)) - - if (recorded.type === 'compositionstart') { - compositionOpen = true - } else if (recorded.type === 'compositionend') { - compositionOpen = false - } else if (recorded.type === 'compositionupdate' && recorded.data) { - const view = terminal.element?.querySelector('.composition-view') - const active = view?.classList.contains('active') === true - shown.push({ data: recorded.data, visible: active }) - if (!compositionOpen) { - resumed.push({ data: recorded.data, shown: active }) - } - } - } - terminal.dispose() - - // Guards against a fixture that silently stops covering anything. - expect(shown).toHaveLength(37) - expect(shown.filter((sample) => !sample.visible)).toEqual([]) - // Dispatch records contain no update-only resumes; seeing one here means batched re-logs contaminated the fixture. - expect(resumed).toEqual([]) - }) -}) diff --git a/src/renderer/src/components/terminal-pane/terminal-layout-leaf-detach.test.ts b/src/renderer/src/components/terminal-pane/terminal-layout-leaf-detach.test.ts deleted file mode 100644 index 8a610702558..00000000000 --- a/src/renderer/src/components/terminal-pane/terminal-layout-leaf-detach.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { TerminalLayoutSnapshot } from '../../../../shared/terminal-tab-types' -import { detachTerminalLayoutLeaf } from './terminal-layout-leaf-detach' - -const LEAF_1 = '11111111-1111-4111-8111-111111111111' -const LEAF_2 = '22222222-2222-4222-8222-222222222222' -const LEAF_3 = '33333333-3333-4333-8333-333333333333' - -function splitLayout(): TerminalLayoutSnapshot { - return { - root: { - type: 'split', - direction: 'vertical', - ratio: 0.25, - first: { type: 'leaf', leafId: LEAF_1 }, - second: { - type: 'split', - direction: 'horizontal', - ratio: 0.7, - first: { type: 'leaf', leafId: LEAF_2 }, - second: { type: 'leaf', leafId: LEAF_3 } - } - }, - activeLeafId: LEAF_2, - expandedLeafId: LEAF_3, - ptyIdsByLeafId: { - [LEAF_1]: 'pty-1', - [LEAF_2]: 'remote:env-1@@terminal-1', - [LEAF_3]: 'pty-3' - }, - buffersByLeafId: { - [LEAF_1]: 'buffer-1', - [LEAF_2]: 'buffer-2', - [LEAF_3]: 'buffer-3' - }, - scrollbackRefsByLeafId: { - [LEAF_1]: 'scrollback-1', - [LEAF_2]: 'scrollback-2', - [LEAF_3]: 'scrollback-3' - }, - titlesByLeafId: { - [LEAF_1]: 'one', - [LEAF_2]: 'remote shell', - [LEAF_3]: 'three' - } - } -} - -describe('detachTerminalLayoutLeaf', () => { - it('extracts a nested leaf into a single-pane layout while preserving SSH PTY state', () => { - const detached = detachTerminalLayoutLeaf(splitLayout(), LEAF_2) - - expect(detached?.ptyId).toBe('remote:env-1@@terminal-1') - expect(detached?.detachedLayout).toEqual({ - root: { type: 'leaf', leafId: LEAF_2 }, - activeLeafId: LEAF_2, - expandedLeafId: null, - ptyIdsByLeafId: { [LEAF_2]: 'remote:env-1@@terminal-1' }, - buffersByLeafId: { [LEAF_2]: 'buffer-2' }, - scrollbackRefsByLeafId: { [LEAF_2]: 'scrollback-2' }, - titlesByLeafId: { [LEAF_2]: 'remote shell' } - }) - expect(detached?.sourceLayout).toEqual({ - root: { - type: 'split', - direction: 'vertical', - ratio: 0.25, - first: { type: 'leaf', leafId: LEAF_1 }, - second: { type: 'leaf', leafId: LEAF_3 } - }, - activeLeafId: LEAF_1, - expandedLeafId: LEAF_3, - ptyIdsByLeafId: { - [LEAF_1]: 'pty-1', - [LEAF_3]: 'pty-3' - }, - buffersByLeafId: { - [LEAF_1]: 'buffer-1', - [LEAF_3]: 'buffer-3' - }, - scrollbackRefsByLeafId: { - [LEAF_1]: 'scrollback-1', - [LEAF_3]: 'scrollback-3' - }, - titlesByLeafId: { - [LEAF_1]: 'one', - [LEAF_3]: 'three' - } - }) - }) - - it('clears source expanded selection when the expanded leaf is detached', () => { - const detached = detachTerminalLayoutLeaf(splitLayout(), LEAF_3) - - expect(detached?.sourceLayout.expandedLeafId).toBeNull() - expect(detached?.sourceLayout.root).toEqual({ - type: 'split', - direction: 'vertical', - ratio: 0.25, - first: { type: 'leaf', leafId: LEAF_1 }, - second: { type: 'leaf', leafId: LEAF_2 } - }) - }) - - it('returns null for missing or only leaf layouts', () => { - expect(detachTerminalLayoutLeaf(splitLayout(), 'missing')).toBeNull() - expect( - detachTerminalLayoutLeaf( - { - root: { type: 'leaf', leafId: LEAF_1 }, - activeLeafId: LEAF_1, - expandedLeafId: null, - ptyIdsByLeafId: { [LEAF_1]: 'pty-1' } - }, - LEAF_1 - ) - ).toBeNull() - }) -}) diff --git a/src/renderer/src/components/terminal-pane/terminal-pane-local-windows-conpty.test.ts b/src/renderer/src/components/terminal-pane/terminal-pane-local-windows-conpty.test.ts deleted file mode 100644 index eb172e4c84a..00000000000 --- a/src/renderer/src/components/terminal-pane/terminal-pane-local-windows-conpty.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants' -import { - isTerminalPaneLocalWindowsConpty, - type TerminalPaneConptyState -} from './terminal-pane-local-windows-conpty' -import type { PtyTransport } from './pty-transport-types' - -const WINDOWS_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' - -function state(overrides: Partial = {}): TerminalPaneConptyState { - return { - repos: [], - worktreesByRepo: {}, - folderWorkspaces: [], - projectGroups: [], - settings: null, - tabsByWorktree: {}, - ...overrides - } -} - -function localTransport( - metadata: { cwd?: string; shellOverride?: string } = {} -): Pick { - return { - getPtyId: () => 'pty-1', - getConnectionId: () => null, - getLocalSessionMetadata: () => metadata - } -} - -function remoteRuntimeTransport(): Pick< - PtyTransport, - 'getPtyId' | 'getConnectionId' | 'getLocalSessionMetadata' -> { - return { - getPtyId: () => 'remote:env-1@@terminal-1', - getConnectionId: () => null, - getLocalSessionMetadata: () => null - } -} - -describe('isTerminalPaneLocalWindowsConpty', () => { - it('uses live local session metadata for synthetic floating terminals with no repo record', () => { - expect( - isTerminalPaneLocalWindowsConpty({ - isWindows: true, - userAgent: WINDOWS_UA, - state: state({ - tabsByWorktree: { - [FLOATING_TERMINAL_WORKTREE_ID]: [{ id: 'tab-1', shellOverride: 'powershell.exe' }] - } - }), - worktreeId: FLOATING_TERMINAL_WORKTREE_ID, - tabId: 'tab-1', - paneId: 1, - paneCwd: new Map(), - fallbackCwd: 'C:\\Users\\me', - transport: localTransport({ cwd: 'C:\\Users\\me', shellOverride: 'powershell.exe' }) - }) - ).toBe(true) - }) - - it('keeps an already-launched local PowerShell pane local after store ownership changes', () => { - const worktreeId = 'repo-1::C:\\repo' - expect( - isTerminalPaneLocalWindowsConpty({ - isWindows: true, - userAgent: WINDOWS_UA, - state: state({ - settings: { activeRuntimeEnvironmentId: 'env-1' }, - tabsByWorktree: { - [worktreeId]: [{ id: 'tab-1', shellOverride: 'wsl.exe' }] - } - }), - worktreeId, - tabId: 'tab-1', - paneId: 1, - paneCwd: new Map([[1, { cwd: '\\\\wsl.localhost\\Ubuntu\\home\\me', confirmed: true }]]), - fallbackCwd: '\\\\wsl.localhost\\Ubuntu\\home\\me', - transport: localTransport({ cwd: 'C:\\repo', shellOverride: 'powershell.exe' }) - }) - ).toBe(true) - }) - - it('does not treat a live remote-runtime terminal as local ConPTY', () => { - expect( - isTerminalPaneLocalWindowsConpty({ - isWindows: true, - userAgent: WINDOWS_UA, - state: state(), - worktreeId: 'repo-1::/remote/repo', - tabId: 'tab-1', - paneId: 1, - paneCwd: new Map([[1, { cwd: '/remote/repo', confirmed: true }]]), - fallbackCwd: '/remote/repo', - transport: remoteRuntimeTransport() - }) - ).toBe(false) - }) - - it('stays conservative for unresolved repo-backed panes before a live session exists', () => { - expect( - isTerminalPaneLocalWindowsConpty({ - isWindows: true, - userAgent: WINDOWS_UA, - state: state(), - worktreeId: 'missing-repo::/home/me/repo', - tabId: 'tab-1', - paneId: 1, - paneCwd: new Map([[1, { cwd: '/home/me/repo', confirmed: true }]]), - fallbackCwd: '/home/me/repo', - transport: null - }) - ).toBe(false) - }) -}) diff --git a/src/renderer/src/components/worktree-jump-palette-status-inputs.test.ts b/src/renderer/src/components/worktree-jump-palette-status-inputs.test.ts deleted file mode 100644 index b4ad7cb7018..00000000000 --- a/src/renderer/src/components/worktree-jump-palette-status-inputs.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { shallow } from 'zustand/shallow' -import { - EMPTY_PALETTE_STATUS_INPUTS, - selectPaletteIndexStatusSnapshot, - selectPaletteStatusInputs, - type PaletteStatusInputsState -} from './worktree-jump-palette-status-inputs' - -const BASE: PaletteStatusInputsState = { - agentStatusByPaneKey: {}, - runtimePaneTitlesByTabId: {}, - ptyIdsByTabId: {}, - terminalLayoutsByTabId: {}, - tabsByWorktree: {}, - unreadTerminalTabs: {}, - unreadAgentCompletionPanes: {} -} - -describe('selectPaletteStatusInputs', () => { - it('returns the shared frozen constant while inactive', () => { - const inactive = selectPaletteStatusInputs(BASE, false) - expect(inactive).toBe(EMPTY_PALETTE_STATUS_INPUTS) - - const churned: PaletteStatusInputsState = { - ...BASE, - ptyIdsByTabId: { 'tab-1': ['pty-1'] }, - tabsByWorktree: { 'wt-1': [] } - } - const afterChurn = selectPaletteStatusInputs(churned, false) - expect(afterChurn).toBe(EMPTY_PALETTE_STATUS_INPUTS) - expect(shallow(inactive, afterChurn)).toBe(true) - }) - - it('exposes the live maps the instant it becomes active', () => { - const ptyIds = { 'tab-1': ['pty-1'] } - const active = selectPaletteStatusInputs({ ...BASE, ptyIdsByTabId: ptyIds }, true) - expect(active.ptyIdsByTabId).toBe(ptyIds) - expect(active).not.toBe(EMPTY_PALETTE_STATUS_INPUTS) - }) - - it('keeps the tab set live while active so a closing tab can backfill the recent section', () => { - const tabs = { 'wt-1': [] } - const closing = selectPaletteStatusInputs({ ...BASE, tabsByWorktree: tabs }, true) - expect(closing.tabsByWorktree).toBe(tabs) - }) - - // Hot agent/title writes must not rerender the whole palette; status dots own those subscriptions. - it('does not move when the two hot maps churn while active', () => { - const r1 = selectPaletteStatusInputs(BASE, true) - const churned: PaletteStatusInputsState = { - ...BASE, - agentStatusByPaneKey: { 'tab-1:leaf-1': {} as never }, - runtimePaneTitlesByTabId: { 'tab-1': { 0: 'claude' } } - } - expect(shallow(r1, selectPaletteStatusInputs(churned, true))).toBe(true) - }) - - it('shallow-changes when a subscribed map reference actually changes while active', () => { - const s1: PaletteStatusInputsState = { ...BASE, ptyIdsByTabId: { 'tab-1': ['pty-1'] } } - const r1 = selectPaletteStatusInputs(s1, true) - expect(shallow(r1, selectPaletteStatusInputs(s1, true))).toBe(true) - - const s2: PaletteStatusInputsState = { ...s1, ptyIdsByTabId: { 'tab-1': [] } } - expect(shallow(r1, selectPaletteStatusInputs(s2, true))).toBe(false) - }) -}) - -describe('selectPaletteIndexStatusSnapshot', () => { - it('passes the hot maps through while active', () => { - const titles = { 'tab-1': { 0: 'claude' } } - const statuses = { 'tab-1:leaf-1': {} as never } - const snapshot = selectPaletteIndexStatusSnapshot( - { ...BASE, runtimePaneTitlesByTabId: titles, agentStatusByPaneKey: statuses }, - true - ) - expect(snapshot.runtimePaneTitlesByTabId).toBe(titles) - expect(snapshot.agentStatusByPaneKey).toBe(statuses) - }) - - // Snapshot unread status with recent membership; live changes cannot agree with frozen open-time order. - it('snapshots the unread maps alongside the status maps', () => { - const unreadTabs = { 'term-1': true } as const - const unreadPanes = { 'term-1:leaf-1': true } as const - const snapshot = selectPaletteIndexStatusSnapshot( - { ...BASE, unreadTerminalTabs: unreadTabs, unreadAgentCompletionPanes: unreadPanes }, - true - ) - expect(snapshot.unreadTerminalTabs).toBe(unreadTabs) - expect(snapshot.unreadAgentCompletionPanes).toBe(unreadPanes) - }) - - it('keeps unread churn out of the subscribed bundle', () => { - const r1 = selectPaletteStatusInputs(BASE, true) - const churned: PaletteStatusInputsState = { - ...BASE, - unreadTerminalTabs: { 'term-1': true }, - unreadAgentCompletionPanes: { 'term-1:leaf-1': true } - } - expect(shallow(r1, selectPaletteStatusInputs(churned, true))).toBe(true) - }) - - it('drops its hold on the live maps once inactive', () => { - const titles = { 'tab-1': { 0: 'claude' } } - const inactive = selectPaletteIndexStatusSnapshot( - { ...BASE, runtimePaneTitlesByTabId: titles }, - false - ) - expect(inactive.runtimePaneTitlesByTabId).toEqual({}) - expect(selectPaletteIndexStatusSnapshot(BASE, false)).toBe(inactive) - }) -}) diff --git a/src/renderer/src/hooks/installed-agent-skill-discovery-cache.test.ts b/src/renderer/src/hooks/installed-agent-skill-discovery-cache.test.ts index 32a6ec4953d..0909e574183 100644 --- a/src/renderer/src/hooks/installed-agent-skill-discovery-cache.test.ts +++ b/src/renderer/src/hooks/installed-agent-skill-discovery-cache.test.ts @@ -1,7 +1,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import type { SkillDiscoveryResult } from '../../../shared/skills' import { - clearInstalledAgentSkillDiscoveryCache, getInstalledAgentSkillDiscoveryCacheSizeForTests, hasInstalledAgentSkillDiscoveryCacheEntryForTests, INSTALLED_AGENT_SKILL_DISCOVERY_CACHE_MAX, @@ -50,14 +49,6 @@ describe('installed agent skill discovery cache', () => { ).toBe(false) }) - it('rewrites an existing key in place without growing the cache', () => { - writeInstalledAgentSkillDiscoveryCache('target', result(1)) - writeInstalledAgentSkillDiscoveryCache('target', result(2)) - - expect(getInstalledAgentSkillDiscoveryCacheSizeForTests()).toBe(1) - expect(peekInstalledAgentSkillDiscoveryCache('target')).toEqual(result(2)) - }) - it('promotes a rewritten key to most-recent so a rescanned target is not evicted first', () => { // Map.set does not promote existing keys; refreshed targets need explicit recency updates before eviction. writeInstalledAgentSkillDiscoveryCache('hot', result(0)) @@ -83,14 +74,6 @@ describe('installed agent skill discovery cache', () => { expect(hasInstalledAgentSkillDiscoveryCacheEntryForTests('target-0')).toBe(false) }) - it('clears every retained result', () => { - writeInstalledAgentSkillDiscoveryCache('target', result(1)) - clearInstalledAgentSkillDiscoveryCache() - - expect(getInstalledAgentSkillDiscoveryCacheSizeForTests()).toBe(0) - expect(peekInstalledAgentSkillDiscoveryCache('target')).toBeNull() - }) - it('stops serving a read once the freshness window lapses', () => { const startedAt = 1_700_000_000_000 const nowSpy = vi.spyOn(Date, 'now').mockReturnValue(startedAt) diff --git a/src/renderer/src/hooks/useIpcEvents-client-hosted-browser-rows.test.ts b/src/renderer/src/hooks/useIpcEvents-client-hosted-browser-rows.test.ts index 2d806357418..dcd0d958342 100644 --- a/src/renderer/src/hooks/useIpcEvents-client-hosted-browser-rows.test.ts +++ b/src/renderer/src/hooks/useIpcEvents-client-hosted-browser-rows.test.ts @@ -43,26 +43,6 @@ async function mountHook(options: IpcEventsHarnessOptions = {}): Promise<{ } describe('useIpcEvents client-hosted browser rows', () => { - it('lands a pushed row in the store the host strip reads', async () => { - const { harness, readRows } = await mountHook() - await harness.settleClientHostedBrowserRowsSnapshot() - - harness.clientHostedBrowserRowsChanged(pushEvent()) - - expect(readRows(WT)).toEqual(pushEvent().rows) - }) - - it('hydrates rows that existed before this window attached', async () => { - const { harness, readRows } = await mountHook({ - clientHostedBrowserRowsSnapshot: [pushEvent()] - }) - - expect(readRows(WT)).toEqual([]) - await harness.settleClientHostedBrowserRowsSnapshot() - - expect(readRows(WT).map((row) => row.browserPageId)).toEqual(['page-a']) - }) - // Subscribe before hydration so older snapshots cannot erase pages pushed during the round trip. it('keeps a push that raced the hydration snapshot', async () => { const { harness, readRows } = await mountHook({ @@ -75,17 +55,6 @@ describe('useIpcEvents client-hosted browser rows', () => { expect(readRows(WT).map((row) => row.browserPageId)).toEqual(['page-a']) }) - it('retracts the rows when the push empties the worktree', async () => { - const { harness, readRows } = await mountHook() - await harness.settleClientHostedBrowserRowsSnapshot() - harness.clientHostedBrowserRowsChanged(pushEvent()) - expect(readRows(WT)).toHaveLength(1) - - harness.clientHostedBrowserRowsChanged({ worktreeId: WT, rows: [] }) - - expect(readRows(WT)).toEqual([]) - }) - // Failed hydration must settle and drain buffered pushes or the host strip stays empty forever. it('applies pushes after the hydration round trip fails', async () => { const { harness, readRows } = await mountHook({ @@ -100,14 +69,4 @@ describe('useIpcEvents client-hosted browser rows', () => { harness.clientHostedBrowserRowsChanged({ worktreeId: WT, rows: [] }) expect(readRows(WT)).toEqual([]) }) - - it('leaves other worktrees alone when one is pushed', async () => { - const { harness, readRows } = await mountHook() - await harness.settleClientHostedBrowserRowsSnapshot() - - harness.clientHostedBrowserRowsChanged(pushEvent()) - - expect(readRows(WT)).toHaveLength(1) - expect(readRows('wt-2')).toEqual([]) - }) }) diff --git a/src/renderer/src/lib/agent-startup-creation-continuation.test.ts b/src/renderer/src/lib/agent-startup-creation-continuation.test.ts index d7651988cb4..6d5a844c493 100644 --- a/src/renderer/src/lib/agent-startup-creation-continuation.test.ts +++ b/src/renderer/src/lib/agent-startup-creation-continuation.test.ts @@ -8,7 +8,6 @@ import { buildAiVaultForkStartup, resolveAiVaultCopiedSessionAgentArgs } from '../../../shared/ai-vault-resume-startup' -import { buildAntigravityReferenceStartup } from './ai-vault-antigravity-reference-startup' vi.mock('@/lib/telemetry', () => ({ tuiAgentToAgentKind: (agent: string) => agent })) it.each(['claude', 'codex'] as const)( @@ -93,25 +92,3 @@ it.each(['claude', 'codex'] as const)( ).toContain('picked') } ) - -it('uses fresh typed defaults for an Antigravity reference rather than continuing its provider identity', () => { - const settings = { - ...getDefaultSettings('/test'), - agentPermissionDefault: 'own' as const, - agentLaunchChoices: { antigravity: { model: 'fresh-reference' } }, - agentDefaultArgs: { antigravity: '--verbose' } - } - const result = buildAntigravityReferenceStartup({ - settings, - platform: 'linux', - shell: 'posix', - cwd: '/project', - session: { - sessionId: 'reference', - filePath: '/home/test/.gemini/antigravity-ide/brain/reference/transcript_full.jsonl' - } - }) - expect(result?.command).toContain('fresh-reference') - expect(result?.command).not.toContain('--conversation') - expect(result?.launchConfig?.agentArgs).toBe('--verbose') -}) diff --git a/src/renderer/src/lib/file-type-icons.test.ts b/src/renderer/src/lib/file-type-icons.test.ts index 64286e956bc..61ff83b7104 100644 --- a/src/renderer/src/lib/file-type-icons.test.ts +++ b/src/renderer/src/lib/file-type-icons.test.ts @@ -1,12 +1,7 @@ import { describe, expect, it } from 'vitest' -import { File } from 'lucide-react' import { parsePluginIconThemeArtifact } from '../../../shared/plugins/artifacts/plugin-icon-theme-artifact' import type { ActivePluginIconTheme } from '../store/plugin-icon-themes' -import { - getFileTypeIcon, - getPluginFileTypeIconIndex, - getPluginFolderIconIndex -} from './file-type-icons' +import { getPluginFileTypeIconIndex } from './file-type-icons' const iconTheme = (maps: Partial): ActivePluginIconTheme => ({ icons: {}, @@ -17,19 +12,6 @@ const iconTheme = (maps: Partial): ActivePluginIconTheme ...maps }) -describe('getFileTypeIcon', () => { - it.each([ - 'constructor', - '__proto__', - 'toString', - 'hasOwnProperty', - 'x.constructor', - 'x.__proto__' - ])('falls back to the generic file icon for %s', (name) => { - expect(getFileTypeIcon(`/a/${name}`)).toBe(File) - }) -}) - describe('parsed artifact lookup', () => { it('finds files named __proto__ and constructor from the parsed theme', () => { const artifact = parsePluginIconThemeArtifact( @@ -44,76 +26,3 @@ describe('parsed artifact lookup', () => { expect(getPluginFileTypeIconIndex(theme, '/a/other')).toBeNull() }) }) - -describe('getPluginFileTypeIconIndex', () => { - const theme = iconTheme({ - icons: { file: 3 }, - fileNames: { 'readme.md': 1, ['__proto__']: 4 }, - fileExtensions: { ts: 2 } - }) - - it('matches exact names, then extensions, then the file icon', () => { - expect(getPluginFileTypeIconIndex(theme, '/a/README.md')).toBe(1) - expect(getPluginFileTypeIconIndex(theme, '/a/x.TS')).toBe(2) - }) - - it('matches the longest dotted extension the theme maps', () => { - const multi = iconTheme({ fileExtensions: { 'd.ts': 5, ts: 2, gz: 6 } }) - expect(getPluginFileTypeIconIndex(multi, '/a/types.d.ts')).toBe(5) - expect(getPluginFileTypeIconIndex(multi, '/a/index.ts')).toBe(2) - expect(getPluginFileTypeIconIndex(multi, '/a/archive.tar.gz')).toBe(6) - expect(getPluginFileTypeIconIndex(theme, '/a/x.unknown')).toBe(3) - }) - - it('ignores inherited Object.prototype names', () => { - const noFile = iconTheme({}) - for (const name of ['constructor', 'toString', 'hasOwnProperty', 'valueOf']) { - expect(getPluginFileTypeIconIndex(noFile, `/a/${name}`)).toBeNull() - expect(getPluginFileTypeIconIndex(noFile, `/a/x.${name}`)).toBeNull() - } - expect(getPluginFileTypeIconIndex(noFile, '/a/__proto__')).toBeNull() - }) - - it('ignores non-index values', () => { - const bad = iconTheme({ fileNames: { 'a.txt': -1 }, fileExtensions: { md: 1.5 } }) - expect(getPluginFileTypeIconIndex(bad, '/a/a.txt')).toBeNull() - expect(getPluginFileTypeIconIndex(bad, '/a/b.md')).toBeNull() - }) -}) - -describe('getPluginFolderIconIndex', () => { - const theme = iconTheme({ - icons: { folder: 0, 'folder-open': 1 }, - folderNames: { src: 2, docs: 4 }, - folderNamesExpanded: { src: 3 } - }) - - it('picks the name icon for the folder state, falling back to the generic slot', () => { - expect(getPluginFolderIconIndex(theme, 'SRC', false)).toBe(2) - expect(getPluginFolderIconIndex(theme, 'src', true)).toBe(3) - expect(getPluginFolderIconIndex(theme, 'other', false)).toBe(0) - expect(getPluginFolderIconIndex(theme, 'other', true)).toBe(1) - expect(getPluginFolderIconIndex(theme, undefined, true)).toBe(1) - }) - - it('prefers the generic open icon over a collapsed name icon when open', () => { - expect(getPluginFolderIconIndex(theme, 'docs', true)).toBe(1) - }) - - it('falls back to closed icons when the theme has no open ones', () => { - const closedOnly = iconTheme({ icons: { folder: 0 }, folderNames: { docs: 4 } }) - expect(getPluginFolderIconIndex(closedOnly, 'docs', true)).toBe(4) - expect(getPluginFolderIconIndex(closedOnly, 'other', true)).toBe(0) - }) - - it('matches the last segment of a compacted or Windows path', () => { - expect(getPluginFolderIconIndex(theme, 'packages/app/src', false)).toBe(2) - expect(getPluginFolderIconIndex(theme, String.raw`C:\repo\src`, true)).toBe(3) - }) - - it('ignores inherited Object.prototype names', () => { - const noSlots = iconTheme({}) - expect(getPluginFolderIconIndex(noSlots, 'constructor', false)).toBeNull() - expect(getPluginFolderIconIndex(noSlots, '__proto__', true)).toBeNull() - }) -}) diff --git a/src/renderer/src/startup/active-workspace-ssh-targets.test.ts b/src/renderer/src/startup/active-workspace-ssh-targets.test.ts deleted file mode 100644 index f36a186b0ac..00000000000 --- a/src/renderer/src/startup/active-workspace-ssh-targets.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { toAppSshPtyId } from '../../../shared/ssh-pty-id' -import type { TerminalTab } from '../../../shared/terminal-tab-types' -import { collectActiveWorkspaceSshTargetIds } from './active-workspace-ssh-targets' - -function tab(id: string, ptyId: string | null = null): TerminalTab { - return { - id, - ptyId, - worktreeId: 'repo-a::/w/a', - title: id, - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 1 - } as TerminalTab -} - -const emptyInput = { - activeWorktreeId: null as string | null, - tabsByWorktree: {} as Record, - pendingReconnectPtyIdByTabId: {} as Record, - terminalLayoutsByTabId: {} as Record }>, - repos: [] as { id: string; connectionId?: string | null }[] -} - -describe('collectActiveWorkspaceSshTargetIds', () => { - it('returns nothing when no workspace is active', () => { - expect(collectActiveWorkspaceSshTargetIds(emptyInput)).toEqual([]) - }) - - it('returns nothing for a purely local active workspace', () => { - expect( - collectActiveWorkspaceSshTargetIds({ - ...emptyInput, - activeWorktreeId: 'repo-a::/w/a', - tabsByWorktree: { 'repo-a::/w/a': [tab('t1', 'local-pty-1')] }, - repos: [{ id: 'repo-a', connectionId: null }] - }) - ).toEqual([]) - }) - - it('names the target from the active workspace repo connection', () => { - expect( - collectActiveWorkspaceSshTargetIds({ - ...emptyInput, - activeWorktreeId: 'repo-remote::/srv/w', - repos: [{ id: 'repo-remote', connectionId: 'ssh-1' }] - }) - ).toEqual(['ssh-1']) - }) - - it('names the target from a restored PTY id when the repo catalog has no connection', () => { - // SSH worktrees are absent from worktreesByRepo at cold start; the PTY id is the durable name. - expect( - collectActiveWorkspaceSshTargetIds({ - ...emptyInput, - activeWorktreeId: 'repo-a::/w/a', - tabsByWorktree: { 'repo-a::/w/a': [tab('t1')] }, - pendingReconnectPtyIdByTabId: { t1: toAppSshPtyId('ssh-2', 'pty-9') }, - repos: [{ id: 'repo-a' }] - }) - ).toEqual(['ssh-2']) - }) - - it('names split-leaf targets on the active workspace', () => { - expect( - collectActiveWorkspaceSshTargetIds({ - ...emptyInput, - activeWorktreeId: 'repo-a::/w/a', - tabsByWorktree: { 'repo-a::/w/a': [tab('t1')] }, - terminalLayoutsByTabId: { - t1: { - ptyIdsByLeafId: { - leaf1: toAppSshPtyId('ssh-3', 'pty-1'), - leaf2: null, - leaf3: 'local-pty-2' - } - } - }, - repos: [{ id: 'repo-a' }] - }) - ).toEqual(['ssh-3']) - }) - - it('ignores targets that only own an inactive workspace', () => { - expect( - collectActiveWorkspaceSshTargetIds({ - ...emptyInput, - activeWorktreeId: 'repo-a::/w/a', - tabsByWorktree: { - 'repo-a::/w/a': [tab('t1', 'local-pty-1')], - 'repo-b::/w/b': [tab('t2', toAppSshPtyId('ssh-other', 'pty-1'))] - }, - repos: [{ id: 'repo-a' }, { id: 'repo-b', connectionId: 'ssh-other' }] - }) - ).toEqual([]) - }) - - it('deduplicates a target named by both the repo and its PTY ids', () => { - expect( - collectActiveWorkspaceSshTargetIds({ - ...emptyInput, - activeWorktreeId: 'repo-remote::/srv/w', - tabsByWorktree: { - 'repo-remote::/srv/w': [tab('t1', toAppSshPtyId('ssh-1', 'pty-1'))] - }, - repos: [{ id: 'repo-remote', connectionId: 'ssh-1' }] - }) - ).toEqual(['ssh-1']) - }) -}) diff --git a/src/renderer/src/store/active-terminal-chrome-selector.test.ts b/src/renderer/src/store/active-terminal-chrome-selector.test.ts deleted file mode 100644 index b4d631000ee..00000000000 --- a/src/renderer/src/store/active-terminal-chrome-selector.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { beforeEach, describe, expect, it } from 'vitest' -import type { TerminalTab } from '../../../shared/terminal-tab-types' -import { - resetActiveTerminalChromeStateSelectorCacheForTest, - selectActiveTerminalChromeState -} from './active-terminal-chrome-selector' - -function terminalTab(id: string, worktreeId = 'wt-1'): TerminalTab { - return { - id, - ptyId: null, - worktreeId, - title: id, - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 1 - } -} - -describe('selectActiveTerminalChromeState', () => { - beforeEach(() => { - resetActiveTerminalChromeStateSelectorCacheForTest() - }) - - it('reuses the projection for stable inputs across selector fanout', () => { - const tabs = [terminalTab('tab-1')] - const state = { - activeWorktreeId: 'wt-1', - activeTabId: 'tab-1', - tabsByWorktree: { 'wt-1': tabs }, - canExpandPaneByTabId: { 'tab-1': true }, - expandedPaneByTabId: { 'tab-1': false } - } satisfies Parameters[0] - - const first = selectActiveTerminalChromeState(state) - let distinctResults = 0 - let previous = first - const iterations = 100_000 - for (let index = 0; index < iterations; index += 1) { - const selected = selectActiveTerminalChromeState(state) - if (selected !== previous) { - distinctResults += 1 - } - previous = selected - } - - expect(distinctResults).toBe(0) - expect(previous).toBe(first) - }) - - it('reuses equivalent replacement inputs and invalidates visible scalar changes', () => { - const tabs = [terminalTab('tab-1')] - const state = { - activeWorktreeId: 'wt-1', - activeTabId: null, - tabsByWorktree: { 'wt-1': tabs }, - canExpandPaneByTabId: { 'tab-1': false }, - expandedPaneByTabId: { 'tab-1': false } - } satisfies Parameters[0] - - const first = selectActiveTerminalChromeState(state) - const equivalentReplacement = selectActiveTerminalChromeState({ - ...state, - tabsByWorktree: { 'wt-1': tabs.map((tab) => ({ ...tab, title: 'new title' })) }, - canExpandPaneByTabId: { 'tab-1': false }, - expandedPaneByTabId: { 'tab-1': false } - }) - expect(equivalentReplacement).toBe(first) - - const afterActiveTabId = selectActiveTerminalChromeState({ - ...state, - activeTabId: 'tab-1' - }) - expect(afterActiveTabId).not.toBe(first) - - const afterActiveWorktreeId = selectActiveTerminalChromeState({ - ...state, - activeWorktreeId: 'wt-2', - tabsByWorktree: { 'wt-2': tabs } - }) - expect(afterActiveWorktreeId).not.toBe(afterActiveTabId) - - const afterEffectiveActiveTabId = selectActiveTerminalChromeState({ - ...state, - tabsByWorktree: { 'wt-1': [terminalTab('tab-2')] } - }) - expect(afterEffectiveActiveTabId).not.toBe(afterActiveWorktreeId) - expect(afterEffectiveActiveTabId.effectiveActiveTabId).toBe('tab-2') - - const afterCanExpand = selectActiveTerminalChromeState({ - ...state, - canExpandPaneByTabId: { 'tab-1': true } - }) - expect(afterCanExpand).not.toBe(afterEffectiveActiveTabId) - expect(afterCanExpand.activeTabCanExpand).toBe(true) - - const afterExpanded = selectActiveTerminalChromeState({ - ...state, - canExpandPaneByTabId: { 'tab-1': true }, - expandedPaneByTabId: { 'tab-1': true } - }) - expect(afterExpanded).not.toBe(afterCanExpand) - expect(afterExpanded.effectiveActiveTabExpanded).toBe(true) - - const afterTabCount = selectActiveTerminalChromeState({ - ...state, - tabsByWorktree: { - 'wt-1': [...tabs, terminalTab('tab-2')] - } - }) - expect(afterTabCount).not.toBe(afterExpanded) - expect(afterTabCount.tabCount).toBe(2) - }) -}) diff --git a/src/renderer/src/store/slices/detected-agents-environment-prune-leak.test.ts b/src/renderer/src/store/slices/detected-agents-environment-prune-leak.test.ts index 70ee7012d45..3b4a4b984ff 100644 --- a/src/renderer/src/store/slices/detected-agents-environment-prune-leak.test.ts +++ b/src/renderer/src/store/slices/detected-agents-environment-prune-leak.test.ts @@ -63,20 +63,6 @@ describe('runtime detected-agents pruned on environment removal (leak regression expect(s.runtimeDetectedAgentIds).toHaveProperty('env-1') expect(s.isDetectingRuntimeAgents['env-1']).toBe(false) }) - - it('retainRuntimeDetectedAgents keeps only the listed environments', () => { - const store = createTestStore() - store.setState({ - runtimeDetectedAgentIds: { a: [], b: [], c: [] }, - isDetectingRuntimeAgents: { a: false, b: false, c: false } - }) - - store.getState().retainRuntimeDetectedAgents(['b']) - - const s = store.getState() - expect(Object.keys(s.runtimeDetectedAgentIds)).toEqual(['b']) - expect(Object.keys(s.isDetectingRuntimeAgents)).toEqual(['b']) - }) }) describe('detect resolving after the environment was pruned does not re-leak', () => { diff --git a/src/renderer/src/store/slices/worktree-catalog-reconciliation.test.ts b/src/renderer/src/store/slices/worktree-catalog-reconciliation.test.ts index e8548165dd5..751b3df18e1 100644 --- a/src/renderer/src/store/slices/worktree-catalog-reconciliation.test.ts +++ b/src/renderer/src/store/slices/worktree-catalog-reconciliation.test.ts @@ -1,89 +1,7 @@ import { describe, expect, it } from 'vitest' -import { catalogRowsEqual, reuseEqualCatalogRows } from './worktree-catalog-reconciliation' +import { reuseEqualCatalogRows } from './worktree-catalog-reconciliation' describe('reuseEqualCatalogRows', () => { - it('does not traverse a catalog already reconciled by identity', () => { - const current = [ - { - get id(): string { - throw new Error('catalog row was traversed') - } - } - ] - - expect(catalogRowsEqual(current, current)).toBe(true) - }) - - it('reuses rows with equivalent nested catalog data', () => { - const current = [ - { id: 'a', nested: { labels: ['one', 'two'] }, optional: undefined }, - { id: 'b', nested: { labels: ['three'] } } - ] - const incoming = [ - { id: 'a', nested: { labels: ['one', 'two'] } }, - { id: 'b', nested: { labels: ['three'] } } - ] - - const reconciled = reuseEqualCatalogRows(current, incoming) - - expect(reconciled).toBe(current) - expect(catalogRowsEqual(current, incoming)).toBe(true) - }) - - it('reuses unaffected rows while publishing nested changes', () => { - const current = [ - { id: 'a', nested: { value: 1 } }, - { id: 'b', nested: { value: 2 } } - ] - const incoming = [ - { id: 'a', nested: { value: 3 } }, - { id: 'b', nested: { value: 2 } } - ] - - const reconciled = reuseEqualCatalogRows(current, incoming) - - expect(reconciled).not.toBe(current) - expect(reconciled[0]).toBe(incoming[0]) - expect(reconciled[1]).toBe(current[1]) - }) - - it('does not hide host ownership changes', () => { - const current = [{ id: 'a', runtimeOwnerEnvironmentId: 'env-a' }] - const incoming = [{ id: 'a', runtimeOwnerEnvironmentId: 'env-b' }] - - expect(reuseEqualCatalogRows(current, incoming)[0]).toBe(incoming[0]) - }) - - it('reuses same-ID rows from different hosts independently', () => { - const current = [ - { id: 'repo::/same/path', hostId: 'ssh:a' }, - { id: 'repo::/same/path', hostId: 'ssh:b' } - ] - const equivalent = structuredClone(current) - - expect(reuseEqualCatalogRows(current, equivalent)).toBe(current) - - const incoming = structuredClone(current.toReversed()) - const reconciled = reuseEqualCatalogRows(current, incoming) - - expect(reconciled).not.toBe(current) - expect(reconciled.map((row) => row.hostId)).toEqual(['ssh:b', 'ssh:a']) - expect(reconciled[0]).toBe(current[1]) - expect(reconciled[1]).toBe(current[0]) - }) - - it('reuses a match inside the duplicate-id scan window', () => { - const current = [ - { id: 'dup', marker: 'a' }, - { id: 'dup', marker: 'b' }, - { id: 'dup', marker: 'c' } - ] - - const reconciled = reuseEqualCatalogRows(current, [{ id: 'dup', marker: 'c' }]) - - expect(reconciled[0]).toBe(current[2]) - }) - // Cap same-ID deep comparisons; count reads for deterministic O(k²) protection instead of flaky CI timing. it('caps the deep compares for one id instead of scanning the whole bucket', () => { const bucketSize = 64 diff --git a/src/renderer/src/store/slices/worktrees-runtime-host-metadata-retirement.test.ts b/src/renderer/src/store/slices/worktrees-runtime-host-metadata-retirement.test.ts deleted file mode 100644 index e2b0c2d6ecb..00000000000 --- a/src/renderer/src/store/slices/worktrees-runtime-host-metadata-retirement.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -// Runtime metadata bypasses local GC; formerly SSH-only explicit retirement stranded every paired-runtime worktree ever seen (#17776). -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { AppState } from '../types' -import { makeWorktree } from './worktrees-slice-test-fixtures' -import { makeDetectedResult } from './worktrees-detected-listing-fixtures' -import { - createTestStore, - forgetRemovedForExecutionHostMock, - resetRemoteRuntimeMocks, - resetWorktreeSliceModuleMemory, - runtimeEnvironmentCall -} from './worktrees-slice-test-harness' - -const REPO_ID = 'repo-runtime' -const HOST_ID = 'runtime:env-1' - -const worktree = (path: string) => - makeWorktree({ id: `${REPO_ID}::${path}`, repoId: REPO_ID, path, hostId: HOST_ID }) - -const live = worktree('/home/orca/live') -const deletedOnHost = worktree('/home/orca/deleted') - -function seedClientWithBothRows(): ReturnType { - const store = createTestStore() - store.setState({ - settings: { activeRuntimeEnvironmentId: 'env-1' } as never, - repos: [ - { - id: REPO_ID, - path: '/home/orca/repo', - displayName: 'Runtime Repo', - badgeColor: '#000', - addedAt: 0, - executionHostId: HOST_ID - } - ], - worktreesByRepo: { [REPO_ID]: [live, deletedOnHost] } - } as Partial) - return store -} - -beforeEach(resetWorktreeSliceModuleMemory) - -describe('runtime-host persisted metadata retirement', () => { - beforeEach(() => { - vi.clearAllMocks() - resetRemoteRuntimeMocks() - }) - - it('retires metadata for rows an authoritative runtime-host scan proved gone', async () => { - const store = seedClientWithBothRows() - runtimeEnvironmentCall.mockResolvedValue({ - id: 'rpc-detected', - ok: true, - result: makeDetectedResult(REPO_ID, [live]), - _meta: { runtimeId: 'runtime-remote' } - }) - - await store.getState().fetchWorktrees(REPO_ID, { executionHostId: HOST_ID }) - - expect(forgetRemovedForExecutionHostMock).toHaveBeenCalledExactlyOnceWith({ - repoId: REPO_ID, - executionHostId: HOST_ID, - worktreeIds: [deletedOnHost.id] - }) - }) - - // A non-authoritative reply is a failed listing, not a report that a checkout is gone. - it('retires nothing when the runtime host could not scan', async () => { - const store = seedClientWithBothRows() - runtimeEnvironmentCall.mockResolvedValue({ - id: 'rpc-detected', - ok: true, - result: makeDetectedResult(REPO_ID, [live], { - authoritative: false, - source: 'metadata-fallback' - }), - _meta: { runtimeId: 'runtime-remote' } - }) - - await store.getState().fetchWorktrees(REPO_ID, { executionHostId: HOST_ID }) - - expect(forgetRemovedForExecutionHostMock).not.toHaveBeenCalled() - }) - - // Why: a host that does not answer is lost contact, never evidence a checkout is gone. - it('retires nothing when the runtime host does not answer', async () => { - const store = seedClientWithBothRows() - runtimeEnvironmentCall.mockRejectedValue(new Error('runtime host unreachable')) - - await store - .getState() - .fetchWorktrees(REPO_ID, { executionHostId: HOST_ID }) - .catch(() => undefined) - - expect(forgetRemovedForExecutionHostMock).not.toHaveBeenCalled() - expect(store.getState().worktreesByRepo[REPO_ID]).toEqual([live, deletedOnHost]) - }) - - // `session-fallback` claims authoritative but is the truncated, visibility-filtered `worktree.list` - // reply from a host too old for `worktree.detectedList`. Its omissions prove nothing. - it('retires nothing from a legacy session-fallback listing', async () => { - const store = seedClientWithBothRows() - runtimeEnvironmentCall.mockResolvedValue({ - id: 'rpc-detected', - ok: true, - result: makeDetectedResult(REPO_ID, [live], { source: 'session-fallback' }), - _meta: { runtimeId: 'runtime-remote' } - }) - - await store.getState().fetchWorktrees(REPO_ID, { executionHostId: HOST_ID }) - - expect(forgetRemovedForExecutionHostMock).not.toHaveBeenCalled() - }) -}) diff --git a/src/shared/agent-startup-update-suppression.test.ts b/src/shared/agent-startup-update-suppression.test.ts index 0a6087be45b..666b8b327d5 100644 --- a/src/shared/agent-startup-update-suppression.test.ts +++ b/src/shared/agent-startup-update-suppression.test.ts @@ -1,90 +1,8 @@ import { describe, expect, it } from 'vitest' -import type { TuiAgent } from './tui-agent' import { buildAgentStartupPlan } from './tui-agent-startup' import { tokenizeStartupCommand } from './tui-agent-startup-shell' -const ENV_CASES = [ - { agent: 'claude', env: { DISABLE_AUTOUPDATER: '1' } }, - { agent: 'claude-agent-teams', env: { DISABLE_AUTOUPDATER: '1' } }, - { agent: 'opencode', env: { OPENCODE_DISABLE_AUTOUPDATE: 'true' } }, - { agent: 'opencode2', env: { OPENCODE_DISABLE_AUTOUPDATE: 'true' } }, - { agent: 'antigravity', env: { AGY_CLI_DISABLE_AUTO_UPDATE: 'true' } }, - { agent: 'grok', env: { GROK_DISABLE_AUTOUPDATER: '1' } }, - { agent: 'pi', env: { PI_SKIP_VERSION_CHECK: '1' } }, - { agent: 'kimi', env: { KIMI_CODE_NO_AUTO_UPDATE: '1', KIMI_CLI_NO_AUTO_UPDATE: '1' } } -] satisfies { agent: TuiAgent; env: Record }[] - describe('unattended agent startup updates', () => { - it.each(ENV_CASES)( - 'suppresses $agent only in the initial environment and preserves configured values for resume', - ({ agent, env }) => { - const agentEnv = { - KEEP: 'a value with spaces', - ...Object.fromEntries(Object.keys(env).map((key) => [key, '0'])) - } - const input = { - agent, - agentEnv, - prompt: 'Run the automation', - cmdOverrides: {}, - platform: 'linux' as const - } - const ordinary = buildAgentStartupPlan(input) - const unattended = buildAgentStartupPlan({ ...input, suppressStartupUpdatePrompt: true }) - expect(unattended).not.toBeNull() - expect(unattended?.env).toEqual({ ...ordinary?.env, ...env }) - expect(ordinary?.env).toMatchObject(agentEnv) - expect(unattended?.launchConfig).toEqual(ordinary?.launchConfig) - expect(unattended?.launchConfig.agentEnv).toEqual(agentEnv) - expect(agentEnv.KEEP).toBe('a value with spaces') - } - ) - - it.each(['posix', 'powershell', 'cmd'] as const)( - 'quotes launch-only flags for %s without saving them for resume', - (shell) => { - const cases = [ - { agent: 'codex', flags: ['-c', 'check_for_update_on_startup=false'] }, - { agent: 'aider', flags: ['--no-check-update', '--no-show-release-notes'] }, - { agent: 'cursor', flags: ['--disable-auto-update'] } - ] satisfies { agent: TuiAgent; flags: string[] }[] - for (const { agent, flags } of cases) { - const input = { - agent, - prompt: "Review today's changes", - cmdOverrides: {}, - platform: 'win32' as const, - shell - } - const ordinary = buildAgentStartupPlan(input) - const unattended = buildAgentStartupPlan({ ...input, suppressStartupUpdatePrompt: true }) - const parsed = tokenizeStartupCommand(unattended?.launchCommand ?? '', shell) - expect(parsed.ok).toBe(true) - if (!parsed.ok) { - throw new Error(parsed.error) - } - for (const flag of flags) { - expect(parsed.tokens).toContain(flag) - } - expect(unattended?.launchConfig).toEqual(ordinary?.launchConfig) - expect(unattended?.followupPrompt).toBe(ordinary?.followupPrompt) - } - } - ) - - it('does not invent controls for a CLI without a verified launch override', () => { - const input = { - agent: 'gemini' as const, - prompt: '', - allowEmptyPromptLaunch: true, - cmdOverrides: {}, - platform: 'linux' as const - } - expect(buildAgentStartupPlan({ ...input, suppressStartupUpdatePrompt: true })).toEqual( - buildAgentStartupPlan(input) - ) - }) - it.each(['posix', 'powershell', 'cmd'] as const)( 'keeps Aider suppression after configured switches and before positional files in %s', (shell) => {