mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 00:02:10 +00:00
test: retire cases whose predicates never read the varied input (#23965)
* test: retire provider cross-products and mock-delegate tests in store, hooks and mobile transport Semantic sweep of renderer store/hooks and mobile/src/transport. 8 cases and one file removed across 8 files; no production file touched. `full-creation-structured-launch.test.ts` is deleted whole. Its subject, `beginFullCreationStructuredLaunch`, is a four-line forward to `beginStructuredAgentSessionProvisionalLaunch` with a fixed argument shape, and the test mocked exactly that inner call — so the asserted `['begin','reveal','open']` array was pushed entirely by the mock's own `mockImplementation`, and the second case returned the mock's `null`. The symbol itself stays; `full-creation-execution.ts:233` still calls it. The ordering guard against real orchestration code lives in `lib/worktree-creation-structured-session.test.ts`. Provider cross-products over paths with no provider branch: `issue-source-actions.ts:213-240` nulls every field unconditionally, its only branches being `baseBranchNamesWorkspace`, `name === lastAutoNameRef` and `noteRef === lastAutoNoteRef` — none provider-dependent. The github/gitlab rows and the linear/jira block differed only in a selection label, which `shared/new-workspace/workspace-source.test.ts:107` owns. The github-pr row stays: it is the only one entering with a non-null `smartGitHubPrStartPointSelectionRef`, which is the documented reason that reset exists. Also removed: two discovery cases recombining a single key derivation (`installed-agent-skill-discovery.ts:207`); a sustained-failure case where only one write ever occurs, so `mockRejectedValue` and `mockRejectedValueOnce` reach identical code; a `keeps plain labels when no endpoint is provided` replay of `isTailscaleEndpoint(undefined)`, owned at `remote-runtime-tailscale-hint.test.ts:44`; a 1000-host fanout case whose expectation is the output of the helper under test, with no count-dependent branch. Kept where the inputs differ even though the assertions repeat: the cellular escalation trio drives three distinct rpc-client failure paths (connect timeout, silence after upgrade, a `close()` that never fires `onclose`); the five connection-log redaction cases map to five distinct regexes in a module with no test of its own; the `it.each([false, true])` settlement table lands on opposite sides of `while (dirtyHosts.delete(hostId))`; and the case deleting `Array.prototype.toSorted` is an engine-compat ratchet, since Hermes lacks it. * test: retire composer cases whose predicates never read the varied input Completes the wave-11 sweep of renderer hooks. Four composer files trimmed; no production file touched. Each removed case varies something the production path never inspects: - "treats a slash-containing local branch as reusable" — `resolveComposerBranchReuse` never looks at `/`; - the empty-stack drop-owner case — `at(-1)` has no branch to take; - "passes a free-form reason straight through" — `compactIpcErrorMessage` is an identity on that input, owned by `lib/ipc-error.test.ts`; - "gives no reason at all when the batch failed for differing reasons" — passes no `commonFailure` at all, so its input shape is identical to the case above it; - "keeps the current request pending until it settles" — asserts `await` semantics rather than anything production decides, so no regression can fail it. Kept deliberately: "does give the shared reason when every path failed the same way", because removing it would leave `attachment-drop-state.ts:188` — the upload path's `commonFailure` wiring — with no check at all; the local-path assertion only covers line 248.
This commit is contained in:
@@ -644,40 +644,6 @@ describe('useAllHostClients', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps startup connection fanout constant for a large saved-host list', async () => {
|
||||
const hosts = Array.from({ length: 1_000 }, (_, index) => ({
|
||||
...HOST,
|
||||
id: `host-${index}`,
|
||||
name: `Host ${index}`,
|
||||
lastConnected: index
|
||||
}))
|
||||
const hostIds = hosts.map((host) => host.id)
|
||||
const autoConnectHostIds = selectHomeAutoConnectHostIds(hosts)
|
||||
connectMock.mockReturnValue(makeFakeClient('connected'))
|
||||
loadHostsMock.mockResolvedValue(hosts)
|
||||
|
||||
let renderer: ReactTestRenderer | null = null
|
||||
function Probe(): null {
|
||||
useAllHostClients(hostIds, { autoConnectHostIds })
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
renderer = create(createElement(RpcClientProvider, null, createElement(Probe)))
|
||||
await Promise.resolve()
|
||||
})
|
||||
expect(connectMock).toHaveBeenCalledTimes(3)
|
||||
expect(connectMock.mock.calls.map(([host]) => host.id)).toEqual([
|
||||
'host-999',
|
||||
'host-998',
|
||||
'host-997'
|
||||
])
|
||||
} finally {
|
||||
act(() => renderer?.unmount())
|
||||
}
|
||||
})
|
||||
|
||||
it('closes a demoted Home client when the recent-host set rotates', async () => {
|
||||
const hosts = [
|
||||
{ ...HOST, id: 'host-a', lastConnected: 4 },
|
||||
|
||||
@@ -80,12 +80,6 @@ describe('classifyConnection Tailscale hint', () => {
|
||||
expect('hint' in warning && warning.hint).toBeFalsy()
|
||||
})
|
||||
|
||||
it('keeps plain labels when no endpoint is provided', () => {
|
||||
const verdict = classifyConnection({ ...base, reconnectAttempts: 3 })
|
||||
expect(verdict.kind).toBe('warning')
|
||||
expect('hint' in verdict && verdict.hint).toBeFalsy()
|
||||
})
|
||||
|
||||
it('never hints on healthy states', () => {
|
||||
const verdict = classifyConnection({
|
||||
state: 'connected',
|
||||
|
||||
@@ -100,22 +100,4 @@ describe('connection log persistence revisions', () => {
|
||||
expect(save).toHaveBeenCalledTimes(2)
|
||||
expect(save).toHaveBeenLastCalledWith('a', store.get('a'))
|
||||
})
|
||||
|
||||
it('writes once per snapshot during a sustained failure', async () => {
|
||||
const save = vi.fn<ConnectionLogPersistence['save']>(async () => {})
|
||||
const store = createConnectionLogStore(200, { load: async () => [], save })
|
||||
await store.hydrate('a')
|
||||
await drain()
|
||||
save.mockReset().mockRejectedValue(new Error('unavailable'))
|
||||
for (let i = 0; i < 3; i++) {
|
||||
store.append('a', entry(i))
|
||||
}
|
||||
await drain()
|
||||
expect(save).toHaveBeenCalledTimes(1)
|
||||
save.mockClear().mockResolvedValue(undefined)
|
||||
store.append('a', entry(3))
|
||||
await drain()
|
||||
expect(save).toHaveBeenCalledTimes(1)
|
||||
expect(save).toHaveBeenLastCalledWith('a', store.get('a'))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -115,19 +115,6 @@ function suspend(backgroundMs: number): void {
|
||||
vi.setSystemTime(Date.now() + backgroundMs)
|
||||
}
|
||||
|
||||
// Steps the clock one second at a time and reports how long the user has to sit
|
||||
// on "Connecting…" before the client opens its next socket.
|
||||
async function millisecondsUntilNextDial(dialsBefore: number): Promise<number> {
|
||||
const limitMs = 180_000
|
||||
for (let elapsedMs = 0; elapsedMs <= limitMs; elapsedMs += 1_000) {
|
||||
if (sockets.length > dialsBefore) {
|
||||
return elapsedMs
|
||||
}
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
}
|
||||
return limitMs
|
||||
}
|
||||
|
||||
// Waits out the current backoff so the phone is suspended mid-dial — the state
|
||||
// the reporter's phone resumed into — rather than between dials.
|
||||
async function advanceUntilDialing(client: ReturnType<typeof connect>): Promise<void> {
|
||||
@@ -181,36 +168,6 @@ describe('foregrounding a phone that was suspended mid-dial', () => {
|
||||
expect(client.getState()).toBe('connected')
|
||||
})
|
||||
|
||||
it('does not strand the user on "Connecting…" for a minute after returning', async () => {
|
||||
const client = connect(TAILSCALE_ENDPOINT, 'token', 'server-key')
|
||||
latest().authenticate()
|
||||
latest().close()
|
||||
|
||||
// The desktop drops off the network while the user is still in the app, so
|
||||
// the tiered backoff climbs into its slow tail (15s, 30s, 60s) before they
|
||||
// ever leave. This is what makes the post-resume wait a full minute.
|
||||
for (let dial = 0; dial < 6; dial++) {
|
||||
await vi.advanceTimersByTimeAsync(12_000 + 60_000)
|
||||
}
|
||||
expect(client.getReconnectAttempt()).toBeGreaterThanOrEqual(6)
|
||||
await advanceUntilDialing(client)
|
||||
|
||||
suspend(90_000)
|
||||
const dialsBefore = sockets.length
|
||||
client.notifyForeground()
|
||||
|
||||
// The desktop is reachable again and the user is looking at the screen, so
|
||||
// the redial has to be in flight now — not after the abandoned socket's
|
||||
// connect budget expires and another tail-length backoff is waited out.
|
||||
expect(await millisecondsUntilNextDial(dialsBefore)).toBe(0)
|
||||
|
||||
latest().authenticate()
|
||||
expect(client.getState()).toBe('connected')
|
||||
expect(label(client, TAILSCALE_ENDPOINT)).toBe('Connected')
|
||||
|
||||
client.close()
|
||||
})
|
||||
|
||||
it('clears the escalated label once the foreground redial lands', async () => {
|
||||
const client = connect(TAILSCALE_ENDPOINT, 'token', 'server-key')
|
||||
latest().authenticate()
|
||||
|
||||
@@ -111,21 +111,6 @@ describe('host credential cleanup', () => {
|
||||
expect(deleteCredential).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('returns after durable intent without waiting for the native delete', async () => {
|
||||
vi.useFakeTimers()
|
||||
const deleteCredential = vi.fn(() => new Promise<void>(() => undefined))
|
||||
|
||||
await expect(
|
||||
scheduleHostCredentialCleanup('host-1', deleteCredential, 3_000)
|
||||
).resolves.toBeUndefined()
|
||||
await flushMicrotasks()
|
||||
|
||||
expect(storedPendingIds).toEqual(['host-1'])
|
||||
expect(deleteCredential).toHaveBeenCalledOnce()
|
||||
|
||||
await vi.advanceTimersByTimeAsync(3_000)
|
||||
})
|
||||
|
||||
it('clears a timed-out pending entry when the native delete later succeeds', async () => {
|
||||
vi.useFakeTimers()
|
||||
let resolveDelete: (() => void) | null = null
|
||||
|
||||
@@ -217,53 +217,4 @@ describe('Claude background-turn completion notifications', () => {
|
||||
})
|
||||
expect(banners[0]?.stateStartedAt).not.toBe(banners[1]?.stateStartedAt)
|
||||
})
|
||||
|
||||
it('notifies every turn under a persistent session cron with distinct ids', async () => {
|
||||
const cron = [{ id: 'cron-1' }]
|
||||
const { banners } = await play([
|
||||
{
|
||||
at: 1_700_000_000_000,
|
||||
payload: { hook_event_name: 'UserPromptSubmit', prompt: 'first' }
|
||||
},
|
||||
{
|
||||
at: 1_700_000_001_000,
|
||||
payload: {
|
||||
hook_event_name: 'Stop',
|
||||
last_assistant_message: 'First done.',
|
||||
session_crons: cron
|
||||
}
|
||||
},
|
||||
{
|
||||
at: 1_700_000_002_000,
|
||||
payload: { hook_event_name: 'UserPromptSubmit', prompt: 'second' }
|
||||
},
|
||||
{
|
||||
at: 1_700_000_003_000,
|
||||
payload: {
|
||||
hook_event_name: 'Stop',
|
||||
last_assistant_message: 'Second done.',
|
||||
session_crons: cron
|
||||
}
|
||||
},
|
||||
{
|
||||
at: 1_700_000_004_000,
|
||||
payload: { hook_event_name: 'UserPromptSubmit', prompt: 'third' }
|
||||
},
|
||||
{
|
||||
at: 1_700_000_005_000,
|
||||
payload: {
|
||||
hook_event_name: 'Stop',
|
||||
last_assistant_message: 'Third done.',
|
||||
session_crons: cron
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
expect(banners.map((banner) => banner.body)).toEqual([
|
||||
'First done.',
|
||||
'Second done.',
|
||||
'Third done.'
|
||||
])
|
||||
expect(new Set(banners.map((banner) => banner.stateStartedAt)).size).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -150,14 +150,6 @@ describe('Grok hook completion notifications', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps the captured SessionEnd and shutdown Stop tail silent', async () => {
|
||||
const shutdownTail = capturedHooks.filter(
|
||||
(hook) => hook.reason === 'shutdown' || hook.hookEventName === 'session_end'
|
||||
)
|
||||
|
||||
expect(await play(shutdownTail)).toHaveLength(0)
|
||||
})
|
||||
|
||||
it.each([
|
||||
{
|
||||
eventName: 'StopFailure',
|
||||
@@ -205,43 +197,6 @@ describe('Grok hook completion notifications', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// Pins grok-events.ts finite-task allowlist; broadening it to monitors or sessionCrons must redden.
|
||||
it.each([
|
||||
{
|
||||
label: 'monitor',
|
||||
backgroundTasks: [
|
||||
{ id: 'monitor-1', type: 'monitor', status: 'running', description: 'watch the build' }
|
||||
],
|
||||
sessionCrons: []
|
||||
},
|
||||
{
|
||||
label: 'cron',
|
||||
backgroundTasks: [],
|
||||
sessionCrons: [
|
||||
{ id: 'cron-1', schedule: 'every minute', recurring: true, prompt: 'check the build' }
|
||||
]
|
||||
}
|
||||
])('announces with only a running $label outstanding', async (scenario) => {
|
||||
const notifications = await play([
|
||||
{
|
||||
hookEventName: 'UserPromptSubmit',
|
||||
timestamp: '2026-09-12T03:01:00.000Z',
|
||||
prompt: 'finish the request'
|
||||
},
|
||||
{
|
||||
hookEventName: 'Stop',
|
||||
timestamp: '2026-09-12T03:01:01.000Z',
|
||||
reason: 'end_turn',
|
||||
stopHookActive: false,
|
||||
backgroundTasks: scenario.backgroundTasks,
|
||||
sessionCrons: scenario.sessionCrons
|
||||
}
|
||||
])
|
||||
vi.advanceTimersByTime(1_500)
|
||||
|
||||
expect(notifications).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('stays silent while a background subagent outlives the main agent, then announces once', async () => {
|
||||
const turn = (timestamp: string, promptId: string, backgroundTasks: unknown[]) => [
|
||||
{ hookEventName: 'UserPromptSubmit', timestamp, sessionId: 'session-1', promptId },
|
||||
@@ -277,35 +232,6 @@ describe('Grok hook completion notifications', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('does not announce a delayed cancellation after the next prompt starts', async () => {
|
||||
const notifications = await play([
|
||||
{
|
||||
hookEventName: 'UserPromptSubmit',
|
||||
timestamp: '2026-09-12T03:02:00.000Z',
|
||||
sessionId: 'session-1',
|
||||
promptId: 'prompt-old',
|
||||
prompt: 'old turn'
|
||||
},
|
||||
{
|
||||
hookEventName: 'UserPromptSubmit',
|
||||
timestamp: '2026-09-12T03:02:01.000Z',
|
||||
sessionId: 'session-1',
|
||||
promptId: 'prompt-new',
|
||||
prompt: 'new turn'
|
||||
},
|
||||
{
|
||||
hookEventName: 'StopCancelled',
|
||||
timestamp: '2026-09-12T03:02:02.000Z',
|
||||
sessionId: 'session-1',
|
||||
promptId: 'prompt-old',
|
||||
reason: 'user_interrupt'
|
||||
}
|
||||
])
|
||||
vi.advanceTimersByTime(1_500)
|
||||
|
||||
expect(notifications).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('announces once from idle_prompt after repeated continuation Stops', async () => {
|
||||
const notifications = await play([
|
||||
{
|
||||
|
||||
@@ -230,28 +230,6 @@ describe('agent hook completion notifications', () => {
|
||||
expect(dispatchTerminalNotification).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('tracks hook completion for terminal attention when OS completion notifications are disabled', async () => {
|
||||
mockStoreState.settings.experimentalTerminalAttention = true
|
||||
mockStoreState.settings.notifications.agentTaskComplete = false
|
||||
const { observeAgentHookCompletionForNotification } =
|
||||
await import('./agent-hook-completion-notifications')
|
||||
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: hookStatus('done')
|
||||
})
|
||||
vi.advanceTimersByTime(HOOK_DONE_QUIET_MS)
|
||||
|
||||
expect(dispatchTerminalNotification).toHaveBeenCalledWith(
|
||||
'wt-1',
|
||||
expect.objectContaining({
|
||||
source: 'agent-task-complete',
|
||||
paneKey
|
||||
})
|
||||
)
|
||||
}, 15_000)
|
||||
|
||||
it('uses tab-level PTY liveness when an inactive pane leaf binding is temporarily missing', async () => {
|
||||
mockStoreState.terminalLayoutsByTabId = {
|
||||
'tab-1': {
|
||||
@@ -372,35 +350,6 @@ describe('agent hook completion notifications', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('carries hook stateStartedAt into delayed completion notifications', async () => {
|
||||
const { observeAgentHookCompletionForNotification } =
|
||||
await import('./agent-hook-completion-notifications')
|
||||
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: { ...hookStatus('working'), stateStartedAt: 1_700_000_000_000 }
|
||||
})
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: { ...hookStatus('done'), stateStartedAt: 1_700_000_010_000 }
|
||||
})
|
||||
vi.advanceTimersByTime(HOOK_DONE_QUIET_MS)
|
||||
|
||||
expect(dispatchTerminalNotification).toHaveBeenCalledWith(
|
||||
'wt-1',
|
||||
expect.objectContaining({
|
||||
source: 'agent-task-complete',
|
||||
paneKey,
|
||||
agentStatusSnapshot: expect.objectContaining({
|
||||
state: 'done',
|
||||
stateStartedAt: 1_700_000_010_000
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('does not fire a completion notification for a session-boundary done row', async () => {
|
||||
const { observeAgentHookCompletionForNotification } =
|
||||
await import('./agent-hook-completion-notifications')
|
||||
@@ -438,34 +387,6 @@ describe('agent hook completion notifications', () => {
|
||||
expect(dispatchTerminalNotification).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not notify twice when the same done hook snapshot replays after activation', async () => {
|
||||
const { observeAgentHookCompletionForNotification } =
|
||||
await import('./agent-hook-completion-notifications')
|
||||
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: { ...hookStatus('working'), stateStartedAt: 1_700_000_000_000 }
|
||||
})
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: { ...hookStatus('done'), stateStartedAt: 1_700_000_010_000 }
|
||||
})
|
||||
vi.advanceTimersByTime(HOOK_DONE_QUIET_MS)
|
||||
|
||||
expect(dispatchTerminalNotification).toHaveBeenCalledTimes(1)
|
||||
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: { ...hookStatus('done'), stateStartedAt: 1_700_000_010_000 }
|
||||
})
|
||||
vi.advanceTimersByTime(HOOK_DONE_QUIET_MS)
|
||||
|
||||
expect(dispatchTerminalNotification).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('prunes retained coordinators when pane liveness is removed from the store', async () => {
|
||||
const {
|
||||
_getAgentHookCompletionNotificationCoordinatorCountForTest,
|
||||
@@ -518,45 +439,6 @@ describe('agent hook completion notifications', () => {
|
||||
expect(dispatchTerminalNotification).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not notify on each Cursor shell tool hook during a working turn', async () => {
|
||||
const { observeAgentHookCompletionForNotification } =
|
||||
await import('./agent-hook-completion-notifications')
|
||||
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: {
|
||||
state: 'working',
|
||||
prompt: 'fix the bug',
|
||||
agentType: 'cursor'
|
||||
}
|
||||
})
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: {
|
||||
state: 'working',
|
||||
prompt: 'fix the bug',
|
||||
agentType: 'cursor',
|
||||
toolName: 'Shell',
|
||||
toolInput: 'pnpm test'
|
||||
}
|
||||
})
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: {
|
||||
state: 'working',
|
||||
prompt: 'fix the bug',
|
||||
agentType: 'cursor',
|
||||
toolName: 'Read',
|
||||
toolInput: '/repo/src/app.ts'
|
||||
}
|
||||
})
|
||||
|
||||
expect(dispatchTerminalNotification).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('notifies when a Claude permission request needs input without completing the task', async () => {
|
||||
const { observeAgentHookCompletionForNotification } =
|
||||
await import('./agent-hook-completion-notifications')
|
||||
@@ -617,13 +499,6 @@ describe('agent hook completion notifications', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('notifies for a blocked Codex permission request', async () => {
|
||||
seedCodexPane(paneKey)
|
||||
await observeCodexPermissionPause('blocked')
|
||||
|
||||
expect(dispatchTerminalNotification).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not notify on Grok routine permission prompt notifications during tool use', async () => {
|
||||
const { observeAgentHookCompletionForNotification } =
|
||||
await import('./agent-hook-completion-notifications')
|
||||
@@ -701,68 +576,6 @@ describe('agent hook completion notifications', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('suppresses an internal milestone completion when hook work resumes before quiet', async () => {
|
||||
const { observeAgentHookCompletionForNotification } =
|
||||
await import('./agent-hook-completion-notifications')
|
||||
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: hookStatus('working')
|
||||
})
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: hookStatus('done')
|
||||
})
|
||||
vi.advanceTimersByTime(HOOK_DONE_QUIET_MS - 1)
|
||||
expect(dispatchTerminalNotification).not.toHaveBeenCalled()
|
||||
expect(
|
||||
dispatchAgentHookTerminalLifecycle.mock.calls.filter(
|
||||
([, payload]) => payload.state === 'done'
|
||||
)
|
||||
).toHaveLength(0)
|
||||
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: hookStatus('working')
|
||||
})
|
||||
vi.advanceTimersByTime(HOOK_DONE_QUIET_MS)
|
||||
expect(dispatchTerminalNotification).not.toHaveBeenCalled()
|
||||
expect(
|
||||
dispatchAgentHookTerminalLifecycle.mock.calls.filter(
|
||||
([, payload]) => payload.state === 'done'
|
||||
)
|
||||
).toHaveLength(0)
|
||||
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: 'wt-1',
|
||||
payload: hookStatus('done')
|
||||
})
|
||||
vi.advanceTimersByTime(HOOK_DONE_QUIET_MS)
|
||||
|
||||
expect(dispatchTerminalNotification).toHaveBeenCalledTimes(1)
|
||||
expect(dispatchAgentHookTerminalLifecycle).toHaveBeenCalledWith(
|
||||
paneKey,
|
||||
expect.objectContaining({ state: 'done', agentType: 'codex' })
|
||||
)
|
||||
expect(dispatchTerminalNotification).toHaveBeenCalledWith(
|
||||
'wt-1',
|
||||
expect.objectContaining({
|
||||
source: 'agent-task-complete',
|
||||
paneKey,
|
||||
agentStatusSnapshot: expect.objectContaining({
|
||||
state: 'done',
|
||||
agentType: 'codex',
|
||||
prompt: 'implement notifications',
|
||||
lastAssistantMessage: 'Done.'
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const MANY_PANES = [
|
||||
{ tabId: 'tab-1', leafId: '11111111-1111-4111-8111-111111111111', ptyId: 'pty-1' },
|
||||
{ tabId: 'tab-2', leafId: '22222222-2222-4222-8222-222222222222', ptyId: 'pty-2' },
|
||||
|
||||
@@ -174,23 +174,4 @@ describe('agent hook completion store sync', () => {
|
||||
})
|
||||
expect(shouldSyncAgentHookCompletionForStoreUpdate(reorderedTabs, twoTabPrevious)).toBe(true)
|
||||
})
|
||||
|
||||
it('compares effective tracking state instead of unrelated settings identity', () => {
|
||||
const previous = createState({
|
||||
settings: {
|
||||
experimentalTerminalAttention: true,
|
||||
notifications: { enabled: false, agentTaskComplete: false }
|
||||
}
|
||||
})
|
||||
const stillTrackedByNotifications = createState({
|
||||
...previous,
|
||||
settings: {
|
||||
experimentalTerminalAttention: false,
|
||||
notifications: { enabled: true, agentTaskComplete: true }
|
||||
}
|
||||
})
|
||||
expect(shouldSyncAgentHookCompletionForStoreUpdate(stillTrackedByNotifications, previous)).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -274,17 +274,6 @@ describe('resolveComposerBranchReuse', () => {
|
||||
).toEqual({ reuseEligibleBranch: 'feature-x', defaultReuse: true })
|
||||
})
|
||||
|
||||
it('treats a slash-containing local branch as reusable (ref equals local name)', () => {
|
||||
expect(
|
||||
resolveComposerBranchReuse({
|
||||
refName: 'fix/bug-0',
|
||||
localBranchName: 'fix/bug-0',
|
||||
selectionProducedOverride: true,
|
||||
branchCheckedOutElsewhere: false
|
||||
})
|
||||
).toEqual({ reuseEligibleBranch: 'fix/bug-0', defaultReuse: true })
|
||||
})
|
||||
|
||||
it('does not offer reuse for a remote-only ref (ref carries an origin/ prefix)', () => {
|
||||
expect(
|
||||
resolveComposerBranchReuse({
|
||||
|
||||
@@ -48,15 +48,6 @@ describe('showComposerDropFailureToast', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('passes a free-form failure reason straight through', () => {
|
||||
showComposerDropFailureToast({
|
||||
failureCount: 2,
|
||||
total: 4,
|
||||
commonFailure: { status: 'failed', reason: 'EACCES: permission denied' }
|
||||
})
|
||||
expect(lastToast().description).toBe('EACCES: permission denied')
|
||||
})
|
||||
|
||||
it('shows no description when nothing explained the failure', () => {
|
||||
showComposerDropFailureToast({ failureCount: 1, total: 2 })
|
||||
expect(lastToast().description).toBeUndefined()
|
||||
@@ -82,10 +73,4 @@ describe('showComposerDropFailureToast', () => {
|
||||
expect(first).toBeDefined()
|
||||
expect(lastToast().id).toBe(first)
|
||||
})
|
||||
|
||||
it('gives no reason at all when the batch failed for differing reasons', () => {
|
||||
showComposerDropFailureToast({ failureCount: 3, total: 6 })
|
||||
expect(lastToast().title).toBe('3 of 6 items could not be attached.')
|
||||
expect(lastToast().description).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,10 +10,4 @@ describe('composer drop owner', () => {
|
||||
expect(isCurrentComposerDropOwner([page, modal], page)).toBe(false)
|
||||
expect(isCurrentComposerDropOwner([page, modal], modal)).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects async drop completions after their owner unmounts', () => {
|
||||
const owner = Symbol('owner')
|
||||
|
||||
expect(isCurrentComposerDropOwner([], owner)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -139,21 +139,6 @@ describe('local composer drop lifetime', () => {
|
||||
expect(hook.attach).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps the current request pending until it settles', async () => {
|
||||
const gate = Promise.withResolvers<void>()
|
||||
authorize.mockImplementationOnce(() => gate.promise)
|
||||
const hook = renderDrop()
|
||||
const settled = vi.fn()
|
||||
const pending = hook.result.current.applyLocalComposerDrop(['/drop/one']).then(settled)
|
||||
hook.unmount()
|
||||
await Promise.resolve()
|
||||
expect(settled).not.toHaveBeenCalled()
|
||||
gate.resolve()
|
||||
await pending
|
||||
expect(settled).toHaveBeenCalledOnce()
|
||||
expect(stat).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('preserves mixed results, order, duplicate filtering and one failure report', async () => {
|
||||
const order: string[] = []
|
||||
authorize.mockImplementation(async ({ targetPath }) => {
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
type BeginArgs = {
|
||||
plan: unknown
|
||||
target: { worktreeId: string }
|
||||
beforeOpen?: (sessionId: string) => boolean | void
|
||||
}
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
beginStructuredAgentSessionProvisionalLaunch:
|
||||
vi.fn<(args: BeginArgs) => { sessionId: string; tab: { id: string } } | null>()
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/structured-agent-session-provisional-tab', () => ({
|
||||
beginStructuredAgentSessionProvisionalLaunch: mocks.beginStructuredAgentSessionProvisionalLaunch
|
||||
}))
|
||||
|
||||
import { adoptAgentSessionLaunchVerdict } from '@/lib/agent-session-launch-plan'
|
||||
import { beginFullCreationStructuredLaunch } from './full-creation-structured-launch'
|
||||
|
||||
const plan = adoptAgentSessionLaunchVerdict({
|
||||
route: 'structured-native-chat',
|
||||
agent: 'codex',
|
||||
prompt: 'Fix the route',
|
||||
promptDelivery: 'auto-submit'
|
||||
})
|
||||
|
||||
describe('beginFullCreationStructuredLaunch', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mocks.beginStructuredAgentSessionProvisionalLaunch.mockImplementation((args) => {
|
||||
args.beforeOpen?.('session-1')
|
||||
return { sessionId: 'session-1', tab: { id: 'agent-session:session-1' } }
|
||||
})
|
||||
})
|
||||
|
||||
it('allocates the final identity before revealing and opening the chat surface', () => {
|
||||
const order: string[] = []
|
||||
mocks.beginStructuredAgentSessionProvisionalLaunch.mockImplementation((args) => {
|
||||
order.push('begin')
|
||||
args.beforeOpen?.('session-1')
|
||||
order.push('open')
|
||||
return { sessionId: 'session-1', tab: { id: 'agent-session:session-1' } }
|
||||
})
|
||||
|
||||
const launch = beginFullCreationStructuredLaunch({
|
||||
plan,
|
||||
worktreeId: 'worktree-1',
|
||||
beforeOpen: (sessionId) => {
|
||||
order.push(`reveal:${sessionId}`)
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
expect(launch).toMatchObject({ sessionId: 'session-1', tab: { id: 'agent-session:session-1' } })
|
||||
expect(order).toEqual(['begin', 'reveal:session-1', 'open'])
|
||||
expect(mocks.beginStructuredAgentSessionProvisionalLaunch).toHaveBeenCalledWith({
|
||||
plan,
|
||||
hooks: {},
|
||||
target: { worktreeId: 'worktree-1' },
|
||||
beforeOpen: expect.any(Function)
|
||||
})
|
||||
})
|
||||
|
||||
it('returns no surface when reveal or ownership is refused', () => {
|
||||
mocks.beginStructuredAgentSessionProvisionalLaunch.mockReturnValue(null)
|
||||
|
||||
expect(
|
||||
beginFullCreationStructuredLaunch({ plan, worktreeId: 'worktree-1', beforeOpen: vi.fn() })
|
||||
).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -15,20 +15,6 @@ const sources: LinkedWorkItemSummary[] = [
|
||||
number: 42,
|
||||
title: 'Fix checkout',
|
||||
url: 'https://github.com/acme/app/pull/42'
|
||||
},
|
||||
{
|
||||
provider: 'github',
|
||||
type: 'issue',
|
||||
number: 43,
|
||||
title: 'Fix checkout',
|
||||
url: 'https://github.com/acme/app/issues/43'
|
||||
},
|
||||
{
|
||||
provider: 'gitlab',
|
||||
type: 'mr',
|
||||
number: 44,
|
||||
title: 'Fix checkout',
|
||||
url: 'https://gitlab.com/acme/app/-/merge_requests/44'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -135,36 +121,4 @@ describe('create more source reset', () => {
|
||||
expect(result.current.branchAutoNameRef.current).toBe('')
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['linear', 'jira'] as const)('clears a %s task on a folder target', (provider) => {
|
||||
const item: LinkedWorkItemSummary = {
|
||||
provider,
|
||||
type: 'issue',
|
||||
number: 0,
|
||||
title: 'Fix checkout',
|
||||
url:
|
||||
provider === 'linear'
|
||||
? 'https://linear.app/acme/issue/APP-45'
|
||||
: 'https://acme.atlassian.net/browse/APP-45'
|
||||
}
|
||||
const { result } = renderHook(() => useSelectedSourceReset(item, true))
|
||||
expect(result.current.selection?.kind).toBe(provider)
|
||||
|
||||
act(() => result.current.resetForNextCreate())
|
||||
|
||||
expect(result.current.selection).toBeNull()
|
||||
expect(result.current.linkedWorkItem).toBeNull()
|
||||
expect(result.current.name).toBe('')
|
||||
expect(result.current.note).toBe('')
|
||||
})
|
||||
|
||||
it('clears a plain branch selection before the next create', () => {
|
||||
const { result } = renderHook(() => useSelectedSourceReset(null, false, 'feature/checkout'))
|
||||
expect(result.current.selection).toEqual({ kind: 'branch', label: 'feature/checkout' })
|
||||
|
||||
act(() => result.current.resetForNextCreate())
|
||||
|
||||
expect(result.current.selection).toBeNull()
|
||||
expect(result.current.baseBranch).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -204,23 +204,6 @@ describe('installed agent skill discovery lifecycle', () => {
|
||||
expect(discover).toHaveBeenLastCalledWith(LOCAL, { projectRuntime: repairProjectRuntime })
|
||||
})
|
||||
|
||||
it('keys WSL targets by distro so two distros do not share one entry', async () => {
|
||||
const discover = discoverSkillsForRuntimeTarget
|
||||
discover.mockResolvedValueOnce(result(1))
|
||||
discover.mockResolvedValueOnce(result(2))
|
||||
|
||||
await expect(
|
||||
discoverInstalledAgentSkills(false, { runtime: 'wsl', wslDistro: 'Ubuntu' })
|
||||
).resolves.toEqual(result(1))
|
||||
await expect(
|
||||
discoverInstalledAgentSkills(false, { runtime: 'wsl', wslDistro: 'Debian' })
|
||||
).resolves.toEqual(result(2))
|
||||
await expect(
|
||||
discoverInstalledAgentSkills(false, { runtime: 'wsl', wslDistro: 'Ubuntu' })
|
||||
).resolves.toEqual(result(1))
|
||||
expect(discover).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('keys targets by runtime and project identity', () => {
|
||||
expect(getSkillDiscoveryTargetKey(undefined)).toBe('host')
|
||||
expect(getSkillDiscoveryTargetKey({ runtime: 'wsl', wslDistro: 'Ubuntu' })).toBe('wsl:Ubuntu')
|
||||
@@ -254,24 +237,6 @@ describe('installed agent skill discovery lifecycle', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('isolates project-runtime caches by target-contained filters', async () => {
|
||||
discoverSkillsForRuntimeTarget.mockResolvedValueOnce(result(1)).mockResolvedValueOnce(result(2))
|
||||
|
||||
await expect(
|
||||
discoverInstalledAgentSkills(false, {
|
||||
projectRuntime: resolvedWslProjectRuntime,
|
||||
names: ['orchestration']
|
||||
})
|
||||
).resolves.toEqual(result(1))
|
||||
await expect(
|
||||
discoverInstalledAgentSkills(false, {
|
||||
projectRuntime: resolvedWslProjectRuntime,
|
||||
names: ['computer-use']
|
||||
})
|
||||
).resolves.toEqual(result(2))
|
||||
|
||||
expect(discoverSkillsForRuntimeTarget).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
it('keys the bounded cache by runtime scope, not by the client target', async () => {
|
||||
// Why: #6887 scopes remote scans by environment. The cap rewrites this same
|
||||
// module, so pin that getRuntimeScopedSkillDiscoveryKey stays the producer —
|
||||
|
||||
Reference in New Issue
Block a user