diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index ee5b4a483ac..63d34f8951b 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -956,7 +956,7 @@ 2 test-no-wall-clock:src/main/rate-limits/service-cursor-usage.test.ts 12 test-no-wall-clock:src/main/rate-limits/service-minimax-usage.test.ts 38 test-no-wall-clock:src/main/rate-limits/service-refresh-orchestration.test.ts -6 test-no-wall-clock:src/main/rate-limits/service-zcode-usage.test.ts +4 test-no-wall-clock:src/main/rate-limits/service-zcode-usage.test.ts 1 test-no-wall-clock:src/main/runtime/agent-session/ownership/agent-session-process-identity-probe.test.ts 1 test-no-wall-clock:src/main/runtime/agent-state-rules/agent-state-rules-engine.test.ts 1 test-no-wall-clock:src/main/runtime/antigravity-readiness-transcripts.test.ts @@ -1106,7 +1106,6 @@ 1 test-no-wall-clock:src/renderer/src/components/sidebar/WorktreeList.lineage-agent-expansion-coupling.test.tsx 1 test-no-wall-clock:src/renderer/src/components/sidebar/WorktreeList.lineage-child-real-card.test.tsx 5 test-no-wall-clock:src/renderer/src/components/sidebar/use-workspace-reference-details.test.tsx -1 test-no-wall-clock:src/renderer/src/components/status-bar/RuntimeHostStatusRow.test.tsx 5 test-no-wall-clock:src/renderer/src/components/status-bar/inline-usage-bars.test.tsx 4 test-no-wall-clock:src/renderer/src/components/status-bar/provider-segment-balance.test.tsx 2 test-no-wall-clock:src/renderer/src/components/status-bar/status-bar-antigravity-segment.test.tsx @@ -1127,7 +1126,6 @@ 1 test-no-wall-clock:src/renderer/src/components/terminal/initial-terminal-structured-launch.test.tsx 2 test-no-wall-clock:src/renderer/src/components/workspace-cleanup/use-workspace-cleanup-removal.agent-stop.test.tsx 6 test-no-wall-clock:src/renderer/src/components/workspace-cleanup/workspace-cleanup-confirm-remove.test.tsx -2 test-no-wall-clock:src/renderer/src/components/worktree-creation/WorktreeCreationPanel.test.tsx 2 test-no-wall-clock:src/renderer/src/hooks/ipc-events/agent-status-terminal-title-tab-write.test.ts 3 test-no-wall-clock:src/renderer/src/hooks/useAutomationDispatchEvents.test.ts 1 test-no-wall-clock:src/renderer/src/hooks/useIpcEvents-agent-status-hook-titles.test.ts @@ -1363,7 +1361,6 @@ 4 test-timeout-needs-reason:src/packages/process-host/src/run-process.test.ts 5 test-timeout-needs-reason:src/renderer/src/components/sidebar/WorktreeCard.compact-hover.test.tsx 2 test-timeout-needs-reason:src/renderer/src/components/sidebar/WorktreeCard.host-badge-toggle.test.tsx -4 test-timeout-needs-reason:src/renderer/src/components/sidebar/WorktreeCard.pinned-repo-icon.test.tsx 7 test-timeout-needs-reason:src/renderer/src/components/sidebar/WorktreeCard.pr-display.test.tsx 1 test-timeout-needs-reason:src/renderer/src/components/sidebar/WorktreeCardAgents.send-target.test.tsx 1 test-timeout-needs-reason:src/renderer/src/components/sidebar/WorktreeCardAgents.test.tsx diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 47831aa07f4..3f9dfd84dc3 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -10811,7 +10811,7 @@ "providers": ["local", "remote-runtime", "ssh", "wsl"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "remote-runtime", "ssh", "wsl"], - "coverageNotes": "Platform-neutral deterministic tests separate the durable Active Server preference from per-client connection, selected-workspace, browser-session, Add Project, and execution-host routing. The composed regression models Local desktop -> connect/navigate Windows 2 -> reveal a local terminal -> restart. Multi-client browser host overrides, multi-server profile caches, generic settings IPC rejection, local and remote workspace ownership, Add Project eligibility/owner capture/stale-result fencing, host-qualified group/folder/worktree identity, and restart reset of transient routing are covered. A visible macOS Orca desktop server plus separate paired Electron and web clients prove selected-runtime Add Project operations and disabled creation when the only listed host is blocked or unavailable; headless-server parity also passed. Live Linux, Windows, WSL, and SSH Add Project journeys remain uncollected.", + "coverageNotes": "Platform-neutral deterministic tests separate the durable Active Server preference from per-client connection, selected-workspace, browser-session, Add Project, and execution-host routing. The composed regression models Local desktop -> connect/navigate Windows 2 -> reveal a local terminal -> restart. Multi-client browser host overrides, multi-server profile caches, generic settings IPC rejection, local and remote workspace ownership, Add Project eligibility/owner capture/stale-result fencing, host-qualified group/folder/worktree identity, and restart reset of transient routing are covered. A visible macOS Orca desktop server plus separate paired Electron and web clients prove selected-runtime Add Project operations and disabled creation when the only listed host is blocked or unavailable; headless-server parity also passed. Live Linux, Windows, WSL, and SSH Add Project journeys remain uncollected. The static wrapper-rendering suite was removed during the low-value-test audit; its supplied-prop host-browser and control-visibility output assertions are no longer checked by that suite. This removal supplies no new runtime-routing evidence.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/9687", "https://linear.app/stably/issue/STA-4024/p1-paired-web-add-project-enables-unavailable-host-fallback-after-pr" @@ -10821,7 +10821,7 @@ "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/settings.test.ts src/main/ipc/runtime-environments/runtime-environments-pairing.test.ts src/renderer/src/store/slices/settings.test.ts src/renderer/src/store/slices/browser.test.ts src/renderer/src/components/settings/browser-session-host-selection.test.ts src/renderer/src/components/settings/RuntimeEnvironmentsPane.test.ts src/renderer/src/components/sidebar/use-add-repo-host-selection.test.ts src/renderer/src/hooks/useIpcEvents-terminal-create-surfacing.test.ts src/renderer/src/web/web-preload-api-runtime-environment.test.ts --maxWorkers=1", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts src/renderer/src/components/sidebar/project-added-default-checkout.test.ts src/renderer/src/components/sidebar/useAddRepoLocalFolderFlow.test.ts src/renderer/src/components/sidebar/useAddRepoNestedImportFlow.test.ts src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts src/renderer/src/components/sidebar/worktree-list/listing/host-filtering.test.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/lib/worktree-runtime-owner.test.ts src/renderer/src/store/selectors.test.ts src/renderer/src/store/slices/repos-catalog-reconciliation.test.ts src/renderer/src/store/slices/repos-project-groups.test.ts src/renderer/src/store/slices/repos-selected-owner-routing.test.ts src/renderer/src/store/slices/selected-host-active-workspace-identity.test.ts src/renderer/src/store/slices/worktrees-fetch-owner-routing.test.ts --maxWorkers=1", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/use-add-repo-host-selection.test.ts src/renderer/src/components/sidebar/add-repo-browse-authority.test.ts src/renderer/src/components/sidebar/AddRepoDialogStepContent.test.tsx src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/store/slices/repos-paired-runtime-add.test.ts --maxWorkers=1", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/use-add-repo-host-selection.test.ts src/renderer/src/components/sidebar/add-repo-browse-authority.test.ts src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/store/slices/repos-paired-runtime-add.test.ts --maxWorkers=1", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts src/renderer/src/components/sidebar/project-added-default-checkout.test.ts src/renderer/src/components/sidebar/useAddRepoLocalFolderFlow.test.ts src/renderer/src/components/sidebar/useAddRepoNestedImportFlow.test.ts src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts src/renderer/src/components/sidebar/worktree-list/listing/host-filtering.test.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/lib/worktree-runtime-owner.test.ts src/renderer/src/store/selectors.test.ts src/renderer/src/store/slices/repos-catalog-reconciliation.test.ts src/renderer/src/store/slices/repos-project-groups.test.ts src/renderer/src/store/slices/repos-selected-owner-routing.test.ts src/renderer/src/store/slices/selected-host-active-workspace-identity.test.ts src/renderer/src/store/slices/worktrees-fetch-owner-routing.test.ts src/renderer/src/runtime/web-runtime-session.test.ts --maxWorkers=1", "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/pr11346-selected-runtime-add.spec.ts --config tests/playwright.config.ts --project electron-headful --workers=1 --reporter=line", "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/pr11346-selected-runtime-add.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1 --reporter=line", @@ -10842,7 +10842,6 @@ "src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx", "src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx", "src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts", - "src/renderer/src/components/sidebar/AddRepoDialogStepContent.test.tsx", "src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx", "src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts", "src/renderer/src/components/sidebar/project-added-default-checkout.test.ts", @@ -10879,10 +10878,6 @@ "paired runtime browsing opens the host browser without invoking native pickFolders while desktop local and SSH routes remain intact" ] }, - { - "file": "src/renderer/src/components/sidebar/AddRepoDialogStepContent.test.tsx", - "assertions": ["paired runtime browsing renders the in-app host filesystem browser"] - }, { "file": "src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx", "assertions": [ diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json index 07ba40eab60..e7443228c83 100644 --- a/config/scripts/ci-shard-timings.json +++ b/config/scripts/ci-shard-timings.json @@ -5469,7 +5469,6 @@ "src/main/runtime/rpc/terminal-agent-prompt-send.test.ts": 1761, "src/main/runtime/rpc/terminal-agent-send-guard.test.ts": 1295, "src/main/runtime/rpc/terminal-claude-agent-view.test.ts": 1058, - "src/main/runtime/rpc/terminal-geometry-stale-handle.test.ts": 1168, "src/main/runtime/rpc/terminal-lease-stale-handle-survival.test.ts": 1201, "src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts": 1505, "src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery-history.test.ts": 1306, @@ -7528,7 +7527,6 @@ "src/renderer/src/components/feature-tips/feature-tip-startup-gate.test.ts": 105, "src/renderer/src/components/feature-tips/use-session-search-tip-setup.test.tsx": 520, "src/renderer/src/components/feature-wall/AgentCapabilitiesSetupAction.test.ts": 1690, - "src/renderer/src/components/feature-wall/ConnectIntegrationsList.test.tsx": 663, "src/renderer/src/components/feature-wall/FeatureWallSetupWorkflowActions.test.tsx": 1589, "src/renderer/src/components/feature-wall/FullDiskAccessSetupPrompt.test.ts": 695, "src/renderer/src/components/feature-wall/browser-animated-visual-sequence.test.ts": 42, @@ -8390,7 +8388,6 @@ "src/renderer/src/components/settings/ReleaseChannelSection.test.tsx": 2427, "src/renderer/src/components/settings/repository/RepositoryGitHubAccountSection.inventory-lifetime.test.tsx": 587, "src/renderer/src/components/settings/repository/RepositoryGitHubAccountSection.test.tsx": 662, - "src/renderer/src/components/settings/repository/RepositoryPane.test.ts": 2778, "src/renderer/src/components/settings/repository/source-control/RepositorySourceControlAiSection.test.ts": 365, "src/renderer/src/components/settings/RuntimeEnvironmentsPane.test.ts": 201, "src/renderer/src/components/settings/RuntimeHostAccessForm.test.tsx": 307, @@ -8401,7 +8398,6 @@ "src/renderer/src/components/settings/SettingsConstants.test.ts": 97, "src/renderer/src/components/settings/SettingsFormControls.font-autocomplete.test.tsx": 789, "src/renderer/src/components/settings/SettingsFormControls.segmented-control.test.tsx": 548, - "src/renderer/src/components/settings/SettingsSidebar.test.tsx": 2169, "src/renderer/src/components/settings/ShareSkillsSettingsPane.test.tsx": 601, "src/renderer/src/components/settings/ShortcutFilterRail.test.ts": 157, "src/renderer/src/components/settings/SparsePresetSettingsSection.test.tsx": 785, @@ -8514,7 +8510,6 @@ "src/renderer/src/components/sidebar/AddRepoCreateStep.test.tsx": 532, "src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts": 42, "src/renderer/src/components/sidebar/AddRepoDialogChrome.test.tsx": 495, - "src/renderer/src/components/sidebar/AddRepoDialogStepContent.test.tsx": 1988, "src/renderer/src/components/sidebar/AddRepoHostSelector.test.tsx": 390, "src/renderer/src/components/sidebar/AddRepoNestedImportStep.test.tsx": 817, "src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx": 681, @@ -8575,7 +8570,6 @@ "src/renderer/src/components/sidebar/WorktreeCard.host-badge-toggle.test.tsx": 1253, "src/renderer/src/components/sidebar/WorktreeCard.hosted-review-refresh.test.tsx": 1647, "src/renderer/src/components/sidebar/WorktreeCard.merged-pr-display.test.tsx": 1682, - "src/renderer/src/components/sidebar/WorktreeCard.pinned-repo-icon.test.tsx": 1604, "src/renderer/src/components/sidebar/WorktreeCard.pr-display.test.tsx": 1246, "src/renderer/src/components/sidebar/WorktreeCard.quick-actions.test.tsx": 1827, "src/renderer/src/components/sidebar/WorktreeCard.single-reference-details.test.tsx": 1662, @@ -8596,7 +8590,6 @@ "src/renderer/src/components/sidebar/WorktreeContextMenu.test.ts": 164, "src/renderer/src/components/sidebar/WorktreeList.card-memo-stability.test.tsx": 819, "src/renderer/src/components/sidebar/WorktreeList.folder-workspace-rows.test.ts": 2943, - "src/renderer/src/components/sidebar/WorktreeList.group-headers.test.ts": 1786, "src/renderer/src/components/sidebar/WorktreeList.lineage-agent-expansion-coupling.test.tsx": 3116, "src/renderer/src/components/sidebar/WorktreeList.lineage-child-card.test.ts": 4240, "src/renderer/src/components/sidebar/WorktreeList.lineage-child-real-card.test.tsx": 3000, @@ -9756,7 +9749,6 @@ "src/renderer/src/components/workspace-space/SpaceWindowStatePublisher.test.tsx": 618, "src/renderer/src/components/workspace-space/space-window-state.test.ts": 162, "src/renderer/src/components/workspace-surface-projection.test.ts": 48, - "src/renderer/src/components/worktree-creation/WorktreeCreationPanel.test.tsx": 509, "src/renderer/src/components/worktree-jump-palette-browser-navigation.test.tsx": 2772, "src/renderer/src/components/worktree-jump-palette-interleaved-sections.test.tsx": 2023, "src/renderer/src/components/worktree-jump-palette-primitives.test.tsx": 384, diff --git a/src/main/browser/cookies/browser-cookie-import-scope.test.ts b/src/main/browser/cookies/browser-cookie-import-scope.test.ts index a224078cd43..fd01bfcbe45 100644 --- a/src/main/browser/cookies/browser-cookie-import-scope.test.ts +++ b/src/main/browser/cookies/browser-cookie-import-scope.test.ts @@ -171,48 +171,4 @@ describe('native cookie import preserves destination cookies', () => { expect(removedNames).not.toContain('SID') expect(removedNames).not.toContain('orphan') }) - - it('keeps extra destination paths and subdomains alongside imported values', async () => { - sessionFromPartitionMock.mockReturnValue({ - cookies: { - get: vi.fn().mockResolvedValue([ - { - name: 'gist-session', - value: 'stale', - domain: 'gist.github.com', - hostOnly: true, - path: '/', - secure: true, - httpOnly: true, - sameSite: 'lax' - }, - { - name: 'lookalike', - value: 'live', - domain: 'notgithub.com', - hostOnly: true, - path: '/', - secure: true, - httpOnly: true, - sameSite: 'lax' - } - ]), - set: vi.fn(async () => undefined), - remove: cookiesRemoveMock, - flushStore: vi.fn().mockResolvedValue(undefined) - }, - clearData: vi.fn().mockResolvedValue(undefined), - setUserAgent: vi.fn(), - getStoragePath: () => join(tmpDir, 'userData', 'Partitions', 'test') - }) - createChromiumCookieTestDatabase(sourceCookiesPath, [ - { domain: '.github.com', name: 'user_session', value: 'imported-github' } - ]).close() - createChromiumCookieTestDatabase(targetCookiesPath, []).close() - - const result = await importCookiesFromBrowser(chromeBrowser(sourceCookiesPath), 'persist:test') - - expect(result.ok).toBe(true) - expect(cookiesRemoveMock).not.toHaveBeenCalled() - }) }) diff --git a/src/main/browser/snapshot-engine.test.ts b/src/main/browser/snapshot-engine.test.ts index 17f5341b220..a3076097ac4 100644 --- a/src/main/browser/snapshot-engine.test.ts +++ b/src/main/browser/snapshot-engine.test.ts @@ -46,13 +46,6 @@ function node( } describe('buildSnapshot', () => { - it('returns empty snapshot for empty tree', async () => { - const result = await buildSnapshot(makeSender([])) - expect(result.snapshot).toBe('') - expect(result.refs).toEqual([]) - expect(result.refMap.size).toBe(0) - }) - it('assigns refs to interactive elements', async () => { const nodes: AXNode[] = [ node('1', 'WebArea', 'page', { childIds: ['2', '3'] }), @@ -68,48 +61,6 @@ describe('buildSnapshot', () => { expect(result.snapshot).toContain('[@e2] link "Home"') }) - it('renders text inputs with friendly role name', async () => { - const nodes: AXNode[] = [ - node('1', 'WebArea', 'page', { childIds: ['2'] }), - node('2', 'textbox', 'Email', { backendDOMNodeId: 10 }) - ] - const result = await buildSnapshot(makeSender(nodes)) - expect(result.snapshot).toContain('text input "Email"') - }) - - it('renders landmarks without refs', async () => { - const nodes: AXNode[] = [ - node('1', 'WebArea', 'page', { childIds: ['2'] }), - node('2', 'navigation', 'Main Nav', { childIds: ['3'] }), - node('3', 'link', 'About', { backendDOMNodeId: 10 }) - ] - const result = await buildSnapshot(makeSender(nodes)) - - expect(result.snapshot).toContain('[Main Nav]') - expect(result.refs).toHaveLength(1) - expect(result.refs[0].name).toBe('About') - }) - - it('renders headings without refs', async () => { - const nodes: AXNode[] = [ - node('1', 'WebArea', 'page', { childIds: ['2'] }), - node('2', 'heading', 'Welcome') - ] - const result = await buildSnapshot(makeSender(nodes)) - expect(result.snapshot).toContain('heading "Welcome"') - expect(result.refs).toHaveLength(0) - }) - - it('renders static text without refs', async () => { - const nodes: AXNode[] = [ - node('1', 'WebArea', 'page', { childIds: ['2'] }), - node('2', 'staticText', 'Hello world') - ] - const result = await buildSnapshot(makeSender(nodes)) - expect(result.snapshot).toContain('text "Hello world"') - expect(result.refs).toHaveLength(0) - }) - it('skips generic/none/presentation roles', async () => { const nodes: AXNode[] = [ node('1', 'WebArea', 'page', { childIds: ['2'] }), @@ -156,41 +107,4 @@ describe('buildSnapshot', () => { expect(entry!.role).toBe('checkbox') expect(entry!.name).toBe('Agree') }) - - it('indents children under landmarks', async () => { - const nodes: AXNode[] = [ - node('1', 'WebArea', 'page', { childIds: ['2'] }), - node('2', 'main', '', { childIds: ['3'] }), - node('3', 'button', 'Action', { backendDOMNodeId: 10 }) - ] - const result = await buildSnapshot(makeSender(nodes)) - const lines = result.snapshot.split('\n') - const mainLine = lines.find((l) => l.includes('[Main Content]')) - const buttonLine = lines.find((l) => l.includes('Action')) - expect(mainLine).toBeDefined() - expect(buttonLine).toBeDefined() - expect(buttonLine!.startsWith(' ')).toBe(true) - }) - - it('handles a realistic page structure', async () => { - const nodes: AXNode[] = [ - node('1', 'WebArea', 'page', { childIds: ['2', '3', '4'] }), - node('2', 'banner', '', { childIds: ['5'] }), - node('3', 'main', '', { childIds: ['6', '7', '8'] }), - node('4', 'contentinfo', '', {}), - node('5', 'link', 'Logo', { backendDOMNodeId: 10 }), - node('6', 'heading', 'Dashboard'), - node('7', 'textbox', 'Search', { backendDOMNodeId: 20 }), - node('8', 'button', 'Go', { backendDOMNodeId: 21 }) - ] - const result = await buildSnapshot(makeSender(nodes)) - - expect(result.refs).toHaveLength(3) - expect(result.refs.map((r) => r.name)).toEqual(['Logo', 'Search', 'Go']) - - expect(result.snapshot).toContain('[Header]') - expect(result.snapshot).toContain('[Main Content]') - expect(result.snapshot).toContain('[Footer]') - expect(result.snapshot).toContain('heading "Dashboard"') - }) }) diff --git a/src/main/claude/claude-structured-inbound-control.test.ts b/src/main/claude/claude-structured-inbound-control.test.ts index 73f3b60a2bf..c675285b0b2 100644 --- a/src/main/claude/claude-structured-inbound-control.test.ts +++ b/src/main/claude/claude-structured-inbound-control.test.ts @@ -170,35 +170,4 @@ describe('Claude permission callbacks', () => { expect.objectContaining({ type: 'prompt-cancelled', promptKey: 'perm-4' }) ) }) - - it('settles every in-flight prompt with null when the registry is cleared', async () => { - const control = callbacksFor() - const first = control.canUseTool( - 'Bash', - { command: 'a' }, - permissionOptions('perm-5', 'tool-5', new AbortController().signal) - ) - const second = control.canUseTool( - 'Bash', - { command: 'b' }, - permissionOptions('perm-6', 'tool-6', new AbortController().signal) - ) - - for (const prompt of control.prompts.clear()) { - prompt.settle(null) - } - - await expect(first).resolves.toBeNull() - await expect(second).resolves.toBeNull() - }) - - it('answers a user dialog deny-safe', async () => { - const control = callbacksFor() - await expect( - control.onUserDialog( - { dialogKind: 'refusal_fallback_prompt', payload: {} }, - { signal: new AbortController().signal, requestId: 'dialog-1' } - ) - ).resolves.toEqual({ behavior: 'cancelled' }) - }) }) diff --git a/src/main/codex/codex-structured-rewind.test.ts b/src/main/codex/codex-structured-rewind.test.ts index 78602b56475..d9f065177b6 100644 --- a/src/main/codex/codex-structured-rewind.test.ts +++ b/src/main/codex/codex-structured-rewind.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it, vi } from 'vitest' import { CodexAppServerRequestError } from './app-server/codex-app-server-request-error' import type { CodexSession } from './codex-structured-session-state' import { recoverCodexRewind, rewindCodexSession } from './codex-structured-rewind' -import { openCodexThread } from './codex-structured-thread-open' function fixture(turns = ['kept', 'drop'], reverted = false) { let history = reverted ? turns.slice(0, turns.indexOf('drop')) : turns @@ -174,15 +173,4 @@ describe('Codex rewind', () => { }) expect(request).toHaveBeenCalledTimes(1) }) - it('captures history mode at both start and resume without changing defaults', async () => { - for (const resumeThreadId of [null, 'thread']) { - const request = vi.fn(async (_method: string, _params?: unknown) => ({ - thread: { id: 'thread', historyMode: 'legacy' } - })) - expect( - await openCodexThread({ request }, { cwd: '/workspace', resumeThreadId }, 10) - ).toMatchObject({ historyMode: 'legacy' }) - expect(request.mock.calls[0]?.[1]).not.toHaveProperty('historyMode') - } - }) }) diff --git a/src/main/codex/codex-user-hook-trust-moves.test.ts b/src/main/codex/codex-user-hook-trust-moves.test.ts index 79312de5163..eca1aa6a701 100644 --- a/src/main/codex/codex-user-hook-trust-moves.test.ts +++ b/src/main/codex/codex-user-hook-trust-moves.test.ts @@ -1,4 +1,5 @@ -import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' + import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -57,17 +58,6 @@ function mutate( } describe('real-home user hook trust rebasing', () => { - it('writes without touching config.toml when no hook moves', () => { - const user = command('user-hook') - mutate( - { Stop: [{ hooks: [user] }] }, - { Stop: [{ hooks: [user] }, { hooks: [command('orca')] }] } - ) - - expect(existsSync(hooksPath)).toBe(true) - expect(existsSync(configPath)).toBe(false) - }) - it('finds multiple shifted user hooks, including a handler from a mixed group', async () => { const orca = command('orca-hook') const first = command('first-user') diff --git a/src/main/codex/config-toml-project-duplicate-repair.test.ts b/src/main/codex/config-toml-project-duplicate-repair.test.ts index 9feb59c0d9e..aaa7c57e955 100644 --- a/src/main/codex/config-toml-project-duplicate-repair.test.ts +++ b/src/main/codex/config-toml-project-duplicate-repair.test.ts @@ -172,11 +172,4 @@ describe('repairing configs broken by #22592', () => { expect(repairOrcaDuplicateTrustTables(broken)).toBe(broken) expect(warn).toHaveBeenCalledTimes(1) }) - - it('does not touch a valid config', () => { - const config = ['["projects"."/repo-f"]', '"trust_level" = "trusted"', ''].join('\n') - - expect(repairOrcaDuplicateTrustTables(config)).toBe(config) - expect(warn).not.toHaveBeenCalled() - }) }) diff --git a/src/main/daemon/desktop-restore-admission.test.ts b/src/main/daemon/desktop-restore-admission.test.ts index f7feb7e1424..55b4ce2d7b6 100644 --- a/src/main/daemon/desktop-restore-admission.test.ts +++ b/src/main/daemon/desktop-restore-admission.test.ts @@ -8,10 +8,7 @@ import { DaemonAutomaticWorkGuard } from './daemon-automatic-work-guard' import { DaemonProcessGrowthMonitor } from './daemon-process-growth-monitor' import type { SubprocessHandle } from './session-subprocess-handle' import { TerminalHost, type TerminalHostOptions } from './terminal-host' -import { - resolveTerminalSpawnAdmissionMode, - TERMINAL_SPAWN_ADMISSION_ENV_VAR -} from './terminal-spawn-gate' +import { resolveTerminalSpawnAdmissionMode } from './terminal-spawn-gate' type SpawnSubprocess = TerminalHostOptions['spawnSubprocess'] @@ -105,19 +102,6 @@ describe('desktop restore admission', () => { }) } - it('enforces on desktop unless the rollback switch asks to observe', () => { - expect(resolveTerminalSpawnAdmissionMode({})).toBe('enforce') - expect( - resolveTerminalSpawnAdmissionMode({ [TERMINAL_SPAWN_ADMISSION_ENV_VAR]: 'enforce' }) - ).toBe('enforce') - expect(resolveTerminalSpawnAdmissionMode({ [TERMINAL_SPAWN_ADMISSION_ENV_VAR]: 'bogus' })).toBe( - 'enforce' - ) - expect( - resolveTerminalSpawnAdmissionMode({ [TERMINAL_SPAWN_ADMISSION_ENV_VAR]: ' Observe ' }) - ).toBe('observe') - }) - it('restores the visible pane at once, at most 8 hidden agents, and keeps the rest sleeping', async () => { startDesktopDaemon() const hidden = Array.from({ length: 19 }, (_, i) => diff --git a/src/main/daemon/pty-subprocess-git-credential-guard.test.ts b/src/main/daemon/pty-subprocess-git-credential-guard.test.ts index 698a8600469..d4b57d0cee9 100644 --- a/src/main/daemon/pty-subprocess-git-credential-guard.test.ts +++ b/src/main/daemon/pty-subprocess-git-credential-guard.test.ts @@ -195,24 +195,4 @@ describe('createPtySubprocess', () => { Object.assign(process.env, savedGitConfigEnv) } }) - - it('guards a trusted daemon agent whose launch command is wrapped', async () => { - const proc = mockPtyProcess() - spawnMock.mockReturnValue(proc) - - await createPtySubprocess({ - sessionId: 'trusted-wrapped-agent', - cols: 80, - rows: 24, - command: 'cd /repo && custom-agent-wrapper', - launchAgent: 'claude', - env: { SHELL: '/bin/bash' } - }) - - const spawnEnv = spawnMock.mock.calls.at(-1)?.[2]?.env as Record - expect(spawnEnv.GIT_TERMINAL_PROMPT).toBe('0') - expect(spawnEnv.GCM_INTERACTIVE).toBe('never') - expect(Object.values(spawnEnv)).toContain('credential.interactive') - expect(Object.values(spawnEnv)).toContain('credential.guiPrompt') - }) }) diff --git a/src/main/emulator/simctl-simulator-devices.test.ts b/src/main/emulator/simctl-simulator-devices.test.ts index 52cfd7c1b3c..bcb26e4c695 100644 --- a/src/main/emulator/simctl-simulator-devices.test.ts +++ b/src/main/emulator/simctl-simulator-devices.test.ts @@ -35,50 +35,6 @@ describe('listSimulatorDevices', () => { platformMock.mockReturnValue('darwin') }) - it('parses simctl devices on macOS', async () => { - execFileMock.mockImplementation((_command, _args, _options, callback) => { - callback( - null, - JSON.stringify({ - devices: { - 'com.apple.CoreSimulator.SimRuntime.iOS-18-0': [ - { - name: 'iPhone 17 Pro', - udid: 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE', - state: 'Shutdown', - isAvailable: true - } - ] - } - }), - '' - ) - }) - - await expect(listSimulatorDevices()).resolves.toEqual([ - { - name: 'iPhone 17 Pro', - udid: 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE', - state: 'Shutdown', - runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', - isAvailable: true - } - ]) - expect(execFileMock).toHaveBeenCalledWith( - 'xcrun', - ['simctl', 'list', 'devices', '-j'], - { timeout: 15_000 }, - expect.any(Function) - ) - }) - - it('returns no devices on non-macOS hosts', async () => { - platformMock.mockReturnValue('linux') - - await expect(listSimulatorDevices()).resolves.toEqual([]) - expect(execFileMock).not.toHaveBeenCalled() - }) - it('normalizes missing simctl errors for UI and CLI callers', async () => { execFileMock.mockImplementation((_command, _args, _options, callback) => { callback( diff --git a/src/main/git/add-sparse-worktree.test.ts b/src/main/git/add-sparse-worktree.test.ts index 75db0437573..cee97ea0a52 100644 --- a/src/main/git/add-sparse-worktree.test.ts +++ b/src/main/git/add-sparse-worktree.test.ts @@ -69,36 +69,6 @@ describe('addSparseWorktree', () => { platformSpy.mockRestore() }) - it('enables long paths on the Windows commands that actually write the deep checkout', async () => { - platformSpy.mockReturnValue('win32') - gitExecFileAsyncMock.mockResolvedValue({ stdout: '', stderr: '' }) - - await addSparseWorktree('C:\\repo', 'C:\\repo-feature', 'feature/test', ['packages/web']) - - const calls = getGitCalls() - expect(calls).toEqual( - expect.arrayContaining([ - 'git -c core.longpaths=true sparse-checkout set -- packages/web', - 'git -c core.longpaths=true checkout feature/test' - ]) - ) - }) - - it('omits the long-path option on non-Windows hosts', async () => { - gitExecFileAsyncMock.mockResolvedValue({ stdout: '', stderr: '' }) - - await addSparseWorktree('/repo', '/repo-feature', 'feature/test', ['packages/web']) - - const calls = getGitCalls() - expect(calls).toEqual( - expect.arrayContaining([ - 'git sparse-checkout set -- packages/web', - 'git checkout feature/test' - ]) - ) - expect(calls.some((call) => call.includes('core.longpaths'))).toBe(false) - }) - it('separates sparse checkout directory operands from options', async () => { gitExecFileAsyncMock.mockResolvedValue({ stdout: '', stderr: '' }) diff --git a/src/main/git/source-control/drive-checkout-suspect-status.test.ts b/src/main/git/source-control/drive-checkout-suspect-status.test.ts index 69b1d333702..172a06726ed 100644 --- a/src/main/git/source-control/drive-checkout-suspect-status.test.ts +++ b/src/main/git/source-control/drive-checkout-suspect-status.test.ts @@ -142,14 +142,6 @@ describe('reconcileDriveCheckoutSuspects', () => { }) } - it('does nothing for checkouts outside the Windows-checkout config', async () => { - mocks.appliesTo = new Set() - await reconcileDriveCheckoutSuspects(repo, [], {}) - changed(['a.ts']) - expect(await reconcileDriveCheckoutSuspects(repo, [], {})).toEqual([]) - expect(mocks.gitStreamStdout).not.toHaveBeenCalled() - }) - it('rechecks only suspects minimal status reported clean, keeping the dirty ones', async () => { await touchFiles(['clean.ts', 'hidden edit.ts', 'already.ts']) await reconcileDriveCheckoutSuspects(repo, [], {}) diff --git a/src/main/git/worktree-base-divergence.test.ts b/src/main/git/worktree-base-divergence.test.ts index f1b97b808f2..123e935952b 100644 --- a/src/main/git/worktree-base-divergence.test.ts +++ b/src/main/git/worktree-base-divergence.test.ts @@ -4,14 +4,10 @@ const mocks = vi.hoisted(() => ({ gitExecFileAsync: vi.fn() })) vi.mock('./runner', () => ({ gitExecFileAsync: mocks.gitExecFileAsync })) -import { GIT_READ_TIMEOUT_MS } from './command-runner/git-command-timeout' import { GitAdmissionScheduler } from './command-runner/git-subprocess-admission' import type { GitAdmissionTier } from './command-runner/git-exec-options' -import { WSL_GIT_READ_ENVIRONMENT_WAIT_MS } from './wsl-git-read-environment' -import { - measureRetargetDivergence, - RETARGET_DIVERGENCE_BUDGET_MS -} from './worktree-base-divergence' + +import { measureRetargetDivergence } from './worktree-base-divergence' type ExecOptions = { cwd: string @@ -21,10 +17,6 @@ type ExecOptions = { admissionTier?: GitAdmissionTier } -function callOptions(): ExecOptions[] { - return mocks.gitExecFileAsync.mock.calls.map((call) => call[1] as ExecOptions) -} - function subcommands(): string[] { return mocks.gitExecFileAsync.mock.calls.map((call) => (call[0] as string[])[0]!) } @@ -73,32 +65,6 @@ describe('measureRetargetDivergence deadlines', () => { } }) - it('puts every probe under one shared budget, not a budget each', async () => { - answerProbes('3\n') - - await expect( - measureRetargetDivergence('/repo', 'refs/heads/main', 'refs/remotes/origin/main') - ).resolves.toBe('within') - - expect(subcommands()).toEqual(['rev-list', 'rev-list', 'merge-base']) - const signals = callOptions().map((options) => options.signal) - // One budget spans all staged probes; per-probe budgets would add together. - expect(new Set(signals).size).toBe(1) - expect(signals[0]).toBeInstanceOf(AbortSignal) - }) - - it('also gives each probe a command timeout well below git default read deadline', async () => { - answerProbes('3\n') - - await measureRetargetDivergence('/repo', 'refs/heads/main', 'refs/remotes/origin/main') - - // Signals cover pre-spawn waits; command timeouts still bound a hung spawn. - for (const options of callOptions()) { - expect(options.timeout).toBe(RETARGET_DIVERGENCE_BUDGET_MS) - } - expect(RETARGET_DIVERGENCE_BUDGET_MS).toBeLessThan(GIT_READ_TIMEOUT_MS) - }) - it('really aborts the in-flight probes when the shared budget expires', async () => { mocks.gitExecFileAsync.mockImplementation( (_args: string[], options: ExecOptions) => @@ -121,12 +87,6 @@ describe('measureRetargetDivergence deadlines', () => { ).resolves.toBe('unknown') }) - it('clears the WSL read-environment wait, which starts before any command timeout', () => { - // Leave walk headroom after the first WSL environment probe. - expect(RETARGET_DIVERGENCE_BUDGET_MS).toBeGreaterThan(WSL_GIT_READ_ENVIRONMENT_WAIT_MS) - expect(RETARGET_DIVERGENCE_BUDGET_MS).toBeLessThan(GIT_READ_TIMEOUT_MS) - }) - it('stops the probes when the create itself is cancelled, without waiting for the budget', async () => { mocks.gitExecFileAsync.mockImplementation( (_args: string[], options: ExecOptions) => @@ -194,16 +154,4 @@ describe('measureRetargetDivergence deadlines', () => { ).resolves.toBe('exceeded') expect(subcommands()).not.toContain('merge-base') }) - - it('routes every probe to the caller-named WSL distro', async () => { - answerProbes('1\n') - - await measureRetargetDivergence('/repo', 'refs/heads/main', 'refs/remotes/origin/main', { - wslDistro: 'Ubuntu' - }) - - for (const options of callOptions()) { - expect(options).toMatchObject({ cwd: '/repo', wslDistro: 'Ubuntu' }) - } - }) }) diff --git a/src/main/github/project-view/project-view-config.test.ts b/src/main/github/project-view/project-view-config.test.ts index 4fcc57c00d8..1f1fb010b5c 100644 --- a/src/main/github/project-view/project-view-config.test.ts +++ b/src/main/github/project-view/project-view-config.test.ts @@ -183,12 +183,4 @@ describe('finalizeView verticalGroupByFields normalization', () => { ]) } }) - - it('omits the key entirely when the host never sent it (wire-compat shape)', () => { - const finalized = finalizeView(base, []) - expect(finalized.ok).toBe(true) - if (finalized.ok) { - expect('verticalGroupByFields' in finalized.view).toBe(false) - } - }) }) diff --git a/src/main/grok/grok-hook-config-file.test.ts b/src/main/grok/grok-hook-config-file.test.ts index 592bb21c485..d18bcf9464a 100644 --- a/src/main/grok/grok-hook-config-file.test.ts +++ b/src/main/grok/grok-hook-config-file.test.ts @@ -43,15 +43,6 @@ describe('guarded Grok hook config mutation', () => { expect(readFileSync(configPath, 'utf8')).toBe(userEdit) }) - it('treats a newer user deletion as a generation mismatch', async () => { - const configPath = makeConfigPath() - const installed = '{"hooks":{"SessionStart":[]}}\n' - writeFileSync(configPath, installed) - rmSync(configPath) - - await expect(removeGrokHookConfigIfUnchanged(configPath, installed)).resolves.toBe(false) - }) - it('does not overwrite a newer user edit', async () => { const configPath = makeConfigPath() const installed = '{"hooks":{"SessionStart":[]}}\n' @@ -152,36 +143,6 @@ describe('guarded Grok hook config mutation', () => { expect(statSync(configPath).mode & 0o777).toBe(0o600) }) - // Grok rejects global hook files with nlink !=1; publishing through a hard link creates a session-failure window. - it('never publishes the config through a second hard link', async () => { - const configPath = makeConfigPath() - const installed = '{"hooks":{"SessionStart":[]}}\n' - writeFileSync(configPath, installed) - - const linkCounts: number[] = [] - const sampler = setInterval(() => { - try { - linkCounts.push(statSync(configPath).nlink) - } catch { - /* absent mid-swap is fine; only a second link is not */ - } - }, 0) - try { - for (let i = 0; i < 40; i += 1) { - const next = `{"hooks":{"SessionStart":[],"n":${i}}}\n` - await expect( - writeGrokHookConfigIfUnchanged(configPath, readFileSync(configPath, 'utf8'), next) - ).resolves.toBe(true) - } - } finally { - clearInterval(sampler) - } - - expect(linkCounts.length).toBeGreaterThan(0) - expect(Math.max(...linkCounts)).toBe(1) - expect(statSync(configPath).nlink).toBe(1) - }) - it('removes the config and reports the removal', async () => { const configPath = makeConfigPath() const installed = '{"hooks":{"SessionStart":[]}}\n' diff --git a/src/main/ipc/workspace-cleanup-execution-host-routing.test.ts b/src/main/ipc/workspace-cleanup-execution-host-routing.test.ts index eddfb0702e5..6b623447e3d 100644 --- a/src/main/ipc/workspace-cleanup-execution-host-routing.test.ts +++ b/src/main/ipc/workspace-cleanup-execution-host-routing.test.ts @@ -202,16 +202,4 @@ describe('workspace cleanup execution-host routing', () => { expect(result.candidates[0]?.blockers).toContain('git-status-error') expect(result.candidates[0]?.executionHostId).toBe('ssh:alpha') }) - - it('still routes a genuinely local repo to this machine', async () => { - const store = makeStore([makeRepo({ path: '/local/repo' })], { - [WORKTREE_ID]: makeMeta() - }) - - const result = await scanWorkspaceCleanup(store) - - expect(listRepoWorktreesMock).toHaveBeenCalled() - expect(getStatusMock).toHaveBeenCalled() - expect(result.candidates[0]?.executionHostId).toBe('local') - }) }) diff --git a/src/main/managed-server/install/orcad-remote-node-runtime-host-fallback.test.ts b/src/main/managed-server/install/orcad-remote-node-runtime-host-fallback.test.ts index 42087af1588..a07c4c4cf54 100644 --- a/src/main/managed-server/install/orcad-remote-node-runtime-host-fallback.test.ts +++ b/src/main/managed-server/install/orcad-remote-node-runtime-host-fallback.test.ts @@ -1,7 +1,5 @@ import type * as ExecCommandModule from '../../ssh/remote-exec/remote-exec-command' -import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' + import { afterEach, describe, expect, it, vi } from 'vitest' import { ensureRemoteOrcadNodeRuntime } from './orcad-remote-node-runtime' import { @@ -11,14 +9,9 @@ import { import { execCommand } from '../../ssh/remote-exec/remote-exec-command' import { uploadRemoteInstallDirectory } from './remote-install-transfers' import { getRemoteHostPlatform } from '../../ssh/ssh-remote-platform' -import { pinnedNodeRuntimeAsset } from '../../../shared/node-runtime-pin' -import { DesktopDownloadUnreachableError } from '../../node-runtime/pinned-runtime-materializer' -import { setNodeDistMirrorSettingReader } from '../../node-runtime/node-dist-mirror-setting' -import { - HOST_NODE_DOWNLOAD_FAILED, - HOST_NODE_DOWNLOAD_OK, - NodeRuntimeUnreachableError -} from './remote-node-runtime-host-download' + +import { HOST_NODE_DOWNLOAD_OK } from './remote-node-runtime-host-download' + import { unusedOrcadHostTransport } from '../../ssh/unused-host-transport.test-fixture' vi.mock('../../ssh/remote-exec/remote-exec-command', async (importOriginal) => ({ @@ -33,7 +26,6 @@ const conn = unusedOrcadHostTransport() const host = getRemoteHostPlatform('linux-x64') const TARGET = 'linux-x64-glibc' const OFFLINE_PATH = '/Users/me/Library/Application Support/Orca/orcad-artifacts/node/archives/x' -const refused = (): Promise => Promise.reject(new Error('net::ERR_CONNECTION_REFUSED')) afterEach(() => { vi.mocked(execCommand).mockReset() @@ -72,18 +64,6 @@ function ensure(archivePath: () => Promise, signal?: AbortSignal) { } describe('pinned Node when this computer cannot download it', () => { - it('has the host download it and promotes that copy, with nothing uploaded', async () => { - const commands = freshHost(HOST_NODE_DOWNLOAD_OK) - - const result = await ensure(refused) - - expect(result.transfer).toBe('host-downloaded') - expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled() - const promote = commands.find((command) => command.includes('tar -xzf')) - expect(promote).toContain(pinnedNodeRuntimeAsset(TARGET).archive) - expect(promote).toContain(pinnedNodeRuntimeAsset(TARGET).executableSha256) - }) - it('also tries the host when the desktop got a file that failed the pin', async () => { freshHost(HOST_NODE_DOWNLOAD_OK) const blockPage = () => Promise.reject(new Error('Node archive checksum mismatch')) @@ -91,68 +71,6 @@ describe('pinned Node when this computer cannot download it', () => { await expect(ensure(blockPage)).resolves.toMatchObject({ transfer: 'host-downloaded' }) }) - it('names both attempts and the offline copy path when the host cannot download either', async () => { - freshHost( - `${HOST_NODE_DOWNLOAD_FAILED}\nORCA_RUNTIME_EXIT=7\ncurl: (7) Failed to connect to nodejs.org` - ) - - const failure = await ensure(refused).catch((error: unknown) => error) - - expect(failure).toBeInstanceOf(NodeRuntimeUnreachableError) - const message = String(failure) - expect(message).toContain('net::ERR_CONNECTION_REFUSED') - expect(message).toContain('curl: (7) Failed to connect to nodejs.org') - expect(message).toContain(OFFLINE_PATH) - expect(message).toContain('Settings > Advanced > Network > Node.js download mirror') - expect(message).toContain("NODEJS_ORG_MIRROR in this computer's environment") - expect(failure).toMatchObject({ mirror: 'unset' }) - expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled() - }) - - it('records that both attempts went through the configured mirror', async () => { - freshHost(`${HOST_NODE_DOWNLOAD_FAILED}\nmirror: curl exit 7`) - setNodeDistMirrorSettingReader(() => 'https://npmmirror.com/mirrors/node') - try { - const failure = await ensure(refused).catch((error: unknown) => error) - expect(failure).toMatchObject({ name: 'NodeRuntimeUnreachableError', mirror: 'tried' }) - expect(String(failure)).toContain('check the mirror of nodejs.org/dist in Settings') - } finally { - setNodeDistMirrorSettingReader(null) - } - }) - - it('offers no mirror for a build nodejs.org/dist does not carry', async () => { - freshHost(`${HOST_NODE_DOWNLOAD_FAILED}\nnodejs.org: curl exit 7`) - const failure = await ensureRemoteOrcadNodeRuntime({ - conn, - host: getRemoteHostPlatform('linux-arm64'), - slotDir: '/home/me/.orca-remote/orcad-0.1.0+abc', - target: 'linux-arm64-musl', - archivePath: refused - }).catch((error: unknown) => error) - expect(failure).toMatchObject({ name: 'NodeRuntimeUnreachableError', mirror: 'unsupported' }) - expect(String(failure)).not.toContain('Node.js download mirror') - }) - - it("quotes only this computer's short reason, not its standalone advice", async () => { - freshHost(`${HOST_NODE_DOWNLOAD_FAILED}\nnodejs.org: curl exit 7`) - const desktop = () => - Promise.reject( - new DesktopDownloadUnreachableError( - "This computer couldn't download Node ... so the host doesn't need internet access", - 'https://nodejs.org/dist/x: net::ERR_CONNECTION_REFUSED', - new Error('net::ERR_CONNECTION_REFUSED') - ) - ) - - const message = String(await ensure(desktop).catch((error: unknown) => error)) - - expect(message).toContain( - 'This computer: https://nodejs.org/dist/x: net::ERR_CONNECTION_REFUSED.' - ) - expect(message).not.toContain("the host doesn't need internet access") - }) - it('does not try the host once the connect was cancelled', async () => { const commands = freshHost(HOST_NODE_DOWNLOAD_OK) const controller = new AbortController() @@ -164,18 +82,4 @@ describe('pinned Node when this computer cannot download it', () => { await expect(ensure(cancelled, controller.signal)).rejects.toThrow('aborted') expect(commands.some((command) => command.includes('command -v curl'))).toBe(false) }) - - it('still uploads the desktop copy when there is one', async () => { - const commands = freshHost(HOST_NODE_DOWNLOAD_OK) - const cache = mkdtempSync(join(tmpdir(), 'orca-runtime-cache-')) - const archive = join(cache, pinnedNodeRuntimeAsset(TARGET).archive) - writeFileSync(archive, 'archive') - try { - await expect(ensure(async () => archive)).resolves.toMatchObject({ transfer: 'uploaded' }) - expect(uploadRemoteInstallDirectory).toHaveBeenCalledTimes(1) - expect(commands.some((command) => command.includes('command -v curl'))).toBe(false) - } finally { - rmSync(cache, { recursive: true, force: true }) - } - }) }) diff --git a/src/main/orcad/orcad-cli-launcher.test.ts b/src/main/orcad/orcad-cli-launcher.test.ts index b4994efb70c..9a01ee0320f 100644 --- a/src/main/orcad/orcad-cli-launcher.test.ts +++ b/src/main/orcad/orcad-cli-launcher.test.ts @@ -103,22 +103,6 @@ describe('orcad profile CLI launcher', () => { } ) - it.skipIf(process.platform === 'win32')( - 'refreshes the launcher when the host switches server slots', - async () => { - await writeCli() - await prepareOrcadCliLauncher() - roots.install = join(directory, 'updated server') - await writeCli() - await prepareOrcadCliLauncher() - const launcher = getOrcadCliLauncherPath() - if (!launcher) { - throw new Error('CLI launcher is missing') - } - expect(await readFile(launcher, 'utf8')).toContain(roots.install) - } - ) - it('leaves old slots without a CLI usable and clears a previous launcher', async () => { await writeCli() await prepareOrcadCliLauncher() @@ -127,24 +111,6 @@ describe('orcad profile CLI launcher', () => { expect(getOrcadCliLauncherPath()).toBeNull() }) - describe('on a Linux host', () => { - beforeEach(() => { - Object.defineProperty(process, 'platform', { configurable: true, value: 'linux' }) - }) - - it('refreshes both CLI names when the runtime switches server slots', async () => { - await writeCli() - await prepareOrcadCliLauncher() - roots.install = join(directory, 'updated server') - await writeCli() - await prepareOrcadCliLauncher() - const binDir = join(roots.profile, 'cli', 'bin') - const script = await readFile(join(binDir, 'orca-ide'), 'utf8') - expect(script).toContain(roots.install) - expect(script).toBe(await readFile(join(binDir, 'orca'), 'utf8')) - }) - }) - describe('on a Windows host', () => { beforeEach(() => { Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) @@ -192,11 +158,5 @@ describe('orcad profile CLI launcher', () => { expect(await readFile(join(binDir(), 'orca.exe'), 'utf8')).toBe('native launcher v2') expect(await readConfig()).toContain(`cli=${join(roots.install, 'out', 'cli', 'index.js')}`) }) - - it('leaves a slot without the native launcher with no orca command', async () => { - await writeCli() - await prepareOrcadCliLauncher() - expect(getOrcadCliLauncherPath()).toBeNull() - }) }) }) diff --git a/src/main/rate-limits/service-zcode-usage.test.ts b/src/main/rate-limits/service-zcode-usage.test.ts index 1a775d67cca..6c30a638fcc 100644 --- a/src/main/rate-limits/service-zcode-usage.test.ts +++ b/src/main/rate-limits/service-zcode-usage.test.ts @@ -8,7 +8,6 @@ import { RateLimitService } from './service' import { fetchClaudeRateLimits } from './claude-fetcher' import { fetchCodexRateLimits } from './codex-fetcher' import { fetchZcodeRateLimits } from './zcode-usage-fetcher' -import { hasZcodePlanApiKey } from '../zcode/zcode-plan-api-key-store' import { deferred, okProvider, @@ -88,39 +87,6 @@ describe('RateLimitService zcode plan credentials', () => { vi.mocked(fetchCodexRateLimits).mockResolvedValue(okProvider('codex', 20)) }) - it('fetches zcode with the site-resolved plan credential when a resolver is set', async () => { - const service = new RateLimitService() - service.setZcodePlanConfigResolver(() => ({ site: 'bigmodel', apiKey: 'glm-key' })) - vi.mocked(hasZcodePlanApiKey).mockReturnValue(true) - vi.mocked(fetchZcodeRateLimits).mockResolvedValueOnce(okProvider('zcode', 33, Date.now())) - - await service.refresh() - - expect(fetchZcodeRateLimits).toHaveBeenCalledTimes(1) - expect(fetchZcodeRateLimits).toHaveBeenCalledWith({ - signal: expect.any(AbortSignal), - planCredential: { apiKey: 'glm-key', baseUrl: 'https://open.bigmodel.cn' } - }) - const state = service.getState() - expect(state.zcode?.status).toBe('ok') - expect(state.zcode?.session?.usedPercent).toBe(33) - expect(state.zcodePlanApiKeyConfigured).toBe(true) - }) - - it('passes no plan credential while no key is saved and still fetches via the CLI config', async () => { - const service = new RateLimitService() - service.setZcodePlanConfigResolver(() => ({ site: 'zai', apiKey: '' })) - vi.mocked(fetchZcodeRateLimits).mockResolvedValueOnce(okProvider('zcode', 12, Date.now())) - - await service.refresh() - - expect(fetchZcodeRateLimits).toHaveBeenCalledWith({ - signal: expect.any(AbortSignal), - planCredential: null - }) - expect(service.getState().zcode?.session?.usedPercent).toBe(12) - }) - it('surfaces a resolver failure as a zcode-only error without fetching', async () => { const service = new RateLimitService() service.setZcodePlanConfigResolver(() => { diff --git a/src/main/runtime/orca-runtime-files-terminal-artifact-grants.test.ts b/src/main/runtime/orca-runtime-files-terminal-artifact-grants.test.ts index 862ac9693c4..497d1ce01ea 100644 --- a/src/main/runtime/orca-runtime-files-terminal-artifact-grants.test.ts +++ b/src/main/runtime/orca-runtime-files-terminal-artifact-grants.test.ts @@ -2,7 +2,8 @@ import { describe, expect, it, vi } from 'vitest' import { mkdtemp, realpath, symlink, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { enoent, resolveAuthorizedPathMock, statMock } from './orca-runtime-files-mock-registry' +import { resolveAuthorizedPathMock, statMock } from './orca-runtime-files-mock-registry' + import { createRuntimeFileCommands, useRuntimeFileCommandsLifecycle @@ -38,34 +39,6 @@ describe('RuntimeFileCommands', () => { describe('resolveTerminalPath', () => { const { tempDirs, tempFile } = useTerminalArtifactTempFiles() - it('returns an absolute open target for an existing local temp path outside the worktree', async () => { - const artifactPath = await tempFile('result.json', '{}') - const { commands } = createRuntimeFileCommands({ path: '/repo' }) - resolveAuthorizedPathMock.mockImplementation(async (p: string) => p) - statMock.mockResolvedValue({ isDirectory: () => false, size: 12, dev: 1, ino: 2, mtimeMs: 3 }) - const canonicalPath = await realpath(artifactPath) - - const result = await resolveTerminalArtifactPath(commands, artifactPath) - - expect(result).toMatchObject({ - worktree: 'wt-1', - relativePath: null, - absolutePath: canonicalPath, - exists: true, - isDirectory: false, - openTarget: { - kind: 'absolute-file', - provider: 'local', - absolutePath: canonicalPath - } - }) - expect(result.openTarget?.kind === 'absolute-file' ? result.openTarget.grantId : '').toMatch( - /\S/ - ) - expect(resolveAuthorizedPathMock).not.toHaveBeenCalledWith(canonicalPath, expect.anything()) - expect(statMock).not.toHaveBeenCalled() - }) - // Why: a chat link or typed path carries no terminal; the caller's terminal access is the authority. it('grants a writable file outside every workspace with no terminal or chat context', async () => { const artifactPath = await tempFile('handoff.md', '# handoff') @@ -108,16 +81,6 @@ describe('RuntimeFileCommands', () => { ).resolves.toEqual({ ok: true }) }) - it('reports a missing file outside every workspace as absent', async () => { - const { commands } = createRuntimeFileCommands({ path: '/repo' }) - const missingPath = join(tmpdir(), `orca-missing-${process.pid}-${Date.now()}.md`) - - const result = await commands.resolveTerminalPath('id:wt-1', missingPath, null, 'client-a') - - expect(result).toMatchObject({ relativePath: null, exists: false }) - expect(result.openTarget).toBeUndefined() - }) - // Why: this machine's home is the Windows user's, not the distro user's. it('refuses tilde paths when the workspace runs in WSL', async () => { Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) @@ -157,24 +120,6 @@ describe('RuntimeFileCommands', () => { ).rejects.toThrow('Not a regular file') }) - it('resolves a relative path against the terminal cwd, not a stale client cwd', async () => { - const dir = await mkdtemp(join(tmpdir(), 'orca-terminal-artifact-')) - tempDirs.push(dir) - await writeFile(join(dir, 'result.json'), '{}') - const { commands } = createRuntimeFileCommands({ path: '/repo' }) - resolveAuthorizedPathMock.mockImplementation(async (p: string) => p) - statMock.mockRejectedValue(enoent()) - - const result = await resolveTerminalArtifactPath(commands, 'result.json', dir) - - expect(result).toMatchObject({ - relativePath: 'result.json', - absolutePath: '/repo/result.json', - exists: false - }) - expect(result.openTarget).toBeUndefined() - }) - it('uses the canonical local temp artifact path for the exact grant', async () => { const dir = await mkdtemp(join(tmpdir(), 'orca-terminal-artifact-')) tempDirs.push(dir) diff --git a/src/main/runtime/restored-pane-hook-store-resume.test.ts b/src/main/runtime/restored-pane-hook-store-resume.test.ts index 770700c32e9..7ad510307df 100644 --- a/src/main/runtime/restored-pane-hook-store-resume.test.ts +++ b/src/main/runtime/restored-pane-hook-store-resume.test.ts @@ -87,13 +87,6 @@ async function restore(runtime: InstanceType): Promis } describe('restoring a pane its host recorded an agent in', () => { - it('resumes the hook store’s conversation instead of replaying the launch command', async () => { - const { runtime, spawn } = makeRuntime([codexRow()], 'codex') - await restore(runtime) - expect(spawn).toHaveBeenCalledOnce() - expect(spawn.mock.calls[0]![0].command).toMatch(RESUMES) - }) - it('resumes an agent typed into a shell, which no launch command names', async () => { const { runtime, spawn } = makeRuntime([codexRow()]) await restore(runtime) @@ -159,22 +152,6 @@ describe('restoring a pane its host recorded an agent in', () => { expectFreshLaunch(spawn.mock.calls[0]![0].command) }) - it('launches the tab’s agent fresh when another pane holds the conversation live', async () => { - const otherPaneKey = 'other-tab:44444444-4444-4444-8444-444444444444' - const { runtime, spawn } = makeRuntime( - [codexRow(), codexRow({ paneKey: otherPaneKey })], - 'codex', - { presence: 'live' } - ) - vi.spyOn( - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the runtime's protected paneHoldsSession; the other pane stands in for a bound live terminal. - runtime as unknown as { paneHoldsSession: (paneKey: string) => boolean }, - 'paneHoldsSession' - ).mockImplementation((paneKey) => paneKey === otherPaneKey) - await restore(runtime) - expectFreshLaunch(spawn.mock.calls[0]![0].command) - }) - it('falls back to the tab’s agent launch when the newest row is a structured host’s', async () => { const { runtime, spawn } = makeRuntime([codexRow({ structuredHost: 'owned' })], 'codex') await restore(runtime) diff --git a/src/main/runtime/rpc/methods/ssh.test.ts b/src/main/runtime/rpc/methods/ssh.test.ts index f05d88317d7..0897214cb19 100644 --- a/src/main/runtime/rpc/methods/ssh.test.ts +++ b/src/main/runtime/rpc/methods/ssh.test.ts @@ -29,60 +29,6 @@ function makeRequest(method: string, params?: unknown): RpcRequest { } describe('ssh RPC methods', () => { - it('returns the registered SSH target state', async () => { - const state = { - targetId: 'ssh-1', - status: 'connected', - error: null, - reconnectAttempt: 0 - } - getRegisteredSshStateMock.mockReturnValueOnce(state) - const runtime = { getRuntimeId: () => 'test-runtime' } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: SSH_METHODS }) - - const response = await dispatcher.dispatch(makeRequest('ssh.getState', { targetId: 'ssh-1' })) - - expect(getRegisteredSshStateMock).toHaveBeenCalledWith('ssh-1') - expect(response).toMatchObject({ ok: true, result: { state } }) - }) - - it('connects through the registered desktop SSH lifecycle', async () => { - const state = { - targetId: 'ssh-1', - status: 'connected', - error: null, - reconnectAttempt: 0 - } - connectRegisteredSshTargetMock.mockResolvedValueOnce(state) - const runtime = { getRuntimeId: () => 'test-runtime' } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: SSH_METHODS }) - - const response = await dispatcher.dispatch(makeRequest('ssh.connect', { targetId: 'ssh-1' })) - - expect(connectRegisteredSshTargetMock).toHaveBeenCalledWith('ssh-1', { automatic: false }) - expect(response).toMatchObject({ ok: true, result: { state } }) - }) - - it("keeps a paired client's automatic connect from lifting a cancelled sign-in", async () => { - connectRegisteredSshTargetMock.mockResolvedValueOnce(null) - const runtime = { getRuntimeId: () => 'test-runtime' } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: SSH_METHODS }) - - await dispatcher.dispatch(makeRequest('ssh.connect', { targetId: 'ssh-1', automatic: true })) - - expect(connectRegisteredSshTargetMock).toHaveBeenCalledWith('ssh-1', { automatic: true }) - }) - - it('returns null when the target has no registered state yet', async () => { - getRegisteredSshStateMock.mockReturnValueOnce(undefined) - const runtime = { getRuntimeId: () => 'test-runtime' } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: SSH_METHODS }) - - const response = await dispatcher.dispatch(makeRequest('ssh.getState', { targetId: 'ssh-1' })) - - expect(response).toMatchObject({ ok: true, result: { state: null } }) - }) - it('redacts HUB-private diagnostics from state and connect failures', async () => { const privateMessage = 'identity /Users/hub/.ssh/private via bastion.internal failed' getRegisteredSshStateMock.mockReturnValue({ @@ -152,37 +98,6 @@ describe('ssh RPC methods', () => { expect(JSON.stringify(response)).not.toContain('bastion') }) - it('does not invent a platform before the SSH host has been detected', async () => { - listRegisteredSshTargetsMock.mockReturnValueOnce([{ id: 'ssh-1', label: 'Dev box' }]) - getRegisteredSshStateMock.mockReturnValueOnce(undefined) - const runtime = { getRuntimeId: () => 'test-runtime' } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: SSH_METHODS }) - - const response = await dispatcher.dispatch(makeRequest('ssh.listTargetSummaries')) - - expect(response).toMatchObject({ - ok: true, - result: { targets: [{ id: 'ssh-1', label: 'Dev box' }] } - }) - expect(JSON.stringify(response)).not.toContain('remotePlatform') - }) - - it('reports disconnected lifecycle states without calling them connected', async () => { - listRegisteredSshTargetsMock.mockReturnValueOnce([{ id: 'ssh-1', label: 'Dev box' }]) - getRegisteredSshStateMock.mockReturnValueOnce({ status: 'reconnecting' }) - const runtime = { getRuntimeId: () => 'test-runtime' } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: SSH_METHODS }) - - const response = await dispatcher.dispatch(makeRequest('ssh.listTargetSummaries')) - - expect(response).toMatchObject({ - ok: true, - result: { - targets: [{ id: 'ssh-1', connected: false, connectionStatus: 'reconnecting' }] - } - }) - }) - it('redacts the legacy target response for older clients', async () => { const targets = [ { @@ -209,15 +124,4 @@ describe('ssh RPC methods', () => { expect(JSON.stringify(response)).not.toContain('/secret/key') expect(JSON.stringify(response)).not.toContain('bastion') }) - - it('lists removed-target labels for ghost-host display on paired clients', async () => { - const labels = { 'ssh-old': 'Dev box' } - listRegisteredRemovedSshTargetLabelsMock.mockReturnValueOnce(labels) - const runtime = { getRuntimeId: () => 'test-runtime' } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: SSH_METHODS }) - - const response = await dispatcher.dispatch(makeRequest('ssh.listRemovedTargetLabels')) - - expect(response).toMatchObject({ ok: true, result: { labels } }) - }) }) diff --git a/src/main/runtime/rpc/terminal-geometry-stale-handle.test.ts b/src/main/runtime/rpc/terminal-geometry-stale-handle.test.ts deleted file mode 100644 index d05194477ab..00000000000 --- a/src/main/runtime/rpc/terminal-geometry-stale-handle.test.ts +++ /dev/null @@ -1,213 +0,0 @@ -import './unused-default-rpc-methods.test-fixture' -import { describe, expect, it, vi } from 'vitest' -import { RpcDispatcher } from './dispatcher' -import type { RpcRequest } from './core' -import type { OrcaRuntimeService } from '../orca-runtime' -import { TERMINAL_METHODS } from './methods/terminal' - -// Geometry RPCs must use resolveLiveLeafForHandle: an unguarded stale handle resolves the replacement PTY and corrupts its display (#7718). - -// Model stale resolution: unguarded lookup returns replacement pty-b; guarded lookup refuses. -const NEW_PTY_UNDER_PANE = 'pty-b' - -function stubStaleHandleRuntime(overrides: Partial = {}): OrcaRuntimeService { - return { - getRuntimeId: () => 'test-runtime', - resolveLeafForHandle: vi.fn().mockReturnValue({ ptyId: NEW_PTY_UNDER_PANE }), - resolveLiveLeafForHandle: vi.fn(() => { - throw new Error('terminal_handle_stale') - }), - ...overrides - } as unknown as OrcaRuntimeService -} - -function makeRequest(method: string, params?: unknown): RpcRequest { - return { id: 'req-1', authToken: 'tok', method, params } -} - -async function expectStale(method: string, params: unknown, mutators: string[]): Promise { - const spies: Record> = {} - for (const name of mutators) { - spies[name] = vi.fn() - } - const runtime = stubStaleHandleRuntime(spies as Partial) - const dispatcher = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }) - - const response = await dispatcher.dispatch(makeRequest(method, params)) - - expect(response.ok).toBe(false) - if (response.ok) { - throw new Error(`expected ${method} to reject a stale handle`) - } - expect(response.error.message).toContain('terminal_handle_stale') - for (const name of mutators) { - expect(spies[name]).not.toHaveBeenCalled() - } -} - -describe('terminal geometry family rejects stale handles instead of mutating the wrong PTY', () => { - it('terminal.resizeForClient fails with terminal_handle_stale', async () => { - await expectStale( - 'terminal.resizeForClient', - { terminal: 'stale-terminal', mode: 'restore', clientId: 'client-1' }, - ['resizeForClient'] - ) - }) - - it('terminal.setDisplayMode fails with terminal_handle_stale', async () => { - await expectStale( - 'terminal.setDisplayMode', - { - terminal: 'stale-terminal', - mode: 'auto', - client: { id: 'client-1', type: 'mobile' }, - viewport: { cols: 80, rows: 24 } - }, - [ - 'setMobileDisplayMode', - 'applyMobileDisplayMode', - 'updateMobileSubscriberViewport', - 'markMobileActor' - ] - ) - }) - - it('terminal.restoreFit fails with terminal_handle_stale', async () => { - await expectStale('terminal.restoreFit', { terminal: 'stale-terminal' }, [ - 'reclaimTerminalForDesktop' - ]) - }) - - it('terminal.updateViewport fails with terminal_handle_stale', async () => { - await expectStale( - 'terminal.updateViewport', - { - terminal: 'stale-terminal', - client: { id: 'client-1', type: 'mobile' }, - viewport: { cols: 80, rows: 24 } - }, - ['updateMobileViewport', 'updateDesktopViewport'] - ) - }) -}) - -describe('terminal geometry family still mutates the live PTY for a fresh handle', () => { - it('terminal.restoreFit reclaims the resolved PTY when the handle is live', async () => { - const reclaimTerminalForDesktop = vi.fn().mockResolvedValue(true) - const runtime = { - getRuntimeId: () => 'test-runtime', - resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: 'pty-a' }), - reclaimTerminalForDesktop - } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }) - - const response = await dispatcher.dispatch( - makeRequest('terminal.restoreFit', { terminal: 'live-terminal' }) - ) - - expect(response.ok).toBe(true) - if (!response.ok) { - throw new Error(response.error.message) - } - expect(response.result).toEqual({ restored: true }) - expect(reclaimTerminalForDesktop).toHaveBeenCalledWith('pty-a') - }) - - it('terminal.resizeForClient resizes the resolved PTY when the handle is live', async () => { - const resizeForClient = vi.fn().mockResolvedValue({ cols: 80, rows: 24 }) - const runtime = { - getRuntimeId: () => 'test-runtime', - resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: 'pty-a' }), - resizeForClient - } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }) - - const response = await dispatcher.dispatch( - makeRequest('terminal.resizeForClient', { - terminal: 'live-terminal', - mode: 'restore', - clientId: 'client-1' - }) - ) - - expect(response.ok).toBe(true) - if (!response.ok) { - throw new Error(response.error.message) - } - expect(resizeForClient).toHaveBeenCalledWith( - 'pty-a', - 'restore', - 'client-1', - undefined, - undefined - ) - }) - - it('terminal.setDisplayMode mutates the resolved PTY when the handle is live', async () => { - const setMobileDisplayMode = vi.fn() - const applyMobileDisplayMode = vi.fn().mockResolvedValue(undefined) - const updateMobileSubscriberViewport = vi.fn() - const markMobileActor = vi.fn() - const runtime = { - getRuntimeId: () => 'test-runtime', - resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: 'pty-a' }), - setMobileDisplayMode, - applyMobileDisplayMode, - updateMobileSubscriberViewport, - markMobileActor, - getLayout: vi.fn().mockReturnValue({ seq: 42 }) - } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }) - - const response = await dispatcher.dispatch( - makeRequest('terminal.setDisplayMode', { - terminal: 'live-terminal', - mode: 'auto', - client: { id: 'client-1', type: 'mobile' }, - viewport: { cols: 80, rows: 24 } - }) - ) - - expect(response.ok).toBe(true) - if (!response.ok) { - throw new Error(response.error.message) - } - expect(updateMobileSubscriberViewport).toHaveBeenCalledWith('pty-a', 'client-1', { - cols: 80, - rows: 24 - }) - expect(markMobileActor).toHaveBeenCalledWith('pty-a', 'client-1') - expect(setMobileDisplayMode).toHaveBeenCalledWith('pty-a', 'auto') - expect(applyMobileDisplayMode).toHaveBeenCalledWith('pty-a') - expect(response.result).toEqual({ mode: 'auto', seq: 42 }) - }) - - it('terminal.updateViewport updates the resolved PTY when the handle is live', async () => { - const updateMobileViewport = vi.fn().mockResolvedValue({ updated: true, applied: true }) - const runtime = { - getRuntimeId: () => 'test-runtime', - resolveLiveLeafForHandle: vi.fn().mockReturnValue({ ptyId: 'pty-a' }), - updateMobileViewport, - getLayout: vi.fn().mockReturnValue({ seq: 7 }) - } as unknown as OrcaRuntimeService - const dispatcher = new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }) - - const response = await dispatcher.dispatch( - makeRequest('terminal.updateViewport', { - terminal: 'live-terminal', - client: { id: 'client-1', type: 'mobile' }, - viewport: { cols: 80, rows: 24 } - }) - ) - - expect(response.ok).toBe(true) - if (!response.ok) { - throw new Error(response.error.message) - } - expect(updateMobileViewport).toHaveBeenCalledWith('pty-a', 'client-1', { - cols: 80, - rows: 24 - }) - expect(response.result).toEqual({ updated: true, applied: true, seq: 7 }) - }) -}) diff --git a/src/main/runtime/structured-agent-session-mobile-attention.test.ts b/src/main/runtime/structured-agent-session-mobile-attention.test.ts index 3a93bb7fdfb..25b45850a2a 100644 --- a/src/main/runtime/structured-agent-session-mobile-attention.test.ts +++ b/src/main/runtime/structured-agent-session-mobile-attention.test.ts @@ -1,20 +1,17 @@ import { describe, expect, it, vi } from 'vitest' -import { - agentSessionAttentionKey, - type AgentSessionAttentionEdge, - type StructuredAttentionState +import type { + AgentSessionAttentionEdge, + StructuredAttentionState } from '../../shared/agent-session-attention' + import type { AgentSessionStatusSummary } from '../../shared/agent-session-wire' import type { NotificationSettings } from '../../shared/notification-settings-types' -import { - structuredAgentSessionPaneKey, - structuredAgentSessionTabId -} from '../../shared/structured-agent-session-projection' + import { createStructuredAttentionMobileDelivery, - readStructuredAttentionWorkspaceLabels, type StructuredAttentionMobileDeliveryDeps } from './structured-agent-session-mobile-attention' + import type { MobileNotificationDispatchEvent } from './runtime-mobile-notification-controller' const SCOPE = { @@ -87,41 +84,6 @@ function harness(overrides: Partial = {}) } describe('structured attention mobile delivery', () => { - it('pushes a raised prompt at once as "needs input", keyed by the prompt', () => { - const h = harness() - h.delivery.deliver(PROMPT, SUMMARY) - const key = agentSessionAttentionKey(PROMPT) - expect(h.sent).toEqual([ - { - type: 'notification', - emittedAt: 42, - source: 'agent-task-complete', - title: 'orca / feature - Claude needs input', - body: 'May I run the migration?', - worktreeId: SCOPE.workspaceId, - paneKey: structuredAgentSessionPaneKey( - structuredAgentSessionTabId('session-1'), - 'session-1' - ), - notificationId: key, - attentionKey: key, - agentState: 'blocked' - } - ]) - }) - - it('words a settled turn by its outcome, and a failure as a failure even while a prompt waits', () => { - const h = harness() - h.delivery.deliver(completion('success'), SUMMARY) - h.delivery.deliver(completion('success', true), SUMMARY) - h.delivery.deliver(completion('failure', true), SUMMARY) - expect(h.sent.map((event) => [event.agentState, event.title])).toEqual([ - ['done', 'orca / feature - Claude finished'], - ['blocked', 'orca / feature - Claude needs input'], - ['done', 'orca / feature - Claude failed'] - ]) - }) - it('sends nothing for a workspace whose notifications are muted', () => { const isWorkspaceMuted = vi.fn(() => true) const quiet = harness({ isWorkspaceMuted }) @@ -160,49 +122,4 @@ describe('structured attention mobile delivery', () => { h.delivery.deliver(edge, SUMMARY) expect(h.sent).toEqual([]) }) - - it('forwards current prompt state for reconciliation', () => { - const h = harness() - const state = { scope: SCOPE, sessionId: 'session-1', pendingPromptIds: ['a', 'b'] } - h.delivery.reconcile(state) - expect(h.reconciled).toEqual([state]) - }) -}) - -describe('readStructuredAttentionWorkspaceLabels', () => { - const store = { - getRepo: vi.fn((id: string) => (id === 'repo-1' ? { displayName: 'orca' } : undefined)), - getWorktreeMeta: vi.fn((id: string) => - id === SCOPE.workspaceId ? { displayName: 'feature' } : undefined - ), - getFolderWorkspaces: vi.fn(() => [{ id: 'folder-1', name: 'Notes' }]) - } - - it('reads a worktree and its repo from persisted metadata', () => { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: only the read fields are stubbed. - const labels = readStructuredAttentionWorkspaceLabels(store as never, SCOPE) - expect(labels).toEqual({ repoLabel: 'orca', worktreeLabel: 'feature' }) - }) - - it('reads a folder workspace from the folder key a chat scope carries', () => { - const labels = readStructuredAttentionWorkspaceLabels( - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: only the read fields are stubbed. - store as never, - { ...SCOPE, workspaceId: 'folder:folder-1', workspaceKind: 'folder' } - ) - expect(labels).toEqual({ worktreeLabel: 'Notes' }) - }) -}) - -it('preserves the missing notification settings failure before dispatch', () => { - let reads = 0 - const h = harness({ - readNotificationSettings() { - reads += 1 - return undefined - } - }) - expect(() => h.delivery.deliver(PROMPT, SUMMARY)).toThrow(TypeError) - expect(reads).toBe(1) - expect(h.sent).toEqual([]) }) diff --git a/src/main/runtime/structured-worker-terminal-read.test.ts b/src/main/runtime/structured-worker-terminal-read.test.ts index 4e49e4cb428..196d4b9101b 100644 --- a/src/main/runtime/structured-worker-terminal-read.test.ts +++ b/src/main/runtime/structured-worker-terminal-read.test.ts @@ -122,12 +122,6 @@ describe('reading a structured worker through the terminal-read path', () => { expect(refusal).not.toContain('worker-read') }) - it('reports dropped history as truncated rather than pretending the page is whole', async () => { - const handle = registerWorker() - installHost({ items: [message('i1', 'tail only')], hasOlder: true }) - expect((await readStructuredWorkerTerminal({ handle, db: null }))?.truncated).toBe(true) - }) - it('redacts dispatch capability tokens the same way the archive path does', async () => { const handle = registerWorker() const token = `dcap_${'a'.repeat(32)}` diff --git a/src/main/server/serve-readiness.test.ts b/src/main/server/serve-readiness.test.ts index 18614c23e4a..24d4fa5b318 100644 --- a/src/main/server/serve-readiness.test.ts +++ b/src/main/server/serve-readiness.test.ts @@ -1,10 +1,10 @@ -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it } from 'vitest' + import { renderServeReadiness, ServeReadinessPublisher, type ServeReadiness } from './serve-readiness' -import type { OrcadHealth } from '../orcad/orcad-health' const ready: ServeReadiness = { runtimeId: 'runtime-1', @@ -22,81 +22,7 @@ const ready: ServeReadiness = { } } -const health: OrcadHealth = { - buildHash: 'abc123def4567890', - buildVersion: '1.4.0', - nodeVersion: '20.11.0', - nodeAbi: '115', - platform: 'linux', - arch: 'x64', - pid: 1234, - terminalDaemon: { - state: 'live', - ownsFreshSessions: true, - pid: 4242, - buildVersion: '1.4.0', - entryPath: '/opt/orcad/daemon-entry.js', - protocolVersion: 36, - cgroupUnit: null, - selfTest: { ok: true, coverage: 'pty-spawn', verdict: 'healthy', durationMs: 12 } - } -} - describe('ServeReadinessPublisher', () => { - it('writes one complete human-readable ready block', async () => { - const write = vi.fn(async () => {}) - const publisher = new ServeReadinessPublisher(write) - - await publisher.publish(ready, { mode: 'human' }) - - expect(write).toHaveBeenCalledOnce() - expect(write).toHaveBeenCalledWith( - expect.stringContaining( - 'Orca server ready\nBound endpoint: ws://0.0.0.0:6768\nAdvertised endpoint: wss://orca.example.test/runtime' - ) - ) - expect(write).toHaveBeenCalledWith( - expect.stringContaining('Pairing URL: orca://pair?code=secret\n') - ) - }) - - it('publishes a versioned JSON contract with explicit endpoints and pairing availability', () => { - expect(JSON.parse(renderServeReadiness(ready, { mode: 'json' }))).toEqual({ - type: 'orca_server_ready', - schemaVersion: 1, - runtimeId: 'runtime-1', - endpoint: 'ws://0.0.0.0:6768', - boundEndpoint: 'ws://0.0.0.0:6768', - advertisedEndpoint: 'wss://orca.example.test/runtime', - managedWslCliReconciliation: 'settled', - pairing: ready.pairing - }) - }) - - it('reports unavailable pairing as an explicit machine-readable object', () => { - const unavailable: ServeReadiness = { - ...ready, - advertisedEndpoint: null, - pairing: { - available: false, - reason: 'invalid_advertised_endpoint', - guidance: 'Use a reachable address.' - } - } - - const json = JSON.parse(renderServeReadiness(unavailable, { mode: 'json' })) - expect(json.pairing).toEqual(unavailable.pairing) - expect(renderServeReadiness(unavailable, { mode: 'human' })).toContain( - 'Pairing unavailable: invalid_advertised_endpoint\nPairing guidance: Use a reachable address.' - ) - }) - - it('preserves the recipe JSON contract', () => { - expect(renderServeReadiness(ready, { mode: 'recipe-json', projectRoot: '/workspace' })).toBe( - '{"schemaVersion":1,"pairingCode":"orca://pair?code=secret","projectRoot":"/workspace"}' - ) - }) - it('fails recipe output with the unavailable reason and guidance', () => { const unavailable: ServeReadiness = { ...ready, @@ -111,49 +37,6 @@ describe('ServeReadinessPublisher', () => { ).toThrow('websocket_unavailable. Choose an unused --port.') }) - it('omits the health block entirely when a host does not report one', () => { - const payload = JSON.parse(renderServeReadiness(ready, { mode: 'json' })) - // Absent health means unreported; it must not imply a green verdict. - expect('health' in payload).toBe(false) - expect(renderServeReadiness(ready, { mode: 'human' })).not.toContain('Terminal daemon') - }) - - it('carries build identity, Node ABI and the daemon self-test in the JSON contract', () => { - const payload = JSON.parse(renderServeReadiness({ ...ready, health }, { mode: 'json' })) - expect(payload.health.buildHash).toBe('abc123def4567890') - expect(payload.health.nodeAbi).toBe('115') - expect(payload.health.terminalDaemon.selfTest).toEqual({ - ok: true, - coverage: 'pty-spawn', - verdict: 'healthy', - durationMs: 12 - }) - }) - - it('says out loud when the daemon self-test failed', () => { - const failed: ServeReadiness = { - ...ready, - health: { - ...health, - terminalDaemon: { - ...health.terminalDaemon, - state: 'degraded', - ownsFreshSessions: false, - selfTest: { - ok: false, - coverage: 'pty-spawn', - verdict: 'pty-spawn-unhealthy', - durationMs: 3_000 - } - } - } - } - const human = renderServeReadiness(failed, { mode: 'human' }) - // Make failure explicit to operators rather than requiring inference from missing output. - expect(human).toContain('PTY self-test FAILED') - expect(human).toContain('terminals survive an orcad restart: NO') - }) - it('rejects concurrent and later duplicate publications', async () => { let finishWrite: (() => void) | undefined const publisher = new ServeReadinessPublisher( diff --git a/src/main/skills/skill-install-request-service.test.ts b/src/main/skills/skill-install-request-service.test.ts index 1f08fdcc1e1..195afa7112f 100644 --- a/src/main/skills/skill-install-request-service.test.ts +++ b/src/main/skills/skill-install-request-service.test.ts @@ -106,16 +106,6 @@ describe('executeSkillInstallRequest', () => { ).toEqual([]) }) - it('rejects local paths at the remote request boundary', async () => { - const { request, dependencies, archive } = await fixture() - await expect( - executeSkillInstallRequest( - { ...request, ingress: { kind: 'local-file', path: archive.archivePath } }, - dependencies - ) - ).rejects.toThrow('skill-install-local-ingress-rejected') - }) - it('rejects invalid shape, identity, scope, destination, and ingress policy before download', async () => { const { request, dependencies, archive, home } = await fixture() const cases: { input: unknown; error: string }[] = [ diff --git a/src/main/ssh/orcad-snapshot-room.test.ts b/src/main/ssh/orcad-snapshot-room.test.ts index a384b10b8f3..c0bac203e07 100644 --- a/src/main/ssh/orcad-snapshot-room.test.ts +++ b/src/main/ssh/orcad-snapshot-room.test.ts @@ -35,12 +35,9 @@ import { writeAtomicOrcadRemoteRecord } from '../managed-server/install/orcad-re import { deployOrcad } from './orcad-remote-deploy' import { getRemoteHostPlatform } from './ssh-remote-platform' import { ORCAD_SNAPSHOT_SPACE_INSUFFICIENT_CODE } from './orcad-snapshot-room' -import { isSnapshotCaptureCommand } from './orcad-snapshot-capture-command' + import { OrcadFenceLostError } from '../managed-server/activation/orcad-activation-fence-scope' -import { - emptyOrcadActivationRecord, - withActivatedVersion -} from '../managed-server/activation/orcad-activation-record' + import { FakeOrcadHost, NEW, @@ -114,44 +111,6 @@ describe('an update on a host without room for its snapshot (#30491)', () => { expect(host.fence).toBe(false) }) - it('counts free space and reclaimable snapshots apart in the refusal', async () => { - answerRoom(400_000, 300_000, 1_000) - - expect(JSON.stringify(await deploy())).toMatch( - /has 1000 KB free \(plus 293\.0 MB Orca would reclaim from earlier snapshots\)/u - ) - }) - - it('keeps the snapshot the activation record names through both the room check and the prune', async () => { - const recorded = 'pre-0.1.0+aa01-500' - host.record = JSON.stringify( - withActivatedVersion( - emptyOrcadActivationRecord(), - OLD, - { - dirName: recorded, - takenBeforeVersion: OLD, - readableByVersion: null, - takenAt: new Date(500).toISOString() - }, - new Date(0) - ) - ) - answerRoom(400_000, 0, 10_000_000) - - expect(await deploy()).toMatchObject({ outcome: 'installed-and-activated' }) - const commands = vi.mocked(execCommand).mock.calls.map(([, command]) => command) - expect(commands.find((command) => command.includes('SNAPSHOT_SPACE'))).toContain( - `('${recorded}')` - ) - expect(commands.find(isSnapshotCaptureCommand)).toMatch( - new RegExp( - `\\('pre-${NEW.replace('+', '\\+')}-\\d+'\\|'${recorded.replace('+', '\\+')}'\\)`, - 'u' - ) - ) - }) - it('measures room before the last terminal recount, so the scan cannot outdate it', async () => { answerRoom(400_000, 0, 10_000_000) const probedBeforeRecount: boolean[] = [] diff --git a/src/main/ssh/ssh-host-server-on-connect-held-move.test.ts b/src/main/ssh/ssh-host-server-on-connect-held-move.test.ts index 0f12d5f017e..fa29d0327e8 100644 --- a/src/main/ssh/ssh-host-server-on-connect-held-move.test.ts +++ b/src/main/ssh/ssh-host-server-on-connect-held-move.test.ts @@ -10,8 +10,6 @@ import { unservedHostServerMessage, unservedServerStatus } from './ssh-host-unse import { admitSshManagedServerStatus } from '../../shared/ssh-managed-server-status-admission' const target: SshTarget = { id: 'ssh-1', label: 'Box', host: 'box', port: 22, username: 'me' } -// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the orchestrator reads only the environment id. -const environment = { id: 'env-1' } as never function fencedHost(phase: 'source-fenced' | 'destination-staged', registered: boolean) { const host: { phase: string | null; registered: boolean } = { phase, registered } @@ -53,36 +51,6 @@ describe('a move to the managed server that stops short', () => { warn.mockRestore() }) - it('resumes the same move on the next connect once the server answers', async () => { - const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) - const { host, deps } = fencedHost('destination-staged', true) - const failing = deps({ - convert: vi.fn(async () => { - throw new Error('socket closed') - }), - abandonConversion: vi.fn(async () => { - throw new Error('socket closed') - }) - }) - await expect(resolveHostServerOnConnect(target, failing)).resolves.toMatchObject({ - held: true - }) - const convert = vi.fn(async () => { - host.phase = 'destination-committed' - return { outcome: 'converted' as const, environment, migrationId: 'm' } - }) - const next = deps({ convert }) - await expect(resolveHostServerOnConnect(target, next)).resolves.toEqual({ - route: 'managed', - environmentId: 'env-1' - }) - // Resumed through the registered server, without a fresh census or a second fence. - expect(next.ensureTunnel).toHaveBeenCalledWith('env-1') - expect(convert).toHaveBeenCalledWith(target, null) - expect(next.relayTerminals).not.toHaveBeenCalled() - warn.mockRestore() - }) - it('gives the host back, with no lock to report, when the server proves it holds nothing', async () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) const { host, deps } = fencedHost('destination-staged', true) @@ -138,39 +106,6 @@ describe('a move to the managed server that stops short', () => { warn.mockRestore() }) - it('finishes, rather than reports a lock, when the abort finds the move already committed', async () => { - const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) - const { host, deps } = fencedHost('destination-staged', true) - const lost = deps({ - convert: vi.fn(async () => { - throw new Error('orcad_migration_commit_reply_lost') - }), - // The abort's re-read found the commit landed and recorded it. - abandonConversion: vi.fn(async () => { - host.phase = 'destination-committed' - }) - }) - await expect(resolveHostServerOnConnect(target, lost)).resolves.not.toHaveProperty('held') - const next = deps() - await expect(resolveHostServerOnConnect(target, next)).resolves.toEqual({ - route: 'managed', - environmentId: 'env-1' - }) - expect(next.convert).not.toHaveBeenCalled() - expect(next.retainCommittedSource).toHaveBeenCalledWith(target) - warn.mockRestore() - }) - - it('keeps a refusal’s own guidance even while the host stays locked', () => { - const decision = { - route: 'unserved' as const, - reason: 'refused' as const, - detail: 'remove that project from this desktop and connect again', - held: true as const - } - expect(unservedHostServerMessage(decision)).toBe(decision.detail) - }) - it('carries the lock to the renderer, and drops anything but a literal true', () => { expect(admitSshManagedServerStatus({ kind: 'relay', reason: 'failed', held: true })).toEqual({ managedServer: { kind: 'relay', reason: 'failed', held: true } diff --git a/src/main/window/clipboard-runtime-owned-ssh-paste.test.ts b/src/main/window/clipboard-runtime-owned-ssh-paste.test.ts index 08afc1e2585..41766e80f5b 100644 --- a/src/main/window/clipboard-runtime-owned-ssh-paste.test.ts +++ b/src/main/window/clipboard-runtime-owned-ssh-paste.test.ts @@ -168,34 +168,10 @@ describe('clipboard image paste for a runtime-owned SSH workspace', () => { expect(fsWriteFileMock).not.toHaveBeenCalled() }) - it('keeps a runtime-host paste (no SSH target) addressed to the runtime itself', async () => { - mockRuntimeUpload() - - await expect( - saveImageHandler()(rendererEvent, { runtimeEnvironmentId: RUNTIME_ID }) - ).resolves.toBe('/tmp/on-runtime-target.png') - - expect(runtimeCall('clipboard.startImageUpload')?.[3]).toEqual({ - expectedBase64Length: PNG.toString('base64').length, - connectionId: null - }) - }) - it('reports the dropped-connection verdict for a client-dialed SSH target', async () => { await expect( saveImageHandler()(rendererEvent, { connectionId: CLIENT_SSH_TARGET }) ).rejects.toThrow(SSH_HOST_NOT_SERVED_MESSAGE) expect(callRuntimeEnvironmentMock).not.toHaveBeenCalled() }) - - it('keeps a plain local paste on the local temp dir', async () => { - fsWriteFileMock.mockResolvedValue(undefined) - - await expect(saveImageHandler()(rendererEvent, undefined)).resolves.toMatch( - /orca-paste-.*\.png$/ - ) - - expect(fsWriteFileMock).toHaveBeenCalledTimes(1) - expect(callRuntimeEnvironmentMock).not.toHaveBeenCalled() - }) }) diff --git a/src/main/worktree-retirement-discovery-wsl.test.ts b/src/main/worktree-retirement-discovery-wsl.test.ts index 7839ba6f584..4e17063b171 100644 --- a/src/main/worktree-retirement-discovery-wsl.test.ts +++ b/src/main/worktree-retirement-discovery-wsl.test.ts @@ -42,35 +42,6 @@ describe('retirement discovery on WSL', () => { vi.restoreAllMocks() }) - it('admits every WSL UNC listing through the bounded filesystem gate', async () => { - await discoverRetiredWorktreeNames({ - workspaceRoots: [DISTRO_ROOT], - home: '/nonexistent-home', - env: {}, - resolveWslHome: async () => '\\\\wsl.localhost\\Ubuntu\\home\\ada' - }) - - // Both UNC listings and absence-proving ancestor probes must share scan admission. - const calls = runWslTranscriptFsTaskMock.mock.calls.map(([options]) => options) - expect(calls.every((options) => options.priority === 'scan')).toBe(true) - expect(calls.map((options) => options.path)).toEqual( - expect.arrayContaining([ - DISTRO_ROOT, - join('\\\\wsl.localhost\\Ubuntu\\home\\ada', '.claude', 'projects') - ]) - ) - }) - - it('leaves no listing ungated when the workspace root is on the Windows side', async () => { - await discoverRetiredWorktreeNames({ - workspaceRoots: ['C:\\Users\\ada\\orca\\workspaces'], - home: '/nonexistent-home', - env: {} - }) - - expect(runWslTranscriptFsTaskMock).not.toHaveBeenCalled() - }) - it('reports a gate refusal as incomplete but still returns what the host side could read', async () => { runWslTranscriptFsTaskMock.mockRejectedValue( new WslTranscriptFsError('timeout', 'filesystem access is taking too long') @@ -140,17 +111,6 @@ describe('retirement discovery on WSL', () => { expect(retired.complete).toBe(false) }) - it('stays retryable when the distro home cannot be resolved, rather than memoizing the hole', async () => { - const retired = await discoverRetiredWorktreeNames({ - workspaceRoots: [DISTRO_ROOT], - home: '/nonexistent-home', - env: {}, - resolveWslHome: async () => null - }) - - expect(retired.complete).toBe(false) - }) - it('keeps a deleted WSL workspace name spent, so the next create cannot reuse its cwd', async () => { // The distro bucket outlives the deleted workspace and must keep its name retired. const distroHome = await mkdtemp(join(tmpdir(), 'orca-wsl-distro-home-')) diff --git a/src/main/wsl/wsl-host-server-stop.test.ts b/src/main/wsl/wsl-host-server-stop.test.ts index 53cffa835dc..10ce4f1d507 100644 --- a/src/main/wsl/wsl-host-server-stop.test.ts +++ b/src/main/wsl/wsl-host-server-stop.test.ts @@ -101,60 +101,6 @@ describe('stopWslHostServer', () => { warn.mockRestore() }) - it('names each terminal that refused, and still asks the server to stop', async () => { - const { all } = steps({ - endTerminals: vi.fn(async () => [ - { handle: 'term_1', worktreeId: 'gone::/home/me/old', reason: 'selector_not_found' } - ]), - census: vi.fn(async () => census(1)), - decommission: vi.fn(async (): Promise => ({ - outcome: 'refused', - verdict: 'live', - code: 'orcad_decommission_terminals_running', - reason: '1 terminal is still running on this host.' - })) - }) - const result = await stopWslHostServer(all) - expect(all.decommission).toHaveBeenCalled() - expect(result).toEqual({ - stopped: false, - reason: - 'Terminals that refused to close: term_1 (gone::/home/me/old): selector_not_found 1 terminal session kept running. Orca’s server kept running: 1 terminal is still running on this host.', - verdict: 'live' - }) - }) - - it('never reports stopped when the decommission could not prove it stopped the serving orcad', async () => { - const { all } = steps({ - decommission: vi.fn(async (): Promise => ({ - outcome: 'refused', - verdict: 'unverifiable', - code: 'orcad_wsl_host_serving_instance_unproven', - reason: 'the orcad this computer was connected to is not the one the distro records' - })) - }) - await expect(stopWslHostServer(all)).resolves.toMatchObject({ - stopped: false, - verdict: 'unverifiable' - }) - }) - - it('calls a census that did not answer unverifiable, not live', async () => { - const { all } = steps({ - census: vi.fn(async () => census(null)), - decommission: vi.fn(async (): Promise => ({ - outcome: 'refused', - verdict: 'unverifiable', - code: 'orcad_decommission_census_unavailable', - reason: 'The terminal daemon did not answer a session count.' - })) - }) - await expect(stopWslHostServer(all)).resolves.toMatchObject({ - stopped: false, - verdict: 'unverifiable' - }) - }) - it('reports a daemon that did not retire', async () => { const { all } = steps({ decommission: vi.fn(async (): Promise => ({ diff --git a/src/main/wsl/wsl-orcad-connect-state-snapshot.test.ts b/src/main/wsl/wsl-orcad-connect-state-snapshot.test.ts index 5457ac15029..c1e81839780 100644 --- a/src/main/wsl/wsl-orcad-connect-state-snapshot.test.ts +++ b/src/main/wsl/wsl-orcad-connect-state-snapshot.test.ts @@ -63,22 +63,6 @@ afterEach(() => { }) describe.skipIf(process.platform === 'win32')('WSL connect state snapshot', () => { - it('reads a fresh host in one command', async () => { - const reader = createWslOrcadConnectStateReader() - - await expect(reader.readActivationRecord(target())).resolves.toEqual( - emptyOrcadActivationRecord() - ) - - expect(mockExec).toHaveBeenCalledOnce() - expect(reader.snapshot()).toEqual({ - activation: { state: 'absent' }, - transactionAbsent: true, - instanceLock: { state: 'absent' }, - fenceOpen: true - }) - }) - it('reads the record, a pending journal, the instance lock and a held fence together', async () => { write('.orca-remote/orcad-active.json', serializeOrcadActivationRecord(ACTIVE)) write('.orca-remote/.orcad-activation-transaction/transaction.json', '{"partial"') @@ -193,12 +177,6 @@ describe('wslOrcadConnectStateHints', () => { fenceOpen: true } - it('hints while the record the snapshot read is the one acted on', () => { - const hints = wslOrcadConnectStateHints(snapshot, ACTIVE) - expect(hints.instanceLock(ACTIVE)).toEqual({ state: 'absent' }) - expect(hints.deployPreRead(ACTIVE)).toEqual({ preRead: { record: ACTIVE, fenceOpen: true } }) - }) - it('stops hinting once recovery, adoption or admission replaced the record', () => { const hints = wslOrcadConnectStateHints(snapshot, ACTIVE) const replaced = { ...ACTIVE } diff --git a/src/main/wsl/wsl-orcad-host-transport.test.ts b/src/main/wsl/wsl-orcad-host-transport.test.ts index 1e713cfd1d1..56344bea2d1 100644 --- a/src/main/wsl/wsl-orcad-host-transport.test.ts +++ b/src/main/wsl/wsl-orcad-host-transport.test.ts @@ -1,15 +1,11 @@ -import { ORCAD_WSL_DISTRO_GUID_ENV } from '../orcad/orcad-migration-wsl-destination' -import { ORCAD_IDLE_TIMEOUT_ENV } from '../../shared/orcad-idle-exit' import { EventEmitter } from 'node:events' import { PassThrough, Readable } from 'node:stream' import { describe, expect, it, vi } from 'vitest' import type { PipedChildProcess, PipedProcessSpec } from '@orca/process-host/process-spec' import { execCommand } from '../ssh/remote-exec/remote-exec-command' -import { isWslOrcadHostTarget } from '../ssh/orcad-host-transport' + import { WSL_CHANNEL_CLOSE_GRACE_MS } from './wsl-command-channel' import { createWslOrcadHostTransport } from './wsl-orcad-host-transport' -import { openOrcadStdioBridgeChannel, resolveOrcadStdioBridge } from '../ssh/orcad-stdio-bridge' -import { orcadBashStdioBridgeCommand } from '../managed-server/lifecycle/orcad-stdio-bridge-script' type FakeChild = EventEmitter & { stdin: PassThrough @@ -72,32 +68,6 @@ function transportWith(children: FakeChild[]) { } describe('createWslOrcadHostTransport', () => { - it('runs the command through wsl.exe --exec sh -c in the chosen distro', async () => { - const child = fakeChild() - const { transport, specs } = transportWith([child]) - const channel = await transport.exec('echo $HOME') - expect(specs[0]?.program).toMatch(/wsl(\.exe)?$/i) - expect(specs[0]?.args).toEqual(['-d', 'Ubuntu-24.04', '--exec', 'sh', '-c', 'echo $HOME']) - expect(specs[0]?.env?.WSL_UTF8).toBe('1') - channel.close() - }) - - it('hands the bash bridge to sh through --exec, so wsl.exe never expands its $ words', async () => { - const child = fakeChild() - const { transport, specs } = transportWith([child]) - const bridge = await resolveOrcadStdioBridge(transport, 6768) - const channel = await openOrcadStdioBridgeChannel(transport, bridge) - expect(specs[0]?.args).toEqual([ - '-d', - 'Ubuntu-24.04', - '--exec', - 'sh', - '-c', - orcadBashStdioBridgeCommand(6768) - ]) - channel.close() - }) - it('emits exit before close and reports the exit code on both', async () => { const child = fakeChild() const { transport } = transportWith([child]) @@ -189,39 +159,4 @@ describe('createWslOrcadHostTransport', () => { await closed expect(Buffer.concat(received).length).toBe(20 * 1024 * 1024) }) - - it('reports a stable WSL identity and never claims the system SSH transport', () => { - const { transport } = transportWith([]) - const target = transport.getTarget() - expect(target).toEqual({ - kind: 'wsl', - id: 'env-1', - distro: 'Ubuntu-24.04', - label: 'My Ubuntu' - }) - expect(isWslOrcadHostTarget(target)).toBe(true) - expect(transport.usesSystemSshTransport()).toBe(false) - expect(transport.getConnectGeneration()).toBe(transport.getConnectGeneration()) - }) - - // Regression: `nohup … &` dies with its `wsl.exe --exec` on a distro without systemd. - it('asks orcad launches to start in their own session', () => { - expect(transportWith([]).transport.orcadLaunchDetach).toBe('setsid') - }) - - it('tells orcad which registration of the distro it runs in, when the registry knows', async () => { - const make = (id: string | null) => - createWslOrcadHostTransport({ - target: { kind: 'wsl', id: 'env-1', distro: 'Ubuntu-24.04', label: 'My Ubuntu' }, - commandLoops: false, - readDistroRegistryId: async (distro) => (distro === 'Ubuntu-24.04' ? id : null) - }) - await expect(make('1a2b')?.orcadLaunchEnv?.({ fullVersion: '0.1.0+abc' })).resolves.toEqual([ - [ORCAD_WSL_DISTRO_GUID_ENV, '1a2b'], - [ORCAD_IDLE_TIMEOUT_ENV, '300000'] - ]) - await expect(make(null)?.orcadLaunchEnv?.({ fullVersion: '0.1.0+abc' })).resolves.toEqual([ - [ORCAD_IDLE_TIMEOUT_ENV, '300000'] - ]) - }) }) diff --git a/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.test.ts b/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.test.ts index 0c01acbd88d..c40a6d11fa8 100644 --- a/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.test.ts +++ b/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.test.ts @@ -3,12 +3,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import { getContextualTour } from '../../../../shared/contextual-tours' import { i18n, loadRendererUiLanguage } from '@/i18n/i18n' -import { - getContextualTourDisplayProgress, - getContextualTourMeasurementAction, - measureContextualTourOverlayRenderState, - isContextualTourLastDisplayStep -} from './contextual-tour-overlay-measurement' +import { measureContextualTourOverlayRenderState } from './contextual-tour-overlay-measurement' afterEach(async () => { document.body.replaceChildren() @@ -16,56 +11,6 @@ afterEach(async () => { }) describe('contextual tour overlay measurement', () => { - it('renders the automation tour copy in Korean when the UI locale is Korean', async () => { - await i18n.changeLanguage(await loadRendererUiLanguage('ko')) - const target = document.createElement('button') - target.setAttribute('data-contextual-tour-target', 'automations-create') - target.getBoundingClientRect = () => new DOMRect(0, 0, 20, 20) - document.body.appendChild(target) - - const result = measureContextualTourOverlayRenderState({ - tour: getContextualTour('automations'), - activeStepIndex: 0, - sidebarOpen: true, - keybindings: undefined, - previousTelemetryTotalSteps: 0 - }) - - expect(result.kind).toBe('render') - if (result.kind !== 'render') { - throw new Error(`Expected render result, received ${result.kind}`) - } - expect(result.renderState.title).toBe('자동화란 무엇인가요?') - expect(result.renderState.body).toBe( - '자동화는 일정에 따라 agent 작업을 실행합니다. 이 버튼을 눌러 자동화를 추가하세요.' - ) - }) - - it('renders the automation results step in Korean when the UI locale is Korean', async () => { - await i18n.changeLanguage(await loadRendererUiLanguage('ko')) - const target = document.createElement('div') - target.setAttribute('data-contextual-tour-target', 'automations-runs') - target.getBoundingClientRect = () => new DOMRect(0, 0, 20, 20) - document.body.appendChild(target) - - const result = measureContextualTourOverlayRenderState({ - tour: getContextualTour('automations'), - activeStepIndex: 1, - sidebarOpen: true, - keybindings: undefined, - previousTelemetryTotalSteps: 0 - }) - - expect(result.kind).toBe('render') - if (result.kind !== 'render') { - throw new Error(`Expected render result, received ${result.kind}`) - } - expect(result.renderState.title).toBe('결과 확인') - expect(result.renderState.body).toBe( - '실행 내역에서 자동화가 언제 실행되었는지, 어떤 일이 발생했는지, 출력을 어디서 확인할 수 있는지 볼 수 있습니다.' - ) - }) - it('localizes the title, shortcut body, and action label of non-automation steps', async () => { await i18n.changeLanguage(await loadRendererUiLanguage('ko')) const target = document.createElement('div') @@ -127,43 +72,6 @@ describe('contextual tour overlay measurement', () => { } expect(result.renderState.title).toBe('자동화란 무엇인가요?') }) - - it('shows all defined browser steps in progress even when step 3 is hidden', () => { - const tour = getContextualTour('browser') - - expect( - getContextualTourDisplayProgress({ - tour, - visibleStepIndexes: [0, 1], - stepIndex: 1, - activeStep: tour.steps[1] - }) - ).toEqual({ current: 2, total: 3 }) - }) - - it('waits for the browser cookie step target instead of cancelling', () => { - const tour = getContextualTour('browser') - - expect( - getContextualTourMeasurementAction({ - tour, - visibleStepIndexes: [0, 1], - activeStepIndex: 2 - }) - ).toEqual({ kind: 'wait' }) - }) - - it('treats browser step 3 as the last display step', () => { - const tour = getContextualTour('browser') - - expect( - isContextualTourLastDisplayStep({ - tour, - activeStepIndex: 2, - progress: { current: 3, total: 3 } - }) - ).toBe(true) - }) }) const { rejectAppStoreAccess } = vi.hoisted(() => ({ diff --git a/src/renderer/src/components/dashboard-popout/remote-runtime-terminal-preview-api.test.ts b/src/renderer/src/components/dashboard-popout/remote-runtime-terminal-preview-api.test.ts index 973715a5c61..00e0f5ceecd 100644 --- a/src/renderer/src/components/dashboard-popout/remote-runtime-terminal-preview-api.test.ts +++ b/src/renderer/src/components/dashboard-popout/remote-runtime-terminal-preview-api.test.ts @@ -76,18 +76,6 @@ describe('remoteRuntimeTerminalPreviewApi', () => { harness.subscriptions.length = 0 }) - it('subscribes on the host named by the pty id with a preview-only client', async () => { - const { sub, payloads } = await openPreview() - expect(sub.environmentId).toBe('env-a') - expect(sub.terminal).toBe('term-1') - expect(sub.clientId).toMatch(/^dashboard-preview:/) - - sub.callbacks.onData('live', { seq: 14 }) - expect(payloads).toEqual([{ type: 'data', ptyId: PTY, data: 'live', bytes: 4 }]) - await expect(api.input(PTY, 'x')).resolves.toBe(true) - expect(sub.stream.sendInput).toHaveBeenCalledWith('x') - }) - it('refuses an id without its owning host instead of guessing one', async () => { await expect(api.connect('remote:term-1')).resolves.toEqual({ snapshot: null, replay: [] }) expect(harness.subscriptions).toHaveLength(0) diff --git a/src/renderer/src/components/editor/EditorContent.test.tsx b/src/renderer/src/components/editor/EditorContent.test.tsx index 1d6fb7442a8..e502e18d6d0 100644 --- a/src/renderer/src/components/editor/EditorContent.test.tsx +++ b/src/renderer/src/components/editor/EditorContent.test.tsx @@ -1,26 +1,11 @@ -import { renderToStaticMarkup } from 'react-dom/server' import { afterEach, describe, expect, it, vi } from 'vitest' -import type { OpenFile } from '@/store/slices/editor' // Stub lazy renderers because renderToStaticMarkup cannot resolve them; retain the banner branch structure. vi.mock('@/lib/lazy-with-retry', () => ({ lazyWithRetry: () => () => null })) -import { EditorContent, getMarkdownSourceLineOffset } from './EditorContent' - -function createOpenFile(overrides: Partial = {}): OpenFile { - return { - id: '/repo/notebook.ipynb', - filePath: '/repo/notebook.ipynb', - relativePath: 'notebook.ipynb', - worktreeId: 'repo::/repo', - language: 'notebook', - isDirty: false, - mode: 'edit', - ...overrides - } -} +import { getMarkdownSourceLineOffset } from './EditorContent' afterEach(() => { vi.restoreAllMocks() @@ -33,188 +18,4 @@ describe('EditorContent', () => { expect(getMarkdownSourceLineOffset('---\r\ntitle: x\r\n---\r\n')).toBe(3) expect(getMarkdownSourceLineOffset('---\rtitle: x\n---\r\n')).toBe(3) }) - - it('counts newline-heavy front matter offsets without regex match', () => { - const matchSpy = vi.spyOn(String.prototype, 'match') - const frontMatterRaw = `---\n${'title: x\n'.repeat(12_000)}---\n` - - expect(getMarkdownSourceLineOffset(frontMatterRaw)).toBe(12_002) - expect(matchSpy).not.toHaveBeenCalled() - }) - - it('surfaces file load errors before notebook content is parsed', () => { - const activeFile = createOpenFile() - const html = renderToStaticMarkup( - - ) - - expect(html).toContain('Unable to load file') - expect(html).toContain('Access denied') - expect(html).not.toContain('Unable to render notebook') - }) - - it('shows the changed-on-disk banner above a dirty edit tab', () => { - const activeFile = createOpenFile({ - id: '/repo/file.ts', - filePath: '/repo/file.ts', - relativePath: 'file.ts', - language: 'typescript', - isDirty: true, - externalMutation: 'changed' - }) - const html = renderToStaticMarkup( - - ) - - expect(html).toContain('role="alert"') - expect(html).toContain('changed on disk') - expect(html).toContain('Reload from Disk') - }) - - it('shows the changed-on-disk banner above a dirty unstaged diff without collapsing it', () => { - const activeFile = createOpenFile({ - id: 'diff:/repo/file.ts', - filePath: '/repo/file.ts', - relativePath: 'file.ts', - language: 'typescript', - mode: 'diff', - diffSource: 'unstaged', - isDirty: true, - externalMutation: 'changed' - }) - const html = renderToStaticMarkup( - - ) - - expect(html).toContain('role="alert"') - expect(html).toContain('changed on disk') - // A flex-1-only wrapper collapsed the diff to zero height. - expect(html).toContain('flex h-full min-h-0 flex-col') - }) - - it('shows the changed-on-disk banner on a dirty markdown diff in preview mode', () => { - const activeFile = createOpenFile({ - id: 'diff:/repo/notes.md', - filePath: '/repo/notes.md', - relativePath: 'notes.md', - language: 'markdown', - mode: 'diff', - diffSource: 'unstaged', - isDirty: true, - externalMutation: 'changed' - }) - const html = renderToStaticMarkup( - - ) - - expect(html).toContain('role="alert"') - expect(html).toContain('changed on disk') - expect(html).toContain('Previewing the modified version of this diff') - }) }) diff --git a/src/renderer/src/components/editor/MediaViewer.test.tsx b/src/renderer/src/components/editor/MediaViewer.test.tsx index b75868b2f96..cd130227265 100644 --- a/src/renderer/src/components/editor/MediaViewer.test.tsx +++ b/src/renderer/src/components/editor/MediaViewer.test.tsx @@ -71,20 +71,6 @@ describe('MediaViewer', () => { expect(video?.getAttribute('src')).toBeNull() }) - it('resumes from the last playback position after the tab remounts', async () => { - vi.spyOn(HTMLMediaElement.prototype, 'pause').mockImplementation(() => {}) - vi.spyOn(HTMLMediaElement.prototype, 'load').mockImplementation(() => {}) - const first = await renderPlaybackViewer('clip.mp4:pdf') - first.video.currentTime = 42 - await act(async () => first.root.unmount()) - - const second = await renderPlaybackViewer('clip.mp4:pdf') - second.video.currentTime = 0 - await act(async () => second.video.dispatchEvent(new Event('loadedmetadata'))) - expect(second.video.currentTime).toBe(42) - await act(async () => second.root.unmount()) - }) - it('retains the position when leaving before metadata and clamps it when the file shrinks', async () => { vi.spyOn(HTMLMediaElement.prototype, 'pause').mockImplementation(() => {}) vi.spyOn(HTMLMediaElement.prototype, 'load').mockImplementation(() => {}) @@ -131,29 +117,6 @@ describe('MediaViewer', () => { await act(async () => otherRestored.root.unmount()) }) - it('shows a persistent playback error and only offers the default app for local files', async () => { - vi.spyOn(HTMLMediaElement.prototype, 'pause').mockImplementation(() => {}) - vi.spyOn(HTMLMediaElement.prototype, 'load').mockImplementation(() => {}) - const container = document.createElement('div') - const root = createRoot(container) - await act(async () => - root.render( - - ) - ) - await act(async () => container.querySelector('video')?.dispatchEvent(new Event('error'))) - expect(container.querySelector('[role="alert"]')?.textContent).toContain( - 'Unable to play this media file' - ) - expect(container.querySelector('button')).toBeNull() - await act(async () => root.unmount()) - }) - it('uses native audio controls and releases the audio source when switching files', async () => { const pause = vi.spyOn(HTMLMediaElement.prototype, 'pause').mockImplementation(() => {}) const load = vi.spyOn(HTMLMediaElement.prototype, 'load').mockImplementation(() => {}) diff --git a/src/renderer/src/components/editor/PluginCustomEditorFrame.test.tsx b/src/renderer/src/components/editor/PluginCustomEditorFrame.test.tsx index 3a9f93278a7..66cd5f98c01 100644 --- a/src/renderer/src/components/editor/PluginCustomEditorFrame.test.tsx +++ b/src/renderer/src/components/editor/PluginCustomEditorFrame.test.tsx @@ -113,21 +113,6 @@ async function renderFrame(props: { } describe('PluginCustomEditorFrame', () => { - it('renders the editor in a scripts-only sandbox inside the CSP shell', async () => { - const { iframe } = await renderFrame({ content: '{}' }) - expect(readCustomEditorEntry).toHaveBeenCalledWith({ - pluginKey: 'stablyai.excalidraw', - editorId: 'excalidraw' - }) - // allow-same-origin would expose app DOM, storage and IPC to the plugin. - expect(iframe.getAttribute('sandbox')).toBe('allow-scripts') - expect(iframe.getAttribute('name')?.startsWith('orca-plugin-panel:editor:')).toBe(true) - // window.name is readable in the frame, so it must not name the file. - expect(iframe.getAttribute('name')).not.toContain('diagram') - expect(iframe.getAttribute('name')).not.toContain('/repo') - expect(iframe.getAttribute('srcdoc')).toContain('
editor
') - }) - it('sends the buffer on ready and applies edits without saving', async () => { const { posted, fromFrame, onContentChange, onSave } = await renderFrame({ content: 'scene' }) await fromFrame(READY) diff --git a/src/renderer/src/components/editor/RichMarkdownErrorBoundary.lazy-chunk.test.tsx b/src/renderer/src/components/editor/RichMarkdownErrorBoundary.lazy-chunk.test.tsx index a9ac63ac6b0..64fe89ad99e 100644 --- a/src/renderer/src/components/editor/RichMarkdownErrorBoundary.lazy-chunk.test.tsx +++ b/src/renderer/src/components/editor/RichMarkdownErrorBoundary.lazy-chunk.test.tsx @@ -107,61 +107,6 @@ describe('RichMarkdownErrorBoundary lazy chunk containment', () => { }) }) - it('records the degraded breadcrumb once across repeated Retry clicks', async () => { - window.sessionStorage.setItem(RELOAD_GUARD_KEY, LANDED_RELOAD_GUARD_VALUE) - const LazyRejectingImport = lazyWithRetry( - () => Promise.reject(new SyntaxError(CORRUPT_CHUNK_PARSE_ERROR)), - { retries: 0 } - ) - ;({ container, root } = createContainer()) - - await act(async () => { - root?.render( - - - - ) - }) - await flushReactWork() - await flushReactWork() - - for (let click = 0; click < 3; click += 1) { - const retry = container?.querySelector('button') - expect(retry).not.toBeNull() - await act(async () => { - retry?.click() - }) - await flushReactWork() - } - - expect(container?.textContent).toContain('rich markdown editor') - expect(recordBreadcrumbMock).toHaveBeenCalledTimes(1) - }) - - it('records the degraded breadcrumb once across boundary remounts', async () => { - window.sessionStorage.setItem(RELOAD_GUARD_KEY, LANDED_RELOAD_GUARD_VALUE) - const LazyRejectingImport = lazyWithRetry( - () => Promise.reject(new SyntaxError("Unexpected token '<'")), - { retries: 0 } - ) - ;({ container, root } = createContainer()) - - for (const boundaryKey of ['pane-a', 'pane-b', 'pane-c']) { - await act(async () => { - root?.render( - - - - ) - }) - await flushReactWork() - await flushReactWork() - } - - expect(container?.textContent).toContain('rich markdown editor') - expect(recordBreadcrumbMock).toHaveBeenCalledTimes(1) - }) - it('still reports ordinary render errors', async () => { const error = new Error('ordinary render failure') function BrokenEditor(): ReactElement { diff --git a/src/renderer/src/components/editor/rich-markdown-cut.test.ts b/src/renderer/src/components/editor/rich-markdown-cut.test.ts index e99f8e6c95c..dc0db06aab0 100644 --- a/src/renderer/src/components/editor/rich-markdown-cut.test.ts +++ b/src/renderer/src/components/editor/rich-markdown-cut.test.ts @@ -75,20 +75,6 @@ function createClipboardEventMock(options?: { failReadback?: boolean }): { } describe('rich markdown cut handler behavior', () => { - it('hard-wrapped document prose stays one paragraph after normalization', () => { - const editor = createEditor('Line one\nLine two\nLine three\n') - - expect(countParagraphs(editor)).toBe(1) - expect(editor.state.doc.firstChild!.textContent).toContain('\n') - - normalizeEmptyListItems(editor) - - expect(countParagraphs(editor)).toBe(1) - expect(editor.state.doc.firstChild!.textContent).toBe('Line one\nLine two\nLine three') - - editor.destroy() - }) - it('soft-break normalization still creates visible paragraph breaks', () => { const editor = createEditor('Line one\nLine two\nLine three\n') normalizeSoftBreaks(editor) @@ -201,28 +187,4 @@ describe('rich markdown cut handler behavior', () => { editor.destroy() } }) - - it('normalizeEmptyListItems is idempotent on already-clean documents', () => { - const editor = createEditor('First.\n\nSecond.\n\nThird.\n') - - const docBefore = editor.state.doc.toJSON() - normalizeEmptyListItems(editor) - const docAfter = editor.state.doc.toJSON() - - expect(docAfter).toEqual(docBefore) - - editor.destroy() - }) - - it('normalizeEmptyListItems does not modify populated list items or blockquotes', () => { - const editor = createEditor('- Item 1\n- Item 2\n') - - const docBefore = editor.state.doc.toJSON() - normalizeEmptyListItems(editor) - const docAfter = editor.state.doc.toJSON() - - expect(docAfter).toEqual(docBefore) - - editor.destroy() - }) }) diff --git a/src/renderer/src/components/feature-wall/ConnectIntegrationsList.test.tsx b/src/renderer/src/components/feature-wall/ConnectIntegrationsList.test.tsx deleted file mode 100644 index a5ad59f2e20..00000000000 --- a/src/renderer/src/components/feature-wall/ConnectIntegrationsList.test.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import { renderToStaticMarkup } from 'react-dom/server' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { PreflightStatus } from '../../../../preload/api-types' -import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context' -import { ConnectIntegrationsList } from './ConnectIntegrationsList' - -type StoreState = { - activeRepoId: string | null - activeWorktreeId: string | null - worktreesByRepo: Record - repos: unknown[] - settings: { activeRuntimeEnvironmentId?: string | null } - preflightStatus: PreflightStatus | null - preflightStatusChecked: boolean - preflightStatusContextKey: string - preflightStatusError: string | null - preflightStatusLoading: boolean - refreshPreflightStatus: () => Promise - linearStatus: { connected: boolean; workspaces?: unknown[] } - linearStatusChecked: boolean - linearStatusContextKey: string | null - checkLinearConnection: () => Promise - testLinearConnection: () => Promise<{ ok: boolean; error?: string }> - disconnectLinear: () => Promise - disconnectLinearWorkspace: () => Promise - jiraStatus: { connected: boolean; sites?: unknown[] } - jiraStatusChecked: boolean - jiraStatusContextKey: string | null - checkJiraConnection: () => Promise - testJiraConnection: () => Promise<{ ok: boolean; error?: string }> - disconnectJira: () => Promise -} - -const { storeState } = vi.hoisted(() => ({ - storeState: { current: null as StoreState | null } -})) - -vi.mock('@/store', () => ({ - useAppStore: Object.assign( - (selector: (state: StoreState) => unknown) => { - if (!storeState.current) { - throw new Error('Store state was not installed') - } - return selector(storeState.current) - }, - { getState: () => storeState.current } - ) -})) - -vi.mock('@/components/linear-api-key-dialog', () => ({ - LinearApiKeyDialog: () => null -})) - -vi.mock('@/components/jira-connect-dialog', () => ({ - JiraConnectDialog: () => null -})) - -function makePreflightStatus(overrides: Partial = {}): PreflightStatus { - const status: PreflightStatus = { - git: { installed: true }, - gh: { installed: true, authenticated: false }, - glab: { installed: true, authenticated: false }, - bitbucket: { - configured: false, - authenticated: false, - account: null - }, - azureDevOps: { - configured: false, - authenticated: false, - account: null, - baseUrl: null, - tokenConfigured: false - }, - gitea: { - configured: false, - authenticated: false, - account: null, - baseUrl: null, - tokenConfigured: false - } - } - return { ...status, ...overrides } -} - -function installStore(preflightStatus: PreflightStatus): void { - const settings = { activeRuntimeEnvironmentId: null } - const providerContextKey = getProviderRuntimeContextKey(settings) - storeState.current = { - activeRepoId: null, - activeWorktreeId: null, - worktreesByRepo: {}, - repos: [], - settings, - preflightStatus, - preflightStatusChecked: true, - preflightStatusContextKey: 'host', - preflightStatusError: null, - preflightStatusLoading: false, - refreshPreflightStatus: vi.fn(async () => {}), - linearStatus: { connected: false, workspaces: [] }, - linearStatusChecked: true, - linearStatusContextKey: providerContextKey, - checkLinearConnection: vi.fn(async () => {}), - testLinearConnection: vi.fn(async () => ({ ok: true })), - disconnectLinear: vi.fn(async () => {}), - disconnectLinearWorkspace: vi.fn(async () => {}), - jiraStatus: { connected: false, sites: [] }, - jiraStatusChecked: true, - jiraStatusContextKey: providerContextKey, - checkJiraConnection: vi.fn(async () => {}), - testJiraConnection: vi.fn(async () => ({ ok: true })), - disconnectJira: vi.fn(async () => {}) - } -} - -async function renderConnectIntegrationsList(): Promise<{ - markup: string -}> { - return { markup: renderToStaticMarkup() } -} - -describe('ConnectIntegrationsList', () => { - beforeEach(() => { - vi.stubGlobal('window', { - api: { - shell: { - openUrl: vi.fn() - } - } - }) - }) - - afterEach(() => { - storeState.current = null - vi.unstubAllGlobals() - vi.restoreAllMocks() - }) - - it('renders the settings review cards in the review step without an inline auth terminal', async () => { - installStore(makePreflightStatus()) - - const { markup } = await renderConnectIntegrationsList() - - for (const provider of ['GitHub', 'GitLab', 'Bitbucket', 'Azure DevOps', 'Gitea']) { - expect(markup).toContain(provider) - } - expect(markup).toContain('gh auth login') - expect(markup).toContain('glab auth login') - expect(markup).not.toContain('Run in terminal') - }) - - it('keeps the upcoming task step collapsed but openable, not inert', async () => { - installStore(makePreflightStatus()) - - const { markup } = await renderConnectIntegrationsList() - - // Review is optional ordering: tracker-first users can expand the task step. - expect(markup).toContain('Open') - expect(markup).not.toContain('opacity-55') - expect(markup).not.toContain('Add Linear access') - }) - - it('collapses the task step to its summary when a tracker connects first', async () => { - installStore(makePreflightStatus()) - if (!storeState.current) { - throw new Error('Store state was not installed') - } - storeState.current.linearStatus = { connected: true, workspaces: [] } - - const { markup } = await renderConnectIntegrationsList() - - expect(markup).toContain('connected for tasks') - expect(markup).not.toContain('Connect Jira') - }) - - it('auto-resolves the task step from a connected code host but keeps it open for trackers', async () => { - installStore(makePreflightStatus({ gh: { installed: true, authenticated: true } })) - - const { markup } = await renderConnectIntegrationsList() - - expect(markup).toContain('GitHub') - expect(markup).toContain('issues available as tasks') - expect(markup).toContain('add Linear or Jira if your team plans work there') - expect(markup).not.toContain('Use GitHub issues') - // Keep dedicated trackers discoverable even when code-host issues already satisfy tasks. - expect(markup).toContain('Add Linear access') - expect(markup).toContain('Connect Jira') - }) - - it('offers GitHub and GitLab as task sources when review came from a non-task provider', async () => { - installStore( - makePreflightStatus({ - bitbucket: { configured: true, authenticated: true, account: 'acme' } - }) - ) - - const { markup } = await renderConnectIntegrationsList() - - expect(markup).toContain('issues also work as tasks.') - expect(markup).toContain('gh auth login') - expect(markup).toContain('glab auth login') - expect(markup).toContain('Linear') - expect(markup).toContain('Jira') - }) - - it('lists the code host alongside a connected tracker in the task summary', async () => { - installStore(makePreflightStatus({ gh: { installed: true, authenticated: true } })) - if (!storeState.current) { - throw new Error('Store state was not installed') - } - storeState.current.linearStatus = { connected: true, workspaces: [] } - - const { markup } = await renderConnectIntegrationsList() - - expect(markup).toContain('Linear') - expect(markup).toContain('GitHub') - expect(markup).toContain('connected for tasks') - expect(markup).toContain(' and ') - expect(markup).not.toContain('Connect Jira') - }) -}) diff --git a/src/renderer/src/components/feature-wall/FullDiskAccessSetupPrompt.test.ts b/src/renderer/src/components/feature-wall/FullDiskAccessSetupPrompt.test.ts index ff4bf3b75b6..484ed7432b9 100644 --- a/src/renderer/src/components/feature-wall/FullDiskAccessSetupPrompt.test.ts +++ b/src/renderer/src/components/feature-wall/FullDiskAccessSetupPrompt.test.ts @@ -8,11 +8,8 @@ import type { DeveloperPermissionRequestResult, DeveloperPermissionState } from '../../../../shared/developer-permissions-types' -import { - FullDiskAccessSetupPrompt, - isFullDiskAccessReady, - isFullDiskAccessSetupVisible -} from './FullDiskAccessSetupPrompt' +import { FullDiskAccessSetupPrompt } from './FullDiskAccessSetupPrompt' + import { toast } from 'sonner' vi.mock('sonner', () => ({ @@ -75,24 +72,6 @@ describe('FullDiskAccessSetupPrompt state helpers', () => { }) }) - it('hides the setup prompt before status is known or when unsupported', () => { - expect(isFullDiskAccessSetupVisible(undefined)).toBe(false) - expect(isFullDiskAccessSetupVisible('unsupported')).toBe(false) - }) - - it('shows the setup prompt only for macOS statuses users can act on', () => { - expect(isFullDiskAccessSetupVisible('unknown')).toBe(true) - expect(isFullDiskAccessSetupVisible('denied')).toBe(true) - expect(isFullDiskAccessSetupVisible('granted')).toBe(false) - expect(isFullDiskAccessSetupVisible('ready')).toBe(false) - }) - - it('treats granted and entitled statuses as ready', () => { - expect(isFullDiskAccessReady('granted')).toBe(true) - expect(isFullDiskAccessReady('ready')).toBe(true) - expect(isFullDiskAccessReady('unknown')).toBe(false) - }) - it('refreshes macOS Full Disk Access status when Orca regains focus', async () => { setUserAgent('Macintosh') const getStatus = vi diff --git a/src/renderer/src/components/host-picker/host-list-surfaces.large-config.test.tsx b/src/renderer/src/components/host-picker/host-list-surfaces.large-config.test.tsx index ade920e47dc..9033b8912ba 100644 --- a/src/renderer/src/components/host-picker/host-list-surfaces.large-config.test.tsx +++ b/src/renderer/src/components/host-picker/host-list-surfaces.large-config.test.tsx @@ -52,21 +52,6 @@ describe('host lists with a 13,692-host ssh config', () => { expect(screen.getAllByRole('option')[0]?.textContent).toContain(lastName) }) - it('HostCombobox treats re-picking the current host as no change', async () => { - const onChange = vi.fn() - render( - ({ id: `ssh-${i}`, label: name }))} - /> - ) - fireEvent.click(screen.getByRole('combobox', { name: 'Host' })) - fireEvent.click(await screen.findByRole('option', { name: names[0] })) - expect(onChange).not.toHaveBeenCalled() - }) - it('Settings > Servers SSH host picker', async () => { const onChange = vi.fn() render( diff --git a/src/renderer/src/components/jira-issue-transition-control.test.tsx b/src/renderer/src/components/jira-issue-transition-control.test.tsx index c562befbc39..be8fb6a3061 100644 --- a/src/renderer/src/components/jira-issue-transition-control.test.tsx +++ b/src/renderer/src/components/jira-issue-transition-control.test.tsx @@ -159,15 +159,6 @@ describe('Jira transition control', () => { expect(mutate).not.toHaveBeenCalled() }) - it('keeps failures visible and exposes Jira fallback', async () => { - await choose( - [close], - vi.fn().mockResolvedValue({ ok: false, error: 'Resolution is required.' }) - ) - expect((await screen.findByRole('alert')).textContent).toContain('Resolution is required.') - expect(screen.getByRole('button', { name: 'Open in Jira' })).toBeTruthy() - }) - it('keeps a rejected mutation visible and offers Jira fallback', async () => { await choose([close], vi.fn().mockRejectedValue(new Error('Disconnected from Jira host.'))) expect((await screen.findByRole('alert')).textContent).toContain('Disconnected from Jira host.') diff --git a/src/renderer/src/components/native-chat-resume-result-view.test.tsx b/src/renderer/src/components/native-chat-resume-result-view.test.tsx index 5f8420e5a3b..9079730f03b 100644 --- a/src/renderer/src/components/native-chat-resume-result-view.test.tsx +++ b/src/renderer/src/components/native-chat-resume-result-view.test.tsx @@ -5,11 +5,10 @@ import { NativeChatResumeOnRestartModal } from './NativeChatResumeRuntimeModal' import { NativeChatResumeStatusSegment } from './status-bar/NativeChatResumeStatusSegment' import { createResumeModalFixture, - failure, - offered, type RestartRpc, type ResumeStatusStream } from './native-chat-resume-modal.test-support' + import { lastToastShow } from './native-chat-resume-toast.test-support' import { chatBox, dontAskAgain } from './native-chat-resume-on-restart-modal.test-support' import { useAppStore } from '../store' @@ -47,22 +46,6 @@ async function mountSurfaces(): Promise { ) } -it('shows a finished resume as a result, in the past tense, once nothing is left to choose', async () => { - fakeHost() - await mountSurfaces() - await act(async () => button('Resume 2 chats').click()) - await act(async () => lastToastShow()?.()) - expect(dialog()?.textContent).toContain('Resumed 2 of 2 chats') - expect(dialog()?.textContent).toContain( - 'Orca restored each one where it stopped, with its full context' - ) - expect(dialog()?.textContent).not.toContain('Resuming restores') - expect(tree()?.querySelectorAll('[role="checkbox"]')).toHaveLength(0) - expect(tree()?.textContent).not.toContain('Select all') - expect(dialog()?.textContent).toContain("Don't ask again") - expect(dialog()?.textContent).not.toContain('Resumed automatically') -}) - it('saves "Don\'t ask again" ticked on a finished resume the user started', async () => { fakeHost() await mountSurfaces() @@ -151,32 +134,6 @@ it('reopens a collapsed result group with tree keys', async () => { expect(document.activeElement).toBe(disclosure) }) -it('shows a settled nonretryable failure as a result with a Done action', async () => { - const failed = { ...failure('a'), outcome: 'refused' as const, retryable: false } - let acted = false - rpc.mockImplementation(async (_target, method) => { - if (method === 'agentSession.restartResumable') { - return { sessions: acted ? [] : [offered[0]], failed: acted ? [failed] : [] } - } - acted = true - return { - sessions: [], - failed: [failed], - continued: [{ sessionId: 'a', outcome: 'refused' }] - } - }) - await mountSurfaces() - await act(async () => button('Resume 1 chat').click()) - await act(async () => lastToastShow()?.()) - expect(dialog()?.textContent).toContain('Resumed 0 of 1 chat') - expect(tree()?.textContent).not.toContain('Select all') - expect(tree()?.querySelectorAll('[role="checkbox"]')).toHaveLength(0) - expect(button('Open chat')).toBeTruthy() - expect(button('Dismiss "Prompt a" in workspace')).toBeTruthy() - expect(button('Done').disabled).toBe(false) - expect(dialog()?.textContent).not.toContain('Resume 0 chats') -}) - it('does not place an untouched offer in the Need you filter', async () => { fakeHost() await mountSurfaces() @@ -216,14 +173,3 @@ it('says a launch resumed on its own and links to the setting', async () => { }) expect(dialog()).toBeNull() }) - -it('keeps the checkboxes while a chat can still be chosen, whichever tab is shown', async () => { - fakeHost({}, (sessionId) => (sessionId === 'b' ? 'refused' : 'continued')) - await mountSurfaces() - await act(async () => button('Resume 2 chats').click()) - await act(async () => button('1 chat failed to resume').click()) - expect(dialog()?.textContent).toContain('The ones Orca resumed were restored') - expect(tree()?.textContent).toContain('Select all') - await act(async () => button('Resumed1').click()) - expect(tree()?.textContent).toContain('Select all') -}) diff --git a/src/renderer/src/components/native-chat/NativeChatMarkdown.mermaid.test.tsx b/src/renderer/src/components/native-chat/NativeChatMarkdown.mermaid.test.tsx index 47724ec02a9..3fe8782efe3 100644 --- a/src/renderer/src/components/native-chat/NativeChatMarkdown.mermaid.test.tsx +++ b/src/renderer/src/components/native-chat/NativeChatMarkdown.mermaid.test.tsx @@ -110,34 +110,6 @@ describe('native chat Mermaid fences', () => { expect(container.querySelector('[data-code-language="mermaid"]')).toBeNull() }) - it('shows partial source while streaming and renders the diagram when the reply finishes', async () => { - const { container, rerender } = render(reply(partial, true)) - expect(container.querySelector('[data-native-chat-code-content]')?.textContent).toContain( - partial - ) - expect(container.querySelector('[data-code-language="mermaid"]')).toBeInTheDocument() - expect(container.querySelector('.mermaid-block')).toBeNull() - - rerender(reply(complete, true)) - expect(container.querySelector('pre')?.textContent).toContain(complete) - - rerender(reply(complete, false)) - await waitFor(() => - expect(container.querySelector('svg')).toHaveTextContent('Sync with main once') - ) - expect(mermaid.render).toHaveBeenCalledWith(expect.any(String), complete) - expect(container.querySelector('pre')).toBeNull() - expect(screen.queryByText(/Diagram error:/)).not.toBeInTheDocument() - }) - - it('still shows the existing error and source for an invalid finished reply', async () => { - mermaid.render.mockRejectedValue(new Error('Invalid mermaid syntax')) - const { container } = render(reply(partial, false)) - await screen.findByText(/Diagram error: Invalid mermaid syntax/) - expect(container.querySelector('pre')?.textContent).toBe(partial.trimEnd()) - expect(container.querySelector('svg')).toBeNull() - }) - it('keeps the source mounted until a pending diagram fails', async () => { const pending = holdNextRender() const view = render(reply(partial, true)) diff --git a/src/renderer/src/components/right-sidebar/checks-panel/panel-content-rendering.test.tsx b/src/renderer/src/components/right-sidebar/checks-panel/panel-content-rendering.test.tsx index a84215092e3..6ee88798382 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/panel-content-rendering.test.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/panel-content-rendering.test.tsx @@ -20,28 +20,6 @@ afterEach(() => { }) describe('checks panel concrete content', () => { - it('renders the no-workspace empty state with its guidance', () => { - const model = { activeWorktree: null } as ChecksPanelEmptyContentModel - - render() - - expect(screen.getByText('No workspace selected')).toBeTruthy() - expect(screen.getByText('Select a workspace to view checks')).toBeTruthy() - }) - - it('renders the folder-workspace empty state without review controls', () => { - const model = { - activeWorktree: { id: 'folder-1' }, - isFolder: true - } as ChecksPanelEmptyContentModel - - render() - - expect(screen.getByText('Checks unavailable')).toBeTruthy() - expect(screen.getByText('Checks require a Git branch and hosted review context')).toBeTruthy() - expect(screen.queryByRole('button')).toBeNull() - }) - it('renders a durable unlinked state that can relink before PR data refetches', () => { const handleLinkSuppressedPullRequest = vi.fn<() => void>() const model = { diff --git a/src/renderer/src/components/settings/NodeDistMirrorDialog.test.tsx b/src/renderer/src/components/settings/NodeDistMirrorDialog.test.tsx index ab444d84185..0b5302039b0 100644 --- a/src/renderer/src/components/settings/NodeDistMirrorDialog.test.tsx +++ b/src/renderer/src/components/settings/NodeDistMirrorDialog.test.tsx @@ -64,16 +64,6 @@ function openDialog(): HTMLInputElement { } describe('Use a Node.js mirror', () => { - it('opens from the host row with the suggested mirror, focused and editable', () => { - render() - const input = openDialog() - - expect(screen.getByRole('dialog', { name: 'Use a Node.js mirror' })).toBeInTheDocument() - expect(input).toHaveValue('https://npmmirror.com/mirrors/node/') - expect(input).toHaveFocus() - expect(screen.getByText(/checked against the checksum Orca pins/)).toBeInTheDocument() - }) - it('saves to the mirror setting, then retries the connect and closes', async () => { render() openDialog() @@ -147,16 +137,6 @@ describe('Use a Node.js mirror', () => { expect(state.activeModal).toBe('none') }) - it('offers nothing in a paired browser, whose settings are not this computer’s', () => { - vi.stubGlobal('__ORCA_WEB_CLIENT__', true) - try { - render() - expect(screen.queryByRole('button')).toBeNull() - } finally { - vi.unstubAllGlobals() - } - }) - it('opens the setting instead when a mirror was set after the toast appeared', () => { useAppStore.getState().setSshConnectionState('ssh-1', { targetId: 'ssh-1', diff --git a/src/renderer/src/components/settings/SettingsSidebar.test.tsx b/src/renderer/src/components/settings/SettingsSidebar.test.tsx deleted file mode 100644 index 399982378d3..00000000000 --- a/src/renderer/src/components/settings/SettingsSidebar.test.tsx +++ /dev/null @@ -1,226 +0,0 @@ -// @vitest-environment happy-dom - -import { renderToStaticMarkup } from 'react-dom/server' -import { Bot, GitBranch, Mic, Network, Puzzle } from 'lucide-react' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { getDefaultSettings } from '../../../../shared/constants' -import { buildSettingsNavigationMetadata } from '@/hooks/useSettingsNavigationMetadata' -import type * as ShortcutLabels from '@/hooks/useShortcutLabel' -import { SettingsSidebar } from './SettingsSidebar' -import { TooltipProvider } from '../ui/tooltip' -import type { SettingsSetupGuideProgress } from './settings-setup-guide-progress' -import type { GlobalSettings } from '../../../../shared/global-settings-types' - -const mocks = vi.hoisted(() => ({ - useSettingsSetupGuideProgress: vi.fn() -})) - -vi.mock('@/hooks/useShortcutLabel', async () => { - const actual = await vi.importActual('@/hooks/useShortcutLabel') - return { - ...actual, - useShortcutLabel: () => '⌘F', - useShortcutKeyComboDetails: () => [{ keys: ['⌘', 'F'], doubleTap: false }] - } -}) - -vi.mock('./settings-setup-guide-progress', () => ({ - useSettingsSetupGuideProgress: mocks.useSettingsSetupGuideProgress -})) - -function makeSetupGuideProgress( - overrides: Partial = {} -): SettingsSetupGuideProgress { - return { - ready: true, - doneCount: 5, - total: 8, - firstIncompleteStepId: 'agent-capabilities', - ...overrides - } -} - -function renderSidebar( - activeSectionId = 'orchestration', - settings: GlobalSettings = getDefaultSettings('/tmp') -): string { - return renderToStaticMarkup( - - - - ) -} - -describe('SettingsSidebar', () => { - beforeEach(() => { - mocks.useSettingsSetupGuideProgress.mockReset() - mocks.useSettingsSetupGuideProgress.mockReturnValue(makeSetupGuideProgress()) - }) - - afterEach(() => { - document.body.innerHTML = '' - }) - - it('renders Chat navigation', () => { - const settings = getDefaultSettings('/tmp') - const sections = buildSettingsNavigationMetadata({ - isMac: false, - isWindows: false, - isWebClient: false, - repos: [] - }).filter((section) => section.group === 'interface') - const container = document.createElement('div') - container.innerHTML = renderToStaticMarkup( - - - - ) - const buttons = Array.from(container.querySelectorAll('button')) - expect(buttons.some((button) => button.textContent === 'Chat')).toBe(true) - }) - - it('applies left sidebar appearance styles to the settings navigation', () => { - const markup = renderSidebar('orchestration', { - ...getDefaultSettings('/tmp'), - leftSidebarAppearanceMode: 'match-terminal', - terminalColorOverrides: { - background: '#101820', - foreground: '#f0f4f8' - } - }) - - expect(markup).toContain('--worktree-sidebar:#101820') - expect(markup).toContain('--worktree-sidebar-foreground:#f0f4f8') - }) - - it('reserves install state labels for actionable skill states', () => { - const markup = renderSidebar() - - expect(markup).not.toContain('Not installed') - expect(markup).not.toContain('Installed') - expect(markup).not.toContain('Up to date') - expect(markup).not.toContain('Checking...') - expect(markup).toContain('Update available') - expect(markup).toContain('Review skill') - expect(markup).toContain('Optional') - }) - - it('does not render the setup guide row before progress readiness settles', () => { - mocks.useSettingsSetupGuideProgress.mockReturnValue( - makeSetupGuideProgress({ - ready: false, - doneCount: 7, - firstIncompleteStepId: 'setup-script' - }) - ) - - expect(renderSidebar()).not.toContain('Onboarding checklist') - }) - - it('renders incomplete setup progress with the full checklist total', () => { - const markup = renderSidebar() - - expect(markup).toContain('Onboarding checklist') - expect(markup).toContain('Onboarding checklist, 5 of 8 done. Show setup guide.') - expect(markup).toContain('5 of 8 setup steps complete') - }) - - it('does not render the setup guide row after every checklist step is complete', () => { - mocks.useSettingsSetupGuideProgress.mockReturnValue( - makeSetupGuideProgress({ - doneCount: 8, - firstIncompleteStepId: null - }) - ) - - expect(renderSidebar()).not.toContain('Onboarding checklist') - }) - - it('keeps the setup guide row available from Settings when incomplete', () => { - const markup = renderSidebar('setup-guide') - - expect(markup).toContain('aria-current="page"') - expect(markup).toContain('Onboarding checklist') - }) -}) diff --git a/src/renderer/src/components/settings/SshTargetForm.test.tsx b/src/renderer/src/components/settings/SshTargetForm.test.tsx index 710e9da9ebd..26253f5204e 100644 --- a/src/renderer/src/components/settings/SshTargetForm.test.tsx +++ b/src/renderer/src/components/settings/SshTargetForm.test.tsx @@ -52,57 +52,6 @@ function button(label: string): HTMLButtonElement { } describe('SshTargetForm', () => { - it('opens add mode as a dialog with primary fields in the viewport', async () => { - const root = await renderForm({}) - expect(document.body.textContent).toContain('Add SSH host') - expect(document.body.textContent).toContain( - 'Add a persistent machine you can log into over SSH.' - ) - expect(document.querySelector('#ssh-target-host')).not.toBeNull() - expect(document.querySelector('#ssh-target-label')).not.toBeNull() - expect(document.body.textContent).toContain('Add host') - expect(document.body.textContent).not.toContain('Editing') - act(() => root.unmount()) - }) - - it('pins header and footer outside the scroll body', async () => { - const root = await renderForm({}) - const content = document.querySelector('[data-slot="dialog-content"]') - const header = document.querySelector('[data-slot="dialog-header"]') - const footer = document.querySelector('[data-slot="dialog-footer"]') - if (!content || !header || !footer) { - throw new Error('missing dialog chrome') - } - expect(content.className).toContain('overflow-hidden') - expect(content.className).toContain('flex-col') - expect(header.className).toContain('shrink-0') - expect(footer.className).toContain('shrink-0') - const scrollBody = content.querySelector('.overflow-y-auto') - expect(scrollBody).not.toBeNull() - expect(scrollBody?.contains(header)).toBe(false) - expect(scrollBody?.contains(footer)).toBe(false) - act(() => root.unmount()) - }) - - it('shows edit title and context chip for the target being edited', async () => { - const root = await renderForm({ - editingId: 'target-1', - form: { - ...EMPTY_FORM, - label: 'dev-box', - host: 'dev-box.lan', - username: 'alice', - port: '2222' - } - }) - expect(document.body.textContent).toContain('Edit SSH host') - expect(document.body.textContent).toContain('Save Changes') - expect(document.body.textContent).toContain('Editing') - expect(document.body.textContent).toContain('dev-box') - expect(document.body.textContent).toContain('alice@dev-box.lan:2222') - act(() => root.unmount()) - }) - it('submits via the primary action and closes via Cancel', async () => { const onSave = vi.fn() const onOpenChange = vi.fn() @@ -120,13 +69,6 @@ describe('SshTargetForm', () => { act(() => root.unmount()) }) - it('does not render dialog content when closed', async () => { - const root = await renderForm({ open: false }) - expect(document.querySelector('#ssh-target-host')).toBeNull() - expect(document.body.textContent).not.toContain('Add SSH host') - act(() => root.unmount()) - }) - it('collapses Advanced again after cancel and reopen', async () => { let form: EditingTarget = EMPTY_FORM const onFormChange = vi.fn((updater: (prev: EditingTarget) => EditingTarget) => { @@ -209,19 +151,4 @@ describe('SshTargetForm', () => { expect(onOpenChange).not.toHaveBeenCalled() act(() => root.unmount()) }) - - it('opens Advanced by default when the target already has advanced values', async () => { - const root = await renderForm({ - editingId: 'target-1', - form: { - ...EMPTY_FORM, - label: 'bastion', - host: 'bastion.example', - jumpHost: 'jump.example' - } - }) - expect(button('Advanced').getAttribute('data-state')).toBe('open') - expect(document.body.textContent).toContain('Jump Host') - act(() => root.unmount()) - }) }) diff --git a/src/renderer/src/components/settings/repository/RepositoryPane.test.ts b/src/renderer/src/components/settings/repository/RepositoryPane.test.ts deleted file mode 100644 index cec981746dc..00000000000 --- a/src/renderer/src/components/settings/repository/RepositoryPane.test.ts +++ /dev/null @@ -1,219 +0,0 @@ -// @vitest-environment happy-dom - -import React, { act } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { renderToStaticMarkup } from 'react-dom/server' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { getDefaultSettings } from '../../../../../shared/constants' -import type { Project } from '../../../../../shared/project-types' -import type { Repo } from '../../../../../shared/repo-types' -import type { TerminalTab } from '../../../../../shared/terminal-tab-types' -import type { AgentStatusEntry } from '../../../../../shared/agent-status-types' -import { useAppStore } from '@/store/index' -import { getRepositoryPaneSearchEntries } from './repository-search' -import { RepositoryPane } from './RepositoryPane' -import { matchesRepositoryIdentitySearch } from './identity/repository-identity-search' -import { matchesSettingsSearch } from '../settings-search' -import { TooltipProvider } from '../../ui/tooltip' - -let container: HTMLDivElement -let root: Root - -const repo: Repo = { - id: 'repo-1', - path: '/tmp/repo', - displayName: 'Example Repo', - badgeColor: '#000000', - addedAt: 1, - kind: 'git' -} - -const project: Project = { - id: 'project-1', - displayName: 'Example Project', - badgeColor: '#000000', - sourceRepoIds: ['repo-1'], - createdAt: 1, - updatedAt: 1 -} - -function terminalTab(id: string, worktreeId: string): TerminalTab { - return { - id, - ptyId: null, - worktreeId, - title: 'Terminal', - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 1 - } -} - -function activeAgent(paneKey: string, worktreeId: string): AgentStatusEntry { - return { - state: 'working', - prompt: 'Implement runtime switch', - updatedAt: 1, - stateStartedAt: 1, - agentType: 'codex', - paneKey, - tabId: paneKey.split(':')[0], - worktreeId, - stateHistory: [] - } -} - -beforeEach(() => { - useAppStore.setState(useAppStore.getInitialState(), true) - container = document.createElement('div') - document.body.appendChild(container) - root = createRoot(container) - // Provide the fs bridge needed by mounted WorktreeSymlinksSection path suggestions. - ;(window as unknown as { api: unknown }).api = { - preflight: { detectAgents: async () => [] }, - fs: { readDir: () => Promise.resolve([]) } - } -}) - -afterEach(() => { - act(() => { - root.unmount() - }) - container.remove() - delete (window as unknown as { api?: unknown }).api - useAppStore.setState(useAppStore.getInitialState(), true) -}) - -describe('RepositoryPane search entries', () => { - it('keeps renamed hook sections reachable through settings search', () => { - const entries = getRepositoryPaneSearchEntries(repo, { isLocalWindowsProject: true }) - - expect(matchesSettingsSearch('setup script', entries)).toBe(true) - expect(matchesSettingsSearch('archive script', entries)).toBe(true) - expect(matchesSettingsSearch('setup command', entries)).toBe(true) - expect(matchesSettingsSearch('archive command', entries)).toBe(true) - expect(matchesSettingsSearch('advanced', entries)).toBe(true) - expect(matchesSettingsSearch('runtime', entries)).toBe(true) - expect(matchesSettingsSearch('wsl', entries)).toBe(true) - expect(matchesSettingsSearch('windows host', entries)).toBe(true) - expect(matchesSettingsSearch('command source', entries)).toBe(true) - expect(matchesSettingsSearch('local settings scripts', entries)).toBe(true) - expect(matchesSettingsSearch('../worktrees', entries)).toBe(true) - expect(matchesSettingsSearch('worktree path', entries)).toBe(true) - }) - - it('includes each project search section once', () => { - const entries = getRepositoryPaneSearchEntries(repo) - - expect(entries.filter(({ title }) => title === 'Project Icon')).toHaveLength(1) - expect(entries.filter(({ title }) => title === 'Default Worktree Base')).toHaveLength(1) - }) - - it('omits project runtime search for remote or unsupported repos', () => { - expect(matchesSettingsSearch('project runtime', getRepositoryPaneSearchEntries(repo))).toBe( - false - ) - expect( - matchesSettingsSearch( - 'project runtime', - getRepositoryPaneSearchEntries( - { - ...repo, - connectionId: 'builder', - executionHostId: 'ssh:builder' - }, - { windowsRuntimeSupported: true } - ) - ) - ).toBe(false) - }) - - it('matches project identity searches on display name and path only', () => { - expect(matchesRepositoryIdentitySearch('example repo', repo)).toBe(true) - expect(matchesRepositoryIdentitySearch('/tmp/repo', repo)).toBe(true) - expect(matchesRepositoryIdentitySearch('setup script', repo)).toBe(false) - }) - - it('renders full hook controls when search matches the project name', () => { - useAppStore.setState({ - settingsSearchQuery: 'Example Repo', - settingsSearchInputQuery: 'Example Repo' - }) - - try { - const html = renderToStaticMarkup( - React.createElement( - TooltipProvider, - null, - React.createElement(RepositoryPane, { - repo, - yamlHooks: null, - hasHooksFile: false, - hooksInspectionReady: true, - mayNeedUpdate: false, - updateRepo: vi.fn(), - removeProject: vi.fn() - }) - ) - ) - - expect(html).toContain('Worktree Hooks') - expect(html).toContain('Setup Script') - expect(html).toContain('Archive Script') - expect(html).toContain('Custom GitHub Issue Command') - } finally { - useAppStore.setState({ - settingsSearchQuery: '', - settingsSearchInputQuery: '' - }) - } - }) - - it('warns about live terminals and active tasks before project runtime changes', () => { - const worktreeId = 'repo-1::/tmp/repo' - useAppStore.setState({ - settingsSearchQuery: 'Example Repo', - settingsSearchInputQuery: 'Example Repo', - settings: getDefaultSettings('/tmp'), - tabsByWorktree: { - [worktreeId]: [terminalTab('tab-1', worktreeId), terminalTab('tab-2', worktreeId)] - }, - ptyIdsByTabId: { - 'tab-1': ['pty-1'], - 'tab-2': ['pty-2'] - }, - agentStatusByPaneKey: { - 'tab-2:0': activeAgent('tab-2:0', worktreeId) - } - }) - - act(() => { - root.render( - React.createElement( - TooltipProvider, - null, - React.createElement(RepositoryPane, { - repo, - project, - yamlHooks: null, - hasHooksFile: false, - hooksInspectionReady: true, - mayNeedUpdate: false, - updateRepo: vi.fn(), - removeProject: vi.fn(), - isLocalWindowsProject: true, - wslAvailable: true, - wslDistros: ['Ubuntu-24.04'], - wslCapabilitiesLoading: false, - updateProject: vi.fn() - }) - ) - ) - }) - - expect(container.textContent).toContain('2 live terminals') - expect(container.textContent).toContain('1 active task') - expect(container.textContent).toContain('finish or restart') - }) -}) diff --git a/src/renderer/src/components/settings/repository/execution/RepositoryWorktreeDefaultsSection.test.tsx b/src/renderer/src/components/settings/repository/execution/RepositoryWorktreeDefaultsSection.test.tsx index 42dbf781b1d..aa921749228 100644 --- a/src/renderer/src/components/settings/repository/execution/RepositoryWorktreeDefaultsSection.test.tsx +++ b/src/renderer/src/components/settings/repository/execution/RepositoryWorktreeDefaultsSection.test.tsx @@ -158,32 +158,9 @@ describe('RepositoryWorktreeDefaultsSection — worktree path', () => { expect(updateRepo).toHaveBeenCalledWith('repo-1', { worktreeBasePath: undefined }) }) - - it('calls updateRepo with undefined when the value is whitespace-only', () => { - const updateRepo = vi.fn() - render({ ...BASE_REPO, worktreeBasePath: '../worktrees' }, updateRepo) - - const input = getWorktreePathInput() - typeText(input, ' ') - blurInput(input) - - expect(updateRepo).toHaveBeenCalledWith('repo-1', { worktreeBasePath: undefined }) - }) }) describe('RepositoryWorktreeDefaultsSection — external visibility', () => { - it('shows the inherited effective value without stamping the repo', () => { - render(BASE_REPO, vi.fn(), { - settings: { - workspaceDir: '/home/user/orca/workspaces', - worktreeVisibilityDefaults: { external: 'show' } - } - }) - - expect(container.textContent).toContain('Using global: Show') - expect(container.querySelector('select')?.value).toBe('global') - }) - it('clears the override with the remote-safe null sentinel and refreshes classification', async () => { const updateRepo = vi.fn().mockResolvedValue(true) const refreshRepo = vi.fn().mockResolvedValue(true) diff --git a/src/renderer/src/components/sidebar/AddRemoteHostDialog.config-picker.test.tsx b/src/renderer/src/components/sidebar/AddRemoteHostDialog.config-picker.test.tsx index 90886e07cc0..1996cd49929 100644 --- a/src/renderer/src/components/sidebar/AddRemoteHostDialog.config-picker.test.tsx +++ b/src/renderer/src/components/sidebar/AddRemoteHostDialog.config-picker.test.tsx @@ -89,28 +89,7 @@ async function openPicker(): Promise> { return user } -async function openPickerWith(result: Record): Promise { - listConfigHosts.mockResolvedValue({ hasMore: false, ...result }) - const user = userEvent.setup() - render() - await user.click(screen.getByRole('button', { name: /Fill from/ })) -} - describe('SSH config picker host selection', () => { - it('drops a resolve that lands after the user backs out of the picker', async () => { - const pending = deferred() - resolveConfigHost.mockReturnValueOnce(pending.promise) - const user = await openPicker() - - await user.click(screen.getByRole('button', { name: /alpha/ })) - await user.click(screen.getByRole('button', { name: 'Back' })) - pending.resolve(resolution('alpha', 'alpha.internal')) - await waitFor(() => expect(screen.getByLabelText('Host or alias')).toBeDefined()) - - expect((screen.getByLabelText('Host or alias') as HTMLInputElement).value).toBe('') - expect(toastMocks.success).not.toHaveBeenCalled() - }) - it('keeps the host the user settled on when an earlier resolve lands late', async () => { const slowAlpha = deferred() resolveConfigHost.mockReturnValueOnce(slowAlpha.promise) @@ -136,76 +115,4 @@ describe('SSH config picker host selection', () => { 'bravo.internal' ) }) - - it('freezes the other rows while a pick is resolving', async () => { - const pending = deferred() - resolveConfigHost.mockReturnValueOnce(pending.promise) - const user = await openPicker() - - await user.click(screen.getByRole('button', { name: /alpha/ })) - - await waitFor(() => - expect(screen.getByRole('button', { name: /bravo/ }).hasAttribute('disabled')).toBe(true) - ) - await act(async () => { - pending.resolve(resolution('alpha', 'alpha.internal')) - await pending.promise - }) - }) -}) - -describe('SSH config picker tombstoned hosts', () => { - it('keeps a previously removed host pickable while an adopted one stays disabled', async () => { - await openPickerWith({ - hosts: [ - configHost('removed', { previouslyRemoved: true }), - configHost('kept', { alreadyInOrca: true }) - ], - totalHostCount: 2, - newHostCount: 0, - matchCount: 2 - }) - - const removed = await screen.findByRole('button', { name: /removed/ }) - expect(removed.hasAttribute('disabled')).toBe(false) - expect(removed.textContent).toContain('Removed from Orca') - - const kept = screen.getByRole('button', { name: /kept/ }) - expect(kept.hasAttribute('disabled')).toBe(true) - expect(kept.textContent).toContain('In Orca') - }) - - it('never claims the config is empty when every host is only tombstoned', async () => { - await openPickerWith({ - hosts: [configHost('removed', { previouslyRemoved: true })], - totalHostCount: 1, - newHostCount: 0, - matchCount: 1 - }) - - const addAll = await screen.findByRole('button', { name: 'No new hosts to add' }) - expect(addAll.hasAttribute('disabled')).toBe(true) - expect(screen.queryByText('No hosts in ~/.ssh/config')).toBeNull() - }) - - it('still shows the empty state when the config really has no hosts', async () => { - await openPickerWith({ hosts: [], totalHostCount: 0, newHostCount: 0, matchCount: 0 }) - - expect(await screen.findByText('No hosts in ~/.ssh/config')).toBeDefined() - expect(screen.getByRole('button', { name: 'Add all to Orca' }).hasAttribute('disabled')).toBe( - true - ) - }) -}) - -describe('SSH config picker bulk add', () => { - it('adds only new hosts and never re-adopts deleted aliases', async () => { - const user = await openPicker() - - await user.click(screen.getByRole('button', { name: /Add all 2 to Orca/ })) - - await waitFor(() => expect(importConfig).toHaveBeenCalled()) - expect(importConfig).toHaveBeenCalledWith() - expect(importConfig.mock.calls[0][0]).toBeUndefined() - }) }) diff --git a/src/renderer/src/components/sidebar/AddRepoDialogStepContent.test.tsx b/src/renderer/src/components/sidebar/AddRepoDialogStepContent.test.tsx deleted file mode 100644 index 69dcdab7231..00000000000 --- a/src/renderer/src/components/sidebar/AddRepoDialogStepContent.test.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import { renderToStaticMarkup } from 'react-dom/server' -import { describe, expect, it, vi } from 'vitest' -import type { ComponentProps } from 'react' -import { Dialog } from '@/components/ui/dialog' -import { TooltipProvider } from '@/components/ui/tooltip' -import { AddRepoDialogStepContent } from './AddRepoDialogStepContent' -import type { NestedRepoScanResult } from '../../../../shared/project-group-types' - -const nestedScan: NestedRepoScanResult = { - selectedPath: '/workspace/platform', - selectedPathKind: 'non_git_folder', - repos: [ - { path: '/workspace/platform/api', displayName: 'api', depth: 1 }, - { path: '/workspace/platform/cli', displayName: 'cli', depth: 1 } - ], - truncated: false, - timedOut: false, - stopped: false, - durationMs: 5, - maxDepth: 3, - maxRepos: 100, - timeoutMs: null -} - -type StepContentProps = ComponentProps - -function renderStepContent(overrides: Partial): string { - const props: StepContentProps = { - step: 'nested', - isRuntimeEnvironmentActive: false, - activeRuntimeEnvironmentId: null, - repoCount: 1, - isAdding: false, - addProjectBusyLabel: null, - nestedScanInProgress: false, - nestedScanId: null, - serverPath: '', - isAddingServerPath: false, - cloneUrl: '', - cloneDestination: '', - cloneError: null, - cloneProgress: null, - isCloning: false, - sshTargets: [], - selectedTargetId: null, - remotePath: '', - remoteError: null, - isAddingRemote: false, - nestedScan, - nestedSelectedPaths: new Set(nestedScan.repos.map((repo) => repo.path)), - nestedGroupName: 'platform', - createName: '', - createParent: '', - createError: null, - isCreating: false, - createDefaultParent: '', - createGitAvailability: 'unknown', - createRuntimeParentStatus: 'idle', - createParentDefaultPending: false, - onBrowse: vi.fn(), - onOpenCloneStep: vi.fn(), - onOpenCreateStep: vi.fn(), - onOpenRemoteStep: vi.fn(), - onStopNestedScan: vi.fn(), - onServerPathChange: vi.fn(), - onAddServerPath: vi.fn(), - onSelectTarget: vi.fn(), - onRemotePathChange: vi.fn(), - onAddRemoteRepo: vi.fn(), - onConnectTarget: vi.fn(), - onCloneUrlChange: vi.fn(), - onCloneDestinationChange: vi.fn(), - onPickCloneDestination: vi.fn(), - onClone: vi.fn(), - onNestedGroupNameChange: vi.fn(), - onNestedSelectedPathsChange: vi.fn(), - onImportNestedRepos: vi.fn(), - onOpenNestedRootFolder: vi.fn(), - onCreateNameChange: vi.fn(), - onCreateParentChange: vi.fn(), - onPickCreateParent: vi.fn(), - onCreate: vi.fn(), - ...overrides - } - - return renderToStaticMarkup( - - - - - - ) -} - -function renderNestedStep(repoCount: number): string { - return renderStepContent({ repoCount }) -} - -describe('AddRepoDialogStepContent nested imports', () => { - it('asks the grouping question when no repos exist yet', () => { - const html = renderNestedStep(0) - - expect(html).toContain('Group these repositories?') - expect(html).toContain('aria-label="Group name"') - expect(html).toContain('Yes, import as group') - expect(html).toContain('No, import separately') - expect(html).not.toContain('>Import') - }) - - it('shows the same grouping import controls after a repo already exists', () => { - const html = renderNestedStep(1) - - expect(html).toContain('Group these repositories?') - expect(html).toContain('aria-label="Group name"') - expect(html).toContain('Yes, import as group') - expect(html).toContain('No, import separately') - expect(html).not.toContain('>Import') - }) - - it('offers opening the parent folder when nested import selection is empty', () => { - const html = renderStepContent({ nestedSelectedPaths: new Set() }) - - expect(html).toContain('No repositories are selected') - expect(html).toContain('Open as Folder') - expect(html).toMatch(/]*disabled=""[^>]*>No, import separately<\/button>/) - expect(html).toMatch(/]*disabled=""[^>]*>Yes, import as group<\/button>/) - }) - - it('offers host browsing for remote create project locations', () => { - const html = renderStepContent({ - step: 'create', - isRuntimeEnvironmentActive: true, - activeRuntimeEnvironmentId: 'env-1' - }) - - expect(html).toContain('Create a new project') - expect(html).toContain('host folder not selected') - }) - - it('opens the host browser for a filled path that Windows did not hand over', () => { - const remote = { - step: 'server-path' as const, - isRuntimeEnvironmentActive: true, - activeRuntimeEnvironmentId: 'env-1', - serverPath: '/home/me/app' - } - const browser = 'Navigate to a directory and click Select to choose it.' - expect(renderStepContent(remote)).toContain(browser) - expect(renderStepContent({ ...remote, serverPathPrefilled: true })).not.toContain(browser) - }) - - it('offers host browsing for remote clone destinations', () => { - const html = renderStepContent({ - step: 'clone', - isRuntimeEnvironmentActive: true, - activeRuntimeEnvironmentId: 'env-1' - }) - - expect(html).toContain('Clone from URL') - expect(html).toContain('aria-label="Browse host filesystem"') - }) - - it('falls back to the start step when no paired server reaches the SSH hosts', () => { - const html = renderStepContent({ step: 'remote', selectedTargetId: 'openclaw-2' }) - - expect(html).toContain('Browse folder') - expect(html).not.toContain('Host path') - }) - - it('uses the standard add step for remote Orca server hosts', () => { - const html = renderStepContent({ - step: 'add', - isRuntimeEnvironmentActive: true, - activeRuntimeEnvironmentId: 'env-1', - browseHostKind: 'runtime' - }) - - expect(html).toContain('Browse folder') - expect(html).toContain('Existing Git repository or folder on this host') - expect(html).toContain('Clone from URL') - expect(html).toContain('Create new project') - expect(html).not.toContain('Browse host') - expect(html).not.toContain('Create on host') - expect(html).not.toContain('Want to import many repos at once?') - }) - - it('opens the in-app filesystem browser for a paired runtime', () => { - const html = renderStepContent({ - step: 'server-path', - isRuntimeEnvironmentActive: true, - activeRuntimeEnvironmentId: 'paired-host' - }) - - expect(html).toContain('Browse host filesystem') - expect(html).toContain('Navigate to a directory and click Select to choose it.') - expect(html).toContain('Select folder') - }) -}) diff --git a/src/renderer/src/components/sidebar/AddRepoNestedImportStep.test.tsx b/src/renderer/src/components/sidebar/AddRepoNestedImportStep.test.tsx index 437f9e4d875..96d94e4d4e5 100644 --- a/src/renderer/src/components/sidebar/AddRepoNestedImportStep.test.tsx +++ b/src/renderer/src/components/sidebar/AddRepoNestedImportStep.test.tsx @@ -1,8 +1,9 @@ // @vitest-environment happy-dom -import { act, useState, type ComponentProps } from 'react' +import { act } from 'react' + import { createRoot, type Root } from 'react-dom/client' -import { renderToStaticMarkup } from 'react-dom/server' + import { afterEach, describe, expect, it, vi } from 'vitest' import { AddRepoNestedImportStep } from './AddRepoNestedImportStep' import { TooltipProvider } from '@/components/ui/tooltip' @@ -26,30 +27,6 @@ const scan: NestedRepoScanResult = { timeoutMs: null } -function renderStepMarkup( - overrides: Partial> = {} -): string { - return renderToStaticMarkup( - - - repo.path))} - isAdding={false} - scanInProgress={false} - onGroupNameChange={vi.fn()} - onSelectedPathsChange={vi.fn()} - onImport={vi.fn()} - onOpenAsFolder={vi.fn()} - onStopScan={vi.fn()} - {...overrides} - /> - - - ) -} - function findButton(container: HTMLElement, label: string): HTMLButtonElement { const button = Array.from(container.querySelectorAll('button')).find((entry) => entry.textContent?.includes(label) @@ -73,36 +50,6 @@ describe('AddRepoNestedImportStep', () => { container = null }) - it('asks whether the selected folder should be grouped', () => { - const html = renderStepMarkup() - - expect(html).toContain('Import repositories from folder') - expect(html).toContain('Found 3 repositories in') - expect(html).toContain('/workspace/platform') - expect(html).toContain('aria-label="Group name"') - expect(html).not.toContain('What is a') - expect(html).toContain('Group these repositories?') - expect(html).toContain('Choose this if these projects belong together') - expect(html).toContain('Orca will group them and let you work from the parent folder') - expect(html).toContain('No, import separately') - expect(html).toContain('Yes, import as group') - expect(html).toContain('payments/api') - expect(html).toContain('billing/api') - expect(html).not.toContain('disabled=""') - expect(html).not.toContain('>Back') - expect(html).not.toContain('Project group') - }) - - it('disables both import actions while scanning', () => { - const html = renderStepMarkup({ scanInProgress: true }) - - expect(html).toContain('Group these repositories?') - expect(html).toContain('No, import separately') - expect(html).toContain('Yes, import as group') - expect(html).toMatch(/]*disabled=""[^>]*>No, import separately<\/button>/) - expect(html).toMatch(/]*disabled=""[^>]*>Yes, import as group<\/button>/) - }) - it('maps the group choice to grouped import and the separate choice to separate import', () => { const onImport = vi.fn() const host = document.createElement('div') @@ -140,51 +87,6 @@ describe('AddRepoNestedImportStep', () => { expect(onImport).toHaveBeenNthCalledWith(2, 'separate') }) - it('shows progress only on the clicked import action', () => { - const onImport = vi.fn() - const host = document.createElement('div') - container = host - document.body.appendChild(host) - root = createRoot(host) - - function Harness(): React.JSX.Element { - const [isAdding, setIsAdding] = useState(false) - return ( - - - repo.path))} - isAdding={isAdding} - scanInProgress={false} - onGroupNameChange={vi.fn()} - onSelectedPathsChange={vi.fn()} - onImport={(mode) => { - onImport(mode) - setIsAdding(true) - }} - onOpenAsFolder={vi.fn()} - onStopScan={vi.fn()} - /> - - - ) - } - - act(() => { - root?.render() - }) - - act(() => { - findButton(host, 'Yes, import as group').click() - }) - - expect(onImport).toHaveBeenCalledWith('group') - expect(findButton(host, 'Yes, import as group').querySelector('.animate-spin')).not.toBeNull() - expect(findButton(host, 'No, import separately').querySelector('.animate-spin')).toBeNull() - }) - it('offers opening the parent folder when no repositories are selected', () => { const onOpenAsFolder = vi.fn() const host = document.createElement('div') diff --git a/src/renderer/src/components/sidebar/SetupGuideSidebarEntry.test.tsx b/src/renderer/src/components/sidebar/SetupGuideSidebarEntry.test.tsx index 6306f1e036d..64d684b190c 100644 --- a/src/renderer/src/components/sidebar/SetupGuideSidebarEntry.test.tsx +++ b/src/renderer/src/components/sidebar/SetupGuideSidebarEntry.test.tsx @@ -1,6 +1,5 @@ // @vitest-environment happy-dom -import { renderToStaticMarkup } from 'react-dom/server' import type { ReactNode } from 'react' import { act } from 'react' import { createRoot, type Root } from 'react-dom/client' @@ -83,17 +82,6 @@ function makeAllDoneProgress( }) } -function makeOnlyBrowserIncompleteProgress(): FeatureWallSetupProgress { - return makeAllDoneProgress({ - stepDone: { - ...makeAllDoneProgress().stepDone, - browser: false - }, - coreDoneCount: 7, - coreTotal: 8 - }) -} - const mountedRoots: Root[] = [] async function renderSetupGuideSidebarEntry(): Promise<{ @@ -132,41 +120,6 @@ describe('SetupGuideSidebarEntry', () => { mocks.useSetupGuideProgress.mockReturnValue(makeProgress()) }) - it('does not render before persisted UI hydration is ready', () => { - persistedUIReady = false - - expect(renderToStaticMarkup()).not.toContain('Onboarding checklist') - }) - - it('does not render before setup progress readiness settles', () => { - mocks.useSetupGuideProgress.mockReturnValue(makeProgress({ ready: false })) - - expect(renderToStaticMarkup()).not.toContain('Onboarding checklist') - }) - - it('does not render after setup is complete and progress is ready', () => { - mocks.useSetupGuideProgress.mockReturnValue(makeAllDoneProgress()) - - expect(renderToStaticMarkup()).not.toContain('Onboarding checklist') - }) - - it('renders for fresh active users when only the browser step is incomplete', () => { - mocks.useSetupGuideProgress.mockReturnValue(makeOnlyBrowserIncompleteProgress()) - - expect(renderToStaticMarkup()).toContain('Onboarding checklist') - }) - - it('does not render when the sidebar entry was dismissed with only browser incomplete', () => { - mocks.useSetupGuideProgress.mockReturnValue(makeOnlyBrowserIncompleteProgress()) - setupGuideSidebarDismissed = true - - expect(renderToStaticMarkup()).not.toContain('Onboarding checklist') - }) - - it('renders after persisted UI and setup progress are ready when setup is incomplete', () => { - expect(renderToStaticMarkup()).toContain('Onboarding checklist') - }) - it('keeps the visible entry mounted during transient setup progress refreshes', async () => { const { container, rerender } = await renderSetupGuideSidebarEntry() diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanSearchField.test.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanSearchField.test.tsx index 9b471e194ef..10dba81dcd3 100644 --- a/src/renderer/src/components/sidebar/WorkspaceKanbanSearchField.test.tsx +++ b/src/renderer/src/components/sidebar/WorkspaceKanbanSearchField.test.tsx @@ -3,8 +3,7 @@ import { act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import WorkspaceKanbanSearchField, { overlayReserve } from './WorkspaceKanbanSearchField' - +import WorkspaceKanbanSearchField from './WorkspaceKanbanSearchField' ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true let container: HTMLDivElement @@ -97,15 +96,6 @@ describe('WorkspaceKanbanSearchField', () => { expect(onClear).toHaveBeenCalledOnce() }) - it('hides the visual match count from assistive tech but keeps the clear button named', () => { - renderField({ query: 'orca', matchCount: 3, totalCount: 12 }) - - const count = container.querySelector('span[aria-hidden="true"]') - expect(count?.textContent).toBe('3 / 12') - expect(clearButton()?.getAttribute('aria-hidden')).toBeNull() - expect(clearButton()?.getAttribute('aria-label')).toBe('Clear search') - }) - it('withholds counts for text that never narrows the board', () => { renderField({ query: ' ', isFiltering: false, matchCount: 12, totalCount: 12 }) @@ -159,18 +149,6 @@ describe('WorkspaceKanbanSearchField', () => { expect(onClose).toHaveBeenCalledOnce() }) - it('says so when a query was discarded for length instead of silently not filtering', () => { - renderField({ query: 'x'.repeat(3000), isFiltering: false, isTooLarge: true }) - - expect(container.textContent).toContain('Too long') - expect(input().getAttribute('aria-invalid')).toBe('true') - expect(liveRegion().textContent).toContain('too long') - - renderField({ query: 'orca', matchCount: 3, totalCount: 12 }) - expect(container.textContent).not.toContain('Too long') - expect(input().getAttribute('aria-invalid')).toBeNull() - }) - it('leaves Escape to the IME while a composition is in progress', () => { renderField({ query: '検索', matchCount: 1, totalCount: 12 }) @@ -188,26 +166,4 @@ describe('WorkspaceKanbanSearchField', () => { expect(onClear).not.toHaveBeenCalled() expect(onClose).not.toHaveBeenCalled() }) - - it('keeps focus in the field after the clear button unmounts itself', () => { - renderField({ query: 'orca', matchCount: 3, totalCount: 12 }) - act(() => { - input().focus() - clearButton()?.click() - }) - - expect(onClear).toHaveBeenCalledOnce() - expect(document.activeElement).toBe(input()) - }) - - it('reserves overlay width in font-relative units, capped so text stays visible', () => { - // Multi-digit counters need font-relative width rather than a fixed reserve. - expect(overlayReserve('298 / 1024')).toContain('10ch') - expect(overlayReserve('3 / 9')).toContain('5ch') - - // Cap reserve so wide counters cannot consume the typed-text area. - expect(overlayReserve('298 / 1024')).toContain('55%') - - expect(overlayReserve(null)).toBe('32px') - }) }) diff --git a/src/renderer/src/components/sidebar/WorktreeCard.pinned-repo-icon.test.tsx b/src/renderer/src/components/sidebar/WorktreeCard.pinned-repo-icon.test.tsx deleted file mode 100644 index a65169caa33..00000000000 --- a/src/renderer/src/components/sidebar/WorktreeCard.pinned-repo-icon.test.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import { renderToStaticMarkup } from 'react-dom/server' -import type { ReactNode } from 'react' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { GlobalSettings } from '../../../../shared/global-settings-types' -import type { Repo } from '../../../../shared/repo-types' -import type { WorktreeCardProperty } from '../../../../shared/ui-chrome-types' -import type { Worktree } from '../../../../shared/worktree/types' - -const fetchHostedReviewForBranch = vi.fn() -const fetchIssue = vi.fn() -const fetchLinearIssue = vi.fn() -const openModal = vi.fn() -const updateWorktreeMeta = vi.fn() - -let worktreeCardProperties: WorktreeCardProperty[] = [] -let settings: Partial | null = null -const WORKTREE_CARD_IMPORT_TIMEOUT_MS = 15_000 - -vi.mock('@/store', () => { - const getState = () => ({ - deleteStateByWorktreeId: {}, - fetchHostedReviewForBranch, - fetchIssue, - fetchLinearIssue, - gitConflictOperationByWorktree: {}, - hostedReviewCache: {}, - issueCache: {}, - linearIssueCache: {}, - openModal, - projectGroups: [], - remoteBranchConflictByWorktreeId: {}, - settings, - sshConnectionStates: new Map(), - sshTargetLabels: new Map(), - updateWorktreeMeta, - workspacePortScan: null, - worktreeCardProperties - }) - return { - useAppStore: Object.assign((selector: (state: unknown) => unknown) => selector(getState()), { - getState, - subscribe: () => () => {} - }) - } -}) - -vi.mock('@/lib/worktree-activation', () => ({ - activateAndRevealWorktree: vi.fn() -})) - -vi.mock('@/components/ui/tooltip', () => ({ - Tooltip: ({ children }: { children: ReactNode }) => <>{children}, - TooltipContent: ({ children }: { children: ReactNode }) => <>{children}, - TooltipTrigger: ({ children }: { children: ReactNode }) => <>{children} -})) - -vi.mock('./use-worktree-activity-status', () => ({ - useWorktreeActivityStatus: () => 'idle' -})) - -vi.mock('./use-worktree-sleep-state', () => ({ - useIsSleepingWorktree: () => false -})) - -vi.mock('./CacheTimer', () => ({ - default: () => null, - usePromptCacheCountdownStartedAt: () => null -})) - -vi.mock('./WorktreeCardAgents', () => ({ - default: () => null -})) - -vi.mock('./WorktreeContextMenu', () => ({ - default: ({ children }: { children: ReactNode }) => <>{children}, - WORKTREE_NATIVE_CONTEXT_MENU_ATTR: 'data-worktree-native-context-menu', - WORKTREE_CONTEXT_MENU_SCOPE_ATTR: 'data-orca-context-menu-scope' -})) - -function makeRepo(overrides: Partial = {}): Repo { - return { - id: 'repo-1', - path: '/repo', - displayName: 'orca', - badgeColor: '#999999', - repoIcon: { type: 'emoji', emoji: '🦊' }, - addedAt: 1, - ...overrides - } -} - -function makeWorktree(overrides: Partial = {}): Worktree { - return { - id: 'repo-1::/repo/worktrees/pinned', - repoId: 'repo-1', - path: '/repo/worktrees/pinned', - displayName: 'Pinned tree', - branch: 'feature/pinned', - head: 'abc123', - isBare: false, - isMainWorktree: false, - comment: '', - linkedIssue: null, - linkedPR: null, - linkedLinearIssue: null, - isArchived: false, - isUnread: false, - isPinned: true, - sortOrder: 0, - lastActivityAt: 1, - ...overrides - } -} - -describe('WorktreeCard pinned repo icon', () => { - beforeEach(() => { - vi.clearAllMocks() - worktreeCardProperties = [] - settings = null - }) - - it( - 'shows the repo icon and project name for pinned cards even when the repo badge is hidden', - async () => { - const { default: WorktreeCard } = await import('./WorktreeCard') - - const markup = renderToStaticMarkup( - - ) - - expect(markup).toContain('🦊') - expect(markup).toContain('Project orca') - expect(markup).toContain('>orca') - }, - WORKTREE_CARD_IMPORT_TIMEOUT_MS - ) - - it( - 'does not render the leading pinned repo icon for non-pinned cards', - async () => { - const { default: WorktreeCard } = await import('./WorktreeCard') - - const markup = renderToStaticMarkup( - - ) - - expect(markup).not.toContain('🦊') - expect(markup).not.toContain('Project orca') - }, - WORKTREE_CARD_IMPORT_TIMEOUT_MS - ) - - it( - 'uses the pinned-style repo icon in new card style instead of a metadata-row badge', - async () => { - settings = { compactWorktreeCards: false, experimentalNewWorktreeCardStyle: true } - worktreeCardProperties = ['status'] - const { default: WorktreeCard } = await import('./WorktreeCard') - - const markup = renderToStaticMarkup( - - ) - - expect(markup).toContain('🦊') - expect(markup).toContain('Project orca') - expect(markup).toContain('>orca') - expect(markup).not.toContain('data-worktree-card-meta-row=""') - }, - WORKTREE_CARD_IMPORT_TIMEOUT_MS - ) - - it( - 'shows the project name on ungrouped compact cards and hides it when grouped by project', - async () => { - settings = { compactWorktreeCards: true, experimentalNewWorktreeCardStyle: false } - const { default: WorktreeCard } = await import('./WorktreeCard') - const worktree = makeWorktree({ isPinned: false, displayName: 'main' }) - - const ungrouped = renderToStaticMarkup( - - ) - expect(ungrouped).toContain('>orca') - expect(ungrouped).toContain('Project orca') - - const groupedByProject = renderToStaticMarkup( - - ) - expect(groupedByProject).not.toContain('>orca') - expect(groupedByProject).not.toContain('Project orca') - }, - WORKTREE_CARD_IMPORT_TIMEOUT_MS - ) -}) diff --git a/src/renderer/src/components/sidebar/WorktreeList.group-headers.test.ts b/src/renderer/src/components/sidebar/WorktreeList.group-headers.test.ts deleted file mode 100644 index f302fe14113..00000000000 --- a/src/renderer/src/components/sidebar/WorktreeList.group-headers.test.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { beforeAll, describe, expect, it, vi } from 'vitest' -import type { ProjectGroup } from '../../../../shared/project-group-types' -import type { Repo } from '../../../../shared/repo-types' -import { - createAppStoreModuleMock, - createDropdownMenuModuleMock, - createProjectHeaderDragModuleMock, - createReactVirtualModuleMock, - createTooltipModuleMock, - createVirtualizedScrollAnchorModuleMock, - createWorktreeCardAgentsModuleMock, - createWorktreeCardModuleMock, - createWorktreeContextMenuModuleMock, - createWorktreeTitleInlineRenameModuleMock, - loadWorktreeList, - mockStore, - renderWorktreeListMarkup -} from './worktree-list-lineage-card-test-harness' -import { - makeFolderWorkspacePathStatusMockState, - makeFolderWorkspacePathStatusState, - makeRepo -} from './worktree-list-lineage-card-test-fixtures' -import { setLineageFixtureState } from './worktree-list-lineage-store-state' -import { setPinnedFixtureState } from './worktree-list-pinned-store-state' - -vi.mock('@/store', () => createAppStoreModuleMock()) -vi.mock('@tanstack/react-virtual', () => createReactVirtualModuleMock()) -vi.mock('@/hooks/useVirtualizedScrollAnchor', () => createVirtualizedScrollAnchorModuleMock()) -vi.mock('./project-header-drag', () => createProjectHeaderDragModuleMock()) -vi.mock('./WorktreeCard', () => createWorktreeCardModuleMock()) -vi.mock('./WorktreeCardAgents', () => createWorktreeCardAgentsModuleMock()) -vi.mock('./WorktreeTitleInlineRename', () => createWorktreeTitleInlineRenameModuleMock()) -vi.mock('./WorktreeContextMenu', () => createWorktreeContextMenuModuleMock()) -vi.mock('@/components/ui/tooltip', () => createTooltipModuleMock()) -vi.mock('@/components/ui/dropdown-menu', () => createDropdownMenuModuleMock()) - -function setProjectGroupWithoutWorktreeRowsState( - filterRepoIds: string[] = [], - collapsedGroups = new Set() -): void { - const group: ProjectGroup = { - id: 'group-1', - name: 'Imported Services', - parentPath: '/tmp/imported-services', - parentGroupId: null, - createdFrom: 'folder-scan', - tabOrder: 0, - isCollapsed: false, - color: null, - createdAt: 1, - updatedAt: 1 - } - const repo: Repo = { - ...makeRepo(), - projectGroupId: group.id - } - - mockStore.state = { - ...makeFolderWorkspacePathStatusMockState(), - startupWorktreeRefreshCompleted: true, - activeModal: '', - activeView: 'terminal', - activeWorktreeId: null, - agentStatusEpoch: 0, - agentStatusByPaneKey: {}, - browserTabsByWorktree: {}, - clearPendingRevealWorktreeId: vi.fn(), - collapsedGroups, - deleteStateByWorktreeId: {}, - filterRepoIds, - ...makeFolderWorkspacePathStatusState(), - groupBy: 'repo', - hideDefaultBranchWorkspace: false, - issueCache: {}, - migrationUnsupportedByPtyId: {}, - openModal: vi.fn(), - pendingRevealWorktree: null, - prCache: {}, - prVisibleRefreshGeneration: 0, - projectGroups: [group], - ptyIdsByTabId: {}, - reorderRepos: vi.fn(), - reportVisibleGitHubPRRefreshCandidates: vi.fn(), - retainedAgentsByPaneKey: {}, - repos: [repo], - runtimePaneTitlesByTabId: {}, - setFilterRepoIds: vi.fn(), - setHideDefaultBranchWorkspace: vi.fn(), - setRenamingWorktreeId: vi.fn(), - setShowSleepingWorkspaces: vi.fn(), - setSortBy: vi.fn(), - settings: null, - renamingWorktreeId: null, - showSleepingWorkspaces: true, - sortBy: 'recent', - sortEpoch: 0, - sshConnectedGeneration: 0, - sshConnectionStates: new Map(), - sshTargetLabels: new Map(), - tabsByWorktree: {}, - terminalLayoutsByTabId: {}, - toggleCollapsedGroup: vi.fn(), - updateWorktreeMeta: vi.fn(), - updateWorktreesMeta: vi.fn(), - workspaceHostScope: 'all', - workspaceStatuses: [], - worktreeCardProperties: ['status', 'inline-agents'], - worktreeLineageById: {}, - worktreesByRepo: { - [repo.id]: [] - } - } -} - -describe('WorktreeList lineage child card renderer', () => { - beforeAll(async () => { - await loadWorktreeList() - }, 60_000) - - it('renders project group headers when repos import before worktree rows load', async () => { - setProjectGroupWithoutWorktreeRowsState() - const markup = await renderWorktreeListMarkup() - - expect(markup).toContain('Imported Services') - expect(markup).not.toContain('No workspaces found') - }) - - it('renders a collapse chevron on project group headers with children', async () => { - setProjectGroupWithoutWorktreeRowsState() - const markup = await renderWorktreeListMarkup() - - expect(markup).toContain('data-repo-header-collapse-affordance=""') - expect(markup).toContain('aria-expanded="true"') - }) - - it('renders collapsed project group header affordance state', async () => { - setProjectGroupWithoutWorktreeRowsState([], new Set(['project-group:group-1'])) - const markup = await renderWorktreeListMarkup() - - expect(markup).toContain('data-repo-header-collapse-affordance=""') - expect(markup).toContain('aria-expanded="false"') - expect(markup).toContain('-rotate-90') - }) - - it('does not render the project collapse affordance on flat section headers', async () => { - setLineageFixtureState('none') - const markup = await renderWorktreeListMarkup() - - expect(markup).not.toContain('data-repo-header-collapse-affordance=""') - }) - - it('renders a collapse chevron on status group headers with worktrees', async () => { - setLineageFixtureState('workspace-status') - const markup = await renderWorktreeListMarkup() - - expect(markup).toContain('In progress') - expect(markup).toContain('data-workspace-status-drop-target=""') - expect(markup).toContain('data-repo-header-collapse-affordance=""') - expect(markup).toContain('aria-expanded="true"') - }) - - it('renders a collapse chevron on the pinned section header with worktrees', async () => { - setPinnedFixtureState() - const markup = await renderWorktreeListMarkup() - - expect(markup).toContain('Pinned') - expect(markup).toContain('data-workspace-pin-drop-target=""') - expect(markup).toContain('data-repo-header-collapse-affordance=""') - expect(markup).toContain('aria-expanded="true"') - }) - - it('renders collapsed pinned section header affordance state', async () => { - setPinnedFixtureState() - mockStore.state = { - ...mockStore.state, - collapsedGroups: new Set(['pinned']) - } - const markup = await renderWorktreeListMarkup() - - expect(markup).toContain('data-workspace-pin-drop-target=""') - expect(markup).toContain('data-repo-header-collapse-affordance=""') - expect(markup).toContain('aria-expanded="false"') - expect(markup).toContain('-rotate-90') - }) - - it('renders a collapse chevron on grouped repo headers with worktrees', async () => { - setLineageFixtureState('repo') - const markup = await renderWorktreeListMarkup() - - expect(markup).toContain('data-repo-header-collapse-affordance=""') - expect(markup).toContain('data-repo-header-id="repo-1"') - expect(markup).toContain('aria-expanded="true"') - }) - - it('shows Clear Filters when filters exclude pre-worktree project groups', async () => { - setProjectGroupWithoutWorktreeRowsState(['another-repo']) - const markup = await renderWorktreeListMarkup() - - expect(markup).toContain('No workspaces found') - expect(markup).toContain('Clear Filters') - expect(markup).not.toContain('Imported Services') - }) -}) diff --git a/src/renderer/src/components/sidebar/delete-worktree-failure-toast.test.tsx b/src/renderer/src/components/sidebar/delete-worktree-failure-toast.test.tsx index 5a921379209..07c2ffca3b0 100644 --- a/src/renderer/src/components/sidebar/delete-worktree-failure-toast.test.tsx +++ b/src/renderer/src/components/sidebar/delete-worktree-failure-toast.test.tsx @@ -90,38 +90,6 @@ describe('showDeleteWorktreeFailureToast', () => { expect(onForceDelete).toHaveBeenCalled() }) - it('keeps non-forceable failures destructive without a force action', () => { - const onViewChanges = vi.fn() - - showDeleteWorktreeFailureToast({ - error: 'permission denied', - canForceDelete: false, - forceDeleteReason: null, - onViewChanges, - onForceDelete: vi.fn(), - onDeleteAnyway: vi.fn(), - worktreeId: 'wt-2', - worktreeName: 'feature/bar' - }) - - expect(toast.error).toHaveBeenCalledWith( - 'Failed to delete workspace feature/bar', - expect.objectContaining({ - id: 'delete-worktree-failure:|wt-2', - dismissible: true, - duration: 10000 - }) - ) - - const body = renderToastBody('error') - expect(body.textContent).toContain('permission denied') - expect(body.textContent).not.toContain('Force Delete') - - clickButton(body, 'View') - expect(toast.dismiss).toHaveBeenCalledWith('delete-worktree-failure:|wt-2') - expect(onViewChanges).toHaveBeenCalled() - }) - it('offers Delete Anyway when the archive hook refused the removal', () => { const onDeleteAnyway = vi.fn() @@ -150,80 +118,4 @@ describe('showDeleteWorktreeFailureToast', () => { expect(toast.dismiss).toHaveBeenCalledWith('delete-worktree-failure:|wt-archive') expect(onDeleteAnyway).toHaveBeenCalled() }) - - it('does not offer Delete Anyway for an ordinary failure', () => { - showDeleteWorktreeFailureToast({ - error: 'permission denied', - canForceDelete: false, - forceDeleteReason: null, - onViewChanges: vi.fn(), - onForceDelete: vi.fn(), - onDeleteAnyway: vi.fn(), - worktreeId: 'wt-plain', - worktreeName: 'feature/plain' - }) - - expect(renderToastBody('error').textContent).not.toContain('Delete Anyway') - }) - - it('offers neither force delete nor View for a locked workspace', () => { - const onViewChanges = vi.fn() - - showDeleteWorktreeFailureToast({ - error: 'Worktree is locked by Git.', - canForceDelete: false, - forceDeleteReason: null, - onViewChanges, - onForceDelete: vi.fn(), - onDeleteAnyway: vi.fn(), - worktreeId: 'wt-locked', - worktreeName: 'feature/locked' - }) - - const body = renderToastBody('info') - expect(body.textContent).toContain('This workspace is locked by Git.') - expect(body.textContent).toContain( - 'Run git worktree unlock from its repository, then retry deletion' - ) - expect(body.textContent).not.toContain('Force Delete') - expect(body.textContent).not.toContain('View') - expect(onViewChanges).not.toHaveBeenCalled() - }) - - it('keeps View for a locked workspace when changed files are known', () => { - showDeleteWorktreeFailureToast({ - error: 'Worktree is locked by Git.', - canForceDelete: false, - forceDeleteReason: null, - lockReason: 'active agent session', - hasKnownChanges: true, - onViewChanges: vi.fn(), - onForceDelete: vi.fn(), - onDeleteAnyway: vi.fn(), - worktreeId: 'wt-locked-dirty', - worktreeName: 'feature/locked-dirty' - }) - - const body = renderToastBody('info') - expect(body.textContent).toContain('Git reported: active agent session') - expect(body.textContent).toContain('View') - }) - it('offers a persistent nested deletion review for the exact failed target', () => { - Object.assign(window, { api: { worktrees: { previewNestedRemoval: vi.fn() } } }) - showDeleteWorktreeFailureToast({ - error: - 'Refusing to delete worktree because it contains another registered worktree: /parent/child', - canForceDelete: false, - forceDeleteReason: null, - onViewChanges: vi.fn(), - onForceDelete: vi.fn(), - onDeleteAnyway: vi.fn(), - worktreeId: 'repo::/parent', - worktreeName: 'parent', - nestedRemovalTarget: { id: 'repo::/parent', executionHostId: 'local' } - }) - const body = renderToastBody('error') - expect(body.textContent).toContain('Delete with nested worktrees…') - expect(vi.mocked(toast.error).mock.calls.at(-1)?.[1]?.duration).toBe(Infinity) - }) }) diff --git a/src/renderer/src/components/sidebar/preserved-branch-batch-toast.test.tsx b/src/renderer/src/components/sidebar/preserved-branch-batch-toast.test.tsx index cba83d4c26e..0a3cf2e7dda 100644 --- a/src/renderer/src/components/sidebar/preserved-branch-batch-toast.test.tsx +++ b/src/renderer/src/components/sidebar/preserved-branch-batch-toast.test.tsx @@ -155,17 +155,6 @@ describe('showPreservedBranchBatchToast', () => { }) }) - it('keeps older-server branches visible without offering an unsafe delete', () => { - showPreservedBranchBatchToast(3, [{ worktreeId: 'repo-a::one', branchName: 'feature/one' }]) - const body = renderToastBody() - - expect(body.querySelector('button')).toBeNull() - expect(toast.warning).toHaveBeenCalledWith( - '3 workspaces removed, 1 branch kept', - expect.not.objectContaining({ duration: Infinity }) - ) - }) - it('serializes branch deletion within one repository', async () => { let resolveFirst: (result: { ok: true; deleted: true }) => void = () => {} mocks.state.forceDeletePreservedBranch diff --git a/src/renderer/src/components/sidebar/workspace-reference-refresh-targets.test.ts b/src/renderer/src/components/sidebar/workspace-reference-refresh-targets.test.ts index 557b5ffaaa1..d4a1aeb864e 100644 --- a/src/renderer/src/components/sidebar/workspace-reference-refresh-targets.test.ts +++ b/src/renderer/src/components/sidebar/workspace-reference-refresh-targets.test.ts @@ -1,7 +1,7 @@ // @vitest-environment happy-dom import { afterEach, beforeEach, expect, it, vi } from 'vitest' import { useAppStore } from '@/store' -import { makeDetectedResult } from '@/store/slices/worktrees-detected-listing-fixtures' + import { createVisibleHostedReviewRefreshScheduler } from '@/store/github/visible-hosted-review-refresh-scheduler' import { getHostedReviewCacheKey } from '@/store/slices/hosted-review-cache-identity' import { getWorkspaceReferenceRefreshTargets } from './workspace-reference-refresh-targets' @@ -173,13 +173,3 @@ it('retains both explicit owners when equal visible workspace IDs exist on two h getWorkspaceReferenceRefreshTargets(useAppStore.getState(), { selectedOnly: true }) ).toHaveLength(1) }) - -it('admits attached reviews on visible external detected worktrees', () => { - useAppStore.setState({ - worktreesByRepo: {}, - detectedWorktreesByRepo: { - repo: makeDetectedResult('repo', [workspace]) - } - }) - expect(update()).toHaveLength(1) -}) diff --git a/src/renderer/src/components/status-bar/RuntimeHostStatusRow.test.tsx b/src/renderer/src/components/status-bar/RuntimeHostStatusRow.test.tsx index daefb0651ab..818bd3cde4c 100644 --- a/src/renderer/src/components/status-bar/RuntimeHostStatusRow.test.tsx +++ b/src/renderer/src/components/status-bar/RuntimeHostStatusRow.test.tsx @@ -1,7 +1,7 @@ // @vitest-environment happy-dom import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' -import { renderToStaticMarkup } from 'react-dom/server' + import type { ReactNode } from 'react' import { afterEach, describe, expect, it, vi } from 'vitest' import { RuntimeHostStatusRow } from './RuntimeHostStatusRow' @@ -38,104 +38,6 @@ vi.mock('@/components/ui/dropdown-menu', () => ({ afterEach(cleanup) describe('RuntimeHostStatusRow', () => { - it('renders reconnecting diagnostics for remote hosts', () => { - const markup = renderToStaticMarkup( - - ) - - expect(markup).toContain('Dev Box') - expect(markup).toContain('Reconnecting') - expect(markup).toContain('Attempt 3') - }) - - it('renders disconnected hosts with a connect action', () => { - const markup = renderToStaticMarkup( - {}} - /> - ) - - expect(markup).toContain('Dev Box') - expect(markup).toContain('Remote Server') - expect(markup).toContain('Disconnected') - expect(markup).toContain('Last closed: 1006') - expect(markup).toContain('Connect') - }) - - it('names the closed workspace window while keeping the disconnect action', () => { - const markup = renderToStaticMarkup( - {}} - onDisconnect={async () => {}} - /> - ) - - expect(markup).toContain('Dev Box') - expect(markup).toContain('Workspace window closed') - expect(markup).toContain('Disconnect') - expect(markup).not.toContain('>Connect') - // The host is still reachable — the row must not read as a lost connection. - expect(markup).not.toContain('Disconnected') - // Require the disconnect handler; a connect-only row must offer no action for an already reachable host. - expect( - renderToStaticMarkup( - {}} - /> - ) - ).not.toContain(' { - const markup = renderToStaticMarkup( - {}} /> - ) - - expect(markup).toContain('Dev Box') - expect(markup).toContain('Connected') - expect(markup).toContain('Disconnect') - }) - - it('renders transport-up runtime-unavailable hosts without implying process death', () => { - const { container } = render( - {}} - /> - ) - - expect(container.textContent).toContain('Orca unavailable') - expect(container.textContent).toContain('SSH transport is connected') - expect(container.textContent).toContain('may still be running') - expect(container.textContent).toContain('Disconnect') - expect(container.textContent).not.toContain('exited') - }) - - it('keeps healthy rows free of a submenu trigger', () => { - const { container } = render() - - expect(container.querySelector('[data-slot="dropdown-menu-sub-trigger"]')).toBeNull() - }) - - it('renders failing rows as submenu triggers', () => { - const { container } = render( - - ) - - const trigger = container.querySelector('[data-slot="dropdown-menu-sub-trigger"]') - expect(trigger).not.toBeNull() - expect(trigger?.querySelector('button')).toBeNull() - }) - it('keeps the row action invokable for failing hosts', async () => { const onConnect = vi.fn(async () => {}) render( @@ -151,51 +53,4 @@ describe('RuntimeHostStatusRow', () => { await waitFor(() => expect(onConnect).toHaveBeenCalledOnce()) }) - - it('renders a long raw error in full inside the submenu', () => { - const longError = `Invalid remote endpoint: ${'wss://relay.example.test/path/'.repeat(8)}` - const { container } = render( - - ) - - const submenu = container.querySelector('[data-slot="dropdown-menu-sub-content"]') - expect(submenu).not.toBeNull() - expect(submenu?.textContent).toContain(longError) - }) - - it('shows the existing connection diagnostics in the submenu', () => { - const { container } = render( - - ) - - const submenu = container.querySelector('[data-slot="dropdown-menu-sub-content"]') - expect(submenu?.textContent).toContain('Last connected') - expect(submenu?.textContent).toContain('Attempt 3') - }) }) diff --git a/src/renderer/src/components/status-bar/provider-segment-monthly-window.test.tsx b/src/renderer/src/components/status-bar/provider-segment-monthly-window.test.tsx index 302b4546fd7..98815f463e8 100644 --- a/src/renderer/src/components/status-bar/provider-segment-monthly-window.test.tsx +++ b/src/renderer/src/components/status-bar/provider-segment-monthly-window.test.tsx @@ -30,46 +30,7 @@ function windowOf( return { usedPercent, windowMinutes, resetsAt, resetDescription: null } } -// Grok unified-billing accounts surface a monthly window and nothing else. -function grokMonthlyLimits(status: ProviderRateLimits['status']): ProviderRateLimits { - return { - provider: 'grok', - session: null, - weekly: null, - monthly: windowOf(25, 43200), - updatedAt: Date.now(), - error: null, - status - } -} - describe('ProviderSegment monthly window', () => { - it('renders a monthly-only snapshot in the chip instead of a bare icon', async () => { - const { ProviderSegment } = await import('./StatusBar') - - const markup = renderToStaticMarkup( - - ) - - expect(markup).toContain('>25% 30d<') - }) - - it('shows monthly data while fetching instead of the loading placeholder', async () => { - const { ProviderSegment } = await import('./StatusBar') - - const markup = renderToStaticMarkup( - - ) - - expect(markup).toContain('>25% 30d<') - expect(markup).not.toContain('···') - }) - it('shows only the highest-used window when several windows exist', async () => { const { ProviderSegment } = await import('./StatusBar') @@ -78,7 +39,7 @@ describe('ProviderSegment monthly window', () => { session: windowOf(10, 300), weekly: windowOf(20, 10080), monthly: windowOf(30, 43200), - updatedAt: Date.now(), + updatedAt: 0, error: null, status: 'ok' } @@ -101,7 +62,7 @@ describe('ProviderSegment monthly window', () => { { ...windowOf(25, 300), name: 'Flash' }, { ...windowOf(80, 300), name: 'Pro' } ], - updatedAt: Date.now(), + updatedAt: 0, error: null, status: 'ok' } @@ -113,74 +74,6 @@ describe('ProviderSegment monthly window', () => { expect(markup).toContain('>80% Pro<') expect(markup).not.toContain('>25%') }) - - // Keep the chip’s countdown consistent with the popup for the same resetsAt. - it('shows remaining session time on the chip when resetsAt is known (repro-8378)', async () => { - const { ProviderSegment } = await import('./StatusBar') - const now = 1_700_000_000_000 - const dateNow = vi.spyOn(Date, 'now').mockReturnValue(now) - const remainingMs = 2 * 60 * 60_000 + 33 * 60_000 - - try { - const limits: ProviderRateLimits = { - provider: 'codex', - session: windowOf(42, 300, now + remainingMs), - weekly: windowOf(10, 10080, now + 6 * 24 * 60 * 60_000), - updatedAt: now, - error: null, - status: 'ok' - } - const markup = renderToStaticMarkup( - - ) - - expect(markup).toContain('>42% 2h 33m<') - expect(markup).not.toContain('5h') - // The consolidated footer intentionally renders only the tightest window. - expect(markup).not.toContain('>10%') - expect(markup).not.toContain('wk') - } finally { - dateNow.mockRestore() - } - }) - - it('shows the footer bar only in verbose mode', async () => { - const { ProviderSegment } = await import('./StatusBar') - const limits = grokMonthlyLimits('ok') - - const verbose = renderToStaticMarkup( - - ) - const compact = renderToStaticMarkup( - - ) - - expect(verbose).toContain('data-usage-bar') - expect(compact).not.toContain('data-usage-bar') - }) - - it('restores every inline window in verbose mode', async () => { - const { ProviderSegment } = await import('./StatusBar') - const limits: ProviderRateLimits = { - provider: 'claude', - session: windowOf(10, 300), - weekly: windowOf(20, 10_080), - fableWeekly: windowOf(30, 10_080), - monthly: windowOf(40, 43_200), - updatedAt: Date.now(), - error: null, - status: 'ok' - } - - const markup = renderToStaticMarkup( - - ) - - expect(markup).toContain('>10% 5h<') - expect(markup).toContain('>20% wk<') - expect(markup).toContain('>30% Fable<') - expect(markup).not.toContain('>40%') - }) }) describe('undefined provider window safety (crash d2c1da69 / bb74236c)', () => { @@ -189,10 +82,11 @@ describe('undefined provider window safety (crash d2c1da69 / bb74236c)', () => { // old `s.window !== null` filter let the undefined-window section through, so // getTightestUsageSection's reduce read `.usedPercent` of undefined and crashed // the status-bar overlay (TypeError in ProviderSegment). + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Deliberately omit session to reproduce malformed persisted provider data. const partialProvider = { provider: 'codex', weekly: windowOf(42, 10080), - updatedAt: Date.now(), + updatedAt: 0, error: null, status: 'ok' } as unknown as ProviderRateLimits // `session` omitted -> undefined at runtime diff --git a/src/renderer/src/components/status-bar/resource-session-bindings.test.ts b/src/renderer/src/components/status-bar/resource-session-bindings.test.ts index fb646551f94..30c4647a314 100644 --- a/src/renderer/src/components/status-bar/resource-session-bindings.test.ts +++ b/src/renderer/src/components/status-bar/resource-session-bindings.test.ts @@ -80,31 +80,6 @@ describe('resource session bindings', () => { expect(index.boundPtyIds.has('pty-closed')).toBe(false) }) - it('counts only daemon sessions without live or restorable tab bindings', () => { - const count = countUnboundDaemonSessions( - [ - { id: 'pty-live', cwd: '/workspace', title: 'live', agentOwnership: 'absent' as const }, - { - id: 'pty-restored', - cwd: '/workspace', - title: 'restored', - agentOwnership: 'absent' as const - }, - { id: 'pty-orphan', cwd: '/tmp', title: 'orphan', agentOwnership: 'absent' as const } - ], - { - ptyIdsByTabId: { 'tab-live': ['pty-live'] }, - tabsByWorktree: { - 'repo::/workspace': [makeTab('tab-live'), makeTab('tab-restored', 'pty-restored')] - }, - terminalLayoutsByTabId: {}, - workspaceSessionReady: true - } - ) - - expect(count).toBe(1) - }) - it('binds a session deferred for SSH reattach so it is never offered for cleanup', () => { const inputs = { ptyIdsByTabId: {}, @@ -158,32 +133,6 @@ describe('resource session bindings', () => { expect(countUnboundDaemonSessions(sessions, inputs)).toBe(1) }) - it('keeps the advertised count and the selected set identical', () => { - const inputs = { - ptyIdsByTabId: { 'tab-live': ['pty-live'] }, - tabsByWorktree: { 'repo::/workspace': [makeTab('tab-live'), makeTab('tab-ssh')] }, - terminalLayoutsByTabId: {}, - deferredSshSessionIdsByTabId: { 'tab-ssh': 'pty-deferred' }, - workspaceSessionReady: true - } - const sessions = [ - { id: 'pty-live', cwd: '/workspace', title: 'live', agentOwnership: 'absent' as const }, - { - id: 'pty-deferred', - cwd: '/workspace', - title: 'deferred', - agentOwnership: 'absent' as const - }, - { id: 'pty-agent', cwd: '/workspace', title: 'codex', agentOwnership: 'present' as const }, - { id: 'pty-orphan', cwd: '/tmp', title: 'orphan', agentOwnership: 'absent' as const } - ] - - expect(selectUnboundDaemonSessions(sessions, inputs).map((s) => s.id)).toEqual(['pty-orphan']) - expect(countUnboundDaemonSessions(sessions, inputs)).toBe( - selectUnboundDaemonSessions(sessions, inputs).length - ) - }) - it('never offers a session whose ownership could not be established', () => { // Older daemons/relays may omit ownership claims; unknown cannot establish owner absence. const inputs = { diff --git a/src/renderer/src/components/status-bar/use-owner-usage-rate-limits.test.tsx b/src/renderer/src/components/status-bar/use-owner-usage-rate-limits.test.tsx index 7f77ccf0f61..31f63315d83 100644 --- a/src/renderer/src/components/status-bar/use-owner-usage-rate-limits.test.tsx +++ b/src/renderer/src/components/status-bar/use-owner-usage-rate-limits.test.tsx @@ -75,29 +75,6 @@ describe('useOwnerUsageRateLimits', () => { }) afterEach(() => cleanup()) - it('opens no watcher and keeps local usage when this device owns accounts', () => { - const { result } = renderHook(() => useOwnerUsageRateLimits(local)) - expect(mocks.watchers).toHaveLength(0) - expect(result.current.rateLimits).toBe(local) - expect(result.current.refreshRemote).toBeNull() - expect(result.current.remoteOwner).toBeNull() - }) - - it("reads usage from the remote owner's accounts stream", () => { - mocks.owner = { kind: 'environment', environmentId: 'mini' } - const { result } = renderHook(() => useOwnerUsageRateLimits(local)) - expect(mocks.watchers[0].target).toEqual({ kind: 'environment', environmentId: 'mini' }) - expect(result.current.remoteOwner).toEqual({ environmentId: 'mini', label: 'Mac Mini' }) - expect(result.current.rateLimits.claude?.session).toBeNull() - - act(() => - mocks.watchers[0].handlers.onSnapshot( - snapshot(createEmptyRateLimitState({ claude: provider('claude', 64) })) - ) - ) - expect(result.current.rateLimits.claude?.session?.usedPercent).toBe(64) - }) - it('reports an older host without published usage as unavailable, not loading', () => { mocks.owner = { kind: 'environment', environmentId: 'mini' } const { result } = renderHook(() => useOwnerUsageRateLimits(local)) @@ -107,19 +84,6 @@ describe('useOwnerUsageRateLimits', () => { expect(result.current.rateLimits.claude?.error).toContain('Mac Mini does not report usage') }) - it('turns loss of contact into an unverifiable verdict that keeps known bars', () => { - mocks.owner = { kind: 'environment', environmentId: 'mini' } - const { result } = renderHook(() => useOwnerUsageRateLimits(local)) - act(() => - mocks.watchers[0].handlers.onSnapshot( - snapshot(createEmptyRateLimitState({ codex: provider('codex', 30) })) - ) - ) - act(() => mocks.watchers[0].handlers.onError(new Error('closed'))) - expect(result.current.rateLimits.codex?.session).toBeNull() - expect(result.current.rateLimits.codex?.error).toContain('cannot reach Mac Mini') - }) - it('never shows server A numbers under server B', () => { mocks.owner = { kind: 'environment', environmentId: 'mini' } const { result, rerender } = renderHook(() => useOwnerUsageRateLimits(local)) diff --git a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-facet-controls.test.tsx b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-facet-controls.test.tsx index 020f0268139..746bb61c0b3 100644 --- a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-facet-controls.test.tsx +++ b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-facet-controls.test.tsx @@ -92,17 +92,6 @@ describe('workspace cleanup facet controls', () => { container = null }) - it('shows a per-facet match count for every group', () => { - renderFacets(createDefaultWorkspaceCleanupFilterState(), createPatchMock()) - - const counts = [...(container?.querySelectorAll('[data-facet-count]') ?? [])].map( - (node) => node.textContent - ) - expect(counts).toHaveLength(10) - expect(counts).toContain('2/8') - expect(counts).toContain('3/8') - }) - it('takes a user-chosen idle threshold in days rather than a fixed enum', () => { const onPatch = createPatchMock() renderFacets(createDefaultWorkspaceCleanupFilterState(), onPatch) @@ -112,27 +101,6 @@ describe('workspace cleanup facet controls', () => { expect(onPatch).toHaveBeenCalledWith('activity', { idleMinDays: 17 }) }) - it('keeps every numeric threshold off type=number so a wheel tick cannot set it', () => { - // Assert input type because happy-dom cannot reproduce Chromium’s wheel-driven numeric changes. - renderFacets(createDefaultWorkspaceCleanupFilterState(), createPatchMock()) - - const numericFacets = [ - 'Idle for at least', - 'At least', - 'At most', - 'Commits ahead ≥', - 'Commits behind ≥' - ] - const found = numericFacets - .map((label) => control(label) as HTMLInputElement | null) - .filter((input): input is HTMLInputElement => input !== null) - expect(found).toHaveLength(numericFacets.length) - for (const input of found) { - expect(input.type).toBe('text') - expect(input.inputMode).toBe('numeric') - } - }) - it('multi-selects git states', () => { const onPatch = createPatchMock() const filters = createDefaultWorkspaceCleanupFilterState() @@ -144,14 +112,6 @@ describe('workspace cleanup facet controls', () => { expect(onPatch).toHaveBeenCalledWith('git', { states: ['dirty', 'unpushed'] }) }) - it('offers the hosts and providers discovered in the fleet, not a hardcoded list', () => { - renderFacets(createDefaultWorkspaceCleanupFilterState(), createPatchMock()) - - expect(control('GitLab')).not.toBeNull() - expect(container?.textContent).toContain('Alpha') - expect(container?.textContent).toContain('In progress') - }) - it('cycles a tri-state safety facet', () => { const onPatch = createPatchMock() renderFacets(createDefaultWorkspaceCleanupFilterState(), onPatch) @@ -160,15 +120,6 @@ describe('workspace cleanup facet controls', () => { expect(onPatch).toHaveBeenCalledWith('safety', { dismissed: 'only' }) }) - it('labels blocker matching without exposing model tokens', () => { - renderFacets(createDefaultWorkspaceCleanupFilterState(), createPatchMock()) - - expect(control('Blocker match: Has any')).not.toBeNull() - expect(control('Blocker match: Has none')).not.toBeNull() - expect(container?.textContent).not.toContain('any-of') - expect(container?.textContent).not.toContain('none-of') - }) - it('toggles the unsized escape hatch for the opt-in space scan', () => { const onPatch = createPatchMock() renderFacets(createDefaultWorkspaceCleanupFilterState(), onPatch) diff --git a/src/renderer/src/components/worktree-creation/WorktreeCreationPanel.test.tsx b/src/renderer/src/components/worktree-creation/WorktreeCreationPanel.test.tsx deleted file mode 100644 index fc09df43b19..00000000000 --- a/src/renderer/src/components/worktree-creation/WorktreeCreationPanel.test.tsx +++ /dev/null @@ -1,203 +0,0 @@ -// @vitest-environment happy-dom - -import { act } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import WorktreeCreationPanel from './WorktreeCreationPanel' -import type { PendingWorktreeCreation } from '@/lib/pending-worktree-creation' - -const mocks = vi.hoisted(() => ({ - state: { - pendingWorktreeCreations: { - 'create-1': { - creationId: 'create-1', - phase: 'creating', - status: 'creating', - startedAt: Date.now(), - indeterminate: false, - loaderVisible: true, - request: { - repoId: 'repo-1', - name: 'new-workspace', - displayName: 'New workspace', - setupDecision: 'skip', - agent: null, - pendingFirstAgentMessageRename: false, - note: '', - startupPlan: null, - quickPrompt: '', - quickTelemetry: null - } - } - } as Record - } -})) - -vi.mock('@/store', () => ({ - useAppStore: (selector: (state: typeof mocks.state) => unknown) => selector(mocks.state) -})) - -vi.mock('@/lib/worktree-creation-flow', () => ({ - retryBackgroundWorktreeCreation: vi.fn() -})) - -const roots: Root[] = [] - -async function renderPanel(reserveCollapsedSidebarHeaderSpace: boolean): Promise { - const container = document.createElement('div') - document.body.appendChild(container) - const root = createRoot(container) - roots.push(root) - - await act(async () => { - root.render( - - ) - }) - - return container -} - -describe('WorktreeCreationPanel', () => { - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - mocks.state.pendingWorktreeCreations['create-1'] = { - creationId: 'create-1', - phase: 'creating', - status: 'creating', - startedAt: Date.now(), - indeterminate: false, - loaderVisible: true, - request: { - repoId: 'repo-1', - name: 'new-workspace', - displayName: 'New workspace', - setupDecision: 'skip', - agent: null, - pendingFirstAgentMessageRename: false, - note: '', - startupPlan: null, - quickPrompt: '', - quickTelemetry: null - } - } - }) - - afterEach(() => { - roots.splice(0).forEach((root) => { - act(() => root.unmount()) - }) - document.body.replaceChildren() - }) - - it('keeps the faux creation tab visible', async () => { - const container = await renderPanel(false) - - expect(container.textContent).toContain('New workspace') - expect(container.textContent).toContain('Creating worktree…') - const title = [...container.querySelectorAll('span')].find( - (node) => node.textContent === 'New workspace' - ) - expect(title?.closest('div')?.className).toContain('border-r') - }) - - it('reserves collapsed left-titlebar space before the faux tab', async () => { - const container = await renderPanel(true) - const title = [...container.querySelectorAll('span')].find( - (node) => node.textContent === 'New workspace' - ) - const spacer = title?.closest('div')?.previousElementSibling as HTMLElement | null - - expect(spacer?.style.width).toBe('var(--collapsed-sidebar-header-width)') - }) - - it('does not reserve left-titlebar space when the header is not floating', async () => { - const container = await renderPanel(false) - const title = [...container.querySelectorAll('span')].find( - (node) => node.textContent === 'New workspace' - ) - - expect(title?.closest('div')?.previousElementSibling).toBeNull() - }) - - it('shows provisioning logs while a VM recipe is running', async () => { - mocks.state.pendingWorktreeCreations['create-1'] = { - ...mocks.state.pendingWorktreeCreations['create-1'], - phase: 'provisioning-vm', - provisioningLog: 'creating sandbox\nstarting orca serve\n' - } - - const container = await renderPanel(false) - - expect(container.textContent).toContain('Provisioning VM') - expect(container.querySelector('pre')?.textContent).toBe( - 'creating sandbox\nstarting orca serve\n' - ) - expect(container.querySelector('pre')?.className).toContain('h-72') - const cancel = [...container.querySelectorAll('button')].find( - (button) => button.textContent === 'Cancel' - ) - expect(cancel).toBeTruthy() - }) - - it('surfaces the captured recipe output when a VM recipe fails', async () => { - mocks.state.pendingWorktreeCreations['create-1'] = { - ...mocks.state.pendingWorktreeCreations['create-1'], - phase: 'provisioning-vm', - status: 'error', - error: 'Recipe exited with code 1.', - provisioningLog: 'pulling image…\nERROR: no space left on device\n' - } - - const container = await renderPanel(false) - - expect(container.textContent).toContain('Couldn’t create worktree') - expect(container.textContent).toContain('Recipe exited with code 1.') - const log = container.querySelector('pre') - expect(log?.textContent).toBe('pulling image…\nERROR: no space left on device\n') - expect(log?.className).toContain('h-72') - expect(container.textContent).toContain('Retry') - }) - - it("shows git's checkout meter as a labelled bar while the checkout runs", async () => { - mocks.state.pendingWorktreeCreations['create-1'] = { - ...mocks.state.pendingWorktreeCreations['create-1'], - checkoutProgress: { percent: 42, completed: 420, total: 1000 } - } - - const container = await renderPanel(false) - - const bar = container.querySelector('[role="progressbar"]') - expect(bar?.getAttribute('aria-valuenow')).toBe('42') - const label = document.getElementById(bar?.getAttribute('aria-labelledby') ?? '') - expect(label?.textContent).toBe('Checking out files… 42%') - }) - - it('shows no bar when git printed no checkout meter, or after the create failed', async () => { - expect((await renderPanel(false)).querySelector('[role="progressbar"]')).toBeNull() - - mocks.state.pendingWorktreeCreations['create-1'] = { - ...mocks.state.pendingWorktreeCreations['create-1'], - status: 'error', - error: 'git worktree add failed', - checkoutProgress: { percent: 42, completed: 420, total: 1000 } - } - expect((await renderPanel(false)).querySelector('[role="progressbar"]')).toBeNull() - }) - - it('omits the recipe output panel for a non-VM creation failure', async () => { - mocks.state.pendingWorktreeCreations['create-1'] = { - ...mocks.state.pendingWorktreeCreations['create-1'], - status: 'error', - error: 'git worktree add failed' - } - - const container = await renderPanel(false) - - expect(container.textContent).toContain('git worktree add failed') - expect(container.querySelector('pre')).toBeNull() - }) -}) diff --git a/src/renderer/src/hooks/composer-state/attachment-drop-failure.test.tsx b/src/renderer/src/hooks/composer-state/attachment-drop-failure.test.tsx index 07ad128ac63..18c28ff5846 100644 --- a/src/renderer/src/hooks/composer-state/attachment-drop-failure.test.tsx +++ b/src/renderer/src/hooks/composer-state/attachment-drop-failure.test.tsx @@ -83,123 +83,4 @@ describe('local composer drop failures', () => { expect(attached).toHaveLength(9) expect(attached).not.toContain('/drop/bad-1.png') }) - - it('stays silent when every dropped path attaches', async () => { - const { result } = renderDropState(() => {}) - - await act(async () => { - await result.current.applyLocalComposerDrop(['/drop/ok-0.png', '/drop/ok-1.png']) - }) - - expect(mocks.toastError).not.toHaveBeenCalled() - }) - - it('says nothing once the composer that owned the drop is gone', async () => { - const { result } = renderDropState(() => {}) - - await act(async () => { - await result.current.applyLocalComposerDrop(dropPaths(12), () => false) - }) - - expect(mocks.toastError).not.toHaveBeenCalled() - }) -}) - -// Why: the upload branch returns early unless a runtime environment or connection is resolved. -const RUNTIME_ENVIRONMENT_ID = 'env-1' - -describe('composer upload failures', () => { - it('aggregates a mixed runtime import into one toast, and withholds a reason that is not shared', async () => { - mocks.importExternalPaths.mockResolvedValue({ - results: [ - { - sourcePath: '/a.png', - status: 'imported', - destPath: '/repo/.orca/drops/a.png', - kind: 'file', - renamed: false - }, - { sourcePath: '/b.png', status: 'skipped', reason: 'permission-denied' }, - { sourcePath: '/c.png', status: 'failed', reason: 'disk full' } - ] - }) - const { result } = renderDropState(() => {}) - - await act(async () => { - await result.current.uploadComposerPaths( - ['/a.png', '/b.png', '/c.png'], - RUNTIME_ENVIRONMENT_ID, - null, - '/repo' - ) - }) - - expect(mocks.toastError).toHaveBeenCalledTimes(1) - const [title, options] = mocks.toastError.mock.calls[0] ?? [] - expect(title).toBe('2 of 3 items could not be attached.') - expect(options.description).toBeUndefined() - }) - - it('stays silent when every uploaded path imports', async () => { - mocks.importExternalPaths.mockResolvedValue({ - results: [ - { - sourcePath: '/a.png', - status: 'imported', - destPath: '/repo/.orca/drops/a.png', - kind: 'file', - renamed: false - } - ] - }) - const { result } = renderDropState(() => {}) - - await act(async () => { - await result.current.uploadComposerPaths(['/a.png'], RUNTIME_ENVIRONMENT_ID, null, '/repo') - }) - - expect(mocks.toastError).not.toHaveBeenCalled() - }) - - it('does not report after the composer that owned the upload is gone', async () => { - mocks.importExternalPaths.mockResolvedValue({ - results: [{ sourcePath: '/b.png', status: 'skipped', reason: 'missing' }] - }) - const { result } = renderDropState(() => {}) - - await act(async () => { - await result.current.uploadComposerPaths( - ['/b.png'], - RUNTIME_ENVIRONMENT_ID, - null, - '/repo', - null, - () => false - ) - }) - - expect(mocks.toastError).not.toHaveBeenCalled() - }) - - it('does give the shared reason when every uploaded path failed the same way', async () => { - mocks.importExternalPaths.mockResolvedValue({ - results: [ - { sourcePath: '/b.png', status: 'skipped', reason: 'permission-denied' }, - { sourcePath: '/c.png', status: 'skipped', reason: 'permission-denied' } - ] - }) - const { result } = renderDropState(() => {}) - - await act(async () => { - await result.current.uploadComposerPaths( - ['/b.png', '/c.png'], - RUNTIME_ENVIRONMENT_ID, - null, - '/repo' - ) - }) - - const [, options] = mocks.toastError.mock.calls[0] ?? [] - expect(options.description).toBe('Permission denied.') - }) }) diff --git a/src/renderer/src/hooks/useReadOnScreenAttention.floating-panel.test.ts b/src/renderer/src/hooks/useReadOnScreenAttention.floating-panel.test.ts index 24adba61d53..95911b713f9 100644 --- a/src/renderer/src/hooks/useReadOnScreenAttention.floating-panel.test.ts +++ b/src/renderer/src/hooks/useReadOnScreenAttention.floating-panel.test.ts @@ -159,26 +159,3 @@ describe('useReadOnScreenAttention — floating workspace panel visibility', () expect(selectFloatingWorkspaceHasUnread(state)).toBe(false) }) }) - -it('does not acknowledge the regular workspace for a colliding floating tab', () => { - vi.spyOn(document, 'hasFocus').mockReturnValue(true) - seedFloatingCompletion() - useAppStore.setState({ - activeView: 'terminal', - activeTabId: FLOATING_TAB_ID, - activeWorktreeId: 'regular', - unreadAgentCompletionPanes: { [FLOATING_PANE_KEY]: true } - }) - openFloatingPanel() - const read = vi.spyOn(useAppStore.getState(), 'readOnScreenAttention') - try { - renderHook(() => useReadOnScreenAttention()) - expect(read).toHaveBeenCalled() - expect( - read.mock.calls.flatMap(([screen]) => screen.targets.map((target) => target.worktreeId)) - ).not.toContain('regular') - } finally { - cleanup() - vi.restoreAllMocks() - } -}) diff --git a/src/renderer/src/lib/app-menu-paste.test.ts b/src/renderer/src/lib/app-menu-paste.test.ts index 6388a5913db..1a508ad7678 100644 --- a/src/renderer/src/lib/app-menu-paste.test.ts +++ b/src/renderer/src/lib/app-menu-paste.test.ts @@ -43,22 +43,6 @@ describe('app menu paste', () => { expect(performNativePaste).not.toHaveBeenCalled() }) - it('falls back to native paste when no Orca-owned target is focused', async () => { - const button = document.createElement('button') - document.body.appendChild(button) - button.focus() - const performNativePaste = vi.fn() - - const result = await handleAppMenuPasteRequest({ - readClipboardText: vi.fn(async () => 'text'), - performNativePaste, - dispatchOwnedPasteEvent: () => false - }) - - expect(result).toEqual({ status: 'native-fallback', reason: 'no-owned-target' }) - expect(performNativePaste).toHaveBeenCalledWith({ mode: 'paste' }) - }) - it('preserves paste-and-match-style when falling back to native paste', async () => { const performNativePaste = vi.fn() @@ -144,21 +128,6 @@ describe('app menu paste', () => { expect(performNativePaste).toHaveBeenCalledWith({ mode: 'paste' }) }) - it('native-pastes an image-only clipboard so the control can take the image', async () => { - const textarea = appendTextarea() - textarea.focus() - const performNativePaste = vi.fn() - - const result = await handleAppMenuPasteRequest({ - readClipboardText: vi.fn(async () => ''), - performNativePaste, - dispatchOwnedPasteEvent: () => false - }) - - expect(result).toEqual({ status: 'native-fallback', reason: 'no-clipboard-text' }) - expect(performNativePaste).toHaveBeenCalledWith({ mode: 'paste' }) - }) - it('does not native-fallback when clipboard read fails after focus moves', async () => { const textarea = appendTextarea('unchanged') const other = appendTextarea('other') @@ -186,33 +155,4 @@ describe('app menu paste', () => { expect(other.value).toBe('other') expect(performNativePaste).not.toHaveBeenCalled() }) - - it('rejects oversized focused text-control paste without native fallback', async () => { - const textarea = appendTextarea('unchanged') - textarea.focus() - const performNativePaste = vi.fn() - - const result = await handleAppMenuPasteRequest({ - readClipboardText: vi.fn(async () => { - throw new Error('Clipboard text is too large for this paste target.') - }), - performNativePaste, - dispatchOwnedPasteEvent: () => false - }) - - expect(result).toMatchObject({ - status: 'rejected', - target: 'text-control', - reason: 'too-large' - }) - expect(result.status === 'rejected' ? result.redactedDiagnostic : '').toContain( - 'reason=too-large' - ) - expect(result.status === 'rejected' ? result.redactedDiagnostic : '').toContain('durationMs=') - expect(result.status === 'rejected' ? result.redactedDiagnostic : '').toContain( - 'content=redacted' - ) - expect(textarea.value).toBe('unchanged') - expect(performNativePaste).not.toHaveBeenCalled() - }) }) diff --git a/src/renderer/src/runtime/local-structured-session-retired-epoch-repair.test.ts b/src/renderer/src/runtime/local-structured-session-retired-epoch-repair.test.ts index 64ef9707364..c56f24d7c94 100644 --- a/src/renderer/src/runtime/local-structured-session-retired-epoch-repair.test.ts +++ b/src/renderer/src/runtime/local-structured-session-retired-epoch-repair.test.ts @@ -4,10 +4,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import type { RuntimeMobileSessionTabsResult } from '../../../shared/runtime-types' import type { Tab } from '../../../shared/tab-types' import { applyLocalStructuredSessionTabSnapshots } from './local-structured-session-tabs-sync/snapshot-apply' -import { - resetLocalStructuredSessionVersionForTests, - localStructuredSessionEpochHistoryByWorktree -} from './local-structured-session-tabs-sync/inventory-generation-fence' +import { resetLocalStructuredSessionVersionForTests } from './local-structured-session-tabs-sync/inventory-generation-fence' + import type { WebSessionTabsSyncState } from './web-session-tabs-sync/state' import { resetWebSessionFocusIntentForTests } from './web-session-focus-intent' @@ -124,14 +122,6 @@ function replayUntilDrop( } describe('retired-epoch repair for a returning publisher', () => { - it('POSITIVE CONTROL: the same frame lands when no epoch has been retired', () => { - const applied = applyLocalStructuredSessionTabSnapshots(stateWithCoordinatorTerminal(), [ - frame(RENDERER_EPOCH, 7, 'claude-1') - ]) - - expect(chatTabs(applied)).toEqual(['Claude Chat']) - }) - it('drops the returning publisher and reports it to the repair lane', () => { const onRetiredEpochDrop = vi.fn() @@ -165,21 +155,4 @@ describe('retired-epoch repair for a returning publisher', () => { expect(chatTabs(redelivered)).toEqual([]) }) - - it('revives only the epoch authority names, leaving other generations fenced', () => { - const dropped = replayUntilDrop() - applyLocalStructuredSessionTabSnapshots( - dropped, - [frame(RENDERER_EPOCH, 7, 'claude-1')], - undefined, - undefined, - { authoritative: true } - ) - - // Authoritative census restored renderer epoch; delayed frames from displaced headless generation must remain retired. - const history = localStructuredSessionEpochHistoryByWorktree.get(WORKTREE) - expect(history?.current).toBe(RENDERER_EPOCH) - expect(history?.retired).toContain(HEADLESS_EPOCH) - expect(history?.retired).not.toContain(RENDERER_EPOCH) - }) }) diff --git a/src/renderer/src/runtime/web-runtime-session-tab-props.test.ts b/src/renderer/src/runtime/web-runtime-session-tab-props.test.ts index 1f971e859ef..2134abf6b4a 100644 --- a/src/renderer/src/runtime/web-runtime-session-tab-props.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-tab-props.test.ts @@ -117,49 +117,6 @@ describe('setWebRuntimeTabProps', () => { }) }) - it('maps mirrored browser/editor unified ids before setting host tab props', async () => { - vi.stubGlobal('__ORCA_WEB_CLIENT__', false) - mocks.getState.mockReturnValue(ownedBy(RUNTIME_ROW)) - mocks.resolveHostSessionTabIdForWebSessionTab.mockImplementation( - (_state, args: { tabId: string }) => - args.tabId === 'local-browser-unified' ? 'host-browser-unified' : null - ) - const runtimeCall = vi.fn().mockResolvedValue({ id: 'p', ok: true, result: { updated: true } }) - stubRuntimeEnvironmentBridge({ call: runtimeCall }) - - expect( - setWebRuntimeTabProps({ - worktreeId: WORKTREE_ID, - tabId: 'local-browser-unified', - color: '#3b82f6' - }) - ).toBe(true) - - await vi.waitFor(() => expect(runtimeCall).toHaveBeenCalledTimes(1)) - expect(runtimeCall).toHaveBeenCalledWith({ - selector: ENVIRONMENT_ID, - method: 'session.tabs.setTabProps', - params: { - worktree: `id:${WORKTREE_ID}`, - tabId: 'host-browser-unified', - color: '#3b82f6' - }, - timeoutMs: 15_000 - }) - }) - - it('no-ops for a worktree with no runtime environment (local tab)', () => { - vi.stubGlobal('__ORCA_WEB_CLIENT__', false) - mocks.getState.mockReturnValue(ownedBy({ hostId: 'local' })) - const runtimeCall = vi.fn() - stubRuntimeEnvironmentBridge({ call: runtimeCall }) - - expect( - setWebRuntimeTabProps({ worktreeId: WORKTREE_ID, tabId: 'local-tab', color: '#fff' }) - ).toBe(false) - expect(runtimeCall).not.toHaveBeenCalled() - }) - it('refuses a local twin of the paired server instead of pushing to either host', () => { vi.stubGlobal('__ORCA_WEB_CLIENT__', false) mocks.getState.mockReturnValue(ownedBy({ hostId: 'local' }, RUNTIME_ROW)) diff --git a/src/renderer/src/store/slices/project-removal-runtime-terminal-order.test.ts b/src/renderer/src/store/slices/project-removal-runtime-terminal-order.test.ts index 02b904f3b44..2c5a8aa0666 100644 --- a/src/renderer/src/store/slices/project-removal-runtime-terminal-order.test.ts +++ b/src/renderer/src/store/slices/project-removal-runtime-terminal-order.test.ts @@ -22,14 +22,6 @@ const remoteRepo: Repo = { executionHostId: 'runtime:env-1' } -const localRepo: Repo = { - id: 'app', - path: '/Users/me/app', - displayName: 'app', - badgeColor: '#000', - addedAt: 1 -} - const hostCalls: string[] = [] let hostKnowsProject = true let hostSelectorAmbiguous = false @@ -172,20 +164,3 @@ describe('project removal after its server is gone', () => { expect(store.getState().repos).toEqual([]) }) }) - -describe('project removal error toast', () => { - it('shows the host’s message without the IPC wrapper', async () => { - removeForHost.mockRejectedValue( - new Error("Error invoking remote method 'repos:removeForHost': Error: Project is busy") - ) - const store = createTestStore() - store.setState({ repos: [localRepo] }) - - await store.getState().removeProject('app', { hostId: 'local', errorFeedback: 'toast' }) - - expect(toast.error).toHaveBeenCalledWith( - 'Failed to remove project', - expect.objectContaining({ description: 'Project is busy' }) - ) - }) -}) diff --git a/src/renderer/src/store/store-identity-churn-probe.test.ts b/src/renderer/src/store/store-identity-churn-probe.test.ts index 04ae08e12d3..51fdb872254 100644 --- a/src/renderer/src/store/store-identity-churn-probe.test.ts +++ b/src/renderer/src/store/store-identity-churn-probe.test.ts @@ -76,16 +76,6 @@ describe('store identity churn probe', () => { expect(readStoreIdentityChurnReport()).toEqual([]) }) - it('does not flag a refresh that returns the original reference', () => { - const store = createProbeStore() - const original = store.getState().rows - armStoreIdentityChurnProbe() - - store.getState().refresh(original) - - expect(readStoreIdentityChurnReport()).toEqual([]) - }) - it('names the write site when capture is requested', () => { const store = createProbeStore() armStoreIdentityChurnProbe({ captureSites: true }) @@ -118,15 +108,6 @@ describe('store identity churn probe', () => { expect(churnFor('rows')).toBe(1) }) - it('ignores a write zustand itself drops as identical', () => { - const store = createProbeStore() - armStoreIdentityChurnProbe() - - store.setState((state) => state) - - expect(readStoreIdentityChurnReport()).toEqual([]) - }) - it('passes disarmed writes straight through without reading state', () => { const innerSet = vi.fn() const innerGet = vi.fn(() => ({ counter: 0 })) @@ -190,14 +171,6 @@ describe('store identity churn probe', () => { expect(churnFor('rows')).toBe(1) }) - it('records nothing while disarmed', () => { - const store = createProbeStore() - - store.getState().refresh([{ id: 'a', label: 'A' }]) - - expect(readStoreIdentityChurnReport()).toEqual([]) - }) - it('treats distinct class instances as changed rather than equal', () => { const store = create<{ value: unknown; put: (value: unknown) => void }>()( withStoreIdentityChurnProbe((set) => ({ diff --git a/src/renderer/src/store/workspace-layout-frame-store-patch.test.ts b/src/renderer/src/store/workspace-layout-frame-store-patch.test.ts index 30481d9596e..e23b72a2ccc 100644 --- a/src/renderer/src/store/workspace-layout-frame-store-patch.test.ts +++ b/src/renderer/src/store/workspace-layout-frame-store-patch.test.ts @@ -82,11 +82,6 @@ function withoutTab(frame: PublishedWorkspaceLayout, tabId: string): PublishedWo } describe('laying frame slices over the store', () => { - it('changes nothing for the frame the store already shows', async () => { - const { state, session, frame } = await restored() - expect(patchFor(state, session, frame)).toEqual({}) - }) - it('drops a terminal tab main closed, with its pane layout', async () => { const { state, session, frame } = await restored() const patch = patchFor(state, session, withoutTab(frame, 'tab-shell')) @@ -114,21 +109,6 @@ describe('laying frame slices over the store', () => { ).not.toHaveProperty('tab-shell') }) - it('gives a row the frame creates the window’s own fields for it', async () => { - const { state, session, frame } = await restored() - const shown = patchFor(state, session, withoutTab(frame, 'tab-shell')) - const before = { ...state, ...shown } - const fields = vi.fn((id: string) => - id === 'tab-shell' ? { pendingActivationSpawn: true } : null - ) - - const patch = patchFor(before, session, frame, undefined, fields) - - const row = patch.tabsByWorktree?.[GIT_KEY]?.find((tab) => tab.id === 'tab-shell') - expect(row?.pendingActivationSpawn).toBe(true) - expect(fields).toHaveBeenCalledTimes(1) - }) - it('keeps an open file’s editor state and place, and drops a file main closed', async () => { const { state, session, frame } = await restored() const file = state.openFiles.find((entry) => entry.worktreeId === GIT_KEY)! diff --git a/src/shared/source-control-tree.test.ts b/src/shared/source-control-tree.test.ts index bb5fd762161..99b5dc80a7b 100644 --- a/src/shared/source-control-tree.test.ts +++ b/src/shared/source-control-tree.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import type { GitBranchChangeEntry } from './git-diff-compare-types' + import type { GitStatusEntry } from './git-status-types' import { buildGitStatusSourceControlTree, @@ -24,22 +24,6 @@ function labels(nodes: ReturnType): string[] { } describe('buildSourceControlTree', () => { - it('groups changed files by path segments', () => { - const tree = buildSourceControlTree('unstaged', [ - entry({ path: 'src/components/Button.tsx' }), - entry({ path: 'src/index.ts' }), - entry({ path: 'README.md' }) - ]) - - expect(labels(flattenSourceControlTree(tree, new Set()))).toEqual([ - 'directory:src', - 'directory:src/components', - 'file:src/components/Button.tsx', - 'file:src/index.ts', - 'file:README.md' - ]) - }) - it('orders numbered sibling directories naturally, matching the file rows', () => { const tree = buildSourceControlTree('unstaged', [ entry({ path: 'migrations/100 - b/a.sql' }), @@ -104,23 +88,6 @@ describe('buildSourceControlTree', () => { ]) }) - it('groups committed branch entries with branch-scoped directory keys', () => { - const entries: GitBranchChangeEntry[] = [ - { path: 'packages/app/src/index.ts', status: 'modified' }, - { path: 'packages/app/package.json', status: 'added' } - ] - const tree = buildSourceControlTree('branch', entries) - - expect(labels(flattenSourceControlTree(tree, new Set()))).toEqual([ - 'directory:packages', - 'directory:packages/app', - 'directory:packages/app/src', - 'file:packages/app/src/index.ts', - 'file:packages/app/package.json' - ]) - expect(tree[0].key).toBe('dir::branch::packages') - }) - it('compacts single-child directory chains for visible source-control rows', () => { const tree = buildSourceControlTree('branch', [ { path: 'src/renderer/src/mockups.css', status: 'modified' }, @@ -160,27 +127,6 @@ describe('buildSourceControlTree', () => { ]) }) - it('can namespace directory keys without changing file entry areas', () => { - const tree = compactSourceControlTree( - buildGitStatusSourceControlTree('unstaged', [ - entry({ - path: 'src/conflict.ts', - conflictKind: 'both_modified', - conflictStatus: 'unresolved' - }) - ]) - ) - - const namespaced = namespaceSourceControlTreeDirectoryKeys(tree, 'conflicts') - const rows = flattenSourceControlTree(namespaced, new Set()) - const directory = rows.find((node) => node.type === 'directory') - const file = rows.find((node) => node.type === 'file') - - expect(directory?.key).toBe('dir::conflicts::src') - expect(file?.key).toBe('unstaged::src/conflict.ts') - expect(file?.area).toBe('unstaged') - }) - it('can preserve file node areas from mixed conflict section entries', () => { const tree = compactSourceControlTree( buildGitStatusSourceControlTree('unstaged', [