diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index c4c935d5ea1..db99e6e09b4 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -755,7 +755,6 @@ 3 test-no-wall-clock:src/main/wsl/orcad/wsl-orcad-real-distro-lane.test.ts 3 test-no-wall-clock:src/renderer/src/components/activity/activity-auto-mark-read-loop.react185.test.tsx 2 test-no-wall-clock:src/renderer/src/components/artifacts/ArtifactCollection.test.tsx -1 test-no-wall-clock:src/renderer/src/components/automations/use-automation-list-search.test.tsx 1 test-no-wall-clock:src/renderer/src/components/browser-pane/assemble-chrome/browser-workspace-pane.adoption-chrome.test.tsx 2 test-no-wall-clock:src/renderer/src/components/dashboard/useLiveDashboardSnapshot.test.ts 1 test-no-wall-clock:src/renderer/src/components/native-chat/NativeChatMessageList.opening-send.test.tsx diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index b2a32f817e7..74e924e64a8 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -2784,10 +2784,8 @@ { "file": "src/shared/ephemeral-vm-runtime-store-rollback.test.ts", "assertions": [ - "ordinary runtime bytes remain unchanged and create no feature sidecar", "malformed, future, oversized, and unknown feature data is preserved without poisoning v1 records", - "current-main poisoned bytes migrate to a rollback-readable projection", - "unchanged compatibility metadata is not rewritten during lifecycle updates" + "current-main poisoned bytes migrate to a rollback-readable projection" ] }, { diff --git a/config/scripts/ci/sharding/ci-shard-timings.json b/config/scripts/ci/sharding/ci-shard-timings.json index ec696f5c9f2..e8602250f7d 100644 --- a/config/scripts/ci/sharding/ci-shard-timings.json +++ b/config/scripts/ci/sharding/ci-shard-timings.json @@ -914,7 +914,6 @@ "src/main/automations/automation-wsl-move-refusal.test.ts": 2285, "src/main/automations/automation-zero-grace-tick-latency.test.ts": 2108, "src/main/automations/dispatch-refusal-wsl-move.test.ts": 577, - "src/main/automations/dispatch-tokens.test.ts": 120, "src/main/automations/external-automation-manager-cache.test.ts": 70, "src/main/automations/external-automation-owner-guard.test.ts": 332, "src/main/automations/external-automation-probe-scheduler.test.ts": 97, @@ -5631,17 +5630,14 @@ "src/renderer/src/components/automations/use-automation-list-search.test.tsx": 679, "src/renderer/src/components/automations/use-automation-runs-dashboard-payload.test.tsx": 6692, "src/renderer/src/components/automations/use-automation-runs-dashboard.test.tsx": 854, - "src/renderer/src/components/automations/use-external-automation-scope-retention.test.tsx": 232, "src/renderer/src/components/automations/use-selected-automation-run-history.test.tsx": 2104, "src/renderer/src/components/browser-favicon.test.tsx": 333, "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx": 6219, "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.drag-focus.test.tsx": 5826, "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.failure-remount.test.tsx": 3085, "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.markup.test.tsx": 4575, - "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.popup-notices.test.tsx": 6196, "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.test.tsx": 6541, "src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.test.tsx": 497, - "src/renderer/src/components/browser-pane/annotate/GrabConfirmationSheet.test.ts": 261, "src/renderer/src/components/browser-pane/annotate/MarkupOverlay.test.tsx": 1124, "src/renderer/src/components/browser-pane/annotate/browser-annotation-output.test.ts": 74, "src/renderer/src/components/browser-pane/annotate/browser-grab-toast-owner-cleanup.test.tsx": 1007, @@ -8689,34 +8685,23 @@ "src/shared/ephemeral-vm-recipes.test.ts": 160, "src/shared/ephemeral-vm-runtime-store-rollback.test.ts": 321, "src/shared/ephemeral-vm-runtime-store.test.ts": 195, - "src/shared/errno-code.test.ts": 50, - "src/shared/event-loop-yield.test.ts": 72, "src/shared/execution-host-registry-shared-control-connect.test.ts": 160, "src/shared/execution-host-registry.test.ts": 169, "src/shared/execution-host.test.ts": 56, - "src/shared/export-let-function-initializer-ban.test.ts": 2489, "src/shared/external-automation-jobs-file.test.ts": 72, "src/shared/external-worktree-inbox.test.ts": 69, "src/shared/feature-education-telemetry.test.ts": 36, - "src/shared/feature-interactions.test.ts": 47, "src/shared/feature-tips.test.ts": 54, - "src/shared/feature-wall-tour-depth.test.ts": 45, - "src/shared/feedback-image-limits.test.ts": 43, "src/shared/file-explorer-display-root.test.ts": 47, "src/shared/file-inventory-budget.test.ts": 44, "src/shared/file-links/file-link-location.test.ts": 40, - "src/shared/file-name-sort.test.ts": 47, - "src/shared/file-preview-kind.test.ts": 46, "src/shared/file-write-disk-precondition.test.ts": 56, - "src/shared/filesystem-watch-batch-window.test.ts": 48, "src/shared/filesystem-watch-event-buffer.test.ts": 42, - "src/shared/fish-binary-requirement.test.ts": 51, "src/shared/folder-workspaces/folder-workspace-execution-host.test.ts": 84, "src/shared/folder-workspaces/folder-workspace-worktree.test.ts": 104, "src/shared/folder-workspaces/folder-workspaces.test.ts": 131, "src/shared/foreground-process-selection.test.ts": 100, "src/shared/foreground-wrapper-agent.test.ts": 82, - "src/shared/generated-code-path.test.ts": 46, "src/shared/git/compatibility/git-binary-compatibility.test.ts": 377, "src/shared/git/diff/git-blob-absence.test.ts": 54, "src/shared/git/branches/git-branch-cleanup-real-git.test.ts": 883, diff --git a/src/main/ai-vault/session-scanner-scope.test.ts b/src/main/ai-vault/session-scanner-scope.test.ts index 1e9a41ee265..39f4b2faa17 100644 --- a/src/main/ai-vault/session-scanner-scope.test.ts +++ b/src/main/ai-vault/session-scanner-scope.test.ts @@ -109,27 +109,6 @@ describe('scanAiVaultSessions scope inclusion', () => { expect(ids(withScope)).toContain('old-in-scope-subdir') }) - it('does not duplicate sessions already in the capped result', async () => { - const root = await mkdtemp(join(tmpdir(), 'orca-ai-vault-scope-')) - tempRoots.push(root) - const claudeRoot = join(root, 'claude-projects') - - await writeClaudeSession({ - claudeRoot, - dirName: '-repo-app', - sessionId: 'recent-in-scope', - cwd: '/repo/app', - iso: '2026-06-24T00:00:00.000Z' - }) - - const result = await scanAiVaultSessions( - scopedScanOptions(claudeRoot, { limit: 50, scopePaths: ['/repo/app'] }) - ) - - const matches = result.sessions.filter((session) => session.sessionId === 'recent-in-scope') - expect(matches).toHaveLength(1) - }) - it('does not assign a native transcript to a WSL distro from cwd alone', async () => { const root = await mkdtemp(join(tmpdir(), 'orca-ai-vault-scope-')) tempRoots.push(root) diff --git a/src/main/ai-vault/session-scanner-timeline.test.ts b/src/main/ai-vault/session-scanner-timeline.test.ts index cd488347a7f..e20c5ed55ae 100644 --- a/src/main/ai-vault/session-scanner-timeline.test.ts +++ b/src/main/ai-vault/session-scanner-timeline.test.ts @@ -1,10 +1,5 @@ -import { describe, expect, it, vi } from 'vitest' -import { - cloneSessionAccumulator, - createAccumulator, - finalizeSession, - updateTimeline -} from './session-scanner-accumulator' +import { describe, expect, it } from 'vitest' +import { createAccumulator, finalizeSession, updateTimeline } from './session-scanner-accumulator' function accumulator() { return createAccumulator({ @@ -44,53 +39,4 @@ describe('session timeline bounds', () => { expect(state.createdAt).toBe(new Date(base - 1).toISOString()) expect(state.latestTimestampMs).toBe(base + 0.1) }) - - it('preserves pre-epoch and extended-year ISO timestamps', () => { - const state = accumulator() - updateTimeline(state, '+010000-01-01T00:00:00.000Z') - updateTimeline(state, '-000001-01-01T00:00:00.000Z') - updateTimeline(state, '1969-12-31T23:59:59.999Z') - expect(state.createdAt).toBe('-000001-01-01T00:00:00.000Z') - expect(state.updatedAt).toBe('+010000-01-01T00:00:00.000Z') - }) - - it('ignores invalid timestamps and retains the existing out-of-range error', () => { - const state = accumulator() - for (const timestamp of [null, undefined, '', 'bad-date', 0, -1, Number.NaN, Infinity]) { - updateTimeline(state, timestamp) - } - expect(state.createdAt).toBeNull() - expect(state.updatedAt).toBeNull() - expect(() => updateTimeline(state, 8_640_000_000_000_001)).toThrow(RangeError) - expect(state.createdAt).toBeNull() - expect(state.updatedAt).toBeNull() - }) - - it('keeps cloned parse-state bounds independent', () => { - const state = accumulator() - updateTimeline(state, '2026-01-02T00:00:00Z') - const clone = cloneSessionAccumulator(state) - updateTimeline(clone, '2026-01-01T00:00:00Z') - updateTimeline(clone, '2026-01-03T00:00:00Z') - expect(state.createdAt).toBe('2026-01-02T00:00:00.000Z') - expect(state.updatedAt).toBe('2026-01-02T00:00:00.000Z') - expect(clone.createdAt).toBe('2026-01-01T00:00:00.000Z') - expect(clone.updatedAt).toBe('2026-01-03T00:00:00.000Z') - }) - - it('does not reparse accumulated bounds for every numeric record', () => { - const state = accumulator() - const spy = vi.spyOn(Date, 'parse') - let parseCalls: number - try { - for (let index = 0; index < 1000; index += 1) { - updateTimeline(state, 1_700_000_000_000 + index) - } - parseCalls = spy.mock.calls.length - } finally { - spy.mockRestore() - } - expect(state.latestTimestampMs).toBe(1_700_000_000_999) - expect(parseCalls).toBe(0) - }) }) diff --git a/src/main/ai-vault/session-scanner-values.test.ts b/src/main/ai-vault/session-scanner-values.test.ts index 243232118bd..7090c7e1ce5 100644 --- a/src/main/ai-vault/session-scanner-values.test.ts +++ b/src/main/ai-vault/session-scanner-values.test.ts @@ -1,11 +1,9 @@ import { homedir } from 'node:os' import { join } from 'node:path' -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it } from 'vitest' import { extractFullFirstUserPromptText, extractPreviewContentText, - normalizeAgentSessionsDir, - normalizeFullFirstUserPromptText, normalizePrimeAgentSessionsDir, primeAgentSessionsDirFromEnv, normalizePreviewText, @@ -41,47 +39,12 @@ describe('AI Vault session scanner text values', () => { expect(extractPreviewContentText([{ type: 'text', text: line }], 'user')).toBe(line) }) - it('folds large preview text directly without full-string replacement', () => { - const replaceSpy = vi.spyOn(String.prototype, 'replace') - const hiddenContext = `${'SECRET\n'.repeat(10_000)}` - const result = normalizePreviewText(`${hiddenContext}\nVisible preview ${'copy '.repeat(120)}`) - const replaceCalls = replaceSpy.mock.calls.length - - expect(replaceCalls).toBe(0) - expect(result?.startsWith('Visible preview copy copy')).toBe(true) - expect(result).not.toContain('SECRET') - expect(result?.endsWith('...')).toBe(true) - }) - - it('stops reading preview array items after the bounded display text is settled', () => { - const unreadItem = {} - Object.defineProperty(unreadItem, 'text', { - get() { - throw new Error('later preview items should not be read') - } - }) - - const result = extractPreviewContentText([ - { type: 'text', text: 'Visible preview '.repeat(30) }, - unreadItem - ]) - const normalizedPreview = Array.from({ length: 30 }, () => 'Visible preview').join(' ') - - expect(result).toBe(`${normalizedPreview.slice(0, 217)}...`) - }) - it('keeps truncation from splitting surrogate pairs', () => { const result = normalizePreviewText(`${'a'.repeat(216)}😀tail`) expect(result).toBe(`${'a'.repeat(216)}...`) }) - it('preserves full first-prompt text including newlines for copy', () => { - const body = `First prompt line one\n\nline two ${'word '.repeat(100)}` - expect(normalizeFullFirstUserPromptText(body)).toBe(body.trim()) - expect(extractFullFirstUserPromptText([{ type: 'text', text: body }])).toBe(body.trim()) - }) - it('reads Codex input_text blocks and ignores tool blocks', () => { const body = `Review the PR\n\n${'detail '.repeat(50).trimEnd()}` expect(extractFullFirstUserPromptText([{ type: 'input_text', text: body }])).toBe(body) @@ -93,41 +56,6 @@ describe('AI Vault session scanner text values', () => { ).toBe('Please continue the editor refactor') }) - it('expands Pi and OMP agent homes to their session directories', () => { - expect(normalizeAgentSessionsDir('/agents/.pi', '.pi')).toBe('/agents/.pi/agent/sessions') - expect(normalizeAgentSessionsDir('/agents/.pi/agent', '.pi')).toBe('/agents/.pi/agent/sessions') - expect(normalizeAgentSessionsDir('/agents/.pi/agent/sessions', '.pi')).toBe( - '/agents/.pi/agent/sessions' - ) - - expect(normalizeAgentSessionsDir('/agents/.omp', '.omp')).toBe('/agents/.omp/agent/sessions') - expect(normalizeAgentSessionsDir('/agents/.omp/agent', '.omp')).toBe( - '/agents/.omp/agent/sessions' - ) - expect(normalizeAgentSessionsDir('/agents/.omp/agent/sessions', '.omp')).toBe( - '/agents/.omp/agent/sessions' - ) - }) - - it('maps any Prime Agent agent dir to its sessions child', () => { - expect(normalizePrimeAgentSessionsDir('/tmp/prime-agent')).toBe('/tmp/prime-agent/sessions') - expect(normalizePrimeAgentSessionsDir('/tmp/prime-agent///')).toBe('/tmp/prime-agent/sessions') - expect(normalizePrimeAgentSessionsDir('/agents/.prime')).toBe('/agents/.prime/sessions') - expect(normalizePrimeAgentSessionsDir('/agents/.prime/agent')).toBe( - '/agents/.prime/agent/sessions' - ) - }) - - it('still appends sessions when the agent dir is itself named sessions', () => { - expect(normalizePrimeAgentSessionsDir('/data/sessions')).toBe('/data/sessions/sessions') - }) - - it('expands a leading tilde in the agent dir', () => { - expect(normalizePrimeAgentSessionsDir('~/work/prime')).toBe( - join(homedir(), 'work', 'prime', 'sessions') - ) - }) - // Drive-relative paths are invalid on POSIX too; native Windows semantics need a Windows runner. it('falls back to the default root for every non-absolute agent dir', () => { const fallback = join(homedir(), '.prime', 'agent', 'sessions') @@ -151,16 +79,6 @@ describe('AI Vault session scanner text values', () => { describe('primeAgentSessionsDirFromEnv', () => { const defaultDir = join(homedir(), '.prime', 'agent', 'sessions') - it('defaults to the home agent dir when nothing is configured', () => { - expect(primeAgentSessionsDirFromEnv({})).toBe(defaultDir) - }) - - it('appends sessions to a configured agent dir', () => { - expect(primeAgentSessionsDirFromEnv({ PRIME_AGENT_CODING_AGENT_DIR: '/opt/prime' })).toBe( - '/opt/prime/sessions' - ) - }) - it('prefers the sessions-root overrides verbatim over the agent dir', () => { expect( primeAgentSessionsDirFromEnv({ diff --git a/src/main/ai-vault/shell-command-words.test.ts b/src/main/ai-vault/shell-command-words.test.ts index 97920230c9d..0c1229828f3 100644 --- a/src/main/ai-vault/shell-command-words.test.ts +++ b/src/main/ai-vault/shell-command-words.test.ts @@ -39,13 +39,6 @@ describe('shellCommands', () => { ['b', 'x', 'y'] ]) }) - - it('stays linear on long and unbalanced texts', () => { - const size = 2 * 1024 * 1024 - expect(commands('a'.repeat(size))[0]![0]).toHaveLength(size) - expect(commands(`"${' \\"'.repeat(size / 3)}`)).toHaveLength(1) - expect(commands(`'${" '".repeat(size / 2)}`)).toHaveLength(1) - }) }) describe('commandProgramIndex', () => { diff --git a/src/main/amp/hook-service.test.ts b/src/main/amp/hook-service.test.ts index b7ed91ddab5..3949890224e 100644 --- a/src/main/amp/hook-service.test.ts +++ b/src/main/amp/hook-service.test.ts @@ -64,46 +64,4 @@ describe('AmpHookService', () => { expect(skipped.state).toBe('partial') expect(existsSync(pluginPath)).toBe(true) }) - - it('reports partial for stale managed plugin content missing required handlers', () => { - const pluginPath = _internals.getPluginPath() - mkdirSync(dirname(pluginPath), { recursive: true }) - writeFileSync(pluginPath, `// ${_internals.AMP_PLUGIN_MARKER}\n`, 'utf-8') - - const status = new AmpHookService().getStatus() - - expect(status).toMatchObject({ - agent: 'amp', - state: 'partial', - managedHooksPresent: true - }) - expect(status.detail).toContain('missing required handlers') - }) - - it('reports partial when a stale managed plugin is missing the session reset handler', () => { - const pluginPath = _internals.getPluginPath() - mkdirSync(dirname(pluginPath), { recursive: true }) - writeFileSync( - pluginPath, - [ - `// ${_internals.AMP_PLUGIN_MARKER}`, - "amp.on('agent.start', () => {})", - "amp.on('tool.call', () => {})", - "amp.on('tool.result', () => {})", - "amp.on('agent.end', () => {})", - '/hook/amp', - '' - ].join('\n'), - 'utf-8' - ) - - const status = new AmpHookService().getStatus() - - expect(status).toMatchObject({ - agent: 'amp', - state: 'partial', - managedHooksPresent: true - }) - expect(status.detail).toContain('missing required handlers') - }) }) diff --git a/src/main/antigravity/hook-service.test.ts b/src/main/antigravity/hook-service.test.ts index 5cd628bb03b..20ae7b9d529 100644 --- a/src/main/antigravity/hook-service.test.ts +++ b/src/main/antigravity/hook-service.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { spawnSync } from 'node:child_process' -import { mkdirSync, mkdtempSync, readFileSync, existsSync, rmSync, writeFileSync } from 'node:fs' +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' @@ -26,7 +26,6 @@ const ANTIGRAVITY_POST_TOOL_USE_COMMAND = const ANTIGRAVITY_PRE_TOOL_USE_COMMAND = process.platform === 'win32' ? 'antigravity-pre-tool-use.cmd' : 'antigravity-hook.sh' const PRE_TOOL_USE_DECISION = '{"decision":"ask"}' -const POLICY_OVERRIDING_DECISIONS = ['allow', 'deny', 'force_ask', 'deny_unless_prior_grant'] function withPlatform(platform: NodeJS.Platform, run: () => T): T { const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') @@ -124,97 +123,6 @@ describe('AntigravityHookService', () => { } ) - it('installs Windows event wrappers without nested cmd quoting and replaces stale PreToolUse hooks', () => { - withPlatform('win32', () => { - const configPath = join(homeDir, '.gemini', 'config', 'hooks.json') - const staleScriptPath = join( - homeDir, - '.orca', - 'agent-hooks', - 'antigravity-hook.cmd' - ).replaceAll('/', '\\') - mkdirSync(dirname(configPath), { recursive: true }) - writeFileSync( - configPath, - `${JSON.stringify( - { - 'orca-status': { - PreToolUse: [ - { - matcher: '*', - hooks: [ - { - type: 'command', - command: `cmd /d /s /c "set "ORCA_ANTIGRAVITY_EVENT=PreToolUse" && call "${staleScriptPath}""` - } - ] - } - ] - } - }, - null, - 2 - )}\n` - ) - - const service = new AntigravityHookService() - const staleStatus = service.getStatus() - expect(staleStatus.state).toBe('partial') - expect(staleStatus.managedHooksPresent).toBe(true) - - const status = service.install() - - expect(status.state).toBe('installed') - - const config = JSON.parse(readFileSync(configPath, 'utf8')) as { - 'orca-status': Record< - string, - { matcher?: string; command?: string; hooks?: { command: string }[] }[] - > - } - expect(config['orca-status'].PreToolUse).toHaveLength(1) - - const expectedWrappers = { - PreInvocation: 'antigravity-pre-invocation.cmd', - PostInvocation: 'antigravity-post-invocation.cmd', - Stop: 'antigravity-stop.cmd', - PreToolUse: 'antigravity-pre-tool-use.cmd', - PostToolUse: 'antigravity-post-tool-use.cmd' - } - for (const [eventName, wrapperFileName] of Object.entries(expectedWrappers)) { - const definition = config['orca-status'][eventName][0] - const command = ['PreToolUse', 'PostToolUse'].includes(eventName) - ? definition.hooks?.[0]?.command - : definition.command - expect(createManagedCommandMatcher(wrapperFileName)(command)).toBe(true) - expect(command).not.toContain('cmd /d /s /c') - expect(command).not.toContain('ORCA_ANTIGRAVITY_EVENT') - - const wrapper = readFileSync(join(homeDir, '.orca', 'agent-hooks', wrapperFileName), 'utf8') - expect(wrapper).toContain(`set "ORCA_ANTIGRAVITY_EVENT=${eventName}"`) - expect(wrapper).toContain('call "%ORCA_ANTIGRAVITY_CORE%"') - if (eventName === 'PreToolUse') { - expect(wrapper).toContain(`echo ${PRE_TOOL_USE_DECISION}`) - } - for (const decision of POLICY_OVERRIDING_DECISIONS) { - expect(wrapper).not.toContain(`{"decision":"${decision}"}`) - } - } - - const script = readFileSync( - join(homeDir, '.orca', 'agent-hooks', 'antigravity-hook.cmd'), - 'utf8' - ) - expect(script).toContain('antigravity-hook-post.cjs') - expect(script).not.toContain('powershell.exe') - expect(script).toContain('ELECTRON_RUN_AS_NODE=1') - expect( - readFileSync(join(homeDir, '.orca', 'agent-hooks', 'antigravity-hook-post.cjs'), 'utf8') - ).toContain('/hook/antigravity') - expect(script).toContain('setlocal DisableDelayedExpansion') - }) - }) - it('preserves the installed core and config when publishing the Windows reader fails', () => { withPlatform('win32', () => { const service = new AntigravityHookService() @@ -234,28 +142,6 @@ describe('AntigravityHookService', () => { }) }) - it('restores the owned Windows reader and resolves the current runtime on refresh', async () => { - vi.spyOn(process, 'platform', 'get').mockReturnValue('win32') - try { - const service = new AntigravityHookService() - let runtimePath = 'C:\\Orca1\\Orca.exe' - service.setWindowsRuntimePathProvider(() => runtimePath) - const readerPath = join(homeDir, '.orca', 'agent-hooks', 'antigravity-hook-post.cjs') - await service.refreshManagedScripts() - expect(existsSync(readerPath)).toBe(false) - expect(service.install().state).toBe('installed') - rmSync(readerPath) - runtimePath = 'C:\\Orca2\\Orca.exe' - await service.refreshManagedScripts() - expect(readFileSync(readerPath, 'utf8')).toContain("require('node:string_decoder')") - expect( - readFileSync(join(homeDir, '.orca', 'agent-hooks', 'antigravity-hook.cmd'), 'utf8') - ).toContain('ORCA_AGENT_HOOK_NODE=C:\\Orca2\\Orca.exe') - } finally { - vi.restoreAllMocks() - } - }) - it('preserves user-authored hook bundles and entries in Orca bundle', () => { const configPath = join(homeDir, '.gemini', 'config', 'hooks.json') mkdirSync(dirname(configPath), { recursive: true }) diff --git a/src/main/artifacts/artifact-cloud-recovery.test.ts b/src/main/artifacts/artifact-cloud-recovery.test.ts index 8008826464f..5c5a8890df0 100644 --- a/src/main/artifacts/artifact-cloud-recovery.test.ts +++ b/src/main/artifacts/artifact-cloud-recovery.test.ts @@ -74,23 +74,6 @@ describe('ArtifactCloudService committed response loss recovery', () => { expect(server.artifactContent('artifact-1')).toBe(writeRequest.content) }) - it('updates a recovered share when its content changed after response loss', async () => { - const userDataPath = await createUserDataPath() - const server = new ArtifactFaultServer() - server.loseNextCreateResponse = true - vi.stubGlobal('fetch', server.fetch) - - await expect(service(userDataPath).share(writeRequest)).rejects.toThrow('response lost') - await expect( - service(userDataPath).share({ ...writeRequest, content: '

Changed share

' }) - ).resolves.toMatchObject({ - status: 'ok', - value: { artifact: { slug: 'artifact-1' } } - }) - expect(server.createMutations).toBe(1) - expect(server.artifactContent('artifact-1')).toBe('

Changed share

') - }) - it('retains recovery until a changed-content update succeeds', async () => { const userDataPath = await createUserDataPath() const server = new ArtifactFaultServer() diff --git a/src/main/artifacts/artifact-cloud-service.test.ts b/src/main/artifacts/artifact-cloud-service.test.ts index f33405d72bd..05b9922687f 100644 --- a/src/main/artifacts/artifact-cloud-service.test.ts +++ b/src/main/artifacts/artifact-cloud-service.test.ts @@ -105,90 +105,6 @@ afterEach(async () => { }) describe('ArtifactCloudService record authorization', () => { - it('passes an opaque cursor and returns the complete list page', async () => { - const { service } = await setup() - const fetchMock = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ artifacts: [], nextCursor: 'next-page' }), { - status: 200, - headers: { 'content-type': 'application/json' } - }) - ) - vi.stubGlobal('fetch', fetchMock) - - await expect( - service.list({ apiUrl, authToken: 'token-a', cursor: 'opaque/+=' }) - ).resolves.toEqual({ - status: 'ok', - value: { artifacts: [], nextCursor: 'next-page' } - }) - expect(fetchMock).toHaveBeenCalledWith( - `${apiUrl}/v1/artifacts?cursor=opaque%2F%2B%3D`, - expect.any(Object) - ) - }) - - it('uses a distinct idempotency key for each logical share', async () => { - const { service } = await setup() - const fetchMock = vi - .fn() - .mockResolvedValueOnce(createResponse('artifact-a')) - .mockResolvedValueOnce(createResponse('artifact-b')) - vi.stubGlobal('fetch', fetchMock) - - await service.share(writeRequest) - await service.share({ ...writeRequest, sourceKey: '/repo/other.html' }) - - const firstKey = requestHeader(fetchMock, 0, 'idempotency-key') - const secondKey = requestHeader(fetchMock, 1, 'idempotency-key') - expect(firstKey).toMatch(/^[0-9a-f-]{36}$/) - expect(secondKey).toMatch(/^[0-9a-f-]{36}$/) - expect(firstKey).not.toBe(secondKey) - }) - - it('creates once and updates on repeated publish', async () => { - const { service } = await setup() - const fetchMock = vi - .fn() - .mockResolvedValueOnce(createResponse()) - .mockResolvedValueOnce(createResponse()) - vi.stubGlobal('fetch', fetchMock) - - await expect(service.publish(writeRequest)).resolves.toMatchObject({ - status: 'ok', - value: { change: 'created', item: { shareUrl: 'https://share.onorca.dev/a/artifact-a' } } - }) - await expect(service.publish(writeRequest)).resolves.toMatchObject({ - status: 'ok', - value: { change: 'updated', item: { shareUrl: 'https://share.onorca.dev/a/artifact-a' } } - }) - - expect(fetchMock.mock.calls[0]?.[1]).toMatchObject({ method: 'POST' }) - expect(fetchMock.mock.calls[1]?.[0]).toBe(`${apiUrl}/v1/artifacts/artifact-a`) - expect(fetchMock.mock.calls[1]?.[1]).toMatchObject({ method: 'PUT' }) - }) - - it('resolves a persisted public link only for its source and cloud scope', async () => { - const { service } = await setup() - const fetchMock = vi.fn().mockResolvedValueOnce(createResponse()) - vi.stubGlobal('fetch', fetchMock) - - await service.publish(writeRequest) - - await expect( - service.getPublishedLink({ sourceKey: writeRequest.sourceKey, apiUrl, authToken: 'token-a' }) - ).resolves.toEqual({ - status: 'ok', - value: { shareUrl: 'https://share.onorca.dev/a/artifact-a' } - }) - await expect( - service.getPublishedLink({ sourceKey: '/repo/other.html', apiUrl, authToken: 'token-a' }) - ).resolves.toEqual({ status: 'ok', value: null }) - await expect( - service.getPublishedLink({ sourceKey: writeRequest.sourceKey, apiUrl, authToken: 'token-b' }) - ).resolves.toEqual({ status: 'ok', value: null }) - expect(fetchMock).toHaveBeenCalledOnce() - }) - it('serializes concurrent publishes for the same source', async () => { const { service } = await setup() let resolveCreate: ((response: Response) => void) | undefined @@ -434,20 +350,6 @@ describe('ArtifactCloudService record authorization', () => { expect(fetchMock).toHaveBeenCalledOnce() }) - it('cleans all matching source mappings after delete by slug', async () => { - const { service } = await setup() - const fetchMock = vi - .fn() - .mockResolvedValueOnce(createResponse()) - .mockResolvedValueOnce(new Response(null, { status: 204 })) - vi.stubGlobal('fetch', fetchMock) - await service.share(writeRequest) - await service.delete('artifact-a', { apiUrl, authToken: 'token-a' }) - - await expect(service.update(writeRequest)).rejects.toThrow(/has not been shared/) - expect(fetchMock).toHaveBeenCalledTimes(2) - }) - it('keeps update and unshare working after an update extends expiration', async () => { vi.useFakeTimers({ toFake: ['Date'] }) vi.setSystemTime('2026-08-07T00:00:00.000Z') diff --git a/src/main/artifacts/artifact-create-intent-store.test.ts b/src/main/artifacts/artifact-create-intent-store.test.ts index 0376d11821b..cc0fa083633 100644 --- a/src/main/artifacts/artifact-create-intent-store.test.ts +++ b/src/main/artifacts/artifact-create-intent-store.test.ts @@ -15,15 +15,6 @@ import { getOrCreateArtifactCreateIntent, removeArtifactCreateIntent } from './artifact-create-intent-store' -import { runProcess, runProcessSync } from '@orca/process-host' -import { - resetWindowsSecurityLoadForTests, - setWindowsSecurityForTests -} from '../../shared/windows-security-addon' -import { - createFakeWindowsSecurity, - FAKE_USER_SID -} from '../../shared/windows-security-addon.test-fixture' import type { ArtifactShareScope } from './artifact-share-record-store' vi.mock('@orca/process-host', () => ({ @@ -179,53 +170,6 @@ describe('artifact create intent store', () => { expect((await readdir(directory)).some((name) => name.endsWith('.tmp'))).toBe(false) }) - it('writes each Windows intent born restricted and relocks the directory once, off the write', async () => { - const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - const fake = createFakeWindowsSecurity() - setWindowsSecurityForTests(fake) - try { - const userDataPath = await createUserDataPath() - const directory = join(userDataPath, 'profiles', 'local-profile', 'artifact-create-intents') - for (const [sourceKey, key] of [ - ['/repo/report.html', 'key-a'], - ['/repo/other.html', 'key-b'] - ]) { - getOrCreateArtifactCreateIntent('local-profile', userDataPath, sourceKey, scope, key, body) - } - await new Promise((resolve) => setTimeout(resolve, 0)) - - const creates = fake.callsFor('create') - expect(creates).toHaveLength(2) - for (const create of creates) { - expect(create.path.startsWith(directory)).toBe(true) - expect(create.path.endsWith('.tmp')).toBe(true) - expect(create).toMatchObject({ - sddl: `D:P(A;;FA;;;S-1-5-18)(A;;FA;;;S-1-5-32-544)(A;;FA;;;${FAKE_USER_SID})` - }) - } - expect(fake.callsFor('setAsync')).toMatchObject([ - { - op: 'setAsync', - path: directory, - sddl: `D:P(A;OICI;FA;;;S-1-5-18)(A;OICI;FA;;;S-1-5-32-544)(A;OICI;FA;;;${FAKE_USER_SID})` - } - ]) - expect(fake.callsFor('set')).toEqual([]) - expect( - getArtifactCreateIntent('local-profile', userDataPath, '/repo/other.html', scope) - ).toMatchObject({ idempotencyKey: 'key-b' }) - expect((await readdir(directory)).filter((name) => name.endsWith('.tmp'))).toEqual([]) - expect(runProcess).not.toHaveBeenCalled() - expect(runProcessSync).not.toHaveBeenCalled() - } finally { - if (originalPlatform) { - Object.defineProperty(process, 'platform', originalPlatform) - } - resetWindowsSecurityLoadForTests() - } - }) - it('refuses to overwrite an unreadable matching intent', async () => { const userDataPath = await createUserDataPath() const directory = join(userDataPath, 'profiles', 'local-profile', 'artifact-create-intents') diff --git a/src/main/artifacts/artifact-share-record-store.test.ts b/src/main/artifacts/artifact-share-record-store.test.ts index 6c1f08ab696..4068e447d26 100644 --- a/src/main/artifacts/artifact-share-record-store.test.ts +++ b/src/main/artifacts/artifact-share-record-store.test.ts @@ -134,37 +134,6 @@ describe('artifact share record store', () => { expect(persisted.shares).toEqual({}) }) - it('caps records deterministically at ten thousand', async () => { - const path = await userDataPath() - clearArtifactShareRecords('local-profile', path) - const recordsPath = join(path, 'profiles', 'local-profile', 'artifact-shares.json') - const shares = Object.fromEntries( - Array.from({ length: 10_001 }, (_, index) => [ - `/repo/report-${String(index).padStart(5, '0')}.html`, - { - ...scopeA, - slug: `artifact-${index}`, - editToken: `secret-${index}`, - shareUrl: `https://share.onorca.dev/a/artifact-${index}`, - expiresAt: '2099-01-01T00:00:00.000Z', - savedAt: index - } - ]) - ) - await writeFile(recordsPath, JSON.stringify({ version: 2, lifecycleGeneration: 0, shares })) - - expect( - getArtifactShareRecord('local-profile', path, '/repo/report-10000.html', scopeA)?.editToken - ).toBe('secret-10000') - expect( - getArtifactShareRecord('local-profile', path, '/repo/report-00000.html', scopeA) - ).toBeNull() - const persisted = JSON.parse(await readFile(recordsPath, 'utf8')) as { - shares: Record - } - expect(Object.keys(persisted.shares)).toHaveLength(10_000) - }) - it('keeps usable legacy version-two records without timestamps', async () => { const path = await userDataPath() clearArtifactShareRecords('local-profile', path) diff --git a/src/main/automations/automation-run-terminal-surface.test.ts b/src/main/automations/automation-run-terminal-surface.test.ts index a3d68be55fd..eceecf0cb51 100644 --- a/src/main/automations/automation-run-terminal-surface.test.ts +++ b/src/main/automations/automation-run-terminal-surface.test.ts @@ -47,21 +47,6 @@ const retainedRun = { } as unknown as AutomationRun describe('resolving an automation run terminal across startup states', () => { - it('cannot answer for any pane while no graph has published', () => { - const runtime = new OrcaRuntimeService(makeStore() as never) - const observer = createRuntimeAutomationRunTerminalObserver(runtime) - - expect(observer.resolveRunTerminal(retainedRun)).toBeNull() - }) - - it('still cannot answer once an explicitly empty headless graph publishes', () => { - const runtime = new OrcaRuntimeService(makeStore() as never) - runtime.syncWindowGraph(HEADLESS_RUNTIME_WINDOW_ID, { tabs: [], leaves: [] }) - const observer = createRuntimeAutomationRunTerminalObserver(runtime) - - expect(observer.resolveRunTerminal(retainedRun)).toBeNull() - }) - it('answers from an adopted PTY bound to the pane, with no leaves at all', () => { const runtime = new OrcaRuntimeService(makeStore() as never) runtime.syncWindowGraph(HEADLESS_RUNTIME_WINDOW_ID, { tabs: [], leaves: [] }) diff --git a/src/main/automations/automation-ssh-readoption-migration.test.ts b/src/main/automations/automation-ssh-readoption-migration.test.ts index 27bef43d3a8..aab37d2f244 100644 --- a/src/main/automations/automation-ssh-readoption-migration.test.ts +++ b/src/main/automations/automation-ssh-readoption-migration.test.ts @@ -104,13 +104,6 @@ describe('migrateAutomationsForSshReadoption', () => { expect(automation.executionTargetGeneration).toBeUndefined() }) - it('leaves automations on other targets alone', () => { - const other = makeAutomation({ id: 'auto-2', executionTargetId: 'ssh-other' }) - expect(migrate([other], 9)).toBe(false) - expect(other.executionTargetId).toBe('ssh-other') - expect(other.executionTargetGeneration).toBe(3) - }) - it('re-points run history so workspace navigation survives re-adoption', () => { const run = { id: 'run-1', @@ -123,20 +116,6 @@ describe('migrateAutomationsForSshReadoption', () => { }) describe('migrateAutomationHostFilterSshTargetId', () => { - it('follows the re-adopted id', () => { - const ui = uiWithFilter( - hostStableKey({ authority: { kind: 'desktop' }, selector: { kind: 'ssh', targetId: OLD_ID } }) - ) - expect(migrateAutomationHostFilterSshTargetId(ui, OLD_ID, NEW_ID)).toBe(true) - expect(ui.automationHostFilter).toEqual({ - kind: 'host', - hostKey: hostStableKey({ - authority: { kind: 'desktop' }, - selector: { kind: 'ssh', targetId: NEW_ID } - }) - }) - }) - it('never rewrites a runtime authority filter that reuses the same target id', () => { const hostKey = hostStableKey({ authority: { kind: 'runtime', environmentId: 'env-1' }, @@ -146,22 +125,4 @@ describe('migrateAutomationHostFilterSshTargetId', () => { expect(migrateAutomationHostFilterSshTargetId(ui, OLD_ID, NEW_ID)).toBe(false) expect(ui.automationHostFilter).toEqual({ kind: 'host', hostKey }) }) - - it('ignores All hosts, other hosts, and unparseable persisted values', () => { - const all = { automationHostFilter: { kind: 'all' } } as PersistedUIState - expect(migrateAutomationHostFilterSshTargetId(all, OLD_ID, NEW_ID)).toBe(false) - const selfKey = hostStableKey({ - authority: { kind: 'desktop' }, - selector: { kind: 'self' } - }) - expect(migrateAutomationHostFilterSshTargetId(uiWithFilter(selfKey), OLD_ID, NEW_ID)).toBe( - false - ) - expect(migrateAutomationHostFilterSshTargetId(uiWithFilter('garbage'), OLD_ID, NEW_ID)).toBe( - false - ) - expect(migrateAutomationHostFilterSshTargetId({} as PersistedUIState, OLD_ID, NEW_ID)).toBe( - false - ) - }) }) diff --git a/src/main/automations/dispatch-tokens.test.ts b/src/main/automations/dispatch-tokens.test.ts deleted file mode 100644 index 03225a0d6f9..00000000000 --- a/src/main/automations/dispatch-tokens.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { - beginAutomationDispatchTokenUse, - createAutomationDispatchToken, - getAutomationDispatchTokenCountForTests, - MAX_AUTOMATION_DISPATCH_TOKENS -} from './dispatch-tokens' - -describe('automation dispatch tokens', () => { - it('bounds distinct token churn', () => { - vi.useFakeTimers({ toFake: ['Date'] }) - vi.setSystemTime(1_700_000_000_000) - try { - const tokens = Array.from({ length: MAX_AUTOMATION_DISPATCH_TOKENS + 4 }, (_, index) => { - const automationId = `automation-${index}` - const runId = `run-${index}` - return { - automationId, - runId, - token: createAutomationDispatchToken(automationId, runId) - } - }) - - expect(getAutomationDispatchTokenCountForTests()).toBe(MAX_AUTOMATION_DISPATCH_TOKENS) - for (const token of tokens.slice(0, 4)) { - expect(beginAutomationDispatchTokenUse({ ...token, reservationId: 'request-1' })).toBe( - false - ) - } - for (const token of tokens.slice(4)) { - expect(beginAutomationDispatchTokenUse({ ...token, reservationId: 'request-1' })).toBe(true) - } - } finally { - vi.useRealTimers() - } - }) -}) diff --git a/src/main/automations/external-automation-manager-cache.test.ts b/src/main/automations/external-automation-manager-cache.test.ts index a86a4ebb0b0..32e428e10d2 100644 --- a/src/main/automations/external-automation-manager-cache.test.ts +++ b/src/main/automations/external-automation-manager-cache.test.ts @@ -1,8 +1,7 @@ -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it } from 'vitest' import { ExternalAutomationManagerCache, - describeExternalManagerFailure, - MAX_EXTERNAL_AUTOMATION_MANAGER_CACHE_ENTRIES + describeExternalManagerFailure } from './external-automation-manager-cache' import { ExternalAutomationProbeCancelledError } from './external-automation-probe-scheduler' import type { ExternalAutomationManager } from '../../shared/automations-types' @@ -25,16 +24,6 @@ const selfOwner = 'owner:desktop:self' const sshOwner = 'owner:desktop:ssh:target-a:3' describe('ExternalAutomationManagerCache', () => { - it('bounds distinct scope churn', () => { - const cache = new ExternalAutomationManagerCache() - - for (let index = 0; index < MAX_EXTERNAL_AUTOMATION_MANAGER_CACHE_ENTRIES + 4; index += 1) { - cache.write({ ownerKey: `owner-${index}`, provider: 'openclaw' }, null) - } - - expect(cache.size).toBe(MAX_EXTERNAL_AUTOMATION_MANAGER_CACHE_ENTRIES) - }) - it('keeps each owner’s manager and failure separate', () => { const cache = new ExternalAutomationManagerCache() const failedOwner = 'owner:desktop:ssh:target-b:1' @@ -75,31 +64,6 @@ describe('ExternalAutomationManagerCache', () => { expect(cache.read({ ownerKey: selfOwner, provider: 'openclaw' })).toBeNull() }) - it('prunes expired entries when the cache is accessed', () => { - let now = 1_000 - const cache = new ExternalAutomationManagerCache({ ttlMs: 100, now: () => now }) - for (let index = 0; index < 10; index += 1) { - cache.write({ ownerKey: `owner-${index}`, provider: 'openclaw' }, manager(String(index))) - } - now += 101 - - expect(cache.read({ ownerKey: 'unrelated', provider: 'openclaw' })).toBeNull() - expect(cache.size).toBe(0) - }) - - it('serves a fresh entry without reloading, and reloads on refresh', async () => { - const cache = new ExternalAutomationManagerCache() - const load = vi.fn(() => Promise.resolve(manager('openclaw:local'))) - const key = { ownerKey: selfOwner, provider: 'openclaw' } - - await cache.resolve(key, load) - await cache.resolve(key, load) - expect(load).toHaveBeenCalledTimes(1) - - await cache.resolve(key, load, { refresh: true }) - expect(load).toHaveBeenCalledTimes(2) - }) - it('records a load failure as error state rather than throwing', async () => { const cache = new ExternalAutomationManagerCache() diff --git a/src/renderer/src/components/automations/external-automation-scope-gating.test.ts b/src/renderer/src/components/automations/external-automation-scope-gating.test.ts index 0f755933487..49e2620f825 100644 --- a/src/renderer/src/components/automations/external-automation-scope-gating.test.ts +++ b/src/renderer/src/components/automations/external-automation-scope-gating.test.ts @@ -39,15 +39,6 @@ const runtimeEntry = entry({ }) describe('external automation scope gating', () => { - it('lists managers only for a desktop host with a captured owner', () => { - const gate = resolveExternalAutomationScopeGate(entry()) - expect(gate.status).toBe('listed') - expect(gate.probeOwner).toEqual({ - authority: { kind: 'desktop' }, - selector: { kind: 'self' } - }) - }) - it('never presents a runtime-owned host as clean', () => { const gate = resolveExternalAutomationScopeGate(runtimeEntry) expect(gate.status).toBe('not-listed') @@ -99,17 +90,6 @@ describe('engine scope errors', () => { ).toBe(EXTERNAL_AUTOMATION_SCOPE_CODES.targetHidden) }) - it('preserves the hidden-target scope code', () => { - const gate = externalAutomationScopeGateFromError( - new ExternalAutomationScopeError(EXTERNAL_AUTOMATION_SCOPE_CODES.targetHidden) - ) - expect(gate).toEqual({ - status: 'not-listed', - probeOwner: null, - code: EXTERNAL_AUTOMATION_SCOPE_CODES.targetHidden - }) - }) - it('leaves unrelated failures alone so they are not relabelled as scope limits', () => { expect(externalAutomationScopeGateFromError(new Error('socket hang up'))).toBeNull() expect(parseExternalAutomationScopeCode(null)).toBeNull() diff --git a/src/renderer/src/components/automations/external-automation-scope-keys.test.ts b/src/renderer/src/components/automations/external-automation-scope-keys.test.ts index f34f9198aaf..ac11d0bbb25 100644 --- a/src/renderer/src/components/automations/external-automation-scope-keys.test.ts +++ b/src/renderer/src/components/automations/external-automation-scope-keys.test.ts @@ -36,16 +36,6 @@ describe('external automation scope keys', () => { ) }) - it('keeps key kinds in separate namespaces', () => { - const kinds = new Set([ - externalAutomationScopeKey(local), - externalAutomationManagerKey(local, 'x'), - externalAutomationJobKey(local, 'x'), - externalAutomationActionKey(local, 'x', 'run') - ]) - expect(kinds.size).toBe(4) - }) - it('escapes components so a hostile ID cannot forge another key', () => { const forged = externalAutomationJobKey(local, 'job|extra') expect(forged).not.toBe(externalAutomationActionKey(local, 'job', 'run')) @@ -55,10 +45,4 @@ describe('external automation scope keys', () => { ) expect(forged.split('|')).toHaveLength(4) }) - - it('distinguishes actions on the same job', () => { - expect(externalAutomationActionKey(local, 'job-1', 'run')).not.toBe( - externalAutomationActionKey(local, 'job-1', 'delete') - ) - }) }) diff --git a/src/renderer/src/components/automations/use-automation-list-search.test.tsx b/src/renderer/src/components/automations/use-automation-list-search.test.tsx index 40bf294a66a..08bc7892067 100644 --- a/src/renderer/src/components/automations/use-automation-list-search.test.tsx +++ b/src/renderer/src/components/automations/use-automation-list-search.test.tsx @@ -6,8 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { Repo } from '../../../../shared/repo-types' import { makeAutomation, makeAutomationListRow, REPO_ID } from './automations-page-fixtures' import type { AutomationListRow } from './automation-list-row-identity' -import { resolveAutomationListEmptyState } from './automation-list-empty-state' -import type { AutomationHostFilterResolution } from './automation-host-filter-resolution' + import * as searchRows from './automation-list-search-rows' import { useAutomationListSearch } from './use-automation-list-search' @@ -54,18 +53,6 @@ function render(query: string, rows: readonly AutomationListRow[]): void { }) } -function makeRows(count: number): AutomationListRow[] { - return Array.from({ length: count }, (_, index) => - makeAutomationListRow({ - automation: makeAutomation({ - id: `a-${index}`, - name: `Automation ${index}`, - prompt: `sweep repo ${index}` - }) - }) - ) -} - beforeEach(() => { container = document.createElement('div') document.body.appendChild(container) @@ -80,62 +67,6 @@ afterEach(() => { latest = null }) -describe('useAutomationListSearch index shape', () => { - it('reuses search indexes across query changes and equivalent row arrays', () => { - render('', makeRows(20)) - const buildsAfterMount = buildRowsSpy.mock.calls.length - - // Fresh arrays per keystroke: a refresh tick mid-typing must not re-index. - for (const query of ['s', 'sw', 'swe', 'swee', 'sweep']) { - render(query, makeRows(20)) - } - - expect(buildRowsSpy.mock.calls.length).toBe(buildsAfterMount) - expect(latest?.filteredRows).toHaveLength(20) - }) - - it('reuses the index when a refresh tick replaces rows with equal search content', () => { - render('', makeRows(5)) - const buildsAfterMount = buildRowsSpy.mock.calls.length - - // New objects, new array, same searchable content — a nextRunAt-style tick. - render( - '', - makeRows(5).map((row) => ({ - ...row, - automation: { ...row.automation, nextRunAt: Date.now() } - })) - ) - - expect(buildRowsSpy.mock.calls.length).toBe(buildsAfterMount) - }) - - it('rebuilds once when searchable content actually changes', () => { - const automations = makeRows(5) - render('', automations) - const buildsAfterMount = buildRowsSpy.mock.calls.length - - const renamed = automations.map((row, index) => - index === 0 ? { ...row, automation: { ...row.automation, name: 'Renamed sweep' } } : row - ) - render('', renamed) - - expect(buildRowsSpy.mock.calls.length).toBe(buildsAfterMount + 1) - }) - - it('matches in a single pass over prebuilt indexes per settled query', () => { - const automations = makeRows(10) - render('', automations) - matchRowsSpy.mockClear() - - render('sweep repo 3', automations) - - // Two row sets (local + external), matched once each for the settled query. - expect(matchRowsSpy.mock.calls.length).toBeLessThanOrEqual(2) - expect(latest?.filteredRows.map((row) => row.automation.id)).toEqual(['a-3']) - }) -}) - describe('useAutomationListSearch cross-host identity', () => { it('keeps both hosts’ copies of one automation ID through a matching query', () => { // Bare-ID reconstruction would duplicate one authority’s row and drop its colliding counterpart. @@ -157,38 +88,3 @@ describe('useAutomationListSearch cross-host identity', () => { ]) }) }) - -describe('useAutomationListSearch counts', () => { - it('reports rows before and after search for the empty-state view', () => { - const automations = makeRows(4) - render('', automations) - expect(latest?.searchCounts).toEqual({ - hostRowCount: 4, - visibleRowCount: 4, - searchActive: false - }) - - render('no-such-automation', automations) - expect(latest?.searchCounts).toEqual({ - hostRowCount: 4, - visibleRowCount: 0, - searchActive: true - }) - }) - - it('feeds the no-match empty state instead of letting it recompute counts', () => { - const resolution: AutomationHostFilterResolution = { - effective: { kind: 'all' }, - entry: null, - status: 'all', - announceFallback: false - } - render('no-such-automation', makeRows(3)) - const counts = latest?.searchCounts - if (!counts) { - throw new Error('hook produced no counts') - } - - expect(resolveAutomationListEmptyState({ resolution, ...counts }).kind).toBe('search-no-match') - }) -}) diff --git a/src/renderer/src/components/automations/use-automation-runs-dashboard.test.tsx b/src/renderer/src/components/automations/use-automation-runs-dashboard.test.tsx index 519904a1e90..d7f4e95e4e1 100644 --- a/src/renderer/src/components/automations/use-automation-runs-dashboard.test.tsx +++ b/src/renderer/src/components/automations/use-automation-runs-dashboard.test.tsx @@ -74,16 +74,6 @@ afterEach(() => { }) describe('useAutomationRunsDashboard', () => { - it('appends the next page when load more fires', async () => { - await render(true) - expect(latest?.entries.map((entry) => entry.run.id)).toEqual(['run-2']) - - await act(async () => latest?.loadMore()) - - expect(latest?.entries.map((entry) => entry.run.id)).toEqual(['run-2', 'run-1']) - expect(historySpy.mock.calls.at(-1)?.[2].cursor).toBe('2:run-2') - }) - it('keeps the cursor when a load more fails so the page stays retryable', async () => { await render(true) historySpy.mockImplementationOnce(async () => ({ @@ -101,18 +91,6 @@ describe('useAutomationRunsDashboard', () => { expect(latest?.entries.map((entry) => entry.run.id)).toEqual(['run-2', 'run-1']) }) - it('refetches the head when the view is re-entered after a load more', async () => { - await render(true) - await act(async () => latest?.loadMore()) - await render(false) - historySpy.mockClear() - - await render(true) - - expect(historySpy.mock.calls.at(-1)?.[2].cursor).toBeUndefined() - expect(latest?.entries.map((entry) => entry.run.id)).toEqual(['run-2']) - }) - it('reloads from the head when the authority is re-paired', async () => { const paired = (pairingRevision: number): AutomationAuthorityRef => ({ kind: 'runtime', diff --git a/src/renderer/src/components/automations/use-external-automation-scope-retention.test.tsx b/src/renderer/src/components/automations/use-external-automation-scope-retention.test.tsx deleted file mode 100644 index 10f6265e05a..00000000000 --- a/src/renderer/src/components/automations/use-external-automation-scope-retention.test.tsx +++ /dev/null @@ -1,140 +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 type { AutomationOwnerRef } from '../../../../shared/automation-owner-ref' -import type { AutomationHostCatalogEntry } from './automation-host-catalog-types' -import { useExternalAutomationScopeRetention } from './use-external-automation-scope-retention' - -const retainExternalScopes = - vi.fn<(request: { owners: readonly AutomationOwnerRef[] }) => Promise>() - -let container: HTMLDivElement -let root: Root - -function entry(overrides: Partial = {}): AutomationHostCatalogEntry { - return { - stableRef: { authority: { kind: 'desktop' }, selector: { kind: 'self' } }, - owner: { authority: { kind: 'desktop' }, selector: { kind: 'self' } }, - stableKey: 'host:desktop:self', - label: 'This computer', - authorityLabel: 'This computer', - kind: 'self', - catalogState: 'authoritative', - authorityHealth: 'fresh', - executionHealth: 'connected', - querySupport: 'scoped', - ...overrides - } -} - -function sshEntry(targetId: string, targetGeneration = 3): AutomationHostCatalogEntry { - return entry({ - stableRef: { authority: { kind: 'desktop' }, selector: { kind: 'ssh', targetId } }, - owner: { - authority: { kind: 'desktop' }, - selector: { kind: 'ssh', targetId, targetGeneration } - }, - stableKey: `host:desktop:ssh:${targetId}`, - label: targetId, - kind: 'ssh' - }) -} - -const runtimeEntry = entry({ - stableRef: { authority: { kind: 'runtime', environmentId: 'env-1' }, selector: { kind: 'self' } }, - owner: { - authority: { kind: 'runtime', environmentId: 'env-1', pairingRevision: 1 }, - selector: { kind: 'self' } - }, - stableKey: 'host:runtime:env-1:self', - label: 'Build box' -}) - -function Harness({ entries }: { entries: readonly AutomationHostCatalogEntry[] }): null { - useExternalAutomationScopeRetention(entries) - return null -} - -function render(entries: readonly AutomationHostCatalogEntry[]): void { - act(() => { - root.render() - }) -} - -function retainedOwners(callIndex: number): readonly AutomationOwnerRef[] { - return retainExternalScopes.mock.calls[callIndex]?.[0].owners ?? [] -} - -beforeEach(() => { - container = document.createElement('div') - document.body.appendChild(container) - root = createRoot(container) - retainExternalScopes.mockReset() - retainExternalScopes.mockResolvedValue(undefined) - Object.assign(window, { api: { automations: { retainExternalScopes } } }) -}) - -afterEach(() => { - container.remove() -}) - -describe('useExternalAutomationScopeRetention', () => { - it('retains only the hosts whose managers are actually listed', () => { - render([entry(), runtimeEntry, sshEntry('t1')]) - - expect(retainExternalScopes).toHaveBeenCalledTimes(1) - expect(retainedOwners(0)).toEqual([ - { authority: { kind: 'desktop' }, selector: { kind: 'self' } }, - { - authority: { kind: 'desktop' }, - selector: { kind: 'ssh', targetId: 't1', targetGeneration: 3 } - } - ]) - act(() => root.unmount()) - }) - - it('does not re-retain the same scope set when it re-renders', () => { - render([entry(), sshEntry('t1')]) - render([entry(), sshEntry('t1')]) - - expect(retainExternalScopes).toHaveBeenCalledTimes(1) - act(() => root.unmount()) - }) - - it('drops a host as soon as the filter moves off it', () => { - render([entry(), sshEntry('t1')]) - render([sshEntry('t1')]) - - expect(retainExternalScopes).toHaveBeenCalledTimes(2) - expect(retainedOwners(1)).toEqual([ - { - authority: { kind: 'desktop' }, - selector: { kind: 'ssh', targetId: 't1', targetGeneration: 3 } - } - ]) - act(() => root.unmount()) - }) - - it('re-retains when a host is re-added under a new incarnation', () => { - render([sshEntry('t1', 3)]) - render([sshEntry('t1', 4)]) - - expect(retainExternalScopes).toHaveBeenCalledTimes(2) - expect(retainedOwners(1)).toEqual([ - { - authority: { kind: 'desktop' }, - selector: { kind: 'ssh', targetId: 't1', targetGeneration: 4 } - } - ]) - act(() => root.unmount()) - }) - - it('retains nothing once the page unmounts', () => { - render([entry()]) - act(() => root.unmount()) - - expect(retainedOwners(retainExternalScopes.mock.calls.length - 1)).toEqual([]) - }) -}) diff --git a/src/renderer/src/components/browser-favicon.test.tsx b/src/renderer/src/components/browser-favicon.test.tsx index 991f71807d7..fd47287073a 100644 --- a/src/renderer/src/components/browser-favicon.test.tsx +++ b/src/renderer/src/components/browser-favicon.test.tsx @@ -27,24 +27,6 @@ it('retries a failed icon after a same-origin reload completes', () => { expect(view.container.querySelector('img')).not.toBeNull() }) -it('shows a loading spinner throughout a reload', () => { - const view = render(icon()) - view.rerender(icon(true)) - expect(view.container.querySelector('img')).toBeNull() - expect(view.container.firstElementChild?.getAttribute('class')).toContain( - 'motion-safe:animate-spin' - ) - view.rerender(icon(false)) - expect(view.container.querySelector('img')?.getAttribute('src')).toBe(faviconUrl) -}) - -it('shows the favicon when initial loading finishes', () => { - const view = render(icon(true)) - expect(view.container.querySelector('img')).toBeNull() - view.rerender(icon(false)) - expect(view.container.querySelector('img')).not.toBeNull() -}) - it('still resets failures when the favicon URL changes or clears', () => { const view = render(icon()) fireEvent.error(view.container.querySelector('img')!) diff --git a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx b/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx index 794085b891f..30b1f23b1c6 100644 --- a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx +++ b/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx @@ -154,17 +154,6 @@ describe('client-hosted browser pane over a dead guest', () => { }) }) - it('stops the spinner it inherited from a page that died mid-load', () => { - webview.getURL.mockImplementation(() => { - throw invalidGuestInstanceId() - }) - const onUpdatePageState = vi.fn() - - render(paneElement(true, { browserTab: page({ loading: true }), onUpdatePageState })) - - expect(onUpdatePageState).toHaveBeenCalledWith('page-a', { loading: false }) - }) - it('flips to the unavailable notice when the guest renderer goes away after attach', () => { const onUpdatePageState = vi.fn() render(paneElement(true, { browserTab: page({ loading: true }), onUpdatePageState })) @@ -208,22 +197,6 @@ describe('client-hosted browser pane over a dead guest', () => { expect(webview.reload).not.toHaveBeenCalled() }) - it('does not freeze silently when a navigation event finds the guest gone', () => { - const onUpdatePageState = vi.fn() - render(paneElement(true, { onUpdatePageState })) - onUpdatePageState.mockClear() - webview.getURL.mockImplementation(() => { - throw invalidGuestInstanceId() - }) - - act(() => { - webview.dispatchEvent(new Event('did-navigate')) - }) - - expect(screen.getByText('Client-hosted browser unavailable')).toBeTruthy() - expect(onUpdatePageState).toHaveBeenCalledWith('page-a', { loading: false }) - }) - it('stops the spinner when the guest dies as a load starts', () => { const onUpdatePageState = vi.fn() render(paneElement(true, { onUpdatePageState })) @@ -253,52 +226,6 @@ describe('client-hosted browser pane over a dead guest', () => { expect(screen.getByText('Client-hosted browser unavailable')).toBeTruthy() }) - it('uses one guarded title read when the next read would find the guest gone', () => { - const onUpdatePageState = vi.fn() - render(paneElement(true, { onUpdatePageState })) - onUpdatePageState.mockClear() - const readTitle = vi.fn(() => { - webview.getTitle = vi.fn(() => { - throw invalidGuestInstanceId() - }) - return 'Last live title' - }) - webview.getTitle = readTitle - - expect(() => act(() => webview.dispatchEvent(new Event('did-navigate')))).not.toThrow() - expect(readTitle).toHaveBeenCalledTimes(1) - expect(webview.getTitle).not.toHaveBeenCalled() - expect(onUpdatePageState).toHaveBeenCalledWith( - 'page-a', - expect.objectContaining({ title: 'Last live title' }) - ) - }) - - it('shows unavailability when a load-failure fallback finds the guest gone', () => { - const onUpdatePageState = vi.fn() - render(paneElement(true, { onUpdatePageState })) - webview.getURL.mockImplementation(() => { - throw invalidGuestInstanceId() - }) - - act(() => webview.dispatchEvent(new Event('did-fail-load'))) - - expect(screen.getByText('Client-hosted browser unavailable')).toBeTruthy() - expect(onUpdatePageState.mock.calls.at(-1)).toEqual(['page-a', { loading: false }]) - }) - - it('removes loss listeners when the initial guest read fails', () => { - const removeListener = vi.spyOn(webview, 'removeEventListener') - webview.getURL.mockImplementation(() => { - throw invalidGuestInstanceId() - }) - - render(paneElement(true)) - - expect(removeListener).toHaveBeenCalledWith('destroyed', expect.any(Function)) - expect(removeListener).toHaveBeenCalledWith('render-process-gone', expect.any(Function)) - }) - it('stops listening for guest loss once the pane lets go of the tag', () => { const onUpdatePageState = vi.fn() const view = render(paneElement(true, { onUpdatePageState })) diff --git a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.drag-focus.test.tsx b/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.drag-focus.test.tsx index bca8ea4e25c..723dd7471e9 100644 --- a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.drag-focus.test.tsx +++ b/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.drag-focus.test.tsx @@ -121,15 +121,6 @@ describe.each([ cleanup() }) - it('focuses the guest when the tab activates outside a drag', () => { - const { focus } = createGuest() - const view = render(paneElement(false)) - - view.rerender(paneElement(true)) - - expect(focus).toHaveBeenCalledTimes(1) - }) - // Guest focus blurs the embedder, triggering the drag’s missed-end fallback and aborting its own preview activation. it('leaves the guest unfocused while a drag holds guests click-through', () => { const { focus } = createGuest() diff --git a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.markup.test.tsx b/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.markup.test.tsx index d639ab4b8d9..743e80e9d59 100644 --- a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.markup.test.tsx +++ b/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.markup.test.tsx @@ -185,25 +185,6 @@ describe('client-hosted screenshot markup', () => { expect(overlay()).toBeNull() }) - it.each(['pending', 'unavailable', 'inactive', 'failed'])( - 'disables Draw for a %s pane', - (state) => { - const pane = renderPane( - { - ...(state === 'pending' ? { placement: null } : {}), - ...(state === 'inactive' ? { isActive: false } : {}) - }, - state !== 'unavailable' - ) - if (state === 'failed') { - fireEvent(pane.webview, new Event('destroyed')) - } - expect(drawButton().disabled).toBe(true) - fireEvent.click(drawButton()) - expect(mocks.capture).not.toHaveBeenCalled() - } - ) - it.each(['cancel', 'deactivate', 'replace', 'loss', 'unmount'])( 'invalidates an in-flight capture on %s', async (action) => { diff --git a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.popup-notices.test.tsx b/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.popup-notices.test.tsx deleted file mode 100644 index 79ecdda5799..00000000000 --- a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.popup-notices.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -// @vitest-environment happy-dom -import { act, cleanup, render } from '@testing-library/react' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const toastMocks = vi.hoisted(() => ({ - loading: vi.fn(), - success: vi.fn(), - error: vi.fn(), - message: vi.fn() -})) - -vi.mock('sonner', () => ({ toast: toastMocks })) - -import { TooltipProvider } from '@/components/ui/tooltip' -import { installClientHostedPaneApi, paneChannel } from './client-hosted-browser-pane-test-rig' -import { ClientHostedBrowserPagePane } from './ClientHostedBrowserPagePane' - -type PopupEvent = { - browserPageId: string - origin: string - action: 'opened-in-orca' | 'opened-external' | 'blocked' -} - -let popups = paneChannel() - -beforeEach(() => { - popups = paneChannel() - installClientHostedPaneApi({ browser: { onPopup: popups.subscribe } }) -}) - -afterEach(() => { - cleanup() - vi.clearAllMocks() -}) - -function renderPane(): void { - render( - - - - ) -} - -function emitPopup(overrides: Partial = {}): void { - const event: PopupEvent = { - browserPageId: 'page-a', - origin: 'https://accounts.example.com', - action: 'blocked', - ...overrides - } - act(() => popups.emit(event)) -} - -describe('ClientHostedBrowserPagePane popup notices', () => { - it('names the origin whose popup was refused', () => { - renderPane() - - emitPopup() - - expect(toastMocks.message).toHaveBeenCalledWith( - 'https://accounts.example.com tried to open a popup Orca does not support here.', - { id: 'browser-popup:page-a:blocked:https://accounts.example.com' } - ) - }) - - it('silences in-Orca opens but reports external opens', () => { - renderPane() - - emitPopup({ action: 'opened-in-orca' }) - expect(toastMocks.message).not.toHaveBeenCalled() - emitPopup({ action: 'opened-external' }) - expect(toastMocks.message).toHaveBeenCalledExactlyOnceWith( - 'https://accounts.example.com opened a new window in your default browser.', - { id: 'browser-popup:page-a:opened-external:https://accounts.example.com' } - ) - }) - - it('ignores popups belonging to another page', () => { - renderPane() - - emitPopup({ browserPageId: 'page-b' }) - - expect(toastMocks.message).not.toHaveBeenCalled() - }) -}) diff --git a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.test.tsx b/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.test.tsx index 19a355f5a94..c123add3e30 100644 --- a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.test.tsx +++ b/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.test.tsx @@ -67,93 +67,6 @@ describe('ClientHostedBrowserPagePane', () => { }) afterEach(() => cleanup()) - it('attaches the exact retained guest once and keeps focus changes local', () => { - const { webview, focus } = createWebview() - const detach = vi.fn() - mocks.attach.mockReturnValue(retainedAttachment(webview, detach)) - const onUpdatePageState = vi.fn() - const onSetUrl = vi.fn() - const view = render( - - ) - - expect(mocks.attach).toHaveBeenCalledTimes(1) - expect(mocks.attach).toHaveBeenCalledWith( - { browserPageId: 'page-a', pageHostGeneration: 7 }, - expect.any(HTMLElement) - ) - view.rerender( - - ) - - expect(mocks.attach).toHaveBeenCalledTimes(1) - expect(focus).toHaveBeenCalledTimes(1) - view.unmount() - expect(detach).toHaveBeenCalledTimes(1) - }) - - it('updates local chrome from the retained guest navigation', () => { - const { webview, setUrl, setTitle } = createWebview() - mocks.attach.mockReturnValue(retainedAttachment(webview)) - const onUpdatePageState = vi.fn() - const onSetUrl = vi.fn() - render( - - ) - onUpdatePageState.mockClear() - onSetUrl.mockClear() - setUrl('https://remote.internal/path') - setTitle('Remote page') - - act(() => webview.dispatchEvent(new Event('did-navigate'))) - - expect(onSetUrl).toHaveBeenCalledWith('page-a', 'https://remote.internal/path', { - preserveLoadError: true - }) - expect(onUpdatePageState).toHaveBeenCalledWith( - 'page-a', - expect.objectContaining({ - title: 'Remote page', - loading: false, - canGoBack: true, - canGoForward: false - }) - ) - expect((screen.getByLabelText('Address') as HTMLInputElement).value).toBe( - 'https://remote.internal/path' - ) - }) - it('searches non-URL address input like the local pane instead of forcing a host', () => { const { webview } = createWebview() mocks.attach.mockReturnValue(retainedAttachment(webview)) @@ -285,38 +198,6 @@ describe('ClientHostedBrowserPagePane', () => { expect(document.querySelector('webview')).toBeNull() }) - it('escapes an unrenderable page to a NEW server-placed page at its last committed URL', async () => { - mocks.attach.mockReturnValue(null) - mocks.createBrowserTab.mockClear() - - render( - - ) - - expect(screen.getByText(/Signed-in and other transient page state may differ/)).not.toBeNull() - await act(async () => { - screen.getByRole('button', { name: 'Reopen on server' }).click() - }) - - expect(mocks.createBrowserTab).toHaveBeenCalledWith({ - worktreeId: 'worktree-a', - environmentId: 'environment-a', - url: 'https://remote.internal/path', - placementPreference: 'server', - focusOnCreate: true - }) - }) - it('requests the one-time intro tour on the first active client-hosted page', async () => { vi.useFakeTimers() const { useAppStore } = await import('@/store') @@ -406,45 +287,6 @@ describe('ClientHostedBrowserPagePane', () => { vi.useRealTimers() } }) - - it('publishes full guest metadata through the exact runtime placement', async () => { - const { webview, setUrl, setTitle } = createWebview() - mocks.attach.mockReturnValue(retainedAttachment(webview)) - render( - - ) - setUrl('https://remote.internal/path') - setTitle('Remote page') - - act(() => webview.dispatchEvent(new Event('did-navigate'))) - - await vi.waitFor(() => expect(mocks.publishMetadata).toHaveBeenCalledTimes(2)) - expect(mocks.publishMetadata).toHaveBeenLastCalledWith({ - environmentId: 'environment-a', - params: { - browserHostClientId: 'host-a', - browserHostGeneration: 3, - browserPageId: 'page-a', - pageHostGeneration: 7, - revision: 2, - url: 'https://remote.internal/path', - title: 'Remote page', - loading: false, - canGoBack: true, - canGoForward: false - } - }) - }) }) describe('ClientHostedBrowserPagePane address bar parity', () => { @@ -510,32 +352,6 @@ describe('ClientHostedBrowserPagePane address bar parity', () => { expect(focus).not.toHaveBeenCalled() }) - it('still hands focus to the guest when the tab opens on a real page', () => { - const { webview, focus, setUrl } = createWebview() - // Why: the attach is mocked, so place the guest where the real attach would put it. - document.body.appendChild(webview) - setUrl('https://remote.internal/path') - mocks.attach.mockReturnValue(retainedAttachment(webview)) - - render( - - ) - act(() => flushFrames()) - - expect(focus).toHaveBeenCalledTimes(1) - expect(document.activeElement).not.toBe(screen.getByLabelText('Address')) - }) - it('gives the guest focus back after the palette hands the address bar a turn', () => { const { webview, focus, setUrl } = createWebview() // Why: the attach is mocked, so place the guest where the real attach would put it. @@ -605,34 +421,6 @@ describe('ClientHostedBrowserPagePane address bar parity', () => { expect(blur).toHaveBeenCalled() expect(document.activeElement).toBe(screen.getByLabelText('Address')) }) - - it('files guest navigations into the URL history the address bar suggests from', () => { - const { webview, setUrl, setTitle } = createWebview() - mocks.attach.mockReturnValue(retainedAttachment(webview)) - render( - - ) - // Why: the initial sync sees about:blank, which never belongs in history. - expect(useAppStore.getState().browserUrlHistory).toHaveLength(0) - - setUrl('https://remote.internal/docs') - setTitle('Remote docs') - act(() => webview.dispatchEvent(new Event('did-navigate'))) - - expect(useAppStore.getState().browserUrlHistory).toEqual([ - expect.objectContaining({ url: 'https://remote.internal/docs', title: 'Remote docs' }) - ]) - }) }) function page(): BrowserPage { diff --git a/src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.test.tsx b/src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.test.tsx index ca1264d7da8..7d802303a96 100644 --- a/src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.test.tsx +++ b/src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.test.tsx @@ -1,6 +1,6 @@ // @vitest-environment happy-dom import '@testing-library/jest-dom/vitest' -import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' +import { cleanup, fireEvent, render, screen } from '@testing-library/react' import { afterEach, describe, expect, it, vi } from 'vitest' const reopenMocks = vi.hoisted(() => ({ reopenBrowserPageOnServer: vi.fn() })) @@ -29,68 +29,7 @@ function renderButton(): HTMLElement { return screen.getByRole('button') } -// Test DOM does not resolve Tailwind invisible; inspect both class and accessibility state. -function shown(text: string): boolean { - const label = screen.getByText(text) - return !label.className.includes('invisible') && label.getAttribute('aria-hidden') === 'false' -} - describe('ReopenBrowserPageOnServerButton', () => { - it('swaps in a spinner label once the remote round-trip outlasts the defer window', async () => { - vi.useFakeTimers() - let settle = (): void => {} - reopenMocks.reopenBrowserPageOnServer.mockImplementation( - () => - new Promise((resolve) => { - settle = () => resolve(true) - }) - ) - const button = renderButton() - - fireEvent.click(button) - - expect(button).toBeDisabled() - expect(button).toHaveAttribute('aria-busy', 'true') - expect(shown('Reopening…')).toBe(false) - - await act(async () => { - await vi.advanceTimersByTimeAsync(200) - }) - - expect(shown('Reopening…')).toBe(true) - expect(shown('Reopen on server')).toBe(false) - - await act(async () => { - settle() - await Promise.resolve() - }) - - expect(button).not.toBeDisabled() - expect(button).toHaveAttribute('aria-busy', 'false') - expect(shown('Reopen on server')).toBe(true) - expect(shown('Reopening…')).toBe(false) - }) - - it('never flashes the spinner for a round-trip that settles quickly', async () => { - vi.useFakeTimers() - reopenMocks.reopenBrowserPageOnServer.mockResolvedValue(true) - const button = renderButton() - - await act(async () => { - fireEvent.click(button) - await vi.advanceTimersByTimeAsync(0) - }) - - expect(shown('Reopening…')).toBe(false) - - await act(async () => { - await vi.advanceTimersByTimeAsync(1_000) - }) - - expect(shown('Reopening…')).toBe(false) - expect(button).not.toBeDisabled() - }) - it('rejects a second click while the first is still running', () => { reopenMocks.reopenBrowserPageOnServer.mockImplementation(() => new Promise(() => {})) const button = renderButton() diff --git a/src/renderer/src/components/browser-pane/annotate/GrabConfirmationSheet.test.ts b/src/renderer/src/components/browser-pane/annotate/GrabConfirmationSheet.test.ts deleted file mode 100644 index 7877d9d803b..00000000000 --- a/src/renderer/src/components/browser-pane/annotate/GrabConfirmationSheet.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { formatGrabPayloadAsText } from './GrabConfirmationSheet' -import type { BrowserGrabPayload } from '../../../../../shared/browser-grab-types' - -function makeTestPayload(overrides?: Partial): BrowserGrabPayload { - return { - page: { - sanitizedUrl: 'https://example.com/pricing', - title: 'Pricing - Example', - viewportWidth: 1280, - viewportHeight: 720, - scrollX: 0, - scrollY: 0, - devicePixelRatio: 2, - capturedAt: '2026-04-10T00:00:00.000Z' - }, - target: { - tagName: 'button', - selector: 'main section:nth-of-type(2) button.primary', - textSnippet: 'Start free trial', - htmlSnippet: '', - attributes: { class: 'primary', type: 'button' }, - accessibility: { - role: 'button', - accessibleName: 'Start free trial', - ariaLabel: null, - ariaLabelledBy: null - }, - rectViewport: { x: 400, y: 300, width: 148, height: 44 }, - rectPage: { x: 400, y: 300, width: 148, height: 44 }, - computedStyles: { - display: 'inline-flex', - position: 'relative', - width: '148px', - height: '44px', - margin: '0px', - padding: '12px 24px', - color: 'rgb(255, 255, 255)', - backgroundColor: 'rgb(99, 102, 241)', - border: '0px none', - borderRadius: '8px', - fontFamily: 'Inter, sans-serif', - fontSize: '16px', - fontWeight: '600', - lineHeight: '20px', - textAlign: 'center', - zIndex: 'auto' - } - }, - nearbyText: ['Pro', '$29/month', 'Unlimited projects'], - ancestorPath: ['section', 'main', 'body'], - screenshot: null, - ...overrides - } -} - -describe('formatGrabPayloadAsText', () => { - it('formats a complete payload with all sections', () => { - const text = formatGrabPayloadAsText(makeTestPayload()) - - expect(text).toContain('Attached browser context from https://example.com/pricing') - expect(text).toContain('Selected element:') - expect(text).toContain('button') - expect(text).toContain('Accessible name: "Start free trial"') - expect(text).toContain('Selector: main section:nth-of-type(2) button.primary') - expect(text).toContain('148x44') - expect(text).toContain('Nearby context:') - expect(text).toContain('- Pro') - expect(text).toContain('- $29/month') - expect(text).toContain('HTML:') - expect(text).toContain('') - }) - - it('omits nearby context section when empty', () => { - const text = formatGrabPayloadAsText(makeTestPayload({ nearbyText: [] })) - expect(text).not.toContain('Nearby context:') - }) - - it('omits text content section when empty', () => { - const payload = makeTestPayload() - payload.target.textSnippet = '' - const text = formatGrabPayloadAsText(payload) - expect(text).not.toContain('Text content:') - }) - - it('includes computed styles that differ from defaults', () => { - const text = formatGrabPayloadAsText(makeTestPayload()) - expect(text).toContain('Computed styles:') - expect(text).toContain('display: inline-flex') - expect(text).toContain('position: relative') - expect(text).toContain('font-size: 16px') - }) - - it('includes ancestor path', () => { - const text = formatGrabPayloadAsText(makeTestPayload()) - expect(text).toContain('Ancestor path: section > main > body') - }) - - it('handles payload with no accessible name', () => { - const payload = makeTestPayload() - payload.target.accessibility.accessibleName = null - const text = formatGrabPayloadAsText(payload) - expect(text).not.toContain('Accessible name:') - }) -}) diff --git a/src/renderer/src/components/browser-pane/annotate/MarkupOverlay.test.tsx b/src/renderer/src/components/browser-pane/annotate/MarkupOverlay.test.tsx index 23055c77086..25b4c29d9a1 100644 --- a/src/renderer/src/components/browser-pane/annotate/MarkupOverlay.test.tsx +++ b/src/renderer/src/components/browser-pane/annotate/MarkupOverlay.test.tsx @@ -41,14 +41,6 @@ function stroke( } describe('MarkupOverlay canvas pointer wiring', () => { - it('commits a stroke on release', () => { - const { canvas, undoButton } = renderOverlay() - - stroke(canvas, 'pointerUp') - - expect(undoButton).toHaveProperty('disabled', false) - }) - it('commits a stroke whose release never arrived once the canvas loses the pointer', () => { const { canvas, undoButton, redoButton } = renderOverlay() @@ -60,12 +52,4 @@ describe('MarkupOverlay canvas pointer wiring', () => { // Only a committed stroke leaves something to redo; a still-held one is just dropped. expect(redoButton).toHaveProperty('disabled', false) }) - - it('discards a stroke whose pointer was cancelled', () => { - const { canvas, undoButton } = renderOverlay() - - stroke(canvas, 'pointerCancel') - - expect(undoButton).toHaveProperty('disabled', true) - }) }) diff --git a/src/renderer/src/components/browser-pane/annotate/browser-grab-toast-owner-cleanup.test.tsx b/src/renderer/src/components/browser-pane/annotate/browser-grab-toast-owner-cleanup.test.tsx index f8921f4f91d..d1e2c5a6cd6 100644 --- a/src/renderer/src/components/browser-pane/annotate/browser-grab-toast-owner-cleanup.test.tsx +++ b/src/renderer/src/components/browser-pane/annotate/browser-grab-toast-owner-cleanup.test.tsx @@ -159,40 +159,6 @@ const screenshot = { height: 40 } as const describe('browser grab toast owner', () => { - it.each(['miss', 'reject'] as const)( - 'releases sixty-four closed shortcut %s owners', - async (outcome) => { - const requests = Array.from({ length: 64 }, () => deferred()) - let index = 0 - extract.mockImplementation(() => requests[index++].promise) - const bounds: ReturnType['bounds'][] = [] - for (let owner = 0; owner < 64; owner++) { - const mounted = mount({ id: `page${owner}` }) - bounds.push(mounted.bounds) - act(() => mounted.view.result.current.handleGrabActionShortcut('c')) - mounted.view.unmount() - } - expect(extract.mock.calls).toEqual( - Array.from({ length: 64 }, (_, owner) => [{ browserPageId: `page${owner}` }]) - ) - expect(vi.getTimerCount()).toBe(0) - await act(async () => { - for (const request of requests) { - if (outcome === 'miss') { - request.complete({ ok: false, reason: 'not-ready' }) - } else { - request.fail(new Error('Guest destroyed')) - } - } - await Promise.resolve() - }) - expect(writes).not.toHaveBeenCalled() - expect(imageWrites).not.toHaveBeenCalled() - expect(state.recordFeatureInteraction).not.toHaveBeenCalled() - expect(bounds.reduce((total, bound) => total + (bound?.mock.calls.length ?? 0), 0)).toBe(0) - expect(vi.getTimerCount()).toBe(0) - } - ) it.each(['c', 's'] as const)( 'finishes late requested %s delivery without closed presentation', async (key) => { @@ -221,38 +187,6 @@ describe('browser grab toast owner', () => { expect(vi.getTimerCount()).toBe(0) } ) - it.each(['c', 's'] as const)( - 'preserves complete live %s toast and exact two-second lifetime', - async (key) => { - const payload = makePayload() - extract.mockResolvedValue({ ok: true, payload }) - capture.mockResolvedValue({ ok: true, screenshot }) - const { view, bounds, grab } = mount({ strict: true }) - await act(async () => { - view.result.current.handleGrabActionShortcut(key) - await Promise.resolve() - }) - expect(writes.mock.calls).toEqual(key === 'c' ? [[formatGrabPayloadAsText(payload)]] : []) - expect(imageWrites.mock.calls).toEqual(key === 's' ? [[screenshot.dataUrl]] : []) - expect(state.recordFeatureInteraction.mock.calls).toEqual([['browser-grab']]) - expect(view.result.current.grabToast).toEqual({ - message: key === 'c' ? 'Copied' : 'Screenshotted', - type: 'success', - x: 50, - y: 0, - below: false, - payload - }) - expect(bounds).toHaveBeenCalledTimes(1) - expect(vi.getTimerCount()).toBe(1) - act(() => vi.advanceTimersByTime(1999)) - expect(view.result.current.grabToast?.type).toBe('success') - act(() => vi.advanceTimersByTime(1)) - expect(view.result.current.grabToast).toBeNull() - expect(grab.rearm).not.toHaveBeenCalled() - expect(vi.getTimerCount()).toBe(0) - } - ) it.each(['miss', 'reject', 'screenshot-reject'] as const)( 'preserves live %s feedback and cleanup', async (outcome) => { diff --git a/src/renderer/src/components/browser-pane/annotate/markup-drawing-model.test.ts b/src/renderer/src/components/browser-pane/annotate/markup-drawing-model.test.ts index fe6e21b4722..94844bd25a2 100644 --- a/src/renderer/src/components/browser-pane/annotate/markup-drawing-model.test.ts +++ b/src/renderer/src/components/browser-pane/annotate/markup-drawing-model.test.ts @@ -1,21 +1,13 @@ import { describe, expect, it } from 'vitest' import { - arrowHeadGeometry, canRedo, - canUndo, - clearShapes, commitShape, createMarkupDocument, - isEmptyDocument, - normalizeRect, redoShape, - scaleShape, setShapes, undoShape, - type ArrowShape, type MarkupShape, - type PenShape, - type TextShape + type PenShape } from './markup-drawing-model' function pen(id: string): PenShape { @@ -23,23 +15,6 @@ function pen(id: string): PenShape { } describe('markup document undo/redo', () => { - it('starts empty', () => { - const doc = createMarkupDocument() - expect(doc.shapes).toHaveLength(0) - expect(isEmptyDocument(doc)).toBe(true) - expect(canUndo(doc)).toBe(false) - expect(canRedo(doc)).toBe(false) - }) - - it('commits shapes in order and enables undo', () => { - let doc = createMarkupDocument() - doc = commitShape(doc, pen('a')) - doc = commitShape(doc, pen('b')) - expect(doc.shapes.map((s) => s.id)).toEqual(['a', 'b']) - expect(canUndo(doc)).toBe(true) - expect(canRedo(doc)).toBe(false) - }) - it('undoes and redoes in order', () => { let doc = createMarkupDocument() doc = commitShape(doc, pen('a')) @@ -62,21 +37,6 @@ describe('markup document undo/redo', () => { expect(canRedo(doc)).toBe(false) }) - it('undo/redo on empty document are no-ops returning the same reference', () => { - const doc = createMarkupDocument() - expect(undoShape(doc)).toBe(doc) - expect(redoShape(doc)).toBe(doc) - }) - - it('clears shapes, and is a no-op (same reference) when already empty', () => { - const empty = createMarkupDocument() - expect(clearShapes(empty)).toBe(empty) - let doc = commitShape(empty, pen('a')) - doc = clearShapes(doc) - expect(doc.shapes).toHaveLength(0) - expect(canRedo(doc)).toBe(false) - }) - it('undoes an edit (not just an add) via whole-list snapshots', () => { let doc = commitShape(createMarkupDocument(), pen('a')) doc = setShapes(doc, []) @@ -86,79 +46,5 @@ describe('markup document undo/redo', () => { }) }) -describe('markup geometry', () => { - it('normalizes rectangles from any drag direction', () => { - expect(normalizeRect({ x: 10, y: 20 }, { x: 4, y: 50 })).toEqual({ - x: 4, - y: 20, - width: 6, - height: 30 - }) - }) - - it('scales pen points and width', () => { - const shape: PenShape = { - id: 'a', - kind: 'pen', - color: '#fff', - width: 4, - points: [ - { x: 2, y: 3 }, - { x: 4, y: 5 } - ] - } - const scaled = scaleShape(shape, 2) as PenShape - expect(scaled.width).toBe(8) - expect(scaled.points).toEqual([ - { x: 4, y: 6 }, - { x: 8, y: 10 } - ]) - }) - - it('scales arrow endpoints and text position + font size', () => { - const arrow: ArrowShape = { - id: 'a', - kind: 'arrow', - color: '#fff', - width: 3, - from: { x: 1, y: 1 }, - to: { x: 3, y: 5 } - } - const scaledArrow = scaleShape(arrow, 3) as ArrowShape - expect(scaledArrow.from).toEqual({ x: 3, y: 3 }) - expect(scaledArrow.to).toEqual({ x: 9, y: 15 }) - expect(scaledArrow.width).toBe(9) - - const text: TextShape = { - id: 't', - kind: 'text', - color: '#fff', - at: { x: 10, y: 20 }, - text: 'fix', - fontSize: 18 - } - const scaledText = scaleShape(text, 2) as TextShape - expect(scaledText.at).toEqual({ x: 20, y: 40 }) - expect(scaledText.fontSize).toBe(36) - }) - - it('returns null arrowhead for a zero-length segment', () => { - expect(arrowHeadGeometry({ x: 5, y: 5 }, { x: 5, y: 5 }, 4)).toBeNull() - }) - - it('produces a symmetric arrowhead pointing at the tip', () => { - const head = arrowHeadGeometry({ x: 0, y: 0 }, { x: 10, y: 0 }, 4) - expect(head).not.toBeNull() - if (!head) { - return - } - expect(head.tip).toEqual({ x: 10, y: 0 }) - // Wings sit behind the tip (smaller x) and mirror across the horizontal axis. - expect(head.left.x).toBeLessThan(10) - expect(head.right.x).toBeLessThan(10) - expect(head.left.y).toBeCloseTo(-head.right.y, 6) - }) -}) - const _exhaustive: MarkupShape['kind'][] = ['pen', 'highlight', 'arrow', 'rect', 'ellipse', 'text'] void _exhaustive diff --git a/src/renderer/src/components/browser-pane/annotate/markup-gesture.test.ts b/src/renderer/src/components/browser-pane/annotate/markup-gesture.test.ts index a2cdfeac2f1..12532004935 100644 --- a/src/renderer/src/components/browser-pane/annotate/markup-gesture.test.ts +++ b/src/renderer/src/components/browser-pane/annotate/markup-gesture.test.ts @@ -6,8 +6,7 @@ import { redoShape, undoShape, type MarkupPoint, - type PenShape, - type TextShape + type PenShape } from './markup-drawing-model' import { applyDocumentCommand, @@ -80,14 +79,6 @@ describe('erase gesture', () => { expect(undoShape(after.doc).shapes.map((shape) => shape.id)).toEqual(['older', 'newer']) }) - it('keeps a click a click when a tap jitters by a fraction of a pixel', () => { - const pressed = erase(editorWith(line('older', 0), line('newer', 0)), [{ x: 50, y: 0 }]) - const jittered = moveGesture(pressed, 1, { x: 50.6, y: 0.4 }, noText) - - expect(jittered).toBe(pressed) - expect(ids(endGesture(jittered, 1))).toEqual(['older']) - }) - it('keeps a click a click when the pointer wanders inside the slop and back', () => { const before = editorWith(line('older', 0), line('newer', 0), line('near', 12)) // `near` is out of reach of the press point but within reach 3.5px below it. @@ -101,15 +92,6 @@ describe('erase gesture', () => { expect(ids(endGesture(wandered, 1))).toEqual(['older', 'near']) }) - it('becomes a drag once the pointer leaves the slop', () => { - const dragged = erase(editorWith(line('older', 0), line('newer', 0)), [ - { x: 50, y: 0 }, - { x: 54, y: 0 } - ]) - - expect(erasedIds(dragged)).toEqual(new Set(['older', 'newer'])) - }) - it('erases every mark under the press point once the click becomes a drag', () => { const before = editorWith(line('older', 0), line('newer', 0), line('away', 200)) // The drag leaves the marks behind straight away, so only the press point touched them. @@ -162,35 +144,6 @@ describe('erase gesture', () => { expect(ids(after)).toEqual(['a']) }) - it('keeps the same erased set while a drag hits nothing new', () => { - const begun = beginEraseGesture(editorWith(line('a', 0)), 1, { x: 50, y: 0 }, noText) - const moved = moveGesture(begun, 1, { x: 50, y: 300 }, noText) - - expect(erasedIds(begun)).toEqual(new Set(['a'])) - expect(erasedIds(moved)).toBe(erasedIds(begun)) - }) - - it('erases a text label the drag passes through', () => { - const label: TextShape = { - id: 'label', - kind: 'text', - color: '#111827', - at: { x: 200, y: 200 }, - text: 'note', - fontSize: 18 - } - const inkBox = () => ({ x: 200, y: 200, width: 60, height: 20 }) - const before: MarkupEditorState = { - doc: commitShape(createMarkupDocument(), label), - gesture: null - } - const begun = beginEraseGesture(before, 1, { x: 100, y: 210 }, inkBox) - const after = endGesture(moveGesture(begun, 1, { x: 230, y: 210 }, inkBox), 1) - - expect(erasedIds(begun)).toEqual(new Set()) - expect(ids(after)).toEqual([]) - }) - it('ignores a second pointer while a gesture is in flight', () => { const first = erase(editorWith(line('a', 0), line('b', 100)), [{ x: 50, y: 0 }], 1) @@ -231,30 +184,6 @@ describe('erase gesture', () => { }) describe('draw gesture', () => { - it('keeps every point of a freehand stroke', () => { - const begun = beginDrawGesture(editorWith(), 1, { - id: 'pen', - kind: 'highlight', - color: '#eab308', - width: 4, - points: [{ x: 0, y: 0 }] - }) - const moved = [ - { x: 10, y: 5 }, - { x: 20, y: 0 } - ].reduce((state, point) => moveGesture(state, 1, point, noText), begun) - - expect(endGesture(moved, 1).doc.shapes).toMatchObject([ - { - points: [ - { x: 0, y: 0 }, - { x: 10, y: 5 }, - { x: 20, y: 0 } - ] - } - ]) - }) - it('commits a stroke whose release was lost and starts afresh on the next press', () => { const stale = moveGesture( beginDrawGesture(editorWith(), 1, line('old', 0)), @@ -321,16 +250,6 @@ describe('draw gesture', () => { } ) - it('lets an eraser click take a mark where a shape was pressed without dragging', () => { - const at = { x: 50, y: 0 } - const clicked = endGesture( - beginDrawGesture(editorWith(line('a', 0)), 1, pressedShape('rect', at)), - 1 - ) - - expect(ids(endGesture(erase(clicked, [at]), 1))).toEqual([]) - }) - it('still saves a pen tap, which leaves a visible dot', () => { const tapped = endGesture( beginDrawGesture(editorWith(), 1, { ...line('dot', 0), points: [{ x: 50, y: 0 }] }), diff --git a/src/renderer/src/components/browser-pane/annotate/markup-screenshot-compose.test.ts b/src/renderer/src/components/browser-pane/annotate/markup-screenshot-compose.test.ts index 0f4c5869bbf..246b28d9274 100644 --- a/src/renderer/src/components/browser-pane/annotate/markup-screenshot-compose.test.ts +++ b/src/renderer/src/components/browser-pane/annotate/markup-screenshot-compose.test.ts @@ -1,31 +1,7 @@ import { describe, expect, it } from 'vitest' -import { - clampMarkupScale, - dataUrlByteLength, - effectiveMarkupScale, - markupCanvasSize -} from './markup-screenshot-compose' - -describe('clampMarkupScale', () => { - it('keeps sane scales and clamps to [1, 4]', () => { - expect(clampMarkupScale(2)).toBe(2) - expect(clampMarkupScale(0.5)).toBe(1) - expect(clampMarkupScale(10)).toBe(4) - }) - - it('guards against non-finite / non-positive values', () => { - expect(clampMarkupScale(0)).toBe(1) - expect(clampMarkupScale(-3)).toBe(1) - expect(clampMarkupScale(Number.NaN)).toBe(1) - }) -}) +import { markupCanvasSize } from './markup-screenshot-compose' describe('markupCanvasSize', () => { - it('sizes the output to the content box times the (clamped) scale', () => { - expect(markupCanvasSize(800, 600, 2)).toEqual({ width: 1600, height: 1200 }) - expect(markupCanvasSize(800, 600, 0.5)).toEqual({ width: 800, height: 600 }) - }) - it('keeps the composite within the pixel budget (never over)', () => { // A large HiDPI pane: 4000×3000 CSS × dpr 2 = 48M px > the ~33.55M (32×1024×1024) ceiling. const maxPixels = 32 * 1024 * 1024 @@ -34,30 +10,3 @@ describe('markupCanvasSize', () => { expect(size.width / size.height).toBeCloseTo(4000 / 3000, 3) }) }) - -describe('effectiveMarkupScale', () => { - it('returns the clamped scale when the composite fits the pixel budget', () => { - expect(effectiveMarkupScale(800, 600, 2, 32 * 1024 * 1024)).toBe(2) - }) - - it('reduces the scale so area stays within the pixel budget', () => { - const scale = effectiveMarkupScale(4000, 3000, 2, 32 * 1024 * 1024) - expect(scale).toBeLessThan(2) - expect(4000 * scale * (3000 * scale)).toBeLessThanOrEqual(32 * 1024 * 1024 + 1) - }) - - it('defaults to the clamped scale with no pixel budget', () => { - expect(effectiveMarkupScale(3840, 2160, 2)).toBe(2) - }) -}) - -describe('dataUrlByteLength', () => { - it('decodes the base64 payload size', () => { - expect(dataUrlByteLength('data:image/png;base64,aGk=')).toBe(2) - expect(dataUrlByteLength('data:image/png;base64,bWFu')).toBe(3) - }) - - it('returns 0 for a malformed data url', () => { - expect(dataUrlByteLength('not-a-data-url')).toBe(0) - }) -}) diff --git a/src/renderer/src/components/browser-pane/annotate/markup-shape-hit-test.test.ts b/src/renderer/src/components/browser-pane/annotate/markup-shape-hit-test.test.ts index e6eedb0f4c5..9ee654adee6 100644 --- a/src/renderer/src/components/browser-pane/annotate/markup-shape-hit-test.test.ts +++ b/src/renderer/src/components/browser-pane/annotate/markup-shape-hit-test.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { arrowHeadGeometry, type MarkupPoint, type MarkupShape } from './markup-drawing-model' +import type { MarkupPoint, MarkupShape } from './markup-drawing-model' import { shapesTouchedBySweep, topmostShapeAt } from './markup-shape-hit-test' // Pinned here on purpose: how close the pointer must come is part of the behaviour. @@ -71,21 +71,6 @@ describe('shapesTouchedBySweep', () => { expect(touches({ ...base, kind: 'pen', width: 4, points }, edge)).toBe(false) }) - it('hits an arrow on its head wings as well as its shaft', () => { - const from = { x: 0, y: 0 } - const to = { x: 200, y: 0 } - const arrow: MarkupShape = { ...base, kind: 'arrow', width: 8, from, to } - const head = arrowHeadGeometry(from, to, 8) - expect(head).not.toBeNull() - // The wing tips sit well off the shaft, so only the head geometry can match them. - for (const wing of head ? [head.left, head.right] : []) { - expect(Math.abs(wing.y)).toBeGreaterThan(4 + ERASER_RADIUS) - expect(touches(arrow, wing)).toBe(true) - } - expect(touches(arrow, { x: 100, y: 4 + ERASER_RADIUS })).toBe(true) - expect(touches(arrow, { x: 100, y: 4 + ERASER_RADIUS + 1 })).toBe(false) - }) - it('hits a rectangle on its border but not in its hollow interior', () => { const rect: MarkupShape = { ...base, diff --git a/src/shared/ephemeral-vm-runtime-store-rollback.test.ts b/src/shared/ephemeral-vm-runtime-store-rollback.test.ts index ce36709999e..d2bd379205c 100644 --- a/src/shared/ephemeral-vm-runtime-store-rollback.test.ts +++ b/src/shared/ephemeral-vm-runtime-store-rollback.test.ts @@ -1,19 +1,9 @@ -import { - existsSync, - mkdtempSync, - readFileSync, - rmSync, - statSync, - truncateSync, - utimesSync, - writeFileSync -} from 'node:fs' +import { mkdtempSync, readFileSync, rmSync, truncateSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import { getEphemeralVmRuntimeFeatureStorePath, - restoreRuntimeFeatureList, MAX_EPHEMERAL_VM_RUNTIME_FEATURE_STORE_FILE_BYTES } from './ephemeral-vm-runtime-feature-store' import { @@ -121,60 +111,6 @@ describe('ephemeral VM runtime store rollback projection', () => { expect(listEphemeralVmRuntimes(userDataPath)).toEqual([provisioned, ordinary]) }) - it('keeps ordinary v1 bytes and sidecar behavior unchanged', () => { - const userDataPath = makeUserDataPath() - const runtime = runtimeRecord() - const expected = JSON.stringify( - EphemeralVmRuntimeStoreSchema.parse({ version: 1, runtimes: [runtime] }) - ) - - upsertEphemeralVmRuntime(userDataPath, runtime) - - expect(readFileSync(getEphemeralVmRuntimeStorePath(userDataPath), 'utf8')).toBe(expected) - expect(existsSync(getEphemeralVmRuntimeFeatureStorePath(userDataPath))).toBe(false) - }) - - it('projects an explicit ordinary checkout mode without changing its current meaning', () => { - const userDataPath = makeUserDataPath() - const runtime = runtimeRecord({ - recipe: { ...runtimeRecord().recipe!, checkoutMode: 'orca-worktree' } - }) - - upsertEphemeralVmRuntime(userDataPath, runtime) - - const persisted = JSON.parse(readFileSync(getEphemeralVmRuntimeStorePath(userDataPath), 'utf8')) - expect(RollbackEphemeralVmRuntimeStoreSchema.safeParse(persisted).success).toBe(true) - expect(listEphemeralVmRuntimes(userDataPath)).toEqual([runtime]) - }) - - it('does not rewrite unchanged features when runtime order differs from feature order', () => { - const userDataPath = makeUserDataPath() - const older = { - ...provisionedRootRecord(), - id: 'a-runtime', - recipeId: 'a-recipe', - createdAt: 1_000 - } - const newer = { - ...provisionedRootRecord(), - id: 'z-runtime', - recipeId: 'z-recipe', - createdAt: 2_000 - } - upsertEphemeralVmRuntime(userDataPath, older) - upsertEphemeralVmRuntime(userDataPath, newer) - const featurePath = getEphemeralVmRuntimeFeatureStorePath(userDataPath) - const oldTimestamp = new Date('2020-01-01T00:00:00.000Z') - utimesSync(featurePath, oldTimestamp, oldTimestamp) - const beforeBytes = readFileSync(featurePath, 'utf8') - const beforeMtime = statSync(featurePath).mtimeMs - - updateEphemeralVmRuntimeStatus(userDataPath, newer.id, { status: 'suspended' }) - - expect(readFileSync(featurePath, 'utf8')).toBe(beforeBytes) - expect(statSync(featurePath).mtimeMs).toBe(beforeMtime) - }) - it('migrates current-main poisoned bytes when they are first read', () => { const userDataPath = makeUserDataPath() const poisoned = { @@ -287,30 +223,3 @@ describe('ephemeral VM runtime store rollback projection', () => { ]) }) }) - -describe('runtime feature restoration scaling', () => { - it('indexes feature identities once and preserves unmatched runtime references', () => { - let reads = 0 - const runtimes = Array.from({ length: 1000 }, (_, i) => runtimeRecord({ id: `runtime-${i}` })) - const features = runtimes.map((runtime) => ({ - get id() { - reads++ - return runtime.id - }, - recipeId: runtime.recipeId, - createdAt: runtime.createdAt, - recipeCheckoutMode: 'provisioned-root' as const - })) - const restored = restoreRuntimeFeatureList(runtimes, features) - expect(reads).toBe(1000) - expect(restored.map((runtime) => runtime.id)).toEqual(runtimes.map((runtime) => runtime.id)) - expect(restored.every((runtime) => runtime.recipe?.checkoutMode === 'provisioned-root')).toBe( - true - ) - expect(restoreRuntimeFeatureList([runtimes[0]], [])[0]).toBe(runtimes[0]) - const first = { ...features[0], recipeCheckoutMode: 'orca-worktree' as const } - expect( - restoreRuntimeFeatureList([runtimes[0]], [first, features[0]])[0].recipe?.checkoutMode - ).toBe('orca-worktree') - }) -}) diff --git a/src/shared/errno-code.test.ts b/src/shared/errno-code.test.ts deleted file mode 100644 index 236673a3f39..00000000000 --- a/src/shared/errno-code.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { errnoCodeOrUnknown } from './errno-code' - -describe('errnoCodeOrUnknown', () => { - it.each([ - ['a plain error', new Error('boom')], - ['a non-errno code', Object.assign(new Error('x'), { code: 'ERR_INVALID_ARG_TYPE' })], - ['a numeric code', Object.assign(new Error('x'), { code: 11 })], - ['a non-error throw', 'EAGAIN'] - ])('reports unknown for %s', (_label, error) => { - expect(errnoCodeOrUnknown(error)).toBe('unknown') - }) -}) diff --git a/src/shared/event-loop-yield.test.ts b/src/shared/event-loop-yield.test.ts deleted file mode 100644 index 0bbba1b36fd..00000000000 --- a/src/shared/event-loop-yield.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import { getPendingRendererYieldCountForTesting, yieldToEventLoop } from './event-loop-yield' - -afterEach(() => { - vi.unstubAllEnvs() - vi.unstubAllGlobals() -}) - -describe('yieldToEventLoop', () => { - it('uses setImmediate in Node runtimes', async () => { - const scheduleImmediate = vi.fn((callback: () => void) => queueMicrotask(callback)) - vi.stubEnv('VITEST', 'false') - vi.stubGlobal('window', undefined) - vi.stubGlobal('setImmediate', scheduleImmediate) - - await yieldToEventLoop() - - expect(scheduleImmediate).toHaveBeenCalledOnce() - }) - - it('releases callbacks during sustained concurrent renderer yields', async () => { - const postMessage = vi.fn() - let peakPendingAfterResolution = 0 - vi.stubEnv('VITEST', 'false') - vi.stubGlobal('window', {}) - vi.stubGlobal( - 'MessageChannel', - class { - port1: { onmessage: ((event: MessageEvent) => void) | null } = { onmessage: null } - port2 = { - postMessage: (data: unknown): void => { - postMessage(data) - setTimeout(() => this.port1.onmessage?.({ data } as MessageEvent), 0) - } - } - } - ) - - const runProducer = async (): Promise => { - for (let index = 0; index < 20; index += 1) { - await yieldToEventLoop() - peakPendingAfterResolution = Math.max( - peakPendingAfterResolution, - getPendingRendererYieldCountForTesting() - ) - } - } - await Promise.all([runProducer(), runProducer()]) - - expect(postMessage).toHaveBeenCalledTimes(40) - expect(peakPendingAfterResolution).toBeLessThanOrEqual(1) - expect(getPendingRendererYieldCountForTesting()).toBe(0) - }) -}) diff --git a/src/shared/execution-host-registry.test.ts b/src/shared/execution-host-registry.test.ts index 0bb5798c1b7..8243011b749 100644 --- a/src/shared/execution-host-registry.test.ts +++ b/src/shared/execution-host-registry.test.ts @@ -1,62 +1,8 @@ import { describe, expect, it } from 'vitest' -import { getExecutionHostLabel } from './execution-host' -import { MIN_COMPATIBLE_RUNTIME_SERVER_VERSION, RUNTIME_PROTOCOL_VERSION } from './protocol-version' + import { buildExecutionHostRegistry } from './execution-host-registry' -const LOCAL_HOST_LABEL = getExecutionHostLabel('local') - describe('execution host registry', () => { - it('returns only the local host for local-only state', () => { - expect( - buildExecutionHostRegistry({ - repos: [{ connectionId: null }], - settings: { activeRuntimeEnvironmentId: null } - }) - ).toEqual([ - { - id: 'local', - kind: 'local', - label: LOCAL_HOST_LABEL, - detail: 'This computer', - health: 'local' - } - ]) - }) - - it('includes saved and repo-derived SSH hosts with connection health', () => { - const hosts = buildExecutionHostRegistry({ - repos: [{ connectionId: 'repo-ssh' }], - settings: { activeRuntimeEnvironmentId: null }, - sshTargetLabels: new Map([['saved-ssh', 'Saved SSH']]), - sshConnectionStates: new Map([ - [ - 'repo-ssh', - { - targetId: 'repo-ssh', - status: 'connected', - error: null, - reconnectAttempt: 0 - } - ], - [ - 'saved-ssh', - { - targetId: 'saved-ssh', - status: 'auth-failed', - error: 'Permission denied', - reconnectAttempt: 1 - } - ] - ]) - }) - - expect(hosts).toMatchObject([ - { id: 'local', health: 'local' }, - { id: 'ssh:saved-ssh', label: 'Saved SSH', health: 'error', connectionStatus: 'auth-failed' }, - { id: 'ssh:repo-ssh', label: 'repo-ssh', health: 'available', connectionStatus: 'connected' } - ]) - }) - it('excludes repository references from configured-only registries', () => { const hosts = buildExecutionHostRegistry({ repos: [ @@ -76,31 +22,6 @@ describe('execution host registry', () => { ]) }) - it('keeps repository references in the default reference-inclusive registry', () => { - const hosts = buildExecutionHostRegistry({ - repos: [ - { connectionId: 'removed-ssh' }, - { connectionId: null, executionHostId: 'runtime:removed-runtime' } - ], - settings: { activeRuntimeEnvironmentId: null } - }) - - expect(hosts.map((host) => host.id)).toEqual([ - 'local', - 'runtime:removed-runtime', - 'ssh:removed-ssh' - ]) - }) - - it('hides machine-provider SSH targets the same way', () => { - const hosts = buildExecutionHostRegistry({ - repos: [{ connectionId: 'machine-0123', executionHostId: 'ssh:machine-0123' }], - settings: { activeRuntimeEnvironmentId: null }, - sshTargetLabels: new Map([['machine-0123', 'omarchy-dev']]) - }) - expect(hosts.some((h) => h.id.includes('machine-0123'))).toBe(false) - }) - it('hides runtime-owned (ephemeral VM) SSH targets from repo-derived hosts', () => { const hosts = buildExecutionHostRegistry({ // A VM-backed repo carries the hidden runtime-owned target on both fields. @@ -135,296 +56,4 @@ describe('execution host registry', () => { expect(hosts.map((h) => h.id)).toEqual(['local', 'runtime:env-1']) }) - - it('adds saved runtime environments and preserves compatibility state per host', () => { - const hosts = buildExecutionHostRegistry({ - repos: [], - settings: { activeRuntimeEnvironmentId: 'old-server' }, - runtimeEnvironments: [{ id: 'builder', name: 'Linux Builder' }], - runtimeStatusByEnvironmentId: new Map([ - [ - 'builder', - { - checkedAt: 0, - appVersion: '1.8.0', - status: { - runtimeId: 'runtime-builder', - rendererGraphEpoch: 1, - graphStatus: 'ready', - authoritativeWindowId: 1, - liveTabCount: 0, - liveLeafCount: 0, - runtimeProtocolVersion: RUNTIME_PROTOCOL_VERSION, - minCompatibleRuntimeClientVersion: 1, - capabilities: ['terminal.binary-stream.v1'], - hostPlatform: 'linux' - } - } - ], - [ - 'old-server', - { - checkedAt: 0, - appVersion: '1.6.0', - status: { - runtimeId: 'runtime-old', - rendererGraphEpoch: 1, - graphStatus: 'ready', - authoritativeWindowId: 1, - liveTabCount: 0, - liveLeafCount: 0, - runtimeProtocolVersion: MIN_COMPATIBLE_RUNTIME_SERVER_VERSION - 1, - minCompatibleRuntimeClientVersion: 1, - capabilities: [] - } - } - ] - ]) - }) - - expect(hosts).toMatchObject([ - { id: 'local', health: 'local' }, - { - id: 'runtime:builder', - label: 'Linux Builder', - health: 'available', - appVersion: '1.8.0', - protocolVersion: RUNTIME_PROTOCOL_VERSION, - capabilities: ['terminal.binary-stream.v1'], - platform: 'linux', - compatibility: { kind: 'ok' } - }, - { - id: 'runtime:old-server', - label: 'old-server', - health: 'blocked', - appVersion: '1.6.0', - compatibility: { kind: 'blocked', reason: 'server-too-old' } - } - ]) - }) - - it('uses shared-control diagnostics to show reconnecting runtime host health', () => { - const hosts = buildExecutionHostRegistry({ - repos: [], - settings: { activeRuntimeEnvironmentId: null }, - runtimeEnvironments: [{ id: 'dev-box', name: 'Dev Box' }], - runtimeStatusByEnvironmentId: new Map([ - [ - 'dev-box', - { - checkedAt: 0, - status: { - runtimeId: 'runtime-dev', - rendererGraphEpoch: 1, - graphStatus: 'ready', - authoritativeWindowId: 1, - liveTabCount: 0, - liveLeafCount: 0, - remoteControl: { - state: 'reconnecting', - pendingRequestCount: 0, - subscriptionCount: 2, - reconnectAttempt: 1, - lastConnectedAt: 123, - lastClose: { code: 1006, reason: '' }, - lastError: 'Remote Orca runtime closed the connection.' - } - } - } - ] - ]) - }) - - expect(hosts).toMatchObject([ - { id: 'local', health: 'local' }, - { - id: 'runtime:dev-box', - label: 'Dev Box', - health: 'connecting', - remoteControlState: { state: 'reconnecting', subscriptionCount: 2 } - } - ]) - }) - - it('treats ready shared-control diagnostics without a status payload as available', () => { - const hosts = buildExecutionHostRegistry({ - repos: [], - settings: { activeRuntimeEnvironmentId: null }, - runtimeEnvironments: [{ id: 'dev-box', name: 'Dev Box' }], - runtimeStatusByEnvironmentId: new Map([ - [ - 'dev-box', - { - checkedAt: 0, - status: null, - remoteControl: { - state: 'ready', - pendingRequestCount: 0, - subscriptionCount: 1, - reconnectAttempt: 0, - lastConnectedAt: 123, - lastClose: null, - lastError: null - } - } - ] - ]) - }) - - expect(hosts).toMatchObject([ - { id: 'local', health: 'local' }, - { - id: 'runtime:dev-box', - label: 'Dev Box', - health: 'available', - remoteControlState: { state: 'ready' } - } - ]) - }) - - it('preserves runtime environment source on runtime hosts', () => { - const hosts = buildExecutionHostRegistry({ - repos: [], - settings: { activeRuntimeEnvironmentId: null }, - runtimeEnvironments: [{ id: 'vm-runtime', name: 'VM Runtime', source: 'ephemeral-vm' }], - runtimeStatusByEnvironmentId: new Map([ - [ - 'vm-runtime', - { - checkedAt: 0, - status: { - runtimeId: 'runtime-vm', - rendererGraphEpoch: 1, - graphStatus: 'ready', - authoritativeWindowId: 1, - liveTabCount: 0, - liveLeafCount: 0, - runtimeProtocolVersion: RUNTIME_PROTOCOL_VERSION, - minCompatibleRuntimeClientVersion: 1, - capabilities: ['project-host-setup.v1'] - } - } - ] - ]) - }) - - expect(hosts).toContainEqual( - expect.objectContaining({ - id: 'runtime:vm-runtime', - kind: 'runtime', - source: 'ephemeral-vm' - }) - ) - }) - - it('applies per-host display-label overrides to derived labels', () => { - const hosts = buildExecutionHostRegistry({ - repos: [{ connectionId: 'repo-ssh' }], - settings: { activeRuntimeEnvironmentId: null }, - sshTargetLabels: new Map([['repo-ssh', 'Derived SSH']]), - hostLabelOverrides: new Map([ - ['ssh:repo-ssh', 'Renamed Box'], - ['local', 'My Laptop'] - ]) - }) - - expect(hosts).toMatchObject([ - { id: 'local', label: 'My Laptop' }, - { id: 'ssh:repo-ssh', label: 'Renamed Box' } - ]) - }) - - it('keeps derived labels for hosts without an override', () => { - const hosts = buildExecutionHostRegistry({ - repos: [{ connectionId: 'repo-ssh' }], - settings: { activeRuntimeEnvironmentId: null }, - sshTargetLabels: new Map([['repo-ssh', 'Derived SSH']]), - hostLabelOverrides: new Map([['ssh:other', 'Unrelated']]) - }) - - expect(hosts).toMatchObject([ - { id: 'local', label: LOCAL_HOST_LABEL }, - { id: 'ssh:repo-ssh', label: 'Derived SSH' } - ]) - }) - - it('keeps runtime hosts checking before their first status result', () => { - const hosts = buildExecutionHostRegistry({ - repos: [{ connectionId: null, executionHostId: 'runtime:env-2' }], - settings: { activeRuntimeEnvironmentId: null } - }) - - expect(hosts).toMatchObject([ - { id: 'local', health: 'local' }, - { id: 'runtime:env-2', kind: 'runtime', label: 'env-2', health: 'connecting' } - ]) - }) - - it('includes runtime hosts from hydrated status even when they are not focused', () => { - const hosts = buildExecutionHostRegistry({ - repos: [], - settings: { activeRuntimeEnvironmentId: null }, - runtimeStatusByEnvironmentId: new Map([ - [ - 'gpu', - { - checkedAt: 0, - appVersion: '1.8.0', - status: { - runtimeId: 'runtime-gpu', - rendererGraphEpoch: 1, - graphStatus: 'ready', - authoritativeWindowId: 1, - liveTabCount: 0, - liveLeafCount: 0, - runtimeProtocolVersion: RUNTIME_PROTOCOL_VERSION, - minCompatibleRuntimeClientVersion: 1, - capabilities: ['project-host-setup.v1'], - hostPlatform: 'linux' - } - } - ] - ]) - }) - - expect(hosts).toMatchObject([ - { id: 'local', health: 'local' }, - { - id: 'runtime:gpu', - kind: 'runtime', - label: 'gpu', - health: 'available', - capabilities: ['project-host-setup.v1'], - platform: 'linux' - } - ]) - }) -}) - -it('keeps an initial unknown-transport verification connecting', () => { - const hosts = buildExecutionHostRegistry({ - repos: [], - settings: null, - runtimeEnvironments: [{ id: 'host', name: 'Host' }], - runtimeStatusByEnvironmentId: new Map([ - [ - 'host', - { - checkedAt: 0, - status: null, - snapshot: { - environmentId: 'host', - pairingRevision: 1, - sequence: 1, - checkedAt: 0, - status: null, - verification: 'checking', - transport: 'unknown' - } - } - ] - ]) - }) - expect(hosts.find((host) => host.id === 'runtime:host')?.health).toBe('connecting') }) diff --git a/src/shared/execution-host.test.ts b/src/shared/execution-host.test.ts index 1c10fa57e2c..6d661518823 100644 --- a/src/shared/execution-host.test.ts +++ b/src/shared/execution-host.test.ts @@ -2,25 +2,15 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import { ALL_EXECUTION_HOSTS_SCOPE, LOCAL_EXECUTION_HOST_ID, - getConnectionExecutionHostId, - getExecutionHostLabel, - getLocalExecutionHostLabel, getRepoDialableSshTargetId, getRepoExecutionHostId, getRepoSshConnectionId, - getSettingsFocusedExecutionHostId, getWorktreeExecutionHostId, - isOwnedSshTargetId, isRepoOnCatalogHost, - isRuntimeOwnedSshTargetId, isUnresolvedOwnerHostId, - normalizeExecutionHostOrder, - normalizeExecutionHostScope, - normalizeVisibleExecutionHostIds, parseExecutionHostId, parseRoutableExecutionHostId, requestedExecutionHostScope, - toRuntimeExecutionHostId, toSshExecutionHostId, UNRESOLVED_OWNER_HOST_ID } from './execution-host' @@ -31,49 +21,6 @@ describe('execution host identity', () => { vi.unstubAllGlobals() }) - it('normalizes local, SSH, and runtime host ids', () => { - expect(parseExecutionHostId('local')).toEqual({ kind: 'local', id: 'local' }) - expect(parseExecutionHostId(toSshExecutionHostId('win vm'))).toEqual({ - kind: 'ssh', - id: 'ssh:win%20vm', - targetId: 'win vm' - }) - expect(parseExecutionHostId(toRuntimeExecutionHostId('prod/server'))).toEqual({ - kind: 'runtime', - id: 'runtime:prod%2Fserver', - environmentId: 'prod/server' - }) - }) - - it('labels the local host by platform and by navigator detection', () => { - expect(getLocalExecutionHostLabel('darwin')).toBe('Local Mac') - expect(getLocalExecutionHostLabel('win32')).toBe('Local Windows') - expect(getLocalExecutionHostLabel('linux')).toBe('Local Linux') - expect(getLocalExecutionHostLabel('freebsd')).toBe('This computer') - - // Exercise navigator detection used by the live host selector. - vi.stubGlobal('navigator', { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' }) - expect(getLocalExecutionHostLabel()).toBe('Local Windows') - - vi.stubGlobal('navigator', { userAgent: 'Mozilla/5.0 (X11; Linux x86_64)' }) - expect(getLocalExecutionHostLabel()).toBe('Local Linux') - - vi.stubGlobal('navigator', { userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)' }) - expect(getLocalExecutionHostLabel()).toBe('Local Mac') - - // Compare to the explicit platform fallback so this remains deterministic across CI hosts. - vi.stubGlobal('navigator', { userAgent: 'totally-unknown-agent' }) - expect(getLocalExecutionHostLabel()).toBe(getLocalExecutionHostLabel(process.platform)) - }) - - it('falls back invalid scopes to all hosts', () => { - expect(normalizeExecutionHostScope(null)).toBe(ALL_EXECUTION_HOSTS_SCOPE) - expect(normalizeExecutionHostScope('')).toBe(ALL_EXECUTION_HOSTS_SCOPE) - expect(normalizeExecutionHostScope('bogus')).toBe(ALL_EXECUTION_HOSTS_SCOPE) - expect(normalizeExecutionHostScope('ssh:')).toBe(ALL_EXECUTION_HOSTS_SCOPE) - expect(normalizeExecutionHostScope('all')).toBe(ALL_EXECUTION_HOSTS_SCOPE) - }) - it('defaults an omitted scope request to this host, not a fan-out', () => { expect(requestedExecutionHostScope(undefined)).toBe(LOCAL_EXECUTION_HOST_ID) expect(requestedExecutionHostScope(null)).toBe(LOCAL_EXECUTION_HOST_ID) @@ -84,34 +31,6 @@ describe('execution host identity', () => { expect(requestedExecutionHostScope('ssh:dev%20box')).toBe('ssh:dev%20box') }) - it('normalizes visible host id arrays', () => { - expect(normalizeVisibleExecutionHostIds(null)).toBeNull() - expect(normalizeVisibleExecutionHostIds([])).toBeNull() - expect(normalizeVisibleExecutionHostIds(['local', 'bogus', 'ssh:win%20vm', 'local'])).toEqual([ - 'local', - 'ssh:win%20vm' - ]) - }) - - it('normalizes host order arrays', () => { - expect(normalizeExecutionHostOrder(null)).toEqual([]) - expect(normalizeExecutionHostOrder([])).toEqual([]) - expect(normalizeExecutionHostOrder(['ssh:win%20vm', 'bogus', 'local', 'ssh:win%20vm'])).toEqual( - ['ssh:win%20vm', 'local'] - ) - }) - - it('maps a legacy connection id to its host without trimming it', () => { - expect(getConnectionExecutionHostId(null)).toBe('local') - expect(getConnectionExecutionHostId('')).toBe('local') - expect(getConnectionExecutionHostId('a b')).toBe('ssh:a%20b') - }) - - it('derives repo ownership from SSH connection ids', () => { - expect(getRepoExecutionHostId({ connectionId: null })).toBe(LOCAL_EXECUTION_HOST_ID) - expect(getRepoExecutionHostId({ connectionId: 'ssh-target-1' })).toBe('ssh:ssh-target-1') - }) - it('prefers explicit worktree ownership before repo and focused-host fallbacks', () => { expect( getWorktreeExecutionHostId( @@ -157,13 +76,6 @@ describe('execution host identity', () => { 'ssh:runtime-ssh-vm-1' ) }) - - it('derives focused host compatibility from active runtime settings', () => { - expect(getSettingsFocusedExecutionHostId(null)).toBe(LOCAL_EXECUTION_HOST_ID) - expect(getSettingsFocusedExecutionHostId({ activeRuntimeEnvironmentId: 'runtime-1' })).toBe( - 'runtime:runtime-1' - ) - }) }) describe('execution host id delimiter invariant', () => { @@ -177,17 +89,6 @@ describe('execution host id delimiter invariant', () => { targetId: 'a|b' }) }) - - // An invalid id denotes one unknown host; All hosts would misrepresent its routing scope. - it('labels an id that names no host as one unknown host, not as every host', () => { - for (const id of ['ssh:', 'ssh:a|b', 'ssh:%zz', 'runtime:', 'quantum:box'] as const) { - expect(getExecutionHostLabel(id as never)).toBe('Unknown host') - } - expect(getExecutionHostLabel(null)).toBe('Unknown host') - expect(getExecutionHostLabel(ALL_EXECUTION_HOSTS_SCOPE)).toBe('All hosts') - expect(getExecutionHostLabel('ssh:box')).toBe('box') - expect(getExecutionHostLabel('runtime:env-1')).toBe('env-1') - }) }) describe('unresolved-owner sentinel', () => { @@ -213,14 +114,3 @@ describe('isRepoOnCatalogHost', () => { expect(isRepoOnCatalogHost({ executionHostId: 'future:host' })).toBe(false) }) }) - -describe('owned SSH target ids', () => { - it('hides recipe-VM and machine targets but keeps the recipe-VM meaning narrow', () => { - expect(isOwnedSshTargetId('runtime-ssh-vm-1')).toBe(true) - expect(isOwnedSshTargetId('machine-0123456789abcdef0123')).toBe(true) - expect(isOwnedSshTargetId('ssh-1792393612859-abc123')).toBe(false) - expect(isOwnedSshTargetId(null)).toBe(false) - // Only recipe VMs carry "replaced every provision" semantics. - expect(isRuntimeOwnedSshTargetId('machine-0123456789abcdef0123')).toBe(false) - }) -}) diff --git a/src/shared/export-let-function-initializer-ban.test.ts b/src/shared/export-let-function-initializer-ban.test.ts deleted file mode 100644 index f8e02de41ea..00000000000 --- a/src/shared/export-let-function-initializer-ban.test.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join, resolve } from 'node:path' -import { beforeAll, describe, expect, it } from 'vitest' -import type { parseAst as ParseAstFn } from 'vite' with { 'resolution-mode': 'import' } -import { scanSourceTree } from './source-scan/source-tree-scan' - -// Dynamic import: vite is ESM-only and this file typechecks under tsconfig.tc.cli.json's node16/CJS. -let parseAst: typeof ParseAstFn - -beforeAll(async () => { - ;({ parseAst } = await import('vite')) -}) - -/** Rolldown folds function-initialized export let bindings into callers, ignoring setter reassignments in built apps. - * Use local null-initialized impl slots behind exported wrappers (src/main/ipc/pty/delivery/debug.ts); null-initialized exports remain allowed. - * Use the bundler’s own Oxc parser to detect this hazard. */ - -type AstNode = { - type: string - start?: number - expression?: AstNode - expressions?: AstNode[] - declaration?: AstNode - kind?: string - declarations?: { - id?: { name?: string } - init?: AstNode | null - start?: number - }[] - specifiers?: { local?: { name?: string } }[] -} - -/** Casts, parens, `!`, and comma sequences around the function don't change what rolldown folds. */ -function unwrapInitializer(node: AstNode): AstNode { - let current = node - for (;;) { - if ( - current.expression && - [ - 'ParenthesizedExpression', - 'TSAsExpression', - 'TSSatisfiesExpression', - 'TSTypeAssertion', - 'TSNonNullExpression' - ].includes(current.type) - ) { - current = current.expression - continue - } - const sequenceTail = - current.type === 'SequenceExpression' ? current.expressions?.at(-1) : undefined - if (sequenceTail) { - current = sequenceTail - continue - } - return current - } -} - -export type FunctionInitializedExportLet = { name: string; line: number } - -export function findFunctionInitializedExportLets( - fileName: string, - sourceText: string -): FunctionInitializedExportLet[] { - // Cheap prefilter: full parses only for files that could contain the pattern. - if (!/\bexport\b/.test(sourceText) || !/\blet\b/.test(sourceText)) { - return [] - } - const lineOf = (offset: number): number => sourceText.slice(0, offset).split('\n').length - const program = parseAst(sourceText, { - lang: fileName.endsWith('.tsx') ? 'tsx' : 'ts' - }) as unknown as { body: AstNode[] } - const functionLets: (FunctionInitializedExportLet & { exported: boolean })[] = [] - const exportedNames = new Set() - const collectLets = (declaration: AstNode | undefined, exported: boolean): void => { - if (declaration?.type !== 'VariableDeclaration' || declaration.kind !== 'let') { - return - } - for (const declarator of declaration.declarations ?? []) { - const initializer = declarator.init && unwrapInitializer(declarator.init) - if ( - initializer && - ['ArrowFunctionExpression', 'FunctionExpression'].includes(initializer.type) - ) { - functionLets.push({ - name: declarator.id?.name ?? '', - line: lineOf(declarator.start ?? 0), - exported - }) - } - } - } - for (const statement of program.body) { - if (statement.type === 'ExportNamedDeclaration') { - collectLets(statement.declaration, true) - // `let fn = () => {}; export { fn }` is the same live binding — same hazard. - for (const specifier of statement.specifiers ?? []) { - if (specifier.local?.name) { - exportedNames.add(specifier.local.name) - } - } - continue - } - collectLets(statement, false) - } - return functionLets - .filter((candidate) => candidate.exported || exportedNames.has(candidate.name)) - .map(({ name, line }) => ({ name, line })) -} - -describe('function-initialized export let ban', () => { - it('flags the shapes rolldown miscompiles', () => { - const flagged = [ - 'export let f = () => {}', - 'export let f = function () {}', - 'export let f = async () => {}', - 'export let f = (() => {}) as () => void', - 'export let f = (() => {})!', - 'export let f = (0, () => {})', - 'export let f: (id: string) => void = (_id) => {}', - 'let f = () => {}\nexport { f }', - 'let f = () => {}\nexport { f as g }' - ] - for (const source of flagged) { - expect(findFunctionInitializedExportLets('a.ts', source), source).toHaveLength(1) - } - }) - - it('reports the declaration line', () => { - const source = 'const a = 1\n\nexport let f = () => {}' - expect(findFunctionInitializedExportLets('a.ts', source)).toEqual([{ name: 'f', line: 3 }]) - }) - - it('allows the safe shapes', () => { - const allowed = [ - 'export let f: (() => void) | null = null', - 'export const f = () => {}', - 'let f = () => {}', - 'let f: (() => void) | null = null\nexport { f }', - 'export let n = 0', - 'export let f' - ] - for (const source of allowed) { - expect(findFunctionInitializedExportLets('a.ts', source), source).toEqual([]) - } - }) - - const repoRoot = resolve(__dirname, '..', '..') - // Tests deliberately exercise banned shapes and are not Rolldown-bundled; scanSourceTree excludes them. - const files = scanSourceTree(join(repoRoot, 'src')) - - it('scans shipped sources and excludes hazardous test fixtures', () => { - expect( - files.some(({ relativePath }) => relativePath === 'main/ipc/pty/delivery/debug.ts') - ).toBe(true) - const root = mkdtempSync(join(tmpdir(), 'orca-export-let-')) - try { - writeFileSync(join(root, 'bridge.ts'), 'export let bridge = () => {}') - writeFileSync(join(root, 'bridge.test.ts'), 'export let fixture = () => {}') - - const offenders = scanSourceTree(root).flatMap(({ relativePath, source }) => - findFunctionInitializedExportLets(relativePath, source) - ) - - expect(offenders).toEqual([{ name: 'bridge', line: 1 }]) - } finally { - rmSync(root, { recursive: true, force: true }) - } - }) - - it('has no function-initialized export let in shipped code', () => { - const offenders = files.flatMap(({ relativePath, source }) => - findFunctionInitializedExportLets(relativePath, source).map( - (offender) => `src/${relativePath}:${offender.line} (${offender.name})` - ) - ) - expect( - offenders, - 'Rolldown miscompiles `export let fn = ` bridges: the initializer is folded into call sites and the setter reassignment is dropped in the built app. Use a module-local null-initialized impl var behind an exported wrapper function instead — see src/main/ipc/pty/delivery/debug.ts.' - ).toEqual([]) - }) -}) diff --git a/src/shared/external-automation-jobs-file.test.ts b/src/shared/external-automation-jobs-file.test.ts index 3c9ed7fbd75..a38e64d5a09 100644 --- a/src/shared/external-automation-jobs-file.test.ts +++ b/src/shared/external-automation-jobs-file.test.ts @@ -21,20 +21,6 @@ async function tempFile(name: string): Promise { } describe('readExternalAutomationJobsFile', () => { - it('preserves supported jobs shapes below the limits', async () => { - const wrappedPath = await tempFile('wrapped.json') - const rootPath = await tempFile('root.json') - await writeFile(wrappedPath, JSON.stringify({ jobs: [{ id: 'one' }] })) - await writeFile(rootPath, JSON.stringify([{ id: 'two' }])) - - await expect( - readExternalAutomationJobsFile(wrappedPath, { allowRootArray: false }) - ).resolves.toEqual([{ id: 'one' }]) - await expect( - readExternalAutomationJobsFile(rootPath, { allowRootArray: true }) - ).resolves.toEqual([{ id: 'two' }]) - }) - it('rejects oversized sparse files with the explicit memory-limit error', async () => { const path = await tempFile('oversized.json') await writeFile(path, '') diff --git a/src/shared/external-worktree-inbox.test.ts b/src/shared/external-worktree-inbox.test.ts index dad9f6870de..66bb7675a3f 100644 --- a/src/shared/external-worktree-inbox.test.ts +++ b/src/shared/external-worktree-inbox.test.ts @@ -1,22 +1,13 @@ import { describe, expect, it } from 'vitest' -import type { GlobalSettings } from './global-settings-types' import type { Repo } from './repo-types' -import type { DetectedWorktree, DetectedWorktreeListResult, Worktree } from './worktree/types' +import type { DetectedWorktree, DetectedWorktreeListResult } from './worktree/types' import { getHiddenExternalWorktrees, getHiddenImportableExternalWorktrees, - getNewExternalWorktreeInboxWorktrees, - getVisibleExternalWorktrees, - getVisibleNonOrcaWorktrees, - mergeExternalWorktreeInboxPaths, - shouldOfferNewExternalWorktreeInbox + getNewExternalWorktreeInboxWorktrees } from './external-worktree-inbox' -import { - buildKnownOrcaWorkspaceLayouts, - EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT, - toDetectedWorktree -} from './worktree/ownership' +import { EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT } from './worktree/ownership' const repo: Repo = { id: 'repo-1', @@ -63,118 +54,7 @@ function detectedResult(worktrees: DetectedWorktree[]): DetectedWorktreeListResu } } -function makeSettings(): GlobalSettings { - return { - workspaceDir: '/orca/workspaces', - nestWorkspaces: true, - workspaceDirHistory: [], - refreshLocalBaseRefOnWorktreeCreate: false, - localBaseRefSuggestionDismissed: false, - branchPrefix: 'none', - branchPrefixCustom: '', - theme: 'system', - appFontFamily: 'Geist', - editorAutoSave: false, - editorAutoSaveDelayMs: 1000, - editorMinimapEnabled: false, - markdownReviewToolsEnabled: true, - terminalFontSize: 14, - terminalFontFamily: 'monospace', - terminalFontWeight: 400, - terminalLineHeight: 1.2 - } as unknown as GlobalSettings -} - -function makeGitWorktree(overrides: Partial = {}): Worktree { - return { - id: `repo-1::${overrides.path ?? '/repo'}`, - repoId: repo.id, - path: '/repo', - displayName: 'repo', - branch: 'refs/heads/main', - head: 'abc123', - isBare: false, - isMainWorktree: true, - comment: '', - linkedIssue: null, - linkedPR: null, - linkedLinearIssue: null, - linkedGitLabMR: null, - linkedGitLabIssue: null, - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 0, - workspaceStatus: 'todo', - ...overrides - } -} - describe('external worktree inbox', () => { - it('merges inbox paths without duplicates when paths match after normalization', () => { - expect(mergeExternalWorktreeInboxPaths(['/repo/one/'], ['/repo/one', '/repo/two'])).toEqual([ - '/repo/one/', - '/repo/two' - ]) - }) - - it('offers the inbox only after the initial prompt is dismissed and discovery is not suppressed', () => { - expect(shouldOfferNewExternalWorktreeInbox(repo)).toBe(true) - expect( - shouldOfferNewExternalWorktreeInbox({ - ...repo, - externalWorktreeVisibilityPromptDismissedAt: undefined - }) - ).toBe(false) - expect( - shouldOfferNewExternalWorktreeInbox({ - ...repo, - externalWorktreeDiscoverySuppressedAt: 1 - }) - ).toBe(false) - expect( - shouldOfferNewExternalWorktreeInbox({ - ...repo, - externalWorktreeVisibility: 'show' - }) - ).toBe(false) - }) - - it('returns only hidden external worktrees outside the inbox baseline', () => { - const hidden = detectedWorktree({ id: 'hidden', path: '/scratch/new-one' }) - const baselined = detectedWorktree({ id: 'baselined', path: '/scratch/old-one' }) - const detected = detectedResult([ - hidden, - baselined, - detectedWorktree({ id: 'visible', visible: true }), - detectedWorktree({ id: 'orca-managed', ownership: 'orca-managed' }) - ]) - - expect(getHiddenExternalWorktrees(detected)).toEqual([hidden, baselined]) - expect( - getNewExternalWorktreeInboxWorktrees(detected, { - ...repo, - externalWorktreeInboxBaselinePaths: ['/scratch/old-one'] - }) - ).toEqual([hidden]) - }) - - it('excludes explicitly visible agent scratch from visibility-control counts', () => { - const visible = detectedWorktree({ id: 'visible', visible: true }) - const scratch = detectedWorktree({ - id: 'agent-scratch', - ownership: 'agent-scratch', - visible: true - }) - - expect(getVisibleExternalWorktrees(detectedResult([visible, scratch]))).toEqual([visible]) - expect(getVisibleNonOrcaWorktrees(detectedResult([visible, scratch]))).toEqual([ - visible, - scratch - ]) - }) - it('lists hidden agent scratch among importable worktrees so recovery stays reachable', () => { // Scratch cannot be revealed by the visibility toggle (#9388); per-path import preserves recovery (#10324). const hiddenScratch = detectedWorktree({ @@ -205,25 +85,6 @@ describe('external worktree inbox', () => { expect(getHiddenImportableExternalWorktrees(undefined)).toEqual([]) }) - it('offers metadata-free nested Orca workspace worktrees through the inbox', () => { - const settings = makeSettings() - const manual = toDetectedWorktree({ - repo, - settings, - worktree: makeGitWorktree({ - path: '/orca/workspaces/orca/manual-from-git', - displayName: 'manual-from-git', - branch: 'refs/heads/manual-from-git', - isMainWorktree: false - }), - knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo) - }) - - expect(manual.ownership).toBe('external') - expect(manual.visible).toBe(false) - expect(getNewExternalWorktreeInboxWorktrees(detectedResult([manual]), repo)).toEqual([manual]) - }) - it('suppresses non-authoritative detected results', () => { expect( getNewExternalWorktreeInboxWorktrees(detectedResult([detectedWorktree()]), { diff --git a/src/shared/feature-interactions.test.ts b/src/shared/feature-interactions.test.ts deleted file mode 100644 index bc3be043c09..00000000000 --- a/src/shared/feature-interactions.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - getFeatureInteractionUsageBucket, - hasFeatureInteraction, - normalizeFeatureInteractionTelemetryBuckets, - normalizeFeatureInteractions -} from './feature-interactions' - -describe('feature interactions', () => { - it('normalizes persisted records by removing unknown ids and malformed values', () => { - expect( - normalizeFeatureInteractions({ - tasks: { firstInteractedAt: 100 }, - browser: { firstInteractedAt: Number.NaN }, - automations: { firstInteractedAt: 200, interactionCount: 3 }, - 'browser-grab': { firstInteractedAt: 250, interactionCount: 0 }, - unknown: { firstInteractedAt: 200 }, - 'voice-dictation': { firstInteractedAt: 300 } - }) - ).toEqual({ - tasks: { firstInteractedAt: 100, interactionCount: 1 }, - automations: { firstInteractedAt: 200, interactionCount: 3 }, - 'browser-grab': { firstInteractedAt: 250, interactionCount: 1 }, - 'voice-dictation': { firstInteractedAt: 300, interactionCount: 1 } - }) - }) - - it('treats only valid known records as interacted', () => { - expect( - hasFeatureInteraction({ tasks: { firstInteractedAt: 100, interactionCount: 1 } }, 'tasks') - ).toBe(true) - expect( - hasFeatureInteraction({ tasks: { firstInteractedAt: 100, interactionCount: 1 } }, 'browser') - ).toBe(false) - expect( - hasFeatureInteraction( - { tasks: { firstInteractedAt: Number.POSITIVE_INFINITY, interactionCount: 1 } }, - 'tasks' - ) - ).toBe(false) - }) - - it('maps interaction counts to the exact top-coded telemetry buckets', () => { - expect(getFeatureInteractionUsageBucket(0)).toBeNull() - expect(getFeatureInteractionUsageBucket(1)).toBe('count_1') - expect(getFeatureInteractionUsageBucket(2)).toBe('count_2') - expect(getFeatureInteractionUsageBucket(3)).toBe('count_3_4') - expect(getFeatureInteractionUsageBucket(4)).toBe('count_3_4') - expect(getFeatureInteractionUsageBucket(5)).toBe('count_5_9') - expect(getFeatureInteractionUsageBucket(999)).toBe('count_500_999') - expect(getFeatureInteractionUsageBucket(1000)).toBe('count_1000_plus') - expect(getFeatureInteractionUsageBucket(1001)).toBe('count_1000_plus') - }) - - it('normalizes persisted telemetry bucket markers by removing unknown ids and buckets', () => { - expect( - normalizeFeatureInteractionTelemetryBuckets({ - tasks: 'count_1', - browser: 'count_1000_plus', - automations: 'count_4', - unknown: 'count_1', - 'voice-dictation': null - }) - ).toEqual({ - tasks: 'count_1', - browser: 'count_1000_plus' - }) - }) -}) diff --git a/src/shared/feature-wall-tour-depth.test.ts b/src/shared/feature-wall-tour-depth.test.ts deleted file mode 100644 index d8a4229ba30..00000000000 --- a/src/shared/feature-wall-tour-depth.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { AgentsStepId } from './agents-orchestration-steps' -import type { FeatureWallWorkflowId } from './feature-wall-workflows' -import type { ReviewStepId } from './review-steps' -import type { WorkbenchStepId } from './workbench-steps' -import { - buildFeatureWallTourDepthSummary, - getFeatureWallTourDepthStep -} from './feature-wall-tour-depth' - -describe('feature wall tour depth summary', () => { - it('maps workflow and nested steps to canonical depth values', () => { - expect(getFeatureWallTourDepthStep({ workflowId: 'workspaces' })).toBe('workspaces') - expect( - getFeatureWallTourDepthStep({ - workflowId: 'agents-orchestration', - agentStepId: 'usage' - }) - ).toBe('agents_usage') - expect( - getFeatureWallTourDepthStep({ workflowId: 'workbench', workbenchStepId: 'browser' }) - ).toBe('workbench_browser') - expect(getFeatureWallTourDepthStep({ workflowId: 'review', reviewStepId: 'ship' })).toBe( - 'review_ship' - ) - }) - - it('builds session-local counts and furthest step from visited sets', () => { - expect( - buildFeatureWallTourDepthSummary({ - visitedWorkflows: new Set(['workspaces', 'workbench']), - visitedAgentSteps: new Set(), - visitedWorkbenchSteps: new Set(['terminal', 'editor']), - visitedReviewSteps: new Set(), - workflowDone: { - workspaces: true, - tasks: false, - 'agents-orchestration': false, - workbench: false, - review: false - }, - agentStepDone: { - statuses: false, - usage: false, - orchestration: false - }, - workbenchStepDone: { - terminal: true, - editor: true, - browser: false - }, - reviewStepDone: { - notes: false, - 'pr-view': false, - ship: false - }, - lastGroupId: 'workbench' - }) - ).toEqual({ - furthest_step: 'workbench_editor', - last_group_id: 'workbench', - visited_workflow_count: 2, - visited_substep_count: 2, - completed_workflow_count: 1, - completed_substep_count: 2 - }) - }) -}) diff --git a/src/shared/feedback-image-limits.test.ts b/src/shared/feedback-image-limits.test.ts deleted file mode 100644 index 6f222e69dc3..00000000000 --- a/src/shared/feedback-image-limits.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { MAX_FEEDBACK_IMAGE_BYTES, MAX_FEEDBACK_IMAGE_TOTAL_BYTES } from './feedback-image-limits' - -// Vercel rejects feedback bodies above 4.5 MB with 413 FUNCTION_PAYLOAD_TOO_LARGE (#22466); keep the budget below it. -const VERCEL_FUNCTION_PAYLOAD_LIMIT_BYTES = 4_500_000 - -describe('feedback image limits', () => { - it('keeps the whole attachment budget under the endpoint host request-body limit', () => { - expect(MAX_FEEDBACK_IMAGE_TOTAL_BYTES).toBeLessThan(VERCEL_FUNCTION_PAYLOAD_LIMIT_BYTES) - }) - - it('cannot let one image exceed the whole budget', () => { - expect(MAX_FEEDBACK_IMAGE_BYTES).toBeLessThanOrEqual(MAX_FEEDBACK_IMAGE_TOTAL_BYTES) - }) -}) diff --git a/src/shared/file-explorer-display-root.test.ts b/src/shared/file-explorer-display-root.test.ts index fe57dcb7a50..0a4aa375880 100644 --- a/src/shared/file-explorer-display-root.test.ts +++ b/src/shared/file-explorer-display-root.test.ts @@ -1,29 +1,7 @@ import { describe, expect, it } from 'vitest' -import { - FILE_EXPLORER_FULL_ROOT, - migrateExplorerDisplayRoots, - normalizeExplorerDisplayRootByWorktree -} from './file-explorer-display-root' +import { normalizeExplorerDisplayRootByWorktree } from './file-explorer-display-root' describe('explorer root persistence', () => { - it('preserves existing sparse worktrees without overriding explicit choices', () => { - expect( - migrateExplorerDisplayRoots({ chosen: 'packages/app' }, false, { - old: { sparseDirectories: ['src'] }, - chosen: { sparseDirectories: ['packages/app'] }, - normal: {} - }) - ).toEqual({ old: FILE_EXPLORER_FULL_ROOT, chosen: 'packages/app' }) - }) - it('does not seed newly created worktrees on subsequent loads', () => { - const migrated = migrateExplorerDisplayRoots({}, false, { old: { sparseDirectories: ['src'] } }) - expect( - migrateExplorerDisplayRoots(migrated, true, { - old: { sparseDirectories: ['src'] }, - newlyCreated: { sparseDirectories: ['app'] } - }) - ).toEqual({ old: FILE_EXPLORER_FULL_ROOT }) - }) it('rejects malformed records and unsafe keys', () => { expect(normalizeExplorerDisplayRootByWorktree(['src'])).toEqual({}) expect( diff --git a/src/shared/file-name-sort.test.ts b/src/shared/file-name-sort.test.ts deleted file mode 100644 index ab778c34138..00000000000 --- a/src/shared/file-name-sort.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { compareFileNames, sortDirEntries } from './file-name-sort' - -describe('compareFileNames', () => { - it('sorts numeric name segments naturally instead of lexicographically', () => { - const names = [ - '1 - item.txt', - '100 - item.txt', - '2 - item.txt', - '200 - item.txt', - '409 - item.txt', - '41 - item.txt', - '410 - item.txt', - '9 - item.txt', - '99 - item.txt' - ] - expect([...names].sort(compareFileNames)).toEqual([ - '1 - item.txt', - '2 - item.txt', - '9 - item.txt', - '41 - item.txt', - '99 - item.txt', - '100 - item.txt', - '200 - item.txt', - '409 - item.txt', - '410 - item.txt' - ]) - }) - - it('sorts embedded numbers naturally within alphabetical order', () => { - expect(['b1.md', 'a10.md', 'a2.md', 'a1.md'].sort(compareFileNames)).toEqual([ - 'a1.md', - 'a2.md', - 'a10.md', - 'b1.md' - ]) - }) - - it('breaks numeric-collation ties deterministically instead of readdir order', () => { - expect(['2.txt', '02.txt'].sort(compareFileNames)).toEqual(['02.txt', '2.txt']) - expect(['02.txt', '2.txt'].sort(compareFileNames)).toEqual(['02.txt', '2.txt']) - expect(compareFileNames('a.txt', 'a.txt')).toBe(0) - }) -}) - -describe('sortDirEntries', () => { - it('keeps directories first, each group in natural order', () => { - const entries = [ - { name: '10 - notes', isDirectory: false }, - { name: '2 - src', isDirectory: true }, - { name: '9 - docs.txt', isDirectory: false }, - { name: '10 - assets', isDirectory: true } - ] - expect(sortDirEntries(entries).map((e) => e.name)).toEqual([ - '2 - src', - '10 - assets', - '9 - docs.txt', - '10 - notes' - ]) - }) -}) diff --git a/src/shared/file-preview-kind.test.ts b/src/shared/file-preview-kind.test.ts deleted file mode 100644 index 2471573e0e8..00000000000 --- a/src/shared/file-preview-kind.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { filePreviewForPath } from './file-preview-kind' - -describe('filePreviewForPath', () => { - it.each([ - ['a/b.MP4', { kind: 'video', mimeType: 'video/mp4' }], - ['a\\b.opus', { kind: 'audio', mimeType: 'audio/ogg' }], - ['x.avif', { kind: 'image', mimeType: 'image/avif' }], - ['doc.PDF', { kind: 'pdf', mimeType: 'application/pdf' }], - ['dir/.mp4', null], - ['lib.so', null], - ['notes.txt', null] - ])('%s', (path, expected) => { - expect(filePreviewForPath(path)).toEqual(expected) - }) -}) diff --git a/src/shared/file-write-disk-precondition.test.ts b/src/shared/file-write-disk-precondition.test.ts index 317a4c8e90d..e7a9ef50b9a 100644 --- a/src/shared/file-write-disk-precondition.test.ts +++ b/src/shared/file-write-disk-precondition.test.ts @@ -1,9 +1,5 @@ import { describe, expect, it } from 'vitest' -import { - diskBaselineSignatureLength, - getDiskBaselineSignature, - utf8ByteSizeCanDecodeToLength -} from './disk-baseline-signature' +import { getDiskBaselineSignature } from './disk-baseline-signature' import { compareDiskWritePrecondition, diskConflictFromReadFailure, @@ -42,16 +38,6 @@ describe('disk write precondition', () => { expect(compareDiskWritePrecondition(precondition, current, decoded)).toBeNull() }) - it('reports an external rewrite as changed before checking for U+FFFD', () => { - expect( - compareDiskWritePrecondition( - { expectedSignature: getDiskBaselineSignature('original'), requireLosslessUtf8: true }, - 'rewritten �', - 'mine' - ) - ).toBe('changed') - }) - it('treats anything but an explicit refusal from a host as a completed write', () => { expect(parseFileWriteResult({ ok: false, diskConflict: 'not-utf8' })).toEqual({ ok: false, @@ -74,16 +60,6 @@ describe('disk write precondition', () => { expect(parseFileWriteResult({ ok: false })).toEqual({ ok: false, diskConflict: 'changed' }) }) - it('bounds the UTF-8 size a baseline can decode from', () => { - const content = 'a€𝄞' - const length = diskBaselineSignatureLength(getDiskBaselineSignature(content)) - expect(length).toBe(content.length) - expect(utf8ByteSizeCanDecodeToLength(Buffer.byteLength(content), length ?? -1)).toBe(true) - expect(utf8ByteSizeCanDecodeToLength(content.length - 1, content.length)).toBe(false) - expect(utf8ByteSizeCanDecodeToLength(content.length * 3 + 1, content.length)).toBe(false) - expect(diskBaselineSignatureLength('not-a-signature')).toBeNull() - }) - it.each([ [ 'an ENOENT code with plain wording', @@ -95,17 +71,6 @@ describe('disk write precondition', () => { expect(diskConflictFromReadFailure(precondition, error)).toBe('missing') }) - it.each([ - [ - 'the desktop IPC wrapping', - "Error invoking remote method 'fs:readFile': Error: ENOENT: no such file or directory, open '/repo/a.ts'" - ], - ['the runtime wording', "ENOENT: no such file or directory, stat '/repo/a.ts'"] - ])('reads a not-found from %s', (_label, message) => { - const precondition = { expectedSignature: getDiskBaselineSignature('base') } - expect(diskConflictFromReadFailure(precondition, new Error(message))).toBe('missing') - }) - it.each([ "EACCES: permission denied, open '/repo/ENOENT.md'", "EACCES: permission denied, open '/repo/ENOENT/notes.md'", diff --git a/src/shared/filesystem-watch-batch-window.test.ts b/src/shared/filesystem-watch-batch-window.test.ts deleted file mode 100644 index 05710c0a5b8..00000000000 --- a/src/shared/filesystem-watch-batch-window.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - WATCH_BATCH_INTERVAL_MS, - WATCH_BATCH_LEADING_MS, - watchBatchFlushDelayMs -} from './filesystem-watch-batch-window' - -describe('watchBatchFlushDelayMs', () => { - it('uses the leading delay before the first flush', () => { - expect(watchBatchFlushDelayMs(1_000_000, 0)).toBe(WATCH_BATCH_LEADING_MS) - }) - - it('uses the leading delay once a full interval has passed', () => { - expect(watchBatchFlushDelayMs(1_000 + WATCH_BATCH_INTERVAL_MS, 1_000)).toBe( - WATCH_BATCH_LEADING_MS - ) - }) - - it('waits out the rest of the interval after a recent flush', () => { - expect(watchBatchFlushDelayMs(1_010, 1_000)).toBe(WATCH_BATCH_INTERVAL_MS - 10) - }) - - it('never returns less than the leading delay', () => { - expect(watchBatchFlushDelayMs(1_090, 1_000)).toBe(WATCH_BATCH_LEADING_MS) - }) - - it('never waits longer than one interval when the clock moves backwards', () => { - expect(watchBatchFlushDelayMs(1_000, 3_600_000)).toBe(WATCH_BATCH_INTERVAL_MS) - }) - - it('honours custom windows', () => { - expect(watchBatchFlushDelayMs(1_000, 950, 5, 200)).toBe(150) - }) -}) diff --git a/src/shared/filesystem-watch-event-buffer.test.ts b/src/shared/filesystem-watch-event-buffer.test.ts index cba80ad750d..f0a58d95575 100644 --- a/src/shared/filesystem-watch-event-buffer.test.ts +++ b/src/shared/filesystem-watch-event-buffer.test.ts @@ -3,24 +3,6 @@ import type { FsChangeEvent } from './filesystem-entry-types' import { FileWatchEventBuffer } from './filesystem-watch-event-buffer' describe('FileWatchEventBuffer', () => { - it('preserves precise event order and releases a consumed batch', () => { - const buffer = new FileWatchEventBuffer() - const events: FsChangeEvent[] = [ - { kind: 'delete', absolutePath: '/repo/old/file.ts' }, - { kind: 'create', absolutePath: '/repo/new/file.ts' }, - { - kind: 'rename', - absolutePath: '/repo/new', - oldAbsolutePath: '/repo/old' - } - ] - buffer.push(events.slice(0, 1)) - buffer.push(events.slice(1)) - expect(buffer.take()).toEqual(events) - expect(buffer.size).toBe(0) - expect(buffer.take()).toEqual([]) - }) - it('retains an explicit overflow until consumed without retaining later paths', () => { const buffer = new FileWatchEventBuffer() const overflow: FsChangeEvent = { kind: 'overflow', absolutePath: '/repo' } diff --git a/src/shared/fish-binary-requirement.test.ts b/src/shared/fish-binary-requirement.test.ts deleted file mode 100644 index e4d57bbf9e4..00000000000 --- a/src/shared/fish-binary-requirement.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - fishRequirementViolation, - REQUIRE_FISH_ENV_VAR, - resolveFishBinary, - type FishBinaryLookup -} from './fish-binary-requirement' - -const FOUND: FishBinaryLookup = { available: true, path: '/usr/bin/fish', majorVersion: 4 } -const MISSING: FishBinaryLookup = { - available: false, - path: null, - majorVersion: 0, - reason: 'no fish binary on PATH' -} - -describe('fishRequirementViolation', () => { - it('lets a missing fish skip when the requirement is not set', () => { - expect(fishRequirementViolation(MISSING, {})).toBeNull() - expect(fishRequirementViolation(MISSING, { [REQUIRE_FISH_ENV_VAR]: '0' })).toBeNull() - }) - - // The #9993 guard is CI's only end-to-end coverage; a skip there is a silent gap. - it('reports a violation naming the reason when CI requires fish and it is absent', () => { - const violation = fishRequirementViolation(MISSING, { [REQUIRE_FISH_ENV_VAR]: '1' }) - expect(violation).toContain(REQUIRE_FISH_ENV_VAR) - expect(violation).toContain('no fish binary on PATH') - }) - - it('reports no violation when fish is present, requirement set or not', () => { - expect(fishRequirementViolation(FOUND, { [REQUIRE_FISH_ENV_VAR]: '1' })).toBeNull() - expect(fishRequirementViolation(FOUND, {})).toBeNull() - }) -}) - -describe('resolveFishBinary', () => { - it('treats an installed fish below the floor as unavailable, with the version in the reason', () => { - const lookup = resolveFishBinary(Number.MAX_SAFE_INTEGER) - if (lookup.available) { - throw new Error('no fish can satisfy an unreachable version floor') - } - // Only when a fish is actually installed does the reason describe a version. - if (lookup.majorVersion > 0) { - expect(lookup.reason).toContain(`fish ${Number.MAX_SAFE_INTEGER}+ required`) - } - }) - - it('never reports a path on Windows', () => { - if (process.platform !== 'win32') { - return - } - expect(resolveFishBinary().path).toBeNull() - }) -}) diff --git a/src/shared/foreground-process-selection.test.ts b/src/shared/foreground-process-selection.test.ts index d034f1ccff2..7f6d0861fb8 100644 --- a/src/shared/foreground-process-selection.test.ts +++ b/src/shared/foreground-process-selection.test.ts @@ -39,37 +39,6 @@ describe('selectForegroundProcessCandidate', () => { expect(selectForegroundProcessCandidate(candidates)).toBeNull() }) - - it.each([ - { helperStat: 'S+', selectedPid: 102 }, - { helperStat: 'S', selectedPid: 101 } - ])('prefers foreground processes, then depth ($helperStat)', ({ helperStat, selectedPid }) => { - const candidates = [ - { pid: 101, ppid: 100, depth: 1, stat: 'S+', command: 'node /opt/bin/codex' }, - { pid: 102, ppid: 101, depth: 2, stat: helperStat, command: '/opt/vendor/bin/codex' } - ] - - expect(selectForegroundProcessCandidate(candidates)).toMatchObject({ - candidate: { pid: selectedPid }, - recognized: { agent: 'codex' } - }) - }) -}) - -it('memoizes shared ancestry while validating a long agent/helper lineage', () => { - let reads = 0 - const candidates = Array.from({ length: 1000 }, (_, index) => ({ - pid: index + 1, - get ppid() { - reads += 1 - return index - }, - depth: index, - stat: 'S+', - command: index % 2 === 0 ? 'omp' : 'codex' - })) - expect(selectForegroundProcessCandidate(candidates)?.candidate.pid).toBe(1) - expect(reads).toBeLessThanOrEqual(1000) }) it('terminates on a ppid cycle that never reaches the outer agent', () => { diff --git a/src/shared/foreground-wrapper-agent.test.ts b/src/shared/foreground-wrapper-agent.test.ts index 3d63958c2f4..267ec75bf5c 100644 --- a/src/shared/foreground-wrapper-agent.test.ts +++ b/src/shared/foreground-wrapper-agent.test.ts @@ -5,46 +5,8 @@ import { } from './foreground-wrapper-agent' describe('resolveOuterWrapperForegroundProcess', () => { - const omp = { agent: 'omp' as const, processName: 'omp' } const pi = { agent: 'pi' as const, processName: 'pi' } - it('collapses a wrapped pi read onto the shallower omp wrapper', () => { - expect( - resolveOuterWrapperForegroundProcess(pi, { pid: 102, ppid: 101, command: 'pi' }, [ - { pid: 102, ppid: 101, command: 'pi' }, - { pid: 101, ppid: 100, command: 'omp' } - ]) - ).toBe('omp') - }) - - it('keeps bare pi when no same-group wrapper is present', () => { - const barePi = { pid: 101, ppid: 100, command: 'pi' } - expect(resolveOuterWrapperForegroundProcess(pi, barePi, [barePi])).toBe('pi') - }) - - it('keeps Codex identity beneath a shell parent', () => { - const codex = { agent: 'codex' as const, processName: 'codex' } - expect( - resolveOuterWrapperForegroundProcess( - codex, - { pid: 102, ppid: 101, command: 'node /usr/bin/codex' }, - [ - { pid: 102, ppid: 101, command: 'node /usr/bin/codex' }, - { pid: 101, ppid: 100, command: 'bash -l' } - ] - ) - ).toBe('codex') - }) - - it('does not promote a deeper pi over an already-outer omp winner', () => { - expect( - resolveOuterWrapperForegroundProcess(omp, { pid: 101, ppid: 100, command: 'omp' }, [ - { pid: 101, ppid: 100, command: 'omp' }, - { pid: 102, ppid: 101, command: 'pi' } - ]) - ).toBe('omp') - }) - it('does not treat an unrelated shallower omp sibling as the pi wrapper', () => { expect( resolveOuterWrapperForegroundProcess(pi, { pid: 103, ppid: 102, command: 'pi' }, [ @@ -64,12 +26,4 @@ describe('resolveOuterWrapperForegroundProcess', () => { ]) ).toEqual({ processName: 'omp', processId: 101 }) }) - - it('keeps the winner pid when nothing collapses', () => { - const barePi = { pid: 101, ppid: 100, command: 'pi' } - expect(resolveOuterWrapperForegroundIdentity(pi, barePi, [barePi])).toEqual({ - processName: 'pi', - processId: 101 - }) - }) }) diff --git a/src/shared/generated-code-path.test.ts b/src/shared/generated-code-path.test.ts deleted file mode 100644 index 6af7617cf40..00000000000 --- a/src/shared/generated-code-path.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { isGeneratedCodePath } from './generated-code-path' - -describe('isGeneratedCodePath', () => { - it('recognizes dependency lockfiles across ecosystems', () => { - for (const filePath of [ - 'package-lock.json', - 'pnpm-lock.yaml', - 'yarn.lock', - 'bun.lockb', - 'Cargo.lock', - 'poetry.lock', - 'uv.lock', - 'Gemfile.lock', - 'composer.lock', - 'go.sum', - 'flake.lock', - 'mobile/pubspec.lock', - 'services/api/Pipfile.lock' - ]) { - expect(isGeneratedCodePath(filePath), filePath).toBe(true) - } - }) - - it('recognizes tool-stamped filename suffixes', () => { - for (const filePath of [ - 'api/service.pb.go', - // grpc-gateway / protoc-gen-validate stack extra segments onto `.pb.` - 'api/service.pb.gw.go', - 'api/service.pb.validate.go', - 'api/service_pb2.py', - 'api/service_pb2_grpc.py', - 'src/schema.gen.ts', - 'internal/bindings_generated.go', - 'src/Api.generated.ts', - 'Forms/Main.Designer.cs', - 'lib/model.g.dart', - 'lib/model.freezed.dart', - 'public/app.min.js', - 'public/app.js.map', - 'src/components/__snapshots__/Chip.tsx.snap' - ]) { - expect(isGeneratedCodePath(filePath), filePath).toBe(true) - } - }) - - it('recognizes generated directories on either separator', () => { - expect(isGeneratedCodePath('dist/renderer/index.js')).toBe(true) - expect(isGeneratedCodePath('src/__generated__/schema.ts')).toBe(true) - expect(isGeneratedCodePath('vendor/github.com/pkg/errors/errors.go')).toBe(true) - expect(isGeneratedCodePath('src\\__pycache__\\mod.pyc')).toBe(true) - }) - - it('leaves hand-written paths alone, including the ambiguous directory names', () => { - for (const filePath of [ - 'src/shared/git-branch-line-total.ts', - // Excluded on purpose: all common as authored source directories. - 'build/scripts/release.ts', - 'target/tracker.rs', - 'src/out/renderer.ts', - 'cmd/bin/main.go', - // Substrings, not whole segments or suffixes. - 'src/distributed/queue.ts', - 'src/vendors/stripe.ts', - 'src/generator/emit.ts', - 'docs/generated-code-policy.md', - 'src/shared/generated-code-path.ts' - ]) { - expect(isGeneratedCodePath(filePath), filePath).toBe(false) - } - }) -}) diff --git a/src/shared/github/account-binding.test.ts b/src/shared/github/account-binding.test.ts index bacb1efc4b5..7d68b80fa83 100644 --- a/src/shared/github/account-binding.test.ts +++ b/src/shared/github/account-binding.test.ts @@ -1,37 +1,5 @@ import { describe, expect, it } from 'vitest' -import { - ghAccountBindingsEqual, - ghTokenEnvVarForHost, - normalizeGhAccountBinding -} from './account-binding' - -describe('normalizeGhAccountBinding', () => { - it('trims, lowercases host, and preserves user case', () => { - expect(normalizeGhAccountBinding({ host: ' GitHub.COM ', user: ' Alice ' })).toEqual({ - host: 'github.com', - user: 'Alice' - }) - }) - - it('rejects malformed shapes', () => { - expect(normalizeGhAccountBinding(null)).toBeNull() - expect(normalizeGhAccountBinding({ host: '', user: 'a' })).toBeNull() - expect(normalizeGhAccountBinding({ host: 'github.com', user: '' })).toBeNull() - expect(normalizeGhAccountBinding({ host: 'bad host', user: 'a' })).toBeNull() - expect(normalizeGhAccountBinding({ host: 'github.com', user: 'has space' })).toBeNull() - }) - - it('accepts GHES/LDAP logins with underscores and dots', () => { - expect(normalizeGhAccountBinding({ host: 'github.acme.com', user: 'svc_build' })).toEqual({ - host: 'github.acme.com', - user: 'svc_build' - }) - expect(normalizeGhAccountBinding({ host: 'github.acme.com', user: 'first.last' })).toEqual({ - host: 'github.acme.com', - user: 'first.last' - }) - }) -}) +import { ghTokenEnvVarForHost } from './account-binding' describe('ghTokenEnvVarForHost', () => { it('selects GH_TOKEN for github.com, github.localhost, and *.ghe.com', () => { @@ -45,20 +13,3 @@ describe('ghTokenEnvVarForHost', () => { expect(ghTokenEnvVarForHost('ghe.internal')).toBe('GH_ENTERPRISE_TOKEN') }) }) - -describe('ghAccountBindingsEqual', () => { - it('compares equal pairs and preserves user case sensitivity', () => { - expect( - ghAccountBindingsEqual( - { host: 'github.com', user: 'Alice' }, - { host: 'github.com', user: 'Alice' } - ) - ).toBe(true) - expect( - ghAccountBindingsEqual( - { host: 'github.com', user: 'Alice' }, - { host: 'github.com', user: 'alice' } - ) - ).toBe(false) - }) -})