mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 09:57:48 +00:00
test: remove 17 low-value suites from audit batch 848 (#36461)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -2330,7 +2330,6 @@
|
||||
"src/main/emulator/scrcpy-video-registry.test.ts": 57,
|
||||
"src/main/emulator/serve-sim-accessibility-tree.test.ts": 62,
|
||||
"src/main/emulator/serve-sim-ax-normalization.test.ts": 35,
|
||||
"src/main/emulator/serve-sim-detached-session.test.ts": 30,
|
||||
"src/main/emulator/serve-sim-execution.test.ts": 64,
|
||||
"src/main/emulator/serve-sim-helper-processes.test.ts": 59,
|
||||
"src/main/emulator/serve-sim-kill-contract.test.ts": 1307,
|
||||
@@ -2404,7 +2403,6 @@
|
||||
"src/main/git/command-runner/wsl-host-failure.test.ts": 120,
|
||||
"src/main/git/command-runner/wsl-service-failure-direct-git.test.ts": 186,
|
||||
"src/main/git/commit-object-ref.test.ts": 25,
|
||||
"src/main/git/commit.test.ts": 178,
|
||||
"src/main/git/create-event-repo-probe.test.ts": 59,
|
||||
"src/main/git/exact-ref-probe.test.ts": 60,
|
||||
"src/main/git/fetch-error-classification.test.ts": 51,
|
||||
@@ -2576,7 +2574,6 @@
|
||||
"src/main/gitea/client-scan-invalidation.test.ts": 485,
|
||||
"src/main/gitea/client.test.ts": 385,
|
||||
"src/main/gitea/pull-request-creation.test.ts": 293,
|
||||
"src/main/gitea/pull-request-mappers.test.ts": 29,
|
||||
"src/main/gitea/repository-ref.test.ts": 436,
|
||||
"src/main/github/account-binding-required-host.test.ts": 220,
|
||||
"src/main/github/auth-diagnose.test.ts": 58,
|
||||
@@ -5020,7 +5017,6 @@
|
||||
"src/main/runtime/orchestration/chat-assignee.test.ts": 2933,
|
||||
"src/main/runtime/orchestration/chat-worker-legacy-failure-read.test.ts": 1651,
|
||||
"src/main/runtime/orchestration/chat-worker-request-failures.test.ts": 1678,
|
||||
"src/main/runtime/orchestration/cli-command.test.ts": 57,
|
||||
"src/main/runtime/orchestration/codex-reconnect-failed-dispatch.test.ts": 6144,
|
||||
"src/main/runtime/orchestration/configured-worker-agent-selector.test.ts": 32,
|
||||
"src/main/runtime/orchestration/coordinator-decision-gates.test.ts": 361,
|
||||
@@ -5572,7 +5568,6 @@
|
||||
"src/main/runtime/rpc/web-session-pairing.test.ts": 590,
|
||||
"src/main/runtime/rpc/workspace-creator-context.test.ts": 32,
|
||||
"src/main/runtime/rpc/worktree-catalog-snapshot.test.ts": 52,
|
||||
"src/main/runtime/rpc/worktree-ps-other-client-marks.test.ts": 92,
|
||||
"src/main/runtime/rpc/worktree-removal-marker-projection.test.ts": 348,
|
||||
"src/main/runtime/rpc/ws-empty-payload-frame-write.test.ts": 95,
|
||||
"src/main/runtime/rpc/ws-transport-accept-order.test.ts": 67,
|
||||
@@ -7364,7 +7359,6 @@
|
||||
"src/renderer/src/components/editor/editor-model-uri-parity.test.ts": 154,
|
||||
"src/renderer/src/components/editor/editor-model-uri.test.ts": 43,
|
||||
"src/renderer/src/components/editor/editor-panel-draft-selector.test.ts": 322,
|
||||
"src/renderer/src/components/editor/editor-panel-git-entry-selector.test.ts": 44,
|
||||
"src/renderer/src/components/editor/editor-panel-render-model.test.ts": 2054,
|
||||
"src/renderer/src/components/editor/editor-path-display.test.ts": 34,
|
||||
"src/renderer/src/components/editor/editor-path-move-inflight.test.ts": 49,
|
||||
@@ -7949,7 +7943,6 @@
|
||||
"src/renderer/src/components/native-chat/native-chat-working-suppression.test.ts": 42,
|
||||
"src/renderer/src/components/native-chat/omp-model-switch-capability.test.ts": 266,
|
||||
"src/renderer/src/components/native-chat/serialized-screen-background-rows.test.ts": 112,
|
||||
"src/renderer/src/components/native-chat/structured-agent-session-delivery-notices.kept-card.test.ts": 427,
|
||||
"src/renderer/src/components/native-chat/structured-agent-session-delivery-notices.test.ts": 234,
|
||||
"src/renderer/src/components/native-chat/structured-agent-session-legacy-outbox.test.ts": 383,
|
||||
"src/renderer/src/components/native-chat/structured-agent-session-message-hand-back.test.ts": 377,
|
||||
@@ -8212,7 +8205,6 @@
|
||||
"src/renderer/src/components/right-sidebar/ai-vault-search-hit-hosts.test.ts": 45,
|
||||
"src/renderer/src/components/right-sidebar/ai-vault-search-scope-identity.test.tsx": 479,
|
||||
"src/renderer/src/components/right-sidebar/ai-vault-search-session.test.ts": 88,
|
||||
"src/renderer/src/components/right-sidebar/ai-vault-session-chat-action.test.ts": 1625,
|
||||
"src/renderer/src/components/right-sidebar/ai-vault-session-cli-fork.test.ts": 1630,
|
||||
"src/renderer/src/components/right-sidebar/ai-vault-session-conditional-refresh-hook.test.tsx": 1736,
|
||||
"src/renderer/src/components/right-sidebar/ai-vault-session-conditional-refresh.test.ts": 133,
|
||||
@@ -8324,7 +8316,6 @@
|
||||
"src/renderer/src/components/right-sidebar/pr-comments-ai-launch-ack.test.ts": 75,
|
||||
"src/renderer/src/components/right-sidebar/pr-comments-list-selection.test.tsx": 1815,
|
||||
"src/renderer/src/components/right-sidebar/push-target-upstream-refresh-cache.test.ts": 184,
|
||||
"src/renderer/src/components/right-sidebar/review-cache-entry-selection.test.ts": 24,
|
||||
"src/renderer/src/components/right-sidebar/right-panel-comment-composer.ime-enter.test.tsx": 273,
|
||||
"src/renderer/src/components/right-sidebar/right-panel-comment-focus-timers.test.ts": 82,
|
||||
"src/renderer/src/components/right-sidebar/right-sidebar-effective-tab.test.ts": 34,
|
||||
@@ -8332,7 +8323,6 @@
|
||||
"src/renderer/src/components/right-sidebar/right-sidebar-width.test.ts": 61,
|
||||
"src/renderer/src/components/right-sidebar/runSourceControlAgentActionStart.test.ts": 276,
|
||||
"src/renderer/src/components/right-sidebar/search-match-open.test.ts": 100,
|
||||
"src/renderer/src/components/right-sidebar/search-rows.test.ts": 35,
|
||||
"src/renderer/src/components/right-sidebar/source-control-action-recipe-match.test.ts": 190,
|
||||
"src/renderer/src/components/right-sidebar/source-control-active-open-file-keys.test.ts": 28,
|
||||
"src/renderer/src/components/right-sidebar/source-control-branch-context-row.test.tsx": 248,
|
||||
@@ -9824,7 +9814,6 @@
|
||||
"src/renderer/src/components/terminal-pane/use-terminal-scroll-visibility-memory.test.ts": 56,
|
||||
"src/renderer/src/components/terminal-pane/use-terminal-tab-cold-parking.test.ts": 461,
|
||||
"src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts": 333,
|
||||
"src/renderer/src/components/terminal-pane/use-wsl-host-unavailable-reason.test.ts": 1271,
|
||||
"src/renderer/src/components/terminal-pane/useTerminalFontZoom.test.ts": 197,
|
||||
"src/renderer/src/components/terminal-pane/wrapped-terminal-http-link-limits.test.ts": 166,
|
||||
"src/renderer/src/components/terminal-pane/wrapped-terminal-http-link-provider.test.ts": 396,
|
||||
@@ -11352,7 +11341,6 @@
|
||||
"src/renderer/src/store/slices/linear.test.ts": 388,
|
||||
"src/renderer/src/store/slices/local-detected-agent-state.test.ts": 191,
|
||||
"src/renderer/src/store/slices/native-chat-launch-draft-teardown.test.ts": 1318,
|
||||
"src/renderer/src/store/slices/new-issue-draft.test.ts": 34,
|
||||
"src/renderer/src/store/slices/new-markdown.test.ts": 1089,
|
||||
"src/renderer/src/store/slices/orca-profiles-auth-actions-connect-overlap.test.ts": 1268,
|
||||
"src/renderer/src/store/slices/orca-profiles-auth-actions-sign-in-wait.test.ts": 1130,
|
||||
@@ -11478,7 +11466,6 @@
|
||||
"src/renderer/src/store/slices/tabs/tabs-reconciliation-batch-identity.test.ts": 1309,
|
||||
"src/renderer/src/store/slices/tabs/tabs-reconciliation-empty-groups.test.ts": 1204,
|
||||
"src/renderer/src/store/slices/tabs/tabs-root-group.test.ts": 1263,
|
||||
"src/renderer/src/store/slices/task-creation-drafts.test.ts": 50,
|
||||
"src/renderer/src/store/slices/terminal-helpers.test.ts": 68,
|
||||
"src/renderer/src/store/slices/terminal-input-activity-store-write.test.ts": 1443,
|
||||
"src/renderer/src/store/slices/terminal-layout-pty-ownership.test.ts": 1313,
|
||||
@@ -11664,7 +11651,6 @@
|
||||
"src/renderer/src/web/web-preload-api-runtime-calls.test.ts": 838,
|
||||
"src/renderer/src/web/web-preload-api-runtime-environment.test.ts": 1240,
|
||||
"src/renderer/src/web/web-preload-api-settings.test.ts": 2080,
|
||||
"src/renderer/src/web/web-preload-api-ssh.test.ts": 682,
|
||||
"src/renderer/src/web/web-preload-api-ui-pairing-local-fields.test.ts": 488,
|
||||
"src/renderer/src/web/web-preload-api-ui.test.ts": 1703,
|
||||
"src/renderer/src/web/web-preload-api-workspace-catalog.test.ts": 1393,
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { toBrowserConsoleResult, toBrowserNetworkLogResult } from './agent-browser-log-entries'
|
||||
|
||||
// Trimmed from real agent-browser 0.27.3 and 0.41.0 `--json` data; both print the same fields.
|
||||
const consoleOutput = {
|
||||
messages: [
|
||||
{ args: [{ type: 'string', value: 'hello log' }], text: 'hello log', type: 'log' },
|
||||
{ args: [{ type: 'string', value: 'a warning' }], text: 'a warning', type: 'warning' },
|
||||
{ args: [{ type: 'string', value: 'an error' }], text: 'an error', type: 'error' }
|
||||
]
|
||||
}
|
||||
|
||||
const networkOutput = {
|
||||
requests: [
|
||||
{
|
||||
headers: { 'Accept-Language': 'en-US,en;q=0.9' },
|
||||
method: 'GET',
|
||||
mimeType: 'text/html',
|
||||
requestId: 'BECA21D6BA0B635E9916700B48479394',
|
||||
resourceType: 'Document',
|
||||
responseHeaders: { 'Content-Length': '254' },
|
||||
status: 200,
|
||||
timestamp: 1793283228914,
|
||||
url: 'http://127.0.0.1:47811/page.html'
|
||||
},
|
||||
{
|
||||
headers: { 'Accept-Language': 'en-US,en;q=0.9' },
|
||||
method: 'GET',
|
||||
requestId: '45376.2',
|
||||
resourceType: 'Fetch',
|
||||
timestamp: 1793283228914,
|
||||
url: 'https://example.com/?a=1'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
describe('toBrowserConsoleResult', () => {
|
||||
it('maps messages to entries and keeps the newest up to the limit', () => {
|
||||
expect(toBrowserConsoleResult(consoleOutput, undefined)).toEqual({
|
||||
entries: [
|
||||
{ level: 'log', text: 'hello log' },
|
||||
{ level: 'warning', text: 'a warning' },
|
||||
{ level: 'error', text: 'an error' }
|
||||
],
|
||||
truncated: false
|
||||
})
|
||||
expect(toBrowserConsoleResult(consoleOutput, 2)).toEqual({
|
||||
entries: [
|
||||
{ level: 'warning', text: 'a warning' },
|
||||
{ level: 'error', text: 'an error' }
|
||||
],
|
||||
truncated: true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('toBrowserNetworkLogResult', () => {
|
||||
it('maps requests to entries, including ones without a response, up to the limit', () => {
|
||||
expect(toBrowserNetworkLogResult(networkOutput, undefined)).toEqual({
|
||||
entries: [
|
||||
{
|
||||
url: 'http://127.0.0.1:47811/page.html',
|
||||
method: 'GET',
|
||||
status: 200,
|
||||
mimeType: 'text/html',
|
||||
timestamp: 1793283228914
|
||||
},
|
||||
{ url: 'https://example.com/?a=1', method: 'GET', timestamp: 1793283228914 }
|
||||
],
|
||||
truncated: false
|
||||
})
|
||||
expect(toBrowserNetworkLogResult(networkOutput, 1).entries).toEqual([
|
||||
{ url: 'https://example.com/?a=1', method: 'GET', timestamp: 1793283228914 }
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,76 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
deriveAxUrlFromStreamUrl,
|
||||
parseServeSimDetachedSession
|
||||
} from './serve-sim-detached-session'
|
||||
|
||||
describe('parseServeSimDetachedSession', () => {
|
||||
it('uses serve-sim streamUrl when present', () => {
|
||||
const info = parseServeSimDetachedSession(
|
||||
{
|
||||
device: 'device-1',
|
||||
streamUrl: 'http://127.0.0.1:3100/stream.mjpeg',
|
||||
wsUrl: 'ws://127.0.0.1:3100/ws'
|
||||
},
|
||||
'device-1'
|
||||
)
|
||||
|
||||
expect(info).toMatchObject({
|
||||
deviceUdid: 'device-1',
|
||||
streamUrl: 'http://127.0.0.1:3100/stream.mjpeg',
|
||||
wsUrl: 'ws://127.0.0.1:3100/ws',
|
||||
axUrl: 'http://127.0.0.1:3100/ax'
|
||||
})
|
||||
})
|
||||
|
||||
it('derives the device-scoped AX endpoint and preserves an explicit one', () => {
|
||||
const derived = parseServeSimDetachedSession(
|
||||
{
|
||||
streamUrl: 'http://127.0.0.1:3200/helper/device-1/stream.mjpeg',
|
||||
wsUrl: 'ws://127.0.0.1:3200/helper/device-1/ws'
|
||||
},
|
||||
'device-1'
|
||||
)
|
||||
const explicit = parseServeSimDetachedSession(
|
||||
{
|
||||
streamUrl: 'http://127.0.0.1:3200/stream.mjpeg',
|
||||
wsUrl: 'ws://127.0.0.1:3200/ws',
|
||||
axUrl: 'http://127.0.0.1:3200/custom-ax'
|
||||
},
|
||||
'device-1'
|
||||
)
|
||||
|
||||
expect(derived.axUrl).toBe('http://127.0.0.1:3200/helper/device-1/ax')
|
||||
expect(explicit.axUrl).toBe('http://127.0.0.1:3200/custom-ax')
|
||||
})
|
||||
|
||||
it('derives the MJPEG stream endpoint from older serve-sim url output', () => {
|
||||
const info = parseServeSimDetachedSession(
|
||||
{
|
||||
device: 'device-2',
|
||||
url: 'http://127.0.0.1:3100',
|
||||
wsUrl: 'ws://127.0.0.1:3100/ws'
|
||||
},
|
||||
'device-2'
|
||||
)
|
||||
|
||||
expect(info.streamUrl).toBe('http://127.0.0.1:3100/stream.mjpeg')
|
||||
})
|
||||
})
|
||||
|
||||
describe('deriveAxUrlFromStreamUrl', () => {
|
||||
it('swaps the mjpeg stream suffix for /ax', () => {
|
||||
expect(deriveAxUrlFromStreamUrl('http://127.0.0.1:3100/stream.mjpeg')).toBe(
|
||||
'http://127.0.0.1:3100/ax'
|
||||
)
|
||||
expect(deriveAxUrlFromStreamUrl('http://127.0.0.1:3200/helper/device-1/stream.mjpeg')).toBe(
|
||||
'http://127.0.0.1:3200/helper/device-1/ax'
|
||||
)
|
||||
})
|
||||
|
||||
it('never fabricates an /ax endpoint from a non-mjpeg or missing url', () => {
|
||||
expect(deriveAxUrlFromStreamUrl('http://127.0.0.1:3100/stream.h264')).toBeUndefined()
|
||||
expect(deriveAxUrlFromStreamUrl('http://127.0.0.1:3100/')).toBeUndefined()
|
||||
expect(deriveAxUrlFromStreamUrl(undefined)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -1,81 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { gitExecFileAsyncMock } = vi.hoisted(() => ({
|
||||
gitExecFileAsyncMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('./runner', () => ({
|
||||
gitExecFileAsync: gitExecFileAsyncMock,
|
||||
gitExecFileAsyncBuffer: vi.fn()
|
||||
}))
|
||||
|
||||
import { commitChanges } from './status'
|
||||
|
||||
describe('commitChanges', () => {
|
||||
beforeEach(() => {
|
||||
gitExecFileAsyncMock.mockReset()
|
||||
})
|
||||
|
||||
it('returns success when git commit completes', async () => {
|
||||
gitExecFileAsyncMock.mockResolvedValue({ stdout: '[main abc123] message\n', stderr: '' })
|
||||
|
||||
const result = await commitChanges('/repo', 'feat: add commit action')
|
||||
|
||||
expect(gitExecFileAsyncMock).toHaveBeenCalledWith(['commit', '-m', 'feat: add commit action'], {
|
||||
cwd: '/repo'
|
||||
})
|
||||
expect(result).toEqual({ success: true })
|
||||
})
|
||||
|
||||
it('returns stderr when commit fails (e.g. pre-commit hook)', async () => {
|
||||
gitExecFileAsyncMock.mockRejectedValue({
|
||||
stderr: 'pre-commit hook failed: lint errors\n'
|
||||
})
|
||||
|
||||
const result = await commitChanges('/repo', 'feat: commit with lint errors')
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'pre-commit hook failed: lint errors\n'
|
||||
})
|
||||
})
|
||||
|
||||
it('returns stdout when git writes to stdout (e.g. nothing to commit)', async () => {
|
||||
gitExecFileAsyncMock.mockRejectedValue({
|
||||
stdout: 'nothing to commit, working tree clean\n',
|
||||
stderr: ''
|
||||
})
|
||||
|
||||
const result = await commitChanges('/repo', 'feat: empty commit')
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'nothing to commit, working tree clean\n'
|
||||
})
|
||||
})
|
||||
|
||||
it('prefers stderr over stdout when both are present', async () => {
|
||||
gitExecFileAsyncMock.mockRejectedValue({
|
||||
stdout: 'some stdout output\n',
|
||||
stderr: 'hook rejected\n'
|
||||
})
|
||||
|
||||
const result = await commitChanges('/repo', 'feat: both channels')
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'hook rejected\n'
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to Error.message when stdout/stderr are empty', async () => {
|
||||
gitExecFileAsyncMock.mockRejectedValue(new Error('spawn git ENOENT'))
|
||||
|
||||
const result = await commitChanges('/repo', 'feat: missing git')
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'spawn git ENOENT'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,77 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
deriveGiteaCommitStatus,
|
||||
mapGiteaMergeable,
|
||||
mapGiteaPullRequest,
|
||||
mapGiteaPullRequestState
|
||||
} from './pull-request-mappers'
|
||||
|
||||
describe('Gitea pull request mappers', () => {
|
||||
it('maps pull request states', () => {
|
||||
expect(mapGiteaPullRequestState({ state: 'open' })).toBe('open')
|
||||
expect(mapGiteaPullRequestState({ state: 'closed' })).toBe('closed')
|
||||
expect(mapGiteaPullRequestState({ state: 'open', draft: true })).toBe('draft')
|
||||
expect(mapGiteaPullRequestState({ state: 'closed', draft: true })).toBe('closed')
|
||||
expect(mapGiteaPullRequestState({ state: 'closed', merged: true })).toBe('merged')
|
||||
})
|
||||
|
||||
it('maps mergeability', () => {
|
||||
expect(mapGiteaMergeable(true)).toBe('MERGEABLE')
|
||||
expect(mapGiteaMergeable(false)).toBe('CONFLICTING')
|
||||
expect(mapGiteaMergeable(undefined)).toBe('UNKNOWN')
|
||||
})
|
||||
|
||||
it('derives commit status from the combined state', () => {
|
||||
expect(deriveGiteaCommitStatus({ state: 'success' })).toBe('success')
|
||||
expect(deriveGiteaCommitStatus({ state: 'failure' })).toBe('failure')
|
||||
expect(deriveGiteaCommitStatus({ state: 'error' })).toBe('failure')
|
||||
expect(deriveGiteaCommitStatus({ state: 'pending' })).toBe('pending')
|
||||
expect(deriveGiteaCommitStatus({ state: 'skipped' })).toBe('neutral')
|
||||
})
|
||||
|
||||
it('rolls up individual statuses when the combined state is neutral', () => {
|
||||
expect(
|
||||
deriveGiteaCommitStatus({
|
||||
state: '',
|
||||
statuses: [{ status: 'success' }, { status: 'failure' }]
|
||||
})
|
||||
).toBe('failure')
|
||||
expect(
|
||||
deriveGiteaCommitStatus({
|
||||
statuses: [{ status: 'success' }, { status: 'pending' }]
|
||||
})
|
||||
).toBe('pending')
|
||||
expect(deriveGiteaCommitStatus({ statuses: [{ status: 'success' }] })).toBe('success')
|
||||
expect(deriveGiteaCommitStatus({ statuses: [] })).toBe('neutral')
|
||||
})
|
||||
|
||||
it('maps a raw pull request to the hosted review shape', () => {
|
||||
expect(
|
||||
mapGiteaPullRequest(
|
||||
{
|
||||
number: 12,
|
||||
title: 'Gitea branch',
|
||||
state: 'open',
|
||||
html_url: 'https://git.example.com/team/project/pulls/12',
|
||||
updated_at: '2026-05-15T00:00:00Z',
|
||||
mergeable: true,
|
||||
head: { sha: 'abc123' }
|
||||
},
|
||||
'success'
|
||||
)
|
||||
).toEqual({
|
||||
number: 12,
|
||||
title: 'Gitea branch',
|
||||
state: 'open',
|
||||
url: 'https://git.example.com/team/project/pulls/12',
|
||||
status: 'success',
|
||||
updatedAt: '2026-05-15T00:00:00Z',
|
||||
mergeable: 'MERGEABLE',
|
||||
headSha: 'abc123'
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects incomplete pull request payloads', () => {
|
||||
expect(mapGiteaPullRequest({ number: 1, title: 'missing url' }, 'neutral')).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,78 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { resolveTerminalOrchestrationCliCommand } from './cli-command'
|
||||
|
||||
describe('resolveTerminalOrchestrationCliCommand', () => {
|
||||
it('uses orca-ide for a pane recorded as WSL', () => {
|
||||
expect(
|
||||
resolveTerminalOrchestrationCliCommand({
|
||||
connectionId: null,
|
||||
isWsl: true,
|
||||
worktreeId: 'repo::C:\\repo'
|
||||
})
|
||||
).toBe('orca-ide')
|
||||
})
|
||||
|
||||
it('uses project runtime and WSL paths when restored pane metadata is unavailable', () => {
|
||||
expect(
|
||||
resolveTerminalOrchestrationCliCommand({
|
||||
connectionId: null,
|
||||
isWsl: null,
|
||||
worktreeId: 'repo::C:\\repo',
|
||||
projectRuntime: {
|
||||
status: 'resolved',
|
||||
runtime: {
|
||||
kind: 'wsl',
|
||||
hostPlatform: 'wsl',
|
||||
projectId: 'project',
|
||||
distro: 'Ubuntu',
|
||||
reason: 'project-override',
|
||||
cacheKey: 'project:wsl:Ubuntu'
|
||||
}
|
||||
}
|
||||
})
|
||||
).toBe('orca-ide')
|
||||
expect(
|
||||
resolveTerminalOrchestrationCliCommand({
|
||||
connectionId: null,
|
||||
isWsl: null,
|
||||
worktreeId: 'repo::\\\\wsl.localhost\\Ubuntu\\home\\alice\\repo'
|
||||
})
|
||||
).toBe('orca-ide')
|
||||
})
|
||||
|
||||
it('preserves native and SSH bare-orca commands', () => {
|
||||
expect(
|
||||
resolveTerminalOrchestrationCliCommand({
|
||||
connectionId: null,
|
||||
isWsl: false,
|
||||
worktreeId: 'repo::/home/alice/repo'
|
||||
})
|
||||
).toBe('orca')
|
||||
expect(
|
||||
resolveTerminalOrchestrationCliCommand({
|
||||
connectionId: 'ssh-1',
|
||||
isWsl: null,
|
||||
worktreeId: 'repo::\\\\wsl.localhost\\Ubuntu\\home\\alice\\repo'
|
||||
})
|
||||
).toBe('orca')
|
||||
})
|
||||
|
||||
it('uses the runtime-provided command locally but never leaks it to SSH', () => {
|
||||
expect(
|
||||
resolveTerminalOrchestrationCliCommand({
|
||||
connectionId: null,
|
||||
isWsl: true,
|
||||
worktreeId: 'repo::C:\\repo',
|
||||
runtimeCliCommand: 'orca-dev'
|
||||
})
|
||||
).toBe('orca-dev')
|
||||
expect(
|
||||
resolveTerminalOrchestrationCliCommand({
|
||||
connectionId: 'ssh-1',
|
||||
isWsl: true,
|
||||
worktreeId: 'repo::C:\\repo',
|
||||
runtimeCliCommand: 'orca-dev'
|
||||
})
|
||||
).toBe('orca')
|
||||
})
|
||||
})
|
||||
@@ -1,80 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type {
|
||||
RuntimeWorktreePsResult,
|
||||
RuntimeWorktreePsSummary
|
||||
} from '../../../shared/runtime-worktree-contracts'
|
||||
import type { RpcContext } from './core'
|
||||
import { markWorktreePsOtherClientRows } from './worktree-ps-other-client-marks'
|
||||
|
||||
function row(
|
||||
worktreeId: string,
|
||||
creatorProvenance?: RuntimeWorktreePsSummary['creatorProvenance']
|
||||
): RuntimeWorktreePsSummary {
|
||||
return {
|
||||
worktreeId,
|
||||
repoId: 'repo',
|
||||
repo: 'repo',
|
||||
path: `/repo/${worktreeId}`,
|
||||
branch: worktreeId,
|
||||
isArchived: false,
|
||||
isMainWorktree: false,
|
||||
hasHostSidebarActivity: false,
|
||||
parentWorktreeId: null,
|
||||
childWorktreeIds: [],
|
||||
displayName: worktreeId,
|
||||
workspaceStatus: 'in-progress',
|
||||
sortOrder: 0,
|
||||
linkedIssue: null,
|
||||
linkedPR: null,
|
||||
linkedLinearIssue: null,
|
||||
linkedGitLabMR: null,
|
||||
linkedGitLabIssue: null,
|
||||
comment: '',
|
||||
isPinned: false,
|
||||
isActive: false,
|
||||
unread: false,
|
||||
liveTerminalCount: 0,
|
||||
hasAttachedPty: false,
|
||||
lastOutputAt: null,
|
||||
preview: '',
|
||||
status: 'inactive',
|
||||
agents: [],
|
||||
...(creatorProvenance ? { creatorProvenance } : {})
|
||||
}
|
||||
}
|
||||
|
||||
const rows = [
|
||||
row('desktop', { kind: 'host' }),
|
||||
row('laptop', { kind: 'paired-device', deviceId: 'laptop' }),
|
||||
row('unknown')
|
||||
]
|
||||
const result: RuntimeWorktreePsResult = { worktrees: rows, totalCount: 4, truncated: false }
|
||||
|
||||
function context(
|
||||
overrides: Partial<Pick<RpcContext, 'clientKind' | 'pairedDeviceParentId'>>
|
||||
): Parameters<typeof markWorktreePsOtherClientRows>[1] {
|
||||
return overrides
|
||||
}
|
||||
|
||||
function marked(next: RuntimeWorktreePsResult): string[] {
|
||||
return next.worktrees.filter((entry) => entry.desktopOtherClient).map((entry) => entry.worktreeId)
|
||||
}
|
||||
|
||||
describe('markWorktreePsOtherClientRows', () => {
|
||||
it("marks, for this host's own phone, the rows its sidebar calls another client's", () => {
|
||||
expect(
|
||||
marked(markWorktreePsOtherClientRows(result, context({ clientKind: 'mobile' })))
|
||||
).toEqual(['laptop'])
|
||||
})
|
||||
|
||||
it('leaves rows untouched for any other caller', () => {
|
||||
expect(markWorktreePsOtherClientRows(result, context({ clientKind: 'runtime' }))).toBe(result)
|
||||
expect(markWorktreePsOtherClientRows(result, context({}))).toBe(result)
|
||||
expect(
|
||||
markWorktreePsOtherClientRows(
|
||||
result,
|
||||
context({ clientKind: 'mobile', pairedDeviceParentId: 'other-desktop' })
|
||||
)
|
||||
).toBe(result)
|
||||
})
|
||||
})
|
||||
@@ -1,75 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { GitBranchChangeEntry } from '../../../../shared/git-diff-compare-types'
|
||||
import type { GitStatusEntry } from '../../../../shared/git-status-types'
|
||||
import {
|
||||
selectEditorPanelGitBranchEntries,
|
||||
selectEditorPanelGitStatusEntries
|
||||
} from './editor-panel-git-entry-selector'
|
||||
|
||||
describe('editor panel Git entry selectors', () => {
|
||||
it('ignore background worktree writes across mounted editor panels', () => {
|
||||
const panelCount = 200
|
||||
const worktreeId = 'worktree-active'
|
||||
const statusEntries = [{ path: 'src/index.ts' }] as GitStatusEntry[]
|
||||
const branchEntries = [{ path: 'src/index.ts' }] as GitBranchChangeEntry[]
|
||||
let gitStatusByWorktree = { [worktreeId]: statusEntries }
|
||||
let gitBranchChangesByWorktree = { [worktreeId]: branchEntries }
|
||||
let wholeMapInvalidations = 0
|
||||
let scopedEntryInvalidations = 0
|
||||
|
||||
for (let write = 0; write < 200; write += 1) {
|
||||
const previousStatusMap = gitStatusByWorktree
|
||||
gitStatusByWorktree = {
|
||||
...gitStatusByWorktree,
|
||||
[`background-${write}`]: [{ path: `generated-${write}.ts` } as GitStatusEntry]
|
||||
}
|
||||
const previousBranchMap = gitBranchChangesByWorktree
|
||||
gitBranchChangesByWorktree = {
|
||||
...gitBranchChangesByWorktree,
|
||||
[`background-${write}`]: [{ path: `generated-${write}.ts` } as GitBranchChangeEntry]
|
||||
}
|
||||
|
||||
for (let panel = 0; panel < panelCount; panel += 1) {
|
||||
wholeMapInvalidations += Number(previousStatusMap !== gitStatusByWorktree)
|
||||
wholeMapInvalidations += Number(previousBranchMap !== gitBranchChangesByWorktree)
|
||||
scopedEntryInvalidations += Number(
|
||||
selectEditorPanelGitStatusEntries({ gitStatusByWorktree }, worktreeId) !== statusEntries
|
||||
)
|
||||
scopedEntryInvalidations += Number(
|
||||
selectEditorPanelGitBranchEntries({ gitBranchChangesByWorktree }, worktreeId) !==
|
||||
branchEntries
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
expect(wholeMapInvalidations).toBe(80_000)
|
||||
expect(scopedEntryInvalidations).toBe(0)
|
||||
})
|
||||
|
||||
it('publishes owning entry replacements and handles an absent worktree', () => {
|
||||
const firstStatus = [{ path: 'src/old.ts' }] as GitStatusEntry[]
|
||||
const nextStatus = [{ path: 'src/new.ts' }] as GitStatusEntry[]
|
||||
const firstBranch = [{ path: 'src/old.ts' }] as GitBranchChangeEntry[]
|
||||
const nextBranch = [{ path: 'src/new.ts' }] as GitBranchChangeEntry[]
|
||||
|
||||
expect(
|
||||
selectEditorPanelGitStatusEntries({ gitStatusByWorktree: { active: firstStatus } }, 'active')
|
||||
).toBe(firstStatus)
|
||||
expect(
|
||||
selectEditorPanelGitStatusEntries({ gitStatusByWorktree: { active: nextStatus } }, 'active')
|
||||
).toBe(nextStatus)
|
||||
expect(
|
||||
selectEditorPanelGitBranchEntries(
|
||||
{ gitBranchChangesByWorktree: { active: firstBranch } },
|
||||
'active'
|
||||
)
|
||||
).toBe(firstBranch)
|
||||
expect(
|
||||
selectEditorPanelGitBranchEntries(
|
||||
{ gitBranchChangesByWorktree: { active: nextBranch } },
|
||||
'active'
|
||||
)
|
||||
).toBe(nextBranch)
|
||||
expect(selectEditorPanelGitStatusEntries({ gitStatusByWorktree: {} }, null)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
// After host admission as a queued card, its own row must not show sending/failure regardless of local delivery phase.
|
||||
|
||||
import { expect, it } from 'vitest'
|
||||
import type { AgentJournalSubmission } from '../../../../shared/agent-session-journal-types'
|
||||
import { agentJournalSubmissionKey } from '../../../../shared/agent-session-journal-item-key'
|
||||
import { structuredAgentSessionSendBody } from '../../../../shared/structured-agent-session-send-mutation'
|
||||
import { structuredAgentSessionDeliveryNotices } from './structured-agent-session-delivery-notices'
|
||||
import type { StructuredAgentSessionPendingSend } from './structured-agent-session-pending-sends'
|
||||
|
||||
const KEPT_ID = 'client-kept'
|
||||
|
||||
function pendingSend(
|
||||
patch: Partial<StructuredAgentSessionPendingSend> = {}
|
||||
): StructuredAgentSessionPendingSend {
|
||||
return {
|
||||
clientMessageId: KEPT_ID,
|
||||
sessionId: 'session-1',
|
||||
body: structuredAgentSessionSendBody('the kept words', []),
|
||||
previewUris: [],
|
||||
queuedAt: 1,
|
||||
phase: 'sending',
|
||||
issued: false,
|
||||
...patch
|
||||
}
|
||||
}
|
||||
|
||||
const restartRejected: AgentJournalSubmission = {
|
||||
clientMessageId: KEPT_ID,
|
||||
fence: 2,
|
||||
payloadFingerprint: 'fingerprint-kept',
|
||||
dispatchState: 'rejected',
|
||||
providerItemId: null,
|
||||
reason: 'Orca restarted before this message was sent.',
|
||||
rejection: { kind: 'hostRestarted' },
|
||||
submittedAt: 1,
|
||||
resolvedAt: 2,
|
||||
recovered: true
|
||||
}
|
||||
|
||||
const kept: AgentJournalSubmission = { ...restartRejected, keptAsQueuedMessageId: KEPT_ID }
|
||||
|
||||
function notices(
|
||||
send: StructuredAgentSessionPendingSend,
|
||||
submissions: readonly AgentJournalSubmission[]
|
||||
) {
|
||||
return structuredAgentSessionDeliveryNotices({
|
||||
pending: [send],
|
||||
submissions,
|
||||
agentName: 'Claude',
|
||||
startFailures: []
|
||||
})
|
||||
}
|
||||
|
||||
// Still being readied, on its way, or resent under its id after a lost answer.
|
||||
const SENDS = [
|
||||
pendingSend(),
|
||||
pendingSend({ issued: true }),
|
||||
pendingSend({ phase: 'recorded', issued: true })
|
||||
]
|
||||
|
||||
it('gives a kept send no notice, so it never reads "Sending…" or not sent', () => {
|
||||
for (const send of SENDS) {
|
||||
expect([...notices(send, [kept])]).toEqual([])
|
||||
}
|
||||
})
|
||||
|
||||
it('still says not sent, never "Sending…", for a send rejected without being kept', () => {
|
||||
for (const send of SENDS) {
|
||||
const notice = notices(send, [restartRejected]).get(agentJournalSubmissionKey(KEPT_ID))
|
||||
expect(notice).toMatchObject({ text: expect.any(String) })
|
||||
expect(notice).not.toHaveProperty('sending')
|
||||
}
|
||||
})
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { AgentJournalSubmission } from '../../../../shared/agent-session-journal-types'
|
||||
import type { StructuredAgentSessionPendingSend } from './structured-agent-session-pending-sends'
|
||||
import {
|
||||
pendingQueueSendsOnTheirWay,
|
||||
pendingSendsOutsideQueuedCards
|
||||
} from './structured-agent-session-sending-queued-cards'
|
||||
|
||||
function submission(clientMessageId: string, queuedMessageId?: string): AgentJournalSubmission {
|
||||
return {
|
||||
clientMessageId,
|
||||
fence: 1,
|
||||
payloadFingerprint: 'fingerprint',
|
||||
dispatchState: 'pending',
|
||||
providerItemId: null,
|
||||
reason: null,
|
||||
submittedAt: 1,
|
||||
resolvedAt: null,
|
||||
...(queuedMessageId !== undefined ? { queuedMessageId } : {})
|
||||
}
|
||||
}
|
||||
|
||||
describe('queue sends on their way', () => {
|
||||
it('a mid-turn queue send on its way is no bubble; a plain or recorded one is, until its row', () => {
|
||||
const entry = (
|
||||
clientMessageId: string,
|
||||
overrides: Partial<StructuredAgentSessionPendingSend> = {}
|
||||
): StructuredAgentSessionPendingSend => ({
|
||||
clientMessageId,
|
||||
sessionId: 'session-1',
|
||||
body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: clientMessageId }] },
|
||||
previewUris: [],
|
||||
queuedAt: 1,
|
||||
phase: 'sending',
|
||||
issued: true,
|
||||
...overrides
|
||||
})
|
||||
const ids = (entries: readonly StructuredAgentSessionPendingSend[]): string[] =>
|
||||
entries.map((candidate) => candidate.clientMessageId)
|
||||
const sends = [
|
||||
entry('queued', { delivery: 'queue-if-active' }),
|
||||
entry('plain'),
|
||||
entry('recorded', { phase: 'recorded' })
|
||||
]
|
||||
// Pending queued sends use cards while working; plain/recorded sends remain bubbles until host ownership appears.
|
||||
expect(ids(pendingSendsOutsideQueuedCards(sends, [], true))).toEqual(['plain', 'recorded'])
|
||||
expect(ids(pendingSendsOutsideQueuedCards(sends, [], false))).toEqual([
|
||||
'queued',
|
||||
'plain',
|
||||
'recorded'
|
||||
])
|
||||
expect(ids(pendingSendsOutsideQueuedCards(sends, ['plain'], false))).toEqual([
|
||||
'queued',
|
||||
'recorded'
|
||||
])
|
||||
})
|
||||
it('a sending card ends once the host records the send or hands its card off', () => {
|
||||
const entry: StructuredAgentSessionPendingSend = {
|
||||
clientMessageId: 'a',
|
||||
sessionId: 'session-1',
|
||||
body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: 'a' }] },
|
||||
previewUris: [],
|
||||
queuedAt: 1,
|
||||
phase: 'sending',
|
||||
issued: true,
|
||||
delivery: 'queue-if-active'
|
||||
}
|
||||
const onItsWay = (submissions: AgentJournalSubmission[]) =>
|
||||
pendingQueueSendsOnTheirWay([entry], [], true, submissions).map(
|
||||
(candidate) => candidate.clientMessageId
|
||||
)
|
||||
expect(onItsWay([])).toEqual(['a'])
|
||||
expect(onItsWay([submission('a')])).toEqual([])
|
||||
expect(onItsWay([submission('a-hand-off', 'a')])).toEqual([])
|
||||
expect(pendingQueueSendsOnTheirWay([entry], ['a'], true, [])).toEqual([])
|
||||
expect(pendingQueueSendsOnTheirWay([entry], [], false, [])).toEqual([])
|
||||
expect(pendingQueueSendsOnTheirWay([{ ...entry, phase: 'recorded' }], [], true, [])).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -1,78 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { PluginListEntry } from './plugin-list-entries'
|
||||
import { matchesPluginFilter, pluginFilterCategories } from './plugin-list-filter'
|
||||
|
||||
function entry(
|
||||
categories: string[],
|
||||
sourceId: string | null,
|
||||
catalogIds: string[] = sourceId ? [sourceId] : []
|
||||
): PluginListEntry {
|
||||
return {
|
||||
pluginKey: `p.${categories.join('-')}`,
|
||||
name: 'P',
|
||||
publisher: null,
|
||||
version: null,
|
||||
description: null,
|
||||
official: false,
|
||||
categories,
|
||||
types: [],
|
||||
minimumOrca: null,
|
||||
updateVersion: null,
|
||||
catalogIds,
|
||||
updatable: false,
|
||||
autoUpdate: { updatedTo: null, needsReview: false },
|
||||
installed: null,
|
||||
listing: sourceId
|
||||
? {
|
||||
marketplaceSourceId: sourceId,
|
||||
marketplaceName: 'Catalog',
|
||||
marketplaceOwner: 'owner',
|
||||
marketplaceCommit: 'c',
|
||||
pluginKey: 'p',
|
||||
source: { kind: 'git', url: 'https://example.invalid/c.git', ref: 'main' },
|
||||
categories,
|
||||
official: false,
|
||||
bundled: false
|
||||
}
|
||||
: null,
|
||||
verifiedSource: { kind: 'unverified' },
|
||||
alsoListedBy: []
|
||||
}
|
||||
}
|
||||
|
||||
describe('plugin list filter', () => {
|
||||
it('counts categories without the trust label, and matches category and catalog together', () => {
|
||||
const themes = entry(['themes', 'official'], 'official')
|
||||
const icons = entry(['icons'], 'acme')
|
||||
expect(pluginFilterCategories([themes, icons])).toEqual([
|
||||
['themes', 1],
|
||||
['icons', 1]
|
||||
])
|
||||
const filter = { categories: new Set(['themes']), sourceIds: new Set(['official']) }
|
||||
expect(matchesPluginFilter(themes, filter)).toBe(true)
|
||||
expect(matchesPluginFilter(icons, filter)).toBe(false)
|
||||
expect(matchesPluginFilter(entry(['themes'], null), filter)).toBe(false)
|
||||
expect(matchesPluginFilter(icons, { categories: new Set(), sourceIds: new Set() })).toBe(true)
|
||||
})
|
||||
|
||||
it('folds catalog categories into readable kinds and keeps unknown ones', () => {
|
||||
const terminal = entry(['terminal-themes'], 'official')
|
||||
const recipes = entry(['vm-recipes'], 'official')
|
||||
const odd = entry(['music'], 'official')
|
||||
expect(pluginFilterCategories([terminal, recipes, odd])).toEqual([
|
||||
['themes', 1],
|
||||
['machines', 1],
|
||||
['music', 1]
|
||||
])
|
||||
expect(
|
||||
matchesPluginFilter(terminal, { categories: new Set(['themes']), sourceIds: new Set() })
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('matches a plugin under any catalog that lists it', () => {
|
||||
const shared = entry(['themes'], 'official', ['official', 'acme'])
|
||||
expect(
|
||||
matchesPluginFilter(shared, { categories: new Set(), sourceIds: new Set(['acme']) })
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,80 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { AiVaultSession } from '../../../../shared/ai-vault-types'
|
||||
import type { AiVaultSearchHit } from '../../../../shared/ai-vault-search-types'
|
||||
import { resolveAiVaultSessionRowChatActions } from './ai-vault-session-chat-action'
|
||||
|
||||
const session: AiVaultSession = {
|
||||
id: 'local:claude:s-1:/tmp/s-1.jsonl',
|
||||
executionHostId: 'local',
|
||||
agent: 'claude',
|
||||
sessionId: 's-1',
|
||||
title: 'Fix auth retry',
|
||||
cwd: '/repo',
|
||||
branch: null,
|
||||
model: null,
|
||||
filePath: '/tmp/s-1.jsonl',
|
||||
codexHome: null,
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
modifiedAt: '2026-09-17T00:00:00.000Z',
|
||||
messageCount: 2,
|
||||
totalTokens: 0,
|
||||
previewMessages: [{ role: 'user', text: 'Fix it', timestamp: null }],
|
||||
queuedMessageCount: 0,
|
||||
subagentTranscriptCount: 0,
|
||||
resumeCommand: 'claude --resume s-1',
|
||||
subagent: null
|
||||
}
|
||||
|
||||
const hit: AiVaultSearchHit = {
|
||||
agent: 'claude',
|
||||
sessionId: 's-1',
|
||||
title: 'Fix auth retry',
|
||||
cwd: '/repo',
|
||||
branch: null,
|
||||
updatedAt: null,
|
||||
messageCount: 2,
|
||||
score: 1,
|
||||
source: { presence: 'missing' },
|
||||
evidence: null
|
||||
}
|
||||
|
||||
function actions(overrides: Partial<Parameters<typeof resolveAiVaultSessionRowChatActions>[0]>) {
|
||||
return resolveAiVaultSessionRowChatActions({
|
||||
session,
|
||||
originalPaneTarget: null,
|
||||
structuredSessionOpen: false,
|
||||
resumeState: { blocked: false, worktreeId: 'wt-1', usesSessionWorktree: true },
|
||||
resumeInChat: { available: true, workspaceId: 'wt-own' },
|
||||
searchHit: undefined,
|
||||
...overrides
|
||||
})
|
||||
}
|
||||
|
||||
describe('session row chat actions', () => {
|
||||
it('resumes with ▶ only while ▶ is enabled and shown', () => {
|
||||
expect(actions({})).toMatchObject({ resumeDisabled: false, resumeWorktreeId: 'wt-1' })
|
||||
expect(
|
||||
actions({ resumeState: { blocked: true, worktreeId: 'wt-1', usesSessionWorktree: true } })
|
||||
).toMatchObject({ resumeDisabled: true, resumeWorktreeId: null })
|
||||
// An open chat hides ▶; switching to it is the only offer.
|
||||
expect(actions({ structuredSessionOpen: true }).resumeWorktreeId).toBeNull()
|
||||
expect(
|
||||
actions({ session: { ...session, messageCount: 0, previewMessages: [] } })
|
||||
).toMatchObject({ resumeDisabled: true, resumeWorktreeId: null })
|
||||
})
|
||||
|
||||
it('offers nothing a hidden button would not', () => {
|
||||
expect(
|
||||
actions({ resumeInChat: { available: false, reason: 'remote' } }).resumeInNewChatWorkspaceId
|
||||
).toBeNull()
|
||||
// A search hit whose transcript is gone cannot resume, whatever eligibility said.
|
||||
expect(actions({ searchHit: hit })).toEqual({
|
||||
canJumpToOriginalPane: false,
|
||||
resumeDisabled: true,
|
||||
resumeWorktreeId: null,
|
||||
resumeInNewChatWorkspaceId: null,
|
||||
resumeInNewCliWorktreeId: null
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,79 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { selectReviewCacheData, selectReviewCacheEntry } from './review-cache-entry-selection'
|
||||
|
||||
type Review = { id: string }
|
||||
type ReviewEntry = { data: Review | null; fetchedAt: number }
|
||||
|
||||
const ACTIVE_KEY = 'repo-active::feature'
|
||||
|
||||
function replaceUnrelatedEntry(
|
||||
cache: Record<string, ReviewEntry>,
|
||||
index: number
|
||||
): Record<string, ReviewEntry> {
|
||||
return {
|
||||
...cache,
|
||||
[`repo-background-${index % 100}::feature`]: {
|
||||
data: { id: `background-${index}` },
|
||||
fetchedAt: index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('review cache entry selection', () => {
|
||||
it('turns unrelated active-panel cache invalidations into stable selections', () => {
|
||||
const activeHostedReview = { id: 'hosted-active' }
|
||||
const activePullRequest = { id: 'pr-active' }
|
||||
let hostedCache: Record<string, ReviewEntry> = {
|
||||
[ACTIVE_KEY]: { data: activeHostedReview, fetchedAt: 1 }
|
||||
}
|
||||
let pullRequestCache: Record<string, ReviewEntry> = {
|
||||
[ACTIVE_KEY]: { data: activePullRequest, fetchedAt: 1 }
|
||||
}
|
||||
let previousHostedMap = hostedCache
|
||||
let previousPullRequestMap = pullRequestCache
|
||||
let previousHostedSelection = selectReviewCacheData(hostedCache, ACTIVE_KEY)
|
||||
let previousPullRequestSelection = selectReviewCacheEntry(pullRequestCache, ACTIVE_KEY)
|
||||
let wholeMapInvalidations = 0
|
||||
let scopedSelectionInvalidations = 0
|
||||
|
||||
for (let index = 0; index < 200; index += 1) {
|
||||
hostedCache = replaceUnrelatedEntry(hostedCache, index)
|
||||
pullRequestCache = replaceUnrelatedEntry(pullRequestCache, index)
|
||||
if (hostedCache !== previousHostedMap) {
|
||||
wholeMapInvalidations += 1
|
||||
}
|
||||
if (pullRequestCache !== previousPullRequestMap) {
|
||||
wholeMapInvalidations += 1
|
||||
}
|
||||
|
||||
const hostedSelection = selectReviewCacheData(hostedCache, ACTIVE_KEY)
|
||||
const pullRequestSelection = selectReviewCacheEntry(pullRequestCache, ACTIVE_KEY)
|
||||
if (hostedSelection !== previousHostedSelection) {
|
||||
scopedSelectionInvalidations += 1
|
||||
}
|
||||
if (pullRequestSelection !== previousPullRequestSelection) {
|
||||
scopedSelectionInvalidations += 1
|
||||
}
|
||||
|
||||
previousHostedMap = hostedCache
|
||||
previousPullRequestMap = pullRequestCache
|
||||
previousHostedSelection = hostedSelection
|
||||
previousPullRequestSelection = pullRequestSelection
|
||||
}
|
||||
|
||||
expect(wholeMapInvalidations).toBe(400)
|
||||
expect(scopedSelectionInvalidations).toBe(0)
|
||||
})
|
||||
|
||||
it('publishes relevant entry replacements and handles absent keys', () => {
|
||||
const first = { data: { id: 'first' }, fetchedAt: 1 }
|
||||
const second = { data: { id: 'second' }, fetchedAt: 2 }
|
||||
|
||||
expect(selectReviewCacheEntry({ [ACTIVE_KEY]: first }, ACTIVE_KEY)).toBe(first)
|
||||
expect(selectReviewCacheEntry({ [ACTIVE_KEY]: second }, ACTIVE_KEY)).toBe(second)
|
||||
expect(selectReviewCacheData({ [ACTIVE_KEY]: second }, ACTIVE_KEY)).toBe(second.data)
|
||||
expect(selectReviewCacheEntry({}, ACTIVE_KEY)).toBeUndefined()
|
||||
expect(selectReviewCacheData({}, ACTIVE_KEY)).toBeNull()
|
||||
expect(selectReviewCacheEntry({ [ACTIVE_KEY]: first }, null)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -1,76 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { buildSearchRows } from './search-rows'
|
||||
|
||||
describe('buildSearchRows', () => {
|
||||
it('includes file headers and expanded matches in row order (summary is rendered separately)', () => {
|
||||
const rows = buildSearchRows(
|
||||
{
|
||||
totalMatches: 3,
|
||||
truncated: false,
|
||||
files: [
|
||||
{
|
||||
filePath: '/repo/a.ts',
|
||||
relativePath: 'a.ts',
|
||||
matches: [
|
||||
{ line: 1, column: 1, matchLength: 3, lineContent: 'foo' },
|
||||
{ line: 2, column: 5, matchLength: 3, lineContent: 'bar foo' }
|
||||
]
|
||||
},
|
||||
{
|
||||
filePath: '/repo/b.ts',
|
||||
relativePath: 'nested/b.ts',
|
||||
matches: [{ line: 8, column: 2, matchLength: 3, lineContent: ' foo' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
new Set<string>()
|
||||
)
|
||||
|
||||
expect(rows.map((row) => row.type)).toEqual(['file', 'match', 'match', 'file', 'match'])
|
||||
})
|
||||
|
||||
it('omits match rows for collapsed files', () => {
|
||||
const rows = buildSearchRows(
|
||||
{
|
||||
totalMatches: 2,
|
||||
truncated: true,
|
||||
files: [
|
||||
{
|
||||
filePath: '/repo/a.ts',
|
||||
relativePath: 'a.ts',
|
||||
matches: [{ line: 1, column: 1, matchLength: 3, lineContent: 'foo' }]
|
||||
},
|
||||
{
|
||||
filePath: '/repo/b.ts',
|
||||
relativePath: 'b.ts',
|
||||
matches: [{ line: 2, column: 1, matchLength: 3, lineContent: 'foo' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
new Set<string>(['/repo/a.ts'])
|
||||
)
|
||||
|
||||
expect(rows.map((row) => row.type)).toEqual(['file', 'file', 'match'])
|
||||
})
|
||||
|
||||
it('preserves the file result object for renderer-side count normalization', () => {
|
||||
const fileResult = {
|
||||
filePath: '/repo/a.ts',
|
||||
relativePath: 'a.ts',
|
||||
matchCount: 5,
|
||||
matches: [{ line: 1, column: 1, matchLength: 3, lineContent: 'foo' }]
|
||||
}
|
||||
|
||||
const rows = buildSearchRows(
|
||||
{
|
||||
totalMatches: 5,
|
||||
truncated: false,
|
||||
files: [fileResult]
|
||||
},
|
||||
new Set<string>()
|
||||
)
|
||||
|
||||
expect(rows[0]).toMatchObject({ type: 'file', fileResult })
|
||||
expect(rows[1]).toMatchObject({ type: 'match', fileResult })
|
||||
})
|
||||
})
|
||||
@@ -1,77 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
createTestStore,
|
||||
runtimeOwnedRepoState,
|
||||
seedStore
|
||||
} from '../../store/slices/store-test-helpers'
|
||||
import { getDefaultSettings } from '../../../../shared/constants'
|
||||
import { selectWslHostUnavailableReason } from './use-wsl-host-unavailable-reason'
|
||||
|
||||
const WORKTREE = 'wsl-repo::/home/me/app'
|
||||
|
||||
function stateWith(options: { enabled: boolean; serverFiles?: 'present' | 'missing' }) {
|
||||
const store = createTestStore()
|
||||
seedStore(store, {
|
||||
...runtimeOwnedRepoState('wsl:ubuntu', 'wsl-repo', '/home/me/app'),
|
||||
runtimeEnvironments: [
|
||||
{
|
||||
id: 'wsl:ubuntu',
|
||||
name: 'Ubuntu (WSL)',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
lastUsedAt: null,
|
||||
runtimeId: null,
|
||||
preferredEndpointId: 'ws',
|
||||
endpoints: [],
|
||||
orcadDeployment: { kind: 'wsl', distro: 'Ubuntu', localPort: 42000, remotePort: 6768 }
|
||||
}
|
||||
],
|
||||
wslHostServerFiles: options.serverFiles ?? 'present'
|
||||
})
|
||||
store.setState({
|
||||
settings: { ...getDefaultSettings('/home/me'), experimentalWslHosts: options.enabled }
|
||||
})
|
||||
return store.getState()
|
||||
}
|
||||
|
||||
describe('selectWslHostUnavailableReason', () => {
|
||||
it('says a WSL host’s terminal is turned off rather than reconnecting', () => {
|
||||
expect(selectWslHostUnavailableReason(stateWith({ enabled: false }), WORKTREE)).toBe(
|
||||
'turned-off'
|
||||
)
|
||||
})
|
||||
|
||||
it('prefers the missing server files, and is quiet when the host can run', () => {
|
||||
expect(
|
||||
selectWslHostUnavailableReason(stateWith({ enabled: true, serverFiles: 'missing' }), WORKTREE)
|
||||
).toBe('server-files-missing')
|
||||
expect(selectWslHostUnavailableReason(stateWith({ enabled: true }), WORKTREE)).toBeNull()
|
||||
})
|
||||
|
||||
it('leaves other hosts’ workspaces alone', () => {
|
||||
expect(
|
||||
selectWslHostUnavailableReason(stateWith({ enabled: false }), 'repo1::/repo1')
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('is quiet when no WSL host exists, even for a workspace on another runtime', () => {
|
||||
const store = createTestStore()
|
||||
seedStore(store, {
|
||||
...runtimeOwnedRepoState('paired', 'remote-repo', '/srv/app'),
|
||||
runtimeEnvironments: [
|
||||
{
|
||||
id: 'paired',
|
||||
name: 'Paired server',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
lastUsedAt: null,
|
||||
runtimeId: null,
|
||||
preferredEndpointId: 'ws',
|
||||
endpoints: []
|
||||
}
|
||||
]
|
||||
})
|
||||
store.setState({ settings: { ...getDefaultSettings('/home/me'), experimentalWslHosts: false } })
|
||||
expect(selectWslHostUnavailableReason(store.getState(), 'remote-repo::/srv/app')).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,79 +0,0 @@
|
||||
import { create } from 'zustand'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { createNewIssueDraftSlice } from './new-issue-draft'
|
||||
import type { AppState } from '../types'
|
||||
import type { GitHubAssignableUser } from '../../../../shared/github/pull-request-types'
|
||||
|
||||
function makeStore() {
|
||||
return create<Pick<AppState, 'newIssueDraft' | 'setNewIssueDraft' | 'clearNewIssueDraft'>>()(
|
||||
(...args) => createNewIssueDraftSlice(...(args as Parameters<typeof createNewIssueDraftSlice>))
|
||||
)
|
||||
}
|
||||
|
||||
const assignee: GitHubAssignableUser = { login: 'octocat', name: 'Octo', avatarUrl: '' }
|
||||
|
||||
describe('createNewIssueDraftSlice', () => {
|
||||
it('starts with no draft', () => {
|
||||
expect(makeStore().getState().newIssueDraft).toBeNull()
|
||||
})
|
||||
|
||||
it('seeds a fresh empty draft with the patch when none exists', () => {
|
||||
const store = makeStore()
|
||||
|
||||
store.getState().setNewIssueDraft({ title: 'Bug' })
|
||||
|
||||
expect(store.getState().newIssueDraft).toEqual({
|
||||
title: 'Bug',
|
||||
body: '',
|
||||
labels: [],
|
||||
assignees: [],
|
||||
repoId: null
|
||||
})
|
||||
})
|
||||
|
||||
it('shallow-merges the patch into the current draft', () => {
|
||||
const store = makeStore()
|
||||
store.getState().setNewIssueDraft({
|
||||
title: 'Bug',
|
||||
body: 'details',
|
||||
labels: ['p1'],
|
||||
assignees: [assignee],
|
||||
repoId: 'repo-a'
|
||||
})
|
||||
|
||||
store.getState().setNewIssueDraft({ body: 'more details' })
|
||||
|
||||
expect(store.getState().newIssueDraft).toEqual({
|
||||
title: 'Bug',
|
||||
body: 'more details',
|
||||
labels: ['p1'],
|
||||
assignees: [assignee],
|
||||
repoId: 'repo-a'
|
||||
})
|
||||
})
|
||||
|
||||
it('clears the draft back to null', () => {
|
||||
const store = makeStore()
|
||||
store.getState().setNewIssueDraft({ title: 'Bug', repoId: 'repo-a' })
|
||||
|
||||
store.getState().clearNewIssueDraft()
|
||||
|
||||
expect(store.getState().newIssueDraft).toBeNull()
|
||||
})
|
||||
|
||||
it('seeds fresh label/assignee arrays per empty draft (no shared-constant aliasing)', () => {
|
||||
// Partial patches must seed fresh label/assignee arrays; shared defaults let mutations corrupt all future drafts.
|
||||
const a = makeStore()
|
||||
const b = makeStore()
|
||||
a.getState().setNewIssueDraft({ title: 'A' })
|
||||
b.getState().setNewIssueDraft({ title: 'B' })
|
||||
|
||||
expect(a.getState().newIssueDraft?.labels).not.toBe(b.getState().newIssueDraft?.labels)
|
||||
expect(a.getState().newIssueDraft?.assignees).not.toBe(b.getState().newIssueDraft?.assignees)
|
||||
|
||||
a.getState().newIssueDraft?.labels.push('leak')
|
||||
const c = makeStore()
|
||||
c.getState().setNewIssueDraft({ title: 'C' })
|
||||
expect(c.getState().newIssueDraft?.labels).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -1,80 +0,0 @@
|
||||
import { create } from 'zustand'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
createTaskCreationDraftsSlice,
|
||||
isTaskCreationDraftContentful
|
||||
} from './task-creation-drafts'
|
||||
import type { AppState } from '../types'
|
||||
|
||||
function makeStore() {
|
||||
return create<
|
||||
Pick<
|
||||
AppState,
|
||||
| 'newLinearIssueDraft'
|
||||
| 'setNewLinearIssueDraft'
|
||||
| 'clearNewLinearIssueDraft'
|
||||
| 'newLinearProjectDraft'
|
||||
| 'setNewLinearProjectDraft'
|
||||
| 'clearNewLinearProjectDraft'
|
||||
| 'newJiraIssueDraft'
|
||||
| 'setNewJiraIssueDraft'
|
||||
| 'clearNewJiraIssueDraft'
|
||||
>
|
||||
>()((...args) =>
|
||||
createTaskCreationDraftsSlice(...(args as Parameters<typeof createTaskCreationDraftsSlice>))
|
||||
)
|
||||
}
|
||||
|
||||
describe('createTaskCreationDraftsSlice', () => {
|
||||
it('starts with no drafts', () => {
|
||||
const state = makeStore().getState()
|
||||
expect(state.newLinearIssueDraft).toBeNull()
|
||||
expect(state.newLinearProjectDraft).toBeNull()
|
||||
expect(state.newJiraIssueDraft).toBeNull()
|
||||
})
|
||||
|
||||
it('stores and clears each draft independently', () => {
|
||||
const store = makeStore()
|
||||
|
||||
store.getState().setNewLinearIssueDraft({ title: 'Linear bug', body: 'details' })
|
||||
store.getState().setNewLinearProjectDraft({
|
||||
name: 'Roadmap',
|
||||
description: 'summary',
|
||||
content: 'brief'
|
||||
})
|
||||
store.getState().setNewJiraIssueDraft({ title: 'Jira bug', body: 'steps' })
|
||||
|
||||
store.getState().clearNewLinearIssueDraft()
|
||||
|
||||
expect(store.getState().newLinearIssueDraft).toBeNull()
|
||||
expect(store.getState().newLinearProjectDraft).toEqual({
|
||||
name: 'Roadmap',
|
||||
description: 'summary',
|
||||
content: 'brief'
|
||||
})
|
||||
expect(store.getState().newJiraIssueDraft).toEqual({ title: 'Jira bug', body: 'steps' })
|
||||
})
|
||||
|
||||
it('replaces a draft wholesale on set', () => {
|
||||
const store = makeStore()
|
||||
store.getState().setNewJiraIssueDraft({ title: 'first', body: 'text' })
|
||||
|
||||
store.getState().setNewJiraIssueDraft({ title: 'second', body: '' })
|
||||
|
||||
expect(store.getState().newJiraIssueDraft).toEqual({ title: 'second', body: '' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('isTaskCreationDraftContentful', () => {
|
||||
it('rejects an all-empty or whitespace-only form', () => {
|
||||
expect(isTaskCreationDraftContentful({ title: '', body: '' })).toBe(false)
|
||||
expect(isTaskCreationDraftContentful({ title: ' ', body: '\n\t' })).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts any field with typed text', () => {
|
||||
expect(isTaskCreationDraftContentful({ title: 'Bug', body: '' })).toBe(true)
|
||||
expect(isTaskCreationDraftContentful({ name: '', description: '', content: 'brief' })).toBe(
|
||||
true
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,74 +0,0 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
|
||||
import {
|
||||
installBrowserGlobals,
|
||||
writeStoredRuntimeEnvironment
|
||||
} from './web-preload-api-test-harness'
|
||||
|
||||
describe('web SSH preload API', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
vi.doUnmock('./web-runtime-client')
|
||||
})
|
||||
|
||||
it('preserves full and partial authority states from the paired runtime', async () => {
|
||||
const runtimeCalls: { method: string; params: unknown }[] = []
|
||||
vi.doMock('./web-runtime-client', () => ({
|
||||
WebRuntimeClient: class {
|
||||
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
|
||||
runtimeCalls.push({ method, params })
|
||||
const state =
|
||||
method === 'ssh.connect'
|
||||
? {
|
||||
targetId: 'ssh-1',
|
||||
status: 'connected',
|
||||
error: null,
|
||||
reconnectAttempt: 0,
|
||||
providerEpoch: 'web-provider-epoch',
|
||||
connectionGeneration: 23
|
||||
}
|
||||
: {
|
||||
targetId: 'ssh-1',
|
||||
status: 'connected',
|
||||
error: null,
|
||||
reconnectAttempt: 0,
|
||||
providerEpoch: 'partial-provider-epoch'
|
||||
}
|
||||
return Promise.resolve({
|
||||
id: `call-${runtimeCalls.length}`,
|
||||
ok: true,
|
||||
result: { state },
|
||||
_meta: { runtimeId: 'runtime-1' }
|
||||
})
|
||||
}
|
||||
|
||||
close(): void {}
|
||||
}
|
||||
}))
|
||||
|
||||
const globals = installBrowserGlobals('Linux')
|
||||
writeStoredRuntimeEnvironment(globals.storage)
|
||||
const { installWebPreloadApi } = await import('./web-preload-api')
|
||||
installWebPreloadApi()
|
||||
|
||||
await expect(globals.window.api.ssh.connect({ targetId: 'ssh-1' })).resolves.toMatchObject({
|
||||
providerEpoch: 'web-provider-epoch',
|
||||
connectionGeneration: 23
|
||||
})
|
||||
const partial = await globals.window.api.ssh.getState({ targetId: 'ssh-1' })
|
||||
|
||||
expect(partial).toMatchObject({ providerEpoch: 'partial-provider-epoch' })
|
||||
expect(partial).not.toHaveProperty('connectionGeneration')
|
||||
// A background connect stays automatic on the host, so it cannot lift a cancelled sign-in.
|
||||
await globals.window.api.ssh.connect({ targetId: 'ssh-1', automatic: true })
|
||||
expect(runtimeCalls).toEqual([
|
||||
{ method: 'ssh.connect', params: { targetId: 'ssh-1' } },
|
||||
{ method: 'ssh.getState', params: { targetId: 'ssh-1' } },
|
||||
{ method: 'ssh.connect', params: { targetId: 'ssh-1', automatic: true } }
|
||||
])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user