mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
* Add host-owned automations flow to mobile * fix: resolve initial mobile automation CI failures * Preserve automation target identity and mobile action state * Preserve selected automation targets through discovery and terminal launch * Cover draft reversion after uncertain automation creation * Preserve automation target and submitted action authority * Preserve selected hosts and editor operations across restored state * Refresh mobile web bundle measurements for merged routes * Fix automation history observation and catalog scaling * Fix automation mobile presentation and merged launch bindings * Refresh measured mobile bundle sweep and correct automation test inputs * Move real-module test loads to collection and consolidate imports * Use typed row access to count worktree ownership reads * Refresh selected automation history on matching host events * Fix automation contract types and merged test imports * Fix automation theme updates and committed history ownership * Update folder terminal contract fixtures for canonical host catalogs * Consolidate historical session activation fixture imports * Repair automation contract project boundaries and owner type ports * Preserve acquired automation folder owners and fix integration lint * Retain terminal pane owners captured at connect * Use checked pane manager context in owner retention test * fix: adapt merged editor and terminal test contracts * fix: preserve captured automation targets and terminal owners * fix: honor explicit automation source clears and refusal contracts * test: construct terminal transport mocks with checked types * test: supply retained-owner context in terminal fixtures * test: align terminal fixtures with callable mocks and store ports * fix(mobile): localize automation screens and run details * fix(mobile): use automation translations in context * fix: preserve editor owner checks and automation repair guidance * fix: reconcile captured workspace contracts after main sync * Repair automation integration tests and retained target diagnostics * Keep test support owned by registered unit roots * Expose full blocked automation run reasons in menus * Anchor mobile bundle envelope to measured route sweep * fix(mobile): localize coded automation run failures * fix: preserve automation decisions and captured resource ownership * fix: qualify remaining automation workspace owner consumers * fix: preserve resolved watch and child editor owners * fix(editor): share qualified worktree watch ownership * fix(editor): reuse immutable repo ownership lookup * fix: preserve host ownership without duplicate catalog scans * fix: align automation destination host types and ratchets * fix: align mobile bundle envelope with measured sweep * fix(mobile): resolve automation setup policy before save * test(mobile): qualify reconciled automation target inspection * fix(mobile): keep server setup consent within its authority * fix: accept narrow qualified workspace owner snapshots * fix: reuse qualified owner state for workspace directories * fix(mobile): localize known automation orphan reasons * Correct automation controller and Chat harness types * Refresh editor requests after paired host restart * Refresh measured mobile web bundle envelope
622 lines
20 KiB
TypeScript
622 lines
20 KiB
TypeScript
// @vitest-environment happy-dom
|
|
|
|
import { createElement } from 'react'
|
|
import { act, cleanup, render } from '@testing-library/react'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { shallow } from 'zustand/shallow'
|
|
import type { AppState } from '@/store/types'
|
|
import type { RuntimeFileOperationArgs } from '../../src/renderer/src/runtime/runtime-file-client-types'
|
|
import { useAppStore } from '@/store'
|
|
import type { TerminalTab } from '../../src/shared/terminal-tab-types'
|
|
import type { Tab } from '../../src/shared/tab-types'
|
|
import { FLOATING_TERMINAL_WORKTREE_ID, getDefaultSettings } from '../../src/shared/constants'
|
|
import {
|
|
createNativeChatImageOwnerSelector,
|
|
createNativeChatImageRuntimeContextSelector,
|
|
resolveNativeChatImageRuntimeContext,
|
|
resolveNativeChatImageRuntimeContextForOwner,
|
|
selectNativeChatImageOwnerState,
|
|
useNativeChatImageRuntimeContext
|
|
} from '../../src/renderer/src/components/native-chat/native-chat-image-runtime-context'
|
|
import {
|
|
makeFolderWorkspace,
|
|
makeProjectGroup
|
|
} from '../../src/renderer/src/components/sidebar/folder-workspace-composer-submit-fixtures'
|
|
import { resolveNativeChatFileLinkContext } from '../../src/renderer/src/components/native-chat/native-chat-file-link'
|
|
import {
|
|
resolveNativeChatSkillDiscoveryContext,
|
|
selectNativeChatSkillStateInputs
|
|
} from '../../src/renderer/src/components/native-chat/native-chat-skill-discovery-context'
|
|
import { getEditorFileOperationContext } from '@/lib/editor-file-operation-owner'
|
|
import { captureWorktreeSshMutationExpectation } from '@/lib/ssh-mutation-expectation'
|
|
import { adoptFromEndpoint } from '@/store/adopt-from-endpoint'
|
|
import { assertRuntimeFileMutationExpectation } from '../../src/main/runtime/files/file-mutation-expectation'
|
|
import {
|
|
resetSshConnectionGenerations,
|
|
setSshConnectionGeneration
|
|
} from '../../src/main/ssh/connection/ssh-connection-generation'
|
|
import {
|
|
agentSessionTabFixture,
|
|
nativeChatFanoutFixture,
|
|
nativeChatFanoutWorkspace,
|
|
repoFixture,
|
|
terminalTabFixture,
|
|
worktreeFixture
|
|
} from '../../src/renderer/src/components/native-chat/native-chat-workspace-test-fixtures'
|
|
|
|
const originalState = useAppStore.getState()
|
|
afterEach(() => {
|
|
cleanup()
|
|
useAppStore.setState(originalState, true)
|
|
})
|
|
|
|
function state(overrides: Partial<AppState> = {}): AppState {
|
|
const tab: TerminalTab = {
|
|
id: 'tab-1',
|
|
ptyId: null,
|
|
worktreeId: 'wt-1',
|
|
title: 'Terminal 1',
|
|
customTitle: null,
|
|
color: null,
|
|
sortOrder: 0,
|
|
createdAt: 0
|
|
}
|
|
const worktree = worktreeFixture('wt-1', '/repo/worktree', { hostId: 'local' })
|
|
return {
|
|
...useAppStore.getInitialState(),
|
|
activeWorkspaceExecutionHostId: 'local',
|
|
activeWorktreeId: 'wt-1',
|
|
detectedWorktreesByRepo: {},
|
|
folderWorkspaces: [],
|
|
getKnownWorktreeById: () => worktree,
|
|
projectGroups: [],
|
|
removedRuntimeEnvironmentIds: new Set(),
|
|
repos: [repoFixture()],
|
|
restoredRuntimeHostIdByWorkspaceSessionKey: {},
|
|
runtimeEnvironmentCatalogHydrated: true,
|
|
runtimeEnvironments: [],
|
|
settings: { ...getDefaultSettings('/home/me'), activeRuntimeEnvironmentId: null },
|
|
sshConnectionStates: new Map(),
|
|
sshStateByEnvironment: new Map(),
|
|
tabsByWorktree: { 'wt-1': [tab] },
|
|
unifiedTabsByWorktree: {},
|
|
worktreesByRepo: { repo: [worktree] },
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
describe('resolveNativeChatImageRuntimeContext', () => {
|
|
it.each(['inherited', 'adopted'] as const)(
|
|
'retains a paired folder target through real file, image, skill and write contexts: %s',
|
|
(shape) => {
|
|
const target = { kind: 'environment', environmentId: 'owner-a' } as const
|
|
const original = makeFolderWorkspace({
|
|
id: 'paired-folder',
|
|
projectGroupId: 'paired-project',
|
|
folderPath: '/remote/folder',
|
|
connectionId: 'box'
|
|
})
|
|
const folder =
|
|
shape === 'adopted'
|
|
? adoptFromEndpoint(target, {
|
|
kind: 'folderWorkspace',
|
|
row: original,
|
|
resolveOwnHostId: () => 'ssh:box' as const
|
|
})
|
|
: { ...original, connectionId: null }
|
|
const workspaceId = `folder:${folder.id}`
|
|
const snapshot = state({
|
|
activeWorktreeId: workspaceId,
|
|
activeWorkspaceExecutionHostId: 'runtime:owner-a',
|
|
folderWorkspaces: [folder],
|
|
projectGroups: [
|
|
adoptFromEndpoint(target, {
|
|
kind: 'projectGroup',
|
|
row: {
|
|
...makeProjectGroup(),
|
|
id: 'paired-project',
|
|
executionHostId: 'ssh:box',
|
|
connectionId: 'box'
|
|
}
|
|
})
|
|
],
|
|
repos: [],
|
|
worktreesByRepo: {},
|
|
tabsByWorktree: {
|
|
[workspaceId]: [{ ...state().tabsByWorktree['wt-1'][0], worktreeId: workspaceId }]
|
|
},
|
|
runtimeEnvironments: [],
|
|
sshStateByEnvironment: new Map([
|
|
[
|
|
'owner-a',
|
|
{
|
|
targetsHydrated: true,
|
|
targetLabels: new Map([['box', 'Server box']]),
|
|
targetGenerations: new Map([['box', 7]]),
|
|
removedTargetLabels: new Map(),
|
|
connectionStates: new Map([
|
|
[
|
|
'box',
|
|
{
|
|
targetId: 'box',
|
|
status: 'connected',
|
|
error: null,
|
|
reconnectAttempt: 0,
|
|
connectionGeneration: 7
|
|
}
|
|
]
|
|
])
|
|
}
|
|
]
|
|
])
|
|
})
|
|
expect.soft(resolveNativeChatFileLinkContext(snapshot, 'tab-1')).toMatchObject({
|
|
worktreeId: workspaceId,
|
|
worktreePath: '/remote/folder',
|
|
runtimeEnvironmentId: 'owner-a'
|
|
})
|
|
expect
|
|
.soft(
|
|
resolveNativeChatImageRuntimeContext(selectNativeChatImageOwnerState(snapshot), 'tab-1')
|
|
)
|
|
.toMatchObject({
|
|
target: { kind: 'environment', environmentId: 'owner-a' },
|
|
worktreePath: '/remote/folder',
|
|
expectedExecutionHostId: 'ssh:box',
|
|
expectedSshConnectionGeneration: 7
|
|
})
|
|
expect
|
|
.soft(
|
|
resolveNativeChatSkillDiscoveryContext(
|
|
selectNativeChatSkillStateInputs(snapshot),
|
|
'tab-1'
|
|
)
|
|
)
|
|
.toMatchObject({
|
|
cwd: '/remote/folder',
|
|
runtimeTarget: { kind: 'environment', environmentId: 'owner-a' },
|
|
discoveryTarget: { worktreeId: workspaceId }
|
|
})
|
|
const editor = getEditorFileOperationContext(
|
|
snapshot,
|
|
{ worktreeId: workspaceId },
|
|
'/remote/folder'
|
|
)
|
|
expect.soft(editor).toMatchObject({
|
|
target: { kind: 'environment', environmentId: 'owner-a' },
|
|
expectedExecutionHostId: 'ssh:box',
|
|
expectedSshConnectionGeneration: 7
|
|
})
|
|
resetSshConnectionGenerations()
|
|
setSshConnectionGeneration('box', 7)
|
|
expect
|
|
.soft(() =>
|
|
assertRuntimeFileMutationExpectation(
|
|
'ssh:box',
|
|
editor.expectedExecutionHostId,
|
|
editor.expectedSshTargetId,
|
|
editor.expectedSshConnectionGeneration
|
|
)
|
|
)
|
|
.not.toThrow()
|
|
resetSshConnectionGenerations()
|
|
expect.soft(captureWorktreeSshMutationExpectation(snapshot, workspaceId)).toEqual({
|
|
expectedExecutionHostId: 'ssh:box',
|
|
expectedSshTargetId: 'box',
|
|
expectedSshConnectionGeneration: 7
|
|
})
|
|
}
|
|
)
|
|
|
|
it('keeps unrelated store writes out of the image-owner selector', () => {
|
|
const storeState = state()
|
|
const first = selectNativeChatImageOwnerState(storeState)
|
|
const second = selectNativeChatImageOwnerState({
|
|
...storeState,
|
|
agentStatusByPaneKey: {} as AppState['agentStatusByPaneKey']
|
|
})
|
|
|
|
expect(shallow(second, first)).toBe(true)
|
|
})
|
|
|
|
it("keeps tab changes in other workspaces out of a chat's owner inputs", () => {
|
|
const storeState = state()
|
|
const select = createNativeChatImageOwnerSelector('tab-1')
|
|
const first = select(storeState)
|
|
const otherWorkspaceTab = { ...storeState.tabsByWorktree['wt-1'][0], id: 'tab-2' }
|
|
const second = select({
|
|
...storeState,
|
|
tabsByWorktree: { ...storeState.tabsByWorktree, 'wt-2': [otherWorkspaceTab] },
|
|
unifiedTabsByWorktree: { 'wt-2': [] }
|
|
})
|
|
|
|
expect(first.owner).toBe('wt-1')
|
|
expect(shallow(second, first)).toBe(true)
|
|
})
|
|
|
|
it('keeps the selected context across rank and catalog-timestamp writes until its path moves', () => {
|
|
const storeState = state()
|
|
const select = createNativeChatImageRuntimeContextSelector('tab-1')
|
|
const first = select(storeState)
|
|
const worktree = storeState.worktreesByRepo.repo[0]
|
|
const ranked = {
|
|
...storeState,
|
|
worktreesByRepo: { repo: [{ ...worktree, sortOrder: 9, isUnread: true }] },
|
|
runtimeEnvironments: [...storeState.runtimeEnvironments]
|
|
}
|
|
|
|
expect(first?.worktreePath).toBe('/repo/worktree')
|
|
expect(select(ranked)).toBe(first)
|
|
|
|
const moved = worktreeFixture('wt-1', '/repo/moved', { hostId: 'local' })
|
|
expect(
|
|
select({
|
|
...ranked,
|
|
getKnownWorktreeById: () => moved,
|
|
worktreesByRepo: { repo: [moved] }
|
|
})?.worktreePath
|
|
).toBe('/repo/moved')
|
|
})
|
|
|
|
it('reuses derived settings when owner inputs are unchanged', () => {
|
|
const storeState = state()
|
|
const first = resolveNativeChatImageRuntimeContext(storeState, 'tab-1')
|
|
const second = resolveNativeChatImageRuntimeContext(storeState, 'tab-1')
|
|
|
|
expect(first).not.toBeNull()
|
|
expect(second?.target).toBe(first?.target)
|
|
expect(shallow(second, first)).toBe(true)
|
|
})
|
|
|
|
it('derives a runtime host from an owner-only route during paired hydration', () => {
|
|
const storeState = state()
|
|
const ownerOnlyWorktree = {
|
|
id: 'wt-1',
|
|
repoId: 'repo',
|
|
path: '/repo/worktree',
|
|
runtimeOwnerEnvironmentId: 'owner-a'
|
|
}
|
|
const ownerState = {
|
|
...storeState,
|
|
activeWorktreeId: null,
|
|
activeWorkspaceExecutionHostId: null,
|
|
getKnownWorktreeById: () => ownerOnlyWorktree,
|
|
worktreesByRepo: { repo: [ownerOnlyWorktree] },
|
|
runtimeEnvironments: [{ id: 'owner-a' }]
|
|
} as unknown as AppState
|
|
|
|
const context = resolveNativeChatImageRuntimeContext(ownerState, 'tab-1')
|
|
|
|
expect(context).toMatchObject({
|
|
worktreeId: 'wt-1',
|
|
worktreePath: '/repo/worktree',
|
|
expectedExecutionHostId: 'local',
|
|
target: { kind: 'environment' as const, environmentId: 'owner-a' }
|
|
})
|
|
})
|
|
|
|
it('reads nothing for an SSH-owned workspace with no paired server to serve it', () => {
|
|
const worktree = worktreeFixture('wt-1', '/remote/worktree', { hostId: 'ssh:ssh-1' })
|
|
const sshState = state({
|
|
activeWorkspaceExecutionHostId: 'ssh:ssh-1',
|
|
getKnownWorktreeById: () => worktree,
|
|
repos: [repoFixture({ connectionId: 'ssh-1' })],
|
|
worktreesByRepo: { repo: [worktree] }
|
|
})
|
|
|
|
expect(resolveNativeChatImageRuntimeContext(sshState, 'tab-1')).toBeNull()
|
|
})
|
|
|
|
it('resolves a floating chat to its pinned folder on the local host, and nothing before the pin', () => {
|
|
const floatingTab: Tab = {
|
|
id: 'floating-chat-1',
|
|
worktreeId: FLOATING_TERMINAL_WORKTREE_ID,
|
|
groupId: 'floating-group',
|
|
contentType: 'agent-session',
|
|
entityId: 'session-1',
|
|
label: 'Codex Chat',
|
|
customLabel: null,
|
|
color: null,
|
|
sortOrder: 0,
|
|
createdAt: 0,
|
|
isPinned: false,
|
|
agentSessionAgent: 'codex'
|
|
}
|
|
const floatingState: AppState = {
|
|
...state(),
|
|
tabsByWorktree: {},
|
|
unifiedTabsByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: [floatingTab] },
|
|
getKnownWorktreeById: () => undefined,
|
|
worktreesByRepo: {},
|
|
// Why a focused runtime: floating must stay local even when one is selected.
|
|
settings: { ...getDefaultSettings('/home/me'), activeRuntimeEnvironmentId: 'env-1' },
|
|
floatingWorkspacePath: '/home/me/changed-setting'
|
|
}
|
|
|
|
expect(resolveNativeChatImageRuntimeContext(floatingState, 'floating-chat-1')).toBeNull()
|
|
expect(
|
|
resolveNativeChatImageRuntimeContext(
|
|
{
|
|
...floatingState,
|
|
structuredSessionLaunchDirectoryByTabId: {
|
|
'floating-chat-1': { sessionId: 'session-1', launchDirectory: '/home/me/pinned' }
|
|
}
|
|
},
|
|
'floating-chat-1'
|
|
)
|
|
).toMatchObject({
|
|
worktreeId: FLOATING_TERMINAL_WORKTREE_ID,
|
|
worktreePath: '/home/me/pinned',
|
|
expectedExecutionHostId: 'local',
|
|
target: { kind: 'local' as const }
|
|
})
|
|
})
|
|
|
|
it('keeps selected-hook results equivalent to full-state resolution through ownership, pin and host transitions', () => {
|
|
const base = state({
|
|
worktreesByRepo: {
|
|
repo: [
|
|
worktreeFixture('wt-1', '/repo/worktree', { hostId: 'local' }),
|
|
worktreeFixture('wt-2', '/repo/second', { hostId: 'local' })
|
|
]
|
|
}
|
|
})
|
|
const duplicate = {
|
|
...base,
|
|
tabsByWorktree: {
|
|
'wt-1': [terminalTabFixture('tab-1', 'wt-1')],
|
|
'wt-2': [terminalTabFixture('tab-1', 'wt-2')]
|
|
},
|
|
unifiedTabsByWorktree: {
|
|
'wt-2': [agentSessionTabFixture('tab-1', 'wt-2')],
|
|
'wt-1': [agentSessionTabFixture('tab-1', 'wt-1')]
|
|
}
|
|
}
|
|
const fallback = { ...duplicate, tabsByWorktree: {} }
|
|
const moved = {
|
|
...fallback,
|
|
unifiedTabsByWorktree: { 'wt-1': [agentSessionTabFixture('tab-1', 'wt-1')] }
|
|
}
|
|
const floatingTab = agentSessionTabFixture('tab-1', FLOATING_TERMINAL_WORKTREE_ID)
|
|
const floating = {
|
|
...base,
|
|
tabsByWorktree: {},
|
|
unifiedTabsByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: [floatingTab] },
|
|
structuredSessionLaunchDirectoryByTabId: {},
|
|
floatingWorkspacePath: '/floating-setting'
|
|
}
|
|
const pinned = {
|
|
...floating,
|
|
structuredSessionLaunchDirectoryByTabId: {
|
|
'tab-1': { sessionId: floatingTab.entityId, launchDirectory: '/pinned' }
|
|
}
|
|
}
|
|
const replaced = {
|
|
...pinned,
|
|
unifiedTabsByWorktree: {
|
|
[FLOATING_TERMINAL_WORKTREE_ID]: [{ ...floatingTab, entityId: 'replacement' }]
|
|
}
|
|
}
|
|
const folder = nativeChatFanoutFixture(0, 1, 0)
|
|
const folderState = {
|
|
...base,
|
|
...folder,
|
|
unifiedTabsByWorktree: {
|
|
[nativeChatFanoutWorkspace(0)]: [
|
|
agentSessionTabFixture('tab-1', nativeChatFanoutWorkspace(0))
|
|
]
|
|
}
|
|
}
|
|
const environment = (id: string): AppState['runtimeEnvironments'][number] => ({
|
|
id,
|
|
name: id,
|
|
createdAt: 0,
|
|
updatedAt: 0,
|
|
lastUsedAt: null,
|
|
runtimeId: null,
|
|
preferredEndpointId: 'endpoint',
|
|
endpoints: []
|
|
})
|
|
const remote = (id: string): AppState => ({
|
|
...base,
|
|
activeWorkspaceExecutionHostId: `runtime:${id}`,
|
|
runtimeEnvironments: [environment(id)],
|
|
repos: [repoFixture({ executionHostId: `runtime:${id}` })],
|
|
worktreesByRepo: {
|
|
repo: [worktreeFixture('wt-1', '/remote', { hostId: `runtime:${id}` })]
|
|
}
|
|
})
|
|
const ssh = (generation?: number): AppState => ({
|
|
...remote('owner-a'),
|
|
activeWorkspaceExecutionHostId: 'ssh:ssh-1',
|
|
worktreesByRepo: {
|
|
repo: [
|
|
worktreeFixture('wt-1', '/ssh', {
|
|
hostId: 'ssh:ssh-1',
|
|
runtimeOwnerEnvironmentId: 'owner-a'
|
|
})
|
|
]
|
|
},
|
|
sshStateByEnvironment: new Map([
|
|
[
|
|
'owner-a',
|
|
{
|
|
targets: [],
|
|
targetGenerations: new Map(),
|
|
targetLabels: new Map(),
|
|
removedTargetLabels: new Map(),
|
|
targetsHydrated: true,
|
|
connectionStates: new Map([
|
|
[
|
|
'ssh-1',
|
|
{
|
|
targetId: 'ssh-1',
|
|
status: 'connected',
|
|
error: null,
|
|
reconnectAttempt: 0,
|
|
connectionGeneration: generation
|
|
}
|
|
]
|
|
])
|
|
}
|
|
]
|
|
])
|
|
})
|
|
const local = (worktreeId: string, worktreePath: string) => ({
|
|
worktreeId,
|
|
worktreePath,
|
|
target: { kind: 'local' as const }
|
|
})
|
|
const paired = ssh(3)
|
|
const cases: [
|
|
name: string,
|
|
snapshot: AppState,
|
|
expected: Partial<RuntimeFileOperationArgs> | null
|
|
][] = [
|
|
['terminal-first and first duplicate', duplicate, local('wt-1', '/repo/worktree')],
|
|
['unified fallback', fallback, local('wt-2', '/repo/second')],
|
|
['move', moved, local('wt-1', '/repo/worktree')],
|
|
['remove', { ...moved, unifiedTabsByWorktree: {} }, null],
|
|
['return', fallback, local('wt-2', '/repo/second')],
|
|
['missing pin', floating, null],
|
|
[
|
|
'wrong pin',
|
|
{
|
|
...pinned,
|
|
structuredSessionLaunchDirectoryByTabId: {
|
|
'tab-1': { sessionId: 'wrong', launchDirectory: '/wrong' }
|
|
}
|
|
},
|
|
null
|
|
],
|
|
['correct pin', pinned, local(FLOATING_TERMINAL_WORKTREE_ID, '/pinned')],
|
|
['replaced session', replaced, null],
|
|
[
|
|
'replaced pin',
|
|
{
|
|
...replaced,
|
|
structuredSessionLaunchDirectoryByTabId: {
|
|
'tab-1': { sessionId: 'replacement', launchDirectory: '/replacement' }
|
|
}
|
|
},
|
|
local(FLOATING_TERMINAL_WORKTREE_ID, '/replacement')
|
|
],
|
|
[
|
|
'floating directory predicate skips unregistered duplicate',
|
|
{
|
|
...pinned,
|
|
unifiedTabsByWorktree: {
|
|
[FLOATING_TERMINAL_WORKTREE_ID]: [
|
|
{ ...floatingTab, entityId: 'unregistered', agentSessionAgent: undefined },
|
|
floatingTab
|
|
]
|
|
}
|
|
},
|
|
local(FLOATING_TERMINAL_WORKTREE_ID, '/pinned')
|
|
],
|
|
[
|
|
'floating terminal fallback',
|
|
{
|
|
...floating,
|
|
tabsByWorktree: {
|
|
[FLOATING_TERMINAL_WORKTREE_ID]: [
|
|
terminalTabFixture('tab-1', FLOATING_TERMINAL_WORKTREE_ID)
|
|
]
|
|
},
|
|
unifiedTabsByWorktree: {}
|
|
},
|
|
local(FLOATING_TERMINAL_WORKTREE_ID, '/floating-setting')
|
|
],
|
|
[
|
|
'floating first registered duplicate wins',
|
|
{
|
|
...pinned,
|
|
unifiedTabsByWorktree: {
|
|
[FLOATING_TERMINAL_WORKTREE_ID]: [floatingTab, { ...floatingTab, entityId: 'second' }]
|
|
},
|
|
structuredSessionLaunchDirectoryByTabId: {
|
|
'tab-1': { sessionId: 'second', launchDirectory: '/wrong-second' }
|
|
}
|
|
},
|
|
null
|
|
],
|
|
['folder', folderState, local(nativeChatFanoutWorkspace(0), '/repo/0')],
|
|
[
|
|
'runtime',
|
|
remote('owner-a'),
|
|
{ worktreePath: '/remote', target: { kind: 'environment', environmentId: 'owner-a' } }
|
|
],
|
|
[
|
|
'runtime replacement',
|
|
remote('owner-b'),
|
|
{ target: { kind: 'environment', environmentId: 'owner-b' } }
|
|
],
|
|
[
|
|
'owner-only runtime hydration',
|
|
{
|
|
...remote('owner-a'),
|
|
activeWorktreeId: null,
|
|
activeWorkspaceExecutionHostId: null,
|
|
worktreesByRepo: {
|
|
repo: [
|
|
worktreeFixture('wt-1', '/remote', {
|
|
hostId: undefined,
|
|
runtimeOwnerEnvironmentId: 'owner-a'
|
|
})
|
|
]
|
|
}
|
|
},
|
|
{ worktreePath: '/remote', target: { kind: 'environment', environmentId: 'owner-a' } }
|
|
],
|
|
[
|
|
'paired SSH',
|
|
paired,
|
|
{
|
|
worktreePath: '/ssh',
|
|
expectedExecutionHostId: 'ssh:ssh-1',
|
|
expectedSshConnectionGeneration: 3,
|
|
target: { kind: 'environment', environmentId: 'owner-a' }
|
|
}
|
|
],
|
|
[
|
|
'SSH generation-only change',
|
|
{ ...paired, sshStateByEnvironment: ssh(4).sshStateByEnvironment },
|
|
{ expectedSshConnectionGeneration: 4 }
|
|
],
|
|
[
|
|
'SSH generation absent',
|
|
{ ...paired, sshStateByEnvironment: ssh().sshStateByEnvironment },
|
|
null
|
|
],
|
|
[
|
|
'unpaired SSH',
|
|
{
|
|
...paired,
|
|
repos: [repoFixture({ connectionId: 'ssh-1' })],
|
|
worktreesByRepo: {
|
|
repo: [worktreeFixture('wt-1', '/ssh', { hostId: 'ssh:ssh-1' })]
|
|
}
|
|
},
|
|
null
|
|
]
|
|
]
|
|
let observed: RuntimeFileOperationArgs | null = null
|
|
function Probe() {
|
|
observed = useNativeChatImageRuntimeContext('tab-1')
|
|
return null
|
|
}
|
|
useAppStore.setState(base)
|
|
render(createElement(Probe))
|
|
for (const [name, snapshot, expected] of cases) {
|
|
act(() => useAppStore.setState(snapshot))
|
|
expect(observed, name).toEqual(resolveNativeChatImageRuntimeContext(snapshot, 'tab-1'))
|
|
if (expected === null) {
|
|
expect(observed, name).toBeNull()
|
|
} else {
|
|
expect(observed, name).toMatchObject(expected)
|
|
}
|
|
}
|
|
expect(resolveNativeChatImageRuntimeContextForOwner(base, 'tab-1', null)).toBeNull()
|
|
})
|
|
})
|