From 554649e20aa49157e2df83b0f928cd434ddd3c26 Mon Sep 17 00:00:00 2001 From: Neil Date: Wed, 21 Oct 2026 01:52:49 -0700 Subject: [PATCH] test(mobile): model capability client lifecycle in modal fixtures --- .../src/components/NewWorktreeModal.test.tsx | 55 +++++++++---------- ...orktree-create-capability-recovery.test.ts | 4 +- ...ost-status-capability-ignorability.test.ts | 6 +- 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/mobile/src/components/NewWorktreeModal.test.tsx b/mobile/src/components/NewWorktreeModal.test.tsx index 10a0cab636e..698a7a97139 100644 --- a/mobile/src/components/NewWorktreeModal.test.tsx +++ b/mobile/src/components/NewWorktreeModal.test.tsx @@ -84,8 +84,11 @@ function sourceInputs(renderer: ReactTestRenderer) { ) } -// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the modal only sends requests. -const requestsOnly = (sendRequest: unknown) => ({ sendRequest }) as unknown as RpcClient +function requestsOnly(sendRequest: RpcClient['sendRequest']): RpcClient { + const client = new FakeSession('connected') + client.sendRequest.mockImplementation(sendRequest) + return client +} function runTargetPicker(renderer: ReactTestRenderer) { return renderer.root @@ -122,7 +125,7 @@ describe('NewWorktreeModal project targets', () => { } return new Promise(() => {}) }) - const client = { sendRequest } as unknown as RpcClient + const client = requestsOnly(sendRequest) await act(async () => { renderer = create( @@ -161,8 +164,8 @@ describe('NewWorktreeModal project targets', () => { upstream: { owner: 'stablyai', repo: 'orca' } } ] - const client = { - sendRequest: vi.fn().mockImplementation((method: string) => { + const client = requestsOnly( + vi.fn().mockImplementation((method: string) => { if (method === 'repo.list') { return Promise.resolve({ ok: true, result: { repos: listedRepos } }) } @@ -171,7 +174,7 @@ describe('NewWorktreeModal project targets', () => { } return new Promise(() => {}) }) - } as unknown as RpcClient + ) await act(async () => { renderer = create( @@ -248,7 +251,7 @@ describe('NewWorktreeModal project targets', () => { } return Promise.resolve({ ok: true, result: {} }) }) - const client = { sendRequest } as unknown as RpcClient + const client = requestsOnly(sendRequest) await act(async () => { renderer = create( @@ -304,7 +307,7 @@ describe('NewWorktreeModal project targets', () => { } return Promise.resolve({ ok: true, result: {} }) }) - const client = { sendRequest } as unknown as RpcClient + const client = requestsOnly(sendRequest) await act(async () => { renderer = create( @@ -556,16 +559,16 @@ describe('NewWorktreeModal project targets', () => { resolveOldList = resolve }) const freshRepo = { ...repos[0]!, id: 'repo-fresh', displayName: 'fresh' } - const oldClient = { - sendRequest: vi.fn().mockImplementation((method: string) => { + const oldClient = requestsOnly( + vi.fn().mockImplementation((method: string) => { if (method === 'repo.list') { return oldList } return new Promise(() => {}) }) - } as unknown as RpcClient - const freshClient = { - sendRequest: vi.fn().mockImplementation((method: string) => { + ) + const freshClient = requestsOnly( + vi.fn().mockImplementation((method: string) => { if (method === 'repo.list') { return Promise.resolve({ ok: true, result: { repos: [freshRepo] } }) } @@ -574,7 +577,7 @@ describe('NewWorktreeModal project targets', () => { } return new Promise(() => {}) }) - } as unknown as RpcClient + ) const modalProps = { visible: true, hostId: 'host-1', @@ -605,7 +608,7 @@ describe('NewWorktreeModal project targets', () => { it('remounts before the reopened session renders, so no stale instance sees visible', async () => { const sendRequest = vi.fn().mockImplementation(() => new Promise(() => {})) - const client = { sendRequest } as unknown as RpcClient + const client = requestsOnly(sendRequest) const modalProps = { client, hostId: 'host-1', @@ -630,8 +633,8 @@ describe('NewWorktreeModal project targets', () => { // remounted the modal mid-edit and silently threw away the picked source. it('keeps the picked source when a reconnect swaps the client for the same host', async () => { const makeClient = () => - ({ - sendRequest: vi.fn().mockImplementation((method: string) => { + requestsOnly( + vi.fn().mockImplementation((method: string) => { if (method === 'repo.list') { return Promise.resolve({ ok: true, result: { repos } }) } @@ -640,7 +643,7 @@ describe('NewWorktreeModal project targets', () => { } return new Promise(() => {}) }) - }) as unknown as RpcClient + ) const modalProps = { visible: true, hostId: 'host-1', @@ -681,9 +684,7 @@ describe('NewWorktreeModal project targets', () => { it('keeps the form when a host switch renders but never commits', async () => { setCachedRepos('host-2', repos) - const client = { - sendRequest: vi.fn().mockImplementation(() => new Promise(() => {})) - } as unknown as RpcClient + const client = requestsOnly(vi.fn().mockImplementation(() => new Promise(() => {}))) const modalProps = { visible: true, client, onCreated: () => {}, onClose: () => {} } let frozen = false const tree = (hostId: string) => @@ -708,9 +709,7 @@ describe('NewWorktreeModal project targets', () => { }) it('keeps the form when a close renders but never commits', async () => { - const client = { - sendRequest: vi.fn().mockImplementation(() => new Promise(() => {})) - } as unknown as RpcClient + const client = requestsOnly(vi.fn().mockImplementation(() => new Promise(() => {}))) const modalProps = { client, hostId: 'host-1', @@ -744,9 +743,7 @@ describe('NewWorktreeModal project targets', () => { it('starts a fresh form session when the modal switches hosts', async () => { setCachedRepos('host-2', repos) - const client = { - sendRequest: vi.fn().mockImplementation(() => new Promise(() => {})) - } as unknown as RpcClient + const client = requestsOnly(vi.fn().mockImplementation(() => new Promise(() => {}))) const modalProps = { visible: true, client, onCreated: () => {}, onClose: () => {} } await act(async () => { @@ -762,9 +759,7 @@ describe('NewWorktreeModal project targets', () => { }) it('starts with fresh form state after closing and reopening', async () => { - const client = { - sendRequest: vi.fn().mockImplementation(() => new Promise(() => {})) - } as unknown as RpcClient + const client = requestsOnly(vi.fn().mockImplementation(() => new Promise(() => {}))) const modalProps = { client, hostId: 'host-1', diff --git a/mobile/src/tasks/worktree-create-capability-recovery.test.ts b/mobile/src/tasks/worktree-create-capability-recovery.test.ts index 2a9854a8c87..bfd3cfeb2a8 100644 --- a/mobile/src/tasks/worktree-create-capability-recovery.test.ts +++ b/mobile/src/tasks/worktree-create-capability-recovery.test.ts @@ -64,7 +64,9 @@ describe('capability probe recovery', () => { const host = connection() host.sendRequest.mockRejectedValueOnce(new Error('timeout')) await render(host.client) - await act(async () => vi.advanceTimersByTimeAsync(1_000)) + await act(async () => { + await vi.advanceTimersByTimeAsync(1_000) + }) expect(current?.tasksSupported).toBe(true) }) diff --git a/mobile/src/transport/host-status-capability-ignorability.test.ts b/mobile/src/transport/host-status-capability-ignorability.test.ts index 35762e8699c..a1dcdb561fc 100644 --- a/mobile/src/transport/host-status-capability-ignorability.test.ts +++ b/mobile/src/transport/host-status-capability-ignorability.test.ts @@ -97,7 +97,7 @@ type ClientVisibleOutcome = { probedCapabilities: readonly string[] | null answeredProbe: boolean quickCommandsSupported: boolean - worktreeCreateSupport: NewWorktreeRuntimeCapabilities + worktreeCreateSupport: NewWorktreeRuntimeCapabilities | null } /** Every released path that reads status.get, plus the states derived from what it published. */ @@ -160,8 +160,8 @@ describe('mobileWeb.bundle.v1 on a released client', () => { expect(advertised.gateStatus).not.toBeNull() expect(advertised.probedCapabilities).toContain(MOBILE_WEB_BUNDLE_CAPABILITY) expect(advertised.quickCommandsSupported).toBe(true) - expect(advertised.worktreeCreateSupport.tasksSupported).toBe(true) - expect(advertised.worktreeCreateSupport.worktreeCreateIdempotency).not.toBe(false) + expect(advertised.worktreeCreateSupport?.tasksSupported).toBe(true) + expect(advertised.worktreeCreateSupport?.worktreeCreateIdempotency).not.toBe(false) expect(oldDesktop.gates.hostCapabilities).not.toContain(MOBILE_WEB_BUNDLE_CAPABILITY) expect(asIfNeverAdvertised(advertised)).toEqual(oldDesktop)