mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
test: remove low-value host caller and UI tests (#42465)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -4097,8 +4097,6 @@
|
||||
"src/renderer/src/components/NewWorkspaceComposerCard.set-location.test.tsx": 1301,
|
||||
"src/renderer/src/components/SelectedTextCopyMenu.test.tsx": 329,
|
||||
"src/renderer/src/components/UpdateCard.error-card.test.tsx": 2851,
|
||||
"src/renderer/src/components/UpdateCard.test.ts": 694,
|
||||
"src/renderer/src/components/WorktreeBaseFallbackDialog.test.tsx": 2104,
|
||||
"src/renderer/src/components/activity/activity-auto-mark-read-loop.react185.test.tsx": 5607,
|
||||
"src/renderer/src/components/activity/activity-clear-completed-hmr.test.ts": 814,
|
||||
"src/renderer/src/components/activity/activity-clear-completed-state-lifetime.test.ts": 4609,
|
||||
|
||||
@@ -47,14 +47,6 @@ describe('orchestration dispatch coordinator handle', () => {
|
||||
json
|
||||
} as never)
|
||||
|
||||
const invokeRun = (flags: Map<string, string | boolean>) =>
|
||||
ORCHESTRATION_HANDLERS['orchestration coordinator-start']({
|
||||
flags,
|
||||
client: { call: callMock },
|
||||
cwd: '/tmp/repo',
|
||||
json: true
|
||||
} as never)
|
||||
|
||||
it('rejects stale coordinator env handles when the caller pane cannot be proven', async () => {
|
||||
process.env.ORCA_TERMINAL_HANDLE = 'term_stale_coord'
|
||||
callMock.mockRejectedValueOnce(staleHandleError())
|
||||
@@ -74,51 +66,4 @@ describe('orchestration dispatch coordinator handle', () => {
|
||||
expect(callMock).toHaveBeenCalledTimes(1)
|
||||
expect(getTerminalHandleMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('retires the legacy coordinator command without runtime effects', async () => {
|
||||
await expect(
|
||||
invokeRun(new Map<string, string | boolean>([['spec', 'run the plan']]))
|
||||
).rejects.toMatchObject({
|
||||
code: 'orchestration_migration_required',
|
||||
data: {
|
||||
reason: 'command_retired',
|
||||
effectsApplied: false,
|
||||
nextCommandArgs: ['skills', 'get', 'orchestration', '--full']
|
||||
}
|
||||
})
|
||||
expect(callMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('orchestration task-create caller handle', () => {
|
||||
beforeEach(() => {
|
||||
callMock.mockReset()
|
||||
getTerminalHandleMock.mockReset()
|
||||
delete process.env.ORCA_TERMINAL_HANDLE
|
||||
delete process.env.ORCA_PANE_KEY
|
||||
})
|
||||
|
||||
const invokeTaskCreate = (flags: Map<string, string | boolean>) =>
|
||||
ORCHESTRATION_HANDLERS['orchestration task-create']({
|
||||
flags,
|
||||
client: { call: callMock },
|
||||
cwd: '/tmp/repo',
|
||||
json: true
|
||||
} as never)
|
||||
|
||||
it('fails closed when a stale task creator handle cannot be reminted', async () => {
|
||||
process.env.ORCA_TERMINAL_HANDLE = 'term_stale'
|
||||
callMock.mockRejectedValueOnce(staleHandleError())
|
||||
getTerminalHandleMock.mockResolvedValue('term_wrong_active')
|
||||
|
||||
await expect(
|
||||
invokeTaskCreate(new Map<string, string | boolean>([['spec', 'do work']]))
|
||||
).rejects.toMatchObject({ code: 'no_active_sender_terminal' })
|
||||
|
||||
expect(callMock).toHaveBeenNthCalledWith(1, 'terminal.resolveIdentity', {
|
||||
terminal: 'term_stale'
|
||||
})
|
||||
expect(getTerminalHandleMock).not.toHaveBeenCalled()
|
||||
expect(callMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
// Inherited pane identity and an available sibling must never override the injected session caller.
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const callMock = vi.hoisted(() => vi.fn())
|
||||
const getTerminalHandleMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('../../output/command-result-output', () => ({ printResult: vi.fn() }))
|
||||
vi.mock('../../terminals/terminal-selector', () => ({ getTerminalHandle: getTerminalHandleMock }))
|
||||
|
||||
import { ORCHESTRATION_HANDLERS } from '../orchestration-handlers'
|
||||
import { findCommandSpec } from '../../arguments/args'
|
||||
import { COMMAND_SPECS } from '../../specs/index'
|
||||
import { refuseConflictingSessionCallerFlags } from './session-caller-flags'
|
||||
import { createOrchestrationCompatibilityEnvelope } from './orchestration-compatibility-envelope'
|
||||
|
||||
const SESSION = 'f7a1c0de-1111-4222-8333-444455556666'
|
||||
const IDENTITY_ENV = [
|
||||
'ORCA_AGENT_SESSION_ID',
|
||||
'ORCA_TERMINAL_HANDLE',
|
||||
'ORCA_PANE_KEY',
|
||||
'ORCA_STRUCTURED_SESSION'
|
||||
] as const
|
||||
const originalEnv = Object.fromEntries(IDENTITY_ENV.map((name) => [name, process.env[name]]))
|
||||
|
||||
const RESULT = {
|
||||
result: {
|
||||
run: { id: 'run_1', objective: 'o', consumer_generation: 1 },
|
||||
runs: [],
|
||||
nextCursor: null,
|
||||
messages: [],
|
||||
count: 0,
|
||||
message: { id: 'msg_1' },
|
||||
lifecycle: { action: 'completed' },
|
||||
dispatch: { id: 'dispatch_1', task_id: 'task_1', status: 'dispatched' },
|
||||
gate: { id: 'gate_1', task_id: 'task_1', status: 'pending', resolution: 'r' },
|
||||
gates: [],
|
||||
task: { id: 'task_1', status: 'pending' },
|
||||
tasks: [],
|
||||
answer: 'yes',
|
||||
messageId: 'msg_1',
|
||||
threadId: 'thread_1',
|
||||
timedOut: false,
|
||||
state: 'ready',
|
||||
runId: 'run_1',
|
||||
taskId: 'task_1',
|
||||
dispatchId: 'dispatch_1',
|
||||
effects: [],
|
||||
residualResources: [],
|
||||
workers: [],
|
||||
counts: {}
|
||||
}
|
||||
}
|
||||
|
||||
/** dispatch-show is excluded: its --from fills preview text rather than naming the caller. */
|
||||
|
||||
function flagMap(flags: Record<string, string | true>): Map<string, string | boolean> {
|
||||
return new Map(Object.entries(flags))
|
||||
}
|
||||
|
||||
async function invoke(
|
||||
command: string,
|
||||
flags: Map<string, string | boolean>,
|
||||
json = true
|
||||
): Promise<void> {
|
||||
const handler = ORCHESTRATION_HANDLERS[`orchestration ${command}`]
|
||||
if (!handler) {
|
||||
throw new Error(`no handler for ${command}`)
|
||||
}
|
||||
refuseConflictingSessionCallerFlags(
|
||||
findCommandSpec(COMMAND_SPECS, ['orchestration', command]),
|
||||
flags
|
||||
)
|
||||
await handler({
|
||||
flags,
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: these handlers read only `call`; RuntimeClient is a class, so a structural double cannot satisfy it without the cast.
|
||||
client: { call: callMock } as never,
|
||||
cwd: '/tmp/repo',
|
||||
json
|
||||
})
|
||||
}
|
||||
|
||||
function setEnv(env: Partial<Record<(typeof IDENTITY_ENV)[number], string>>): void {
|
||||
for (const name of IDENTITY_ENV) {
|
||||
const value = env[name]
|
||||
if (value === undefined) {
|
||||
delete process.env[name]
|
||||
} else {
|
||||
process.env[name] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
callMock.mockReset().mockResolvedValue(RESULT)
|
||||
getTerminalHandleMock.mockReset().mockResolvedValue('term_sibling')
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
setEnv(originalEnv)
|
||||
process.exitCode = undefined
|
||||
})
|
||||
|
||||
describe('the identity a session presents', () => {
|
||||
it('keeps the identity-less refusal, without --from advice, for a child that has no id', async () => {
|
||||
setEnv({ ORCA_STRUCTURED_SESSION: '1' })
|
||||
|
||||
await expect(invoke('reply', flagMap({ id: 'msg_1', body: 'b' }))).rejects.toMatchObject({
|
||||
code: 'no_active_sender_terminal',
|
||||
message: expect.not.stringContaining('Pass --from')
|
||||
})
|
||||
expect(getTerminalHandleMock).not.toHaveBeenCalled()
|
||||
expect(callMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('the orchestration envelope', () => {
|
||||
it('carries the injected id beside whatever terminal evidence the process also has', () => {
|
||||
const envelope = createOrchestrationCompatibilityEnvelope({
|
||||
ORCA_AGENT_SESSION_ID: ` ${SESSION} `,
|
||||
ORCA_TERMINAL_HANDLE: 'term_inherited_pane'
|
||||
})
|
||||
|
||||
expect(envelope.orchestrationCompatibilityEvidence).toEqual({
|
||||
terminalHandle: 'term_inherited_pane',
|
||||
agentSessionId: SESSION
|
||||
})
|
||||
})
|
||||
|
||||
it('claims no session without an injected id', () => {
|
||||
expect(
|
||||
createOrchestrationCompatibilityEnvelope({ ORCA_AGENT_SESSION_ID: ' ' })
|
||||
.orchestrationCompatibilityEvidence
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
import { mkdtempSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as BrowserClientHostIdModule from './browser-client-host-id'
|
||||
|
||||
function profileDirectory(): string {
|
||||
return mkdtempSync(join(tmpdir(), 'orca-host-id-'))
|
||||
}
|
||||
|
||||
async function freshModule(): Promise<typeof BrowserClientHostIdModule> {
|
||||
vi.resetModules()
|
||||
return await import('./browser-client-host-id')
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
describe('browser client host id', () => {
|
||||
it('answers the same durable id to every launch of one profile', async () => {
|
||||
const directory = profileDirectory()
|
||||
|
||||
const first = await freshModule()
|
||||
first.initializeBrowserClientHostId(directory)
|
||||
const second = await freshModule()
|
||||
second.initializeBrowserClientHostId(directory)
|
||||
|
||||
expect(first.getBrowserClientHostId()).toBe(second.getBrowserClientHostId())
|
||||
})
|
||||
|
||||
it('keeps the id a renderer already holds when startup resolves the profile too late', async () => {
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const module = await freshModule()
|
||||
|
||||
const stamped = module.getBrowserClientHostId()
|
||||
module.initializeBrowserClientHostId(profileDirectory())
|
||||
|
||||
expect(module.getBrowserClientHostId()).toBe(stamped)
|
||||
expect(warn).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
-31
@@ -84,37 +84,6 @@ describe('PairedRuntimeBrowserClientHostComposition', () => {
|
||||
expect(rig.routes.reconnect).toHaveBeenCalledTimes(2)
|
||||
expect(rig.executor.handle).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('fails closed without racing page cleanup when handlers do not settle', async () => {
|
||||
const rig = createRig({ hostSettled: false })
|
||||
const composition = rig.createComposition()
|
||||
await composition.start()
|
||||
|
||||
await expect(composition.close()).resolves.toBe(false)
|
||||
|
||||
expect(rig.order).toEqual([
|
||||
'activate-routes',
|
||||
'closing',
|
||||
'suspend-routes',
|
||||
'fence-navigation',
|
||||
'close-host',
|
||||
'close-routes'
|
||||
])
|
||||
expect(rig.executor.close).not.toHaveBeenCalled()
|
||||
|
||||
rig.settleHandlers()
|
||||
await composition.whenClosed()
|
||||
|
||||
expect(rig.order).toEqual([
|
||||
'activate-routes',
|
||||
'closing',
|
||||
'suspend-routes',
|
||||
'fence-navigation',
|
||||
'close-host',
|
||||
'close-routes',
|
||||
'close-executor'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
function createRig(
|
||||
|
||||
+2
-52
@@ -33,41 +33,6 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('PairedRuntimeBrowserClientHost reconnect', () => {
|
||||
it('reattaches once with the refreshed unavailable-page inventory', async () => {
|
||||
const attempts = mockAttempts()
|
||||
let state: 'active' | 'outcomeUnknown' = 'active'
|
||||
const host = new PairedRuntimeBrowserClientHost({
|
||||
connection: pairedRuntimeConnectionFromOffer(pairing),
|
||||
authorityRuntimeId: 'runtime-a',
|
||||
browserHostClientId: 'host-a',
|
||||
hostCapabilities: ['webview'],
|
||||
handler: vi.fn(() => ({ status: 'completed' as const })),
|
||||
getPageInventory: () => [inventoryPage(state)],
|
||||
pageReconciliationProtocolVersion: 1,
|
||||
reconnectRetryDelayMs: 1
|
||||
})
|
||||
const starting = host.start()
|
||||
await vi.waitFor(() => expect(attempts).toHaveLength(1))
|
||||
expect(subscribeRemoteRuntimeRequestMock.mock.calls[0]?.[2]).toMatchObject({
|
||||
pageInventory: [expect.objectContaining({ state: 'active' })]
|
||||
})
|
||||
attempts[0]!.callbacks.onResponse(readyResponse({ reconciliation: true }))
|
||||
await starting
|
||||
|
||||
state = 'outcomeUnknown'
|
||||
const refreshing = host.refreshPageInventory()
|
||||
await vi.waitFor(() => expect(attempts).toHaveLength(2))
|
||||
expect(attempts[0]!.close).toHaveBeenCalledOnce()
|
||||
expect(subscribeRemoteRuntimeRequestMock.mock.calls[1]?.[2]).toMatchObject({
|
||||
pageInventory: [expect.objectContaining({ state: 'outcomeUnknown' })]
|
||||
})
|
||||
attempts[1]!.callbacks.onResponse(readyResponse({ reconciliation: true }))
|
||||
|
||||
await expect(refreshing).resolves.toBeUndefined()
|
||||
expect(attempts).toHaveLength(2)
|
||||
await host.close()
|
||||
})
|
||||
|
||||
it('replays a completed mutation without executing its handler twice', async () => {
|
||||
const attempts = mockAttempts()
|
||||
const handler = vi.fn(() => ({ status: 'completed' as const }))
|
||||
@@ -139,7 +104,7 @@ function mockAttempts(): {
|
||||
return attempts
|
||||
}
|
||||
|
||||
function readyResponse(options: { reconciliation?: boolean } = {}) {
|
||||
function readyResponse() {
|
||||
return {
|
||||
id: 'browser-host',
|
||||
ok: true as const,
|
||||
@@ -149,27 +114,12 @@ function readyResponse(options: { reconciliation?: boolean } = {}) {
|
||||
browserHostGeneration: 4,
|
||||
pageCommandProtocolVersion: 1 as const,
|
||||
pageInventoryProtocolVersion: 1 as const,
|
||||
leaseReconnectProtocolVersion: 1 as const,
|
||||
...(options.reconciliation ? { pageReconciliationProtocolVersion: 1 as const } : {})
|
||||
leaseReconnectProtocolVersion: 1 as const
|
||||
},
|
||||
_meta: { runtimeId: 'runtime-a' }
|
||||
}
|
||||
}
|
||||
|
||||
function inventoryPage(state: 'active' | 'outcomeUnknown') {
|
||||
return {
|
||||
authorityRuntimeId: 'runtime-a',
|
||||
authorityEpoch: 'epoch-a',
|
||||
browserHostClientId: 'host-a',
|
||||
browserHostGeneration: 4,
|
||||
browserPageId: 'page-a',
|
||||
pageHostGeneration: 1,
|
||||
browserProfileId: 'default',
|
||||
executionHostKey: 'native:runtime-a:1',
|
||||
state
|
||||
} as const
|
||||
}
|
||||
|
||||
function commandResponse() {
|
||||
return {
|
||||
id: 'browser-host',
|
||||
|
||||
-232
@@ -1,232 +0,0 @@
|
||||
import type { subscribeRemoteRuntimeTransport } from '../../../../shared/remote-runtime/requests/remote-runtime-subscription-transport'
|
||||
import { pairedRuntimeConnectionFromOffer } from '../../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { PairingOffer } from '../../../../shared/remote-runtime/pairing/mobile-relay-pairing-offer'
|
||||
import type {
|
||||
RemoteRuntimeTransportSubscription,
|
||||
RemoteRuntimeTransportSubscriptionCallbacks
|
||||
} from '../../../../shared/remote-runtime/requests/remote-runtime-subscription-contract'
|
||||
|
||||
const { subscribeRemoteRuntimeRequestMock } = vi.hoisted(() => ({
|
||||
subscribeRemoteRuntimeRequestMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('../../../../shared/remote-runtime/requests/remote-runtime-subscription-transport', () => ({
|
||||
subscribeRemoteRuntimeTransport: subscribeRemoteRuntimeRequestMock
|
||||
}))
|
||||
|
||||
import { PairedRuntimeBrowserHostLease } from '../leases/paired-runtime-browser-host-lease'
|
||||
import type { PairedRuntimeBrowserHostLeaseOptions } from '../leases/paired-runtime-browser-host-lease-options'
|
||||
|
||||
const pairing = {
|
||||
v: 2,
|
||||
endpoint: 'ws://127.0.0.1:6768',
|
||||
deviceToken: 'device-token',
|
||||
publicKeyB64: 'public-key',
|
||||
pairedDeviceId: 'device-a',
|
||||
scope: 'runtime'
|
||||
} as PairingOffer
|
||||
|
||||
afterEach(() => {
|
||||
subscribeRemoteRuntimeRequestMock.mockReset()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe('paired runtime browser host command admission', () => {
|
||||
it('rejects page commands when an old host did not negotiate them', async () => {
|
||||
const { callbacks, close } = await subscribeLease({
|
||||
sendRequest: undefined
|
||||
})
|
||||
const onError = vi.fn()
|
||||
const onPageCommand = vi.fn()
|
||||
const lease = createLease({
|
||||
pageCommandProtocolVersion: 1,
|
||||
onPageCommand,
|
||||
onError
|
||||
})
|
||||
const starting = lease.start()
|
||||
await vi.waitFor(() => expect(callbacks.current).toBeDefined())
|
||||
callbacks.current!.onResponse({
|
||||
id: 'browser-host',
|
||||
ok: true,
|
||||
result: {
|
||||
type: 'ready',
|
||||
authorityEpoch: 'epoch-a',
|
||||
browserHostGeneration: 4
|
||||
},
|
||||
_meta: { runtimeId: 'runtime-a' }
|
||||
})
|
||||
await starting
|
||||
|
||||
callbacks.current!.onResponse({
|
||||
id: 'browser-host',
|
||||
ok: true,
|
||||
result: {
|
||||
type: 'command',
|
||||
pageCommandProtocolVersion: 1,
|
||||
authorityRuntimeId: 'runtime-a',
|
||||
authorityEpoch: 'epoch-a',
|
||||
browserHostClientId: 'host-a',
|
||||
browserHostGeneration: 4,
|
||||
browserPageId: 'page-a',
|
||||
pageHostGeneration: 1,
|
||||
commandSequence: 1,
|
||||
commandId: 'command-a',
|
||||
command: {
|
||||
type: 'createPage',
|
||||
browserProfileId: 'default',
|
||||
executionHostKey: 'native:runtime-a:1'
|
||||
}
|
||||
},
|
||||
_meta: { runtimeId: 'runtime-a' }
|
||||
})
|
||||
|
||||
await vi.waitFor(() => expect(close).toHaveBeenCalledOnce())
|
||||
expect(onPageCommand).not.toHaveBeenCalled()
|
||||
expect(onError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Unnegotiated browser host page command'
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['authorityEpoch', { authorityEpoch: 'epoch-b' }],
|
||||
['authorityRuntimeId', { authorityRuntimeId: 'runtime-b' }],
|
||||
['browserHostClientId', { browserHostClientId: 'host-b' }],
|
||||
['browserHostGeneration', { browserHostGeneration: 5 }]
|
||||
])(
|
||||
'rejects a negotiated command whose %s is stale before delivery',
|
||||
async (_field, staleness) => {
|
||||
const { callbacks, close } = await subscribeLease()
|
||||
const onError = vi.fn()
|
||||
const onPageCommand = vi.fn()
|
||||
const lease = createLease({
|
||||
pageCommandProtocolVersion: 1,
|
||||
onPageCommand,
|
||||
onError
|
||||
})
|
||||
const starting = lease.start()
|
||||
await vi.waitFor(() => expect(callbacks.current).toBeDefined())
|
||||
callbacks.current!.onResponse({
|
||||
id: 'browser-host',
|
||||
ok: true,
|
||||
result: {
|
||||
type: 'ready',
|
||||
authorityEpoch: 'epoch-a',
|
||||
browserHostGeneration: 4,
|
||||
pageCommandProtocolVersion: 1
|
||||
},
|
||||
_meta: { runtimeId: 'runtime-a' }
|
||||
})
|
||||
await starting
|
||||
|
||||
callbacks.current!.onResponse({
|
||||
id: 'browser-host',
|
||||
ok: true,
|
||||
result: {
|
||||
type: 'command',
|
||||
pageCommandProtocolVersion: 1,
|
||||
authorityRuntimeId: 'runtime-a',
|
||||
authorityEpoch: 'epoch-a',
|
||||
browserHostClientId: 'host-a',
|
||||
browserHostGeneration: 4,
|
||||
browserPageId: 'page-a',
|
||||
pageHostGeneration: 1,
|
||||
commandSequence: 1,
|
||||
commandId: 'command-a',
|
||||
command: {
|
||||
type: 'createPage',
|
||||
browserProfileId: 'default',
|
||||
executionHostKey: 'native:runtime-a:1'
|
||||
},
|
||||
...staleness
|
||||
},
|
||||
_meta: { runtimeId: 'runtime-a' }
|
||||
})
|
||||
|
||||
await vi.waitFor(() => expect(close).toHaveBeenCalledOnce())
|
||||
expect(onPageCommand).not.toHaveBeenCalled()
|
||||
expect(onError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: 'Stale browser host page command' })
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it('refuses an inventory refresh on a lease that never negotiated reconnect', async () => {
|
||||
const { callbacks, close } = await subscribeLease()
|
||||
const lease = createLease({
|
||||
pageInventoryProtocolVersion: 1,
|
||||
getPageInventory: () => []
|
||||
})
|
||||
const starting = lease.start()
|
||||
await vi.waitFor(() => expect(callbacks.current).toBeDefined())
|
||||
callbacks.current!.onResponse({
|
||||
id: 'browser-host',
|
||||
ok: true,
|
||||
result: {
|
||||
type: 'ready',
|
||||
authorityEpoch: 'epoch-a',
|
||||
browserHostGeneration: 4,
|
||||
pageInventoryProtocolVersion: 1
|
||||
},
|
||||
_meta: { runtimeId: 'runtime-a' }
|
||||
})
|
||||
await starting
|
||||
|
||||
await expect(lease.refreshPageInventory()).rejects.toThrow(
|
||||
'Browser host inventory refresh is unavailable'
|
||||
)
|
||||
expect(close).not.toHaveBeenCalled()
|
||||
await lease.close()
|
||||
})
|
||||
})
|
||||
|
||||
function createLease(
|
||||
overrides: Partial<PairedRuntimeBrowserHostLeaseOptions> = {}
|
||||
): PairedRuntimeBrowserHostLease {
|
||||
return new PairedRuntimeBrowserHostLease({
|
||||
connection: pairedRuntimeConnectionFromOffer(pairing),
|
||||
authorityRuntimeId: 'runtime-a',
|
||||
browserHostClientId: 'host-a',
|
||||
hostCapabilities: ['webview'],
|
||||
...overrides
|
||||
})
|
||||
}
|
||||
|
||||
async function subscribeLease(options?: {
|
||||
sendRequest?: RemoteRuntimeTransportSubscription['sendRequest']
|
||||
}): Promise<{
|
||||
callbacks: { current?: RemoteRuntimeTransportSubscriptionCallbacks }
|
||||
close: ReturnType<typeof vi.fn>
|
||||
}> {
|
||||
const callbacks: { current?: RemoteRuntimeTransportSubscriptionCallbacks } = {}
|
||||
const close = vi.fn()
|
||||
const sendRequest =
|
||||
options && 'sendRequest' in options
|
||||
? options.sendRequest
|
||||
: vi.fn().mockResolvedValue({
|
||||
id: 'command-result',
|
||||
ok: true,
|
||||
result: { accepted: true },
|
||||
_meta: { runtimeId: 'runtime-a' }
|
||||
})
|
||||
subscribeRemoteRuntimeRequestMock.mockImplementationOnce(
|
||||
async (
|
||||
...args: Parameters<typeof subscribeRemoteRuntimeTransport>
|
||||
): Promise<RemoteRuntimeTransportSubscription> => {
|
||||
callbacks.current = args[4]
|
||||
return {
|
||||
requestId: 'browser-host',
|
||||
close,
|
||||
sendBinary: () => false,
|
||||
...(sendRequest
|
||||
? {
|
||||
sendRequest: sendRequest
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}
|
||||
)
|
||||
return { callbacks, close }
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
import { createStore, type StoreApi } from 'zustand/vanilla'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { UpdateStatus } from '../../../shared/update-status-types'
|
||||
import { createUISlice } from '../store/slices/ui/create-ui-slice'
|
||||
import type { AppState } from '../store/types'
|
||||
import { isUpdateCardVisible } from './maintenance/update-card/update-card-visibility'
|
||||
|
||||
function createTestStore(): StoreApi<AppState> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return createStore<any>()((...args: any[]) => ({
|
||||
repos: [],
|
||||
rightSidebarWidth: 280,
|
||||
...createUISlice(...(args as Parameters<typeof createUISlice>))
|
||||
})) as unknown as StoreApi<AppState>
|
||||
}
|
||||
|
||||
function setState(store: StoreApi<AppState>, status: UpdateStatus): void {
|
||||
store.getState().setUpdateStatus(status)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('window', {
|
||||
api: {
|
||||
ui: { set: vi.fn().mockResolvedValue(undefined) },
|
||||
shell: { openUrl: vi.fn() },
|
||||
updater: {
|
||||
download: vi.fn().mockResolvedValue(undefined),
|
||||
quitAndInstall: vi.fn().mockResolvedValue(undefined),
|
||||
dismissNudge: vi.fn().mockResolvedValue(undefined),
|
||||
dismissAvailableUpdate: vi.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
},
|
||||
matchMedia: vi.fn().mockReturnValue({
|
||||
matches: false,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('updateCardCollapsed', () => {
|
||||
it('resets to false on every state transition so new phases re-surface', () => {
|
||||
const store = createTestStore()
|
||||
|
||||
setState(store, { state: 'downloading', percent: 20, version: '1.2.0' })
|
||||
store.getState().setUpdateCardCollapsed(true)
|
||||
expect(store.getState().updateCardCollapsed).toBe(true)
|
||||
|
||||
// Why: percent-only updates are not transitions and must not reset.
|
||||
setState(store, { state: 'downloading', percent: 50, version: '1.2.0' })
|
||||
expect(store.getState().updateCardCollapsed).toBe(true)
|
||||
|
||||
setState(store, { state: 'downloaded', version: '1.2.0' })
|
||||
expect(store.getState().updateCardCollapsed).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// Exercise visibility gates without a full DOM so irrelevant cards stay hidden and real updates remain visible.
|
||||
|
||||
type VisibilityInput = {
|
||||
status: UpdateStatus
|
||||
dismissedVersion: string | null
|
||||
cachedVersion: string | null
|
||||
updateUserInitiatedCycle?: boolean
|
||||
collapsed?: boolean
|
||||
}
|
||||
|
||||
type VisibilityResult = 'hidden' | 'visible'
|
||||
|
||||
function computeVisibility(input: VisibilityInput): VisibilityResult {
|
||||
return isUpdateCardVisible({
|
||||
...input,
|
||||
updateUserInitiatedCycle: input.updateUserInitiatedCycle ?? false
|
||||
})
|
||||
? 'visible'
|
||||
: 'hidden'
|
||||
}
|
||||
|
||||
describe('UpdateCard visibility gates', () => {
|
||||
it('hides check errors once a new checking cycle cleared the cached version', () => {
|
||||
const store = createTestStore()
|
||||
setState(store, { state: 'available', version: '1.2.0', changelog: null })
|
||||
setState(store, { state: 'checking' })
|
||||
setState(store, { state: 'error', message: 'network timeout' })
|
||||
|
||||
expect(
|
||||
computeVisibility({
|
||||
status: store.getState().updateStatus,
|
||||
collapsed: store.getState().updateCardCollapsed,
|
||||
dismissedVersion: '1.2.0',
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('hidden')
|
||||
})
|
||||
})
|
||||
|
||||
describe('full update lifecycle through setUpdateStatus', () => {
|
||||
it('dismiss → new version cycle → card visible again', () => {
|
||||
const store = createTestStore()
|
||||
|
||||
setState(store, { state: 'available', version: '1.2.0', changelog: null })
|
||||
store.getState().dismissUpdate()
|
||||
expect(store.getState().dismissedUpdateVersion).toBe('1.2.0')
|
||||
|
||||
setState(store, { state: 'checking' })
|
||||
setState(store, { state: 'available', version: '1.3.0', changelog: null })
|
||||
|
||||
expect(
|
||||
computeVisibility({
|
||||
status: store.getState().updateStatus,
|
||||
dismissedVersion: store.getState().dismissedUpdateVersion,
|
||||
cachedVersion: '1.3.0'
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
})
|
||||
@@ -1,61 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { act } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { useAppStore } from '@/store'
|
||||
import type { AppState } from '@/store/types'
|
||||
import WorktreeBaseFallbackDialog from './WorktreeBaseFallbackDialog'
|
||||
import {
|
||||
requestWorktreeBaseFallbackNotice,
|
||||
resetWorktreeBaseFallbackNoticesForTests
|
||||
} from './worktree-base-fallback-notice'
|
||||
|
||||
const initialState = useAppStore.getInitialState()
|
||||
let root: Root | null = null
|
||||
|
||||
async function renderDialog(activeModal: AppState['activeModal'] = 'none'): Promise<void> {
|
||||
useAppStore.setState({
|
||||
activeModal,
|
||||
setContextualToursBlockingSurfaceVisible: vi.fn()
|
||||
})
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
root = createRoot(container)
|
||||
await act(async () => {
|
||||
root?.render(<WorktreeBaseFallbackDialog />)
|
||||
})
|
||||
}
|
||||
|
||||
describe('WorktreeBaseFallbackDialog', () => {
|
||||
beforeEach(() => {
|
||||
useAppStore.setState(initialState, true)
|
||||
resetWorktreeBaseFallbackNoticesForTests()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await act(async () => {
|
||||
root?.unmount()
|
||||
})
|
||||
root = null
|
||||
document.body.innerHTML = ''
|
||||
resetWorktreeBaseFallbackNoticesForTests()
|
||||
useAppStore.setState(initialState, true)
|
||||
})
|
||||
|
||||
it('waits until another app modal closes', async () => {
|
||||
requestWorktreeBaseFallbackNotice({
|
||||
requestedRef: 'origin/main',
|
||||
localRef: 'main'
|
||||
})
|
||||
await renderDialog('new-workspace-composer')
|
||||
|
||||
expect(document.body.textContent).not.toContain('Workspace created from a local base')
|
||||
|
||||
await act(async () => {
|
||||
useAppStore.setState({ activeModal: 'none' })
|
||||
})
|
||||
|
||||
expect(document.body.textContent).toContain('Workspace created from a local base')
|
||||
})
|
||||
})
|
||||
@@ -28,8 +28,3 @@ export function subscribeWorktreeBaseFallbackNotice(listener: () => void): () =>
|
||||
listeners.add(listener)
|
||||
return () => listeners.delete(listener)
|
||||
}
|
||||
|
||||
export function resetWorktreeBaseFallbackNoticesForTests(): void {
|
||||
notices.length = 0
|
||||
notifyListeners()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user