mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
Keep mobile preference tests in the mobile compiler project
This commit is contained in:
+26
-16
@@ -1,9 +1,8 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { act, cleanup, render } from '@testing-library/react'
|
||||
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
import { useMobileNativeChatQueueFollowUps } from '../../../../../mobile/src/session/use-mobile-native-chat-queue-follow-ups'
|
||||
import type { RpcClient } from '../../../../../mobile/src/transport/rpc-client'
|
||||
import type { ConnectionState, RpcResponse } from '../../../../../mobile/src/transport/types'
|
||||
import { useMobileNativeChatQueueFollowUps } from './use-mobile-native-chat-queue-follow-ups'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import type { ConnectionState, RpcResponse } from '../transport/types'
|
||||
|
||||
function connection() {
|
||||
let state: ConnectionState = 'connected'
|
||||
@@ -60,7 +59,15 @@ const reply = (behavior: string): RpcResponse => ({
|
||||
_meta: { runtimeId: 'r' }
|
||||
})
|
||||
|
||||
afterEach(cleanup)
|
||||
const renderers = new Set<ReactTestRenderer>()
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
for (const renderer of renderers) {
|
||||
renderer.unmount()
|
||||
}
|
||||
})
|
||||
renderers.clear()
|
||||
})
|
||||
|
||||
it('shares one live scalar per connection, fences late reads/events, and isolates real hook renders', async () => {
|
||||
const a = connection(),
|
||||
@@ -72,13 +79,17 @@ it('shares one live scalar per connection, fences late reads/events, and isolate
|
||||
renders.set(id, (renders.get(id) ?? 0) + 1)
|
||||
return null
|
||||
}
|
||||
const mounted = render(
|
||||
<>
|
||||
<Chat id="a1" client={a.client} />
|
||||
<Chat id="a2" client={a.client} />
|
||||
<Chat id="b" client={b.client} />
|
||||
</>
|
||||
)
|
||||
let mounted: ReactTestRenderer | undefined
|
||||
await act(async () => {
|
||||
mounted = create(
|
||||
<>
|
||||
<Chat id="a1" client={a.client} />
|
||||
<Chat id="a2" client={a.client} />
|
||||
<Chat id="b" client={b.client} />
|
||||
</>
|
||||
)
|
||||
renderers.add(mounted)
|
||||
})
|
||||
await act(async () => {})
|
||||
expect(a.client.subscribe).toHaveBeenCalledTimes(1)
|
||||
expect(b.client.subscribe).toHaveBeenCalledTimes(1)
|
||||
@@ -135,7 +146,7 @@ it('shares one live scalar per connection, fences late reads/events, and isolate
|
||||
expect(values.get('a1')).toBe(true)
|
||||
await act(async () => a.replies[1]!(reply('steer')))
|
||||
expect(values.get('a1')).toBe(true)
|
||||
mounted.unmount()
|
||||
await act(async () => mounted?.unmount())
|
||||
expect(a.states.size).toBe(0)
|
||||
expect(b.states.size).toBe(0)
|
||||
expect(a.stops).toHaveBeenCalledTimes(3)
|
||||
@@ -143,8 +154,7 @@ it('shares one live scalar per connection, fences late reads/events, and isolate
|
||||
})
|
||||
|
||||
it('existing phone workspace event reader ignores additive preference snapshot and events', async () => {
|
||||
const { startHostWorktreeRefresh } =
|
||||
await import('../../../../../mobile/src/worktree/host-worktree-refresh')
|
||||
const { startHostWorktreeRefresh } = await import('../worktree/host-worktree-refresh')
|
||||
const host = connection()
|
||||
const fetchWorktrees = vi.fn(async () => {}),
|
||||
fetchRepoMetadata = vi.fn(async () => {}),
|
||||
@@ -376,7 +376,14 @@ it('keeps the draft through Continue admission, refusal, uncertainty and recorde
|
||||
fence: 1,
|
||||
submissions: [],
|
||||
queuedMessageIds: [],
|
||||
queue: { capability: 'unsupported', enabled: false },
|
||||
queue: {
|
||||
capability: 'unsupported',
|
||||
followUpsQueue: false,
|
||||
isWorking: false,
|
||||
stopping: false,
|
||||
session: {},
|
||||
prompts: []
|
||||
},
|
||||
historyLoaded: false,
|
||||
sendOptions: () => options
|
||||
})
|
||||
|
||||
@@ -111,6 +111,7 @@ export function createStructuredSessionMocks() {
|
||||
retryLaunch: vi.fn<(worktreeId: string, sessionId: string) => unknown>(),
|
||||
relaunchWithMessage: vi.fn<(worktreeId: string, sessionId: string, text: string) => void>(),
|
||||
controllerProps: nullable<{ transportEnabled?: boolean }>(),
|
||||
controllerRender: vi.fn<(sessionId: string, queueFollowUps: boolean | undefined) => void>(),
|
||||
send: vi.fn<(text: string, attachments?: unknown[]) => boolean>(() => true),
|
||||
status: 'ready' as 'idle' | 'loading' | 'ready' | 'error',
|
||||
readRefusal: absent<AgentSessionRefusalReference>(),
|
||||
@@ -182,7 +183,9 @@ export function createStructuredSessionMocks() {
|
||||
sessionId: string
|
||||
target: { kind: 'local' } | { kind: 'environment'; environmentId: string }
|
||||
transportEnabled?: boolean
|
||||
queueFollowUps?: boolean
|
||||
}) => {
|
||||
mocks.controllerRender(props.sessionId, props.queueFollowUps)
|
||||
mocks.controllerProps = props
|
||||
return {
|
||||
contextClear: { clearing: false, clears: 0 },
|
||||
@@ -363,6 +366,7 @@ export function createStructuredSessionMocks() {
|
||||
mocks.relaunchWithMessage.mockReset()
|
||||
mocks.lifecycleLookup.mockReset()
|
||||
mocks.controllerProps = null
|
||||
mocks.controllerRender.mockClear()
|
||||
mocks.send.mockClear()
|
||||
mocks.status = 'ready'
|
||||
mocks.readRefusal = undefined
|
||||
|
||||
@@ -35,6 +35,61 @@ import { structuredAgentSessionPaneKey } from '../../../../shared/structured-age
|
||||
import { structuredAgentSessionDraftScopeKey } from './native-chat-composer-draft-store'
|
||||
|
||||
describe('NativeChatStructuredSession', () => {
|
||||
it('isolates mounted chats from unrelated settings and renders their live follow-up choice', async () => {
|
||||
const original = useAppStore.getState().settings
|
||||
const settings = { ...getDefaultSettings('/tmp'), nativeChatFollowUpBehavior: 'steer' as const }
|
||||
useAppStore.setState({ settings })
|
||||
const view = render(
|
||||
<>
|
||||
{['local-chat', 'ssh-chat'].map((sessionId) => (
|
||||
<NativeChatStructuredSession
|
||||
key={sessionId}
|
||||
isVisible
|
||||
isFocusedGroup
|
||||
tabId={sessionId}
|
||||
sessionId={sessionId}
|
||||
target={
|
||||
sessionId === 'local-chat'
|
||||
? { kind: 'local' }
|
||||
: { kind: 'environment', environmentId: 'ssh-host' }
|
||||
}
|
||||
agent="codex"
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
try {
|
||||
await act(async () => {})
|
||||
mocks.controllerRender.mockClear()
|
||||
act(() =>
|
||||
useAppStore.setState({ settings: { ...settings, nativeChatFollowUpBehavior: 'steer' } })
|
||||
)
|
||||
expect(mocks.controllerRender).not.toHaveBeenCalled()
|
||||
act(() =>
|
||||
useAppStore.setState({
|
||||
settings: { ...settings, showTasksButton: !settings.showTasksButton }
|
||||
})
|
||||
)
|
||||
expect(mocks.controllerRender).not.toHaveBeenCalled()
|
||||
act(() =>
|
||||
useAppStore.setState({ settings: { ...settings, nativeChatFollowUpBehavior: 'queue' } })
|
||||
)
|
||||
expect(new Set(mocks.controllerRender.mock.calls.map(([sessionId]) => sessionId))).toEqual(
|
||||
new Set(['local-chat', 'ssh-chat'])
|
||||
)
|
||||
expect(mocks.controllerRender.mock.calls.every(([, queue]) => queue === true)).toBe(true)
|
||||
mocks.controllerRender.mockClear()
|
||||
act(() => useAppStore.setState({ settings }))
|
||||
expect(new Set(mocks.controllerRender.mock.calls.map(([sessionId]) => sessionId))).toEqual(
|
||||
new Set(['local-chat', 'ssh-chat'])
|
||||
)
|
||||
expect(mocks.controllerRender.mock.calls.every(([, queue]) => queue === false)).toBe(true)
|
||||
} finally {
|
||||
view.unmount()
|
||||
useAppStore.setState({ settings: original })
|
||||
}
|
||||
})
|
||||
|
||||
it('applies persisted appearance at the chat root and updates it live', () => {
|
||||
const original = useAppStore.getState().settings
|
||||
useAppStore.setState({
|
||||
|
||||
@@ -177,6 +177,9 @@ export function registerRuntimeClientIpcBridge(
|
||||
if (event.type === 'sidebarInputsChanged') {
|
||||
return
|
||||
}
|
||||
if (event.type !== 'activateWorktree') {
|
||||
return
|
||||
}
|
||||
// Older hosts broadcast local/CLI activation without an address; that is not this viewer's intent.
|
||||
if (!event.navigation || !navigationTargetsClients(event.navigation)) {
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user