test: remove low-value renderer projection and wiring tests (#37496)

This commit is contained in:
OrcaWin
2026-11-02 07:37:19 -08:00
committed by GitHub
parent 02e93251ec
commit a4d6f5e4bf
47 changed files with 9 additions and 3854 deletions
-14
View File
@@ -10581,26 +10581,21 @@
"src/renderer/src/lib/browser-link-open-layout-owner.test.ts": 3133,
"src/renderer/src/lib/browser-link-open.test.ts": 2215,
"src/renderer/src/lib/browser-link-profile-isolation.test.ts": 2511,
"src/renderer/src/lib/browser-page-conversion-history.test.ts": 62,
"src/renderer/src/lib/browser-page-palette-activation.test.ts": 1983,
"src/renderer/src/lib/browser-palette-page-entries.test.ts": 480,
"src/renderer/src/lib/browser-palette-search.test.ts": 467,
"src/renderer/src/lib/browser-workspace-source-resolution.test.ts": 52,
"src/renderer/src/lib/browser-workspace-tab-activation.test.ts": 1925,
"src/renderer/src/lib/bundled-skill-target.test.ts": 54,
"src/renderer/src/lib/check-run-details-log-normalization-performance.test.ts": 846,
"src/renderer/src/lib/check-run-details-log-normalization.test.ts": 75,
"src/renderer/src/lib/claude-account-switch-sessions.test.ts": 782,
"src/renderer/src/lib/claude-session-account-move.test.ts": 1816,
"src/renderer/src/lib/cli-conversation-exit-guidance.test.ts": 202,
"src/renderer/src/lib/client-creation-action-policy.test.ts": 1720,
"src/renderer/src/lib/clipboard-file-path.test.ts": 241,
"src/renderer/src/lib/clipboard-image-copy.test.ts": 243,
"src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts": 526,
"src/renderer/src/lib/cmd-j-linear-issue-intent.test.ts": 42,
"src/renderer/src/lib/code-editor-theme.test.ts": 101,
"src/renderer/src/lib/code-host-error-copy.test.ts": 297,
"src/renderer/src/lib/codex-account-display-label.test.ts": 47,
"src/renderer/src/lib/codex-pane-selection-lane.test.ts": 543,
"src/renderer/src/lib/codex-session-restart-account-recheck.test.ts": 1758,
"src/renderer/src/lib/codex-session-restart-shell-flap.test.ts": 1745,
@@ -10610,32 +10605,23 @@
"src/renderer/src/lib/comment-body-line-count.test.ts": 84,
"src/renderer/src/lib/comment-body-submit-state.test.ts": 55,
"src/renderer/src/lib/composer-create-handoff.test.ts": 227,
"src/renderer/src/lib/composer-issue-command.test.ts": 1775,
"src/renderer/src/lib/composer-submit-cancellation.test.ts": 41,
"src/renderer/src/lib/connection-context.test.ts": 1817,
"src/renderer/src/lib/crash-diagnostics.test.ts": 176,
"src/renderer/src/lib/create-untitled-markdown.test.ts": 566,
"src/renderer/src/lib/default-creation-host.test.ts": 48,
"src/renderer/src/lib/default-terminal-tabs-applied-persist.test.ts": 1828,
"src/renderer/src/lib/desktop-created-server-tabs-switch-off.test.ts": 51,
"src/renderer/src/lib/desktop-created-server-tabs.test.ts": 1933,
"src/renderer/src/lib/desktop-sidebar-feed.test.ts": 831,
"src/renderer/src/lib/desktop-window-chrome.test.ts": 40,
"src/renderer/src/lib/diff-comment-compat.test.ts": 40,
"src/renderer/src/lib/diff-comments-format.test.ts": 61,
"src/renderer/src/lib/direct-ssh-target-scope.test.ts": 48,
"src/renderer/src/lib/direct-work-item-setup-callers.test.tsx": 2436,
"src/renderer/src/lib/direct-work-item-setup.test.ts": 2072,
"src/renderer/src/lib/doc-preview-grants.test.ts": 2094,
"src/renderer/src/lib/document-theme.test.ts": 136,
"src/renderer/src/lib/editable-target.test.ts": 175,
"src/renderer/src/lib/editor-file-operation-owner.test.ts": 2848,
"src/renderer/src/lib/editor-typography.test.ts": 65,
"src/renderer/src/lib/empty-workspace-default-agent-chat.test.ts": 1895,
"src/renderer/src/lib/emulator-error-copy.test.ts": 415,
"src/renderer/src/lib/ensure-hooks-confirmed.test.ts": 799,
"src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts": 3601,
"src/renderer/src/lib/ensure-simulator-tab.test.ts": 44,
"src/renderer/src/lib/ephemeral-vm-failed-create-cleanup.test.ts": 40,
"src/renderer/src/lib/ephemeral-vm-runtime-cleanup.test.ts": 68,
"src/renderer/src/lib/ephemeral-vm-workspace-target.integration.test.ts": 351,
@@ -2,7 +2,6 @@ import { beforeEach, describe, expect, it } from 'vitest'
import { buildBrowserPage, buildWorkspaceFromPage } from '@/store/slices/browser-page-records'
import {
browserLinkPageMatches,
browserLinkUrlIdentity,
browserOpenResultForPage,
selectBrowserLinkDestination
} from './browser-link-destination'
@@ -23,119 +22,7 @@ beforeEach(() => {
store = createBrowserLinkTestStore()
})
it('normalizes only explicit HTTP URLs and keeps path, query and fragment identity', () => {
expect(browserLinkUrlIdentity('https://EXAMPLE.com:443')).toBe('https://example.com/')
for (const input of [
'example.com',
'search words',
'file:///tmp/a.html',
'javascript:alert(1)',
'https://user:password@example.com/',
'https://kagi.com/search?token=secret&q=a'
]) {
expect(browserLinkUrlIdentity(input)).toBeNull()
}
const identities = ['/a', '/a/', '/A', '/a?a=1&b=2', '/a?b=2&a=1', '/a#one', '/a#two'].map(
(path) => browserLinkUrlIdentity(`https://example.com${path}`)
)
expect(new Set(identities).size).toBe(identities.length)
expect(browserLinkUrlIdentity('http://localhost:3000')).not.toBe(
browserLinkUrlIdentity('http://127.0.0.1:3000')
)
})
describe('browser destination scope', () => {
it.each([
['worktree', 'local', undefined, 'local'],
['worktree', 'ssh:target', undefined, 'ssh:target'],
['worktree', 'runtime:server', undefined, 'runtime:server'],
['worktree', 'ssh:target', 'server', 'ssh:target'],
['worktree', 'ssh:target', 'server', 'runtime:server'],
['folder', 'local', undefined, 'local'],
['folder', 'runtime:server', undefined, 'runtime:server']
] as const)(
'places a hostless %s source owned by %s (%s) on %s',
(kind, hostId, runtimeOwner, destinationHostId) => {
const workspaceId = kind === 'folder' ? 'folder:legacy' : WORKSPACE
store.setState((state) => ({
activeWorktreeId: workspaceId,
activeWorkspaceExecutionHostId: hostId,
worktreesByRepo: {
repo1: [
makeWorktree({
id: WORKSPACE,
repoId: 'repo1',
hostId,
runtimeOwnerEnvironmentId: runtimeOwner
})
]
},
folderWorkspaces: [makeFolderWorkspace({ id: 'legacy', executionHostId: hostId })],
unifiedTabsByWorktree: {
[workspaceId]: state.unifiedTabsByWorktree[WORKSPACE]!.map((tab) => ({
...tab,
worktreeId: workspaceId,
executionHostId: undefined
}))
},
groupsByWorktree: { [workspaceId]: state.groupsByWorktree[WORKSPACE]! },
layoutByWorktree: { [workspaceId]: state.layoutByWorktree[WORKSPACE]! },
activeGroupIdByWorktree: { [workspaceId]: 'c' }
}))
const state = store.getState()
expect(
selectBrowserLinkDestination(
state,
browserLinkTestDestination({
worktreeId: workspaceId,
executionHostId: destinationHostId
}),
captureLinkOpenIntent(state, workspaceId, 'source')
)
).toMatchObject({
status: 'ready',
groupId: 'b',
match: null
})
}
)
it('places hostless sources beside their group and reuses hostless browser wrappers', () => {
const existing = store.getState().createBrowserTab(WORKSPACE, URL_TO_OPEN, {
targetGroupId: 'b',
executionHostId: 'local',
browserRuntimeEnvironmentId: null,
sessionProfileId: null,
activate: false
})
store.setState((state) => ({
unifiedTabsByWorktree: {
[WORKSPACE]: state.unifiedTabsByWorktree[WORKSPACE]!.map((tab) => ({
...tab,
executionHostId: undefined
}))
}
}))
const state = store.getState()
const intent = captureLinkOpenIntent(state, WORKSPACE, 'source')
const destination = browserLinkTestDestination()
expect(selectBrowserLinkDestination(state, destination, intent)).toMatchObject({
status: 'ready',
groupId: 'b',
match: { browserWorkspaceId: existing.id, browserPageId: existing.activePageId }
})
expect(
browserOpenResultForPage(state, destination, existing.id, existing.activePageId!)
).toMatchObject({ groupId: 'b', browserWorkspaceId: existing.id })
store.setState({ browserTabsByWorktree: {} })
expect(selectBrowserLinkDestination(store.getState(), destination, intent)).toMatchObject({
status: 'ready',
groupId: 'b',
match: null
})
})
it.each(['listed', 'detected'] as const)(
'refuses hostless ownership for %s twins even when the active workspace names one host',
(catalog) => {
@@ -339,41 +226,6 @@ describe('browser destination scope', () => {
).toMatchObject({ groupId: 'b', match: null })
})
it('uses a visible browser and reuses a neighbor when that browser becomes hidden', () => {
const existing = store.getState().createBrowserTab(WORKSPACE, 'https://unrelated.example/', {
targetGroupId: 'a',
executionHostId: 'local',
browserRuntimeEnvironmentId: null,
activate: true
})
store.setState((state) => ({
unifiedTabsByWorktree: {
[WORKSPACE]: state.unifiedTabsByWorktree[WORKSPACE]!.map((tab) =>
tab.id === 'source' ? { ...tab, groupId: 'b' } : tab
)
}
}))
const intent = captureLinkOpenIntent(store.getState(), WORKSPACE, 'source')
expect(
selectBrowserLinkDestination(store.getState(), browserLinkTestDestination(), intent)
).toMatchObject({ groupId: 'a', match: null })
store.setState((state) => ({
groupsByWorktree: {
[WORKSPACE]: state.groupsByWorktree[WORKSPACE]!.map((group) =>
group.id === 'a' ? { ...group, activeTabId: 'source' } : group
)
}
}))
expect(
selectBrowserLinkDestination(store.getState(), browserLinkTestDestination(), intent)
).toMatchObject({ groupId: 'c', match: null })
expect(
store
.getState()
.browserTabsByWorktree[WORKSPACE]?.some((workspace) => workspace.id === existing.id)
).toBe(true)
})
it('requires doc ownership and path identity, excluding current website URLs on mixed containers', () => {
const docLocation = {
kind: 'workspace-doc' as const,
@@ -65,150 +65,6 @@ function browser(groupId: string, url = URL_TO_OPEN) {
}
describe('browser link placement and exact page activation', () => {
it.each([
['a', 'b'],
['b', 'a']
])('opens from %s in %s without adding a third group', async (sourceGroupId, targetGroupId) => {
store.setState((state) => ({
groupsByWorktree: {
[WORKSPACE]: state.groupsByWorktree[WORKSPACE]!.filter((group) => group.id !== 'c')
},
unifiedTabsByWorktree: {
[WORKSPACE]: state.unifiedTabsByWorktree[WORKSPACE]!.filter((tab) => tab.groupId !== 'c')
},
layoutByWorktree: {
[WORKSPACE]: {
type: 'split',
direction: 'horizontal',
first: { type: 'leaf', groupId: 'a' },
second: { type: 'leaf', groupId: 'b' }
}
},
activeGroupIdByWorktree: { [WORKSPACE]: sourceGroupId }
}))
const before = store.getState()
const sourceTabId = sourceGroupId === 'a' ? 'source' : 'terminal-b'
const result = await open({
linkIntent: captureLinkOpenIntent(before, WORKSPACE, sourceTabId),
isCurrentRequest: beginLinkOpenRequest()
})
expect(result).toMatchObject({ status: 'opened', result: { groupId: targetGroupId } })
const state = store.getState()
expect(state.groupsByWorktree[WORKSPACE]).toHaveLength(2)
expect(state.layoutByWorktree[WORKSPACE]).toEqual(before.layoutByWorktree[WORKSPACE])
expect(
state.groupsByWorktree[WORKSPACE]?.find((g) => g.id === sourceGroupId)?.activeTabId
).toBe(sourceTabId)
expect(
state.groupsByWorktree[WORKSPACE]?.find((g) => g.id === targetGroupId)?.tabOrder
).toContain(targetGroupId === 'a' ? 'source' : 'terminal-b')
})
it('creates a second group for a new URL when the source is alone', async () => {
store.setState((state) => ({
groupsByWorktree: { [WORKSPACE]: [state.groupsByWorktree[WORKSPACE]![0]!] },
unifiedTabsByWorktree: {
[WORKSPACE]: state.unifiedTabsByWorktree[WORKSPACE]!.filter((tab) => tab.groupId === 'a')
},
layoutByWorktree: { [WORKSPACE]: { type: 'leaf', groupId: 'a' } },
activeGroupIdByWorktree: { [WORKSPACE]: 'a' }
}))
expect(await open()).toMatchObject({ status: 'opened' })
expect(store.getState().groupsByWorktree[WORKSPACE]).toHaveLength(2)
expect(
store.getState().groupsByWorktree[WORKSPACE]?.find((g) => g.id === 'a')?.activeTabId
).toBe('source')
})
it('opens beside the source despite focus in a nonadjacent group, returning the real identities', async () => {
const result = await open()
expect(result.status).toBe('opened')
if (result.status !== 'opened') {
throw new Error('Expected open')
}
const state = store.getState()
expect(result.result.groupId).toBe('b')
expect(state.groupsByWorktree[WORKSPACE]?.find((group) => group.id === 'a')?.activeTabId).toBe(
'source'
)
expect(
state.groupsByWorktree[WORKSPACE]?.find((group) => group.id === result.result.groupId)
?.activeTabId
).toBe(result.result.unifiedTabId)
expect(state.browserPagesByWorkspace[result.result.browserWorkspaceId]?.[0]?.id).toBe(
result.result.browserPageId
)
expect(state.pendingAddressBarFocusByPageId).toEqual({})
expect(requestBrowserFocus).toHaveBeenLastCalledWith({
pageId: result.result.browserPageId,
target: 'webview'
})
expect(state.groupsByWorktree[WORKSPACE]).toHaveLength(3)
expect(state.layoutByWorktree[WORKSPACE]).toMatchObject({
first: { type: 'leaf', groupId: 'a' },
second: { first: { type: 'leaf', groupId: 'b' } }
})
expect(
state.groupsByWorktree[WORKSPACE]?.find((group) => group.id === 'b')?.tabOrder
).toContain('terminal-b')
})
it('uses a distant visible browser and preserves the neighboring terminal', async () => {
store.getState().createBrowserTab(WORKSPACE, 'https://unrelated.example/', {
targetGroupId: 'c',
executionHostId: 'local',
browserRuntimeEnvironmentId: null,
activate: true
})
expect(await open()).toMatchObject({ status: 'opened', result: { groupId: 'c' } })
expect(
store.getState().groupsByWorktree[WORKSPACE]?.find((group) => group.id === 'a')?.activeTabId
).toBe('source')
})
it('prefers the focused visible browser, otherwise the most recently focused one', async () => {
const left = browser('b', 'https://left.example/')
const right = browser('c', 'https://right.example/')
store.setState((state) => ({
unifiedTabsByWorktree: {
[WORKSPACE]: state.unifiedTabsByWorktree[WORKSPACE]!.map((tab) => ({
...tab,
lastFocusedAt: tab.entityId === left.id ? 200 : 100
}))
},
groupsByWorktree: {
[WORKSPACE]: state.groupsByWorktree[WORKSPACE]!.map((group) => ({
...group,
activeTabId:
state.unifiedTabsByWorktree[WORKSPACE]!.find(
(tab) =>
tab.entityId === (group.id === 'b' ? left.id : right.id) && tab.groupId === group.id
)?.id ?? group.activeTabId
}))
},
activeGroupIdByWorktree: { [WORKSPACE]: 'c' }
}))
expect(await open()).toMatchObject({ status: 'opened', result: { groupId: 'c' } })
store.getState().focusGroup(WORKSPACE, 'a')
const destination = browserLinkTestDestination({
resource: { kind: 'website', url: 'https://another.example/' }
})
expect(
selectBrowserLinkDestination(store.getState(), destination, context().linkIntent)
).toMatchObject({ groupId: 'c' })
store.setState((state) => ({
unifiedTabsByWorktree: {
[WORKSPACE]: state.unifiedTabsByWorktree[WORKSPACE]!.map((tab) => ({
...tab,
lastFocusedAt: tab.entityId === left.id ? 1000 : 100
}))
}
}))
expect(
selectBrowserLinkDestination(store.getState(), destination, context().linkIntent)
).toMatchObject({ groupId: 'b' })
})
it('prefers an old inactive nonadjacent match over a nearby browser without navigation or creation', async () => {
store.getState().createBrowserTab(WORKSPACE, 'https://nearby.example/', {
targetGroupId: 'b',
@@ -276,17 +132,6 @@ describe('browser link placement and exact page activation', () => {
expect(store.getState().activeGroupIdByWorktree[WORKSPACE]).toBe('c')
})
it('reuses an exact match in the source group without creating another page', async () => {
const existing = browser('a')
const result = await open()
expect(result).toMatchObject({ status: 'opened', result: { groupId: 'a' } })
if (result.status !== 'opened') {
throw new Error('Expected open')
}
expect(result.result.browserPageId).toBe(existing.activePageId)
expect(store.getState().browserPagesByWorkspace[existing.id]?.[0]?.url).toBe(URL_TO_OPEN)
})
it('removes a newly created tab after an activation error while preserving existing tabs', async () => {
const existing = browser('c', 'https://unrelated.example/')
const before = store.getState()
@@ -308,49 +153,6 @@ describe('browser link placement and exact page activation', () => {
expect(state.recentlyClosedBrowserTabsByWorktree[WORKSPACE] ?? []).toEqual([])
})
it('retains a successfully created background tab without activating it', async () => {
const outcome = await openLocalBrowserLink({
destination: browserLinkTestDestination(),
context: context(),
url: URL_TO_OPEN,
options: { activate: false },
ownsRoute: () => true
})
expect(outcome.status).toBe('opened')
if (outcome.status !== 'opened') {
throw new Error('Expected open')
}
const state = store.getState()
expect(state.browserTabsByWorktree[WORKSPACE]?.map((tab) => tab.id)).toEqual([
outcome.result.browserWorkspaceId
])
expect(state.browserPagesByWorkspace[outcome.result.browserWorkspaceId]?.[0]?.url).toBe(
URL_TO_OPEN
)
expect(state.activeGroupIdByWorktree[WORKSPACE]).toBe('c')
})
it('uses the selected matching wrapper, then active page, instead of recency', async () => {
browser('b')
const selected = browser('b')
const second = store.getState().createBrowserPage(selected.id, URL_TO_OPEN, {
activate: true,
browserRuntimeEnvironmentId: null
})
const wrapper = store
.getState()
.unifiedTabsByWorktree[WORKSPACE]?.find((tab) => tab.entityId === selected.id)
if (!wrapper || !second) {
throw new Error('Missing fixture')
}
store.getState().activateTab(wrapper.id, { worktreeId: WORKSPACE })
expect(await open()).toMatchObject({
status: 'opened',
result: { unifiedTabId: wrapper.id, browserPageId: second.id }
})
})
it('follows source movement while an explicit request stays pinned', async () => {
const automatic = context()
store.setState({
@@ -439,43 +241,9 @@ describe('browser link placement and exact page activation', () => {
.unifiedTabsByWorktree[WORKSPACE]?.some((tab) => tab.contentType === 'browser')
).toBe(false)
})
it('reuses in one group and does not create a split', async () => {
store.setState({
groupsByWorktree: { [WORKSPACE]: [store.getState().groupsByWorktree[WORKSPACE]![0]!] },
layoutByWorktree: { [WORKSPACE]: { type: 'leaf', groupId: 'a' } }
})
const existing = browser('a')
expect(await open()).toMatchObject({
status: 'opened',
result: { browserWorkspaceId: existing.id, groupId: 'a' }
})
expect(store.getState().groupsByWorktree[WORKSPACE]).toHaveLength(1)
})
it('supersedes a pending operation before creation', async () => {
const captured = context()
beginLinkOpenRequest()
expect(await open(captured)).toEqual({ status: 'superseded' })
expect(store.getState().browserPagesByWorkspace).toEqual({})
})
})
describe('existing open routes with link intent', () => {
it('opens local HTTP through the managed route and reports the exact destination', async () => {
expect(
await openWorkspaceBrowserTabForLink({
workspaceId: WORKSPACE,
url: URL_TO_OPEN,
intent: { kind: 'url' },
linkContext: context()
})
).toMatchObject({
status: 'opened',
result: { worktreeId: WORKSPACE, executionHostId: 'local' }
})
})
it('refuses a source owner that changed while HTTP routing was pending', async () => {
const captured = context()
const host = 'ssh:changed-owner'
@@ -1,82 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
advanceAcrossBrowserPageConversion,
returnAcrossBrowserPageConversion
} from './browser-page-conversion-history'
const mocks = vi.hoisted(() => ({
convertBrowserPage: vi.fn(),
convertBrowserPageToWorkspaceDoc: vi.fn()
}))
vi.mock('@/store', () => ({
useAppStore: { getState: () => ({ convertBrowserPage: mocks.convertBrowserPage }) }
}))
vi.mock('@/lib/file-preview', () => ({
convertBrowserPageToWorkspaceDoc: mocks.convertBrowserPageToWorkspaceDoc
}))
beforeEach(() => {
vi.clearAllMocks()
})
// Carry ownership back or Back would move remote browsing onto this desktop.
describe('returnAcrossBrowserPageConversion', () => {
it('passes the recorded runtime ownership through to the rebuilt web page', () => {
returnAcrossBrowserPageConversion('page-1', {
kind: 'url',
url: 'https://remote.example/',
browserRuntimeEnvironmentId: 'env-1'
})
expect(mocks.convertBrowserPage).toHaveBeenCalledWith(
'page-1',
{ kind: 'web', url: 'https://remote.example/', browserRuntimeEnvironmentId: 'env-1' },
{ leg: 'history-return' }
)
})
it('says inferred explicitly when the origin recorded no ownership', () => {
returnAcrossBrowserPageConversion('page-1', { kind: 'url', url: 'https://remote.example/' })
const target = mocks.convertBrowserPage.mock.calls[0]?.[1] as Record<string, unknown>
// Property present with undefined — the plan's "worktree-inferred", never client-local.
expect('browserRuntimeEnvironmentId' in target).toBe(true)
expect(target.browserRuntimeEnvironmentId).toBeUndefined()
})
it('routes a workspace-doc origin through the doc door as a return leg', () => {
const docLocation = { kind: 'workspace-doc' as const, worktreeId: 'wt-1', filePath: '/a.html' }
returnAcrossBrowserPageConversion('page-1', { kind: 'workspace-doc', docLocation })
expect(mocks.convertBrowserPageToWorkspaceDoc).toHaveBeenCalledWith('page-1', docLocation, {
leg: 'history-return'
})
expect(mocks.convertBrowserPage).not.toHaveBeenCalled()
})
})
describe('advanceAcrossBrowserPageConversion', () => {
it('re-crosses to a web target with ownership intact, as an advance leg', () => {
advanceAcrossBrowserPageConversion('page-1', {
kind: 'url',
url: 'https://remote.example/',
browserRuntimeEnvironmentId: 'env-1'
})
expect(mocks.convertBrowserPage).toHaveBeenCalledWith(
'page-1',
{ kind: 'web', url: 'https://remote.example/', browserRuntimeEnvironmentId: 'env-1' },
{ leg: 'history-advance' }
)
})
it('re-crosses to a workspace-doc target as an advance leg', () => {
const docLocation = { kind: 'workspace-doc' as const, worktreeId: 'wt-1', filePath: '/a.html' }
advanceAcrossBrowserPageConversion('page-1', { kind: 'workspace-doc', docLocation })
expect(mocks.convertBrowserPageToWorkspaceDoc).toHaveBeenCalledWith('page-1', docLocation, {
leg: 'history-advance'
})
expect(mocks.convertBrowserPage).not.toHaveBeenCalled()
})
})
@@ -149,32 +149,6 @@ describe('activateBrowserPagePaletteResult', () => {
seedStore()
})
it('activates the browser workspace and page', () => {
expect(activateBrowserPagePaletteResult(target)).toEqual({
status: 'activated',
pageId: 'page-1',
focusTarget: 'webview'
})
const state = useAppStore.getState()
expect(mocks.activateAndRevealWorkspace).toHaveBeenCalledWith('wt-1', {})
expect(state.activeBrowserTabId).toBe('ws-1')
expect(state.activeTabType).toBe('browser')
expect(state.browserTabsByWorktree['wt-1'][0].activePageId).toBe('page-1')
})
it('threads the worktree execution host into worktree activation', () => {
seedStore({
worktreesByRepo: { 'repo-1': [makeWorktree({ hostId: 'ssh:host-1' })] }
})
activateBrowserPagePaletteResult(target)
expect(mocks.activateAndRevealWorkspace).toHaveBeenCalledWith('wt-1', {
executionHostId: 'ssh:host-1'
})
})
it('activates an SSH worktree through its paired-runtime owner alias', () => {
seedStore({
worktreesByRepo: {
@@ -273,55 +247,6 @@ describe('activateBrowserPagePaletteResult', () => {
focusTarget: 'address-bar'
})
})
it('reports a missing page or workspace as a stale target', () => {
seedStore({ browserPagesByWorkspace: {} })
expect(activateBrowserPagePaletteResult(target)).toEqual({
status: 'failed',
reason: 'missing-page'
})
seedStore({ browserTabsByWorktree: {} })
expect(activateBrowserPagePaletteResult(target)).toEqual({
status: 'failed',
reason: 'missing-page'
})
expect(mocks.activateAndRevealWorkspace).not.toHaveBeenCalled()
})
// A live page in a dead workspace is a different story than a dead page.
it('reports an absent worktree as a missing workspace', () => {
seedStore({ worktreesByRepo: {} })
expect(activateBrowserPagePaletteResult(target)).toEqual({
status: 'failed',
reason: 'missing-worktree'
})
expect(mocks.activateAndRevealWorkspace).not.toHaveBeenCalled()
})
// Workspace removal also purges pages; check it first to report the actual missing owner.
it('reports a deleted worktree as a missing workspace once its pages are purged', () => {
seedStore({
worktreesByRepo: {},
browserTabsByWorktree: {},
browserPagesByWorkspace: {}
})
expect(activateBrowserPagePaletteResult(target)).toEqual({
status: 'failed',
reason: 'missing-worktree'
})
expect(mocks.activateAndRevealWorkspace).not.toHaveBeenCalled()
})
it('reports blocked activation without claiming the workspace disappeared', () => {
mocks.activateAndRevealWorkspace.mockReturnValue(false)
expect(activateBrowserPagePaletteResult(target)).toEqual({ status: 'blocked' })
expect(useAppStore.getState().activeBrowserTabId).toBeNull()
})
})
// Preserve cross-column focus through setActiveBrowserTab → activateTab.
@@ -367,12 +292,6 @@ describe('activateBrowserPagePaletteResult group focus', () => {
expect(state.groupsByWorktree['wt-1'][1].activeTabId).toBe('unified-browser-1')
})
it('leaves the focused group unchanged when the page is already in it', () => {
expect(activateBrowserPagePaletteResult(target).status).toBe('activated')
expect(useAppStore.getState().activeGroupIdByWorktree['wt-1']).toBe('group-1')
})
// Without a unified tab, success would leave the previous tab visible.
it('fails instead of activating when no unified tab backs the browser workspace', () => {
seedStore({ unifiedTabsByWorktree: {}, groupsByWorktree: { 'wt-1': [] } })
@@ -130,17 +130,6 @@ function buildFixture(
}
describe('buildSearchableBrowserPages', () => {
it('treats an unstamped legacy worktree as local when its tab is stamped', () => {
expect(
buildFixture({
worktrees: [worktreeA],
unifiedTabsByWorktree: {
'wt-1': [browserUnifiedTab('tab-local', 'ws-1', 'wt-1', 'local')]
}
}).map((entry) => entry.workspace.id)
).toEqual(['ws-1', 'ws-1', 'ws-2'])
})
it('keeps same-id browser tabs isolated by execution host', () => {
const sharedId = 'repo-shared::/workspace'
const local = makeWorktree({ id: sharedId, hostId: 'local', displayName: 'Local workspace' })
@@ -276,161 +265,6 @@ describe('buildSearchableBrowserPages', () => {
).toEqual([])
})
it('builds one entry per page across every workspace in a worktree', () => {
const entries = buildFixture()
expect(entries.map((entry) => entry.page.id)).toEqual(['page-1', 'page-2', 'page-3', 'page-4'])
expect(entries.map((entry) => entry.workspace.id)).toEqual(['ws-1', 'ws-1', 'ws-2', 'ws-3'])
expect(entries.map((entry) => entry.repoName)).toEqual([
'repo/one',
'repo/one',
'repo/one',
'repo/two'
])
expect(entries.map((entry) => entry.worktreeSortIndex)).toEqual([0, 0, 0, 1])
})
it('marks isCurrentPage only for the active page of the active browser workspace', () => {
expect(buildFixture().map((entry) => entry.isCurrentPage)).toEqual([true, false, false, false])
})
it('marks no page current when the active tab type is not browser', () => {
expect(buildFixture({ activeTabType: 'terminal' }).map((entry) => entry.isCurrentPage)).toEqual(
[false, false, false, false]
)
})
it('marks isCurrentWorktree only for the active worktree', () => {
expect(buildFixture().map((entry) => entry.isCurrentWorktree)).toEqual([
true,
true,
true,
false
])
})
it('returns an empty array for a worktree with no browser workspaces', () => {
expect(
buildSearchableBrowserPages({
worktrees: [worktreeA],
repoMap,
worktreeOrder,
browserTabsByWorktree: {},
browserPagesByWorkspace: {},
activeBrowserTabId: null,
activeWorktreeId: 'wt-1',
activeTabType: 'terminal'
})
).toEqual([])
})
it('falls back to MAX_SAFE_INTEGER sort index for worktrees outside the order map', () => {
const entries = buildSearchableBrowserPages({
worktrees: [worktreeA],
repoMap,
worktreeOrder: new Map(),
browserTabsByWorktree: { 'wt-1': [makeWorkspace()] },
browserPagesByWorkspace: { 'ws-1': [makePage()] },
activeBrowserTabId: null,
activeWorktreeId: null,
activeTabType: 'browser'
})
expect(entries[0].worktreeSortIndex).toBe(Number.MAX_SAFE_INTEGER)
})
it('reads page recency from the owning workspace unified tab, clamped to page age', () => {
function browserTab(overrides: Partial<Tab>): Tab {
return {
id: 'tab-ws-1',
entityId: 'ws-1',
groupId: 'group-1',
worktreeId: 'wt-1',
contentType: 'browser',
label: 'Example',
customLabel: null,
color: null,
sortOrder: 0,
createdAt: 0,
...overrides
}
}
const [unfocused] = buildSearchableBrowserPages({
worktrees: [worktreeA],
repoMap,
worktreeOrder,
browserTabsByWorktree: { 'wt-1': [makeWorkspace()] },
browserPagesByWorkspace: { 'ws-1': [makePage()] },
unifiedTabsByWorktree: { 'wt-1': [browserTab({})] },
activeBrowserTabId: null,
activeWorktreeId: null,
activeTabType: 'browser'
})
expect(unfocused.lastActiveAt).toBeNull()
const entries = buildSearchableBrowserPages({
worktrees: [worktreeA],
repoMap,
worktreeOrder,
browserTabsByWorktree: { 'wt-1': [makeWorkspace({ pageIds: ['page-1', 'page-2'] })] },
browserPagesByWorkspace: {
'ws-1': [makePage(), makePage({ id: 'page-2', createdAt: 9000 })]
},
unifiedTabsByWorktree: { 'wt-1': [browserTab({ lastFocusedAt: 4000 })] },
activeBrowserTabId: null,
activeWorktreeId: null,
activeTabType: 'browser'
})
expect(entries.map((entry) => entry.lastActiveAt)).toEqual([4000, 9000])
expect(entries.map((entry) => entry.lastFocusedAt)).toEqual([4000, undefined])
})
it('moves the workspace-focus proxy when the active browser page changes', () => {
const browserTab: Tab = {
id: 'tab-ws-1',
entityId: 'ws-1',
groupId: 'group-1',
worktreeId: 'wt-1',
contentType: 'browser',
label: 'Example',
customLabel: null,
color: null,
sortOrder: 0,
createdAt: 0,
lastFocusedAt: 8_000
}
const pages = [makePage({ createdAt: 1_000 }), makePage({ id: 'page-2', createdAt: 2_000 })]
const build = (activePageId: string) =>
buildSearchableBrowserPages({
worktrees: [worktreeA],
repoMap,
worktreeOrder,
browserTabsByWorktree: {
'wt-1': [makeWorkspace({ activePageId, pageIds: ['page-1', 'page-2'] })]
},
browserPagesByWorkspace: { 'ws-1': pages },
unifiedTabsByWorktree: { 'wt-1': [browserTab] },
activeBrowserTabId: null,
activeWorktreeId: null,
activeTabType: 'browser'
})
expect(build('page-1').map((entry) => entry.lastActiveAt)).toEqual([8_000, 2_000])
expect(build('page-2').map((entry) => entry.lastActiveAt)).toEqual([1_000, 8_000])
expect(build('page-1').map((entry) => entry.lastFocusedAt)).toEqual([8_000, undefined])
expect(build('page-2').map((entry) => entry.lastFocusedAt)).toEqual([undefined, 8_000])
})
it('feeds Cmd+J browser search the same ranking as the inline builder did', () => {
const results = searchBrowserPages(buildFixture(), 'docs')
// Primary title proofs lead URL-only proofs even across worktrees.
expect(results.map((result) => result.pageId)).toEqual(['page-1', 'page-4', 'page-2', 'page-3'])
expect(results[0].isCurrentPage).toBe(true)
})
// The index keeps the first tab per workspace ID; prevent duplicates from hiding a later owned tab.
it('omits a workspace claimed by more than one unified browser tab', () => {
const entries = buildFixture({
@@ -5,8 +5,6 @@ import {
BROWSER_PALETTE_QUERY_MAX_BYTES,
buildSearchableBrowserPageDocument,
searchBrowserPages,
formatBrowserPaletteUrl,
isBlankBrowserUrl,
type SearchableBrowserPage
} from './browser-palette-search'
@@ -74,372 +72,6 @@ function makeEntry(entry: Omit<SearchableBrowserPage, 'document'>): SearchableBr
}
describe('browser-palette-search', () => {
it('formats browser urls without protocol for palette display', () => {
expect(formatBrowserPaletteUrl('https://example.com/docs?q=1#hash')).toBe(
'example.com/docs?q=1#hash'
)
})
it('stamps the row execution host so activation never resolves by id alone', () => {
// IDs repeat across hosts; retain the row’s owner so activation cannot open its same-ID twin.
const [result] = searchBrowserPages(
[
makeEntry({
page: makePage({ id: 'page-1', title: 'Docs' }),
workspace: makeWorkspace({ id: 'ws-1' }),
worktree: makeWorktree({ id: 'shared', hostId: 'ssh:box' }),
repoName: 'repo/one',
worktreeSortIndex: 0,
isCurrentPage: false,
isCurrentWorktree: false
})
],
''
)
expect(result.executionHostId).toBe('ssh:box')
})
it('carries the page favicon into palette results', () => {
const faviconUrl = 'https://example.com/favicon.ico'
const [result] = searchBrowserPages(
[
makeEntry({
page: makePage({ faviconUrl }),
workspace: makeWorkspace(),
worktree: makeWorktree(),
repoName: 'repo/one',
worktreeSortIndex: 0,
isCurrentPage: false,
isCurrentWorktree: false
})
],
''
)
expect(result.faviconUrl).toBe(faviconUrl)
})
it('keeps empty-query ordering deterministic and context-first', () => {
const results = searchBrowserPages(
[
makeEntry({
page: makePage({ id: 'page-current', title: 'Current Page' }),
workspace: makeWorkspace({ id: 'ws-current', activePageId: 'page-current' }),
worktree: makeWorktree({ id: 'wt-current', displayName: 'Current WT' }),
repoName: 'repo/current',
worktreeSortIndex: 1,
isCurrentPage: true,
isCurrentWorktree: true
}),
makeEntry({
page: makePage({
id: 'page-sibling',
workspaceId: 'ws-sibling',
worktreeId: 'wt-current',
title: 'Sibling Page',
url: 'https://example.com/sibling'
}),
workspace: makeWorkspace({
id: 'ws-sibling',
worktreeId: 'wt-current',
activePageId: 'page-sibling'
}),
worktree: makeWorktree({ id: 'wt-current', displayName: 'Current WT' }),
repoName: 'repo/current',
worktreeSortIndex: 1,
isCurrentPage: false,
isCurrentWorktree: true
}),
makeEntry({
page: makePage({
id: 'page-other',
workspaceId: 'ws-other',
worktreeId: 'wt-other',
title: 'Other Page',
url: 'https://example.com/other'
}),
workspace: makeWorkspace({
id: 'ws-other',
worktreeId: 'wt-other',
activePageId: 'page-other'
}),
worktree: makeWorktree({ id: 'wt-other', displayName: 'Other WT', repoId: 'repo-2' }),
repoName: 'repo/other',
worktreeSortIndex: 2,
isCurrentPage: false,
isCurrentWorktree: false
})
],
''
)
expect(results.map((result) => result.pageId)).toEqual([
'page-current',
'page-sibling',
'page-other'
])
})
it('searches against page titles before worktree metadata', () => {
const results = searchBrowserPages(
[
makeEntry({
page: makePage({ id: 'page-1', title: 'Design Spec' }),
workspace: makeWorkspace({ id: 'ws-1' }),
worktree: makeWorktree({ id: 'wt-1', displayName: 'Unrelated' }),
repoName: 'repo/one',
worktreeSortIndex: 1,
isCurrentPage: false,
isCurrentWorktree: false
}),
makeEntry({
page: makePage({
id: 'page-2',
workspaceId: 'ws-2',
worktreeId: 'wt-2',
title: 'Home',
url: 'https://example.com/home'
}),
workspace: makeWorkspace({ id: 'ws-2', worktreeId: 'wt-2', activePageId: 'page-2' }),
worktree: makeWorktree({ id: 'wt-2', repoId: 'repo-2', displayName: 'Design Review' }),
repoName: 'repo/two',
worktreeSortIndex: 2,
isCurrentPage: false,
isCurrentWorktree: false
})
],
'design'
)
expect(results).toHaveLength(2)
expect(results[0].pageId).toBe('page-1')
expect(results[0].titleRanges).toEqual([{ start: 0, end: 6 }])
expect(results[1].worktreeRanges).toEqual([{ start: 0, end: 6 }])
})
it('matches a two-token query across the page title and the url host', () => {
const results = searchBrowserPages(
[
makeEntry({
page: makePage({
id: 'page-1',
title: 'Design Spec',
url: 'https://staging.example.com/docs'
}),
workspace: makeWorkspace({ id: 'ws-1' }),
worktree: makeWorktree({ id: 'wt-1', displayName: 'Unrelated' }),
repoName: 'repo/one',
worktreeSortIndex: 1,
isCurrentPage: false,
isCurrentWorktree: false
})
],
'design staging'
)
expect(results).toHaveLength(1)
expect(results[0].titleRanges).toEqual([{ start: 0, end: 6 }])
expect(results[0].secondaryRanges).toEqual([{ start: 0, end: 7 }])
})
it('drops a row when only one token of a multi-keyword query lands', () => {
const results = searchBrowserPages(
[
makeEntry({
page: makePage({ id: 'page-1', title: 'Design Spec' }),
workspace: makeWorkspace({ id: 'ws-1' }),
worktree: makeWorktree({ id: 'wt-1', displayName: 'Unrelated' }),
repoName: 'repo/one',
worktreeSortIndex: 1,
isCurrentPage: false,
isCurrentWorktree: false
})
],
'design zzzznonexistent'
)
expect(results).toEqual([])
})
it('matches against formatted URLs when title does not match', () => {
const results = searchBrowserPages(
[
makeEntry({
page: makePage({
id: 'page-1',
title: 'Dashboard',
url: 'https://app.example.com/settings'
}),
workspace: makeWorkspace({ id: 'ws-1' }),
worktree: makeWorktree({ id: 'wt-1' }),
repoName: 'repo/one',
worktreeSortIndex: 1,
isCurrentPage: false,
isCurrentWorktree: false
})
],
'settings'
)
expect(results).toHaveLength(1)
expect(results[0].secondaryRanges).toEqual([{ start: 16, end: 24 }])
expect(results[0].titleRanges).toEqual([])
})
it('matches against raw URL when formatted URL does not match', () => {
const results = searchBrowserPages(
[
makeEntry({
page: makePage({ id: 'page-1', title: 'Docs', url: 'https://docs.example.com/' }),
workspace: makeWorkspace({ id: 'ws-1' }),
worktree: makeWorktree({ id: 'wt-1' }),
repoName: 'repo/one',
worktreeSortIndex: 1,
isCurrentPage: false,
isCurrentWorktree: false
})
],
'https'
)
expect(results).toHaveLength(1)
expect(results[0].secondaryText).toBe('https://docs.example.com/')
expect(results[0].secondaryRanges).toEqual([{ start: 0, end: 5 }])
})
it('returns empty array when query matches nothing', () => {
const results = searchBrowserPages(
[
makeEntry({
page: makePage({ id: 'page-1', title: 'Dashboard', url: 'https://example.com' }),
workspace: makeWorkspace({ id: 'ws-1' }),
worktree: makeWorktree({ id: 'wt-1', displayName: 'Feature' }),
repoName: 'myrepo',
worktreeSortIndex: 1,
isCurrentPage: false,
isCurrentWorktree: false
})
],
'zzzznonexistent'
)
expect(results).toHaveLength(0)
})
it('formats blank URLs as New Tab', () => {
expect(formatBrowserPaletteUrl('about:blank')).toBe('New Tab')
expect(formatBrowserPaletteUrl('data:text/html,')).toBe('New Tab')
})
it('identifies blank browser URLs', () => {
expect(isBlankBrowserUrl('about:blank')).toBe(true)
expect(isBlankBrowserUrl('data:text/html,')).toBe(true)
expect(isBlankBrowserUrl('https://example.com')).toBe(false)
})
it('boosts current page and current worktree in scored results', () => {
const entries = [
makeEntry({
page: makePage({ id: 'page-other', title: 'React Docs', url: 'https://react.dev' }),
workspace: makeWorkspace({
id: 'ws-other',
worktreeId: 'wt-other',
activePageId: 'page-other'
}),
worktree: makeWorktree({ id: 'wt-other', displayName: 'Other' }),
repoName: 'repo',
worktreeSortIndex: 1,
isCurrentPage: false,
isCurrentWorktree: false
}),
makeEntry({
page: makePage({
id: 'page-current',
workspaceId: 'ws-current',
worktreeId: 'wt-current',
title: 'React Native Docs',
url: 'https://reactnative.dev'
}),
workspace: makeWorkspace({
id: 'ws-current',
worktreeId: 'wt-current',
activePageId: 'page-current'
}),
worktree: makeWorktree({ id: 'wt-current', displayName: 'Current' }),
repoName: 'repo',
worktreeSortIndex: 1,
isCurrentPage: true,
isCurrentWorktree: true
})
]
const results = searchBrowserPages(entries, 'react')
expect(results).toHaveLength(2)
expect(results[0].pageId).toBe('page-current')
expect(results[1].pageId).toBe('page-other')
})
it('breaks a rank tie between equally-matching pages by recency', () => {
// Ids are ordered so an id-only tiebreak would flip this expectation.
const entries = [
makeEntry({
page: makePage({ id: 'page-a-older', title: 'React Docs' }),
workspace: makeWorkspace({ id: 'ws-1' }),
worktree: makeWorktree(),
repoName: 'repo',
worktreeSortIndex: 0,
isCurrentPage: false,
isCurrentWorktree: false,
lastActiveAt: 1000
}),
makeEntry({
page: makePage({ id: 'page-z-newer', title: 'React Docs' }),
workspace: makeWorkspace({ id: 'ws-2' }),
worktree: makeWorktree(),
repoName: 'repo',
worktreeSortIndex: 0,
isCurrentPage: false,
isCurrentWorktree: false,
lastActiveAt: 5000
})
]
const results = searchBrowserPages(entries, 'react')
expect(results.map((result) => result.pageId)).toEqual(['page-z-newer', 'page-a-older'])
expect(results[0].lastActiveAt).toBe(5000)
})
it('matches the visible workspace label in browser search', () => {
const results = searchBrowserPages(
[
makeEntry({
page: makePage({ id: 'page-1', title: 'Docs' }),
workspace: makeWorkspace({ id: 'ws-1', label: 'Browser 7' }),
// Branch off the default: it also contains "browser" and would win the tie.
worktree: makeWorktree({
id: 'wt-1',
displayName: 'Palette Worktree',
branch: 'refs/heads/main'
}),
repoName: 'repo/one',
worktreeSortIndex: 1,
isCurrentPage: false,
isCurrentWorktree: false
})
],
'browser 7'
)
expect(results).toHaveLength(1)
// One range per keyword: the tokens are matched independently, not as a phrase.
expect(results[0].workspaceRanges).toEqual([
{ start: 0, end: 7 },
{ start: 8, end: 9 }
])
})
it('rejects a query with more unique tokens than the matcher accepts', () => {
const query = 't0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 t15 t16'
const entries = [
@@ -498,53 +130,4 @@ describe('browser-palette-search', () => {
])
expect(searchBrowserPages([entry], ' '.repeat(2049))).toEqual([])
})
it('falls back to the branch label when a cleared display name left it undefined', () => {
// Cmd+J shares worktree objects with searchWorktrees, including corrupt raw display names.
const cleared = makeWorktree({
displayName: undefined as unknown as string,
branch: 'refs/heads/feature/browser-search'
})
const entries = [
makeEntry({
page: makePage(),
workspace: makeWorkspace(),
worktree: cleared,
repoName: 'orca',
worktreeSortIndex: 0,
isCurrentPage: false,
isCurrentWorktree: false
})
]
const results = searchBrowserPages(entries, 'browser-search')
expect(results[0]).toMatchObject({
worktreeName: 'feature/browser-search',
worktreeRanges: [{ start: 'feature/'.length, end: 'feature/browser-search'.length }]
})
})
it('lists a branch-less row on the empty query without throwing', () => {
const cleared = makeWorktree({
displayName: undefined as unknown as string,
branch: undefined as unknown as string,
path: '/repos/design-review'
})
const entries = [
makeEntry({
page: makePage(),
workspace: makeWorkspace(),
worktree: cleared,
repoName: 'orca',
worktreeSortIndex: 0,
isCurrentPage: false,
isCurrentWorktree: false
})
]
expect(searchBrowserPages(entries, '')[0]).toMatchObject({
worktreeName: 'design-review',
worktreeRanges: []
})
})
})
@@ -1,115 +0,0 @@
import { describe, expect, it } from 'vitest'
import type { Tab, TabGroup } from '../../../shared/tab-types'
import type { BrowserPage, BrowserWorkspace } from '../../../shared/browser-workspace-types'
import { resolveBrowserSourceUnifiedTab } from './browser-workspace-source-resolution'
const WT = 'wt-1'
type ResolverState = Parameters<typeof resolveBrowserSourceUnifiedTab>[0]
function wrapper(id: string, workspaceId: string, groupId = 'group-1'): Tab {
return {
id,
entityId: workspaceId,
groupId,
worktreeId: WT,
contentType: 'browser',
label: 'Example',
customLabel: null,
color: null,
sortOrder: 0,
createdAt: 1
}
}
function workspace(id: string): BrowserWorkspace {
return {
id,
worktreeId: WT,
activePageId: null,
pageIds: [],
url: 'about:blank',
title: '',
loading: false,
faviconUrl: null,
canGoBack: false,
canGoForward: false,
loadError: null,
createdAt: 1
}
}
function page(id: string, workspaceId: string): BrowserPage {
return {
id,
workspaceId,
worktreeId: WT,
url: 'about:blank',
title: '',
loading: false,
faviconUrl: null,
canGoBack: false,
canGoForward: false,
loadError: null,
createdAt: 1
}
}
function makeState(overrides: {
workspaces?: string[]
pages?: Record<string, string[]>
wrappers?: Tab[]
groups?: TabGroup[]
}): ResolverState {
const workspaces = overrides.workspaces ?? ['ws-1']
const pages = overrides.pages ?? { 'ws-1': ['page-1'] }
const wrappers = overrides.wrappers ?? [wrapper('tab-1', 'ws-1')]
return {
browserTabsByWorktree: {
[WT]: workspaces.map(workspace)
},
browserPagesByWorkspace: Object.fromEntries(
Object.entries(pages).map(([workspaceId, ids]) => [
workspaceId,
ids.map((id) => page(id, workspaceId))
])
),
unifiedTabsByWorktree: { [WT]: wrappers },
groupsByWorktree: {
[WT]: overrides.groups ?? [
{ id: 'group-1', worktreeId: WT, activeTabId: null, tabOrder: wrappers.map((t) => t.id) }
]
}
}
}
describe('resolveBrowserSourceUnifiedTab', () => {
it('returns the unique live wrapper for a source page', () => {
expect(resolveBrowserSourceUnifiedTab(makeState({}), 'page-1', WT)?.id).toBe('tab-1')
})
it('stays inside the caller workspace', () => {
expect(resolveBrowserSourceUnifiedTab(makeState({}), 'page-1', 'wt-other')).toBeUndefined()
})
it('omits the anchor for ambiguous wrappers', () => {
const state = makeState({ wrappers: [wrapper('tab-1', 'ws-1'), wrapper('tab-2', 'ws-1')] })
expect(resolveBrowserSourceUnifiedTab(state, 'page-1', WT)).toBeUndefined()
})
it('omits the anchor when two workspaces claim the page', () => {
const state = makeState({
workspaces: ['ws-1', 'ws-2'],
pages: { 'ws-1': ['page-1'], 'ws-2': ['page-1'] }
})
expect(resolveBrowserSourceUnifiedTab(state, 'page-1', WT)).toBeUndefined()
})
it('omits the anchor for a closed page or a wrapper outside its group order', () => {
expect(resolveBrowserSourceUnifiedTab(makeState({}), 'closed-page', WT)).toBeUndefined()
const detached = makeState({
groups: [{ id: 'group-1', worktreeId: WT, activeTabId: null, tabOrder: [] }]
})
expect(resolveBrowserSourceUnifiedTab(detached, 'page-1', WT)).toBeUndefined()
})
})
@@ -1,38 +1,7 @@
import { describe, expect, it } from 'vitest'
import type { ProjectExecutionRuntimeResolution } from '../../../shared/project-execution-runtime'
import {
bundledSkillTargetKey,
resolveBundledSkillTarget,
servingExecutionHostId
} from './bundled-skill-target'
const WSL_PROJECT: ProjectExecutionRuntimeResolution = {
status: 'resolved',
runtime: {
kind: 'wsl',
hostPlatform: 'wsl',
projectId: 'p',
distro: 'Ubuntu',
reason: 'project-override',
cacheKey: 'p:wsl:Ubuntu'
}
}
import { resolveBundledSkillTarget, servingExecutionHostId } from './bundled-skill-target'
describe('resolveBundledSkillTarget', () => {
it('sends a paired server or SSH host to that host, whatever the local project says', () => {
const target = resolveBundledSkillTarget(
{ kind: 'environment', environmentId: 'env-1' },
WSL_PROJECT
)
expect(target).toEqual({ kind: 'environment', environmentId: 'env-1' })
expect(bundledSkillTargetKey(target!)).toBe('runtime:env-1')
})
it('plans nothing for a Windows project that runs in WSL', () => {
const target = resolveBundledSkillTarget({ kind: 'local' }, WSL_PROJECT)
expect(target).toBeNull()
})
it('plans nothing while a WSL project needs its distro repaired, never the Windows home', () => {
expect(
resolveBundledSkillTarget(
@@ -85,12 +54,4 @@ describe('servingExecutionHostId', () => {
environmentId: 'box-server'
})
})
it('keeps an SSH host no server serves as SSH, which never plans this machine', () => {
const hostId = servingExecutionHostId('ssh:other', environments)
expect(hostId).toBe('ssh:other')
const target = resolveBundledSkillTarget({ kind: 'local' }, undefined, hostId)
expect(target).toEqual({ kind: 'ssh', targetId: 'other' })
expect(bundledSkillTargetKey(target!)).toBe('ssh:other')
})
})
@@ -55,32 +55,6 @@ describe('check details log ingress normalization', () => {
expect(normalizeCheckRunDetailsLogTails(result)).toEqual(result)
})
it.each([null, checkLogDetails([]), checkLogDetails([checkLogJob(null)])])(
'retains null/no-string responses (%#)',
(raw) => {
const sanitize = vi.spyOn(sanitation, 'sanitizeCheckJobLogExcerpt')
expect(normalizeCheckRunDetailsLogTails(raw)).toBe(raw)
expect(sanitize).not.toHaveBeenCalled()
}
)
it('replaces string-bearing objects even when the owned returned text is equal or empty', () => {
const own = vi.spyOn(ownership, 'ownRetainedString')
const raw = checkLogDetails([
checkLogJob('plain unchanged text'),
checkLogJob('', 2),
{ ...checkLogJob('\x00\x01', 3), logState: 'available' }
])
const result = normalizeCheckRunDetailsLogTails(raw)
if (!result) {
throw new Error('Expected details')
}
expect(result.jobs.map((job) => job.logTail)).toEqual(['plain unchanged text', '', ''])
expect(result.jobs[2].logState).toBe('available')
result.jobs.forEach((job, i) => expect(job).not.toBe(raw.jobs[i]))
expect(own.mock.calls).toEqual([['plain unchanged text'], [''], ['']])
})
it('uses the real no-Buffer owned copier and does not split multibyte boundaries', () => {
vi.stubGlobal('Buffer', undefined)
ownership.resetOwnRetainedStringCopier()
@@ -78,11 +78,6 @@ const SWITCH = {
before: { state: ROSTER, accountId: 'account-a' },
after: { state: ROSTER, accountId: 'account-b' }
}
const SAME_FOLDER = {
...SWITCH,
before: { state: ROSTER, accountId: null },
after: { state: ROSTER, accountId: null }
}
describe('handleRunningClaudeSessionsAfterSwitch', () => {
beforeEach(() => {
@@ -94,16 +89,6 @@ describe('handleRunningClaudeSessionsAfterSwitch', () => {
)
})
it('asks nothing when no Claude session runs on the outgoing account', async () => {
vi.mocked(findClaudeSwitchSessions).mockResolvedValue(scan([]))
expect(await handleRunningClaudeSessionsAfterSwitch(SWITCH)).toBe('show-confirmation')
expect(getClaudeAccountSwitchPrompt()).toBeNull()
expect(findClaudeSwitchSessions).toHaveBeenCalledWith(
expect.objectContaining({ fromAccountId: 'account-a' })
)
})
it('keeps earlier queued restarts except those already on the new account', async () => {
vi.mocked(findClaudeSwitchSessions).mockResolvedValue(scan([]))
vi.mocked(findQueuedClaudeRestartsOnAccount).mockReturnValueOnce(['pty-home'])
@@ -135,20 +120,6 @@ describe('handleRunningClaudeSessionsAfterSwitch', () => {
expect(findQueuedClaudeRestartsOnAccount).toHaveBeenCalledTimes(1)
})
it('restarts on the new account when the user picks Restart', async () => {
vi.mocked(findClaudeSwitchSessions).mockResolvedValue(scan([candidate(1), candidate(2)]))
expect(await handleRunningClaudeSessionsAfterSwitch(SWITCH)).toBe('handled')
expect(
getClaudeAccountSwitchPrompt()?.sessions.onAccount.map((session) => session.ptyId)
).toEqual(['pty-1', 'pty-2'])
answerClaudeAccountSwitchPrompt('restart')
expect(store.queuePaneAccountRestarts).toHaveBeenLastCalledWith(['pty-1', 'pty-2'], 'claude')
expect(getClaudeAccountSwitchPrompt()).toBeNull()
expect(store.updateSettings).not.toHaveBeenCalled()
})
it('restarts only the ticked sessions, and only ones this question listed', async () => {
vi.mocked(findClaudeSwitchSessions).mockResolvedValue(scan([candidate(1), candidate(2)]))
await handleRunningClaudeSessionsAfterSwitch(SWITCH)
@@ -168,49 +139,6 @@ describe('handleRunningClaudeSessionsAfterSwitch', () => {
expect(store.queuePaneAccountRestarts).toHaveBeenLastCalledWith(['pty-1-reattached'], 'claude')
})
it('asks nothing when launches run on the same folder before and after the switch', async () => {
vi.mocked(findClaudeSwitchSessions).mockResolvedValue(scan([candidate(1)]))
expect(await handleRunningClaudeSessionsAfterSwitch(SAME_FOLDER)).toBe('show-confirmation')
expect(findClaudeSwitchSessions).not.toHaveBeenCalled()
})
it('leaves sessions alone when the user keeps them', async () => {
vi.mocked(findClaudeSwitchSessions).mockResolvedValue(scan([candidate(1)]))
await handleRunningClaudeSessionsAfterSwitch(SWITCH)
answerClaudeAccountSwitchPrompt('keep')
expect(store.queuePaneAccountRestarts).not.toHaveBeenCalled()
expect(getClaudeAccountSwitchPrompt()).toBeNull()
})
it.each(['restart', 'keep'] as const)(
'turns on automatic restarts on %s when told not to ask again',
async (answer) => {
vi.mocked(findClaudeSwitchSessions).mockResolvedValue(scan([candidate(1)]))
await handleRunningClaudeSessionsAfterSwitch(SWITCH)
answerClaudeAccountSwitchPrompt(answer, { dontAskAgain: true })
expect(store.updateSettings).toHaveBeenCalledWith({
claudeAccountSwitchRunningSessions: 'restart'
})
}
)
it('restarts without asking when the setting is Always restart', async () => {
store.settings = { claudeAccountSwitchRunningSessions: 'restart' }
vi.mocked(findClaudeSwitchSessions).mockResolvedValue(scan([candidate(1), candidate(2)]))
expect(await handleRunningClaudeSessionsAfterSwitch(SWITCH)).toBe('handled')
expect(getClaudeAccountSwitchPrompt()).toBeNull()
expect(store.queuePaneAccountRestarts).toHaveBeenLastCalledWith(['pty-1', 'pty-2'], 'claude')
expect(toastSuccess).toHaveBeenCalledWith(
'Restarting 2 Claude sessions on account-b@example.com'
)
})
it('names sessions an automatic restart left behind, and reviews only those', async () => {
store.settings = { claudeAccountSwitchRunningSessions: 'restart' }
vi.mocked(findClaudeSwitchSessions).mockResolvedValue(
@@ -13,12 +13,10 @@ import {
inspectRuntimeTerminalProcess
} from '@/runtime/runtime-terminal-inspection'
import {
claudeLaunchAccountId,
findClaudeSwitchSessions,
findQueuedClaudeRestartsOnAccount,
isClaudePaneMidTurn
findQueuedClaudeRestartsOnAccount
} from './claude-session-account-move'
import { resolveClaudePaneFolder, type ClaudeHostFolders } from './claude-pane-account-folder'
import type { ClaudeHostFolders } from './claude-pane-account-folder'
const laneByPtyId: Record<string, string> = {}
let projectRuntime: ProjectExecutionRuntimeResolution | undefined
@@ -165,17 +163,6 @@ describe('findMovableClaudeSessions', () => {
vi.mocked(confirmRuntimeTerminalForegroundProcess).mockResolvedValue('claude')
})
it('finds resumable Claude panes and skips other agents and session-less panes', async () => {
seedPanes([
{ n: 1, agentType: 'claude' },
{ n: 2, agentType: 'codex' },
{ n: 3, agentType: 'claude', sessionId: null },
{ n: 4 }
])
expect(await movable()).toEqual([ptyId(1)])
})
it('counts only sessions running on the outgoing account', async () => {
seedPanes([
{ n: 1, agentType: 'claude', configDir: FOLDER_A },
@@ -216,17 +203,6 @@ describe('findMovableClaudeSessions', () => {
expect(await movable()).toEqual([])
})
it('skips a pane where Claude has exited to the shell', async () => {
seedPanes([{ n: 1, agentType: 'claude' }])
vi.mocked(inspectRuntimeTerminalProcess).mockResolvedValue({
foregroundProcess: 'zsh',
hasChildProcesses: false
})
vi.mocked(confirmRuntimeTerminalForegroundProcess).mockResolvedValue('zsh')
expect(await movable()).toEqual([])
})
it('leaves a node or python program the user started in a pane Claude has left', async () => {
seedPanes([
{ n: 1, agentType: 'claude' },
@@ -327,48 +303,6 @@ describe('findMovableClaudeSessions', () => {
})
})
describe('resolveClaudePaneFolder', () => {
const accounts = [account('acct-a', FOLDER_A)]
it('reads the account from the transcript folder, with System default as the rest', () => {
expect(resolveClaudePaneFolder(`${FOLDER_A}/projects/-repo/s.jsonl`, accounts)).toEqual({
kind: 'account',
accountId: 'acct-a'
})
expect(resolveClaudePaneFolder(`${SYSTEM_FOLDER}/projects/-repo/s.jsonl`, accounts)).toEqual({
kind: 'system-default'
})
expect(resolveClaudePaneFolder(undefined, accounts)).toBeUndefined()
})
it('matches Windows paths whatever their separators', () => {
const windowsAccounts = [account('acct-w', 'C:\\Users\\dev\\claude\\acct-w')]
expect(
resolveClaudePaneFolder(
'C:\\Users\\dev\\claude\\acct-w\\projects\\-repo\\s.jsonl',
windowsAccounts
)
).toEqual({ kind: 'account', accountId: 'acct-w' })
})
})
describe('isClaudePaneMidTurn', () => {
it('waits on a reply, a permission prompt or a question, not on an idle session', () => {
seedPanes([
{ n: 1, agentType: 'claude', state: 'working' },
{ n: 2, agentType: 'claude', state: 'blocked' },
{ n: 3, agentType: 'claude', state: 'waiting' },
{ n: 4, agentType: 'claude', state: 'done' }
])
const state = useAppStore.getState()
const midTurn = [1, 2, 3, 4].map((n) =>
isClaudePaneMidTurn(state, makePaneKey(tabId(n), leafId(n)))
)
expect(midTurn).toEqual([true, true, true, false])
})
})
describe('findQueuedClaudeRestartsOnAccount', () => {
it('lists queued Claude restarts in the lane whose session already runs on the account', () => {
seedPanes([
@@ -473,14 +407,3 @@ describe('a switch away from the login System default holds', () => {
expect(await movable('acct-a', HOST, hostFolders())).toEqual([ptyId(1)])
})
})
describe('claudeLaunchAccountId', () => {
it('reads an account System default covers as System default, where its sessions run', () => {
const covered = { ...account('acct-a', FOLDER_A), runsOnSystemDefault: true as const }
const own = account('acct-b', FOLDER_B)
expect(claudeLaunchAccountId({ accounts: [covered, own] }, 'acct-a')).toBeNull()
expect(claudeLaunchAccountId({ accounts: [covered, own] }, 'acct-b')).toBe('acct-b')
expect(claudeLaunchAccountId({ accounts: [covered, own] }, null)).toBeNull()
})
})
@@ -1,10 +0,0 @@
import { expect, it } from 'vitest'
import { cliConversationExitGuidance } from './cli-conversation-exit-guidance'
// Once the agent quits, an older host drops the pane's row and its Continue button, so the
// refusal must point to the Session History route that still opens the conversation.
it('sends a refusal in this terminal to Session History', () => {
expect(cliConversationExitGuidance('claude', true)).toBe(
'Claude is still running this conversation in this terminal. Quit Claude here, then open this conversation from Session History.'
)
})
@@ -1,17 +1,14 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createBrowserLinkTestStore, WORKSPACE } from './browser-link-open.test-fixture'
import { FLOATING_TERMINAL_WORKTREE_ID, getDefaultSettings } from '../../../shared/constants'
import { getDefaultSettings } from '../../../shared/constants'
import type { RuntimeStatus } from '../../../shared/runtime-types'
import { toRuntimeExecutionHostId, toSshExecutionHostId } from '../../../shared/execution-host'
import { folderWorkspaceKey } from '../../../shared/workspace-scope'
import { BROWSER_CLIENT_HOSTING_RUNTIME_CAPABILITIES } from '../../../shared/browser-client-hosting-eligibility'
import type { PublicKnownRuntimeEnvironment } from '../../../shared/runtime-environments'
import { BROWSER_SCREENCAST_RUNTIME_CAPABILITY } from '../../../shared/protocol-version'
import {
FLOATING_BROWSER_UNAVAILABLE_MESSAGE,
LOCAL_BROWSER_UNAVAILABLE_MESSAGE,
MANAGED_BROWSER_UNAVAILABLE_MESSAGE,
MOBILE_EMULATOR_UNAVAILABLE_MESSAGE,
assertClientCreationActionAvailable,
assertManagedBrowserMaterializationAllowed,
assertRuntimeManagedBrowserCreationAvailable,
@@ -37,96 +34,6 @@ function runtimeStatus(capabilities?: string[]): RuntimeStatus {
afterEach(() => vi.unstubAllGlobals())
describe('resolveClientCreationActionPolicy', () => {
it('preserves Electron creation behavior without negotiated runtime signals', () => {
expect(resolveClientCreationActionPolicy({ surface: 'electron', runtimeStatus: null })).toEqual(
{
'managed-browser': { state: 'enabled', provider: 'local-client' },
'mobile-emulator': { state: 'enabled', provider: 'local-client' }
}
)
})
it('selects the provider that the browser action path can actually consume', () => {
const desktopHost = runtimeStatus(['browser.screencast.v1'])
const npmHostWithoutDisplay = { ...runtimeStatus(), hostPlatform: 'linux' as const }
const npmHostWithDisplay = {
...runtimeStatus(['browser.screencast.v1', 'browser.headless.v1']),
hostPlatform: 'linux' as const
}
expect(
resolveClientCreationActionPolicy({ surface: 'electron', runtimeStatus: desktopHost })
).toEqual({
'managed-browser': { state: 'enabled', provider: 'paired-runtime' },
'mobile-emulator': { state: 'enabled', provider: 'local-client' }
})
expect(
resolveClientCreationActionPolicy({
surface: 'electron',
runtimeStatus: npmHostWithoutDisplay
})
).toEqual({
'managed-browser': { state: 'enabled', provider: 'local-client' },
'mobile-emulator': { state: 'enabled', provider: 'local-client' }
})
expect(
resolveClientCreationActionPolicy({ surface: 'paired-web', runtimeStatus: desktopHost })
).toEqual({
'managed-browser': { state: 'enabled', provider: 'paired-runtime' },
'mobile-emulator': { state: 'hidden', reason: MOBILE_EMULATOR_UNAVAILABLE_MESSAGE }
})
expect(
resolveClientCreationActionPolicy({
surface: 'paired-web',
runtimeStatus: npmHostWithoutDisplay
})
).toEqual({
'managed-browser': { state: 'hidden', reason: MANAGED_BROWSER_UNAVAILABLE_MESSAGE },
'mobile-emulator': { state: 'hidden', reason: MOBILE_EMULATOR_UNAVAILABLE_MESSAGE }
})
expect(
resolveClientCreationActionPolicy({
surface: 'paired-web',
runtimeStatus: npmHostWithDisplay
})['managed-browser']
).toEqual({ state: 'enabled', provider: 'paired-runtime' })
})
it('enables paired-web browser creation only with negotiated streaming support', () => {
expect(
resolveClientCreationActionPolicy({
surface: 'paired-web',
runtimeStatus: runtimeStatus(['browser.screencast.v1'])
})['managed-browser']
).toEqual({ state: 'enabled', provider: 'paired-runtime' })
expect(
resolveClientCreationActionPolicy({
surface: 'paired-web',
runtimeStatus: runtimeStatus()
})['managed-browser']
).toEqual({ state: 'hidden', reason: MANAGED_BROWSER_UNAVAILABLE_MESSAGE })
})
it('hides web-client floating browsers and mobile emulators as impossible surfaces', () => {
const policy = resolveClientCreationActionPolicy({
surface: 'paired-web',
runtimeStatus: runtimeStatus(['browser.screencast.v1', 'mobile.tasks.v1']),
floatingWorkspace: true
})
expect(policy['managed-browser']).toEqual({
state: 'hidden',
reason: FLOATING_BROWSER_UNAVAILABLE_MESSAGE
})
expect(policy['mobile-emulator']).toEqual({
state: 'hidden',
reason: MOBILE_EMULATOR_UNAVAILABLE_MESSAGE
})
})
})
describe('client creation action guards', () => {
it.each([false, true])('does not read server focus for a workspace policy (web: %s)', (web) => {
vi.stubGlobal('__ORCA_WEB_CLIENT__', web)
@@ -168,34 +75,6 @@ describe('client creation action guards', () => {
)
})
it('permits host-confirmed remote browser materialization in paired web', () => {
vi.stubGlobal('__ORCA_WEB_CLIENT__', true)
const state = {
runtimeStatusByEnvironmentId: new Map([
['runtime-1', { status: runtimeStatus(['browser.screencast.v1']), checkedAt: 1 }]
])
}
expect(() =>
assertManagedBrowserMaterializationAllowed(state as never, 'runtime-1')
).not.toThrow()
})
it('treats the floating workspace as local even with an active capable runtime', () => {
vi.stubGlobal('__ORCA_WEB_CLIENT__', true)
const state = {
settings: { activeRuntimeEnvironmentId: 'runtime-1' },
runtimeStatusByEnvironmentId: new Map([
['runtime-1', { status: runtimeStatus(['browser.screencast.v1']), checkedAt: 1 }]
])
}
expect(
getClientCreationActionPolicy(state as never, FLOATING_TERMINAL_WORKTREE_ID)[
'managed-browser'
]
).toEqual({ state: 'hidden', reason: FLOATING_BROWSER_UNAVAILABLE_MESSAGE })
})
it('uses folder and SSH workspace ownership instead of the focused runtime', () => {
vi.stubGlobal('__ORCA_WEB_CLIENT__', true)
const capableStatus = runtimeStatus(['browser.screencast.v1'])
@@ -286,25 +165,4 @@ describe('a runtime whose pages this desktop hosts', () => {
expect(runtimeHostsPagesOnThisDesktop(state, 'managed-ssh')).toBe(false)
expect(() => assertRuntimeManagedBrowserCreationAvailable(state, 'managed-ssh')).toThrow()
})
it('leaves pages on a streaming server when the setting asks for server rendering', () => {
const streaming = hostingState(
{ id: 'electron-server' },
{ browserClientHostedRemoteEnabled: false },
[BROWSER_SCREENCAST_RUNTIME_CAPABILITY]
)
expect(runtimeHostsPagesOnThisDesktop(streaming, 'electron-server')).toBe(false)
})
it('hosts through the server it cannot stream from even when the setting asks for server rendering', () => {
const state = hostingState({ id: 'orcad-server' }, { browserClientHostedRemoteEnabled: false })
expect(runtimeHostsPagesOnThisDesktop(state, 'orcad-server')).toBe(true)
})
it('stays off in the web client', () => {
vi.stubGlobal('__ORCA_WEB_CLIENT__', true)
expect(
runtimeHostsPagesOnThisDesktop(hostingState({ id: 'orcad-server' }), 'orcad-server')
).toBe(false)
})
})
@@ -1,42 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { formatClipboardFilePath } from './clipboard-file-path'
import { resetRendererAppPlatformCacheForTests } from './renderer-app-platform'
import type { LocalPathOpenOwner } from './local-path-open-guard'
function stubClient(platform: NodeJS.Platform, webClient = false): void {
resetRendererAppPlatformCacheForTests()
vi.stubGlobal('window', {
api: { platform: { get: () => ({ platform }) } },
...(webClient ? { __ORCA_WEB_CLIENT__: true } : {})
})
}
afterEach(() => {
vi.unstubAllGlobals()
resetRendererAppPlatformCacheForTests()
})
describe('formatClipboardFilePath', () => {
it.each([
['C:/repo/a space/é.txt', 'C:\\repo\\a space\\é.txt'],
['D:\\repo/mixed\\b.txt', 'D:\\repo\\mixed\\b.txt'],
['//server/share/b.txt', '\\\\server\\share\\b.txt']
])('uses backslashes for local Windows path %s', (path, expected) => {
stubClient('win32')
expect(formatClipboardFilePath(path, 'local')).toBe(expected)
})
it.each<[string, NodeJS.Platform, LocalPathOpenOwner, string, boolean?]>([
['SSH owner', 'win32', 'ssh:box', 'C:/repo/b.txt'],
['runtime owner', 'win32', 'runtime:env-1', 'C:/repo/b.txt'],
['unresolved owner', 'win32', 'unresolved', 'C:/repo/b.txt'],
['macOS client', 'darwin', 'local', 'C:/repo/b.txt'],
['Linux client', 'linux', 'local', '/repo/b.txt'],
['URL', 'win32', 'local', 'https://example.com/C:/b.txt'],
['relative path', 'win32', 'local', 'src/b.txt'],
['paired web client', 'win32', 'local', 'C:/repo/b.txt', true]
])('leaves the path unchanged for a %s', (_case, platform, owner, path, webClient) => {
stubClient(platform, webClient)
expect(formatClipboardFilePath(path, owner)).toBe(path)
})
})
@@ -63,11 +63,4 @@ describe('copyImageToClipboard', () => {
expect(writeClipboardImage).not.toHaveBeenCalled()
})
it('propagates a local clipboard write failure', async () => {
const error = new Error('Clipboard unavailable')
writeClipboardImage.mockRejectedValue(error)
await expect(copyImageToClipboard(png)).rejects.toBe(error)
})
})
@@ -283,88 +283,6 @@ describe('Cmd-J host-qualified candidate ownership', () => {
])
})
it('omits editor rows whose bare file id cannot be activated safely', () => {
const entries = buildSearchableWorkspaceTabs({
worktrees: pairedWorktrees(),
repoMap: new Map(),
worktreeOrder: new Map(),
unifiedTabsByWorktree: {
[SHARED_WORKTREE_ID]: [
makeTab({
id: 'shared-editor',
entityId: 'shared-file',
contentType: 'editor',
executionHostId: 'local'
}),
makeTab({
id: 'shared-editor',
entityId: 'shared-file',
contentType: 'editor',
executionHostId: RUNTIME_HOST_ID
})
]
},
tabsByWorktree: {},
openFiles: [
{
id: 'shared-file',
filePath: '/local/local-atlas.ts',
relativePath: 'local/local-atlas.ts',
worktreeId: SHARED_WORKTREE_ID,
language: 'typescript',
isDirty: false,
mode: 'edit'
},
{
id: 'shared-file',
filePath: '/remote/remote-atlas.ts',
relativePath: 'remote/remote-atlas.ts',
worktreeId: SHARED_WORKTREE_ID,
language: 'typescript',
isDirty: false,
runtimeEnvironmentId: 'paired-host',
mode: 'edit'
}
],
agentStatusByPaneKey: {},
retainedAgentsByPaneKey: {},
sleepingAgentSessionsByPaneKey: {},
activeGroupIdByWorktree: {},
groupsByWorktree: {},
activeWorktreeId: null,
activeTabType: 'terminal',
activeTabId: null,
activeTabIdByWorktree: {},
activeFileId: null,
activeFileIdByWorktree: {},
activeTabTypeByWorktree: {},
generatedTitlesEnabled: true
})
expect(searchWorkspaceTabs(entries, 'local-atlas')).toEqual([])
expect(searchWorkspaceTabs(entries, 'remote-atlas')).toEqual([])
})
it('retains one unambiguous legacy tab without guessing between sibling hosts', () => {
const legacyWorktree = makeWorktree({ hostId: undefined })
const entries = buildSearchableSimulatorTabs({
worktrees: [legacyWorktree],
repoMap: new Map(),
worktreeOrder: new Map(),
unifiedTabsByWorktree: {
[SHARED_WORKTREE_ID]: [
makeTab({ id: 'legacy-simulator', contentType: 'simulator', executionHostId: undefined })
]
},
activeGroupIdByWorktree: {},
groupsByWorktree: {},
activeWorktreeId: null,
activeTabType: 'terminal'
})
expect(entries.map((entry) => entry.tab.id)).toEqual(['legacy-simulator'])
})
it('keeps hidden same-id hosts in the legacy ownership ambiguity set', () => {
const ownershipWorktrees = pairedWorktrees()
const browserWorkspace = makeBrowserWorkspace('legacy-browser')
@@ -6,23 +6,6 @@ import {
} from '../../../shared/linear/links'
describe('Cmd+J Linear issue intent', () => {
it('accepts canonical Linear issue URLs with optional slugs', () => {
expect(
parseLinearIssueUrlIntent(
'https://linear.app/stably/issue/STA-4052/agent-terminals-disappearing-randomly'
)
).toEqual({ identifier: 'STA-4052', organizationUrlKey: 'stably' })
expect(
parseLinearIssueUrlIntent(
' https://linear.app/stably/issue/sta-4084/restore-shell-integration '
)
).toEqual({ identifier: 'STA-4084', organizationUrlKey: 'stably' })
expect(parseLinearIssueUrlIntent('http://linear.app/acme/issue/ENG-1')).toEqual({
identifier: 'ENG-1',
organizationUrlKey: 'acme'
})
})
it('does not classify names, bare issue keys, or ambiguous URLs as decisive intent', () => {
expect(parseLinearIssueUrlIntent('restore-shell-integration')).toBeNull()
expect(parseLinearIssueUrlIntent('STA-4084')).toBeNull()
+1 -34
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import type { PluginThemeRegistration } from '../../../shared/plugins/artifacts/plugin-theme-artifact'
import { paintCodeEditorColors, resolveCodeEditorTheme } from './code-editor-theme'
import { resolveCodeEditorTheme } from './code-editor-theme'
import type { ColorProbe } from './plugin-theme-tokens'
// Parses #rrggbb, which is all these tests feed it.
@@ -54,14 +54,6 @@ function resolve(
}
describe('resolveCodeEditorTheme', () => {
it('follows the painted app theme when nothing or the app theme itself is chosen', () => {
expect(resolve(null)).toEqual({ monaco: null, override: null })
expect(resolve('light')).toEqual({ monaco: null, override: null })
const followed = resolve(dawn, dawn)
expect(followed.override).toBeNull()
expect(followed.monaco?.data.colors['editor.background']).toBe('#fdf6e3')
})
it("paints a dark plugin editor in a light app from that theme's own tokens", () => {
const result = resolve(dusk, dawn)
expect(result.monaco?.data.base).toBe('vs-dark')
@@ -97,28 +89,3 @@ describe('resolveCodeEditorTheme', () => {
})
})
})
describe('paintCodeEditorColors', () => {
it('publishes an override on the root and clears it when the editor follows the app', () => {
const properties = new Map<string, string>()
const dataset: DOMStringMap = {}
const root = {
dataset,
style: {
setProperty: (name: string, value: string | null) => {
properties.set(name, value ?? '')
},
removeProperty: (name: string) => {
properties.delete(name)
return ''
}
}
}
paintCodeEditorColors(resolve(dusk, dawn).override, root)
expect(dataset.codeEditorTheme).toBe('dark')
expect(properties.get('--code-editor-background')).toBe('#120d22')
paintCodeEditorColors(null, root)
expect(dataset.codeEditorTheme).toBeUndefined()
expect(properties.size).toBe(0)
})
})
@@ -1,60 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
getCodexAccountDisplayLabel,
type CodexDisplayAccount
} from './codex-account-display-label'
const email = 'same@example.com'
const labels = (accounts: CodexDisplayAccount[]) =>
accounts.map((account) => getCodexAccountDisplayLabel(account, accounts))
describe('Codex account display labels', () => {
it('names personal and enterprise workspaces sharing an email', () => {
expect(
labels([
{ id: 'personal', email, workspaceLabel: 'Personal (Plus)' },
{ id: 'enterprise', email, workspaceLabel: 'Enterprise' }
])
).toEqual([`${email} (Personal (Plus))`, `${email} (Enterprise)`])
})
it.each([null, 'Enterprise'])(
'disambiguates missing or duplicate workspace names: %s',
(workspaceLabel) => {
const accounts = [
{ id: '12345678-a', email, workspaceLabel },
{ id: '12345678-b', email, workspaceLabel }
]
const result = labels(accounts)
expect(new Set(result).size).toBe(2)
expect(result[0]).toContain('12345678-a')
expect(result[1]).toContain('12345678-b')
expect(labels(accounts.toReversed())).toEqual(result.toReversed())
}
)
it('handles legacy and remote summaries without workspace metadata', () => {
expect(
labels([
{ id: 'account-a', email },
{ id: 'account-b', email: email.toUpperCase() }
])
).toEqual([`${email} (account-a)`, `${email.toUpperCase()} (account-b)`])
})
it('keeps unambiguous accounts concise', () => {
expect(labels([{ id: 'account-a', email }])).toEqual([email])
expect(labels([{ id: 'account-a', email, workspaceLabel: 'Acme' }])).toEqual([
`${email} (Acme)`
])
})
it('does not collide with a workspace name that looks like an ID suffix', () => {
const result = labels([
{ id: '12345678-a', email },
{ id: '87654321-b', email },
{ id: 'abcdefgh-c', email, workspaceLabel: '12345678' }
])
expect(new Set(result).size).toBe(3)
})
})
@@ -1,11 +1,9 @@
import { describe, expect, it, vi } from 'vitest'
import { describe, expect, it } from 'vitest'
import type { AppState } from '@/store'
import { getCodexSelectionLaneKey } from '../../../shared/codex-selection-lane'
import { toRemoteRuntimePtyId } from '../../../shared/remote-runtime/remote-runtime-pty-id'
import {
getCodexAccountSwitchLaneMatcher,
isForeignMachineCodexPtyId,
isLocalCodexSelectionLaneKey,
resolveCodexPaneSelectionLane,
resolveCodexPaneSelectionLaneKey
} from './codex-pane-selection-lane'
@@ -91,49 +89,6 @@ function withWindowsRenderer(run: () => void): void {
const HOST_TAB = { worktreeId: 'wt1', shellOverride: undefined }
describe('resolveCodexPaneSelectionLaneKey', () => {
it('keys an ordinary local pane to the host lane', () => {
expect(
resolveCodexPaneSelectionLaneKey({ state: laneState(), tab: HOST_TAB, ptyId: 'pty-1' })
).toBe('host')
})
it('keys a pane in a WSL UNC worktree to that distro lane', () => {
expect(
resolveCodexPaneSelectionLaneKey({
state: laneState({ worktreePath: '\\\\wsl.localhost\\Ubuntu\\home\\dev\\orca' }),
tab: HOST_TAB,
ptyId: 'pty-1'
})
).toBe('wsl:Ubuntu')
})
it('keys a wsl.exe pane outside a UNC worktree to the default WSL lane', () => {
expect(
resolveCodexPaneSelectionLaneKey({
state: laneState(),
tab: { worktreeId: 'wt1', shellOverride: 'wsl.exe' },
ptyId: 'pty-1'
})
).toBe('wsl:__default__')
})
// Use the resolved distro lane; wsl:__default__ would miss that distro’s account switch.
it('keys a wsl.exe pane on a Windows-path worktree to the configured distro', () => {
withWindowsRenderer(() => {
expect(
resolveCodexPaneSelectionLaneKey({
state: laneState({
terminalWindowsShell: 'wsl.exe',
terminalWindowsWslDistro: 'Ubuntu',
worktreePath: 'C:\\code\\app'
}),
tab: { worktreeId: 'wt1', shellOverride: 'wsl.exe' },
ptyId: 'pty-1'
})
).toBe('wsl:Ubuntu')
})
})
it('still keys an ordinary host pane to host when a WSL distro is configured', () => {
withWindowsRenderer(() => {
expect(
@@ -265,29 +220,6 @@ describe('resolveCodexPaneSelectionLaneKey', () => {
})
).toBe('remote-runtime')
})
it('keeps an owner-less remote pane off the host lane when no environment is active', () => {
const laneKey = resolveCodexPaneSelectionLaneKey({
state: laneState(),
tab: HOST_TAB,
ptyId: 'remote:term-1'
})
// Assert separation from host: a local switch must never mute a remote pane.
expect(laneKey).not.toBe(getCodexSelectionLaneKey({ runtime: 'host' }))
expect(isLocalCodexSelectionLaneKey(laneKey)).toBe(false)
})
it('keys an SSH-connection pane to a lane no account selection can name', () => {
const laneKey = resolveCodexPaneSelectionLaneKey({
state: laneState(),
tab: HOST_TAB,
ptyId: 'ssh:my-box@@pty-7'
})
expect(laneKey).toBe('ssh-connection')
// Only host/wsl lanes have managed selections; no switch can reach this foreign key.
expect(laneKey).not.toBe(getCodexSelectionLaneKey({ runtime: 'host' }))
expect(isLocalCodexSelectionLaneKey(laneKey)).toBe(false)
})
})
describe('resolveCodexPaneSelectionLane', () => {
@@ -306,53 +238,6 @@ describe('resolveCodexPaneSelectionLane', () => {
})
})
it('reports the disagreement so a re-derivation bug stays diagnosable', () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
try {
resolveCodexPaneSelectionLane({
state: laneState(),
tab: HOST_TAB,
ptyId: 'pty-1',
recordedLaneKey: 'wsl:Ubuntu'
})
expect(warn).toHaveBeenCalledWith(
expect.stringContaining('[codex-lane]'),
expect.objectContaining({ ptyId: 'pty-1', recorded: 'wsl:Ubuntu', derived: 'host' })
)
} finally {
warn.mockRestore()
}
})
it('stays quiet when the record and the derivation agree', () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
try {
const lane = resolveCodexPaneSelectionLane({
state: laneState(),
tab: HOST_TAB,
ptyId: 'pty-1',
recordedLaneKey: 'host'
})
expect(lane.source).toBe('recorded')
expect(warn).not.toHaveBeenCalled()
} finally {
warn.mockRestore()
}
})
it('derives a local host pane that main never recorded', () => {
for (const recordedLaneKey of [undefined, null, '', ' ']) {
expect(
resolveCodexPaneSelectionLane({
state: laneState(),
tab: HOST_TAB,
ptyId: 'pty-1',
recordedLaneKey
})
).toEqual({ laneKey: 'host', source: 'derived', derivedLaneKey: 'host' })
}
})
it('falls back to the derivation when the record names no selectable lane', () => {
// Registry strings are untrusted on-disk values; invalid lanes would silently lose notices.
expect(
@@ -403,23 +288,6 @@ describe('resolveCodexPaneSelectionLane', () => {
})
describe('getCodexAccountSwitchLaneMatcher', () => {
it('scopes a local switch to the runtime slot it wrote', () => {
const hostSwitch = getCodexAccountSwitchLaneMatcher({
owner: { kind: 'local' },
target: { runtime: 'host' }
})
expect(hostSwitch('host')).toBe(true)
expect(hostSwitch('wsl:Ubuntu')).toBe(false)
const ubuntuSwitch = getCodexAccountSwitchLaneMatcher({
owner: { kind: 'local' },
target: { runtime: 'wsl', wslDistro: 'Ubuntu' }
})
expect(ubuntuSwitch('wsl:Ubuntu')).toBe(true)
expect(ubuntuSwitch('wsl:Debian')).toBe(false)
expect(ubuntuSwitch('host')).toBe(false)
})
// Clearing a distro-less selection clears every WSL slot; matching only __default__ would miss them.
it('claims every WSL distro when the change cleared them all', () => {
const wslDefaultSwitch = getCodexAccountSwitchLaneMatcher({
@@ -458,47 +326,4 @@ describe('getCodexAccountSwitchLaneMatcher', () => {
expect(ubuntuClear('wsl:Debian')).toBe(false)
expect(ubuntuClear('wsl:__default__')).toBe(false)
})
it('scopes a switch made against a runtime environment to that machine', () => {
const environmentSwitch = getCodexAccountSwitchLaneMatcher({
owner: { kind: 'environment', environmentId: 'env-1' },
target: { runtime: 'host' }
})
expect(environmentSwitch('env:env-1')).toBe(true)
expect(environmentSwitch('host')).toBe(false)
expect(environmentSwitch('env:env-2')).toBe(false)
})
it('never lets a local host switch claim a remote or SSH pane', () => {
const hostSwitch = getCodexAccountSwitchLaneMatcher({
owner: { kind: 'local' },
target: { runtime: 'host' }
})
const state = laneState()
for (const ptyId of ['remote:env-owner@@term-1', 'remote:term-1', 'ssh:my-box@@pty-7']) {
expect(hostSwitch(resolveCodexPaneSelectionLaneKey({ state, tab: HOST_TAB, ptyId }))).toBe(
false
)
}
})
})
describe('isForeignMachineCodexPtyId', () => {
it('separates panes whose shell runs on another machine from local ones', () => {
expect(isForeignMachineCodexPtyId('remote:env-1@@term-1')).toBe(true)
expect(isForeignMachineCodexPtyId('remote:term-1')).toBe(true)
expect(isForeignMachineCodexPtyId('ssh:my-box@@pty-7')).toBe(true)
expect(isForeignMachineCodexPtyId('pty-1')).toBe(false)
})
it('agrees with the lane keys, so the sweep and the scan skip the same panes', () => {
const state = laneState({ activeRuntimeEnvironmentId: 'env-1' })
for (const ptyId of ['remote:env-1@@term-1', 'remote:term-1', 'ssh:my-box@@pty-7', 'pty-1']) {
expect(
isLocalCodexSelectionLaneKey(
resolveCodexPaneSelectionLaneKey({ state, tab: HOST_TAB, ptyId })
)
).toBe(!isForeignMachineCodexPtyId(ptyId))
}
})
})
@@ -8,11 +8,6 @@ import {
markLiveCodexSessionsForRestart,
markRestoredStaleCodexSessionsForRestart
} from './codex-session-restart'
import {
createCompatibleRuntimeStatusResponseIfNeeded,
type RuntimeEnvironmentCallRequest
} from '@/runtime/runtime-compatibility-test-fixture'
import { clearRuntimeCompatibilityCacheForTests } from '@/runtime/runtime-rpc-client'
const ACCOUNT_A = 'account-a@example.com'
const ACCOUNT_B = 'account-b@example.com'
@@ -20,8 +15,6 @@ const ACCOUNT_B = 'account-b@example.com'
// Reconfirm cached shell readings: one transient flap during account switch would silently lose the only restart notice (#11064).
describe('spurious shell readings on Codex-launched panes', () => {
const originalWindow = (globalThis as { window?: typeof window }).window
const runtimeEnvironmentCall = vi.fn()
const runtimeEnvironmentTransportCall = vi.fn()
function seedPane(args: { ptyId?: string; launchAgent?: 'codex' } = {}): void {
const ptyId = args.ptyId ?? 'pty-1'
@@ -49,12 +42,6 @@ describe('spurious shell readings on Codex-launched panes', () => {
}
beforeEach(() => {
clearRuntimeCompatibilityCacheForTests()
runtimeEnvironmentCall.mockReset()
runtimeEnvironmentTransportCall.mockReset()
runtimeEnvironmentTransportCall.mockImplementation((args: RuntimeEnvironmentCallRequest) => {
return createCompatibleRuntimeStatusResponseIfNeeded(args) ?? runtimeEnvironmentCall(args)
})
seedPane({ launchAgent: 'codex' })
;(globalThis as { window: typeof window }).window = {
...originalWindow,
@@ -77,10 +64,6 @@ describe('spurious shell readings on Codex-launched panes', () => {
activeAccountId: 'account-b'
}),
listStalePanes: vi.fn().mockResolvedValue([])
},
runtimeEnvironments: {
...originalWindow?.api?.runtimeEnvironments,
call: runtimeEnvironmentTransportCall
}
}
} as unknown as typeof window
@@ -149,27 +132,6 @@ describe('spurious shell readings on Codex-launched panes', () => {
expect(useAppStore.getState().codexRestartNoticeByPtyId).toEqual({})
})
it('never routes a remote runtime pane through the local confirm bridge', async () => {
seedPane({ ptyId: 'remote:env-1@@term-1', launchAgent: 'codex' })
useAppStore.setState({ settings: { activeRuntimeEnvironmentId: 'env-1' } as never })
runtimeEnvironmentCall.mockResolvedValue({
id: 'rpc-1',
ok: true,
result: { process: { foregroundProcess: 'zsh', hasChildProcesses: false } },
_meta: { runtimeId: 'remote-runtime' }
})
await markLiveCodexSessionsForRestart({
owner: focusedOwner(),
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B
})
// Local fresh scans say nothing about remote PTYs; reject cross-lane confirmation.
expect(window.api.pty.confirmForegroundProcess).not.toHaveBeenCalled()
expect(useAppStore.getState().codexRestartNoticeByPtyId).toEqual({})
})
it('heals a spurious shell reading before the restored-pane stale lookup', async () => {
vi.mocked(window.api.pty.confirmForegroundProcess).mockResolvedValue('codex')
vi.mocked(window.api.codexAccounts.listStalePanes).mockResolvedValue([
@@ -11,10 +11,7 @@ const markForFocusedHost = (
...args
})
import type { TuiAgent } from '../../../shared/tui-agent'
import {
markLiveCodexSessionsForRestart,
markRestoredStaleCodexSessionsForRestart
} from './codex-session-restart'
import { markLiveCodexSessionsForRestart } from './codex-session-restart'
import {
createCompatibleRuntimeStatusResponseIfNeeded,
type RuntimeEnvironmentCallRequest
@@ -106,25 +103,6 @@ describe('markLiveCodexSessionsForRestart', () => {
}
})
it('marks a live Codex PTY for restart', async () => {
vi.mocked(window.api.pty.inspectProcess).mockResolvedValue({
foregroundProcess: 'codex',
hasChildProcesses: false
})
await markForFocusedHost({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B
})
expect(window.api.pty.inspectProcess).toHaveBeenCalledWith('pty-1')
expect(useAppStore.getState().codexRestartNoticeByPtyId['pty-1']).toEqual({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B
})
expect(window.api.codexAccounts.listStalePanes).not.toHaveBeenCalled()
})
it('marks every live Codex split pane and ignores non-Codex panes', async () => {
useAppStore.setState({
tabsByWorktree: {
@@ -179,20 +157,6 @@ describe('markLiveCodexSessionsForRestart', () => {
})
})
it('does not mark non-codex foreground processes', async () => {
vi.mocked(window.api.pty.inspectProcess).mockResolvedValue({
foregroundProcess: 'zsh',
hasChildProcesses: false
})
await markForFocusedHost({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B
})
expect(useAppStore.getState().codexRestartNoticeByPtyId).toEqual({})
})
it('marks a launcher-started Codex pane whose deepest process is a subagent', async () => {
// Windows reports pwsh -> node -> codex.exe -> claude.exe as "claude".
setLaunchAgentOnFirstTab('codex')
@@ -266,40 +230,6 @@ describe('markLiveCodexSessionsForRestart', () => {
})
})
it('treats codex.exe as codex for Windows PTYs', async () => {
vi.mocked(window.api.pty.inspectProcess).mockResolvedValue({
foregroundProcess: 'codex.exe',
hasChildProcesses: false
})
await markForFocusedHost({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B
})
expect(useAppStore.getState().codexRestartNoticeByPtyId['pty-1']).toEqual({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B
})
})
it('treats codex-prefixed packaged binaries as codex', async () => {
vi.mocked(window.api.pty.inspectProcess).mockResolvedValue({
foregroundProcess: 'codex-aarch64-ap',
hasChildProcesses: false
})
await markForFocusedHost({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B
})
expect(useAppStore.getState().codexRestartNoticeByPtyId['pty-1']).toEqual({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B
})
})
it('clears stale restart notices when the selected account switches back to the live pane account', async () => {
vi.mocked(window.api.pty.inspectProcess).mockResolvedValue({
foregroundProcess: 'codex',
@@ -512,19 +442,6 @@ describe('markLiveCodexSessionsForRestart lane scoping', () => {
}
})
it('leaves a live remote Codex pane alone on a host switch, and never inspects it', async () => {
seedPanes([{ ptyId: 'remote:env-1@@term-1' }])
await markForFocusedHost({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B,
target: { runtime: 'host' }
})
expect(useAppStore.getState().codexRestartNoticeByPtyId).toEqual({})
expect(runtimeEnvironmentCall).not.toHaveBeenCalled()
})
it('leaves a live SSH-connection Codex pane alone on a host switch', async () => {
seedPanes([{ ptyId: 'ssh:my-box@@pty-7' }])
@@ -571,19 +488,6 @@ describe('markLiveCodexSessionsForRestart lane scoping', () => {
})
})
it('still marks a local host pane when the switch names no target at all', async () => {
seedPanes([{ ptyId: 'pty-1' }])
await markForFocusedHost({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B
})
expect(useAppStore.getState().codexRestartNoticeByPtyId).toEqual({
'pty-1': { previousAccountLabel: ACCOUNT_A, nextAccountLabel: ACCOUNT_B }
})
})
// Test a WSL pane whose foreground is Codex so lane exclusion cannot accidentally rely on wsl.exe failing identity checks.
it('leaves a WSL Codex pane alone on a host switch even when its foreground is codex', async () => {
seedPanes([{ ptyId: 'pty-wsl' }], { wt1: '\\\\wsl.localhost\\Ubuntu\\home\\dev\\orca' })
@@ -693,26 +597,6 @@ describe('markLiveCodexSessionsForRestart lane scoping', () => {
})
})
it('suppresses a null-to-account reauth notice when the recorded pane is already current', async () => {
seedPanes([{ ptyId: 'pty-1' }])
vi.mocked(window.api.codexAccounts.listRecordedPaneLanes).mockResolvedValue({
'pty-1': 'host'
})
await markForFocusedHost({
previousAccountLabel: 'System default',
nextAccountLabel: ACCOUNT_A,
previousAccountId: null,
nextAccountId: 'account-a',
target: { runtime: 'host' }
})
expect(window.api.codexAccounts.listStalePanes).toHaveBeenCalledWith({
ptyIds: ['pty-1']
})
expect(useAppStore.getState().codexRestartNoticeByPtyId).toEqual({})
})
it('classifies current, stale, and unrecorded panes independently', async () => {
seedPanes([{ ptyId: 'pty-current' }, { ptyId: 'pty-stale' }, { ptyId: 'pty-unknown' }])
vi.mocked(window.api.codexAccounts.listRecordedPaneLanes).mockResolvedValue({
@@ -784,132 +668,5 @@ describe('markLiveCodexSessionsForRestart lane scoping', () => {
nextAccountLabel: ACCOUNT_B
})
})
it('asks only about panes main could have recorded', async () => {
seedPanes([
{ ptyId: 'pty-1' },
{ ptyId: 'remote:env-1@@term-1' },
{ ptyId: 'ssh:my-box@@pty-7' }
])
await markForFocusedHost({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B,
target: { runtime: 'host' }
})
// Main records only daemon host spawns; skip foreign IDs and batch the remaining lookup.
expect(window.api.codexAccounts.listRecordedPaneLanes).toHaveBeenCalledTimes(1)
expect(window.api.codexAccounts.listRecordedPaneLanes).toHaveBeenCalledWith({
ptyIds: ['pty-1']
})
})
})
})
describe('markRestoredStaleCodexSessionsForRestart', () => {
const originalWindow = (globalThis as { window?: typeof window }).window
beforeEach(() => {
clearRuntimeCompatibilityCacheForTests()
useAppStore.setState({
tabsByWorktree: {
wt1: [
{
id: 'tab-1',
ptyId: 'pty-1',
worktreeId: 'wt1',
title: 'orca-1',
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 1
}
]
},
ptyIdsByTabId: { 'tab-1': ['pty-1'] },
pendingPaneAccountRestarts: {},
codexRestartNoticeByPtyId: {}
})
;(globalThis as { window: typeof window }).window = {
...originalWindow,
api: {
...originalWindow?.api,
pty: {
...originalWindow?.api?.pty,
getForegroundProcess: vi.fn(),
hasChildProcesses: vi.fn().mockResolvedValue(false),
inspectProcess: vi
.fn()
.mockResolvedValue({ foregroundProcess: 'codex', hasChildProcesses: false }),
confirmForegroundProcess: vi.fn().mockResolvedValue(null)
},
codexAccounts: {
...originalWindow?.api?.codexAccounts,
list: vi.fn().mockResolvedValue({
accounts: [
{ id: 'account-a', email: ACCOUNT_A },
{ id: 'account-b', email: ACCOUNT_B }
],
activeAccountId: 'account-b'
}),
listStalePanes: vi.fn().mockResolvedValue([])
}
}
} as unknown as typeof window
})
afterEach(() => {
if (originalWindow) {
;(globalThis as { window: typeof window }).window = originalWindow
} else {
delete (globalThis as { window?: typeof window }).window
}
})
it('re-raises the prompt for a pane the app restart forgot', async () => {
vi.mocked(window.api.codexAccounts.listStalePanes).mockResolvedValue([
{ ptyId: 'pty-1', launchAccountId: 'account-a', activeAccountId: 'account-b' }
])
await markRestoredStaleCodexSessionsForRestart()
expect(window.api.codexAccounts.listStalePanes).toHaveBeenCalledWith({ ptyIds: ['pty-1'] })
expect(useAppStore.getState().codexRestartNoticeByPtyId['pty-1']).toEqual({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B,
previousAccountId: 'account-a',
nextAccountId: 'account-b'
})
})
it('labels the system default when a pane launched without a managed account', async () => {
vi.mocked(window.api.codexAccounts.listStalePanes).mockResolvedValue([
{ ptyId: 'pty-1', launchAccountId: null, activeAccountId: 'account-b' }
])
await markRestoredStaleCodexSessionsForRestart()
expect(useAppStore.getState().codexRestartNoticeByPtyId['pty-1']?.previousAccountLabel).toBe(
'System default'
)
})
it('prompts nothing when every restored pane is on the selected account', async () => {
await markRestoredStaleCodexSessionsForRestart()
expect(useAppStore.getState().codexRestartNoticeByPtyId).toEqual({})
})
it('skips the account lookup entirely when no pane is running Codex', async () => {
vi.mocked(window.api.pty.inspectProcess).mockResolvedValue({
foregroundProcess: 'zsh',
hasChildProcesses: false
})
await markRestoredStaleCodexSessionsForRestart()
expect(window.api.codexAccounts.listStalePanes).not.toHaveBeenCalled()
expect(useAppStore.getState().codexRestartNoticeByPtyId).toEqual({})
})
})
@@ -2,8 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useAppStore } from '@/store'
import {
notifyCodexPaneBoundForStaleSweep,
resetCodexStalePaneSweepForTests,
sweepRestoredCodexPanesForStaleAccounts
resetCodexStalePaneSweepForTests
} from './codex-stale-pane-sweep'
const ACCOUNT_A = 'account-a@example.com'
@@ -80,36 +79,6 @@ describe('notifyCodexPaneBoundForStaleSweep', () => {
}
})
it('raises the prompt once the pane PTY actually binds', async () => {
vi.mocked(window.api.codexAccounts.listStalePanes).mockResolvedValue([STALE_PANE])
notifyCodexPaneBoundForStaleSweep('pty-1')
expect(window.api.pty.inspectProcess).not.toHaveBeenCalled()
await vi.advanceTimersByTimeAsync(300)
expect(window.api.codexAccounts.listStalePanes).toHaveBeenCalledWith({ ptyIds: ['pty-1'] })
expect(useAppStore.getState().codexRestartNoticeByPtyId['pty-1']).toEqual({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B,
previousAccountId: 'account-a',
nextAccountId: 'account-b'
})
})
it('coalesces a burst of startup binds into one sweep', async () => {
useAppStore.setState({ ptyIdsByTabId: { 'tab-1': ['pty-1', 'pty-2'] } })
vi.mocked(window.api.codexAccounts.listStalePanes).mockResolvedValue([STALE_PANE])
notifyCodexPaneBoundForStaleSweep('pty-1')
notifyCodexPaneBoundForStaleSweep('pty-2')
await vi.advanceTimersByTimeAsync(300)
expect(window.api.codexAccounts.listStalePanes).toHaveBeenCalledExactlyOnceWith({
ptyIds: ['pty-1', 'pty-2']
})
})
it('still coalesces a burst whose binds land milliseconds apart', async () => {
// Staggered startup binds must still batch registry reads rather than make one round trip per pane.
useAppStore.setState({ ptyIdsByTabId: { 'tab-1': ['pty-1', 'pty-2'] } })
@@ -125,24 +94,6 @@ describe('notifyCodexPaneBoundForStaleSweep', () => {
})
})
it('retries a PTY whose process read is unusable until the reattach settles', async () => {
vi.mocked(window.api.codexAccounts.listStalePanes).mockResolvedValue([STALE_PANE])
vi.mocked(window.api.pty.inspectProcess).mockRejectedValueOnce(new Error('terminal_gone'))
notifyCodexPaneBoundForStaleSweep('pty-1')
await vi.advanceTimersByTimeAsync(300)
expect(useAppStore.getState().codexRestartNoticeByPtyId).toEqual({})
await vi.advanceTimersByTimeAsync(1500)
expect(useAppStore.getState().codexRestartNoticeByPtyId['pty-1']).toEqual({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B,
previousAccountId: 'account-a',
nextAccountId: 'account-b'
})
})
it('gives up after a bounded ladder instead of polling forever', async () => {
vi.mocked(window.api.pty.inspectProcess).mockRejectedValue(new Error('terminal_gone'))
@@ -411,25 +362,4 @@ describe('notifyCodexPaneBoundForStaleSweep', () => {
nextAccountId: 'account-b'
})
})
it('re-raises the prompt for a restored pane whose tab never mounts', async () => {
// Restored background tabs must get stale-account checks without ever mounting a pane.
vi.mocked(window.api.codexAccounts.listStalePanes).mockResolvedValue([STALE_PANE])
sweepRestoredCodexPanesForStaleAccounts({
ptyIdsByTabId: { 'tab-1': ['pty-1', 'remote:env-1@@term-1'] }
})
await vi.advanceTimersByTimeAsync(300)
expect(window.api.codexAccounts.listStalePanes).toHaveBeenCalledExactlyOnceWith({
ptyIds: ['pty-1']
})
expect(inspectCallCountFor('remote:env-1@@term-1')).toBe(0)
expect(useAppStore.getState().codexRestartNoticeByPtyId['pty-1']).toEqual({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B,
previousAccountId: 'account-a',
nextAccountId: 'account-b'
})
})
})
@@ -10,13 +10,6 @@ afterEach(() => {
})
describe('comment body layout line count', () => {
it('matches normal newline counts used for comment layout', () => {
expect(getCommentBodyLayoutLineCount('')).toBe(1)
expect(getCommentBodyLayoutLineCount('one line')).toBe(1)
expect(getCommentBodyLayoutLineCount('one\ntwo\nthree')).toBe(3)
expect(getCommentBodyLayoutLineCount('one\r\ntwo')).toBe(2)
})
it('caps newline-heavy pasted comments without splitting the body', () => {
const split = vi.spyOn(String.prototype, 'split')
const charCodeAt = vi.spyOn(String.prototype, 'charCodeAt')
@@ -6,19 +6,6 @@ import {
} from './comment-body-submit-state'
describe('comment body submit state', () => {
it('treats blank bodies as empty without producing submit text', () => {
expect(hasBoundedCommentBodyText(' \n\t ')).toBe(false)
expect(getCommentBodySubmitState(' \n\t ')).toEqual({ status: 'empty' })
})
it('returns trimmed submit text for bounded comment bodies', () => {
expect(hasBoundedCommentBodyText(' hello world ')).toBe(true)
expect(getCommentBodySubmitState(' hello world ')).toEqual({
status: 'ready',
body: 'hello world'
})
})
it('counts multibyte leading whitespace toward the scan budget', () => {
const oversizedLeadingWhitespace = '\u3000'.repeat(
Math.floor(COMMENT_BODY_NONBLANK_SCAN_MAX_BYTES / 3) + 1
@@ -78,17 +78,6 @@ describe('scheduleComposerCreateHandoff', () => {
vi.clearAllMocks()
})
it('starts directly without staging or closing when preflight is fast', () => {
const { handoff, onHandOff } = schedule()
handoff.start(finalRequest)
vi.runAllTimers()
expect(flow.run).toHaveBeenCalledWith(finalRequest)
expect(flow.begin).not.toHaveBeenCalled()
expect(onHandOff).not.toHaveBeenCalled()
expect(handoff.fail('boom')).toBe(false)
})
it('closes the composer onto a staged row when preflight stalls, then continues it', () => {
const { handoff, onHandOff } = handOff()
@@ -145,12 +134,4 @@ describe('scheduleComposerCreateHandoff', () => {
expect(flow.continue).not.toHaveBeenCalled()
expect(toastError).not.toHaveBeenCalled()
})
it('does not stage or close again after the user dismissed the composer', () => {
const { onHandOff } = schedule(() => true)
vi.runAllTimers()
expect(flow.begin).not.toHaveBeenCalled()
expect(onHandOff).not.toHaveBeenCalled()
})
})
@@ -1,30 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
buildTrustedComposerIssueCommand,
shouldPrepareComposerIssueCommand
} from './composer-issue-command'
const readyInput = {
enabled: true,
provider: 'github' as const,
issueNumber: 42,
template: 'gh issue view {{issue}} --repo {{artifact_url}}',
artifactUrl: 'https://github.com/stablyai/orca/issues/42'
}
describe('composer issue command', () => {
it('renders a trusted GitHub issue command', () => {
expect(buildTrustedComposerIssueCommand({ ...readyInput, trustDecision: 'run' })).toEqual({
command: 'gh issue view 42 --repo https://github.com/stablyai/orca/issues/42'
})
})
it('skips untrusted, disabled, PR, and empty commands', () => {
expect(
buildTrustedComposerIssueCommand({ ...readyInput, trustDecision: 'skip' })
).toBeUndefined()
expect(shouldPrepareComposerIssueCommand({ ...readyInput, enabled: false })).toBe(false)
expect(shouldPrepareComposerIssueCommand({ ...readyInput, issueNumber: null })).toBe(false)
expect(shouldPrepareComposerIssueCommand({ ...readyInput, template: ' ' })).toBe(false)
})
})
@@ -11,7 +11,6 @@ import {
getConnectionIdFromState,
isWorktreeConnectionResolved
} from './connection-context'
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants'
import { folderWorkspaceKey } from '../../../shared/workspace-scope'
import { createConnectionIdForFileSelector } from './connection-owner-resolution'
@@ -53,85 +52,6 @@ describe('getConnectionId', () => {
useAppStore.setState(initialState, true)
})
it('resolves SSH targets from composite worktree IDs before worktree discovery completes', () => {
useAppStore.setState({
repos: [
makeRepo({
id: 'repo-ssh',
connectionId: 'ssh-1'
})
],
worktreesByRepo: {}
})
expect(getConnectionId('repo-ssh::/home/neil/repo-feature')).toBe('ssh-1')
})
it('returns null for known local repos without a discovered worktree', () => {
useAppStore.setState({
repos: [makeRepo({ id: 'repo-local' })],
worktreesByRepo: {}
})
expect(getConnectionId('repo-local::/Users/me/repo-feature')).toBeNull()
})
it('returns undefined when neither the worktree nor repo is known', () => {
useAppStore.setState({
repos: [],
worktreesByRepo: {}
})
expect(getConnectionId('repo-missing::/tmp/repo-feature')).toBeUndefined()
})
it('resolves SSH targets for folder workspaces from repos in the folder scope', () => {
useAppStore.setState({
folderWorkspaces: [
{
id: 'folder-workspace-1',
projectGroupId: 'group-1',
name: 'Platform workspace',
folderPath: '/home/neil/platform',
linkedTask: null,
comment: '',
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 1,
lastActivityAt: 0,
createdAt: 1,
updatedAt: 1
}
],
projectGroups: [
{
id: 'group-1',
name: 'Platform',
parentPath: '/home/neil/platform',
parentGroupId: null,
createdFrom: 'folder-scan',
tabOrder: 0,
isCollapsed: false,
color: null,
createdAt: 1,
updatedAt: 1
}
],
repos: [
makeRepo({
id: 'repo-ssh',
path: '/home/neil/platform/api',
projectGroupId: 'group-1',
connectionId: 'ssh-1'
})
],
worktreesByRepo: {}
})
expect(getConnectionId(folderWorkspaceKey('folder-workspace-1'))).toBe('ssh-1')
})
it('resolves SSH targets for repo-less folder workspaces from persisted scope provenance', () => {
useAppStore.setState({
folderWorkspaces: [
@@ -281,46 +201,6 @@ describe('getConnectionId', () => {
expect(getConnectionIdForFile(workspaceKey, '/home/neil/platform/README.md')).toBeUndefined()
})
it('resolves folder workspace combined diff sections by child repo path', () => {
const workspaceKey = folderWorkspaceKey('folder-workspace-1')
useAppStore.setState({
folderWorkspaces: [makeFolderWorkspace()],
projectGroups: [
{
id: 'group-1',
name: 'Platform',
parentPath: '/home/neil/platform',
parentGroupId: null,
createdFrom: 'folder-scan',
tabOrder: 0,
isCollapsed: false,
color: null,
createdAt: 1,
updatedAt: 1
}
],
repos: [
makeRepo({
id: 'repo-local',
path: '/home/neil/platform/web',
projectGroupId: 'group-1'
}),
makeRepo({
id: 'repo-ssh',
path: '/home/neil/platform/api',
projectGroupId: 'group-1',
connectionId: 'ssh-1'
})
],
worktreesByRepo: {}
})
expect(getConnectionIdForFile(workspaceKey, '/home/neil/platform')).toBeUndefined()
expect(getConnectionIdForFile(workspaceKey, '/home/neil/platform/api/src/index.ts')).toBe(
'ssh-1'
)
})
it('keeps explicit folder workspace provenance isolated from unrelated same-path SSH repos', () => {
useAppStore.setState({
folderWorkspaces: [
@@ -401,21 +281,6 @@ describe('getConnectionId', () => {
expect(isWorktreeConnectionResolved('repo-ssh::/home/neil/repo')).toBe(true)
})
it('treats null worktrees and folder workspaces as resolved owners', () => {
useAppStore.setState({ repos: [], worktreesByRepo: {} })
expect(isWorktreeConnectionResolved(null)).toBe(true)
// Folder workspaces resolve per-file via getConnectionIdForFile.
expect(isWorktreeConnectionResolved(folderWorkspaceKey('folder-workspace-1'))).toBe(true)
})
it('treats the floating workspace as a resolved local owner (#6831)', () => {
useAppStore.setState({ repos: [], worktreesByRepo: {} })
expect(getConnectionId(FLOATING_TERMINAL_WORKTREE_ID)).toBeNull()
expect(getConnectionIdForFile(FLOATING_TERMINAL_WORKTREE_ID, '/tmp/orca/note.md')).toBeNull()
expect(isWorktreeConnectionResolved(FLOATING_TERMINAL_WORKTREE_ID)).toBe(true)
})
it('keeps normalized same-path folder repo ambiguity when resolving files', () => {
const workspaceKey = folderWorkspaceKey('folder-workspace-1')
useAppStore.setState({
@@ -534,17 +399,6 @@ describe('getConnectionIdFromState', () => {
expect(getStateSpy).not.toHaveBeenCalled()
})
it('resolves SSH repo provenance for non-folder worktrees from the passed-in state', () => {
const state: ConnectionContextState = {
folderWorkspaces: [],
projectGroups: [],
repos: [makeRepo({ id: 'repo-ssh', connectionId: 'ssh-2' })],
worktreesByRepo: {}
}
expect(getConnectionIdFromState(state, 'repo-ssh::/home/neil/repo-feature')).toBe('ssh-2')
})
it('refuses to resolve a connection when duplicate repo rows disagree about the owning host', () => {
// Same-ID repo rows with conflicting owners must not lend a client SSH connection to a runtime (#17799).
const state: ConnectionContextState = {
@@ -560,20 +414,6 @@ describe('getConnectionIdFromState', () => {
expect(getConnectionIdFromState(state, 'repo-dup::/home/neil/repo-feature')).toBeUndefined()
})
it('still resolves duplicate repo rows that agree about the owning host', () => {
const state: ConnectionContextState = {
folderWorkspaces: [],
projectGroups: [],
repos: [
makeRepo({ id: 'repo-dup', connectionId: 'ssh-same' }),
makeRepo({ id: 'repo-dup', connectionId: 'ssh-same', path: '/home/neil/other' })
],
worktreesByRepo: {}
}
expect(getConnectionIdFromState(state, 'repo-dup::/home/neil/repo-feature')).toBe('ssh-same')
})
it('never hands a worktree the SSH connection of a different host', () => {
// Host lookup misses must not fall back by repo ID to another SSH machine’s connection (#11163).
const state: ConnectionContextState = {
@@ -750,17 +590,6 @@ describe('getConnectionIdFromState', () => {
expect(getConnectionIdFromState(local, key)).toBeNull()
})
it('returns null for a null worktreeId', () => {
const state: ConnectionContextState = {
folderWorkspaces: [],
projectGroups: [],
repos: [],
worktreesByRepo: {}
}
expect(getConnectionIdFromState(state, null)).toBeNull()
})
it('recomputes a retained file-owner selector when ownership slices hydrate', () => {
const selector = createConnectionIdForFileSelector(
'repo-ssh::/home/neil/repo-feature',
@@ -59,15 +59,6 @@ describe('renderer crash diagnostics', () => {
vi.unstubAllGlobals()
})
it('records renderer breadcrumbs through preload', () => {
diagnostics.recordRendererCrashBreadcrumb('renderer_bootstrap_started', { dev: true })
expect(recordBreadcrumbMock).toHaveBeenCalledWith({
name: 'renderer_bootstrap_started',
data: { dev: true }
})
})
it('installs startup, error, rejection, and memory breadcrumbs once', () => {
diagnostics.installRendererCrashDiagnostics()
diagnostics.installRendererCrashDiagnostics()
@@ -119,24 +110,6 @@ describe('renderer crash diagnostics', () => {
})
})
it('suppresses benign ResizeObserver loop errors instead of recording them', () => {
diagnostics.installRendererCrashDiagnostics()
recordBreadcrumbMock.mockClear()
const preventDefault = vi.fn()
listeners.get('error')?.[0]?.({
message: 'ResizeObserver loop completed with undelivered notifications.',
preventDefault
})
listeners.get('error')?.[0]?.({
message: 'ResizeObserver loop limit exceeded',
preventDefault
})
expect(preventDefault).toHaveBeenCalledTimes(2)
expect(recordBreadcrumbMock).not.toHaveBeenCalled()
})
it('includes delivered and queued long tasks once in each memory sample', () => {
let now = 0
let deliver = (_durations: number[]): void => {}
@@ -186,33 +159,6 @@ describe('renderer crash diagnostics', () => {
})
})
it('records application errors that only mention a ResizeObserver loop', () => {
diagnostics.installRendererCrashDiagnostics()
recordBreadcrumbMock.mockClear()
const preventDefault = vi.fn()
listeners.get('error')?.[0]?.({
message: 'ResizeObserver loop failed while rendering the terminal',
preventDefault
})
expect(preventDefault).not.toHaveBeenCalled()
expect(recordBreadcrumbMock).toHaveBeenCalledWith({
name: 'renderer_error',
data: expect.objectContaining({
message: 'ResizeObserver loop failed while rendering the terminal'
})
})
})
it('does not throw when preload is unavailable', () => {
vi.stubGlobal('window', {})
expect(() =>
diagnostics.recordRendererCrashBreadcrumb('renderer_bootstrap_started')
).not.toThrow()
})
it('emits one-shot renderer_memory_highwater breadcrumbs with profile counts', async () => {
const memory = (window.performance as unknown as { memory: Record<string, number> }).memory
memory.usedJSHeapSize = 0.7 * memory.jsHeapSizeLimit
@@ -271,19 +217,6 @@ describe('renderer crash diagnostics', () => {
unregister()
})
it('skips highwater emission when heap readings are not finite', () => {
const memory = (window.performance as unknown as { memory: Record<string, number> }).memory
memory.usedJSHeapSize = Number.NaN
diagnostics.installRendererCrashDiagnostics()
expect(
recordBreadcrumbMock.mock.calls.some(
(call) => (call[0] as { name: string }).name === 'renderer_memory_highwater'
)
).toBe(false)
})
describe('exact V8 heap statistics', () => {
const KB = 1024
let readHeapStatistics: ReturnType<typeof vi.fn>
@@ -304,24 +237,6 @@ describe('renderer crash diagnostics', () => {
readHeapStatistics
})
it('prefers exact statistics over performance.memory and labels the source', () => {
stubHeap(101)
diagnostics.installRendererCrashDiagnostics()
// Blink still reports 32MB; 101 proves exact data replaces it rather than merely accompanying it.
expect(recordBreadcrumbMock).toHaveBeenCalledWith({
name: 'renderer_memory',
data: expect.objectContaining({
usedHeapMB: 101,
heapLimitMB: 512,
heapSource: 'v8',
mallocedMB: 3,
blinkAllocatedMB: 7
})
})
})
it('observes growth that performance.memory quantizes away', () => {
// Blink’s long-lived quantized bucket can hide heap growth; exact readings must still cross highwater thresholds.
const quantized = (window.performance as unknown as { memory: Record<string, number> }).memory
@@ -351,36 +266,6 @@ describe('renderer crash diagnostics', () => {
})
})
it('omits the Blink field instead of emitting a junk value for it', () => {
// Missing source data must omit blinkAllocatedMB rather than emit NaN from undefined * 1024.
readHeapStatistics.mockReturnValue({
usedHeapKB: 77 * KB,
totalHeapKB: 154 * KB,
heapLimitKB: 512 * KB,
mallocedKB: 3 * KB,
blinkAllocatedKB: undefined
})
diagnostics.installRendererCrashDiagnostics()
const call = recordBreadcrumbMock.mock.calls.find(
([entry]) => (entry as { name: string }).name === 'renderer_memory'
)?.[0] as { data: Record<string, unknown> }
expect(call.data).toMatchObject({ usedHeapMB: 77, heapSource: 'v8', mallocedMB: 3 })
expect(call.data).not.toHaveProperty('blinkAllocatedMB')
})
it('falls back to performance.memory when the bridge returns null', () => {
readHeapStatistics.mockReturnValue(null)
diagnostics.installRendererCrashDiagnostics()
expect(recordBreadcrumbMock).toHaveBeenCalledWith({
name: 'renderer_memory',
data: expect.objectContaining({ usedHeapMB: 32, heapSource: 'quantized' })
})
})
it('samples on an older shell whose preload lacks the bridge', () => {
;(window.api.crashReports as unknown as Record<string, unknown>).readHeapStatistics =
undefined
@@ -436,22 +321,6 @@ describe('renderer crash diagnostics', () => {
.filter((call) => (call[0] as { name: string }).name === 'renderer_memory_highwater')
.map((call) => call[0] as { data: Record<string, unknown> })
it('reports the private footprint and what it holds outside the heap counters', async () => {
// Model Windows crash 36048e26: private memory far exceeds V8 heap, exposing native xterm/atlas growth that heap counters miss.
stubFootprint(618)
diagnostics.installRendererCrashDiagnostics()
await flush()
const tick = setIntervalMock.mock.calls[0][0] as () => void
tick()
const latest = memoryCalls().at(-1)!
expect(latest.data).toMatchObject({
usedHeapMB: 150,
privateMB: 618,
outsideHeapMB: 438
})
})
it('arms the leak census on footprint even when the heap ratio never trips', async () => {
// Low heap/limit ratio must not prevent the census that identifies native-memory growth.
stubFootprint(618)
@@ -492,17 +361,6 @@ describe('renderer crash diagnostics', () => {
expect(highwaterCalls().at(-1)!.data).toMatchObject({ thresholdPrivateMB: 1000 })
})
it('keeps sampling when the shell has no footprint bridge at all', async () => {
;(window.api.crashReports as unknown as Record<string, unknown>).readProcessMemory = undefined
expect(() => diagnostics.installRendererCrashDiagnostics()).not.toThrow()
await flush()
const latest = memoryCalls().at(-1)!
expect(latest.data).toMatchObject({ usedHeapMB: 150 })
expect(latest.data).not.toHaveProperty('privateMB')
expect(highwaterCalls()).toHaveLength(0)
})
it('does not let a rejected footprint read break the heap sample', async () => {
readProcessMemory.mockRejectedValue(new Error('bridge gone'))
@@ -1,9 +1,5 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
createUntitledMarkdownFile,
createUntitledMarkdownFileWithTemplateSelection
} from './create-untitled-markdown'
import { subscribeMarkdownTemplatePicker } from './markdown-template-picker-request'
import { createUntitledMarkdownFile } from './create-untitled-markdown'
import {
createCompatibleRuntimeStatusResponseIfNeeded,
type RuntimeEnvironmentCallRequest
@@ -108,47 +104,6 @@ describe('createUntitledMarkdownFile', () => {
}
)
it('creates a plugin new-file type with its extension and verbatim template', async () => {
const pathExists = vi.fn().mockResolvedValueOnce(true).mockResolvedValue(false)
const createFile = vi.fn().mockResolvedValue(undefined)
const writeFile = vi.fn().mockResolvedValue(undefined)
vi.stubGlobal('window', { api: { fs: { pathExists, createFile, writeFile } } })
const content = '{"type":"excalidraw","title":"{{title}}"}\n'
await expect(
createUntitledMarkdownFile('/repo', 'wt', undefined, LOCAL, {
newFile: { extension: '.excalidraw', content }
})
).resolves.toMatchObject({
filePath: '/repo/untitled-2.excalidraw',
relativePath: 'untitled-2.excalidraw',
isUntitled: true,
deleteUntouchedOnClose: false
})
// Why verbatim: placeholders are a Markdown template feature, not a plugin template one.
expect(writeFile).toHaveBeenCalledWith(
expect.objectContaining({ filePath: '/repo/untitled-2.excalidraw', content })
)
})
it('leaves an empty plugin new file unwritten, like a blank markdown file', async () => {
const writeFile = vi.fn()
vi.stubGlobal('window', {
api: {
fs: {
pathExists: vi.fn().mockResolvedValue(false),
createFile: vi.fn().mockResolvedValue(undefined),
writeFile
}
}
})
const created = await createUntitledMarkdownFile('/repo', 'wt', undefined, LOCAL, {
newFile: { extension: '.drawio', content: '' }
})
expect(created.filePath).toBe('/repo/untitled.drawio')
expect(created.deleteUntouchedOnClose).toBeUndefined()
expect(writeFile).not.toHaveBeenCalled()
})
it('never overwrites or deletes a file another writer filled after the create', async () => {
const createFile = vi.fn().mockResolvedValue(undefined)
const writeFile = vi
@@ -241,111 +196,6 @@ describe('createUntitledMarkdownFile', () => {
})
})
it('writes selected template content with placeholders after creating the untitled file', async () => {
const stat = vi.fn().mockRejectedValue(new Error('ENOENT: no such file'))
const createFile = vi.fn().mockResolvedValueOnce(undefined)
const readFile = vi.fn().mockResolvedValueOnce({
content: '# {{ title }}\n{{date}}\n{{filename}}\n',
isBinary: false
})
const writeFile = vi.fn().mockResolvedValueOnce(undefined)
vi.stubGlobal('window', {
api: {
shell: { pathExists: vi.fn() },
fs: { createFile, pathExists: vi.fn().mockResolvedValue(false), readFile, stat, writeFile }
}
})
await expect(
createUntitledMarkdownFile('/repo', 'wt-1', undefined, LOCAL, {
now: new Date(2026, 4, 29, 7, 5),
template: {
id: '.orca/templates/daily.md',
name: 'Daily',
filePath: '/repo/.orca/templates/daily.md',
relativePath: '.orca/templates/daily.md',
templateRelativePath: 'daily.md',
basename: 'daily.md'
}
})
).resolves.toMatchObject({
filePath: '/repo/untitled.md',
relativePath: 'untitled.md',
deleteUntouchedOnClose: false
})
expect(readFile).toHaveBeenCalledWith(
expect.objectContaining({ filePath: '/repo/.orca/templates/daily.md' })
)
expect(createFile).toHaveBeenCalledWith(
expect.objectContaining({ filePath: '/repo/untitled.md' })
)
expect(writeFile).toHaveBeenCalledWith(
expect.objectContaining({
filePath: '/repo/untitled.md',
content: '# Untitled\n2026-05-29\nuntitled.md\n'
})
)
})
it('discovers templates before creating a new markdown file and applies the selected template', async () => {
const readDir = vi.fn().mockResolvedValueOnce([
{ name: 'daily.md', isDirectory: false, isSymlink: false },
{ name: 'draft.txt', isDirectory: false, isSymlink: false }
])
const stat = vi.fn().mockRejectedValue(new Error('ENOENT: no such file'))
const createFile = vi.fn().mockResolvedValueOnce(undefined)
const readFile = vi.fn().mockResolvedValueOnce({
content: '# {{title}}\n',
isBinary: false
})
const writeFile = vi.fn().mockResolvedValueOnce(undefined)
const pathExists = vi.fn(async ({ filePath }: { filePath: string }) =>
filePath.endsWith('/.orca/templates')
)
const unsubscribe = subscribeMarkdownTemplatePicker((request) => {
const template = request.templates[0]
if (!template) {
throw new Error('Expected a discovered template')
}
request.resolve({ type: 'template', template })
})
vi.stubGlobal('window', {
api: {
shell: { pathExists: vi.fn() },
fs: {
createFile,
pathExists,
readDir,
readFile,
stat,
writeFile
}
}
})
try {
await expect(
createUntitledMarkdownFileWithTemplateSelection('/repo', 'wt-1', undefined, LOCAL)
).resolves.toMatchObject({
filePath: '/repo/untitled.md',
relativePath: 'untitled.md',
deleteUntouchedOnClose: false
})
} finally {
unsubscribe()
}
expect(readDir).toHaveBeenCalledWith(
expect.objectContaining({ dirPath: '/repo/.orca/templates' })
)
expect(writeFile).toHaveBeenCalledWith(
expect.objectContaining({ filePath: '/repo/untitled.md', content: '# Untitled\n' })
)
})
it('creates untitled files through the selected runtime environment', async () => {
clearRuntimeCompatibilityCacheForTests()
const stat = vi.fn()
@@ -1,38 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
defaultCreationHost,
defaultScopeHost,
defaultScopeSource,
rowLessSourceTarget
} from './default-creation-host'
describe('defaultCreationHost', () => {
it('is this computer unless a server is chosen', () => {
expect(defaultCreationHost(null)).toEqual({ kind: 'local' })
expect(defaultCreationHost({ activeRuntimeEnvironmentId: null })).toEqual({ kind: 'local' })
expect(defaultCreationHost({ activeRuntimeEnvironmentId: ' env-a ' })).toEqual({
kind: 'environment',
environmentId: 'env-a'
})
})
})
describe('row-less sources', () => {
it('starts on the default host until a source or host is named', () => {
const settings = { activeRuntimeEnvironmentId: 'env-a' }
expect(defaultScopeHost(settings)).toEqual({ kind: 'environment', environmentId: 'env-a' })
expect(rowLessSourceTarget(defaultScopeSource(settings))).toEqual({
kind: 'environment',
environmentId: 'env-a'
})
expect(rowLessSourceTarget({ kind: 'local' })).toEqual({ kind: 'local' })
expect(
rowLessSourceTarget({
kind: 'task-source',
provider: 'linear',
projectId: 'p',
hostId: 'runtime:env-b'
})
).toEqual({ kind: 'environment', environmentId: 'env-b' })
})
})
@@ -1,23 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import {
DESKTOP_CREATED_SERVER_TAB_HOLDS,
forgetDesktopCreatedServerTabsOf,
noteDesktopCreatedServerTab,
pruneDesktopCreatedServerTabs
} from './desktop-created-server-tabs'
vi.mock('../../../shared/layout-owner-switch', () => ({ ORCA_LAYOUT_OWNER_IS_MAIN: false }))
describe('the desktop-created server tab record in a switch-off build', () => {
it('never reads nor keeps the record', () => {
const storage = { getItem: vi.fn(() => null), setItem: vi.fn(), removeItem: vi.fn() }
vi.stubGlobal('localStorage', storage)
noteDesktopCreatedServerTab('web-env-1', 'repo::/worktree', 'simulator-1')
pruneDesktopCreatedServerTabs({ unifiedTabsByWorktree: {} })
forgetDesktopCreatedServerTabsOf('repo::/worktree')
expect(DESKTOP_CREATED_SERVER_TAB_HOLDS.isTabHeld('repo::/worktree', 'simulator-1')).toBe(false)
expect(storage.getItem).not.toHaveBeenCalled()
expect(storage.setItem).not.toHaveBeenCalled()
expect(storage.removeItem).not.toHaveBeenCalled()
})
})
@@ -37,10 +37,6 @@ import {
DESKTOP_CREATED_SERVER_TAB_HOLDS,
_reloadDesktopCreatedServerTabsForTests,
_resetDesktopCreatedServerTabsForTests,
forgetDesktopCreatedServerTab,
forgetDesktopCreatedServerTabsOf,
forgetPublishedServerTabs,
isDesktopCreatedServerTab,
noteDesktopCreatedServerTab
} from './desktop-created-server-tabs'
@@ -56,40 +52,6 @@ beforeEach(() => {
})
describe('the desktop-created server tab record', () => {
it('records an id per workspace: one workspace’s publish or removal leaves the other’s', () => {
noteDesktopCreatedServerTab(ENV, WT1, 'tab-x')
noteDesktopCreatedServerTab(ENV, WT2, 'tab-x')
expect([held(WT1, 'tab-x'), held(WT2, 'tab-x')]).toEqual([true, true])
forgetPublishedServerTabs(WT1, ['tab-x'])
expect([held(WT1, 'tab-x'), held(WT2, 'tab-x')]).toEqual([false, true])
noteDesktopCreatedServerTab(ENV, WT1, 'tab-x')
forgetDesktopCreatedServerTabsOf(WT2)
expect([held(WT1, 'tab-x'), held(WT2, 'tab-x')]).toEqual([true, false])
})
it('forgets a workspace’s every id, and a closed id in any workspace', () => {
noteDesktopCreatedServerTab(ENV, WT1, 'tab-a')
noteDesktopCreatedServerTab(ENV, WT1, 'tab-b')
noteDesktopCreatedServerTab(ENV, WT2, 'tab-c')
forgetDesktopCreatedServerTabsOf(WT1)
expect([held(WT1, 'tab-a'), held(WT1, 'tab-b'), held(WT2, 'tab-c')]).toEqual([
false,
false,
true
])
forgetDesktopCreatedServerTab('tab-c')
expect(held(WT2, 'tab-c')).toBe(false)
})
it('names a tab recorded by its entity id by its tab-bar id too', () => {
noteDesktopCreatedServerTab(ENV, WT1, 'browser-workspace-1')
const tabs = [{ id: 'tab-bar-1', entityId: 'browser-workspace-1' }]
expect(isDesktopCreatedServerTab(tabs, WT1, 'tab-bar-1')).toBe(true)
expect(isDesktopCreatedServerTab(tabs, WT2, 'tab-bar-1')).toBe(false)
})
it('drops the removed worktree’s ids when the worktree is removed, and only those', async () => {
const store = createTestStore()
seedStore(store, {
@@ -96,17 +96,6 @@ describe('desktop sidebar feed', () => {
expect(second.result.current?.domains.workspaces.size).toBe(2)
})
it('answers a refresh with an install from that read or later', async () => {
connectMain()
const reader = await load()
const installs: number[] = []
reader.onDesktopSidebarFeedInstalled((installed) => installs.push(installed.readId))
renderHook(() => reader.useDesktopSidebarFeed())
await vi.waitFor(() => expect(installs).toHaveLength(1))
const readId = reader.refreshDesktopSidebarFeed()
await vi.waitFor(() => expect(installs.at(-1)).toBeGreaterThanOrEqual(readId))
})
it('retries a failed first read on its own', async () => {
const main = connectMain()
main.failOnce()
@@ -1,18 +0,0 @@
import { describe, expect, it } from 'vitest'
import { shouldRenderDesktopWindowChrome } from './desktop-window-chrome'
describe('shouldRenderDesktopWindowChrome', () => {
it('renders custom chrome for frameless desktop Linux and Windows windows', () => {
expect(shouldRenderDesktopWindowChrome({ platform: 'linux', isWebClient: false })).toBe(true)
expect(shouldRenderDesktopWindowChrome({ platform: 'win32', isWebClient: false })).toBe(true)
})
it('keeps macOS on native traffic lights', () => {
expect(shouldRenderDesktopWindowChrome({ platform: 'darwin', isWebClient: false })).toBe(false)
})
it('does not render desktop-only window controls in the paired web client', () => {
expect(shouldRenderDesktopWindowChrome({ platform: 'linux', isWebClient: true })).toBe(false)
expect(shouldRenderDesktopWindowChrome({ platform: 'win32', isWebClient: true })).toBe(false)
})
})
@@ -1,43 +0,0 @@
import { describe, expect, it } from 'vitest'
import type { DiffComment } from '../../../shared/diff-comment-types'
import {
getDiffCommentLineLabel,
getDiffCommentSource,
isDiffComment,
isMarkdownComment
} from './diff-comment-compat'
function makeComment(overrides: Partial<DiffComment> = {}): DiffComment {
return {
id: 'c1',
worktreeId: 'wt1',
filePath: 'README.md',
lineNumber: 4,
body: 'note',
createdAt: 0,
side: 'modified',
...overrides
}
}
describe('diff comment compatibility helpers', () => {
it('routes legacy comments with no source as diff comments', () => {
const comment = makeComment()
expect(getDiffCommentSource(comment)).toBe('diff')
expect(isDiffComment(comment)).toBe(true)
expect(isMarkdownComment(comment)).toBe(false)
})
it('routes markdown comments by explicit source', () => {
const comment = makeComment({ source: 'markdown' })
expect(getDiffCommentSource(comment)).toBe('markdown')
expect(isMarkdownComment(comment)).toBe(true)
expect(isDiffComment(comment)).toBe(false)
})
it('formats compact and full range labels', () => {
const comment = makeComment({ startLine: 2, lineNumber: 4 })
expect(getDiffCommentLineLabel(comment)).toBe('Lines 2-4')
expect(getDiffCommentLineLabel(comment, true)).toBe('L2-L4')
})
})
@@ -1,98 +0,0 @@
import { describe, it, expect } from 'vitest'
import type { DiffComment } from '../../../shared/diff-comment-types'
import { formatDiffComment, formatDiffComments } from './diff-comments-format'
function makeComment(overrides: Partial<DiffComment> = {}): DiffComment {
return {
id: 'id-1',
worktreeId: 'wt-1',
filePath: 'src/app.ts',
lineNumber: 10,
body: 'Needs validation',
createdAt: 0,
side: 'modified',
...overrides
}
}
describe('formatDiffComment', () => {
it('emits the fixed three-line structure', () => {
const out = formatDiffComment(makeComment())
expect(out).toBe(
['File: src/app.ts', 'Line: 10', 'User comment: "Needs validation"'].join('\n')
)
})
it('keeps explicit diff comments without ranges in the legacy format', () => {
const out = formatDiffComment(makeComment({ source: 'diff' }))
expect(out).toBe(
['File: src/app.ts', 'Line: 10', 'User comment: "Needs validation"'].join('\n')
)
})
it('formats persisted ranges when startLine is present', () => {
const out = formatDiffComment(makeComment({ source: 'diff', startLine: 7 }))
expect(out).toBe(
['File: src/app.ts', 'Lines: 7-10', 'User comment: "Needs validation"'].join('\n')
)
})
it('formats file-level diff notes', () => {
const out = formatDiffComment(makeComment({ source: 'diff', lineNumber: 0 }))
expect(out).toBe(
['File: src/app.ts', 'Scope: file', 'User comment: "Needs validation"'].join('\n')
)
})
it('adds markdown source metadata for markdown notes', () => {
const out = formatDiffComment(makeComment({ source: 'markdown', startLine: 8 }))
expect(out).toBe(
[
'File: src/app.ts',
'Source: markdown',
'Lines: 8-10',
'User comment: "Needs validation"'
].join('\n')
)
})
it('escapes embedded quotes in the body', () => {
const out = formatDiffComment(makeComment({ body: 'why "this" path?' }))
expect(out).toContain('User comment: "why \\"this\\" path?"')
})
it('escapes backslashes before quotes so the body cannot break out of the literal', () => {
const out = formatDiffComment(makeComment({ body: 'path\\to\\"thing"' }))
expect(out).toContain('User comment: "path\\\\to\\\\\\"thing\\""')
})
it('escapes newlines so the body cannot break out of the fixed 3-line structure', () => {
const out = formatDiffComment(makeComment({ body: 'first\nsecond' }))
expect(out).toContain('User comment: "first\\nsecond"')
expect(out.split('\n')).toHaveLength(3)
})
})
describe('formatDiffComments', () => {
it('joins multiple comments with a blank line', () => {
const out = formatDiffComments([
makeComment({ id: 'a', lineNumber: 1, body: 'first' }),
makeComment({ id: 'b', lineNumber: 2, body: 'second' })
])
expect(out).toBe(
[
'File: src/app.ts',
'Line: 1',
'User comment: "first"',
'',
'File: src/app.ts',
'Line: 2',
'User comment: "second"'
].join('\n')
)
})
it('returns an empty string for an empty input', () => {
expect(formatDiffComments([])).toBe('')
})
})
@@ -12,7 +12,6 @@ import {
fixBrokenPullRequestChecks,
startFixChecksFromDialog
} from '@/components/pull-request-page/checks/fix-launch'
import { fixBrokenGitHubChecks } from '@/components/github-item-dialog/inspect-pull-request/checks-tab-actions'
import * as preflight from './launch-work-item-direct-preflight'
import * as preparation from './launch-work-item-direct-route-preparation'
import { createCompatibleRuntimeStatusResponse } from '@/runtime/runtime-compatibility-test-fixture'
@@ -258,22 +257,6 @@ it('retains an edited Fix checks request through the PR-page Start handoff', asy
)
})
it('lets the GitHub item checks action answer Ask without its toast fallback', async () => {
const launch = fixBrokenGitHubChecks({
item,
repoId: 'repo-1',
fixingChecks: false,
failedChecksLength: 1,
list: [],
setFixingChecks: vi.fn()
})
;(await pending()).choose('skip')
await launch
expect(mocks.createWorktree).toHaveBeenCalledOnce()
expect(mocks.createWorktree.mock.calls[0][3]).toBe('skip')
expect(mocks.toastError).not.toHaveBeenCalled()
})
it.each(['Start work', 'Use item'])(
'keeps the project %s action in Orca when setup asks',
async (entry) => {
@@ -1,9 +1,7 @@
import { describe, expect, it } from 'vitest'
import {
applyCachedPluginAppTheme,
applyDocumentTheme,
applyPluginAppTheme,
readCachedPluginAppTheme,
THEME_TRANSITION_DISABLED_CLASS
} from './document-theme'
@@ -78,25 +76,6 @@ function createFrameQueue(): {
}
describe('document theme', () => {
it('removes the transition suppression class after two animation frames', () => {
const root = createThemeRoot()
const frames = createFrameQueue()
applyDocumentTheme('dark', {
root,
requestAnimationFrame: frames.requestAnimationFrame,
cancelAnimationFrame: frames.cancelAnimationFrame
})
expect(root.classList.contains(THEME_TRANSITION_DISABLED_CLASS)).toBe(true)
frames.flushNextFrame()
expect(root.classList.contains(THEME_TRANSITION_DISABLED_CLASS)).toBe(true)
frames.flushNextFrame()
expect(root.classList.contains(THEME_TRANSITION_DISABLED_CLASS)).toBe(false)
})
it('cancels stale transition suppression frames on rapid theme changes', () => {
const root = createThemeRoot()
const frames = createFrameQueue()
@@ -142,23 +121,6 @@ describe('applyPluginAppTheme', () => {
}
}
}
function memoryStorage(): Storage {
const values = new Map<string, string>()
return {
get length() {
return values.size
},
clear: () => values.clear(),
getItem: (key) => values.get(key) ?? null,
key: (index) => [...values.keys()][index] ?? null,
removeItem: (key) => {
values.delete(key)
},
setItem: (key, value) => {
values.set(key, value)
}
}
}
// Test probe: #rrggbb or #rrggbbaa.
const probe = (value: string) => {
const match = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i.exec(value)
@@ -192,19 +154,6 @@ describe('applyPluginAppTheme', () => {
return Object.fromEntries(root.properties)
}
it('overlays tokens, drops ones a new theme stops setting, and restores built-ins on removal', () => {
const root = fakeRoot()
applyPluginAppTheme(theme({ '--background': '#000000', '--border': '#111111' }), options(root))
expect(Object.fromEntries(root.properties)).toEqual({
'--background': '#000000',
'--border': '#111111'
})
applyPluginAppTheme(theme({ '--background': '#222222' }), options(root))
expect(Object.fromEntries(root.properties)).toEqual({ '--background': '#222222' })
applyPluginAppTheme(null, options(root))
expect(root.properties.size).toBe(0)
})
it('checks contrast on the effective set, so dropping one pair cannot expose another', () => {
// White background passes with black text, but fails against muted text; dropping the
// background leaves black text on the dark built-in background, so that drops too.
@@ -217,33 +166,6 @@ describe('applyPluginAppTheme', () => {
).toEqual({})
})
it('checks plugin colors against the built-ins they sit on', () => {
// A light plugin surface would put the dark built-in light text on near-white.
expect(painted({ '--background': '#f4f4f4' })).toEqual({})
expect(painted({ '--background': '#151520' })).toEqual({ '--background': '#151520' })
})
it('keeps text readable on the editor surface notebooks paint on', () => {
const tokens = { '--background': '#000000', '--foreground': '#ffffff' }
expect(painted({ ...tokens, '--editor-surface': '#ffffff' })).not.toHaveProperty(
'--editor-surface'
)
expect(painted({ ...tokens, '--editor-surface': '#111111' })).toHaveProperty(
'--editor-surface',
'#111111'
)
})
it('rejects invisible rings and borders in any spelling', () => {
expect(
painted({ '--ring': '#0a0a0a', '--border': '#00000000', '--input': '#ffffff03' })
).toEqual({})
expect(painted({ '--ring': '#ff2bd6', '--border': '#ffffff1a' })).toEqual({
'--ring': '#ff2bd6',
'--border': '#ffffff1a'
})
})
it('drops only the status or link color that fails, never the shared surface', () => {
const sidebar = { '--sidebar': '#202020', '--sidebar-foreground': '#ffffff' }
expect(
@@ -304,24 +226,4 @@ describe('applyPluginAppTheme', () => {
})
).toEqual({ ...sidebar, '--accent': '#ffffff', '--accent-foreground': '#000000' })
})
it('keeps the startup cache tied to the theme id and revision until cleared', () => {
const storage = memoryStorage()
applyPluginAppTheme(theme({ '--background': '#101018' }), { ...options(), storage })
expect(readCachedPluginAppTheme(storage)).toEqual({
id: 'plugin:samples.midnight/midnight',
revision: 'r1',
base: 'dark',
tokens: { '--background': '#101018' }
})
const startup = fakeRoot()
const cached = readCachedPluginAppTheme(storage)
if (cached) {
applyCachedPluginAppTheme(cached, { root: startup, probe, builtIns: darkBuiltIns })
}
expect(startup.properties.get('--background')).toBe('#101018')
applyPluginAppTheme(null, { ...options(), storage })
expect(readCachedPluginAppTheme(storage)).toBeNull()
})
})
@@ -1,45 +0,0 @@
// @vitest-environment happy-dom
import { afterEach, describe, expect, it } from 'vitest'
import { isSelectAllShortcut } from './editable-target'
const originalUserAgent = navigator.userAgent
function setPlatformUserAgent(userAgent: string): void {
Object.defineProperty(navigator, 'userAgent', { configurable: true, value: userAgent })
}
function keyEvent(overrides: Partial<Parameters<typeof isSelectAllShortcut>[0]> = {}) {
return {
altKey: false,
ctrlKey: false,
key: 'a',
metaKey: false,
shiftKey: false,
...overrides
}
}
afterEach(() => setPlatformUserAgent(originalUserAgent))
describe('isSelectAllShortcut', () => {
it.each([
['macOS Cmd+A', 'Macintosh', { metaKey: true }],
['Linux Ctrl+A', 'Linux x86_64', { ctrlKey: true }],
['Windows Ctrl+A', 'Windows NT 10.0', { ctrlKey: true }]
])('recognizes %s', (_label, userAgent, modifiers) => {
setPlatformUserAgent(userAgent)
expect(isSelectAllShortcut(keyEvent(modifiers))).toBe(true)
})
it.each([
['macOS Ctrl+A', 'Macintosh', { ctrlKey: true }],
['Linux Cmd+A', 'Linux x86_64', { metaKey: true }],
['Windows Cmd+A', 'Windows NT 10.0', { metaKey: true }]
])('rejects wrong-modifier %s', (_label, userAgent, modifiers) => {
setPlatformUserAgent(userAgent)
expect(isSelectAllShortcut(keyEvent(modifiers))).toBe(false)
})
})
@@ -11,7 +11,6 @@ import {
captureEditorFileOperationProvenance,
getEditorFileOperationContext
} from './editor-file-operation-owner'
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants'
import { applyOpenFileToState } from '@/store/slices/editor/actions/open-file-apply'
import { buildEditorSessionData } from './workspace-session'
import { restorableEditorFile } from '@/store/slices/editor/tabs/editor-tab-layout-owner'
@@ -123,51 +122,6 @@ describe('editor file operation owner', () => {
queue.dispose()
}
)
it('retains published hints and owner-only capture without inventing physical-local evidence', () => {
useAppStore.setState({
worktreesByRepo: {
repo: [
makeWorktree({ id: worktreeId, repoId: 'repo', path: '/remote/repo', hostId: undefined })
]
}
})
const hinted = captureEditorFileOperationProvenance(
useAppStore.getState(),
worktreeId,
'hub-a',
true
)
expect(hinted.generation.authority).toBeUndefined()
expect(assertEditorFileOperationCurrent(useAppStore.getState(), worktreeId, hinted)).toEqual(
hinted.generation.route
)
useAppStore.setState({
worktreesByRepo: {
repo: [
{
id: worktreeId,
repoId: 'repo',
path: '/remote/repo',
runtimeOwnerEnvironmentId: 'hub-a'
} as never
]
}
})
const ownerOnly = captureEditorFileOperationProvenance(
useAppStore.getState(),
worktreeId,
'hub-a',
true
)
expect(ownerOnly.generation.authority).toBeUndefined()
expect(assertEditorFileOperationCurrent(useAppStore.getState(), worktreeId, ownerOnly)).toEqual(
ownerOnly.generation.route
)
expect(ownerOnly.generation.route).toEqual({
runtimeEnvironmentId: 'hub-a',
executionHostId: null
})
})
it('keeps catalog identity and drafts across capture, saved writers and reload while fencing physical retargets', async () => {
const id = 'folder:physical-owner'
useAppStore.setState({
@@ -440,30 +394,6 @@ describe('editor file operation owner', () => {
'Reopen the file'
)
})
it('round-trips the synthetic local owner for floating workspace files', () => {
const provenance = captureEditorFileOperationProvenance(
useAppStore.getState(),
FLOATING_TERMINAL_WORKTREE_ID,
null,
true
)
expect(
getEditorFileOperationContext(
useAppStore.getState(),
{
worktreeId: FLOATING_TERMINAL_WORKTREE_ID,
runtimeEnvironmentId: null,
operationProvenance: provenance
},
'/tmp/orca/floating-workspace'
)
).toMatchObject({
worktreeId: FLOATING_TERMINAL_WORKTREE_ID,
worktreePath: '/tmp/orca/floating-workspace',
expectedExecutionHostId: 'local'
})
})
it('uses the explicit worktree owner instead of global runtime focus', () => {
useAppStore.setState({
@@ -645,42 +575,6 @@ describe('editor file operation owner', () => {
})
})
it('round-trips capture and assert for a local folder workspace (#10251)', () => {
const provenance = captureEditorFileOperationProvenance(
useAppStore.getState(),
folderKey,
undefined,
false
)
expect(provenance.generation.route).toEqual({
executionHostId: 'local',
runtimeEnvironmentId: null
})
expect(
assertEditorFileOperationCurrent(useAppStore.getState(), folderKey, provenance)
).toEqual(provenance.generation.route)
})
it('uses the folder root when a legacy caller passes an empty worktree path', () => {
useAppStore.setState({
folderWorkspaces: [
{
id: folderWorkspaceId,
projectGroupId: 'group-1',
connectionId: null,
folderPath: '/workspace/folder'
} as never
]
})
const context = getEditorFileOperationContext(
useAppStore.getState(),
{ worktreeId: folderKey },
''
)
expect(context.worktreePath).toBe('/workspace/folder')
})
it('fails closed when folder ownership changes between capture and assert', () => {
const provenance = captureEditorFileOperationProvenance(
useAppStore.getState(),
@@ -1,70 +0,0 @@
import { describe, expect, it } from 'vitest'
import { resolveEditorTypography, type EditorTypographyInputs } from './editor-typography'
const terminal = { terminalFontFamily: 'JetBrains Mono', terminalFontSize: 14 }
describe('resolveEditorTypography', () => {
it('keeps pre-setting rendering when no editor key is set', () => {
expect(resolveEditorTypography(terminal, 0)).toMatchObject({
fontFamily: 'JetBrains Mono',
fontSize: 14,
fontWeight: 'normal',
lineHeight: 0,
fontLigatures: false
})
})
it('applies editor overrides independently of the terminal', () => {
const typography = resolveEditorTypography(
{
...terminal,
editorFontSize: 16,
editorFontWeight: 550,
editorLineHeight: 1.6,
editorLigatures: 'auto'
},
2
)
expect(typography).toMatchObject({
fontSize: 18,
fontWeight: '550',
lineHeight: 29,
fontLigatures: true
})
})
it('derives diff size and line height from the editor base size', () => {
const typography = resolveEditorTypography(
{ ...terminal, editorFontSize: 16, editorLineHeight: 2 },
0,
'diff'
)
expect(typography.fontSize).toBe(15.5)
expect(typography.lineHeight).toBe(31)
})
it('reads line heights of 8 or more as pixels so they do not clamp to 3x', () => {
const typography = resolveEditorTypography({ ...terminal, editorLineHeight: 21 }, 0)
expect(typography.lineHeight).toBe(21)
})
it('ignores hand-edited values that are not usable', () => {
// Settings files are untyped JSON on disk, so corrupt values arrive through JSON.parse.
const handEdited: EditorTypographyInputs = JSON.parse(
JSON.stringify({
...terminal,
editorFontSize: 'big',
editorFontWeight: null,
editorLineHeight: -1,
editorLigatures: 'yes'
})
)
const typography = resolveEditorTypography(handEdited, 0)
expect(typography).toMatchObject({
fontSize: 14,
fontWeight: 'normal',
lineHeight: 0,
fontLigatures: false
})
})
})
@@ -5,7 +5,6 @@ import { getDefaultSettings } from '../../../shared/constants'
import { useAppStore } from '@/store'
import { setLocalRuntimeCapabilitiesForTests } from '@/runtime/local-runtime-capabilities'
import {
emptyWorkspaceDefaultChatAwaitsDetection,
loadEmptyWorkspaceDefaultChatDetection,
openDefaultAgentChatInEmptyWorkspace
} from './empty-workspace-default-agent-chat'
@@ -59,24 +58,6 @@ afterEach(() => {
})
describe('openDefaultAgentChatInEmptyWorkspace', () => {
it("seeds a plain shell, not the agent's terminal, when the host declines the chat", () => {
seedSettings({})
openDefaultAgentChatInEmptyWorkspace('wt-1', mocks.seedShell)
const onDeclined = mocks.launchAgentInNewTab.mock.calls[0]?.[0]?.onStructuredHostDeclined
expect(mocks.seedShell).not.toHaveBeenCalled()
expect(onDeclined()).toEqual({ opened: true })
expect(mocks.seedShell).toHaveBeenCalledOnce()
})
it('opens no agent when the default agent is left on Auto', () => {
seedSettings({ defaultTuiAgent: null })
expect(openDefaultAgentChatInEmptyWorkspace('wt-1', mocks.seedShell)).toBeNull()
expect(mocks.launchAgentInNewTab).not.toHaveBeenCalled()
})
it('reads the agents detected on the workspace SSH host', () => {
seedSettings({})
mocks.detectionTargetKey.mockReturnValue('ssh:conn-1')
@@ -91,25 +72,6 @@ describe('openDefaultAgentChatInEmptyWorkspace', () => {
})
describe('agent detection for the default chat', () => {
it('waits only while chat is the default and the host list has not loaded', () => {
seedSettings({})
setLocalRuntimeCapabilitiesForTests([])
mocks.detectionTargetKey.mockReturnValue('ssh:conn-1')
useAppStore.setState({ remoteDetectedAgentIds: {} })
expect(emptyWorkspaceDefaultChatAwaitsDetection('wt-1')).toBe(true)
useAppStore.setState({ remoteDetectedAgentIds: { 'conn-1': [] } })
expect(emptyWorkspaceDefaultChatAwaitsDetection('wt-1')).toBe(false)
seedSettings({ nativeChatProviders: {} })
useAppStore.setState({ remoteDetectedAgentIds: {} })
expect(emptyWorkspaceDefaultChatAwaitsDetection('wt-1')).toBe(false)
seedSettings({ defaultTuiAgent: null })
useAppStore.setState({ remoteDetectedAgentIds: {} })
expect(emptyWorkspaceDefaultChatAwaitsDetection('wt-1')).toBe(false)
})
it('probes the workspace host and gives up after a bounded wait', async () => {
vi.useFakeTimers()
seedSettings({})
@@ -142,68 +142,6 @@ describe('ensureHooksConfirmed', () => {
await expect(promise).resolves.toBe('skip')
})
it('prompts for VM recipes using the recipe lifecycle declarations', async () => {
const { state, pending } = createTestState()
hooksCheckMock.mockResolvedValue({
hasHooks: true,
hooks: {
environmentRecipes: [
{
id: 'cloud-sandbox',
name: 'Cloud Sandbox',
description: 'Starts a per-workspace VM.',
create: './scripts/start-vm.sh',
suspend: './scripts/suspend-vm.sh',
resume: './scripts/resume-vm.sh',
destroy: './scripts/destroy-vm.sh'
}
]
},
mayNeedUpdate: false
})
const promise = ensureHooksConfirmed(state, 'repo-1', 'vmRecipe')
await vi.waitFor(() => expect(pending).toHaveLength(1))
expect(pending[0].data.scriptKind).toBe('vmRecipe')
expect(pending[0].data.scriptContent).toContain('# environmentRecipes.cloud-sandbox')
expect(pending[0].data.scriptContent).toContain('create: ./scripts/start-vm.sh')
expect(pending[0].data.scriptContent).toContain('suspend: ./scripts/suspend-vm.sh')
expect(pending[0].data.scriptContent).toContain('resume: ./scripts/resume-vm.sh')
expect(pending[0].data.scriptContent).toContain('destroy: ./scripts/destroy-vm.sh')
pending[0].resolve('run')
await expect(promise).resolves.toBe('run')
})
it('returns run without inspecting hooks when the repo is always trusted', async () => {
const { state, pending } = createTestState()
state.trustedOrcaHooks['repo-1'] = {
all: { approvedAt: 1 }
}
hooksCheckMock.mockRejectedValue(new Error('boom'))
const decision = await ensureHooksConfirmed(state, 'repo-1', 'setup')
expect(decision).toBe('run')
expect(hooksCheckMock).not.toHaveBeenCalled()
expect(pending).toHaveLength(0)
})
it('returns run without prompting when no script of that kind is configured', async () => {
const { state, pending } = createTestState()
hooksCheckMock.mockResolvedValue({
hasHooks: true,
hooks: { scripts: {} },
mayNeedUpdate: false
})
const decision = await ensureHooksConfirmed(state, 'repo-1', 'archive')
expect(decision).toBe('run')
expect(pending).toHaveLength(0)
})
it('checks SSH repo hooks through local IPC even when a runtime is focused', async () => {
const { state, pending } = createTestState({
settings: { activeRuntimeEnvironmentId: 'env-1' },
@@ -302,81 +240,6 @@ describe('ensureHooksConfirmed', () => {
expect(pending).toHaveLength(0)
})
it('does not prompt for orca.yaml when the repo uses local commands only', async () => {
const { state, pending } = createTestState({
repos: [
{
id: 'repo-1',
path: '/repo-1',
displayName: 'Repo One',
badgeColor: 'blue',
addedAt: 1,
hookSettings: {
mode: 'auto',
commandSourcePolicy: 'local-only',
scripts: { setup: 'echo local', archive: '' }
}
}
]
} as Partial<AppState>)
hooksCheckMock.mockResolvedValue({
hasHooks: true,
hooks: { scripts: { setup: 'echo shared' } },
mayNeedUpdate: false
})
const decision = await ensureHooksConfirmed(state, 'repo-1', 'setup')
expect(decision).toBe('run')
expect(hooksCheckMock).not.toHaveBeenCalled()
expect(pending).toHaveLength(0)
})
it('does not prompt for orca.yaml when local commands are the implicit default', async () => {
const { state, pending } = createTestState({
repos: [
{
id: 'repo-1',
path: '/repo-1',
displayName: 'Repo One',
badgeColor: 'blue',
addedAt: 1,
hookSettings: {
mode: 'auto',
scripts: { setup: 'echo local', archive: '' }
}
}
]
} as Partial<AppState>)
hooksCheckMock.mockResolvedValue({
hasHooks: true,
hooks: { scripts: { setup: 'echo shared' } },
mayNeedUpdate: false
})
const decision = await ensureHooksConfirmed(state, 'repo-1', 'setup')
expect(decision).toBe('run')
expect(hooksCheckMock).not.toHaveBeenCalled()
expect(pending).toHaveLength(0)
})
it('returns run without prompting when issueCommand source is local (user-owned)', async () => {
const { state, pending } = createTestState()
readIssueCommandMock.mockResolvedValue({
source: 'local',
sharedContent: null,
localContent: 'user content',
effectiveContent: 'user content',
localFilePath: ''
})
const decision = await ensureHooksConfirmed(state, 'repo-1', 'issueCommand')
expect(decision).toBe('run')
expect(pending).toHaveLength(0)
})
it('still honors local issueCommand overrides when shared inspection reports an error', async () => {
const { state, pending } = createTestState()
readIssueCommandMock.mockResolvedValue({
@@ -394,31 +257,6 @@ describe('ensureHooksConfirmed', () => {
expect(pending).toHaveLength(0)
})
it('forwards the explicit host to issueCommand inspection when repo ids collide', async () => {
const { state } = createTestState({
repos: [
{ id: 'repo-1', displayName: 'Local Row' },
{ id: 'repo-1', displayName: 'SSH Row', connectionId: 'server' }
]
} as unknown as Partial<AppState>)
readIssueCommandMock.mockResolvedValue({
source: 'local',
sharedContent: null,
localContent: 'user content',
effectiveContent: 'user content',
localFilePath: ''
})
await ensureHooksConfirmed(state, 'repo-1', 'issueCommand', 'ssh:server')
// A launch is a user act, so its read may start a stopped WSL distro (E9).
expect(readIssueCommandMock).toHaveBeenCalledWith({
repoId: 'repo-1',
hostId: 'ssh:server',
wakeWslDistro: true
})
})
it('approves and returns the exact issue-command bytes from one host-qualified read', async () => {
const { state, pending } = createTestState({
repos: [
@@ -529,30 +367,6 @@ describe('ensureHooksConfirmed', () => {
expect(pending).toHaveLength(0)
})
it('opens a modal with the computed content hash and resolves with the user decision', async () => {
const { state, pending } = createTestState()
hooksCheckMock.mockResolvedValue({
hasHooks: true,
hooks: { scripts: { setup: 'pnpm install' } },
mayNeedUpdate: false
})
const promise = ensureHooksConfirmed(state, 'repo-1', 'setup')
await vi.waitFor(() => expect(pending).toHaveLength(1))
expect(pending[0].data).toMatchObject({
repoId: 'repo-1',
repoName: 'Repo One',
scriptKind: 'setup',
scriptContent: 'pnpm install',
contentHash: await hashOrcaHookScript('pnpm install'),
previouslyApproved: false
})
pending[0].resolve('run')
await expect(promise).resolves.toBe('run')
})
it('serializes overlapping prompts so a second call waits for the first to resolve', async () => {
const { state, pending } = createTestState()
hooksCheckMock.mockResolvedValue({
@@ -246,17 +246,6 @@ describe('ensureSimulatorTab', () => {
drawn.store = null
})
it('activates an existing simulator tab through unified tab state', async () => {
const { ensureSimulatorTab } = await import('./ensure-simulator-tab')
expect(ensureSimulatorTab('wt-1')).toBe('sim-1')
expect(mockStoreState.activateTab).toHaveBeenCalledWith('sim-1')
expect(mockStoreState.setActiveTab).not.toHaveBeenCalled()
expect(mockStoreState.focusGroup).toHaveBeenCalledWith('wt-1', 'group-1')
expect(mockStoreState.setActiveTabType).toHaveBeenCalledWith('simulator', 'wt-1')
})
it('does not reuse a simulator owned by a same-id sibling host', async () => {
mockStoreState.activeWorkspaceExecutionHostId = 'runtime:host-b'
mockStoreState.allWorktrees.mockReturnValue([
@@ -470,47 +459,6 @@ describe('ensureSimulatorTab', () => {
expect(mockStoreState.setActiveTabType).toHaveBeenCalledWith('simulator', 'wt-1')
})
it('falls back to the source group when atomic right split creation fails', async () => {
mockStoreState.unifiedTabsByWorktree = { 'wt-1': [] }
mockStoreState.createUnifiedTabInSplit.mockReturnValue(null)
stubCreatedSimulatorTab('group-1')
const { ensureSimulatorTab } = await import('./ensure-simulator-tab')
if (ORCA_LAYOUT_OWNER_IS_MAIN) {
const { tabId, tab, sent, store } = await openThroughMain(() =>
ensureSimulatorTab('wt-1', { placement: 'rightSplit' })
)
expect(sent).toEqual([
expect.objectContaining({ tabId, split: { besideGroupId: 'group-1', direction: 'right' } })
])
expect(tab).toMatchObject({ contentType: 'simulator', label: 'Mobile Emulator' })
expectSurfaced(store, tab!)
return
}
expect(ensureSimulatorTab('wt-1', { placement: 'rightSplit' })).toBe(createdSimulatorTabId())
expect(mockStoreState.createUnifiedTabInSplit).toHaveBeenCalledWith(
'wt-1',
'simulator',
{
sourceGroupId: 'group-1',
splitDirection: 'right'
},
{
label: 'Mobile Emulator',
activate: true
}
)
expect(mockStoreState.createUnifiedTab).toHaveBeenCalledWith('wt-1', 'simulator', {
id: expect.any(String),
label: 'Mobile Emulator',
targetGroupId: 'group-1',
activate: true
})
expect(mockStoreState.dropUnifiedTab).not.toHaveBeenCalled()
expect(mockStoreState.focusGroup).toHaveBeenCalledWith('wt-1', 'group-1')
})
it('does not create a split for background auto-attach', async () => {
mockStoreState.unifiedTabsByWorktree = { 'wt-1': [] }
stubCreatedSimulatorTab('group-1')
@@ -624,14 +572,4 @@ describe('ensureSimulatorTab', () => {
)
expect(mockStoreState.createUnifiedTab).not.toHaveBeenCalled()
})
it('does not create or focus a simulator tab when disabled in settings', async () => {
mockStoreState.settings = { mobileEmulatorEnabled: false }
const { ensureSimulatorTab } = await import('./ensure-simulator-tab')
expect(ensureSimulatorTab('wt-1')).toBeNull()
expect(mockStoreState.activateTab).not.toHaveBeenCalled()
expect(mockStoreState.createUnifiedTab).not.toHaveBeenCalled()
})
})
@@ -1,34 +0,0 @@
import { describe, expect, it } from 'vitest'
import { shouldShutdownSimulatorForPaneUnmountFromTabs } from './simulator-tab-shutdown'
describe('shouldShutdownSimulatorForPaneUnmountFromTabs', () => {
it('keeps the simulator alive when the same tab still exists during a remount', () => {
expect(
shouldShutdownSimulatorForPaneUnmountFromTabs(
[{ id: 'sim-1', contentType: 'simulator' }],
'sim-1'
)
).toBe(false)
})
it('shuts down when the last simulator tab has been removed', () => {
expect(
shouldShutdownSimulatorForPaneUnmountFromTabs(
[{ id: 'terminal-1', contentType: 'terminal' }],
'sim-1'
)
).toBe(true)
})
it('keeps the simulator alive when another simulator tab remains', () => {
expect(
shouldShutdownSimulatorForPaneUnmountFromTabs(
[
{ id: 'terminal-1', contentType: 'terminal' },
{ id: 'sim-2', contentType: 'simulator' }
],
'sim-1'
)
).toBe(false)
})
})