mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
test: remove 338 low-value cases and 28 suites (#34234)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -6013,7 +6013,7 @@
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime-browser.test.ts src/main/runtime/rpc/methods/browser.test.ts src/renderer/src/lib/file-preview.test.ts src/renderer/src/runtime/web-session-browser-placement.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync.test.ts src/renderer/src/runtime/remote-server-parity.test.ts",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/runtime/web-runtime-browser-materialization.test.ts",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/runtime/web-runtime-browser-capability-cleanup.test.ts",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/browser/doc-preview-grant-registry.test.ts src/main/browser/doc-preview-file-reader.test.ts src/main/browser/doc-preview-protocol.test.ts src/main/browser/doc-preview-guest-policy.test.ts src/main/browser/doc-preview-failure-notice.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/lib/doc-preview-grants.test.ts src/renderer/src/lib/file-preview.test.ts src/renderer/src/lib/file-preview-action-visibility.test.tsx src/main/window/main-window-webview-security.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/browser/doc-preview-grant-registry.test.ts src/main/browser/doc-preview-file-reader.test.ts src/main/browser/doc-preview-protocol.test.ts src/main/browser/doc-preview-guest-policy.test.ts src/main/browser/doc-preview-failure-notice.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/lib/doc-preview-grants.test.ts src/renderer/src/lib/file-preview.test.ts src/main/window/main-window-webview-security.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/browser/browser-session-partition-policies.test.ts src/main/browser/doc-preview-protocol.test.ts",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/browser/doc-preview-download-block-notice.test.ts src/main/browser/browser-session-partition-policies.test.ts src/main/browser/doc-preview-guest-policy.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/browser/browser-manager-guest-policy-profile.test.ts src/main/window/main-window-webview-security.test.ts src/main/browser/doc-preview-guest-policy.test.ts",
|
||||
@@ -6081,7 +6081,6 @@
|
||||
"src/shared/doc-preview-scheme.test.ts",
|
||||
"src/renderer/src/lib/doc-preview-grants.test.ts",
|
||||
"src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx",
|
||||
"src/renderer/src/lib/file-preview-action-visibility.test.tsx",
|
||||
"src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts",
|
||||
"src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts",
|
||||
"src/main/ipc/doc-preview-grant-ipc.test.ts",
|
||||
@@ -6231,12 +6230,6 @@
|
||||
"file": "src/main/browser/doc-preview-failure-notice.test.ts",
|
||||
"assertions": ["413 and 415 map to distinct reasons and everything else stays generic"]
|
||||
},
|
||||
{
|
||||
"file": "src/renderer/src/lib/file-preview-action-visibility.test.tsx",
|
||||
"assertions": [
|
||||
"the Explorer row predicate and the preview plan return one answer for the same file"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts",
|
||||
"assertions": [
|
||||
@@ -6452,7 +6445,8 @@
|
||||
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/doc-preview-grant-registry.test.ts src/main/browser/doc-preview-file-reader.test.ts src/main/browser/doc-preview-protocol.test.ts src/main/browser/doc-preview-guest-policy.test.ts src/main/browser/doc-preview-failure-notice.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/lib/doc-preview-grants.test.ts src/renderer/src/lib/file-preview.test.ts src/renderer/src/lib/file-preview-action-visibility.test.tsx src/main/window/main-window-webview-security.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts",
|
||||
"result": "passed",
|
||||
"durationSeconds": 1.63,
|
||||
"summary": "189/189 over the grant, reader, protocol, guest-policy, scheme-admission, plan-routing, action-visibility, failure-message, external-link and terminal-row contracts. This replaces the 2026-08-27 record of the same command at 174/174, whose thirteenth file was the deleted doc-preview-external-link-bridge.test.ts: the STA-5758 hardening moved that behavior into the reader-facing confirmation (doc-preview-external-link-confirmation.test.ts), which now proves the exact destination is shown and nothing opens on cancel, alongside everything the retired run carried — the stale-mint rejection that must not evict a reopened preview tab, the CSP header and session request filter, the SSH symlink refusal, subframe containment, truncated and over-cap reads, the unservable-asset and empty-file split, the out-of-worktree paired refusal, and the guest deny-only navigation sinks. The count also carries the entry-only rule added on top of the hardening: a grant whose document directory is its own request base — a workspace-root or out-of-workspace document — reads nothing but the entry file until the reader approves a directory, at both the lexical and the canonical containment pass, because that directory is where secrets live and a DNS-prefetch beacon needs no click."
|
||||
"summary": "189/189 over the grant, reader, protocol, guest-policy, scheme-admission, plan-routing, action-visibility, failure-message, external-link and terminal-row contracts. This replaces the 2026-08-27 record of the same command at 174/174, whose thirteenth file was the deleted doc-preview-external-link-bridge.test.ts: the STA-5758 hardening moved that behavior into the reader-facing confirmation (doc-preview-external-link-confirmation.test.ts), which now proves the exact destination is shown and nothing opens on cancel, alongside everything the retired run carried — the stale-mint rejection that must not evict a reopened preview tab, the CSP header and session request filter, the SSH symlink refusal, subframe containment, truncated and over-cap reads, the unservable-asset and empty-file split, the out-of-worktree paired refusal, and the guest deny-only navigation sinks. The count also carries the entry-only rule added on top of the hardening: a grant whose document directory is its own request base — a workspace-root or out-of-workspace document — reads nothing but the entry file until the reader approves a directory, at both the lexical and the canonical containment pass, because that directory is where secrets live and a DNS-prefetch beacon needs no click.",
|
||||
"currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/doc-preview-grant-registry.test.ts src/main/browser/doc-preview-file-reader.test.ts src/main/browser/doc-preview-protocol.test.ts src/main/browser/doc-preview-guest-policy.test.ts src/main/browser/doc-preview-failure-notice.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/lib/doc-preview-grants.test.ts src/renderer/src/lib/file-preview.test.ts src/main/window/main-window-webview-security.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts"
|
||||
},
|
||||
{
|
||||
"date": "2026-08-27",
|
||||
|
||||
@@ -1919,7 +1919,6 @@
|
||||
"src/main/codex/codex-trust-config-mutation-queue.test.ts": 52,
|
||||
"src/main/codex/codex-trust-grant-host.test.ts": 99,
|
||||
"src/main/codex/codex-trust-grant-ledger.test.ts": 70,
|
||||
"src/main/codex/codex-trust-grant-telemetry.test.ts": 159,
|
||||
"src/main/codex/codex-turn-ordinals.test.ts": 74,
|
||||
"src/main/codex/codex-usage-limits.test.ts": 238,
|
||||
"src/main/codex/codex-user-hook-trust-moves.test.ts": 75,
|
||||
@@ -2337,7 +2336,6 @@
|
||||
"src/main/durable-file-write.test.ts": 99,
|
||||
"src/main/electron-updater-mac-staging-contract.test.ts": 141,
|
||||
"src/main/emulator/android/adb-devices.test.ts": 62,
|
||||
"src/main/emulator/android/android-app-control.test.ts": 45,
|
||||
"src/main/emulator/android/android-capability-operations.test.ts": 75,
|
||||
"src/main/emulator/android/android-device-inventory.test.ts": 37,
|
||||
"src/main/emulator/android/android-input-commands.test.ts": 63,
|
||||
@@ -2407,7 +2405,6 @@
|
||||
"src/main/ghostty/theme-resolution.test.ts": 59,
|
||||
"src/main/git-bash.test.ts": 26,
|
||||
"src/main/git/add-sparse-worktree.test.ts": 139,
|
||||
"src/main/git/admission-tier-plumbing.test.ts": 51,
|
||||
"src/main/git/base-ref-search-selector.test.ts": 157,
|
||||
"src/main/git/branch-rename.test.ts": 51,
|
||||
"src/main/git/canonical-repo-key.test.ts": 108,
|
||||
@@ -2996,7 +2993,6 @@
|
||||
"src/main/ipc/pty-claude-agent-view-readiness.test.ts": 3531,
|
||||
"src/main/ipc/pty-painted-cold-restore-ack-layout-owner.test.ts": 2928,
|
||||
"src/main/ipc/pty-spawn-saved-resume-admission.test.ts": 6908,
|
||||
"src/main/ipc/pty/configured-terminal-shell-args.test.ts": 66,
|
||||
"src/main/ipc/pty/daemon-late-exit.test.ts": 6475,
|
||||
"src/main/ipc/pty/delivery/attached-pty-size.test.ts": 68,
|
||||
"src/main/ipc/pty/delivery/interactive-typing-burst.test.ts": 34,
|
||||
@@ -3551,7 +3547,6 @@
|
||||
"src/main/memory/memory-snapshot-buckets.test.ts": 92,
|
||||
"src/main/memory/verified-local-folder-workspaces.test.ts": 50,
|
||||
"src/main/memory/windows-process-sample-parsing.test.ts": 93,
|
||||
"src/main/menu/gpu-acceleration-about-panel.test.ts": 66,
|
||||
"src/main/menu/register-app-menu.test.ts": 133,
|
||||
"src/main/mimo/hook-service.test.ts": 112,
|
||||
"src/main/minimax/minimax-api-key-store.test.ts": 125,
|
||||
@@ -3561,7 +3556,6 @@
|
||||
"src/main/muse-usage/store-snapshot-filtering.test.ts": 471,
|
||||
"src/main/muse/hook-config-json.test.ts": 48,
|
||||
"src/main/muse/hook-service.test.ts": 91,
|
||||
"src/main/muse/hook-settings.test.ts": 72,
|
||||
"src/main/native-chat/agent-model-catalog/agent-model-catalog-discovery-freshness.test.ts": 110,
|
||||
"src/main/native-chat/agent-model-catalog/agent-model-catalog-entry.test.ts": 46,
|
||||
"src/main/native-chat/agent-model-catalog/agent-model-catalog-independent-discovery.test.ts": 121,
|
||||
@@ -4694,7 +4688,6 @@
|
||||
"src/main/rate-limits/cursor-fetcher.test.ts": 241,
|
||||
"src/main/rate-limits/cursor-session-token.test.ts": 63,
|
||||
"src/main/rate-limits/cursor-usage-mapping.test.ts": 92,
|
||||
"src/main/rate-limits/gemini-bucket-formatting.test.ts": 46,
|
||||
"src/main/rate-limits/gemini-oauth-sources.test.ts": 64,
|
||||
"src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts": 105,
|
||||
"src/main/rate-limits/gemini-usage-fetcher.test.ts": 82,
|
||||
@@ -5382,7 +5375,6 @@
|
||||
"src/main/runtime/rpc/methods/linear.test.ts": 513,
|
||||
"src/main/runtime/rpc/methods/managed-runtime-install.test.ts": 994,
|
||||
"src/main/runtime/rpc/methods/managed-server.test.ts": 374,
|
||||
"src/main/runtime/rpc/methods/mobile-project-sections.test.ts": 825,
|
||||
"src/main/runtime/rpc/methods/mobile-web-bundle-range.test.ts": 1942,
|
||||
"src/main/runtime/rpc/methods/mobile-web-bundle-read-concurrency.test.ts": 492,
|
||||
"src/main/runtime/rpc/methods/mobile-web-bundle-window-refusals.test.ts": 365,
|
||||
@@ -6270,7 +6262,6 @@
|
||||
"src/main/source-control/repo-default-branch.test.ts": 87,
|
||||
"src/main/source-control/repo-remote-head-branch.test.ts": 103,
|
||||
"src/main/source-control/stacked-hosted-review-creation.test.ts": 46,
|
||||
"src/main/speech/model-catalog.test.ts": 45,
|
||||
"src/main/speech/model-manager-download-error.test.ts": 96,
|
||||
"src/main/speech/model-manager-download-resume.test.ts": 1162,
|
||||
"src/main/speech/model-manager-progress-callback.test.ts": 54,
|
||||
@@ -6481,7 +6472,6 @@
|
||||
"src/main/ssh/ssh-remote-windows-command-line-limit.test.ts": 177,
|
||||
"src/main/ssh/ssh-request-outcome-verdict.test.ts": 40,
|
||||
"src/main/ssh/ssh-security-key-identity.test.ts": 95,
|
||||
"src/main/ssh/ssh-session-limit-error.test.ts": 43,
|
||||
"src/main/ssh/ssh-system-transport.integration.test.ts": 280,
|
||||
"src/main/ssh/ssh-tcp-forwarding-probe.test.ts": 1020,
|
||||
"src/main/ssh/ssh-windows-host-cells.test.ts": 57,
|
||||
@@ -6571,7 +6561,6 @@
|
||||
"src/main/startup/run-electron-vite-dev.test.ts": 899,
|
||||
"src/main/startup/select-linux-keyring-backend.test.ts": 63,
|
||||
"src/main/startup/serve-desktop-activation.test.ts": 32,
|
||||
"src/main/startup/serve-mode-argv-cli-redirect-order.test.ts": 140,
|
||||
"src/main/startup/serve-mode-argv.test.ts": 70,
|
||||
"src/main/startup/serve-native-quit-guard.test.ts": 54,
|
||||
"src/main/startup/serve-options.test.ts": 59,
|
||||
@@ -7910,7 +7899,6 @@
|
||||
"src/renderer/src/components/mobile/use-mobile-pairing-address-preference.test.tsx": 235,
|
||||
"src/renderer/src/components/native-chat-launch-resume-decision.test.tsx": 1631,
|
||||
"src/renderer/src/components/native-chat-restart-action-notifications.test.ts": 153,
|
||||
"src/renderer/src/components/native-chat-resume-activity-label.test.ts": 117,
|
||||
"src/renderer/src/components/native-chat-resume-bulk-progress.test.tsx": 1690,
|
||||
"src/renderer/src/components/native-chat-resume-failed-chat-watch.test.ts": 2022,
|
||||
"src/renderer/src/components/native-chat-resume-failure-guidance.test.ts": 111,
|
||||
@@ -8586,9 +8574,7 @@
|
||||
"src/renderer/src/components/right-sidebar/source-control-recovery-admission.test.ts": 131,
|
||||
"src/renderer/src/components/right-sidebar/source-control-section-order.test.ts": 69,
|
||||
"src/renderer/src/components/right-sidebar/source-control-split-open.test.ts": 52,
|
||||
"src/renderer/src/components/right-sidebar/source-control-status-sort.test.ts": 25,
|
||||
"src/renderer/src/components/right-sidebar/source-control-submodule-expansion.test.ts": 46,
|
||||
"src/renderer/src/components/right-sidebar/source-control-text-generation-defaults.test.ts": 102,
|
||||
"src/renderer/src/components/right-sidebar/source-control-too-many-changes-banner.test.tsx": 632,
|
||||
"src/renderer/src/components/right-sidebar/source-control-tree.test.ts": 57,
|
||||
"src/renderer/src/components/right-sidebar/source-control/ai/use-ai.operation-recovery.test.tsx": 1955,
|
||||
@@ -8741,7 +8727,6 @@
|
||||
"src/renderer/src/components/settings/NotificationsPane.hidden-workspaces.test.tsx": 1362,
|
||||
"src/renderer/src/components/settings/NotificationsPane.test.tsx": 1723,
|
||||
"src/renderer/src/components/settings/OpenInMenuSetting.test.ts": 148,
|
||||
"src/renderer/src/components/settings/OrcaAccountSettingsPane.test.tsx": 405,
|
||||
"src/renderer/src/components/settings/OrchestrationPane.test.tsx": 1824,
|
||||
"src/renderer/src/components/settings/OrchestrationSkillAgentCoverage.test.tsx": 247,
|
||||
"src/renderer/src/components/settings/PluginConsentDialog.test.tsx": 1638,
|
||||
@@ -8809,12 +8794,10 @@
|
||||
"src/renderer/src/components/settings/ZcodePlanAccountsSection.test.tsx": 858,
|
||||
"src/renderer/src/components/settings/account-add-cancel-target.test.ts": 105,
|
||||
"src/renderer/src/components/settings/accounts-pane-opencode-credentials.test.ts": 464,
|
||||
"src/renderer/src/components/settings/accounts-search.test.ts": 32,
|
||||
"src/renderer/src/components/settings/agent-availability-settings.test.ts": 108,
|
||||
"src/renderer/src/components/settings/agent-default-env-draft.test.ts": 32,
|
||||
"src/renderer/src/components/settings/agent-launch-settings-transport.test.ts": 151,
|
||||
"src/renderer/src/components/settings/agent-permission-conversion-persistence.test.tsx": 8787,
|
||||
"src/renderer/src/components/settings/appearance-status-bar-search.test.ts": 72,
|
||||
"src/renderer/src/components/settings/bitbucket-integration-card.test.tsx": 421,
|
||||
"src/renderer/src/components/settings/browser-link-routing-localization.test.ts": 216,
|
||||
"src/renderer/src/components/settings/browser-search.test.ts": 323,
|
||||
@@ -8832,7 +8815,6 @@
|
||||
"src/renderer/src/components/settings/general-editor-search.test.ts": 136,
|
||||
"src/renderer/src/components/settings/general-preview-tab-search.test.ts": 136,
|
||||
"src/renderer/src/components/settings/git-description-templates.test.tsx": 1759,
|
||||
"src/renderer/src/components/settings/integrations-pane-status.test.ts": 29,
|
||||
"src/renderer/src/components/settings/jira-integration-card.test.tsx": 459,
|
||||
"src/renderer/src/components/settings/linear-agent-skill-install-cta.test.tsx": 1871,
|
||||
"src/renderer/src/components/settings/managed-server-row-parts.test.tsx": 437,
|
||||
@@ -8842,7 +8824,6 @@
|
||||
"src/renderer/src/components/settings/native-chat-provider-choice.test.tsx": 619,
|
||||
"src/renderer/src/components/settings/notification-host-pending-save.test.tsx": 1663,
|
||||
"src/renderer/src/components/settings/plugin-install-source.test.ts": 110,
|
||||
"src/renderer/src/components/settings/provider-account-scope.test.ts": 109,
|
||||
"src/renderer/src/components/settings/provider-account-visibility.test.ts": 49,
|
||||
"src/renderer/src/components/settings/repository-source-control-ai-global-ux.test.ts": 585,
|
||||
"src/renderer/src/components/settings/repository/RepositoryJiraProjectSection.test.tsx": 292,
|
||||
@@ -9031,7 +9012,6 @@
|
||||
"src/renderer/src/components/sidebar/child-workspaces-toggle-target.test.ts": 1665,
|
||||
"src/renderer/src/components/sidebar/child-workspaces-toggle-visibility.test.ts": 1793,
|
||||
"src/renderer/src/components/sidebar/clone-defaults.test.ts": 39,
|
||||
"src/renderer/src/components/sidebar/create-project-defaults.test.ts": 37,
|
||||
"src/renderer/src/components/sidebar/default-branch-folder-project-root.test.ts": 100,
|
||||
"src/renderer/src/components/sidebar/default-branch-visible-under-hide-sleeping.test.ts": 158,
|
||||
"src/renderer/src/components/sidebar/delete-worktree-always-force-preference.test.tsx": 1368,
|
||||
@@ -9352,9 +9332,7 @@
|
||||
"src/renderer/src/components/status-bar/provider-segment-stale-warning.test.tsx": 261,
|
||||
"src/renderer/src/components/status-bar/remote-host-connection-status.test.ts": 123,
|
||||
"src/renderer/src/components/status-bar/resource-manager-exit-render-budget.test.tsx": 1534,
|
||||
"src/renderer/src/components/status-bar/resource-manager-terminal-copy.test.ts": 78,
|
||||
"src/renderer/src/components/status-bar/resource-manager-worktree-target.test.ts": 34,
|
||||
"src/renderer/src/components/status-bar/resource-memory-metric-copy.test.ts": 55,
|
||||
"src/renderer/src/components/status-bar/resource-pty-view-model.test.ts": 142,
|
||||
"src/renderer/src/components/status-bar/resource-pty-virtualization.test.tsx": 645,
|
||||
"src/renderer/src/components/status-bar/resource-session-bindings.test.ts": 75,
|
||||
@@ -9367,7 +9345,6 @@
|
||||
"src/renderer/src/components/status-bar/resource-usage-space-scan-ready.test.ts": 38,
|
||||
"src/renderer/src/components/status-bar/runtime-host-resources.test.ts": 45,
|
||||
"src/renderer/src/components/status-bar/ssh-status-menu-rows.test.ts": 48,
|
||||
"src/renderer/src/components/status-bar/status-bar-agent-gating.test.ts": 27,
|
||||
"src/renderer/src/components/status-bar/status-bar-antigravity-segment.test.tsx": 968,
|
||||
"src/renderer/src/components/status-bar/status-bar-copy-localization.test.tsx": 885,
|
||||
"src/renderer/src/components/status-bar/status-bar-cursor-segment.test.tsx": 1503,
|
||||
@@ -9385,7 +9362,6 @@
|
||||
"src/renderer/src/components/status-bar/usage-provider-settings-target.test.ts": 34,
|
||||
"src/renderer/src/components/status-bar/usage-roster-row-state.test.ts": 71,
|
||||
"src/renderer/src/components/status-bar/usage-unit-label-parity.test.tsx": 335,
|
||||
"src/renderer/src/components/status-bar/usage-unit-label.test.ts": 90,
|
||||
"src/renderer/src/components/status-bar/use-owner-usage-rate-limits.test.tsx": 261,
|
||||
"src/renderer/src/components/status-bar/use-resource-memory-snapshot.test.tsx": 198,
|
||||
"src/renderer/src/components/status-bar/use-resource-pty-actions.test.tsx": 486,
|
||||
@@ -9540,7 +9516,6 @@
|
||||
"src/renderer/src/components/task-page/task-page-localized-options.test.ts": 211,
|
||||
"src/renderer/src/components/task-page/task-page-mutation-page-allocation.test.ts": 128,
|
||||
"src/renderer/src/components/task-page/task-page-new-issue-draft.test.ts": 76,
|
||||
"src/renderer/src/components/task-page/task-page-repo-source-context.test.ts": 407,
|
||||
"src/renderer/src/components/task-page/task-page-repo-source-divergence.test.ts": 1043,
|
||||
"src/renderer/src/components/task-page/task-page-task-source-host-availability.test.ts": 401,
|
||||
"src/renderer/src/components/task-page/task-page-work-item-pagination.test.ts": 56,
|
||||
@@ -10636,7 +10611,6 @@
|
||||
"src/renderer/src/lib/feedback-image-shrink.test.ts": 40,
|
||||
"src/renderer/src/lib/file-artifact-source-key.test.ts": 969,
|
||||
"src/renderer/src/lib/file-owner-runtime-target.test.ts": 230,
|
||||
"src/renderer/src/lib/file-preview-action-visibility.test.tsx": 1253,
|
||||
"src/renderer/src/lib/file-preview-link-open.test.ts": 2059,
|
||||
"src/renderer/src/lib/file-preview.test.ts": 1742,
|
||||
"src/renderer/src/lib/file-search-result-owner.test.ts": 33,
|
||||
@@ -10764,7 +10738,6 @@
|
||||
"src/renderer/src/lib/monaco-languages/textmate-language-registration.test.ts": 37,
|
||||
"src/renderer/src/lib/monaco-languages/textmate-token-provider.test.ts": 233,
|
||||
"src/renderer/src/lib/monaco-languages/zig-highlighting.test.ts": 4871,
|
||||
"src/renderer/src/lib/monaco-peek-preview-options.test.ts": 777,
|
||||
"src/renderer/src/lib/monaco-setup-steps.test.ts": 48,
|
||||
"src/renderer/src/lib/monospace-font-family.test.ts": 36,
|
||||
"src/renderer/src/lib/native-chat-cli-fork-preparation.test.ts": 2392,
|
||||
@@ -13079,7 +13052,6 @@
|
||||
"src/shared/subprocess-stdin-write.test.ts": 17,
|
||||
"src/shared/synthetic-agent-title.test.ts": 39,
|
||||
"src/shared/tab-has-live-pty.test.ts": 33,
|
||||
"src/shared/tab-title-resolution.test.ts": 46,
|
||||
"src/shared/tailnet-address.test.ts": 34,
|
||||
"src/shared/task-providers.test.ts": 44,
|
||||
"src/shared/task-query.test.ts": 41,
|
||||
|
||||
@@ -51,17 +51,6 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('noticeDocPreviewDownloadBlocked', () => {
|
||||
it('tells the shell which preview had a download refused', async () => {
|
||||
const notice = await loadNotifier()
|
||||
|
||||
notice(guestBoundTo(GRANT_ID))
|
||||
|
||||
expect(mocks.publishDocPreviewFailure).toHaveBeenCalledWith({
|
||||
grantId: GRANT_ID,
|
||||
reason: 'download-blocked'
|
||||
})
|
||||
})
|
||||
|
||||
it('says it once however often the document asks', async () => {
|
||||
const notice = await loadNotifier()
|
||||
const guest = guestBoundTo(GRANT_ID)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { DOC_PREVIEW_LOAD_FAILURE_CHANNEL } from '../../shared/doc-preview-scheme'
|
||||
import { publishDocPreviewFailure, setDocPreviewFailureSink } from './doc-preview-failure-notice'
|
||||
|
||||
afterEach(() => {
|
||||
@@ -8,33 +7,6 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('publishDocPreviewFailure', () => {
|
||||
it('sends the grant, path, and reason on the failure channel', () => {
|
||||
const send = vi.fn()
|
||||
setDocPreviewFailureSink({ send })
|
||||
|
||||
publishDocPreviewFailure({
|
||||
grantId: 'a'.repeat(32),
|
||||
relativePath: 'index.html',
|
||||
reason: 'too-large'
|
||||
})
|
||||
|
||||
expect(send).toHaveBeenCalledWith(DOC_PREVIEW_LOAD_FAILURE_CHANNEL, {
|
||||
grantId: 'a'.repeat(32),
|
||||
relativePath: 'index.html',
|
||||
reason: 'too-large'
|
||||
})
|
||||
})
|
||||
|
||||
it('is a no-op with no sink registered', () => {
|
||||
expect(() =>
|
||||
publishDocPreviewFailure({
|
||||
grantId: 'b'.repeat(32),
|
||||
relativePath: 'index.html',
|
||||
reason: 'unreadable'
|
||||
})
|
||||
).not.toThrow()
|
||||
})
|
||||
|
||||
it('sends nothing into a destroyed window', () => {
|
||||
const send = vi.fn()
|
||||
setDocPreviewFailureSink({ send, isDestroyed: () => true })
|
||||
|
||||
@@ -38,11 +38,6 @@ describe('parseCodexLoginAuthUrl', () => {
|
||||
).toBe(AUTH_URL)
|
||||
})
|
||||
|
||||
it('reports nothing for output without a link yet', () => {
|
||||
expect(parseCodexLoginAuthUrl('')).toBe(null)
|
||||
expect(parseCodexLoginAuthUrl('Codex login failed: network unreachable\n')).toBe(null)
|
||||
})
|
||||
|
||||
it('offers no link at all rather than an unrelated one when the notice is missing', () => {
|
||||
expect(
|
||||
parseCodexLoginAuthUrl('A new version of codex is available: https://openai.com/codex\n')
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { WSL_CODEX_NOT_FOUND_MESSAGE } from '../codex-accounts/wsl-codex-command'
|
||||
import { CodexAppServerTimeoutError } from './codex-app-server-client'
|
||||
import { classifyCodexTrustGrantError } from './codex-trust-grant-telemetry'
|
||||
|
||||
describe('classifyCodexTrustGrantError', () => {
|
||||
it.each([
|
||||
[new CodexAppServerTimeoutError('entry exceeded 20000ms session deadline'), 'timeout'],
|
||||
[new Error('spawn codex ENOENT'), 'binary-missing'],
|
||||
[new Error('spawn /Users/ada/.local/bin/codex ENOENT'), 'binary-missing'],
|
||||
[
|
||||
new Error(
|
||||
`codex app-server exited before completing the session: ${WSL_CODEX_NOT_FOUND_MESSAGE}`
|
||||
),
|
||||
'binary-missing'
|
||||
],
|
||||
[new Error('spawn wsl.exe ENOENT'), 'unexpected'],
|
||||
[
|
||||
new Error("ENOENT: no such file or directory, open '/home/ada/.codex/config.toml'"),
|
||||
'unexpected'
|
||||
],
|
||||
[new Error('codex trust-grant entry bundle not found'), 'entry-failed'],
|
||||
[new Error('codex trust-grant entry produced no result (exit 1)'), 'entry-failed'],
|
||||
[
|
||||
new Error('codex app-server exited before completing the session: panicked at main.rs'),
|
||||
'early-exit'
|
||||
],
|
||||
[new Error('codex app-server config/batchWrite failed: unknown key'), 'rpc-failed'],
|
||||
[new Error('write EPIPE'), 'unexpected'],
|
||||
['not an error object', 'unexpected']
|
||||
] as const)('classifies %s as %s', (error, expected) => {
|
||||
expect(classifyCodexTrustGrantError(error)).toBe(expected)
|
||||
})
|
||||
|
||||
it('keeps an ENOENT-mentioning stderr tail classified as early-exit', () => {
|
||||
expect(
|
||||
classifyCodexTrustGrantError(
|
||||
new Error('codex app-server exited before completing the session: ENOENT in codex output')
|
||||
)
|
||||
).toBe('early-exit')
|
||||
})
|
||||
})
|
||||
@@ -1,74 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { installApkArgs, launchAppArgs } from './android-app-control'
|
||||
|
||||
describe('installApkArgs', () => {
|
||||
it('builds install args without -r by default', () => {
|
||||
expect(installApkArgs('emulator-5554', '/tmp/app.apk')).toEqual([
|
||||
'-s',
|
||||
'emulator-5554',
|
||||
'install',
|
||||
'/tmp/app.apk'
|
||||
])
|
||||
})
|
||||
|
||||
it('omits -r when reinstall is false', () => {
|
||||
expect(installApkArgs('emulator-5554', '/tmp/app.apk', { reinstall: false })).toEqual([
|
||||
'-s',
|
||||
'emulator-5554',
|
||||
'install',
|
||||
'/tmp/app.apk'
|
||||
])
|
||||
})
|
||||
|
||||
it('adds -r before the apk path when reinstall is true', () => {
|
||||
expect(installApkArgs('emulator-5554', '/tmp/app.apk', { reinstall: true })).toEqual([
|
||||
'-s',
|
||||
'emulator-5554',
|
||||
'install',
|
||||
'-r',
|
||||
'/tmp/app.apk'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('launchAppArgs', () => {
|
||||
it('uses am start with the explicit package/activity component', () => {
|
||||
expect(launchAppArgs('emulator-5554', 'com.example.app', '.MainActivity')).toEqual([
|
||||
'-s',
|
||||
'emulator-5554',
|
||||
'shell',
|
||||
'am',
|
||||
'start',
|
||||
'-n',
|
||||
'com.example.app/.MainActivity'
|
||||
])
|
||||
})
|
||||
|
||||
it('falls back to monkey LAUNCHER when no activity is given', () => {
|
||||
expect(launchAppArgs('emulator-5554', 'com.example.app')).toEqual([
|
||||
'-s',
|
||||
'emulator-5554',
|
||||
'shell',
|
||||
'monkey',
|
||||
'-p',
|
||||
'com.example.app',
|
||||
'-c',
|
||||
'android.intent.category.LAUNCHER',
|
||||
'1'
|
||||
])
|
||||
})
|
||||
|
||||
it('falls back to monkey when the activity is an empty/whitespace string', () => {
|
||||
expect(launchAppArgs('emulator-5554', 'com.example.app', ' ')).toEqual([
|
||||
'-s',
|
||||
'emulator-5554',
|
||||
'shell',
|
||||
'monkey',
|
||||
'-p',
|
||||
'com.example.app',
|
||||
'-c',
|
||||
'android.intent.category.LAUNCHER',
|
||||
'1'
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -3,34 +3,6 @@ import { EmulatorError } from '../emulator-errors'
|
||||
import { permissionArgs } from './android-permissions'
|
||||
|
||||
describe('permissionArgs', () => {
|
||||
it('builds grant args', () => {
|
||||
expect(
|
||||
permissionArgs('emulator-5554', 'grant', 'com.example.app', 'android.permission.CAMERA')
|
||||
).toEqual([
|
||||
'-s',
|
||||
'emulator-5554',
|
||||
'shell',
|
||||
'pm',
|
||||
'grant',
|
||||
'com.example.app',
|
||||
'android.permission.CAMERA'
|
||||
])
|
||||
})
|
||||
|
||||
it('builds revoke args', () => {
|
||||
expect(
|
||||
permissionArgs('emulator-5554', 'revoke', 'com.example.app', 'android.permission.CAMERA')
|
||||
).toEqual([
|
||||
'-s',
|
||||
'emulator-5554',
|
||||
'shell',
|
||||
'pm',
|
||||
'revoke',
|
||||
'com.example.app',
|
||||
'android.permission.CAMERA'
|
||||
])
|
||||
})
|
||||
|
||||
it('builds reset-permissions args without a package (it resets all apps)', () => {
|
||||
expect(permissionArgs('emulator-5554', 'reset', 'com.example.app')).toEqual([
|
||||
'-s',
|
||||
@@ -41,12 +13,6 @@ describe('permissionArgs', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('ignores the package and permission for reset', () => {
|
||||
expect(
|
||||
permissionArgs('emulator-5554', 'reset', 'com.example.app', 'android.permission.CAMERA')
|
||||
).toEqual(['-s', 'emulator-5554', 'shell', 'pm', 'reset-permissions'])
|
||||
})
|
||||
|
||||
it('throws EmulatorError with code emulator_error when grant has no permission', () => {
|
||||
expect(() => permissionArgs('emulator-5554', 'grant', 'com.example.app')).toThrowError(
|
||||
EmulatorError
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { hostedReviewOptionArgs } from '../github/pr-refresh-candidate-policy'
|
||||
import { getHostedReviewLocalGitOptions } from '../source-control/hosted-review-git-options'
|
||||
import { gitOptionsForWorktree, gitReadOptionsForWorktree } from './git-runtime-options'
|
||||
|
||||
describe('git admission tier plumbing', () => {
|
||||
it('preserves tiers through both runtime option constructors', () => {
|
||||
expect(
|
||||
gitOptionsForWorktree('/repo', { wslDistro: 'Ubuntu', admissionTier: 'interactive' })
|
||||
).toEqual({ cwd: '/repo', wslDistro: 'Ubuntu', admissionTier: 'interactive' })
|
||||
expect(
|
||||
gitReadOptionsForWorktree('/repo', { wslDistro: 'Ubuntu', admissionTier: 'background' })
|
||||
).toEqual({
|
||||
cwd: '/repo',
|
||||
wslDistro: 'Ubuntu',
|
||||
admissionTier: 'background',
|
||||
preferWslDirectGit: true
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves the hosted-review execution tier beside WSL routing', () => {
|
||||
expect(
|
||||
getHostedReviewLocalGitOptions({
|
||||
localGitExecOptions: { wslDistro: 'Ubuntu', admissionTier: 'interactive' }
|
||||
})
|
||||
).toEqual({ wslDistro: 'Ubuntu', admissionTier: 'interactive' })
|
||||
})
|
||||
|
||||
it.each([
|
||||
['manual', 'interactive'],
|
||||
['visible', 'background'],
|
||||
['active', 'background'],
|
||||
['post-push', 'background'],
|
||||
['swr', 'background']
|
||||
] as const)('maps PR refresh reason %s to %s admission', (reason, admissionTier) => {
|
||||
const [options] = hostedReviewOptionArgs(
|
||||
{
|
||||
localGitOptions: { wslDistro: 'Ubuntu' },
|
||||
linkedPRNumber: null,
|
||||
fallbackPRNumber: null,
|
||||
fallbackPRSource: null,
|
||||
currentHeadOid: null
|
||||
},
|
||||
reason
|
||||
)
|
||||
expect(options?.localGitExecOptions).toEqual({ wslDistro: 'Ubuntu', admissionTier })
|
||||
})
|
||||
})
|
||||
@@ -5,19 +5,6 @@ import {
|
||||
} from './fetch-error-classification'
|
||||
|
||||
describe('isMissingRemoteRefGitError', () => {
|
||||
it('matches missing remote ref messages', () => {
|
||||
expect(
|
||||
isMissingRemoteRefGitError(
|
||||
new Error('fatal: could not find remote ref refs/heads/feature/test')
|
||||
)
|
||||
).toBe(true)
|
||||
expect(
|
||||
isMissingRemoteRefGitError(
|
||||
new Error("fatal: couldn't find remote ref refs/heads/feature/test")
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('does not match auth or network failures', () => {
|
||||
expect(isMissingRemoteRefGitError(new Error('fatal: Authentication failed'))).toBe(false)
|
||||
expect(
|
||||
|
||||
@@ -26,7 +26,7 @@ vi.mock('./project-error-classification', () => ({
|
||||
rateLimitedError: vi.fn()
|
||||
}))
|
||||
|
||||
import { listAssignableUsersBySlug, listLabelsBySlug } from './repository-field-options'
|
||||
import { listLabelsBySlug } from './repository-field-options'
|
||||
|
||||
describe('repository field options', () => {
|
||||
beforeEach(() => {
|
||||
@@ -49,25 +49,4 @@ describe('repository field options', () => {
|
||||
)
|
||||
expect(releaseMock).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('merges seed assignees after paginated provider results', async () => {
|
||||
ghExecFileAsyncMock.mockResolvedValue({
|
||||
stdout: '{"login":"octo","name":null,"avatarUrl":"octo.png"}\n',
|
||||
stderr: ''
|
||||
})
|
||||
|
||||
const result = await listAssignableUsersBySlug({
|
||||
owner: 'acme',
|
||||
repo: 'widgets',
|
||||
seedLogins: ['octo', 'hubot']
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
users: [
|
||||
{ login: 'octo', name: null, avatarUrl: 'octo.png' },
|
||||
{ login: 'hubot', name: null, avatarUrl: '' }
|
||||
]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,18 +13,6 @@ describe('getGrokAccountStatus', () => {
|
||||
vi.mocked(isGrokAccessTokenFresh).mockReturnValue(true)
|
||||
})
|
||||
|
||||
it('reports unsigned status when the Grok auth file is missing', () => {
|
||||
vi.mocked(readGrokAuthSession).mockReturnValue({ status: 'missing' })
|
||||
|
||||
expect(getGrokAccountStatus()).toEqual({
|
||||
signedIn: false,
|
||||
email: null,
|
||||
teamId: null,
|
||||
tokenFresh: false,
|
||||
error: null
|
||||
})
|
||||
})
|
||||
|
||||
it('reports auth read errors without exposing token fields', () => {
|
||||
vi.mocked(readGrokAuthSession).mockReturnValue({
|
||||
status: 'error',
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { resolveConfiguredTerminalShellArgs } from './configured-terminal-shell-args'
|
||||
|
||||
const profile = {
|
||||
connectionId: undefined,
|
||||
requestedShellOverride: undefined,
|
||||
launchCommand: undefined
|
||||
}
|
||||
|
||||
describe('resolveConfiguredTerminalShellArgs', () => {
|
||||
it('forwards the configured argument list, including an explicit empty one', () => {
|
||||
expect(
|
||||
resolveConfiguredTerminalShellArgs({
|
||||
...profile,
|
||||
settings: { terminalDefaultShell: '/bin/bash', terminalDefaultShellArgs: [] }
|
||||
})
|
||||
).toEqual([])
|
||||
expect(
|
||||
resolveConfiguredTerminalShellArgs({
|
||||
...profile,
|
||||
settings: {
|
||||
terminalDefaultShell: '/bin/bash',
|
||||
terminalDefaultShellArgs: ['--rcfile', '/tmp/rc']
|
||||
}
|
||||
})
|
||||
).toEqual(['--rcfile', '/tmp/rc'])
|
||||
})
|
||||
|
||||
it('falls back to the controlled default when no profile is configured', () => {
|
||||
expect(
|
||||
resolveConfiguredTerminalShellArgs({
|
||||
...profile,
|
||||
settings: { terminalDefaultShell: '/bin/bash' }
|
||||
})
|
||||
).toBeUndefined()
|
||||
expect(
|
||||
resolveConfiguredTerminalShellArgs({
|
||||
...profile,
|
||||
settings: { terminalDefaultShell: ' ', terminalDefaultShellArgs: [] }
|
||||
})
|
||||
).toBeUndefined()
|
||||
expect(resolveConfiguredTerminalShellArgs({ ...profile, settings: undefined })).toBeUndefined()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ connectionId: 'ssh-host' },
|
||||
{ requestedShellOverride: '/bin/fish' },
|
||||
{ launchCommand: 'codex' }
|
||||
])('ignores the profile for %o', (overrides) => {
|
||||
expect(
|
||||
resolveConfiguredTerminalShellArgs({
|
||||
...profile,
|
||||
...overrides,
|
||||
settings: { terminalDefaultShell: '/bin/bash', terminalDefaultShellArgs: [] }
|
||||
})
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -22,12 +22,6 @@ async function resolveSpawnIntent(args: PtySpawnIpcArgs): Promise<unknown> {
|
||||
const PANE = { cols: 80, rows: 24 }
|
||||
|
||||
describe('renderer pty spawn: declared spawn intent', () => {
|
||||
it('hands a known intent to the provider', async () => {
|
||||
await expect(resolveSpawnIntent({ ...PANE, spawnIntent: 'user-resume' })).resolves.toBe(
|
||||
'user-resume'
|
||||
)
|
||||
})
|
||||
|
||||
it('treats a missing or unknown intent as undeclared', async () => {
|
||||
await expect(resolveSpawnIntent(PANE)).resolves.toBeUndefined()
|
||||
await expect(resolveSpawnIntent({ ...PANE, spawnIntent: 'urgent' })).resolves.toBeUndefined()
|
||||
|
||||
@@ -22,40 +22,6 @@ afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('keeps disabled spawn timing silent', () => {
|
||||
vi.stubEnv('ORCA_PTY_SPAWN_TIMING', '0')
|
||||
const timing = createPtySpawnTiming()
|
||||
timing.mark('provider_spawn')
|
||||
timing.log('pty-1')
|
||||
expect(records).toEqual([])
|
||||
expect(console.log).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('writes numeric monotonic phase durations through the existing local trace sink', () => {
|
||||
vi.stubEnv('ORCA_PTY_SPAWN_TIMING', '1')
|
||||
const timing = createPtySpawnTiming()
|
||||
vi.advanceTimersByTime(25)
|
||||
timing.mark('preflight')
|
||||
vi.advanceTimersByTime(80)
|
||||
timing.mark('provider_spawn')
|
||||
timing.log('pty-1', { daemon: true, reattach: false })
|
||||
expect(records).toEqual([
|
||||
expect.objectContaining({
|
||||
name: 'pty.spawn.timing',
|
||||
attributes: expect.objectContaining({
|
||||
ptyId: 'pty-1',
|
||||
totalMs: 105,
|
||||
phaseDurations: { preflight: 25, provider_spawn: 80 },
|
||||
daemon: true,
|
||||
reattach: false
|
||||
})
|
||||
})
|
||||
])
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('total=105ms preflight=25ms provider_spawn=80ms')
|
||||
)
|
||||
})
|
||||
|
||||
it('does not fail a successful spawn when the diagnostic sink throws', () => {
|
||||
vi.stubEnv('ORCA_PTY_SPAWN_TIMING', '1')
|
||||
setActiveSink({
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
createGpuAccelerationAboutPanelOptions,
|
||||
describeGpuAcceleration
|
||||
} from './gpu-acceleration-about-panel'
|
||||
|
||||
describe('GPU acceleration About panel', () => {
|
||||
it.each([
|
||||
['enabled', 'Enabled'],
|
||||
['disabled_off', 'Disabled'],
|
||||
['disabled_software', 'Software rendering'],
|
||||
['unavailable_software', 'Software rendering'],
|
||||
['unavailable', 'Unavailable'],
|
||||
['undefined', 'Status unavailable']
|
||||
])('describes Chromium compositing status %s', (gpu_compositing, expected) => {
|
||||
expect(describeGpuAcceleration({ gpu_compositing }, false)).toBe(expected)
|
||||
})
|
||||
|
||||
it('gives Safe Graphics Mode precedence over Chromium status', () => {
|
||||
expect(describeGpuAcceleration({ gpu_compositing: 'enabled' }, true)).toBe(
|
||||
'Disabled (Safe Graphics Mode)'
|
||||
)
|
||||
})
|
||||
|
||||
it('reports status as unavailable until Chromium publishes GPU information', () => {
|
||||
expect(describeGpuAcceleration(null, false)).toBe('Status unavailable')
|
||||
})
|
||||
|
||||
it('uses the Linux-visible copyright field', () => {
|
||||
expect(
|
||||
createGpuAccelerationAboutPanelOptions({
|
||||
appName: 'Orca',
|
||||
appVersion: '1.2.3',
|
||||
platform: 'linux',
|
||||
gpuFallbackActive: false,
|
||||
gpuFeatureStatus: { gpu_compositing: 'enabled' }
|
||||
})
|
||||
).toEqual({
|
||||
applicationName: 'Orca',
|
||||
applicationVersion: '1.2.3',
|
||||
copyright: 'GPU acceleration: Enabled'
|
||||
})
|
||||
})
|
||||
|
||||
it.each(['darwin', 'win32'] satisfies NodeJS.Platform[])('uses credits on %s', (platform) => {
|
||||
expect(
|
||||
createGpuAccelerationAboutPanelOptions({
|
||||
appName: 'Orca',
|
||||
appVersion: '1.2.3',
|
||||
platform,
|
||||
gpuFallbackActive: true,
|
||||
gpuFeatureStatus: null
|
||||
})
|
||||
).toEqual({
|
||||
applicationName: 'Orca',
|
||||
applicationVersion: '1.2.3',
|
||||
credits: 'GPU acceleration: Disabled (Safe Graphics Mode)'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -6,11 +6,6 @@ import {
|
||||
} from './hook-config-json'
|
||||
|
||||
describe('muse hook-config-json', () => {
|
||||
it('creates a fresh settings file with the required schema_version', () => {
|
||||
const parsed = parseMuseSettingsText(serializeMuseSettings(null, '/x/muse-hooks.json'), 'test')
|
||||
expect(parsed?.managed_hooks_env_vars).toEqual(MUSE_MANAGED_HOOK_ENV_VARS)
|
||||
})
|
||||
|
||||
it('sets the pointer while preserving user keys and formatting', () => {
|
||||
const original = '{\n "schema_version": 1,\n "model": "muse-spark-1.2"\n}\n'
|
||||
const next = serializeMuseSettings(original, '/x/muse-hooks.json')
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
isSafeUnicodeWindowsBatchHookPath,
|
||||
wrapWindowsHookCommand
|
||||
} from '../agent-hooks/windows-hook-command'
|
||||
import { wrapPosixHookCommand } from '../agent-hooks/posix-hook-command'
|
||||
import { getMuseManagedCommand, getMuseRemoteManagedCommand } from './hook-settings'
|
||||
|
||||
afterEach(() => vi.restoreAllMocks())
|
||||
|
||||
describe('Muse hook commands', () => {
|
||||
it('dispatches safe Windows batch paths without PowerShell', () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
|
||||
expect(getMuseManagedCommand('C:\\Users\\alice\\.orca\\agent-hooks\\muse-hook.cmd')).toBe(
|
||||
'C:/Users/alice/.orca/agent-hooks/muse-hook.cmd'
|
||||
)
|
||||
})
|
||||
|
||||
it.each([
|
||||
'C:\\Users\\Alice Smith\\.orca\\agent-hooks\\muse-hook.cmd',
|
||||
'C:\\Users\\a&b\\.orca\\agent-hooks\\muse-hook.cmd',
|
||||
'C:\\Users\\a%b\\.orca\\agent-hooks\\muse-hook.cmd',
|
||||
"C:\\Users\\O'Brien\\.orca\\agent-hooks\\muse-hook.cmd",
|
||||
'C:\\Users\\日本語\\.orca\\agent-hooks\\muse-hook.cmd',
|
||||
'\\\\server\\share\\.orca\\agent-hooks\\muse-hook.cmd'
|
||||
])('retains the established quoting fallback for %s', (path) => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
|
||||
const policy = { useProcessPolicyEnvironment: isSafeUnicodeWindowsBatchHookPath(path) }
|
||||
expect(getMuseManagedCommand(path)).toBe(wrapWindowsHookCommand(path, {}, policy))
|
||||
})
|
||||
|
||||
it.each(['darwin', 'linux'] as const)('keeps local %s commands POSIX', (platform) => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue(platform)
|
||||
const path = '/home/alice smith/.orca/agent-hooks/muse-hook.sh'
|
||||
expect(getMuseManagedCommand(path)).toBe(wrapPosixHookCommand(path))
|
||||
})
|
||||
|
||||
it('keeps SSH commands POSIX when the client runs Windows', () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
|
||||
const path = '/home/alice smith/.orca/agent-hooks/muse-hook.sh'
|
||||
expect(getMuseRemoteManagedCommand(path)).toBe(wrapPosixHookCommand(path))
|
||||
})
|
||||
})
|
||||
@@ -20,10 +20,6 @@ describe('structured chat configured Arguments', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it('uses the existing default when the Arguments key is absent', () => {
|
||||
expect(structuredAgentConfiguredArgs('claude', {})).toEqual(['--dangerously-skip-permissions'])
|
||||
})
|
||||
|
||||
it('preserves Windows paths under the configured shell', () => {
|
||||
expect(
|
||||
structuredAgentConfiguredArgs(
|
||||
|
||||
@@ -16,23 +16,3 @@ describe('retired config directory recognition', () => {
|
||||
expect(isOpenCodeLegacySharedConfigDir('C:/ORCA/opencode-hooks/shared/', 'c:\\orca')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isOpenCodeLegacySharedConfigDir', () => {
|
||||
const userData = join('fixture', 'user-data')
|
||||
|
||||
it('matches only the retired shared dirs of both OpenCode variants', () => {
|
||||
expect(
|
||||
isOpenCodeLegacySharedConfigDir(join(userData, 'opencode-hooks', 'shared'), userData)
|
||||
).toBe(true)
|
||||
expect(
|
||||
isOpenCodeLegacySharedConfigDir(join(userData, 'opencode2-hooks', 'shared'), userData)
|
||||
).toBe(true)
|
||||
expect(
|
||||
isOpenCodeLegacySharedConfigDir(join(userData, 'opencode-hooks', 'mine'), userData)
|
||||
).toBe(false)
|
||||
expect(
|
||||
isOpenCodeLegacySharedConfigDir(join('other', 'opencode-hooks', 'shared'), userData)
|
||||
).toBe(false)
|
||||
expect(isOpenCodeLegacySharedConfigDir(undefined, userData)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,13 +23,6 @@ describe('the Orca version a server reports', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('is the release its bundle was built from, not a dev placeholder', () => {
|
||||
mkdirSync(join(root, 'out', 'cli'), { recursive: true })
|
||||
writeFileSync(join(root, 'out', 'package.json'), JSON.stringify({ version: '1.4.224' }))
|
||||
stampOrcadAppVersion(() => root)
|
||||
expect(process.env.ORCA_APP_VERSION).toBe('1.4.224')
|
||||
})
|
||||
|
||||
it('replaces a version inherited from the Orca that started it', () => {
|
||||
process.env.ORCA_APP_VERSION = '1.4.300'
|
||||
mkdirSync(join(root, 'out'), { recursive: true })
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
bindHostIsNetworkExposed,
|
||||
describeOrcadBindExposure,
|
||||
ORCAD_LOOPBACK_BIND_HOST,
|
||||
OrcadBindAddressError,
|
||||
resolveOrcadBindHost
|
||||
@@ -37,9 +36,4 @@ describe('bindHostIsNetworkExposed', () => {
|
||||
expect(bindHostIsNetworkExposed('::')).toBe(true)
|
||||
expect(bindHostIsNetworkExposed('10.1.2.3')).toBe(true)
|
||||
})
|
||||
|
||||
it('says out loud when a deployment is reachable from the network', () => {
|
||||
expect(describeOrcadBindExposure('0.0.0.0')).toContain('reachable from the network')
|
||||
expect(describeOrcadBindExposure('127.0.0.1')).toContain('local only')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,16 +12,6 @@ const managed = {
|
||||
}
|
||||
|
||||
describe('managed orcad compile cache', () => {
|
||||
it('caches inside a managed slot, where slot GC removes it with the slot', () => {
|
||||
const enableCompileCache = vi.fn()
|
||||
enableManagedOrcadCompileCache('/home/u/.orca-remote/orcad-1+ab', managed, {
|
||||
enableCompileCache
|
||||
})
|
||||
expect(enableCompileCache).toHaveBeenCalledWith(
|
||||
join('/home/u/.orca-remote/orcad-1+ab', ORCAD_COMPILE_CACHE_DIRNAME)
|
||||
)
|
||||
})
|
||||
|
||||
it('never writes into a slot no client manages, such as a packaged app', () => {
|
||||
const enableCompileCache = vi.fn()
|
||||
enableManagedOrcadCompileCache('/Applications/Orca.app/orcad', {}, { enableCompileCache })
|
||||
|
||||
@@ -30,11 +30,6 @@ describe('detectAgentCommandsOnHost install-dir fallback', () => {
|
||||
guestMounts.value = []
|
||||
})
|
||||
|
||||
it('searches the process PATH everywhere except a WSL host orcad', async () => {
|
||||
await detectAgentCommandsOnHost(['codex'])
|
||||
expect(detectInstallDirs).toHaveBeenCalledWith(['codex'], undefined)
|
||||
})
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'drops /mnt/<drive> dirs from the fallback PATH on a WSL host orcad',
|
||||
async () => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
PROVIDER_SPAWN_FAILURE_MARKER,
|
||||
providerStderrForDisplay,
|
||||
supervisedProviderSpawnFailure
|
||||
} from './provider-spawn-failure-report'
|
||||
|
||||
@@ -33,13 +32,4 @@ describe('provider spawn failure report', () => {
|
||||
])('reads no spawn failure from %s', (_, code, stderr) => {
|
||||
expect(supervisedProviderSpawnFailure(code, stderr)).toBeNull()
|
||||
})
|
||||
|
||||
it('shows a report as the spawn error a direct spawn gives, and other stderr unchanged', () => {
|
||||
expect(
|
||||
providerStderrForDisplay(
|
||||
`Warning: a runtime notice\n${report(false, 'ENOENT', 'spawn /opt/claude ENOENT')}`
|
||||
)
|
||||
).toBe('Warning: a runtime notice\nspawn /opt/claude ENOENT\n')
|
||||
expect(providerStderrForDisplay('Error: auth failed\n')).toBe('Error: auth failed\n')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import { listFilesystemMarkdownDocuments } from './filesystem-markdown-listing'
|
||||
|
||||
it('uses a native markdown capability without requiring unrelated provider methods', async () => {
|
||||
const documents = [
|
||||
{ filePath: '/repo/docs/a.md', relativePath: 'docs/a.md', basename: 'a.md', name: 'a' }
|
||||
]
|
||||
const listMarkdownDocuments = vi.fn(async () => documents)
|
||||
await expect(listFilesystemMarkdownDocuments({ listMarkdownDocuments }, '/repo')).resolves.toBe(
|
||||
documents
|
||||
)
|
||||
expect(listMarkdownDocuments).toHaveBeenCalledWith('/repo')
|
||||
})
|
||||
|
||||
it('retains the path-list fallback for providers without native markdown listing', async () => {
|
||||
const listFiles = vi.fn(async () => ['b.mdx', 'ignored.ts', '../escape.md', 'a.md'])
|
||||
const documents = await listFilesystemMarkdownDocuments({ listFiles }, '/repo')
|
||||
|
||||
@@ -12,12 +12,6 @@ describe('resolveStableForegroundProcess', () => {
|
||||
expect(result.lastRecognizedAgent).toBe('claude')
|
||||
})
|
||||
|
||||
it('remembers the agent from a completed scan that found it', () => {
|
||||
const result = resolveStableForegroundProcess({ available: true, processName: 'claude' }, null)
|
||||
expect(result.processName).toBe('claude')
|
||||
expect(result.lastRecognizedAgent).toBe('claude')
|
||||
})
|
||||
|
||||
it('reports a real exit and clears memory when a completed scan finds no agent', () => {
|
||||
const result = resolveStableForegroundProcess(
|
||||
{ available: true, processName: 'powershell.exe' },
|
||||
@@ -27,15 +21,6 @@ describe('resolveStableForegroundProcess', () => {
|
||||
expect(result.lastRecognizedAgent).toBeNull()
|
||||
})
|
||||
|
||||
it('passes through when a scan is unavailable and nothing is remembered', () => {
|
||||
const result = resolveStableForegroundProcess(
|
||||
{ available: false, processName: 'powershell.exe' },
|
||||
null
|
||||
)
|
||||
expect(result.processName).toBe('powershell.exe')
|
||||
expect(result.lastRecognizedAgent).toBeNull()
|
||||
})
|
||||
|
||||
it('prefers the remembered agent even when a degraded scan returns null', () => {
|
||||
const result = resolveStableForegroundProcess({ available: false, processName: null }, 'codex')
|
||||
expect(result.processName).toBe('codex')
|
||||
|
||||
@@ -6,14 +6,6 @@ function rateLimitedResponse(headers?: Record<string, string>): Response {
|
||||
}
|
||||
|
||||
describe('createOAuthUsageError', () => {
|
||||
it('captures a numeric Retry-After on 429', async () => {
|
||||
const error = await createOAuthUsageError(rateLimitedResponse({ 'retry-after': '3037' }))
|
||||
expect(error.status).toBe(429)
|
||||
expect(error.skipPtyFallback).toBe(true)
|
||||
expect(error.retryAfterMs).toBe(3037 * 1000)
|
||||
expect(error.message).toBe('Claude usage is rate limited right now.')
|
||||
})
|
||||
|
||||
it('captures an HTTP-date Retry-After on 429', async () => {
|
||||
const error = await createOAuthUsageError(
|
||||
rateLimitedResponse({ 'retry-after': new Date(Date.now() + 90_000).toUTCString() })
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { RateLimitBucket } from '../../shared/rate-limit-types'
|
||||
import { getBucketName, deriveSessionSummary } from './gemini-bucket-formatting'
|
||||
|
||||
describe('getBucketName', () => {
|
||||
it('maps known model IDs to stable names', () => {
|
||||
expect(getBucketName('gemini-2.5-pro')).toBe('Pro')
|
||||
expect(getBucketName('gemini-2.5-flash')).toBe('Flash')
|
||||
expect(getBucketName('gemini-2.5-flash-lite')).toBe('Flash Lite')
|
||||
expect(getBucketName('gemini-2.0-flash-lite')).toBe('2.0 Flash Lite')
|
||||
expect(getBucketName('gemini-2.0-flash')).toBe('2.0 Flash')
|
||||
})
|
||||
|
||||
it('humanizes unknown model IDs by stripping the gemini- prefix', () => {
|
||||
expect(getBucketName('gemini-3.0-ultra')).toBe('3.0 Ultra')
|
||||
expect(getBucketName('gemini-experimental')).toBe('Exp')
|
||||
expect(getBucketName('some-random-id')).toBe('Some Random Id')
|
||||
})
|
||||
})
|
||||
|
||||
describe('deriveSessionSummary', () => {
|
||||
it('returns null for empty buckets', () => {
|
||||
expect(deriveSessionSummary([])).toBeNull()
|
||||
})
|
||||
|
||||
it('picks the most constrained bucket (highest usedPercent) as session summary', () => {
|
||||
const buckets: RateLimitBucket[] = [
|
||||
{ name: 'Pro', usedPercent: 30, windowMinutes: 60, resetsAt: null, resetDescription: null },
|
||||
{ name: 'Flash', usedPercent: 80, windowMinutes: 60, resetsAt: null, resetDescription: null },
|
||||
{
|
||||
name: 'Flash Lite',
|
||||
usedPercent: 10,
|
||||
windowMinutes: 60,
|
||||
resetsAt: null,
|
||||
resetDescription: null
|
||||
}
|
||||
]
|
||||
const summary = deriveSessionSummary(buckets)
|
||||
expect(summary).not.toBeNull()
|
||||
expect(summary!.usedPercent).toBe(80)
|
||||
expect(summary!.windowMinutes).toBe(60)
|
||||
})
|
||||
|
||||
it('preserves reset metadata from the most constrained bucket', () => {
|
||||
const buckets: RateLimitBucket[] = [
|
||||
{
|
||||
name: 'Pro',
|
||||
usedPercent: 30,
|
||||
windowMinutes: 60,
|
||||
resetsAt: 1000,
|
||||
resetDescription: '2:00 PM'
|
||||
},
|
||||
{
|
||||
name: 'Flash',
|
||||
usedPercent: 80,
|
||||
windowMinutes: 60,
|
||||
resetsAt: 2000,
|
||||
resetDescription: '3:00 PM'
|
||||
}
|
||||
]
|
||||
const summary = deriveSessionSummary(buckets)
|
||||
expect(summary!.resetsAt).toBe(2000)
|
||||
expect(summary!.resetDescription).toBe('3:00 PM')
|
||||
})
|
||||
})
|
||||
@@ -46,11 +46,4 @@ describe('resolveBundledRipgrepWslRouting', () => {
|
||||
wslDistroForOutput: 'Ubuntu'
|
||||
})
|
||||
})
|
||||
|
||||
it('runs the host rg when the project has no WSL runtime', () => {
|
||||
expect(resolveBundledRipgrepWslRouting('/home/me/repo', undefined)).toEqual({
|
||||
wslDistro: undefined,
|
||||
wslDistroForOutput: undefined
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -35,11 +35,6 @@ describe('row column lists', () => {
|
||||
expect([...columns].sort()).toEqual(tableColumns(table))
|
||||
})
|
||||
|
||||
it('qualifies each column when the statement joins under an alias', () => {
|
||||
expect(selectColumns(['id', 'run_id'])).toBe('id, run_id')
|
||||
expect(selectColumns(['id', 'run_id'], 't')).toBe('t.id, t.run_id')
|
||||
})
|
||||
|
||||
// Alias-qualified SQL must still return bare column names for lineage readers.
|
||||
it('returns bare column names for an alias-qualified projection', () => {
|
||||
db = new OrchestrationDb(':memory:')
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import '../unused-default-rpc-methods.test-fixture'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { RpcDispatcher } from '../dispatcher'
|
||||
import type { OrcaRuntimeService } from '../../orca-runtime'
|
||||
import { MOBILE_PROJECT_SECTIONS_METHOD } from '../../../../shared/mobile-project-sections-contract'
|
||||
import { PROJECT_SECTION_PARITY_SERVED } from '../../../../shared/project-section-parity.test-fixture'
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the method reads no runtime member; the dispatcher only stamps the runtime id.
|
||||
const runtime = { getRuntimeId: () => 'test-runtime' } as unknown as OrcaRuntimeService
|
||||
|
||||
// Reset modules because the published snapshot is process state.
|
||||
beforeEach(() => {
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
async function sectionsDispatcher(): Promise<RpcDispatcher> {
|
||||
const { MOBILE_PROJECT_SECTIONS_METHODS } = await import('./mobile-project-sections')
|
||||
return new RpcDispatcher({ runtime, methods: MOBILE_PROJECT_SECTIONS_METHODS })
|
||||
}
|
||||
|
||||
describe(MOBILE_PROJECT_SECTIONS_METHOD, () => {
|
||||
it('answers no catalog before a desktop window has published one', async () => {
|
||||
const response = await (
|
||||
await sectionsDispatcher()
|
||||
).dispatch({
|
||||
id: 'req-1',
|
||||
authToken: 'tok',
|
||||
method: MOBILE_PROJECT_SECTIONS_METHOD
|
||||
})
|
||||
|
||||
expect(response).toMatchObject({ ok: true, result: { catalog: null } })
|
||||
})
|
||||
|
||||
it("answers the window's last published catalog", async () => {
|
||||
const dispatcher = await sectionsDispatcher()
|
||||
const { publishMobileProjectSections } = await import('../../mobile-project-sections-snapshot')
|
||||
publishMobileProjectSections(PROJECT_SECTION_PARITY_SERVED)
|
||||
|
||||
const response = await dispatcher.dispatch({
|
||||
id: 'req-2',
|
||||
authToken: 'tok',
|
||||
method: MOBILE_PROJECT_SECTIONS_METHOD
|
||||
})
|
||||
|
||||
expect(response).toMatchObject({ ok: true, result: { catalog: PROJECT_SECTION_PARITY_SERVED } })
|
||||
})
|
||||
})
|
||||
@@ -19,11 +19,6 @@ describe('FederationAttachStartParams', () => {
|
||||
expect(result.success && result.data.runId).toBeUndefined()
|
||||
})
|
||||
|
||||
it('keeps a v1.4.199 runId verbatim', () => {
|
||||
const result = FederationAttachStartParams.parse({ ...legacyRequest, runId: 'run_home' })
|
||||
expect(result.runId).toBe('run_home')
|
||||
})
|
||||
|
||||
// OptionalString drops empty/non-strings to mint a stub Run; whitespace survives parsing and is refused at attachment.
|
||||
it('maps an empty or non-string runId to undefined but passes whitespace through', () => {
|
||||
expect(FederationAttachStartParams.parse({ ...legacyRequest, runId: '' }).runId).toBeUndefined()
|
||||
|
||||
@@ -36,25 +36,4 @@ describe('exposeRun', () => {
|
||||
expect(exposed).not.toHaveProperty('coordinator_orca_session_id')
|
||||
expect(exposed).not.toHaveProperty('coordinator_orca_session_id_generation')
|
||||
})
|
||||
|
||||
it('preserves every published column by value', () => {
|
||||
const exposed = exposeRun(RUN_ROW)
|
||||
|
||||
expect(exposed).toEqual({
|
||||
id: 'run_1',
|
||||
objective: 'Coordinate reviews',
|
||||
coordinator_handle: 'term_coord',
|
||||
consumer_generation: 3,
|
||||
legacy: 0,
|
||||
created_at: '2026-09-04T18:53:07Z',
|
||||
updated_at: '2026-09-04T18:53:09Z'
|
||||
})
|
||||
})
|
||||
|
||||
it('does not mutate the source row', () => {
|
||||
const row = { ...RUN_ROW }
|
||||
exposeRun(row)
|
||||
|
||||
expect(row).toEqual(RUN_ROW)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY } from '../../../../shared/protocol-version'
|
||||
import { STRUCTURED_AGENT_RUNTIME_REGISTRATIONS } from '../../structured-agent-runtime-registrations'
|
||||
|
||||
import type { RpcContext } from '../core'
|
||||
import { STRUCTURED_AGENT_SESSION_AGENTS_METHODS } from './structured-agent-session-agents'
|
||||
|
||||
@@ -16,18 +16,6 @@ function context(overrides: Partial<RpcContext>): RpcContext {
|
||||
}
|
||||
|
||||
describe('agentSession.agents', () => {
|
||||
it("lists this build's registered agents without installing the host", async () => {
|
||||
const ctx = context({})
|
||||
|
||||
const result = await method.handler({}, ctx)
|
||||
|
||||
expect(result.agents.map(({ agent }) => agent)).toEqual(
|
||||
STRUCTURED_AGENT_RUNTIME_REGISTRATIONS.map(({ definition }) => definition.agent)
|
||||
)
|
||||
expect(result.agents.map(({ agent }) => agent)).toContain('opencode')
|
||||
expect(ensureStructuredAgentSessionHost).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('still refuses a remote client that cannot read structured sessions', async () => {
|
||||
await expect(
|
||||
method.handler({}, context({ clientKind: 'mobile', clientCapabilities: [] }))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { resolveStructuredCodexAccountKind } from './structured-agent-account-home'
|
||||
import { getSystemCodexHomePath, resolveOrcaManagedCodexHomePath } from '../codex/codex-home-paths'
|
||||
import { getSystemCodexHomePath } from '../codex/codex-home-paths'
|
||||
import type { CodexManagedAccount } from '../../shared/managed-account-types'
|
||||
const ACCOUNT: CodexManagedAccount = {
|
||||
id: 'account-a',
|
||||
@@ -24,20 +24,6 @@ describe('Codex sign-in instructions follow the probed home', () => {
|
||||
it('recognizes the system home even when Settings selects a managed account', () => {
|
||||
expect(resolveStructuredCodexAccountKind(getSystemCodexHomePath(), SETTINGS)).toBe('system')
|
||||
})
|
||||
it.each([
|
||||
['account-a', 'managed'],
|
||||
[null, 'system']
|
||||
] as const)(
|
||||
'classifies the shared mirror with selection=%s',
|
||||
(activeCodexManagedAccountId, kind) => {
|
||||
expect(
|
||||
resolveStructuredCodexAccountKind(resolveOrcaManagedCodexHomePath(), {
|
||||
...SETTINGS,
|
||||
activeCodexManagedAccountId
|
||||
})
|
||||
).toBe(kind)
|
||||
}
|
||||
)
|
||||
it('does not infer a managed account from a custom or WSL home', () => {
|
||||
expect(resolveStructuredCodexAccountKind('/custom/home', SETTINGS)).toBeUndefined()
|
||||
expect(
|
||||
|
||||
@@ -12,21 +12,6 @@ function environment(
|
||||
}
|
||||
|
||||
describe('getWindowsDefaultRouteInterfaceNames', () => {
|
||||
it('returns active IPv4 and IPv6 default-route interface aliases', async () => {
|
||||
const runPowerShell = vi.fn().mockResolvedValue('["vEthernet (Production LAN)","Ethernet"]')
|
||||
|
||||
await expect(getWindowsDefaultRouteInterfaceNames(environment(runPowerShell))).resolves.toEqual(
|
||||
new Set(['vEthernet (Production LAN)', 'Ethernet'])
|
||||
)
|
||||
|
||||
const script = runPowerShell.mock.calls[0]![0] as string
|
||||
expect(runPowerShell.mock.calls[0]![1]).toBe(3_000)
|
||||
expect(script).toContain("$ProgressPreference = 'SilentlyContinue'")
|
||||
expect(script).toContain("$_.State -eq 'Alive'")
|
||||
expect(script).toContain("$_.DestinationPrefix -eq '0.0.0.0/0'")
|
||||
expect(script).toContain("$_.DestinationPrefix -eq '::/0'")
|
||||
})
|
||||
|
||||
it('returns unknown when route inspection fails or emits malformed data', async () => {
|
||||
await expect(
|
||||
getWindowsDefaultRouteInterfaceNames(
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getCatalogModel, SPEECH_MODEL_CATALOG } from './model-catalog'
|
||||
|
||||
describe('SPEECH_MODEL_CATALOG', () => {
|
||||
it('includes the Japanese Parakeet TDT-CTC model with a valid manifest', () => {
|
||||
const manifest = getCatalogModel('parakeet-tdt-ctc-0.6b-ja-int8')
|
||||
|
||||
expect(manifest).toBeDefined()
|
||||
expect(manifest?.type).toBe('nemo-ctc')
|
||||
expect(manifest?.provider).toBe('local')
|
||||
expect(manifest?.language).toBe('ja')
|
||||
expect(manifest?.streaming).toBe(false)
|
||||
expect(manifest?.sampleRate).toBe(16000)
|
||||
expect(manifest?.files).toEqual(['model.int8.onnx', 'tokens.txt'])
|
||||
expect(manifest?.sizeBytes).toBe(655_571_161)
|
||||
expect(manifest?.downloadFiles?.map(({ name }) => name)).toEqual([
|
||||
'model.int8.onnx',
|
||||
'tokens.txt'
|
||||
])
|
||||
})
|
||||
|
||||
it('has unique ids across the catalog', () => {
|
||||
const ids = SPEECH_MODEL_CATALOG.map((m) => m.id)
|
||||
|
||||
expect(new Set(ids).size).toBe(ids.length)
|
||||
})
|
||||
|
||||
it('registers SenseVoice as a non-streaming local model', () => {
|
||||
const model = getCatalogModel('sense-voice-zh-en-ja-ko-yue')
|
||||
expect(model).toBeDefined()
|
||||
expect(model?.type).toBe('senseVoice')
|
||||
expect(model?.provider).toBe('local')
|
||||
expect(model?.language).toBe('multilingual')
|
||||
expect(model?.streaming).toBe(false)
|
||||
})
|
||||
|
||||
it('ships the single-file SenseVoice model layout the loader resolves', () => {
|
||||
const model = getCatalogModel('sense-voice-zh-en-ja-ko-yue')
|
||||
expect(model?.files).toEqual(['model.int8.onnx', 'tokens.txt'])
|
||||
})
|
||||
|
||||
it('downloads only the pinned SenseVoice runtime files', () => {
|
||||
const model = getCatalogModel('sense-voice-zh-en-ja-ko-yue')
|
||||
expect(model?.sizeBytes).toBe(239_549_735)
|
||||
expect(model?.downloadFiles).toHaveLength(2)
|
||||
expect(model?.downloadFiles?.map(({ name }) => name)).toEqual(['model.int8.onnx', 'tokens.txt'])
|
||||
})
|
||||
})
|
||||
@@ -4,7 +4,7 @@ import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { installFakeAppEnvironment } from '../../../config/scripts/vitest-host-ports-setup'
|
||||
import { SPEECH_RECOGNIZER_DIR_ENV } from '../../shared/speech-recognizer-package'
|
||||
import { getSherpaModulePath, getSttWorkerPath, hasLocalSpeechRecognizer } from './stt-worker-paths'
|
||||
import { getSherpaModulePath, hasLocalSpeechRecognizer } from './stt-worker-paths'
|
||||
|
||||
const roots: string[] = []
|
||||
afterEach(() => {
|
||||
@@ -38,9 +38,4 @@ describe('speech worker paths on orcad (#31174)', () => {
|
||||
process.env[SPEECH_RECOGNIZER_DIR_ENV] = dir
|
||||
expect(getSherpaModulePath()).toBeNull()
|
||||
})
|
||||
|
||||
it('starts the worker shipped beside the server bundle', () => {
|
||||
installFakeAppEnvironment({ isPackaged: () => true })
|
||||
expect(getSttWorkerPath()).toBe(join(__dirname, 'stt-worker.js'))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { isSshSessionLimitError } from './ssh-session-limit-error'
|
||||
|
||||
describe('isSshSessionLimitError', () => {
|
||||
it('matches ssh2 open failures with the resource-shortage reason code', () => {
|
||||
expect(
|
||||
isSshSessionLimitError(
|
||||
Object.assign(new Error('(SSH) Channel open failure: open failed'), { reason: 4 })
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('matches the OpenSSH MaxSessions rejection (SSH2_OPEN_CONNECT_FAILED + "open failed")', () => {
|
||||
// OpenSSH 9.2 uses reason 2 plus open failed for MaxSessions refusal.
|
||||
expect(
|
||||
isSshSessionLimitError(
|
||||
Object.assign(new Error('(SSH) Channel open failure: open failed'), { reason: 2 })
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('matches OpenSSH mux and MaxSessions failures', () => {
|
||||
expect(
|
||||
isSshSessionLimitError(
|
||||
new Error(
|
||||
'mux_client_request_session: session request failed: Session open refused by peer'
|
||||
)
|
||||
)
|
||||
).toBe(true)
|
||||
expect(
|
||||
isSshSessionLimitError(new Error('open failed: MaxSessions limit reached on remote host'))
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('does not match generic channel-open failures without a session-limit reason', () => {
|
||||
expect(
|
||||
isSshSessionLimitError(
|
||||
Object.assign(new Error('(SSH) Channel open failure: open failed'), { reason: 1 })
|
||||
)
|
||||
).toBe(false)
|
||||
expect(
|
||||
isSshSessionLimitError(
|
||||
Object.assign(new Error('(SSH) Channel open failure: open failed'), { reason: 3 })
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('does not match unrelated command failures', () => {
|
||||
expect(
|
||||
isSshSessionLimitError(new Error('Command "node" failed (exit 1): Node.js not found'))
|
||||
).toBe(false)
|
||||
expect(isSshSessionLimitError(new Error('channel open failure while parsing output'))).toBe(
|
||||
false
|
||||
)
|
||||
expect(
|
||||
isSshSessionLimitError(
|
||||
new Error('open failed: administratively prohibited: forwarding disabled')
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,51 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getCliLaunchArgs } from './cli-launch-redirect'
|
||||
import { argvRequestsServeMode, normalizeServeModeArgv } from './serve-mode-argv'
|
||||
|
||||
const CLI_ENTRY_PATH = '/opt/orca/resources/app.asar.unpacked/out/cli/index.js'
|
||||
const REDIRECT_OPTIONS = {
|
||||
platform: 'linux' as const,
|
||||
isPackaged: true,
|
||||
commandNames: ['serve', 'status']
|
||||
}
|
||||
|
||||
function rewriteAsIndexDoes(argv: string[]): string[] {
|
||||
return argvRequestsServeMode(argv) ? normalizeServeModeArgv(argv) : argv
|
||||
}
|
||||
|
||||
describe('serve argv rewrite vs CLI launch redirect ordering', () => {
|
||||
const launchArgv = [
|
||||
'/opt/orca/orca-ide',
|
||||
'--disable-features=Vulkan',
|
||||
'serve',
|
||||
'--port',
|
||||
'7777',
|
||||
'--json'
|
||||
]
|
||||
|
||||
it('leaves direct serve in the main process', () => {
|
||||
expect(getCliLaunchArgs(launchArgv, CLI_ENTRY_PATH, REDIRECT_OPTIONS)).toBeNull()
|
||||
})
|
||||
|
||||
it('rewrites direct serve into the in-process flag shape', () => {
|
||||
const rewritten = rewriteAsIndexDoes(launchArgv)
|
||||
expect(rewritten).toContain('--disable-features=Vulkan')
|
||||
expect(rewritten).toContain('--serve')
|
||||
expect(rewritten).toContain('--serve-port')
|
||||
expect(getCliLaunchArgs(rewritten, CLI_ENTRY_PATH, REDIRECT_OPTIONS)).toBeNull()
|
||||
})
|
||||
|
||||
it('leaves non-serve CLI commands redirectable either way', () => {
|
||||
const argv = ['/opt/orca/orca-ide', 'status']
|
||||
expect(rewriteAsIndexDoes(argv)).toEqual(argv)
|
||||
expect(getCliLaunchArgs(argv, CLI_ENTRY_PATH, REDIRECT_OPTIONS)).toEqual(['status'])
|
||||
})
|
||||
|
||||
it('redirects serve help instead of binding a server', () => {
|
||||
const argv = ['/opt/orca/orca-ide', 'serve', '--help']
|
||||
expect(rewriteAsIndexDoes(argv)).toEqual(argv)
|
||||
expect(getCliLaunchArgs(argv, CLI_ENTRY_PATH, REDIRECT_OPTIONS)).toEqual(['serve', '--help'])
|
||||
})
|
||||
|
||||
// Source order is the contract: behavior cases remain green if preflight redirect/rewrite order reverses.
|
||||
})
|
||||
@@ -16,10 +16,6 @@ function makeFakeStore(getRepos: () => Repo[]): Store {
|
||||
} as unknown as Store
|
||||
}
|
||||
|
||||
function makeRepos(n: number): Repo[] {
|
||||
return Array.from({ length: n }, (_, i) => ({ id: `repo-${i}` }) as unknown as Repo)
|
||||
}
|
||||
|
||||
describe('cohort-classifier', () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
@@ -31,21 +27,6 @@ describe('cohort-classifier', () => {
|
||||
_setStoreForTests(null)
|
||||
})
|
||||
|
||||
it('returns the current repo count', () => {
|
||||
initCohortClassifier(makeFakeStore(() => makeRepos(3)))
|
||||
expect(getCohortAtEmit()).toEqual({ nth_repo_added: 3 })
|
||||
})
|
||||
|
||||
// Zero is the canonical pre-repo cohort, not absent classifier data.
|
||||
it('returns 0 (not undefined) for an empty repo list', () => {
|
||||
initCohortClassifier(makeFakeStore(() => []))
|
||||
expect(getCohortAtEmit()).toEqual({ nth_repo_added: 0 })
|
||||
})
|
||||
|
||||
it('returns undefined when the store is not initialized', () => {
|
||||
expect(getCohortAtEmit()).toEqual({ nth_repo_added: undefined })
|
||||
})
|
||||
|
||||
it('returns undefined and never throws when getRepos throws', () => {
|
||||
initCohortClassifier(
|
||||
makeFakeStore(() => {
|
||||
|
||||
@@ -23,17 +23,6 @@ describe('buildClipboardImageThumbnail', () => {
|
||||
expect(image.resize).toHaveBeenCalledWith({ height: 160, quality: 'good', width: 320 })
|
||||
})
|
||||
|
||||
it('skips the resize for an image that already fits', () => {
|
||||
const image = fakeImage({ getSize: () => ({ height: 200, width: 320 }) })
|
||||
|
||||
expect(buildClipboardImageThumbnail(image)?.dataUrl).toBe('data:image/png;base64,FULL')
|
||||
expect(image.resize).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('reports no thumbnail for an empty clipboard so text paste falls through', () => {
|
||||
expect(buildClipboardImageThumbnail(fakeImage({ isEmpty: () => true }))).toBeNull()
|
||||
})
|
||||
|
||||
it('reports no thumbnail rather than throwing for an oversized image', () => {
|
||||
// Preview only decides placeholder visibility; save still reports the actual oversized-image failure.
|
||||
const image = fakeImage({ getSize: () => ({ height: 100_000, width: 100_000 }) })
|
||||
|
||||
@@ -34,20 +34,4 @@ describe('resolveWorktreeCreateBase', () => {
|
||||
expect(resolveDefaultBaseRef).not.toHaveBeenCalled()
|
||||
expect(isBaseUsable).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps a usable persisted base when it is still valid', async () => {
|
||||
const resolveDefaultBaseRef = vi.fn().mockResolvedValue('origin/main')
|
||||
const isBaseUsable = vi.fn().mockResolvedValue(true)
|
||||
|
||||
await expect(
|
||||
resolveWorktreeCreateBase({
|
||||
repoWorktreeBaseRef: 'origin/master',
|
||||
resolveDefaultBaseRef,
|
||||
isBaseUsable
|
||||
})
|
||||
).resolves.toBe('origin/master')
|
||||
|
||||
expect(resolveDefaultBaseRef).toHaveBeenCalledTimes(1)
|
||||
expect(isBaseUsable).toHaveBeenCalledWith('origin/master')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,13 +14,6 @@ const option = (id: string, label: string, count: number, detail = ''): PaletteF
|
||||
searchText: buildPaletteFilterOptionSearchText(label, detail)
|
||||
})
|
||||
|
||||
describe('buildPaletteFilterOptionSearchText', () => {
|
||||
it('lowercases label and detail once for cheap includes checks', () => {
|
||||
expect(buildPaletteFilterOptionSearchText('Acme', 'SSH host')).toBe('acme ssh host')
|
||||
expect(buildPaletteFilterOptionSearchText('Local', '')).toBe('local')
|
||||
})
|
||||
})
|
||||
|
||||
describe('rankPaletteFilterOptions', () => {
|
||||
const options = [
|
||||
option('a', 'Alpha', 1),
|
||||
@@ -29,62 +22,6 @@ describe('rankPaletteFilterOptions', () => {
|
||||
option('d', 'Delta Host', 2, 'ssh')
|
||||
]
|
||||
|
||||
it('moves a toggled option to the front, so a cursor must track ids not positions', () => {
|
||||
const before = rankPaletteFilterOptions({
|
||||
options,
|
||||
query: '',
|
||||
selectedIds: new Set(),
|
||||
rankMode: 'registry'
|
||||
})
|
||||
expect(before.ordered.map((entry) => entry.id)).toEqual(['a', 'b', 'c', 'd'])
|
||||
|
||||
// A toggle pins the row first, invalidating the previous highlight index.
|
||||
const after = rankPaletteFilterOptions({
|
||||
options,
|
||||
query: '',
|
||||
selectedIds: new Set(['c']),
|
||||
rankMode: 'registry'
|
||||
})
|
||||
expect(after.ordered[0]?.id).toBe('c')
|
||||
expect(before.ordered[1]?.id).not.toBe(after.ordered[1]?.id)
|
||||
})
|
||||
|
||||
it('pins selected matches first and ranks the rest by popularity', () => {
|
||||
const ranked = rankPaletteFilterOptions({
|
||||
options,
|
||||
selectedIds: new Set(['a']),
|
||||
query: '',
|
||||
rankMode: 'popularity'
|
||||
})
|
||||
|
||||
expect(ranked.ordered.map((row) => row.id)).toEqual(['a', 'b', 'c', 'd'])
|
||||
expect(ranked.selectedCount).toBe(1)
|
||||
expect(ranked.unselectedCount).toBe(3)
|
||||
expect(ranked.unselectedIds).toEqual(['b', 'c', 'd'])
|
||||
})
|
||||
|
||||
it('keeps registry order for hosts within each partition', () => {
|
||||
const ranked = rankPaletteFilterOptions({
|
||||
options,
|
||||
selectedIds: new Set(['c']),
|
||||
query: '',
|
||||
rankMode: 'registry'
|
||||
})
|
||||
|
||||
expect(ranked.ordered.map((row) => row.id)).toEqual(['c', 'a', 'b', 'd'])
|
||||
})
|
||||
|
||||
it('filters with pre-normalized searchText without re-lowercasing options', () => {
|
||||
const ranked = rankPaletteFilterOptions({
|
||||
options,
|
||||
selectedIds: new Set(),
|
||||
query: 'ssh',
|
||||
rankMode: 'popularity'
|
||||
})
|
||||
|
||||
expect(ranked.ordered.map((row) => row.id)).toEqual(['d'])
|
||||
})
|
||||
|
||||
it('keeps selected rows visible even when they miss the query text', () => {
|
||||
// Selected rows must remain undoable after a search that would otherwise hide them.
|
||||
const ranked = rankPaletteFilterOptions({
|
||||
|
||||
@@ -29,54 +29,6 @@ function card(overrides: Partial<DashboardCard>): DashboardCard {
|
||||
}
|
||||
|
||||
describe('agent board filtering', () => {
|
||||
it('searches visible names, messages, review numbers, and subagent names', () => {
|
||||
const cards = [
|
||||
card({ paneKey: 'conversation', conversationName: 'Sparse-checkout parser' }),
|
||||
card({ paneKey: 'messages', lastAgentMessage: 'Relay authentication repaired' }),
|
||||
card({ paneKey: 'review', review: { number: 11012, state: 'open' } }),
|
||||
card({
|
||||
paneKey: 'subagent',
|
||||
subagents: [{ id: 'child', name: 'terminal orphan cleanup', dotState: 'working' }]
|
||||
})
|
||||
]
|
||||
|
||||
expect(filterDashboardCards(cards, 'sparse-checkout', EMPTY_DASHBOARD_FILTERS)[0].paneKey).toBe(
|
||||
'conversation'
|
||||
)
|
||||
expect(filterDashboardCards(cards, 'authentication', EMPTY_DASHBOARD_FILTERS)[0].paneKey).toBe(
|
||||
'messages'
|
||||
)
|
||||
expect(filterDashboardCards(cards, '#11012', EMPTY_DASHBOARD_FILTERS)[0].paneKey).toBe('review')
|
||||
expect(filterDashboardCards(cards, 'orphan cleanup', EMPTY_DASHBOARD_FILTERS)[0].paneKey).toBe(
|
||||
'subagent'
|
||||
)
|
||||
})
|
||||
|
||||
it('combines filter categories while treating values within a category as alternatives', () => {
|
||||
const cards = [
|
||||
card({
|
||||
paneKey: 'match',
|
||||
repoId: 'orca',
|
||||
workspaceStatusId: 'in-review',
|
||||
review: { number: 1, state: 'open' }
|
||||
}),
|
||||
card({
|
||||
paneKey: 'wrong-status',
|
||||
repoId: 'orca',
|
||||
workspaceStatusId: 'todo',
|
||||
review: { number: 2, state: 'open' }
|
||||
})
|
||||
]
|
||||
|
||||
const result = filterDashboardCards(cards, '', {
|
||||
projects: ['orca', 'relay'],
|
||||
workspaceStatuses: ['in-review'],
|
||||
reviewStates: ['open']
|
||||
})
|
||||
|
||||
expect(result.map((candidate) => candidate.paneKey)).toEqual(['match'])
|
||||
})
|
||||
|
||||
it('supports the synthesized no-review filter and immutable toggles', () => {
|
||||
const cards = [
|
||||
card({ paneKey: 'none' }),
|
||||
|
||||
@@ -2,19 +2,6 @@ import { describe, expect, it } from 'vitest'
|
||||
import { buildMarkdownExportHtml } from './markdown-export-html'
|
||||
|
||||
describe('buildMarkdownExportHtml', () => {
|
||||
it('wraps rendered html in a complete standalone document', () => {
|
||||
const html = buildMarkdownExportHtml({
|
||||
title: 'Hello',
|
||||
renderedHtml: '<h1>Hello</h1><p>world</p>'
|
||||
})
|
||||
expect(html.startsWith('<!DOCTYPE html>')).toBe(true)
|
||||
expect(html).toContain('<meta charset="utf-8"')
|
||||
expect(html).toContain('<title>Hello</title>')
|
||||
expect(html).toContain('<h1>Hello</h1><p>world</p>')
|
||||
expect(html).toContain('class="orca-export-root"')
|
||||
expect(html).toContain('<style>')
|
||||
})
|
||||
|
||||
it('escapes HTML-unsafe characters in the title', () => {
|
||||
const html = buildMarkdownExportHtml({
|
||||
title: '<script>alert(1)</script>',
|
||||
@@ -23,16 +10,4 @@ describe('buildMarkdownExportHtml', () => {
|
||||
expect(html).toContain('<title><script>alert(1)</script></title>')
|
||||
expect(html).not.toContain('<title><script>')
|
||||
})
|
||||
|
||||
it('falls back to "Untitled" when the title is empty', () => {
|
||||
const html = buildMarkdownExportHtml({ title: '', renderedHtml: '<p>x</p>' })
|
||||
expect(html).toContain('<title>Untitled</title>')
|
||||
})
|
||||
|
||||
it('hides preview annotation controls even if DOM scrubbing misses them', () => {
|
||||
const html = buildMarkdownExportHtml({ title: 'Notes', renderedHtml: '<p>x</p>' })
|
||||
expect(html).toContain('.markdown-annotation-controls')
|
||||
expect(html).toContain('[data-orca-export-hide')
|
||||
expect(html).toContain('display: none')
|
||||
})
|
||||
})
|
||||
|
||||
+2
-57
@@ -1,66 +1,11 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { findNodeAtLocation, parseTree } from 'jsonc-parser'
|
||||
|
||||
import { FEATURE_WALL_SETUP_STEPS } from '../../../../shared/feature-wall-setup-steps'
|
||||
import { getLocalizedFeatureWallSetupChecklistCopy } from './feature-wall-setup-checklist-localized-copy'
|
||||
import en from '../../i18n/locales/en.json'
|
||||
import ja from '../../i18n/locales/ja.json'
|
||||
import jaSource from '../../i18n/locales/ja.json?raw'
|
||||
import ko from '../../i18n/locales/ko.json'
|
||||
|
||||
import { i18n } from '../../i18n/i18n'
|
||||
|
||||
describe('feature-wall-setup-checklist-localized-copy', () => {
|
||||
it('defines one Japanese setup checklist catalog without shadowing', () => {
|
||||
const catalog = parseTree(jaSource)
|
||||
expect(catalog).toBeDefined()
|
||||
if (!catalog) {
|
||||
throw new Error('Invalid Japanese catalog')
|
||||
}
|
||||
const featureWall = findNodeAtLocation(catalog, [
|
||||
'auto',
|
||||
'components',
|
||||
'feature',
|
||||
'wall',
|
||||
'feature',
|
||||
'wall'
|
||||
])
|
||||
const setupSections = featureWall?.children?.filter(
|
||||
(property) => property.children?.[0].value === 'setup'
|
||||
)
|
||||
expect(setupSections).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('returns non-empty localized name and description for all setup checklist steps', () => {
|
||||
for (const step of FEATURE_WALL_SETUP_STEPS) {
|
||||
const localized = getLocalizedFeatureWallSetupChecklistCopy(step)
|
||||
expect(localized.name).toBeTruthy()
|
||||
expect(localized.description).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
it('has 16 English catalog entries for the setup checklist steps', () => {
|
||||
const enKeys = en.auto.components.feature.wall.feature.wall.setup.checklist.localized.copy
|
||||
expect(Object.keys(enKeys).length).toBe(16)
|
||||
for (const enVal of Object.values(enKeys)) {
|
||||
expect(typeof enVal).toBe('string')
|
||||
}
|
||||
})
|
||||
|
||||
it.each(Object.entries({ ja, ko }))(
|
||||
'%s translates every setup checklist step',
|
||||
(_locale, catalog) => {
|
||||
const enKeys = en.auto.components.feature.wall.feature.wall.setup.checklist.localized.copy
|
||||
const localeKeys: Record<string, string> =
|
||||
catalog.auto.components.feature.wall.feature.wall.setup.checklist.localized.copy
|
||||
expect(Object.keys(localeKeys).length).toBe(16)
|
||||
for (const [hash, enVal] of Object.entries(enKeys)) {
|
||||
const localeVal = localeKeys[hash]
|
||||
expect(typeof localeVal, hash).toBe('string')
|
||||
expect(localeVal, hash).toBeTruthy()
|
||||
expect(localeVal, hash).not.toBe(enVal)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['ja', 'ko'])(
|
||||
'resolves every checklist step through the %s runtime catalog',
|
||||
async (locale) => {
|
||||
|
||||
@@ -25,51 +25,6 @@ function getContext(source: string, line: number, contextBefore = 0, contextAfte
|
||||
}
|
||||
|
||||
describe('getPrCommentCodeContext', () => {
|
||||
it('preserves small comment context and nearest brace block behavior', () => {
|
||||
const source = ['function outer() {', ' if (ready) {', ' run()', ' }', '}', 'after'].join(
|
||||
'\n'
|
||||
)
|
||||
|
||||
const context = getContext(source, 3)
|
||||
|
||||
expect(context).toMatchObject({
|
||||
selectedLines: [' run()'],
|
||||
totalLines: 6,
|
||||
commentFrom: 3,
|
||||
commentTo: 3,
|
||||
from: 3,
|
||||
to: 3,
|
||||
blockRange: { startLine: 2, endLine: 4 },
|
||||
shouldUseBlockRange: true,
|
||||
canExpandAbove: true,
|
||||
canExpandBelow: true,
|
||||
canExpandBlock: true
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes CRLF line endings like the previous split path', () => {
|
||||
const context = getContext('one\r\ntwo\r\nthree', 2, 1, 1)
|
||||
|
||||
expect(context?.selectedLines).toEqual(['one', 'two', 'three'])
|
||||
expect(context?.totalLines).toBe(3)
|
||||
})
|
||||
|
||||
it('extracts newline-heavy context without splitting the full source', () => {
|
||||
const split = vi.spyOn(String.prototype, 'split')
|
||||
const source = Array.from({ length: 5_000 }, (_, index) => `line ${index + 1}`).join('\n')
|
||||
|
||||
const context = getContext(source, 4_995, 2, 2)
|
||||
|
||||
expect(context?.selectedLines).toEqual([
|
||||
'line 4993',
|
||||
'line 4994',
|
||||
'line 4995',
|
||||
'line 4996',
|
||||
'line 4997'
|
||||
])
|
||||
expect(split).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('caps pathological single-line excerpts and skips optional block scanning for huge files', () => {
|
||||
const split = vi.spyOn(String.prototype, 'split')
|
||||
const source = '{'.repeat(PR_COMMENT_CODE_CONTEXT_BLOCK_SCAN_CODE_UNITS + 10_000)
|
||||
|
||||
@@ -1,41 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { expect, it } from 'vitest'
|
||||
import { resolveLinearIssueAttributeFilterPrimaryTeam } from './linear-issue-attribute-filter-primary-team'
|
||||
import type { LinearTeam } from '../../../shared/linear/workspace-types'
|
||||
|
||||
const teams: LinearTeam[] = [
|
||||
{ id: 't-b', name: 'Backend', key: 'BE' },
|
||||
{ id: 't-a', name: 'App', key: 'APP' },
|
||||
{ id: 't-c', name: 'Core', key: 'CORE' }
|
||||
]
|
||||
|
||||
describe('resolveLinearIssueAttributeFilterPrimaryTeam', () => {
|
||||
it('picks the first available team by stable name/id when none selected', () => {
|
||||
expect(
|
||||
resolveLinearIssueAttributeFilterPrimaryTeam({
|
||||
selectedTeamIds: [],
|
||||
availableTeams: teams
|
||||
})?.id
|
||||
).toBe('t-a')
|
||||
})
|
||||
|
||||
it('picks the first selected team by stable name/id order', () => {
|
||||
expect(
|
||||
resolveLinearIssueAttributeFilterPrimaryTeam({
|
||||
selectedTeamIds: ['t-c', 't-b'],
|
||||
availableTeams: teams
|
||||
})?.id
|
||||
).toBe('t-b')
|
||||
})
|
||||
|
||||
it('falls back to first available when selected ids are unavailable', () => {
|
||||
expect(
|
||||
resolveLinearIssueAttributeFilterPrimaryTeam({
|
||||
selectedTeamIds: ['missing'],
|
||||
availableTeams: teams
|
||||
})?.id
|
||||
).toBe('t-a')
|
||||
})
|
||||
})
|
||||
|
||||
it('selects a primary team without pairwise membership checks or sorting all teams', () => {
|
||||
let reads = 0
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { resumeActivityLabel } from './native-chat-resume-activity-label'
|
||||
|
||||
describe('resumeActivityLabel', () => {
|
||||
it('says nothing for an offer from a host that recorded no activity', () => {
|
||||
expect(resumeActivityLabel(undefined)).toBeNull()
|
||||
})
|
||||
|
||||
it('names a lead that was mid-reply', () => {
|
||||
expect(resumeActivityLabel({ state: 'working', prompts: [], tasks: [] })?.summary).toBe(
|
||||
'Was mid-reply'
|
||||
)
|
||||
})
|
||||
|
||||
it('names the prompt a waiting chat lost', () => {
|
||||
expect(
|
||||
resumeActivityLabel({
|
||||
state: 'blocked',
|
||||
prompts: [{ kind: 'approval', label: 'Bash' }],
|
||||
tasks: []
|
||||
})?.summary
|
||||
).toBe('Waiting for your approval: Bash')
|
||||
})
|
||||
|
||||
it('says nothing for a settled lead beyond its tasks', () => {
|
||||
expect(
|
||||
resumeActivityLabel({
|
||||
state: 'done',
|
||||
prompts: [],
|
||||
tasks: [{ kind: 'agent', label: 'Review loop 4' }]
|
||||
})?.summary
|
||||
).toBe('Subagent running: Review loop 4')
|
||||
})
|
||||
|
||||
it('tells subagents apart from monitoring, as the sidebar does', () => {
|
||||
expect(
|
||||
resumeActivityLabel({
|
||||
state: 'done',
|
||||
prompts: [],
|
||||
tasks: [
|
||||
{ kind: 'agent', label: 'Review loop 4' },
|
||||
{ kind: 'command', label: 'Watch CI' }
|
||||
]
|
||||
})
|
||||
).toEqual({
|
||||
summary: 'Subagent running: Review loop 4 · Monitoring: Watch CI',
|
||||
detail: 'Review loop 4\nWatch CI'
|
||||
})
|
||||
})
|
||||
|
||||
it('counts several of a kind instead of naming them', () => {
|
||||
expect(
|
||||
resumeActivityLabel({
|
||||
state: 'done',
|
||||
prompts: [],
|
||||
tasks: [
|
||||
{ kind: 'agent', label: 'One' },
|
||||
{ kind: 'agent', label: 'Two' },
|
||||
{ kind: 'monitor', label: '' },
|
||||
{ kind: 'workflow', label: 'Deploy' }
|
||||
]
|
||||
})?.summary
|
||||
).toBe('2 subagents running · Monitoring 2 background tasks')
|
||||
})
|
||||
|
||||
it('does not call a mid-reply command monitoring, but keeps it in the detail', () => {
|
||||
expect(
|
||||
resumeActivityLabel({
|
||||
state: 'working',
|
||||
prompts: [],
|
||||
tasks: [{ kind: 'command', label: 'Count .plist files' }]
|
||||
})
|
||||
).toEqual({ summary: 'Was mid-reply', detail: 'Count .plist files' })
|
||||
})
|
||||
|
||||
it('returns null for a settled lead with nothing running', () => {
|
||||
expect(resumeActivityLabel({ state: 'done', prompts: [], tasks: [] })).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,44 +1,19 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { i18n, setRendererPluginLanguagePacks } from '@/i18n/i18n'
|
||||
import en from '@/i18n/locales/en.json'
|
||||
import fr from '@/i18n/locales/fr.json'
|
||||
import ja from '@/i18n/locales/ja.json'
|
||||
import ko from '@/i18n/locales/ko.json'
|
||||
import zh from '@/i18n/locales/zh.json'
|
||||
|
||||
import { pluginLanguageResourceId } from '../../../../shared/plugins/plugin-language-pack-artifact'
|
||||
import { agentSessionWriteNoticeEnglish } from '../../../../shared/agent-session-refusal-notice'
|
||||
import {
|
||||
AGENT_SESSION_WRITE_NOTICE_COPY,
|
||||
type AgentSessionWriteNoticePart,
|
||||
type AgentSessionWriteNoticeSentence
|
||||
} from '../../../../shared/agent-session-write-notice-copy'
|
||||
|
||||
import {
|
||||
agentSessionWriteFailureText,
|
||||
agentSessionWriteNoticeText
|
||||
} from './agent-session-write-notice-text'
|
||||
|
||||
const SENTENCES = Object.keys(AGENT_SESSION_WRITE_NOTICE_COPY).filter(
|
||||
(key): key is AgentSessionWriteNoticeSentence => key in AGENT_SESSION_WRITE_NOTICE_COPY
|
||||
)
|
||||
|
||||
afterEach(async () => {
|
||||
setRendererPluginLanguagePacks([])
|
||||
await i18n.changeLanguage('en')
|
||||
})
|
||||
|
||||
describe('desktop words for a write that did not happen', () => {
|
||||
it('says exactly what the phone says in English', () => {
|
||||
for (const sentence of SENTENCES) {
|
||||
expect(agentSessionWriteNoticeText([sentence])).toBe(
|
||||
agentSessionWriteNoticeEnglish([sentence])
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps the English catalog in step with the shared copy', () => {
|
||||
expect(en.components['native-chat'].writeNotice).toEqual(AGENT_SESSION_WRITE_NOTICE_COPY)
|
||||
})
|
||||
|
||||
it('does not promise the old goal survived a refused replacement, including older translations', async () => {
|
||||
for (const language of ['en', 'fr']) {
|
||||
await i18n.changeLanguage(language)
|
||||
@@ -55,41 +30,6 @@ describe('desktop words for a write that did not happen', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('translates each sentence whole and shows a provider reason as written', async () => {
|
||||
await i18n.changeLanguage('fr')
|
||||
expect(agentSessionWriteNoticeText(['restartFailed', 'notDoneSend'])).toBe(
|
||||
"L'agent n'a pas pu redémarrer. Votre message n'a pas été envoyé."
|
||||
)
|
||||
expect(
|
||||
agentSessionWriteNoticeText([{ text: 'Claude messages support at most 20 images' }])
|
||||
).toBe('Claude messages support at most 20 images')
|
||||
})
|
||||
|
||||
it('runs sentences on in Japanese and Chinese, and spaces them in other languages', async () => {
|
||||
// Two sentences from the notice, and two the failure words join inside their own sentence.
|
||||
const parts: AgentSessionWriteNoticePart[] = [
|
||||
'notDoneSend',
|
||||
{ failure: { kind: 'startFailed' }, surface: 'rejection', context: { agentName: 'Claude' } }
|
||||
]
|
||||
for (const [language, catalog, gap] of [
|
||||
['ja', ja, ''],
|
||||
['zh', zh, ''],
|
||||
['en', en, ' '],
|
||||
['fr', fr, ' '],
|
||||
['ko', ko, ' ']
|
||||
] as const) {
|
||||
await i18n.changeLanguage(language)
|
||||
const chat = catalog.components['native-chat']
|
||||
expect(agentSessionWriteNoticeText(parts)).toBe(
|
||||
[
|
||||
chat.writeNotice.notDoneSend,
|
||||
chat.failureWords.couldNotStart.replace('{{agent}}', 'Claude'),
|
||||
chat.failureWords.sendToTryAgain
|
||||
].join(gap)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("runs on after a plugin language's full stop, and keeps the space after English it left", async () => {
|
||||
const id = 'plugin:example.chinese/zh-TW' as const
|
||||
setRendererPluginLanguagePacks([
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import {
|
||||
nativeChatModelPillLabel,
|
||||
nativeChatOptionValuesLabel,
|
||||
nativeChatSessionChoiceLabel
|
||||
} from './native-chat-session-option-labels'
|
||||
import { nativeChatModelPillLabel } from './native-chat-session-option-labels'
|
||||
import type { SessionOptionDescriptor } from '../../../../shared/native-chat-session-options'
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
@@ -30,14 +25,6 @@ function modelDescriptor(
|
||||
}
|
||||
|
||||
describe('nativeChatModelPillLabel', () => {
|
||||
it('names a model the CLI defaulted to, not the bare category', () => {
|
||||
expect(nativeChatModelPillLabel(modelDescriptor('default', 'grok-4.5'))).toBe('Grok 4.5')
|
||||
})
|
||||
|
||||
it('names a model the user picked', () => {
|
||||
expect(nativeChatModelPillLabel(modelDescriptor('applied', 'grok-4.5'))).toBe('Grok 4.5')
|
||||
})
|
||||
|
||||
it('withholds a value it has no evidence for', () => {
|
||||
expect(nativeChatModelPillLabel(modelDescriptor('unknown', 'grok-4.5'))).toBe('Model')
|
||||
expect(nativeChatModelPillLabel(modelDescriptor('default'))).toBe('Model')
|
||||
@@ -48,53 +35,3 @@ describe('nativeChatModelPillLabel', () => {
|
||||
expect(nativeChatModelPillLabel(modelDescriptor('reported', 'grok-build'))).toBe('grok-build')
|
||||
})
|
||||
})
|
||||
|
||||
describe('nativeChatSessionChoiceLabel', () => {
|
||||
it('routes ultra through the localized effort label', () => {
|
||||
nativeChatSessionChoiceLabel({ value: 'ultra', label: 'Ultra' })
|
||||
|
||||
expect(translate).toHaveBeenCalledWith(
|
||||
'components.native-chat.composer.optionValue.ultra',
|
||||
'Ultra'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('nativeChatSessionChoiceLabel for speed', () => {
|
||||
it('localizes Standard only inside the speed choice', () => {
|
||||
nativeChatSessionChoiceLabel({ value: 'default', label: 'Standard' }, 'serviceTier')
|
||||
expect(translate).toHaveBeenCalledWith(
|
||||
'components.native-chat.composer.optionValue.standard',
|
||||
'Standard'
|
||||
)
|
||||
expect(nativeChatSessionChoiceLabel({ value: 'default', label: 'Ask first' }, 'mode')).toBe(
|
||||
'Ask first'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('nativeChatOptionValuesLabel', () => {
|
||||
function speedDescriptor(currentValue: string): SessionOptionDescriptor {
|
||||
return {
|
||||
id: 'serviceTier',
|
||||
label: 'Speed',
|
||||
valueSource: 'reported',
|
||||
transport: 'agent-session',
|
||||
settable: true,
|
||||
kind: {
|
||||
type: 'select',
|
||||
currentValue,
|
||||
choices: [
|
||||
{ value: 'default', label: 'Standard' },
|
||||
{ value: 'priority', label: 'Fast' },
|
||||
{ value: 'ultrafast', label: 'Ultrafast' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('names a faster tier and leaves Standard out, as Fast off was', () => {
|
||||
expect(nativeChatOptionValuesLabel([speedDescriptor('ultrafast')])).toBe('Ultrafast')
|
||||
expect(nativeChatOptionValuesLabel([speedDescriptor('default')])).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -41,29 +41,4 @@ describe('RepoIconGlyph', () => {
|
||||
'https://github.com/acme.png?size=64'
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps rendering a loadable image icon', () => {
|
||||
const { container } = render(
|
||||
<RepoIconGlyph repoIcon={{ type: 'image', src: GHE_AVATAR, source: 'github' }} />
|
||||
)
|
||||
|
||||
expect(container.querySelector('img')?.getAttribute('src')).toBe(GHE_AVATAR)
|
||||
expect(container.querySelector('svg')).toBeNull()
|
||||
})
|
||||
|
||||
it('renders emoji and lucide icons unchanged', () => {
|
||||
const { container: emoji } = render(<RepoIconGlyph repoIcon={{ type: 'emoji', emoji: '🐙' }} />)
|
||||
expect(emoji.textContent).toBe('🐙')
|
||||
|
||||
const { container: lucide } = render(
|
||||
<RepoIconGlyph repoIcon={{ type: 'lucide', name: 'Database' }} />
|
||||
)
|
||||
expect(lucide.querySelector('svg')?.getAttribute('class')).toContain('lucide-database')
|
||||
})
|
||||
|
||||
it('falls back to Folder for an unknown lucide name', () => {
|
||||
const { container } = render(<RepoIconGlyph repoIcon={{ type: 'lucide', name: 'NotAnIcon' }} />)
|
||||
|
||||
expect(container.querySelector('svg')?.getAttribute('class')).toContain('lucide-folder')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
getExplorerDisplayDepth,
|
||||
getExplorerDisplayRootOptions,
|
||||
getExplorerDisplayRootPath,
|
||||
resolveExplorerDisplayRootChoice,
|
||||
getExplorerEffectiveExpanded
|
||||
} from './file-explorer-display-root'
|
||||
import { createVisibleFileExplorerRowProjection } from './useFileExplorerVisibleRowProjection'
|
||||
@@ -12,23 +11,6 @@ import { fileExplorerEntriesToTreeNodes } from './file-explorer-directory-listin
|
||||
const flags = { ignoredSet: new Set<string>(), showDotfiles: true, showGitIgnoredFiles: true }
|
||||
|
||||
describe('sparse explorer projection', () => {
|
||||
it('resolves defaults and preserves explicit full root', () => {
|
||||
const options = getExplorerDisplayRootOptions({
|
||||
isSparse: true,
|
||||
sparseDirectories: ['packages/app', 'packages/api']
|
||||
})
|
||||
expect(resolveExplorerDisplayRootChoice(options)).toBe('/')
|
||||
expect(resolveExplorerDisplayRootChoice(options, '/')).toBe('/')
|
||||
expect(resolveExplorerDisplayRootChoice(options, 'packages/api')).toBe('packages/api')
|
||||
expect(
|
||||
resolveExplorerDisplayRootChoice(
|
||||
getExplorerDisplayRootOptions({ isSparse: true, sparseDirectories: ['packages/app'] })
|
||||
)
|
||||
).toBe('packages/app')
|
||||
expect(resolveExplorerDisplayRootChoice(options, 'removed')).toBe('/')
|
||||
expect(getExplorerDisplayRootOptions({ sparseDirectories: ['src'] })).toBeNull()
|
||||
expect(resolveExplorerDisplayRootChoice(null, 'src')).toBe('/')
|
||||
})
|
||||
it('rejects unsafe directories and deduplicates normalized options', () => {
|
||||
expect(
|
||||
getExplorerDisplayRootOptions({
|
||||
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getDefaultSettings } from '../../../../shared/constants'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import { sourceControlTextGenerationDefaultsMatchTarget } from './source-control/ai/text-generation-defaults'
|
||||
|
||||
function settings(): GlobalSettings {
|
||||
const base = getDefaultSettings('/tmp')
|
||||
return {
|
||||
...base,
|
||||
sourceControlAi: {
|
||||
...base.sourceControlAi!,
|
||||
enabled: true,
|
||||
agentId: 'codex',
|
||||
actions: {
|
||||
commitMessage: {
|
||||
agentId: 'codex',
|
||||
commandInputTemplate: '{basePrompt}',
|
||||
agentArgs: '--model sonnet'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('sourceControlTextGenerationDefaultsMatchTarget', () => {
|
||||
it('returns true when the current params match the global saved recipe', () => {
|
||||
const currentSettings = settings()
|
||||
expect(
|
||||
sourceControlTextGenerationDefaultsMatchTarget({
|
||||
actionId: 'commitMessage',
|
||||
target: { type: 'global' },
|
||||
params: {
|
||||
agentId: 'codex',
|
||||
model: 'gpt-5.5',
|
||||
commandInputTemplate: '{basePrompt}',
|
||||
agentArgs: '--model sonnet'
|
||||
},
|
||||
settings: currentSettings
|
||||
})
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false when the command template differs from the saved recipe', () => {
|
||||
expect(
|
||||
sourceControlTextGenerationDefaultsMatchTarget({
|
||||
actionId: 'commitMessage',
|
||||
target: { type: 'global' },
|
||||
params: {
|
||||
agentId: 'codex',
|
||||
model: 'gpt-5.5',
|
||||
commandInputTemplate: '{basePrompt}\n\nchanged',
|
||||
agentArgs: '--model sonnet'
|
||||
},
|
||||
settings: settings()
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
-17
@@ -33,16 +33,6 @@ function statusProps(): React.ComponentProps<typeof SourceControlContentStatus>
|
||||
afterEach(cleanup)
|
||||
|
||||
describe('SourceControlContentStatus recovery', () => {
|
||||
it.each(['merge', 'rebase', 'cherry-pick'] as const)(
|
||||
'wires the zero-conflict %s recovery button to the launch flow',
|
||||
(conflictOperation) => {
|
||||
const props = { ...statusProps(), conflictOperation }
|
||||
const view = render(<SourceControlContentStatus {...props} />)
|
||||
fireEvent.click(view.getByRole('button', { name: 'Resolve with AI' }))
|
||||
expect(props.onResolveWithAi).toHaveBeenCalledTimes(1)
|
||||
}
|
||||
)
|
||||
|
||||
it.each([0, 1])(
|
||||
'blocks recovery while Git runs with %i unmerged paths',
|
||||
(unresolvedConflictCount) => {
|
||||
@@ -71,11 +61,4 @@ describe('SourceControlContentStatus recovery', () => {
|
||||
expect(props.onResolveWithAi).not.toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
|
||||
it('hides recovery when AI is disabled', () => {
|
||||
const view = render(
|
||||
<SourceControlContentStatus {...statusProps()} sourceControlAiActionsVisible={false} />
|
||||
)
|
||||
expect(view.queryByRole('button', { name: 'Resolve with AI' })).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -59,17 +59,6 @@ describe('GrokAccountsSection', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('explains the host-scoped Grok recovery flow without requiring a chat message', async () => {
|
||||
render(<GrokAccountsSection />)
|
||||
|
||||
expect(
|
||||
await screen.findByText(
|
||||
'Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
expect(screen.queryByText(/grok login/i)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Why: #15740 — an unreported percentage must be stated, never shown as 0%.
|
||||
it('shows why usage is unknown instead of hiding the row', async () => {
|
||||
mocks.grokUsage.mockReturnValue({
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
import type { ReactNode } from 'react'
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
type MockAuthStatus = {
|
||||
configured: boolean
|
||||
state: string
|
||||
cloud?: { displayName: string; email: string }
|
||||
} | null
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
const state: { orcaProfileAuthStatus: MockAuthStatus } = {
|
||||
orcaProfileAuthStatus: {
|
||||
configured: true,
|
||||
state: 'connected',
|
||||
cloud: { displayName: 'Ada Lovelace', email: 'ada@example.com' }
|
||||
}
|
||||
}
|
||||
return {
|
||||
connect: vi.fn(),
|
||||
fetchAuthStatus: vi.fn(),
|
||||
signOut: vi.fn(),
|
||||
state
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (_key: string, fallback: string) => fallback
|
||||
}))
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: (selector: (state: Record<string, unknown>) => unknown) =>
|
||||
selector({
|
||||
...mocks.state,
|
||||
connectCurrentOrcaProfile: mocks.connect,
|
||||
fetchOrcaProfileAuthStatus: mocks.fetchAuthStatus,
|
||||
signOutCurrentOrcaProfile: mocks.signOut
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('../orca-profiles/OrcaProfileSignOutConfirmDialog', () => ({
|
||||
OrcaProfileSignOutConfirmDialog: ({
|
||||
open,
|
||||
onConfirm
|
||||
}: {
|
||||
open: boolean
|
||||
onConfirm: () => void
|
||||
children?: ReactNode
|
||||
}) => (open ? <button onClick={onConfirm}>Confirm sign out</button> : null)
|
||||
}))
|
||||
|
||||
import { OrcaAccountSettingsPane } from './OrcaAccountSettingsPane'
|
||||
|
||||
describe('OrcaAccountSettingsPane', () => {
|
||||
beforeEach(() => {
|
||||
mocks.connect.mockReset()
|
||||
mocks.fetchAuthStatus.mockReset()
|
||||
mocks.signOut.mockReset()
|
||||
mocks.signOut.mockResolvedValue({ status: 'signed-out' })
|
||||
mocks.state.orcaProfileAuthStatus = {
|
||||
configured: true,
|
||||
state: 'connected',
|
||||
cloud: { displayName: 'Ada Lovelace', email: 'ada@example.com' }
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
it('shows the connected identity and confirms sign out', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<OrcaAccountSettingsPane />)
|
||||
|
||||
expect(screen.getByText('Ada Lovelace')).toBeInTheDocument()
|
||||
expect(screen.getByText('ada@example.com')).toBeInTheDocument()
|
||||
expect(screen.getByText('Artifact sharing')).toBeInTheDocument()
|
||||
expect(screen.getByText('Orca Relay')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Sign out' }))
|
||||
await user.click(screen.getByRole('button', { name: 'Confirm sign out' }))
|
||||
expect(mocks.signOut).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('offers sign in for a local profile', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.state.orcaProfileAuthStatus = { configured: true, state: 'local' }
|
||||
mocks.connect.mockReturnValue(new Promise(() => {}))
|
||||
render(<OrcaAccountSettingsPane />)
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Sign in to extend Orca with cloud features, including Artifacts and Orca Relay.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
await user.click(screen.getByRole('button', { name: 'Sign in to Orca' }))
|
||||
expect(mocks.connect).toHaveBeenCalledOnce()
|
||||
expect(screen.getByRole('button', { name: 'Sign in to Orca' })).toBeEnabled()
|
||||
await user.click(screen.getByRole('button', { name: 'Sign in to Orca' }))
|
||||
expect(mocks.connect).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('loads account status when it is not hydrated yet', () => {
|
||||
mocks.state.orcaProfileAuthStatus = null
|
||||
render(<OrcaAccountSettingsPane />)
|
||||
|
||||
expect(mocks.fetchAuthStatus).toHaveBeenCalledOnce()
|
||||
expect(screen.getByRole('button', { name: 'Sign in to Orca' })).toBeDisabled()
|
||||
})
|
||||
})
|
||||
@@ -1,80 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (_key: string, fallback: string) => fallback
|
||||
}))
|
||||
|
||||
vi.mock('@/i18n/localized-catalog', () => ({
|
||||
createLocalizedCatalog:
|
||||
<T>(loader: () => T) =>
|
||||
() =>
|
||||
loader()
|
||||
}))
|
||||
|
||||
vi.mock('./settings-search-keywords', () => ({
|
||||
translateSearchKeyword: (_key: string, fallback: string) => [fallback]
|
||||
}))
|
||||
|
||||
import {
|
||||
getAccountsMiniMaxSearchEntries,
|
||||
getAccountsOpencodeSearchEntries,
|
||||
getAccountsPaneSearchEntries
|
||||
} from './accounts-search'
|
||||
|
||||
describe('getAccountsPaneSearchEntries', () => {
|
||||
it('keeps Antigravity and GLM plan settings discoverable in pane order', () => {
|
||||
const entries = getAccountsPaneSearchEntries()
|
||||
const titles = entries.map((entry) => entry.title)
|
||||
expect(
|
||||
titles.filter((title) =>
|
||||
['Antigravity Accounts', 'Cursor Usage', 'GLM Coding Plan'].includes(title)
|
||||
)
|
||||
).toEqual(['Antigravity Accounts', 'Cursor Usage', 'GLM Coding Plan'])
|
||||
expect(entries.find((entry) => entry.title === 'Antigravity Accounts')?.keywords).toEqual(
|
||||
expect.arrayContaining(['antigravity', 'agy', 'google', 'accounts'])
|
||||
)
|
||||
expect(entries.find((entry) => entry.title === 'GLM Coding Plan')?.keywords).toEqual(
|
||||
expect.arrayContaining(['glm', 'zai', 'zhipu', 'bigmodel', 'coding plan'])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAccountsMiniMaxSearchEntries', () => {
|
||||
it('returns a single entry that targets the MiniMax session cookie flow', () => {
|
||||
const entries = getAccountsMiniMaxSearchEntries()
|
||||
expect(entries).toHaveLength(1)
|
||||
const [entry] = entries
|
||||
expect(entry.title).toBe('MiniMax Usage')
|
||||
expect(entry.description.toLowerCase()).toContain('cookie')
|
||||
expect(entry.description.toLowerCase()).toContain('api key')
|
||||
})
|
||||
|
||||
it('exposes the keywords that drive the Settings search index', () => {
|
||||
const [entry] = getAccountsMiniMaxSearchEntries()
|
||||
expect(entry.keywords).toEqual(
|
||||
expect.arrayContaining(['minimax', 'cookie', 'session', 'rate limit', 'status bar'])
|
||||
)
|
||||
})
|
||||
|
||||
it('is included in the rolled-up pane search entries', () => {
|
||||
const allEntries = getAccountsPaneSearchEntries()
|
||||
const titles = allEntries.map((entry) => entry.title)
|
||||
expect(titles).toContain('MiniMax Usage')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAccountsOpencodeSearchEntries', () => {
|
||||
it('tells search to paste the full Cookie header including the console session', () => {
|
||||
const cookieEntry = getAccountsOpencodeSearchEntries().find(
|
||||
(entry) => entry.title === 'OpenCode Go Session Cookie'
|
||||
)
|
||||
|
||||
expect(cookieEntry).toBeDefined()
|
||||
expect(cookieEntry?.description).toContain('__Host-console_session')
|
||||
expect(cookieEntry?.description).toContain('Cookie header')
|
||||
expect(cookieEntry?.description).not.toMatch(/Fe26\.2\*\*/)
|
||||
expect(cookieEntry?.keywords).toEqual(
|
||||
expect.arrayContaining(['opencode', 'cookie', 'session', 'console', 'rate limit'])
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,56 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (_key: string, fallback: string) => fallback
|
||||
}))
|
||||
|
||||
vi.mock('@/i18n/localized-catalog', () => ({
|
||||
createLocalizedCatalog:
|
||||
<T>(loader: () => T) =>
|
||||
() =>
|
||||
loader()
|
||||
}))
|
||||
|
||||
vi.mock('./settings-search-keywords', () => ({
|
||||
translateSearchKeyword: (_key: string, fallback: string) => [fallback]
|
||||
}))
|
||||
|
||||
import { getStatusBarToggles } from './appearance-status-bar-search'
|
||||
|
||||
describe('getStatusBarToggles', () => {
|
||||
it('includes Antigravity usage so Appearance can toggle the default-on status item', () => {
|
||||
const antigravityToggle = getStatusBarToggles().find((entry) => entry.id === 'antigravity')
|
||||
|
||||
expect(antigravityToggle).toMatchObject({
|
||||
title: 'Antigravity Usage',
|
||||
description: 'Show Antigravity subscription usage in the status bar.',
|
||||
toggleDescription: 'Show Antigravity subscription usage for the active workspace.'
|
||||
})
|
||||
expect(antigravityToggle?.keywords).toEqual(
|
||||
expect.arrayContaining(['status bar', 'antigravity', 'usage', 'subscription', 'google'])
|
||||
)
|
||||
})
|
||||
|
||||
it('includes ZCode quota in Appearance search', () => {
|
||||
const toggle = getStatusBarToggles().find((entry) => entry.id === 'zcode')
|
||||
|
||||
expect(toggle).toMatchObject({
|
||||
title: 'ZCode Usage',
|
||||
toggleDescription: 'Show ZCode Coding Plan quota usage.'
|
||||
})
|
||||
expect(toggle?.keywords).toEqual(expect.arrayContaining(['zcode', 'zai', 'glm']))
|
||||
})
|
||||
|
||||
it('includes MiniMax usage so Appearance can toggle the default-on status item', () => {
|
||||
const miniMaxToggle = getStatusBarToggles().find((entry) => entry.id === 'minimax')
|
||||
|
||||
expect(miniMaxToggle).toMatchObject({
|
||||
title: 'MiniMax Usage',
|
||||
description: 'Show MiniMax subscription usage in the status bar.',
|
||||
toggleDescription: 'Show MiniMax subscription usage for the active workspace.'
|
||||
})
|
||||
expect(miniMaxToggle?.keywords).toEqual(
|
||||
expect.arrayContaining(['status bar', 'minimax', 'usage', 'subscription', 'cookie'])
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,99 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { PreflightStatus } from '../../../../preload/api-types'
|
||||
import {
|
||||
getPreflightIntegrationStatuses,
|
||||
giteaStatusFromPreflight,
|
||||
tokenApiStatusFromPreflight
|
||||
} from './integrations-pane-status'
|
||||
|
||||
const connectedPreflight: PreflightStatus = {
|
||||
git: { installed: true },
|
||||
gh: { installed: true, authenticated: true },
|
||||
glab: { installed: true, authenticated: true },
|
||||
bitbucket: { configured: true, authenticated: true, account: 'bb-user' },
|
||||
azureDevOps: {
|
||||
configured: true,
|
||||
authenticated: true,
|
||||
account: 'ado-user',
|
||||
baseUrl: 'https://dev.azure.com/acme',
|
||||
tokenConfigured: true
|
||||
},
|
||||
gitea: {
|
||||
configured: true,
|
||||
authenticated: true,
|
||||
account: 'gitea-user',
|
||||
baseUrl: 'https://gitea.example/api/v1',
|
||||
tokenConfigured: true
|
||||
}
|
||||
}
|
||||
|
||||
describe('tokenApiStatusFromPreflight', () => {
|
||||
it('treats token-only Azure DevOps setup as configured while base URL is inferred later', () => {
|
||||
expect(
|
||||
tokenApiStatusFromPreflight({
|
||||
configured: true,
|
||||
authenticated: false,
|
||||
account: null,
|
||||
baseUrl: null,
|
||||
tokenConfigured: true
|
||||
})
|
||||
).toBe('configured')
|
||||
})
|
||||
|
||||
it('marks configured Azure DevOps credentials as unauthenticated after auth failure', () => {
|
||||
expect(
|
||||
tokenApiStatusFromPreflight({
|
||||
configured: true,
|
||||
authenticated: false,
|
||||
account: null,
|
||||
baseUrl: 'https://dev.azure.com/acme',
|
||||
tokenConfigured: true
|
||||
})
|
||||
).toBe('not-authenticated')
|
||||
})
|
||||
})
|
||||
|
||||
describe('giteaStatusFromPreflight', () => {
|
||||
it('keeps missing Gitea setup optional and flags configured auth failures', () => {
|
||||
expect(giteaStatusFromPreflight(undefined)).toBe('not-configured')
|
||||
expect(
|
||||
giteaStatusFromPreflight({
|
||||
configured: true,
|
||||
authenticated: false,
|
||||
account: null,
|
||||
baseUrl: 'https://gitea.example/api/v1',
|
||||
tokenConfigured: true
|
||||
})
|
||||
).toBe('not-authenticated')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getPreflightIntegrationStatuses', () => {
|
||||
it('shows checking before preflight status arrives', () => {
|
||||
expect(getPreflightIntegrationStatuses(null, new Set()).ghStatus).toBe('checking')
|
||||
})
|
||||
|
||||
it('derives connected status labels and account details from preflight state', () => {
|
||||
expect(getPreflightIntegrationStatuses(connectedPreflight, new Set())).toMatchObject({
|
||||
ghStatus: 'connected',
|
||||
glabStatus: 'connected',
|
||||
bitbucketStatus: 'connected',
|
||||
bitbucketAccount: 'bb-user',
|
||||
azureDevOpsStatus: 'configured',
|
||||
azureDevOpsAccount: 'ado-user',
|
||||
giteaStatus: 'configured',
|
||||
giteaAccount: 'gitea-user'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps a manually refreshed provider in checking state until its request settles', () => {
|
||||
expect(
|
||||
getPreflightIntegrationStatuses(connectedPreflight, new Set(['glab', 'gitea']))
|
||||
).toMatchObject({
|
||||
ghStatus: 'connected',
|
||||
glabStatus: 'checking',
|
||||
bitbucketStatus: 'connected',
|
||||
giteaStatus: 'checking'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,65 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getExecutionHostLabel } from '../../../../shared/execution-host'
|
||||
import {
|
||||
getProviderAccountScope,
|
||||
getProviderRateLimitScope,
|
||||
getRemoteAccountsPaneScope
|
||||
} from './provider-account-scope'
|
||||
|
||||
const LOCAL_HOST_LABEL = getExecutionHostLabel('local')
|
||||
|
||||
describe('getProviderAccountScope', () => {
|
||||
it('describes provider accounts as client-owned without an active runtime', () => {
|
||||
for (const activeRuntimeEnvironmentId of [null, ' ']) {
|
||||
expect(getProviderAccountScope({ activeRuntimeEnvironmentId })).toEqual({
|
||||
label: LOCAL_HOST_LABEL,
|
||||
description:
|
||||
'Credentials and account checks for this provider are owned by this desktop client. To use a server, change Settings > Remote Orca Servers > New projects open on.'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('describes provider accounts as remote-server-owned with an active runtime', () => {
|
||||
expect(getProviderAccountScope({ activeRuntimeEnvironmentId: ' env-1 ' })).toEqual({
|
||||
label: 'Remote server: env-1',
|
||||
description:
|
||||
'Credentials and account checks for this provider are owned by this remote server. To use another host, change Settings > Remote Orca Servers > New projects open on.'
|
||||
})
|
||||
})
|
||||
|
||||
it('describes provider API budgets as host-scoped', () => {
|
||||
expect(getProviderRateLimitScope({ kind: 'local' }, 'GitHub')).toEqual({
|
||||
label: LOCAL_HOST_LABEL,
|
||||
description:
|
||||
'GitHub API budget is fetched from the CLI on this desktop client. Choose a server in the Host menu at the top of Settings to see its budget.'
|
||||
})
|
||||
expect(
|
||||
getProviderRateLimitScope({ kind: 'environment', environmentId: 'env-1' }, 'GitLab')
|
||||
).toEqual({
|
||||
label: 'Remote server: env-1',
|
||||
description:
|
||||
'GitLab API budget is fetched from the CLI on this remote server. Choose another host in the Host menu at the top of Settings to see its budget.'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getRemoteAccountsPaneScope', () => {
|
||||
const LOCAL_ACCOUNTS_KEPT =
|
||||
'Accounts managed on this desktop are unchanged. Choose this computer in the Host menu at the top of Settings to view them.'
|
||||
|
||||
it('names the owning server once the saved-server list resolves', () => {
|
||||
expect(getRemoteAccountsPaneScope(' build-box ')).toEqual({
|
||||
label: 'Remote server: build-box',
|
||||
description: LOCAL_ACCOUNTS_KEPT
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps the label bare before the server name is known', () => {
|
||||
for (const unnamed of [null, '', ' ']) {
|
||||
expect(getRemoteAccountsPaneScope(unnamed)).toEqual({
|
||||
label: 'Remote server',
|
||||
description: LOCAL_ACCOUNTS_KEPT
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
+1
-64
@@ -1,20 +1,12 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { ExecutionHostRegistryEntry } from '../../../../../../shared/execution-host-registry'
|
||||
import {
|
||||
PROJECT_HOST_SETUP_RUNTIME_CAPABILITY,
|
||||
WORKSPACE_RUN_CONTEXT_RUNTIME_CAPABILITY
|
||||
} from '../../../../../../shared/worktree-runtime-capabilities'
|
||||
import { PROJECT_HOST_SETUP_RUNTIME_CAPABILITY } from '../../../../../../shared/worktree-runtime-capabilities'
|
||||
import { buildSetupHostOptions } from './repository-host-setup-options'
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (_key: string, fallback: string) => fallback
|
||||
}))
|
||||
|
||||
const FULL_HOST_MODEL_RUNTIME_CAPABILITIES = [
|
||||
PROJECT_HOST_SETUP_RUNTIME_CAPABILITY,
|
||||
WORKSPACE_RUN_CONTEXT_RUNTIME_CAPABILITY
|
||||
]
|
||||
|
||||
function runtimeHost(
|
||||
overrides: Partial<ExecutionHostRegistryEntry> = {}
|
||||
): ExecutionHostRegistryEntry {
|
||||
@@ -28,46 +20,7 @@ function runtimeHost(
|
||||
} as ExecutionHostRegistryEntry
|
||||
}
|
||||
|
||||
function sshHost(health: ExecutionHostRegistryEntry['health']): ExecutionHostRegistryEntry {
|
||||
return {
|
||||
id: 'ssh:ssh-1',
|
||||
kind: 'ssh',
|
||||
label: 'SSH Host',
|
||||
detail: 'SSH',
|
||||
health
|
||||
}
|
||||
}
|
||||
|
||||
describe('buildSetupHostOptions', () => {
|
||||
it('enables connected SSH hosts', () => {
|
||||
expect(
|
||||
buildSetupHostOptions({
|
||||
projectHostSetups: [],
|
||||
hostOptions: [sshHost('available')]
|
||||
})[0]
|
||||
).toMatchObject({
|
||||
isAvailable: true,
|
||||
canUsePathActions: true,
|
||||
detail: 'SSH'
|
||||
})
|
||||
})
|
||||
|
||||
it.each(['disconnected', 'connecting', 'error'] as const)(
|
||||
'disables path actions for %s SSH hosts',
|
||||
(health) => {
|
||||
expect(
|
||||
buildSetupHostOptions({
|
||||
projectHostSetups: [],
|
||||
hostOptions: [sshHost(health)]
|
||||
})[0]
|
||||
).toMatchObject({
|
||||
isAvailable: true,
|
||||
canUsePathActions: false,
|
||||
detail: 'Connect this host before importing or cloning the project'
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
it('disables runtime hosts while capabilities are unknown', () => {
|
||||
expect(
|
||||
buildSetupHostOptions({
|
||||
@@ -80,22 +33,6 @@ describe('buildSetupHostOptions', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('enables runtime hosts that advertise project setup and workspace run support', () => {
|
||||
expect(
|
||||
buildSetupHostOptions({
|
||||
projectHostSetups: [],
|
||||
hostOptions: [
|
||||
runtimeHost({
|
||||
capabilities: FULL_HOST_MODEL_RUNTIME_CAPABILITIES
|
||||
})
|
||||
]
|
||||
})[0]
|
||||
).toMatchObject({
|
||||
isAvailable: true,
|
||||
detail: 'Orca server'
|
||||
})
|
||||
})
|
||||
|
||||
it('disables runtime hosts that cannot run workspaces with explicit host context', () => {
|
||||
expect(
|
||||
buildSetupHostOptions({
|
||||
|
||||
@@ -24,23 +24,6 @@ function createThemeOption(
|
||||
}
|
||||
|
||||
describe('settings-form-option-filter', () => {
|
||||
it('filters terminal themes by label and source label', () => {
|
||||
const themes = [
|
||||
createThemeOption('Solarized Dark'),
|
||||
createThemeOption('Night Owl', 'imported', 'Imported from Ghostty'),
|
||||
createThemeOption('Tango Light')
|
||||
]
|
||||
|
||||
expect(filterTerminalThemeOptions(themes, 'ghostty')).toEqual([themes[1]])
|
||||
expect(filterTerminalThemeOptions(themes, ' dark ')).toEqual([themes[0]])
|
||||
})
|
||||
|
||||
it('orders font suggestions by prefix matches before substring matches', () => {
|
||||
expect(filterFontSuggestions(['Fira Code', 'Cascadia Code', 'Code New Roman'], 'code')).toEqual(
|
||||
['Code New Roman', 'Fira Code', 'Cascadia Code']
|
||||
)
|
||||
})
|
||||
|
||||
it('enforces the query budget by UTF-8 byte length', () => {
|
||||
const query = 'é'.repeat(SETTINGS_FORM_OPTION_QUERY_MAX_BYTES)
|
||||
|
||||
|
||||
@@ -1,32 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getKeybindingDefinition } from '../../../../shared/keybindings'
|
||||
import type { ShortcutGroup } from './shortcut-groups'
|
||||
|
||||
import { buildShortcutRowVisibility } from './shortcut-row-visibility'
|
||||
|
||||
function creationGroup(): ShortcutGroup {
|
||||
const items = (
|
||||
['tab.newTerminal', 'tab.newBrowser', 'tab.newMarkdown', 'tab.newSimulator'] as const
|
||||
).map((actionId) => {
|
||||
const definition = getKeybindingDefinition(actionId)
|
||||
if (!definition) {
|
||||
throw new Error(`Missing keybinding definition: ${actionId}`)
|
||||
}
|
||||
return definition
|
||||
})
|
||||
return { title: 'Tabs', items }
|
||||
}
|
||||
|
||||
function globalGroup(): ShortcutGroup {
|
||||
const items = (['workspace.openBoard', 'dashboard.toggle'] as const).map((actionId) => {
|
||||
const definition = getKeybindingDefinition(actionId)
|
||||
if (!definition) {
|
||||
throw new Error(`Missing keybinding definition: ${actionId}`)
|
||||
}
|
||||
return definition
|
||||
})
|
||||
return { title: 'Global', items }
|
||||
}
|
||||
|
||||
const baseOptions = {
|
||||
keybindings: {},
|
||||
conflictByAction: new Map(),
|
||||
@@ -55,42 +31,4 @@ describe('buildShortcutRowVisibility', () => {
|
||||
expect(result.filterCounts.modified).toBe(0)
|
||||
expect(result.filterCounts.unassigned).toBe(1)
|
||||
})
|
||||
|
||||
it('hides client-impossible creation shortcuts without hiding terminal or markdown', () => {
|
||||
const result = buildShortcutRowVisibility({
|
||||
groups: [creationGroup()],
|
||||
keybindings: {},
|
||||
conflictByAction: new Map(),
|
||||
terminalShortcutPolicy: 'orca-first',
|
||||
platform: 'darwin',
|
||||
managedBrowserCreationEnabled: false,
|
||||
mobileEmulatorCreationEnabled: false,
|
||||
agentDashboardEnabled: false,
|
||||
settingsSearchQuery: '',
|
||||
shortcutQuery: '',
|
||||
shortcutFilter: 'all'
|
||||
})
|
||||
|
||||
expect(result.shortcutRows.map((row) => row.item.id)).toEqual([
|
||||
'tab.newTerminal',
|
||||
'tab.newMarkdown'
|
||||
])
|
||||
})
|
||||
|
||||
it('hides the agent dashboard toggle while its experiment is off', () => {
|
||||
const hidden = buildShortcutRowVisibility({ ...baseOptions, groups: [globalGroup()] })
|
||||
|
||||
expect(hidden.shortcutRows.map((row) => row.item.id)).toEqual(['workspace.openBoard'])
|
||||
|
||||
const shown = buildShortcutRowVisibility({
|
||||
...baseOptions,
|
||||
groups: [globalGroup()],
|
||||
agentDashboardEnabled: true
|
||||
})
|
||||
|
||||
expect(shown.shortcutRows.map((row) => row.item.id)).toEqual([
|
||||
'workspace.openBoard',
|
||||
'dashboard.toggle'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
formatCreateProjectParentSummary,
|
||||
getCreateProjectDefaultParentAutoFill,
|
||||
getDefaultCreateProjectParent,
|
||||
joinCreateProjectPath
|
||||
} from './create-project-defaults'
|
||||
|
||||
describe('create project defaults', () => {
|
||||
it('builds the POSIX default project parent', () => {
|
||||
expect(getDefaultCreateProjectParent('/Users/alice')).toBe('/Users/alice/orca/projects')
|
||||
})
|
||||
|
||||
it('builds the Windows default project parent', () => {
|
||||
expect(getDefaultCreateProjectParent('C:\\Users\\alice')).toBe(
|
||||
'C:\\Users\\alice\\orca\\projects'
|
||||
)
|
||||
})
|
||||
|
||||
it('derives the runtime project default from a resolved server home', () => {
|
||||
expect(getDefaultCreateProjectParent('/home/alice')).toBe('/home/alice/orca/projects')
|
||||
})
|
||||
|
||||
it('joins path previews without mixing separators', () => {
|
||||
expect(joinCreateProjectPath('/home/alice/orca/projects', 'demo')).toBe(
|
||||
'/home/alice/orca/projects/demo'
|
||||
)
|
||||
expect(joinCreateProjectPath('C:\\Users\\alice\\orca\\projects', 'demo')).toBe(
|
||||
'C:\\Users\\alice\\orca\\projects\\demo'
|
||||
)
|
||||
})
|
||||
|
||||
it('auto-fills only the first empty local create step', () => {
|
||||
expect(
|
||||
getCreateProjectDefaultParentAutoFill({
|
||||
step: 'create',
|
||||
createParent: '',
|
||||
activeRuntimeEnvironmentId: null,
|
||||
defaultParent: '/Users/alice/orca/projects',
|
||||
createStepAutoFilled: false
|
||||
})
|
||||
).toEqual({ parent: '/Users/alice/orca/projects' })
|
||||
expect(
|
||||
getCreateProjectDefaultParentAutoFill({
|
||||
step: 'create',
|
||||
createParent: '/tmp/project',
|
||||
activeRuntimeEnvironmentId: null,
|
||||
defaultParent: '/Users/alice/orca/projects',
|
||||
createStepAutoFilled: false
|
||||
})
|
||||
).toBeNull()
|
||||
expect(
|
||||
getCreateProjectDefaultParentAutoFill({
|
||||
step: 'create',
|
||||
createParent: '',
|
||||
activeRuntimeEnvironmentId: null,
|
||||
defaultParent: '/Users/alice/orca/projects',
|
||||
createStepAutoFilled: true
|
||||
})
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('does not apply a local default while a runtime environment is active', () => {
|
||||
expect(
|
||||
getCreateProjectDefaultParentAutoFill({
|
||||
step: 'create',
|
||||
createParent: '',
|
||||
activeRuntimeEnvironmentId: 'env-1',
|
||||
defaultParent: '/Users/alice/orca/projects',
|
||||
createStepAutoFilled: false
|
||||
})
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('uses a short local summary only for the local default parent', () => {
|
||||
expect(
|
||||
formatCreateProjectParentSummary({
|
||||
parent: '/Users/alice/orca/projects',
|
||||
defaultParent: '/Users/alice/orca/projects'
|
||||
})
|
||||
).toBe('~/orca/projects')
|
||||
expect(
|
||||
formatCreateProjectParentSummary({
|
||||
parent: '/home/alice/orca/projects',
|
||||
defaultParent: '/home/alice/orca/projects'
|
||||
})
|
||||
).toBe('~/orca/projects')
|
||||
expect(
|
||||
formatCreateProjectParentSummary({
|
||||
parent: 'C:\\Users\\alice\\orca\\projects',
|
||||
defaultParent: 'C:\\Users\\alice\\orca\\projects'
|
||||
})
|
||||
).toBe('~/orca/projects')
|
||||
expect(
|
||||
formatCreateProjectParentSummary({
|
||||
parent: '',
|
||||
defaultParent: '',
|
||||
runtimeEnvironmentId: 'env-1'
|
||||
})
|
||||
).toBe('host folder not selected')
|
||||
expect(
|
||||
formatCreateProjectParentSummary({
|
||||
parent: '/Users/alice/orca/projects',
|
||||
defaultParent: '/Users/alice/orca/projects',
|
||||
isRemoteHost: true
|
||||
})
|
||||
).toBe('/Users/alice/orca/projects')
|
||||
expect(
|
||||
formatCreateProjectParentSummary({
|
||||
parent: '',
|
||||
defaultParent: '',
|
||||
isRemoteHost: true
|
||||
})
|
||||
).toBe('host folder not selected')
|
||||
})
|
||||
|
||||
it('keeps a configured Workspace Directory verbatim in the summary', () => {
|
||||
expect(
|
||||
formatCreateProjectParentSummary({
|
||||
parent: 'J:\\PROJECTS',
|
||||
defaultParent: 'J:\\PROJECTS'
|
||||
})
|
||||
).toBe('J:\\PROJECTS')
|
||||
expect(
|
||||
formatCreateProjectParentSummary({
|
||||
parent: '/data/orca/projects',
|
||||
defaultParent: '/data/orca/projects'
|
||||
})
|
||||
).toBe('/data/orca/projects')
|
||||
expect(
|
||||
formatCreateProjectParentSummary({
|
||||
parent: 'D:\\code\\orca\\projects',
|
||||
defaultParent: 'D:\\code\\orca\\projects'
|
||||
})
|
||||
).toBe('D:\\code\\orca\\projects')
|
||||
})
|
||||
})
|
||||
@@ -3,7 +3,7 @@
|
||||
import { act, type ReactNode } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { isSidebarLabelTruncated, TruncatedSidebarLabel } from './truncated-sidebar-label'
|
||||
import { TruncatedSidebarLabel } from './truncated-sidebar-label'
|
||||
|
||||
vi.mock('@/components/ui/tooltip', () => ({
|
||||
Tooltip: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
@@ -15,17 +15,6 @@ vi.mock('@/components/ui/tooltip', () => ({
|
||||
TooltipTrigger: ({ children }: { children: ReactNode }) => <>{children}</>
|
||||
}))
|
||||
|
||||
describe('isSidebarLabelTruncated', () => {
|
||||
it('returns false when the label fits', () => {
|
||||
expect(isSidebarLabelTruncated({ clientWidth: 120, scrollWidth: 120 })).toBe(false)
|
||||
expect(isSidebarLabelTruncated({ clientWidth: 120, scrollWidth: 119 })).toBe(false)
|
||||
})
|
||||
|
||||
it('returns true when the label overflows', () => {
|
||||
expect(isSidebarLabelTruncated({ clientWidth: 120, scrollWidth: 121 })).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('TruncatedSidebarLabel', () => {
|
||||
let container: HTMLDivElement
|
||||
let root: Root
|
||||
@@ -95,17 +84,6 @@ describe('TruncatedSidebarLabel', () => {
|
||||
expect(container.querySelector('[data-tooltip-content]')).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps the nested tooltip disabled when a parent hover owns the full identity', async () => {
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<TruncatedSidebarLabel text="feature/really-long-branch-name" tooltipEnabled={false} />
|
||||
)
|
||||
})
|
||||
|
||||
expect(container.textContent).toContain('feature/really-long-branch-name')
|
||||
expect(container.querySelector('[data-tooltip-content]')).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps one ResizeObserver across a label text change', async () => {
|
||||
const originalResizeObserver = globalThis.ResizeObserver
|
||||
let constructed = 0
|
||||
|
||||
@@ -34,32 +34,6 @@ describe('summarizeCodexRestartStatus', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('counts stale Codex sessions, tabs, and worktrees', () => {
|
||||
expect(
|
||||
summarizeCodexRestartStatus({
|
||||
tabsByWorktree: {
|
||||
wt1: [{ id: 'tab-1' }, { id: 'tab-2' }],
|
||||
wt2: [{ id: 'tab-3' }]
|
||||
},
|
||||
ptyIdsByTabId: {
|
||||
'tab-1': ['pty-1', 'pty-2'],
|
||||
'tab-2': ['pty-3'],
|
||||
'tab-3': ['pty-4']
|
||||
},
|
||||
codexRestartNoticeByPtyId: {
|
||||
'pty-1': { previousAccountLabel: 'a', nextAccountLabel: 'b' },
|
||||
'pty-2': { previousAccountLabel: 'a', nextAccountLabel: 'b' },
|
||||
'pty-4': { previousAccountLabel: 'a', nextAccountLabel: 'b' }
|
||||
}
|
||||
})
|
||||
).toEqual({
|
||||
stalePtyIds: ['pty-1', 'pty-2', 'pty-4'],
|
||||
staleSessionCount: 3,
|
||||
staleTabCount: 2,
|
||||
staleWorktreeCount: 2
|
||||
})
|
||||
})
|
||||
|
||||
it('stops offering sessions whose restart was already requested', () => {
|
||||
expect(
|
||||
summarizeCodexRestartStatus({
|
||||
@@ -96,16 +70,4 @@ describe('summarizeCodexRestartStatus', () => {
|
||||
staleWorktreeCount: 1
|
||||
})
|
||||
})
|
||||
|
||||
it('hides the prompt once every stale session has a requested restart', () => {
|
||||
expect(
|
||||
summarizeCodexRestartStatus({
|
||||
tabsByWorktree: { wt1: [{ id: 'tab-1' }] },
|
||||
ptyIdsByTabId: { 'tab-1': ['pty-1'] },
|
||||
codexRestartNoticeByPtyId: {
|
||||
'pty-1': { previousAccountLabel: 'a', nextAccountLabel: 'b', restartRequested: true }
|
||||
}
|
||||
}).staleTabCount
|
||||
).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { RemoteRuntimeSharedConnectionDiagnostics } from '../../../../shared/remote-runtime/control/remote-runtime-shared-control-types'
|
||||
import {
|
||||
runtimeHostBlockedCopy,
|
||||
runtimeHostConnectionDetail,
|
||||
runtimeHostStatusForOverall
|
||||
} from './remote-host-connection-status'
|
||||
@@ -12,25 +11,6 @@ describe('blocked runtime hosts', () => {
|
||||
expect(runtimeHostStatusForOverall('connected', 'incompatible')).toBe('blocked')
|
||||
expect(runtimeHostStatusForOverall('disconnected', undefined)).toBe('disconnected')
|
||||
})
|
||||
|
||||
it('words each refusal for what the user must do', () => {
|
||||
expect(runtimeHostBlockedCopy('access-revoked').label).toBe('Access revoked')
|
||||
expect(runtimeHostBlockedCopy('access-revoked').explanation).toMatch(/new pairing link/)
|
||||
expect(runtimeHostBlockedCopy('access-revoked', undefined, 'ssh').explanation).toMatch(/SSH/)
|
||||
// WSL and cloud VMs never re-pair over SSH, so they are not told they tried.
|
||||
const onConnect = runtimeHostBlockedCopy('access-revoked', undefined, 'connect').explanation
|
||||
expect(onConnect).toBe('Connect to try again.')
|
||||
const update = runtimeHostBlockedCopy('incompatible', {
|
||||
kind: 'blocked',
|
||||
reason: 'server-too-old',
|
||||
clientProtocolVersion: 5,
|
||||
serverProtocolVersion: 3,
|
||||
requiredServerProtocolVersion: 4
|
||||
})
|
||||
expect(update.label).toBe('Update required')
|
||||
expect(update.detail).toContain('Update Orca on the server')
|
||||
expect(runtimeHostBlockedCopy('incompatible').detail).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('runtimeHostConnectionDetail', () => {
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// Use catalog fallbacks with i18next-style placeholder expansion to assert source copy.
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (_key: string, fallback: string, options?: Record<string, unknown>) =>
|
||||
fallback.replace(/\{\{(\w+)\}\}/g, (match, name: string) => String(options?.[name] ?? match))
|
||||
}))
|
||||
|
||||
import {
|
||||
formatTerminalSessionCount,
|
||||
getResourceManagerAriaLabel,
|
||||
getResourceManagerTooltipLines
|
||||
} from './resource-manager-terminal-copy'
|
||||
|
||||
describe('resource manager terminal copy', () => {
|
||||
it('formats terminal session counts with the terminal noun visible', () => {
|
||||
expect(formatTerminalSessionCount(1)).toBe('1 terminal session')
|
||||
expect(formatTerminalSessionCount(3)).toBe('3 terminal sessions')
|
||||
})
|
||||
|
||||
it('points users from the status-bar count back to workspace terminals', () => {
|
||||
expect(
|
||||
getResourceManagerTooltipLines({
|
||||
memoryLabel: '512 MB · Σ RSS',
|
||||
sessionCount: 2,
|
||||
spaceScanReady: false
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
id: 'summary',
|
||||
text: 'Resource Manager - 512 MB · Σ RSS - 2 terminal sessions',
|
||||
emphasized: false
|
||||
},
|
||||
{
|
||||
id: 'sessions-hint',
|
||||
text: 'Terminal sessions are grouped by workspace.',
|
||||
emphasized: false
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps local session copy active under runtime focus', () => {
|
||||
expect(
|
||||
getResourceManagerTooltipLines({
|
||||
memoryLabel: '-',
|
||||
sessionCount: 0,
|
||||
spaceScanReady: true
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
id: 'summary',
|
||||
text: 'Resource Manager - memory unavailable - 0 terminal sessions',
|
||||
emphasized: false
|
||||
},
|
||||
{ id: 'space-scan', text: 'Space scan ready', emphasized: true },
|
||||
{ id: 'sessions-hint', text: 'No terminal sessions yet.', emphasized: false }
|
||||
])
|
||||
})
|
||||
|
||||
// Translated space-scan rows must retain emphasis without matching English text.
|
||||
it('flags the space-scan row instead of leaving callers to match its wording', () => {
|
||||
const lines = getResourceManagerTooltipLines({
|
||||
memoryLabel: '512 MB',
|
||||
sessionCount: 1,
|
||||
spaceScanReady: true
|
||||
})
|
||||
|
||||
expect(lines.filter((line) => line.emphasized).map((line) => line.text)).toEqual([
|
||||
'Space scan ready'
|
||||
])
|
||||
})
|
||||
|
||||
// Why: the tooltip keys rows by id, so a repeated id would silently drop a row.
|
||||
it('gives every tooltip row a unique id to key on', () => {
|
||||
for (const spaceScanReady of [false, true]) {
|
||||
for (const sessionCount of [0, 1, 4]) {
|
||||
const ids = getResourceManagerTooltipLines({
|
||||
memoryLabel: '512 MB',
|
||||
sessionCount,
|
||||
spaceScanReady
|
||||
}).map((line) => line.id)
|
||||
|
||||
expect(new Set(ids).size).toBe(ids.length)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps the trigger label descriptive for screen readers', () => {
|
||||
expect(
|
||||
getResourceManagerAriaLabel({
|
||||
sessionCount: 1,
|
||||
spaceScanReady: true
|
||||
})
|
||||
).toBe('Resource Manager, 1 terminal session, Space scan ready')
|
||||
})
|
||||
})
|
||||
@@ -1,73 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (_key: string, fallback: string) => fallback
|
||||
}))
|
||||
|
||||
import {
|
||||
getCommitPressureToneClass,
|
||||
getResourceCommitMetricCopy,
|
||||
getResourceMemoryMetricCopy
|
||||
} from './resource-memory-metric-copy'
|
||||
|
||||
describe('resource memory metric copy', () => {
|
||||
it('discloses that Unix RSS sums can repeat shared and aliased pages', () => {
|
||||
expect(getResourceMemoryMetricCopy('rss')).toEqual({
|
||||
columnLabel: 'RSS',
|
||||
summaryLabel: 'Σ RSS',
|
||||
description:
|
||||
'Summed resident set size (RSS). Shared or aliased pages can appear in more than one process.'
|
||||
})
|
||||
})
|
||||
|
||||
it('says working set counts only resident pages, so paged-out memory is missing', () => {
|
||||
expect(getResourceMemoryMetricCopy('working-set')).toEqual({
|
||||
columnLabel: 'WS',
|
||||
summaryLabel: 'Σ WS',
|
||||
description:
|
||||
'Summed working set (WS): pages resident in RAM right now. Shared pages can appear in more than one process, and memory Windows has paged out is not counted here.'
|
||||
})
|
||||
})
|
||||
|
||||
it('labels committed bytes as a separate quantity, not a corrected working set', () => {
|
||||
expect(getResourceCommitMetricCopy()).toEqual({
|
||||
summaryLabel: 'Σ Private',
|
||||
description:
|
||||
'Summed private bytes: memory these processes have committed, counted whether it is resident or paged out. This is what the host charges against its commit limit, so it keeps rising while the working set above shrinks under paging.'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('commit pressure tone', () => {
|
||||
const hostTotalMemory = 16 * 1024 ** 3
|
||||
|
||||
it('stays silent while tracked commit is a modest share of RAM', () => {
|
||||
expect(getCommitPressureToneClass({ privateMemory: 4 * 1024 ** 3, hostTotalMemory })).toBeNull()
|
||||
})
|
||||
|
||||
it('warns at the same 60/80 thresholds the host usage bars already use', () => {
|
||||
expect(getCommitPressureToneClass({ privateMemory: 10 * 1024 ** 3, hostTotalMemory })).toBe(
|
||||
'text-yellow-500'
|
||||
)
|
||||
// The reported host: 13.4 GB committed by agents on 16 GB of RAM.
|
||||
expect(getCommitPressureToneClass({ privateMemory: 13.4 * 1024 ** 3, hostTotalMemory })).toBe(
|
||||
'text-red-500'
|
||||
)
|
||||
})
|
||||
|
||||
it('stays silent for a snapshot that carries no commit figure at all', () => {
|
||||
expect(getCommitPressureToneClass({ privateMemory: undefined, hostTotalMemory })).toBeNull()
|
||||
})
|
||||
|
||||
it('stays silent when the host total is unknown, rather than dividing by zero', () => {
|
||||
expect(
|
||||
getCommitPressureToneClass({ privateMemory: 8 * 1024 ** 3, hostTotalMemory: 0 })
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps warning above 100% of RAM rather than capping the share', () => {
|
||||
expect(getCommitPressureToneClass({ privateMemory: 32 * 1024 ** 3, hostTotalMemory })).toBe(
|
||||
'text-red-500'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -25,24 +25,6 @@ describe('splitSshStatusMenuRows', () => {
|
||||
expect(rows.inactive.map((row) => row.id)).toEqual(['d', 'a'])
|
||||
expect(rows.hiddenIdleCount).toBe(2)
|
||||
})
|
||||
|
||||
it('fills the idle cap with hosts that own projects before the rest of the config', () => {
|
||||
const rows = splitSshStatusMenuRows(
|
||||
[target('a', 'disconnected'), target('b', 'disconnected'), target('c', 'disconnected')],
|
||||
{ projectHostIds: new Set(['c']), idleRowLimit: 2 }
|
||||
)
|
||||
expect(rows.inactive.map((row) => row.id)).toEqual(['c', 'a'])
|
||||
expect(rows.hiddenIdleCount).toBe(1)
|
||||
})
|
||||
|
||||
it('keeps config order and hides nothing at or under the limit', () => {
|
||||
const rows = splitSshStatusMenuRows(
|
||||
[target('a', 'disconnected'), target('b', 'error'), target('c', 'disconnected')],
|
||||
{ projectHostIds: new Set(['c']), idleRowLimit: 2 }
|
||||
)
|
||||
expect(rows.hiddenIdleCount).toBe(0)
|
||||
expect(rows.inactive.map((row) => row.id)).toEqual(['a', 'b', 'c'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('findSshHostMatches', () => {
|
||||
@@ -58,12 +40,4 @@ describe('findSshHostMatches', () => {
|
||||
expect(hiddenCount).toBe(27)
|
||||
expect(findSshHostMatches(targets, 'east', 5).shown.map((row) => row.id)).toEqual(['east-prod'])
|
||||
})
|
||||
|
||||
it('leaves out connected hosts, which the menu already lists', () => {
|
||||
const { shown } = findSshHostMatches(
|
||||
[target('web-1', 'connected'), target('web-2', 'disconnected')],
|
||||
'web'
|
||||
)
|
||||
expect(shown.map((row) => row.id)).toEqual(['web-2'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { isStatusBarItemAvailable } from './status-bar-agent-gating'
|
||||
|
||||
describe('isStatusBarItemAvailable', () => {
|
||||
it('shows non-CLI items regardless of detection', () => {
|
||||
expect(isStatusBarItemAvailable('ssh', null)).toBe(true)
|
||||
expect(isStatusBarItemAvailable('ssh', [])).toBe(true)
|
||||
expect(isStatusBarItemAvailable('resource-usage', [])).toBe(true)
|
||||
expect(isStatusBarItemAvailable('ports', [])).toBe(true)
|
||||
expect(isStatusBarItemAvailable('opencode-go', [])).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps CLI items visible while detection is in flight', () => {
|
||||
// Unknown detection preserves items to avoid a cold-start flash.
|
||||
expect(isStatusBarItemAvailable('claude', null)).toBe(true)
|
||||
expect(isStatusBarItemAvailable('codex', null)).toBe(true)
|
||||
expect(isStatusBarItemAvailable('gemini', null)).toBe(true)
|
||||
expect(isStatusBarItemAvailable('antigravity', null)).toBe(true)
|
||||
expect(isStatusBarItemAvailable('grok', null)).toBe(true)
|
||||
expect(isStatusBarItemAvailable('zcode', null)).toBe(true)
|
||||
})
|
||||
|
||||
it('hides CLI items not detected on PATH', () => {
|
||||
expect(isStatusBarItemAvailable('claude', [])).toBe(false)
|
||||
expect(isStatusBarItemAvailable('codex', ['claude'])).toBe(false)
|
||||
expect(isStatusBarItemAvailable('gemini', ['claude', 'codex'])).toBe(false)
|
||||
expect(isStatusBarItemAvailable('antigravity', ['claude', 'codex'])).toBe(false)
|
||||
expect(isStatusBarItemAvailable('grok', ['claude', 'kimi'])).toBe(false)
|
||||
expect(isStatusBarItemAvailable('zcode', ['claude', 'kimi'])).toBe(false)
|
||||
})
|
||||
|
||||
it('shows CLI items detected on PATH', () => {
|
||||
expect(isStatusBarItemAvailable('claude', ['claude'])).toBe(true)
|
||||
expect(isStatusBarItemAvailable('codex', ['codex', 'claude'])).toBe(true)
|
||||
expect(isStatusBarItemAvailable('gemini', ['gemini'])).toBe(true)
|
||||
expect(isStatusBarItemAvailable('antigravity', ['antigravity'])).toBe(true)
|
||||
expect(isStatusBarItemAvailable('grok', ['grok'])).toBe(true)
|
||||
expect(isStatusBarItemAvailable('zcode', ['zcode'])).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -2,7 +2,7 @@ import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { ProviderRateLimits, RateLimitWindow } from '../../../../shared/rate-limit-types'
|
||||
import { ProviderSegment } from './StatusBarProviderSegment'
|
||||
import { UsageUnitLabel, usageChipShowsPercentage } from './usage-unit-label'
|
||||
import { usageChipShowsPercentage } from './usage-unit-label'
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (_key: string, fallback: string, values: Record<string, string> = {}) =>
|
||||
@@ -89,15 +89,3 @@ describe('usageChipShowsPercentage parity with ProviderSegment', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('UsageUnitLabel', () => {
|
||||
it('names the chosen display and stays mounted while collapsed', () => {
|
||||
expect(renderToStaticMarkup(<UsageUnitLabel collapsed={false} display="used" />)).toContain(
|
||||
'>Used<'
|
||||
)
|
||||
const collapsed = renderToStaticMarkup(<UsageUnitLabel collapsed display="remaining" />)
|
||||
expect(collapsed).toContain('>Remaining<')
|
||||
expect(collapsed).toContain('data-usage-collapsed="true"')
|
||||
expect(collapsed).toContain('aria-hidden="true"')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { ProviderRateLimits, RateLimitWindow } from '../../../../shared/rate-limit-types'
|
||||
import { getUsageUnitLabelState, usageChipShowsPercentage } from './usage-unit-label'
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({ translate: (_key: string, fallback: string) => fallback }))
|
||||
vi.mock('@/lib/agent-catalog', () => ({ AgentIcon: () => null }))
|
||||
vi.mock('@/hooks/useResetCountdownClock', () => ({ useResetCountdownClock: () => 0 }))
|
||||
vi.mock('@/components/ui/dropdown-menu', () => ({ DropdownMenuItem: () => null }))
|
||||
vi.mock('@/components/settings/SettingsFormControls', () => ({
|
||||
SettingsSegmentedControl: () => null
|
||||
}))
|
||||
|
||||
function windowOf(usedPercent: number): RateLimitWindow {
|
||||
return { usedPercent, windowMinutes: 300, resetsAt: null, resetDescription: null }
|
||||
}
|
||||
|
||||
function limits(
|
||||
provider: ProviderRateLimits['provider'],
|
||||
status: ProviderRateLimits['status'],
|
||||
session: RateLimitWindow | null = null
|
||||
): ProviderRateLimits {
|
||||
return { provider, session, weekly: null, updatedAt: 0, error: null, status }
|
||||
}
|
||||
|
||||
describe('usageChipShowsPercentage', () => {
|
||||
it('is false for chips that render no percentage', () => {
|
||||
expect(usageChipShowsPercentage(null, 'verbose')).toBe(false)
|
||||
expect(usageChipShowsPercentage(limits('claude', 'idle', windowOf(5)), 'verbose')).toBe(false)
|
||||
expect(usageChipShowsPercentage(limits('claude', 'unavailable'), 'verbose')).toBe(false)
|
||||
expect(usageChipShowsPercentage(limits('claude', 'error'), 'verbose')).toBe(false)
|
||||
expect(usageChipShowsPercentage(limits('claude', 'fetching'), 'compact')).toBe(false)
|
||||
})
|
||||
|
||||
it('is true for an error that still has stale data', () => {
|
||||
expect(usageChipShowsPercentage(limits('codex', 'error', windowOf(40)), 'verbose')).toBe(true)
|
||||
})
|
||||
|
||||
it('follows the verbose bucket filter, which compact mode bypasses', () => {
|
||||
const gemini: ProviderRateLimits = {
|
||||
...limits('gemini', 'ok'),
|
||||
buckets: [{ ...windowOf(30), name: 'Experimental' }]
|
||||
}
|
||||
expect(usageChipShowsPercentage(gemini, 'verbose')).toBe(false)
|
||||
expect(usageChipShowsPercentage(gemini, 'compact')).toBe(true)
|
||||
expect(usageChipShowsPercentage({ ...gemini, weekly: windowOf(10) }, 'verbose')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getUsageUnitLabelState', () => {
|
||||
it('shows the label when a later chip has a percentage and the first is in error', () => {
|
||||
const providers = [limits('codex', 'error'), limits('gemini', 'ok', windowOf(0))]
|
||||
expect(getUsageUnitLabelState(providers, [], 'verbose')).toBe('shown')
|
||||
})
|
||||
|
||||
it('is absent when no chip shows a percentage', () => {
|
||||
const providers = [
|
||||
limits('claude', 'idle'),
|
||||
limits('codex', 'unavailable'),
|
||||
limits('gemini', 'error')
|
||||
]
|
||||
expect(getUsageUnitLabelState(providers, [], 'verbose')).toBe('absent')
|
||||
expect(getUsageUnitLabelState([], [], 'compact')).toBe('absent')
|
||||
})
|
||||
|
||||
it('collapses only when every chip with a percentage is folded', () => {
|
||||
const providers = [
|
||||
limits('codex', 'error'),
|
||||
limits('gemini', 'ok', windowOf(10)),
|
||||
limits('claude', 'error', windowOf(20))
|
||||
]
|
||||
expect(getUsageUnitLabelState(providers, ['gemini'], 'verbose')).toBe('shown')
|
||||
expect(getUsageUnitLabelState(providers, ['gemini', 'claude'], 'verbose')).toBe('collapsed')
|
||||
expect(getUsageUnitLabelState(providers, ['gemini', 'claude'], 'compact')).toBe('collapsed')
|
||||
})
|
||||
})
|
||||
@@ -1,68 +0,0 @@
|
||||
import { describe, expect, it, afterEach, vi } from 'vitest'
|
||||
import { buildGitLabProviderIdentity, getTaskPageRepoCacheInput } from './task-page-source-context'
|
||||
import type { Repo } from '../../../../shared/repo-types'
|
||||
|
||||
describe('buildGitLabProviderIdentity', () => {
|
||||
it('splits namespace and project and builds the web URL', () => {
|
||||
expect(
|
||||
buildGitLabProviderIdentity({
|
||||
host: 'gitlab.example.com',
|
||||
path: 'acme/platform/orca'
|
||||
})
|
||||
).toEqual({
|
||||
provider: 'gitlab',
|
||||
projectId: 'acme/platform/orca',
|
||||
namespace: 'acme/platform',
|
||||
project: 'orca',
|
||||
webUrl: 'https://gitlab.example.com/acme/platform/orca'
|
||||
})
|
||||
})
|
||||
|
||||
it('treats a single path segment as the project with no namespace', () => {
|
||||
expect(
|
||||
buildGitLabProviderIdentity({
|
||||
host: 'gitlab.com',
|
||||
path: 'solo'
|
||||
})
|
||||
).toEqual({
|
||||
provider: 'gitlab',
|
||||
projectId: 'solo',
|
||||
namespace: null,
|
||||
project: 'solo',
|
||||
webUrl: 'https://gitlab.com/solo'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTaskPageRepoCacheInput', () => {
|
||||
it('copies repo identity fields used by the GitHub work-item cache', () => {
|
||||
const repo = {
|
||||
id: 'repo-1',
|
||||
path: '/tmp/orca',
|
||||
executionHostId: 'local'
|
||||
} as Repo
|
||||
const input = getTaskPageRepoCacheInput(repo)
|
||||
expect(input.id).toBe('repo-1')
|
||||
expect(input.path).toBe('/tmp/orca')
|
||||
expect(input.executionHostId).toBe('local')
|
||||
})
|
||||
})
|
||||
|
||||
const { rejectAppStoreAccess } = vi.hoisted(() => ({
|
||||
rejectAppStoreAccess: vi.fn(() => {
|
||||
throw new Error('Pure helper tests must not access the app store')
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: Object.assign(rejectAppStoreAccess, {
|
||||
getState: rejectAppStoreAccess,
|
||||
getInitialState: rejectAppStoreAccess,
|
||||
setState: rejectAppStoreAccess,
|
||||
subscribe: rejectAppStoreAccess
|
||||
})
|
||||
}))
|
||||
|
||||
afterEach(() => {
|
||||
expect(rejectAppStoreAccess).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -1,19 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { resolvePaneDisplayTitle, resolvePaneTitleDecision } from './terminal-title-evidence'
|
||||
|
||||
describe('resolvePaneDisplayTitle', () => {
|
||||
it('normalizes a Pi-compatible title to the resolved OMP owner', () => {
|
||||
expect(resolvePaneDisplayTitle('Pi ready', 'omp')).toBe('OMP ready')
|
||||
})
|
||||
|
||||
it('passes an unowned title through unchanged', () => {
|
||||
expect(resolvePaneDisplayTitle('bash', undefined)).toBe('bash')
|
||||
})
|
||||
|
||||
it('rewrites an OMP wrapper title through explicit launch Pi ownership', () => {
|
||||
expect(resolvePaneDisplayTitle('\u280b OMP', 'pi', true)).toBe('\u280b Pi')
|
||||
})
|
||||
})
|
||||
import { resolvePaneTitleDecision } from './terminal-title-evidence'
|
||||
|
||||
describe('resolvePaneTitleDecision', () => {
|
||||
it('derives the display label and the renderer veto from a pane-scoped OMP owner', () => {
|
||||
@@ -43,17 +29,4 @@ describe('resolvePaneTitleDecision', () => {
|
||||
expect(decision.rendererPolicy.gpuEnabled).toBe(false)
|
||||
expect(decision.rendererPolicy.reason).toBe('agent-compatibility')
|
||||
})
|
||||
|
||||
it('DOM-gates a genuine Gemini pane while preserving its raw title', () => {
|
||||
const decision = resolvePaneTitleDecision({
|
||||
normalizedTitle: '✦ Gemini CLI',
|
||||
rawTitle: '✦ Gemini CLI',
|
||||
displayOwnerAgentType: 'gemini',
|
||||
rendererOwnerAgentType: 'gemini',
|
||||
userGpuMode: 'auto'
|
||||
})
|
||||
expect(decision.rawTitle).toBe('✦ Gemini CLI')
|
||||
expect(decision.rendererPolicy.gpuEnabled).toBe(false)
|
||||
expect(decision.rendererPolicy.reason).toBe('agent-compatibility')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -26,26 +26,6 @@ describe('terminal close copy kind', () => {
|
||||
setState()
|
||||
})
|
||||
|
||||
it('agrees between the pane lookup and the busy-pty lookup for the same pane', () => {
|
||||
expect(resolveLeafCloseCopyKind('tab-1', LEAF_B)).toBe('agent')
|
||||
expect(resolveBusyPtyCloseCopyKind('tab-1', ['pty-b'])).toBe('agent')
|
||||
|
||||
expect(resolveLeafCloseCopyKind('tab-1', LEAF_A)).toBe('command')
|
||||
expect(resolveBusyPtyCloseCopyKind('tab-1', ['pty-a'])).toBe('command')
|
||||
})
|
||||
|
||||
it('prefers the agent copy when a split has both busy', () => {
|
||||
expect(resolveBusyPtyCloseCopyKind('tab-1', ['pty-a', 'pty-b'])).toBe('agent')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['a missing leaf id', undefined],
|
||||
['a null leaf id', null],
|
||||
['a legacy non-uuid leaf id', 'leaf']
|
||||
])('falls back to the command copy for %s', (_label, leafId) => {
|
||||
expect(resolveLeafCloseCopyKind('tab-1', leafId)).toBe('command')
|
||||
})
|
||||
|
||||
it('never throws on a tab id that makePaneKey would reject', () => {
|
||||
setState({
|
||||
terminalLayoutsByTabId: { 'tab:1': { ptyIdsByLeafId: { [LEAF_B]: 'pty-b' } } }
|
||||
|
||||
@@ -50,21 +50,6 @@ function renderFilterBar(
|
||||
}
|
||||
|
||||
describe('WorkspaceCleanupFilterBar', () => {
|
||||
it('keeps size measurement out of the browse controls', () => {
|
||||
renderFilterBar()
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'Scan' })).toBeNull()
|
||||
})
|
||||
|
||||
it('names an applied filter in the bar without opening the panel', () => {
|
||||
// Persisted idle thresholds narrowed rows without the user setting them; counts alone hid the cause.
|
||||
const filters = createDefaultWorkspaceCleanupFilterState()
|
||||
filters.activity.idleMinDays = 20
|
||||
renderFilterBar(false, { filters, hasActiveFilters: true })
|
||||
|
||||
expect(screen.getByRole('listitem').textContent).toContain('Idle 20d+')
|
||||
})
|
||||
|
||||
it('clears one named filter from its chip', () => {
|
||||
const filters = createDefaultWorkspaceCleanupFilterState()
|
||||
filters.activity.idleMinDays = 20
|
||||
@@ -76,28 +61,4 @@ describe('WorkspaceCleanupFilterBar', () => {
|
||||
expect(onClearAppliedFilter).toHaveBeenCalledTimes(1)
|
||||
expect(onClearAppliedFilter.mock.calls[0][0].id).toBe('activity.idleMinDays')
|
||||
})
|
||||
|
||||
it('offers Clear filters in the bar, not only inside the panel', () => {
|
||||
renderFilterBar(false, { hasActiveFilters: true })
|
||||
|
||||
expect(screen.getAllByRole('button', { name: /Clear filters/ }).length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('shows no chips and no Clear for a default profile', () => {
|
||||
renderFilterBar()
|
||||
|
||||
expect(screen.queryAllByRole('listitem')).toHaveLength(0)
|
||||
expect(screen.queryByRole('button', { name: /Clear filters/ })).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps the footer visible while the facet panel scrolls', () => {
|
||||
renderFilterBar(true)
|
||||
|
||||
const content = document.querySelector('[data-slot="popover-content"]')
|
||||
const root = document.querySelector('[data-slot="scroll-area"]')
|
||||
expect(content?.className).toContain(
|
||||
'h-[min(471px,var(--radix-popover-content-available-height))]'
|
||||
)
|
||||
expect(root?.className).toContain('min-h-0 flex-1')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** Reject dead exemption paths and security-copy keys that drift into the plugin-chrome allowlist. */
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { translatablePluginChromePaths } from '../../../shared/plugins/plugin-translatable-chrome'
|
||||
import en from './locales/en.json'
|
||||
|
||||
// Match the security components pinned by plugin-language-pack-artifact.test.ts.
|
||||
const SECURITY_COPY_COMPONENTS = [
|
||||
@@ -15,22 +14,7 @@ const SECURITY_COPY_COMPONENTS = [
|
||||
'PluginVmRecipeConsentPreview'
|
||||
]
|
||||
|
||||
function lookup(key: string): string | undefined {
|
||||
const value = key
|
||||
.split('.')
|
||||
.reduce<unknown>(
|
||||
(node, part) =>
|
||||
node && typeof node === 'object' ? (node as Record<string, unknown>)[part] : undefined,
|
||||
en as unknown
|
||||
)
|
||||
return typeof value === 'string' ? value : undefined
|
||||
}
|
||||
|
||||
describe('translatable plugin chrome', () => {
|
||||
it.each(translatablePluginChromePaths())('%s still exists in the English catalog', (path) => {
|
||||
expect(lookup(path)).toBeTypeOf('string')
|
||||
})
|
||||
|
||||
it('exempts nothing from a component the security tests pin', () => {
|
||||
const leaked = translatablePluginChromePaths().filter((path) =>
|
||||
SECURITY_COPY_COMPONENTS.some((component) =>
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
//
|
||||
// The Explorer row decides whether to show the preview action with the hook, and activating it
|
||||
// runs the plan. The hook used to re-derive that answer from its own copy of the rules; it now
|
||||
// delegates, and these pin the two to one answer so a future rule change cannot split them.
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
browserAvailability: { state: 'enabled', provider: 'local-client' } as
|
||||
| { state: 'enabled'; provider: 'local-client' | 'paired-runtime' }
|
||||
| { state: 'hidden'; reason: string },
|
||||
environmentId: null as string | null
|
||||
}))
|
||||
|
||||
vi.mock('sonner', () => ({ toast: { error: vi.fn() } }))
|
||||
vi.mock('@/lib/client-creation-action-policy', () => ({
|
||||
getWorkspaceClientCreationActionPolicy: () => ({ 'managed-browser': mocks.browserAvailability })
|
||||
}))
|
||||
vi.mock('@/lib/worktree-runtime-owner', () => ({
|
||||
runtimeEnvironmentIdOrLocalUnlessTwin: () => mocks.environmentId
|
||||
}))
|
||||
|
||||
const storeState = {
|
||||
getKnownWorktreeById: () => ({ id: 'wt-1', path: '/repo' }),
|
||||
repos: [{ id: 'repo-1', connectionId: null }],
|
||||
worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] }
|
||||
}
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: Object.assign((selector: (state: unknown) => unknown) => selector(storeState), {
|
||||
getState: () => storeState
|
||||
})
|
||||
}))
|
||||
|
||||
import {
|
||||
canShowWorkspaceFileBrowserAction,
|
||||
useWorkspaceFileBrowserActionPredicate
|
||||
} from './file-preview'
|
||||
|
||||
const FILE_PATH = '/repo/report.html'
|
||||
|
||||
function predicateAnswer(): boolean {
|
||||
return renderHook(() => useWorkspaceFileBrowserActionPredicate('wt-1')).result.current(FILE_PATH)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.browserAvailability = { state: 'enabled', provider: 'local-client' }
|
||||
mocks.environmentId = null
|
||||
})
|
||||
|
||||
describe('workspace file browser action visibility', () => {
|
||||
it('agrees with the plan for a local workspace that can open a browser', () => {
|
||||
expect(predicateAnswer()).toBe(
|
||||
canShowWorkspaceFileBrowserAction(storeState as never, 'wt-1', FILE_PATH)
|
||||
)
|
||||
expect(predicateAnswer()).toBe(true)
|
||||
})
|
||||
|
||||
it('hides the action when the local workspace has no managed browser', () => {
|
||||
mocks.browserAvailability = { state: 'hidden', reason: 'browser unavailable' }
|
||||
|
||||
expect(canShowWorkspaceFileBrowserAction(storeState as never, 'wt-1', FILE_PATH)).toBe(false)
|
||||
expect(predicateAnswer()).toBe(false)
|
||||
})
|
||||
|
||||
it('agrees with the plan on a paired workspace with no managed browser', () => {
|
||||
mocks.environmentId = 'env-1'
|
||||
mocks.browserAvailability = { state: 'hidden', reason: 'browser unavailable' }
|
||||
|
||||
expect(predicateAnswer()).toBe(
|
||||
canShowWorkspaceFileBrowserAction(storeState as never, 'wt-1', FILE_PATH)
|
||||
)
|
||||
expect(predicateAnswer()).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps a stable predicate identity across re-renders of the same workspace', () => {
|
||||
const { result, rerender } = renderHook(() => useWorkspaceFileBrowserActionPredicate('wt-1'))
|
||||
const first = result.current
|
||||
|
||||
rerender()
|
||||
|
||||
expect(result.current).toBe(first)
|
||||
})
|
||||
})
|
||||
@@ -1,98 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type * as Monaco from 'monaco-editor'
|
||||
import { installMonacoPeekReferencesPreviewOptions } from './monaco-peek-preview-options'
|
||||
|
||||
type FakePreviewEditor = Pick<Monaco.editor.ICodeEditor, 'updateOptions'>
|
||||
|
||||
type FakeReferenceWidgetInstance = {
|
||||
_preview?: FakePreviewEditor
|
||||
_fillBody?: (containerElement: HTMLElement) => void
|
||||
_revealReference?: (...args: unknown[]) => Promise<unknown>
|
||||
}
|
||||
|
||||
function createReferenceWidgetConstructor(hooks: {
|
||||
fillBody?: (instance: FakeReferenceWidgetInstance) => void
|
||||
revealReference?: (instance: FakeReferenceWidgetInstance) => Promise<unknown>
|
||||
}): {
|
||||
prototype: FakeReferenceWidgetInstance &
|
||||
Required<Pick<FakeReferenceWidgetInstance, '_fillBody' | '_revealReference'>>
|
||||
} {
|
||||
return {
|
||||
prototype: {
|
||||
_fillBody(this: FakeReferenceWidgetInstance): void {
|
||||
hooks.fillBody?.(this)
|
||||
},
|
||||
async _revealReference(this: FakeReferenceWidgetInstance): Promise<unknown> {
|
||||
return hooks.revealReference?.(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createPreviewEditor(): FakePreviewEditor {
|
||||
return { updateOptions: vi.fn() }
|
||||
}
|
||||
|
||||
const peekPreviewOptions = {
|
||||
smoothScrolling: false,
|
||||
stickyScroll: { enabled: false },
|
||||
wordWrap: 'off'
|
||||
}
|
||||
|
||||
describe('installMonacoPeekReferencesPreviewOptions', () => {
|
||||
it('updates the embedded preview after ReferenceWidget creates it', () => {
|
||||
const preview = createPreviewEditor()
|
||||
const referenceWidget = createReferenceWidgetConstructor({
|
||||
fillBody(instance) {
|
||||
instance._preview = preview
|
||||
}
|
||||
})
|
||||
|
||||
installMonacoPeekReferencesPreviewOptions(referenceWidget)
|
||||
referenceWidget.prototype._fillBody(document.createElement('div'))
|
||||
|
||||
expect(preview.updateOptions).toHaveBeenCalledWith(peekPreviewOptions)
|
||||
})
|
||||
|
||||
it('updates the embedded preview before revealing a reference', async () => {
|
||||
const calls: string[] = []
|
||||
const preview: FakePreviewEditor = {
|
||||
updateOptions: vi.fn(() => calls.push('updateOptions'))
|
||||
}
|
||||
const referenceWidget = createReferenceWidgetConstructor({
|
||||
revealReference: async () => {
|
||||
calls.push('revealReference')
|
||||
}
|
||||
})
|
||||
referenceWidget.prototype._preview = preview
|
||||
|
||||
installMonacoPeekReferencesPreviewOptions(referenceWidget)
|
||||
await referenceWidget.prototype._revealReference('reference')
|
||||
|
||||
expect(calls).toEqual(['updateOptions', 'revealReference', 'updateOptions'])
|
||||
expect(preview.updateOptions).toHaveBeenCalledWith(peekPreviewOptions)
|
||||
})
|
||||
|
||||
it('skips patching when the private Monaco hooks are missing', () => {
|
||||
const referenceWidget: { prototype: FakeReferenceWidgetInstance } = { prototype: {} }
|
||||
|
||||
installMonacoPeekReferencesPreviewOptions(referenceWidget)
|
||||
|
||||
expect(referenceWidget.prototype._fillBody).toBeUndefined()
|
||||
expect(referenceWidget.prototype._revealReference).toBeUndefined()
|
||||
})
|
||||
|
||||
it('does not wrap ReferenceWidget more than once', async () => {
|
||||
const preview = createPreviewEditor()
|
||||
const referenceWidget = createReferenceWidgetConstructor({})
|
||||
referenceWidget.prototype._preview = preview
|
||||
|
||||
installMonacoPeekReferencesPreviewOptions(referenceWidget)
|
||||
installMonacoPeekReferencesPreviewOptions(referenceWidget)
|
||||
await referenceWidget.prototype._revealReference('reference')
|
||||
|
||||
expect(preview.updateOptions).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { SshManagedServerStatus } from '../../../shared/ssh-types'
|
||||
import { nodeMirrorRecovery } from './ssh-host-node-download-copy'
|
||||
import { sshConnectFailureText, sshHostServerStatusLine } from './ssh-host-server-status-copy'
|
||||
|
||||
const DETAIL =
|
||||
'Neither this computer nor the host could download Node from https://nodejs.org/dist/x.'
|
||||
@@ -42,32 +41,4 @@ describe('Node.js mirror recovery for a setup that could not download Node', ()
|
||||
it('never reads the failure from its text', () => {
|
||||
expect(nodeMirrorRecovery({ kind: 'relay', reason: 'failed', detail: DETAIL }, '')).toBeNull()
|
||||
})
|
||||
|
||||
it('names the blocked download on the host row and in the connect toast', () => {
|
||||
const line = sshHostServerStatusLine({ label: 'Box' }, { managedServer: blocked('unset') })
|
||||
expect(line).toEqual({
|
||||
tone: 'destructive',
|
||||
text: expect.stringContaining('Use a Node.js mirror'),
|
||||
detail: DETAIL
|
||||
})
|
||||
expect(sshConnectFailureText({ managedServer: blocked('tried') }, 'fallback')).toContain(
|
||||
'Check the mirror setting'
|
||||
)
|
||||
expect(
|
||||
sshHostServerStatusLine({ label: 'Box' }, { managedServer: blocked('unsupported') })?.text
|
||||
).not.toContain('mirror')
|
||||
})
|
||||
|
||||
it('keeps the held-move and lost-contact lines ahead of the download', () => {
|
||||
expect(
|
||||
sshHostServerStatusLine({ label: 'Box' }, { managedServer: blocked('unset', { held: true }) })
|
||||
?.text
|
||||
).toContain('paused moving')
|
||||
expect(
|
||||
sshHostServerStatusLine(
|
||||
{ label: 'Box' },
|
||||
{ managedServer: blocked('unset', { contactLost: true }) }
|
||||
)?.text
|
||||
).toContain('lost contact')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
canResumeAiVaultSessionOnTarget,
|
||||
getAiVaultResumeRepoTargetStatus,
|
||||
isWslStoredAiVaultSessionFile
|
||||
} from './ai-vault-resume-target'
|
||||
import { canResumeAiVaultSessionOnTarget } from './ai-vault-resume-target'
|
||||
|
||||
describe('ai vault session storage compatibility', () => {
|
||||
const hostSessionFile = '/Users/ada/.claude/projects/-Users-ada-repo/session-1.jsonl'
|
||||
@@ -12,15 +8,6 @@ describe('ai vault session storage compatibility', () => {
|
||||
const wslLocalhostSessionFile =
|
||||
'\\\\wsl.localhost\\Ubuntu\\home\\ada\\.claude\\projects\\-home-ada-repo\\s-1.jsonl'
|
||||
|
||||
it('detects WSL-stored session files', () => {
|
||||
expect(isWslStoredAiVaultSessionFile(wslSessionFile)).toBe(true)
|
||||
expect(isWslStoredAiVaultSessionFile(wslLocalhostSessionFile)).toBe(true)
|
||||
expect(isWslStoredAiVaultSessionFile(hostSessionFile)).toBe(false)
|
||||
expect(isWslStoredAiVaultSessionFile(windowsHostSessionFile)).toBe(false)
|
||||
expect(isWslStoredAiVaultSessionFile(null)).toBe(false)
|
||||
expect(isWslStoredAiVaultSessionFile(undefined)).toBe(false)
|
||||
})
|
||||
|
||||
it('allows host-stored sessions on local targets only', () => {
|
||||
expect(
|
||||
canResumeAiVaultSessionOnTarget({ sessionFilePath: hostSessionFile, targetStatus: 'local' })
|
||||
@@ -143,21 +130,3 @@ describe('ai vault session storage compatibility', () => {
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ai vault resume repo owner', () => {
|
||||
it('classifies local, SSH, runtime, and unknown repo owners', () => {
|
||||
expect(getAiVaultResumeRepoTargetStatus({ connectionId: null, executionHostId: 'local' })).toBe(
|
||||
'local'
|
||||
)
|
||||
expect(getAiVaultResumeRepoTargetStatus({ connectionId: 'ssh-1', executionHostId: null })).toBe(
|
||||
'ssh'
|
||||
)
|
||||
expect(
|
||||
getAiVaultResumeRepoTargetStatus({
|
||||
connectionId: null,
|
||||
executionHostId: 'runtime:env-1'
|
||||
})
|
||||
).toBe('runtime')
|
||||
expect(getAiVaultResumeRepoTargetStatus(null)).toBe('unknown')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,34 +2,6 @@ import { describe, expect, it } from 'vitest'
|
||||
import { planCommitMessageGeneration } from './commit-message-plan'
|
||||
|
||||
describe('Antigravity generation planning', () => {
|
||||
it('plans Antigravity generation with one structured prompt on stdin', () => {
|
||||
const result = planCommitMessageGeneration(
|
||||
{ agentId: 'antigravity', model: 'Gemini 3.5 Flash (Medium)' },
|
||||
'real commit prompt'
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
plan: {
|
||||
binary: 'agy',
|
||||
args: [
|
||||
'--print=',
|
||||
'--input-format=stream-json',
|
||||
'--output-format=stream-json',
|
||||
'--sandbox',
|
||||
'--model',
|
||||
'Gemini 3.5 Flash (Medium)'
|
||||
],
|
||||
stdinPayload: `${JSON.stringify({
|
||||
event: 'user',
|
||||
message: { role: 'user', content: [{ type: 'text', text: 'real commit prompt' }] }
|
||||
})}\n`,
|
||||
label: 'Antigravity',
|
||||
outputFormat: 'antigravity-stream-json'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it.each(['-fix: something', '--sandbox', 'line one\n"quoted"\r\n日本語 🐳'])(
|
||||
'keeps Antigravity prompt text out of argv: %s',
|
||||
(prompt) => {
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { GitStatusEntry } from './git-status-types'
|
||||
import { compareGitStatusEntries } from './git-status-sort'
|
||||
|
||||
function entry(path: string, conflictStatus?: GitStatusEntry['conflictStatus']): GitStatusEntry {
|
||||
return {
|
||||
path,
|
||||
area: 'unstaged',
|
||||
status: 'modified',
|
||||
...(conflictStatus ? { conflictStatus } : {})
|
||||
} as GitStatusEntry
|
||||
}
|
||||
|
||||
// Reference: the pre-change comparator, which resolved a collator per call.
|
||||
function referenceCompare(a: GitStatusEntry, b: GitStatusEntry): number {
|
||||
const rank = (value: GitStatusEntry): number => {
|
||||
if (value.conflictStatus === 'unresolved') {
|
||||
return 0
|
||||
}
|
||||
if (value.conflictStatus === 'resolved_locally') {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
}
|
||||
// Pin en to match the shared comparator; runner locales would make the oracle vary.
|
||||
return rank(a) - rank(b) || a.path.localeCompare(b.path, 'en', { numeric: true })
|
||||
}
|
||||
|
||||
describe('compareGitStatusEntries', () => {
|
||||
it('orders identically to a per-call localeCompare', () => {
|
||||
const paths = [
|
||||
'src/a.ts',
|
||||
'src/A.ts',
|
||||
'src/file2.ts',
|
||||
'src/file10.ts',
|
||||
'src/file1.ts',
|
||||
'src/File3.ts',
|
||||
'src/nested/deep/z.ts',
|
||||
'src/nested/a.ts',
|
||||
'README.md',
|
||||
'package.json',
|
||||
'src/日本語.ts',
|
||||
'src/émoji.ts',
|
||||
'src/file-2.ts',
|
||||
'src/file_2.ts',
|
||||
'src/10.ts',
|
||||
'src/9.ts'
|
||||
]
|
||||
const entries = paths.map((path) => entry(path))
|
||||
const sorted = [...entries].sort(compareGitStatusEntries).map((value) => value.path)
|
||||
const reference = [...entries].sort(referenceCompare).map((value) => value.path)
|
||||
expect(sorted).toEqual(reference)
|
||||
})
|
||||
|
||||
it('keeps conflicts ahead of clean paths regardless of name', () => {
|
||||
const entries = [
|
||||
entry('z-clean.ts'),
|
||||
entry('a-resolved.ts', 'resolved_locally'),
|
||||
entry('m-unresolved.ts', 'unresolved')
|
||||
]
|
||||
expect([...entries].sort(compareGitStatusEntries).map((value) => value.path)).toEqual([
|
||||
'm-unresolved.ts',
|
||||
'a-resolved.ts',
|
||||
'z-clean.ts'
|
||||
])
|
||||
})
|
||||
|
||||
it('sorts numeric path segments naturally', () => {
|
||||
const entries = ['f10.ts', 'f9.ts', 'f1.ts'].map((path) => entry(path))
|
||||
expect([...entries].sort(compareGitStatusEntries).map((value) => value.path)).toEqual([
|
||||
'f1.ts',
|
||||
'f9.ts',
|
||||
'f10.ts'
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -6,43 +6,6 @@ function makeReader(files: Record<string, string>) {
|
||||
}
|
||||
|
||||
describe('package manager setup script suggestions', () => {
|
||||
it('suggests setup commands from package manager lockfiles', async () => {
|
||||
const candidates = await inspectSetupScriptImportCandidates(
|
||||
makeReader({
|
||||
'package.json': JSON.stringify({ scripts: { dev: 'vite' } }),
|
||||
'pnpm-lock.yaml': 'lockfileVersion: 9.0'
|
||||
})
|
||||
)
|
||||
|
||||
expect(candidates).toEqual([
|
||||
{
|
||||
provider: 'package-manager',
|
||||
label: 'package manager',
|
||||
files: ['pnpm-lock.yaml'],
|
||||
setup: 'pnpm install',
|
||||
unsupportedFields: []
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('uses packageManager when no lockfile is present', async () => {
|
||||
const candidates = await inspectSetupScriptImportCandidates(
|
||||
makeReader({
|
||||
'package.json': JSON.stringify({ packageManager: 'bun@1.2.0' })
|
||||
})
|
||||
)
|
||||
|
||||
expect(candidates).toEqual([
|
||||
{
|
||||
provider: 'package-manager',
|
||||
label: 'package manager',
|
||||
files: ['package.json'],
|
||||
setup: 'bun install',
|
||||
unsupportedFields: []
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('uses explicit packageManager over conflicting lockfiles', async () => {
|
||||
const candidates = await inspectSetupScriptImportCandidates(
|
||||
makeReader({
|
||||
@@ -137,24 +100,4 @@ describe('package manager setup script suggestions', () => {
|
||||
|
||||
expect(candidates).toEqual([])
|
||||
})
|
||||
|
||||
it('allows multiple lockfiles for the same package manager family', async () => {
|
||||
const candidates = await inspectSetupScriptImportCandidates(
|
||||
makeReader({
|
||||
'package.json': JSON.stringify({ scripts: { dev: 'vite' } }),
|
||||
'package-lock.json': '{}',
|
||||
'npm-shrinkwrap.json': '{}'
|
||||
})
|
||||
)
|
||||
|
||||
expect(candidates).toEqual([
|
||||
{
|
||||
provider: 'package-manager',
|
||||
label: 'package manager',
|
||||
files: ['package-lock.json'],
|
||||
setup: 'npm install',
|
||||
unsupportedFields: []
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
isUnreadNews,
|
||||
enterAgentPaneState,
|
||||
RECENT_STAMP_THROTTLE_MS,
|
||||
REST_PANE,
|
||||
workspaceSmartStamp,
|
||||
type AgentPaneState,
|
||||
type SortStamps
|
||||
@@ -24,14 +23,6 @@ function agentEvent(
|
||||
}
|
||||
|
||||
describe('enterAgentPaneState', () => {
|
||||
it('enters a phase at now', () => {
|
||||
expect(enterAgentPaneState(REST_PANE, 'working', NOW)).toEqual({ phase: 'working', at: NOW })
|
||||
expect(enterAgentPaneState({ phase: 'working', at: 1 }, 'done', NOW)).toEqual({
|
||||
phase: 'done',
|
||||
at: NOW
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps the same object when the phase holds, so running stays running', () => {
|
||||
const working = { phase: 'working' as const, at: NOW - 5 }
|
||||
expect(enterAgentPaneState(working, 'working', NOW)).toBe(working)
|
||||
@@ -65,44 +56,6 @@ describe('workspaceSmartStamp', () => {
|
||||
})
|
||||
|
||||
describe('classifySortStampEvent', () => {
|
||||
it('stamps a prompt from rest: Recent now, Working now', () => {
|
||||
const working = { phase: 'working' as const, at: NOW }
|
||||
expect(classifySortStampEvent(IDLE, agentEvent('rest', 'working', [working]), NOW)).toEqual({
|
||||
recentAt: NOW,
|
||||
smartTier: 'working',
|
||||
smartAt: NOW
|
||||
})
|
||||
})
|
||||
|
||||
it('stamps a finish and a request for input', () => {
|
||||
const current: SortStamps = { recentAt: NOW - 60_000, smartTier: 'working', smartAt: 1 }
|
||||
expect(
|
||||
classifySortStampEvent(
|
||||
current,
|
||||
agentEvent('working', 'done', [{ phase: 'done', at: NOW }]),
|
||||
NOW
|
||||
)
|
||||
).toEqual({ recentAt: NOW, smartTier: 'done', smartAt: NOW })
|
||||
expect(
|
||||
classifySortStampEvent(
|
||||
current,
|
||||
agentEvent('working', 'needs-you', [{ phase: 'needs-you', at: NOW }]),
|
||||
NOW
|
||||
)
|
||||
).toEqual({ recentAt: NOW, smartTier: 'needs-you', smartAt: NOW })
|
||||
})
|
||||
|
||||
it('stamps a new prompt after Done', () => {
|
||||
const current: SortStamps = { recentAt: NOW - 60_000, smartTier: 'done', smartAt: 1 }
|
||||
expect(
|
||||
classifySortStampEvent(
|
||||
current,
|
||||
agentEvent('done', 'working', [{ phase: 'working', at: NOW }]),
|
||||
NOW
|
||||
)
|
||||
).toEqual({ recentAt: NOW, smartTier: 'working', smartAt: NOW })
|
||||
})
|
||||
|
||||
it('moves a resumed approval to Working without a Recent stamp, keeping its time', () => {
|
||||
const current: SortStamps = { recentAt: NOW - 60_000, smartTier: 'needs-you', smartAt: 7 }
|
||||
expect(
|
||||
@@ -174,19 +127,6 @@ describe('classifySortStampEvent', () => {
|
||||
})
|
||||
|
||||
describe('the stamps follow the agent transitions', () => {
|
||||
const phases: readonly AgentPhase[] = ['rest', 'working', 'needs-you', 'done']
|
||||
it.each(phases.flatMap((from) => phases.map((to) => ({ from, to }))))(
|
||||
'$from -> $to',
|
||||
({ from, to }) => {
|
||||
const transition = agentPhaseTransition(from, to)
|
||||
const event = agentEvent(from, to, [])
|
||||
expect(classifySortStampEvent(IDLE, event, NOW)?.recentAt === NOW).toBe(transition !== null)
|
||||
expect(isUnreadNews(event)).toBe(
|
||||
transition === 'turn-finished' || transition === 'needs-input'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it('flags a failed turn as news, and a session boundary’s done as none while it still stamps', () => {
|
||||
expect(isUnreadNews(agentEvent('working', 'done', [], 'errored'))).toBe(true)
|
||||
const boundary = agentEvent('working', 'done', [{ phase: 'done', at: NOW }], null)
|
||||
|
||||
@@ -114,33 +114,6 @@ describe('buildWorkspaceRows', () => {
|
||||
expect(rows.get('|repo-1::/r/a')?.hostId).toBe('local')
|
||||
})
|
||||
|
||||
it('carries the stored lineage edge for the builder to validate', () => {
|
||||
const rows = buildWorkspaceRows({
|
||||
worktrees: [worktree({ hostId: 'local', instanceId: 'a-1' })],
|
||||
folderWorkspaces: [],
|
||||
repos: [localRepo],
|
||||
lineageById: {
|
||||
'repo-1::/r/a': {
|
||||
worktreeId: 'repo-1::/r/a',
|
||||
worktreeInstanceId: 'a-1',
|
||||
parentWorktreeId: 'repo-1::/r',
|
||||
parentWorktreeInstanceId: 'p-1',
|
||||
origin: 'cli',
|
||||
capture: { source: 'explicit-cli-flag', confidence: 'explicit' },
|
||||
createdAt: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(rows.get('local|repo-1::/r/a')).toMatchObject({
|
||||
instanceId: 'a-1',
|
||||
lineage: {
|
||||
parentWorktreeId: 'repo-1::/r',
|
||||
worktreeInstanceId: 'a-1',
|
||||
parentWorktreeInstanceId: 'p-1'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('carries the owning host’s sort stamps, on worktrees and folder workspaces', () => {
|
||||
const rows = buildWorkspaceRows({
|
||||
worktrees: [
|
||||
@@ -174,17 +147,6 @@ describe('buildWorkspaceRows', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('parses what it builds', () => {
|
||||
const rows = buildWorkspaceRows({
|
||||
worktrees: [worktree({ hostId: 'local' })],
|
||||
folderWorkspaces: [folder],
|
||||
repos: [localRepo],
|
||||
lineageById: {}
|
||||
})
|
||||
const snapshot = { revision: 1, rows: Object.fromEntries(rows) }
|
||||
expect(WorkspaceRowsSnapshotSchema.parse(snapshot)).toEqual(snapshot)
|
||||
})
|
||||
|
||||
it('lists the keys it builds rows under, which sleeping retention is matched on', () => {
|
||||
const otherRepo: Repo = { ...localRepo, id: 'repo-2', path: '/o' }
|
||||
const serverRepo: Repo = { ...localRepo, id: 'repo-3', executionHostId: 'runtime:env' }
|
||||
|
||||
@@ -33,10 +33,6 @@ function packageFile(path: string, size = 0): SkillPackageFile {
|
||||
}
|
||||
|
||||
describe('skill package manifest', () => {
|
||||
it('parses a canonical manifest', () => {
|
||||
expect(parseSkillPackageManifest(manifest()).name).toBe('test-skill')
|
||||
})
|
||||
|
||||
it.each(['con', 'prn', 'aux', 'nul', 'com1', 'com9', 'lpt1', 'lpt9'])(
|
||||
'rejects the Windows device skill name %s',
|
||||
(name) => {
|
||||
|
||||
@@ -36,22 +36,6 @@ describe('resolveCreateReviewIntentEligibility', () => {
|
||||
).toEqual({ eligible: false, kind: null })
|
||||
})
|
||||
|
||||
it('rejects unavailable eligibility when the default branch is blank', () => {
|
||||
expect(
|
||||
resolveCreateReviewIntentEligibility({
|
||||
stagedCount: 1,
|
||||
hasStageableChanges: true,
|
||||
hasMessage: true,
|
||||
hasUnresolvedConflicts: false,
|
||||
upstreamStatus: { hasUpstream: true, ahead: 0, behind: 0 },
|
||||
hostedReviewCreation: {
|
||||
...unavailableEligibility('dirty'),
|
||||
defaultBaseRef: ' '
|
||||
}
|
||||
})
|
||||
).toEqual({ eligible: false, kind: null })
|
||||
})
|
||||
|
||||
it('stays ineligible when no local blocker remains under unavailable lookup', () => {
|
||||
expect(
|
||||
resolveCreateReviewIntentEligibility({
|
||||
|
||||
@@ -36,17 +36,6 @@ describe('source-control primary action decision', () => {
|
||||
expect(result.disabled).toBe(false)
|
||||
})
|
||||
|
||||
it('returns enabled Commit for staged changes with a message', () => {
|
||||
const result = resolveSourceControlCommitAreaPrimaryActionDecision(
|
||||
inputs({ stagedCount: 2, hasMessage: true })
|
||||
)
|
||||
expect(result).toMatchObject({
|
||||
kind: 'commit',
|
||||
titleIntent: 'commit_staged_changes',
|
||||
disabled: false
|
||||
})
|
||||
})
|
||||
|
||||
it('blocks commits while unresolved conflicts exist', () => {
|
||||
const result = resolveSourceControlCommitAreaPrimaryActionDecision(
|
||||
inputs({ stagedCount: 1, hasMessage: true, hasUnresolvedConflicts: true })
|
||||
@@ -58,38 +47,6 @@ describe('source-control primary action decision', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('returns remote push, pull, and sync decisions for clean tracked branches', () => {
|
||||
expect(
|
||||
resolveSourceControlCommitAreaPrimaryActionDecision(
|
||||
inputs({ upstreamStatus: { hasUpstream: true, ahead: 2, behind: 0 } })
|
||||
)
|
||||
).toMatchObject({ kind: 'push', titleIntent: 'push_count', count: 2 })
|
||||
expect(
|
||||
resolveSourceControlCommitAreaPrimaryActionDecision(
|
||||
inputs({ upstreamStatus: { hasUpstream: true, ahead: 0, behind: 3 } })
|
||||
)
|
||||
).toMatchObject({ kind: 'pull', titleIntent: 'pull_count', count: 3 })
|
||||
expect(
|
||||
resolveSourceControlCommitAreaPrimaryActionDecision(
|
||||
inputs({ upstreamStatus: { hasUpstream: true, ahead: 2, behind: 3 } })
|
||||
)
|
||||
).toMatchObject({ kind: 'sync', titleIntent: 'sync_counts', ahead: 2, behind: 3 })
|
||||
})
|
||||
|
||||
it('returns Publish Branch for clean unpublished branches with a current branch', () => {
|
||||
const result = resolveSourceControlCommitAreaPrimaryActionDecision(
|
||||
inputs({
|
||||
upstreamStatus: { hasUpstream: false, ahead: 0, behind: 0 },
|
||||
hasCurrentBranch: true
|
||||
})
|
||||
)
|
||||
expect(result).toMatchObject({
|
||||
kind: 'publish',
|
||||
titleIntent: 'publish_branch',
|
||||
disabled: false
|
||||
})
|
||||
})
|
||||
|
||||
it('blocks unpublished branch publishing when HEAD is detached', () => {
|
||||
const result = resolveSourceControlCommitAreaPrimaryActionDecision(
|
||||
inputs({
|
||||
|
||||
@@ -22,17 +22,6 @@ describe('push failure detection and summary', () => {
|
||||
expect(summarizePushFailure(raw)).toBe('Pre-push hook failed.')
|
||||
})
|
||||
|
||||
it('detects lint failures during push', () => {
|
||||
const raw = [
|
||||
'git push failed: Command failed: git push origin main',
|
||||
'error: failed to push some refs to origin',
|
||||
'eslint found 3 errors'
|
||||
].join('\n')
|
||||
|
||||
expect(isPushHookFailure(raw)).toBe(true)
|
||||
expect(summarizePushFailure(raw)).toBe('Lint failed during push.')
|
||||
})
|
||||
|
||||
it('does not treat auth failures as push hook failures', () => {
|
||||
const raw =
|
||||
'git push failed: Command failed: git push origin main\nremote: Repository not found.\nfatal: Authentication failed'
|
||||
@@ -61,16 +50,6 @@ describe('push failure detection and summary', () => {
|
||||
expect(summarizePushFailure(raw)).toBe('Lint failed during push.')
|
||||
})
|
||||
|
||||
it('reports whether expanded details add information beyond the summary', () => {
|
||||
expect(
|
||||
hasExpandedPushFailureDetails(
|
||||
'husky - pre-push hook\neslint found 2 errors\nfull output',
|
||||
'Lint failed during push.'
|
||||
)
|
||||
).toBe(true)
|
||||
expect(hasExpandedPushFailureDetails('', 'Push failed.')).toBe(false)
|
||||
})
|
||||
|
||||
it('bounds summary analysis for pathological single-line logs', () => {
|
||||
const split = vi.spyOn(String.prototype, 'split')
|
||||
const raw = 'x'.repeat(PUSH_FAILURE_SUMMARY_SCAN_CODE_UNITS + 10_000)
|
||||
@@ -82,24 +61,6 @@ describe('push failure detection and summary', () => {
|
||||
})
|
||||
|
||||
describe('buildFixPushFailurePrompt', () => {
|
||||
it('builds a provider-neutral AI prompt for fixing a failed push hook', () => {
|
||||
const prompt = buildFixPushFailurePrompt({
|
||||
summary: 'Lint failed during push.',
|
||||
error: 'oxlint found 2 errors\nhusky - pre-push script failed',
|
||||
branchName: 'feature/push-hook',
|
||||
worktreePath: '/repo/worktree',
|
||||
entries: [{ path: 'src/app.ts', status: 'modified', area: 'staged' }]
|
||||
})
|
||||
|
||||
expect(prompt).toContain('Fix the failed git push in this worktree')
|
||||
expect(prompt).toContain('- Branch: "feature/push-hook"')
|
||||
expect(prompt).toContain('- Failure summary: "Lint failed during push."')
|
||||
expect(prompt).toContain('- "src/app.ts" (modified, staged)')
|
||||
expect(prompt).toContain('Do not bypass hooks with --no-verify')
|
||||
expect(prompt).toContain('Do not push, create a pull request')
|
||||
expect(prompt).toContain('oxlint found 2 errors')
|
||||
})
|
||||
|
||||
it('caps changed files in push prompts and reports the omitted count', () => {
|
||||
const entries = Array.from({ length: PUSH_FAILURE_PROMPT_FILE_LIMIT + 3 }, (_, index) => ({
|
||||
path: `src/file-${index}.ts`,
|
||||
|
||||
@@ -11,19 +11,6 @@ function task(over: Partial<AgentSessionBackgroundTask> = {}): AgentSessionBackg
|
||||
}
|
||||
|
||||
describe('structuredAgentSessionAgentStatus', () => {
|
||||
it('maps a lead that is still working or needs attention without consulting children', () => {
|
||||
expect(structuredAgentSessionAgentStatus({ status: 'working' })).toEqual({
|
||||
state: 'working',
|
||||
mainAgent: { state: 'working' }
|
||||
})
|
||||
expect(
|
||||
structuredAgentSessionAgentStatus({
|
||||
status: 'attention',
|
||||
childWork: [task({ kind: 'command' })]
|
||||
})
|
||||
).toEqual({ state: 'blocked', mainAgent: { state: 'blocked' } })
|
||||
})
|
||||
|
||||
it('keeps an idle main agent working while a subagent runs, and says the main agent itself is done', () => {
|
||||
expect(structuredAgentSessionAgentStatus({ status: 'idle', childWork: [task()] })).toEqual({
|
||||
state: 'working',
|
||||
|
||||
@@ -53,21 +53,6 @@ function turn(sequence: number, contextUsage: AgentSessionContextUsage): AgentJo
|
||||
}
|
||||
|
||||
describe('selectStructuredAgentContextUsage', () => {
|
||||
it('reads the report on the newest turn that carries a used count', () => {
|
||||
expect(
|
||||
selectStructuredAgentContextUsage([
|
||||
turn(1, { used: estimate(20_000), window: WINDOW }),
|
||||
turn(3, { used: { kind: 'report', ...REPORT } })
|
||||
])
|
||||
).toEqual({
|
||||
usedTokens: 29_400,
|
||||
windowTokens: 200_000,
|
||||
percentage: 15,
|
||||
estimated: false,
|
||||
categories: REPORT.categories
|
||||
})
|
||||
})
|
||||
|
||||
it('orders by journal position, not by the clock on the fact', () => {
|
||||
const late = { ...REPORT, capturedAt: 99_000 }
|
||||
expect(
|
||||
@@ -78,21 +63,6 @@ describe('selectStructuredAgentContextUsage', () => {
|
||||
).toMatchObject({ usedTokens: 54_617, windowTokens: 1_000_000, estimated: true })
|
||||
})
|
||||
|
||||
it('estimates against the newest window, which may sit on an older turn', () => {
|
||||
expect(
|
||||
selectStructuredAgentContextUsage([
|
||||
turn(1, { used: estimate(10_000), window: WINDOW }),
|
||||
turn(2, { used: estimate(18_600) })
|
||||
])
|
||||
).toEqual({
|
||||
usedTokens: 18_600,
|
||||
windowTokens: 1_000_000,
|
||||
percentage: 2,
|
||||
estimated: true,
|
||||
categories: []
|
||||
})
|
||||
})
|
||||
|
||||
it('divides by the newest window, not the one written beside the estimate', () => {
|
||||
expect(
|
||||
selectStructuredAgentContextUsage([
|
||||
@@ -138,14 +108,6 @@ describe('selectStructuredAgentContextUsage', () => {
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('is null for a journal with neither', () => {
|
||||
expect(
|
||||
selectStructuredAgentContextUsage([
|
||||
item('u', 1, { kind: 'message', role: 'user', blocks: [{ type: 'text', text: 'hi' }] })
|
||||
])
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('fills a part the loaded rows lack from the host whole-journal answer', () => {
|
||||
const wholeJournal = { used: estimate(90_000), window: WINDOW }
|
||||
expect(selectStructuredAgentContextUsage([], wholeJournal)).toMatchObject({
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { ResumeSource } from './rpc-contract/structured-agent-session-params'
|
||||
import { STRUCTURED_AGENT_SESSION_CLI_SOURCE_RUNTIME_CAPABILITY } from './structured-agent-session-surface-capabilities'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { NATIVE_REMOTE_RUNTIME_CLIENT_CAPABILITIES, RUNTIME_CAPABILITIES } from './protocol-version'
|
||||
import {
|
||||
AGENT_SESSION_CREATE_TAB_ID_RUNTIME_CAPABILITY,
|
||||
AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY
|
||||
} from './agent-session-create-capabilities'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from './electron-remote-runtime-client-capabilities'
|
||||
import { RUNTIME_CAPABILITIES } from './protocol-version'
|
||||
import { AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY } from './agent-session-create-capabilities'
|
||||
|
||||
import {
|
||||
structuredAgentSessionCreateParams,
|
||||
type StructuredAgentSessionFirstMessage,
|
||||
@@ -46,14 +43,6 @@ function createParams(
|
||||
}
|
||||
|
||||
describe('structured agent session create params', () => {
|
||||
it('advertises create-message support only on clients using the new flow', () => {
|
||||
expect(ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES).toContain(
|
||||
AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY
|
||||
)
|
||||
expect(NATIVE_REMOTE_RUNTIME_CLIENT_CAPABILITIES).not.toContain(
|
||||
AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY
|
||||
)
|
||||
})
|
||||
it('fingerprints the first message identity, content and opening option picks', () => {
|
||||
const firstMessage: StructuredAgentSessionFirstMessage = {
|
||||
clientMessageId: 'message-1',
|
||||
@@ -85,23 +74,6 @@ describe('structured agent session create params', () => {
|
||||
expect(RUNTIME_CAPABILITIES).toContain(AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY)
|
||||
expect(AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY).toBe('agentSession.create.message.v1')
|
||||
})
|
||||
it('carries resumeFrom only when the create adopts a conversation', () => {
|
||||
expect(createParams()).not.toHaveProperty('resumeFrom')
|
||||
expect(createParams({ resumeFrom: RESUME })).toMatchObject({ resumeFrom: RESUME })
|
||||
})
|
||||
|
||||
it('declares a fingerprint the host can recompute from the same fields', () => {
|
||||
const params = createParams({ resumeFrom: RESUME })
|
||||
|
||||
expect(params.envelope.payloadFingerprint).toBe(
|
||||
structuredAgentSessionCreateFingerprint({
|
||||
sessionId: SESSION_ID,
|
||||
worktree: 'id:repo-1::/repo/orca',
|
||||
agent: 'codex',
|
||||
resumeFrom: RESUME
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('separates an adopting create from a blank one and from another row', () => {
|
||||
const blank = createParams().envelope.payloadFingerprint
|
||||
@@ -114,14 +86,6 @@ describe('structured agent session create params', () => {
|
||||
expect(otherRow).not.toBe(adopted)
|
||||
})
|
||||
|
||||
it('gives a replay of the same adoption the same digest under a new operation id', () => {
|
||||
const first = createParams({ resumeFrom: RESUME })
|
||||
const second = createParams({ resumeFrom: RESUME })
|
||||
|
||||
expect(second.envelope.clientOperationId).not.toBe(first.envelope.clientOperationId)
|
||||
expect(second.envelope.payloadFingerprint).toBe(first.envelope.payloadFingerprint)
|
||||
})
|
||||
|
||||
it('leaves a blank create byte-identical to the pre-resume digest', () => {
|
||||
// Pin the legacy no-resume digest; new fields must not break mixed-version create fingerprints.
|
||||
expect(createParams().envelope.payloadFingerprint).toBe(
|
||||
@@ -153,11 +117,6 @@ describe('the tab id a create reserves', () => {
|
||||
// The declared digest covers the tab; the host still replays a retry on its attach fingerprint.
|
||||
expect(params.envelope.payloadFingerprint).not.toBe(createParams().envelope.payloadFingerprint)
|
||||
})
|
||||
|
||||
it('is advertised as a capability, because an older host refuses the strict payload', () => {
|
||||
expect(RUNTIME_CAPABILITIES).toContain(AGENT_SESSION_CREATE_TAB_ID_RUNTIME_CAPABILITY)
|
||||
expect(AGENT_SESSION_CREATE_TAB_ID_RUNTIME_CAPABILITY).toBe('agentSession.create.tab-id.v1')
|
||||
})
|
||||
})
|
||||
|
||||
it('negotiates a strict pane identity and includes it in the create fingerprint without accepting paths', () => {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
AGENT_SESSION_FAILURE_KINDS,
|
||||
agentSessionFailureFact,
|
||||
isSubmissionRejectionKind
|
||||
} from './agent-session-failure'
|
||||
import { agentSessionFailureFact } from './agent-session-failure'
|
||||
import { agentSessionFailureWords } from './agent-session-failure-words'
|
||||
import {
|
||||
classifyDispatchRejection,
|
||||
@@ -29,25 +25,6 @@ describe('classifyDispatchRejection', () => {
|
||||
expect(classifyDispatchRejection({ reason })).toEqual({ category, verdict, kind })
|
||||
})
|
||||
|
||||
it('reads any other legacy reason as a sentence that failed the send', () => {
|
||||
expect(classifyDispatchRejection({ reason: 'Claude does not support .bmp' })).toEqual({
|
||||
category: 'content',
|
||||
verdict: 'failure'
|
||||
})
|
||||
expect(classifyDispatchRejection({ reason: null })).toEqual({
|
||||
category: 'content',
|
||||
verdict: 'failure'
|
||||
})
|
||||
})
|
||||
|
||||
it('fails the send for every kind but a withdrawal, a restart, a close, or a lost send', () => {
|
||||
const noOneFailed = ['cancelled', 'hostRestarted', 'chatClosed', 'notDelivered']
|
||||
for (const kind of AGENT_SESSION_FAILURE_KINDS.filter(isSubmissionRejectionKind)) {
|
||||
const verdict = classifyDispatchRejection({ reason: 'x', rejection: { kind } }).verdict
|
||||
expect([kind, verdict]).toEqual([kind, noOneFailed.includes(kind) ? null : 'failure'])
|
||||
}
|
||||
})
|
||||
|
||||
it.each([
|
||||
['cancelled', 'withdrawn', null],
|
||||
['hostRestarted', 'undelivered', null],
|
||||
@@ -67,14 +44,6 @@ describe('classifyDispatchRejection', () => {
|
||||
expect(classifyDispatchRejection(written)).toEqual({ category, verdict, kind })
|
||||
})
|
||||
|
||||
it('reads a legacy not_delivered row with no fact as a send nobody failed', () => {
|
||||
expect(classifyDispatchRejection({ reason: 'not_delivered' })).toEqual({
|
||||
category: 'undelivered',
|
||||
verdict: null,
|
||||
kind: 'notDelivered'
|
||||
})
|
||||
})
|
||||
|
||||
it('reads the typed fact over the sentence beside it', () => {
|
||||
expect(
|
||||
classifyDispatchRejection({
|
||||
|
||||
@@ -85,14 +85,6 @@ describe('structuredAgentSessionOptionPicks', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('reads the committed model rather than the record a deferred commit has not settled', () => {
|
||||
// The caller passes pre-commit state: the record still tracks the old model.
|
||||
const state = liveState({ model: 'account-model', effort: 'medium' })
|
||||
expect(structuredAgentSessionOptionPicks(state, { model: 'other-model' })).toEqual([
|
||||
{ modelId: 'other-model', optionId: 'model', value: 'other-model' }
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps a per-model effort so reselecting the old model restores its level', () => {
|
||||
const persisted = persist([
|
||||
...structuredAgentSessionOptionPicks(liveState({ model: 'account-model' }), {
|
||||
@@ -144,21 +136,6 @@ describe('structuredAgentSessionOptionPicks', () => {
|
||||
})
|
||||
|
||||
describe('applyNativeChatSessionOptionPicks', () => {
|
||||
it('keeps a later pick in the batch from dropping an earlier one', () => {
|
||||
const persisted = applyNativeChatSessionOptionPicks({
|
||||
persisted: undefined,
|
||||
agent: 'codex',
|
||||
picks: [
|
||||
{ modelId: 'gpt-fast', optionId: 'model', value: 'gpt-fast' },
|
||||
{ modelId: 'gpt-fast', optionId: 'effort', value: 'low' }
|
||||
]
|
||||
})
|
||||
expect(resolveStructuredLaunchSeedOptions(persisted, 'codex')).toEqual({
|
||||
model: 'gpt-fast',
|
||||
effort: 'low'
|
||||
})
|
||||
})
|
||||
|
||||
it('leaves every other agent untouched', () => {
|
||||
const persisted = applyNativeChatSessionOptionPicks({
|
||||
persisted: { claude: { model: 'opus', valuesByModel: { opus: { effort: 'high' } } } },
|
||||
@@ -174,10 +151,4 @@ describe('applyNativeChatSessionOptionPicks', () => {
|
||||
effort: 'low'
|
||||
})
|
||||
})
|
||||
|
||||
it('returns the record unchanged for an empty batch', () => {
|
||||
expect(
|
||||
applyNativeChatSessionOptionPicks({ persisted: undefined, agent: 'codex', picks: [] })
|
||||
).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,7 +5,6 @@ import { agentSessionWriteNoticeEnglish } from './agent-session-refusal-notice'
|
||||
import { DISPATCH_REJECTED_QUEUE_FULL } from './structured-agent-session-dispatch-rejection'
|
||||
import {
|
||||
structuredAgentSessionRecordedRejectionParts,
|
||||
structuredAgentSessionRejectionNotice,
|
||||
structuredAgentSessionRejectionParts
|
||||
} from './structured-agent-session-rejection-words'
|
||||
|
||||
@@ -21,31 +20,10 @@ describe('what a rejection shows the reader', () => {
|
||||
expect(shown).toBe("Orca couldn't reach the agent. Your message was not sent.")
|
||||
})
|
||||
|
||||
it("shows a content rejection in the provider's own words", () => {
|
||||
expect(notice('Claude messages support at most 20 images')).toBe(
|
||||
'Claude messages support at most 20 images'
|
||||
)
|
||||
})
|
||||
|
||||
it('shows the sentence a host wrote for the person reading it', () => {
|
||||
const reason = 'The provider stopped before it finished starting.'
|
||||
expect(notice(reason)).toBe(reason)
|
||||
})
|
||||
|
||||
it('never puts a legacy or local-capacity marker on screen', () => {
|
||||
expect(notice('not_delivered')).toBe('Your message was not sent.')
|
||||
expect(notice(DISPATCH_REJECTED_QUEUE_FULL)).toBe('Your message was not sent.')
|
||||
})
|
||||
|
||||
it('claims no cause when the rejection names none', () => {
|
||||
expect(notice(null)).toBe('Your message was not sent.')
|
||||
})
|
||||
|
||||
it('tells the phone how to send it again after a transport failure', () => {
|
||||
const phone = structuredAgentSessionRejectionNotice('provider_write_failed', 'composer-send')
|
||||
expect(phone.startsWith("Orca couldn't reach the agent. Your message was not sent.")).toBe(true)
|
||||
expect(phone.length).toBeGreaterThan(notice('provider_write_failed').length)
|
||||
})
|
||||
})
|
||||
|
||||
// A row that carries the host's fact is worded from it; the reason is not read.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user