From 86cfddfafd92c9c9a7d155e2f5c2d4cb4fae104d Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:40:16 -0700 Subject: [PATCH] test: fix the two CI failures #25751 left on main - structured-agent-session-live-turn.test.ts: pass the host turn source ({ items }) the module now takes; without latestTurn it reads the rows, so each assertion keeps its meaning. - use-mobile-native-chat-turn-disclosure.test.tsx was over mobile's 800-line limit. The mid-turn-send describe moves whole to its own file under the same outer describe, and the shared harness moves to a test-support module. The harness now renders a component instead of a host-element string, so both files typecheck and the original leaves the tests-typecheck baseline. --- ...hat-turn-disclosure-mid-turn-send.test.tsx | 225 ++++++++++++++ ...tive-chat-turn-disclosure.test-support.tsx | 59 ++++ ...obile-native-chat-turn-disclosure.test.tsx | 294 ++---------------- mobile/tests-typecheck-baseline.txt | 1 - ...structured-agent-session-live-turn.test.ts | 8 +- 5 files changed, 319 insertions(+), 268 deletions(-) create mode 100644 mobile/src/session/use-mobile-native-chat-turn-disclosure-mid-turn-send.test.tsx create mode 100644 mobile/src/session/use-mobile-native-chat-turn-disclosure.test-support.tsx diff --git a/mobile/src/session/use-mobile-native-chat-turn-disclosure-mid-turn-send.test.tsx b/mobile/src/session/use-mobile-native-chat-turn-disclosure-mid-turn-send.test.tsx new file mode 100644 index 000000000000..bdec9763772d --- /dev/null +++ b/mobile/src/session/use-mobile-native-chat-turn-disclosure-mid-turn-send.test.tsx @@ -0,0 +1,225 @@ +import { createElement } from 'react' +import { act, create, type ReactTestRenderer } from 'react-test-renderer' +import { afterEach, describe, expect, it, vi } from 'vitest' +import type { + AgentJournalRenderItem, + AgentJournalTurnScope +} from '../../../src/shared/agent-session-journal-types' +import { agentJournalSubmissionKey } from '../../../src/shared/agent-session-journal-item-key' +import type { NativeChatMessage } from '../../../src/shared/native-chat-types' +import { + DisclosureResult, + Harness, + userMessage +} from './use-mobile-native-chat-turn-disclosure.test-support' + +describe('useMobileNativeChatTurnDisclosure', () => { + let renderer: ReactTestRenderer | null = null + + afterEach(() => { + act(() => renderer?.unmount()) + renderer = null + }) + + describe('keeps the live bar under the prompt that opened the running turn, not a mid-turn send', () => { + const tool: NativeChatMessage = { + id: 'tool-a', + role: 'assistant', + blocks: [ + { type: 'tool-call', name: 'Bash', input: { command: 'sleep 15' }, state: 'running' } + ], + timestamp: null, + source: 'transcript' + } + const messages = [userMessage('A'), tool, userMessage('B')] + const user = ( + id: string, + sequence: number, + turnScope?: AgentJournalTurnScope + ): AgentJournalRenderItem => ({ + itemId: id, + revision: 0, + sequence, + observedAt: sequence, + body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: id }] }, + ...(turnScope ? { turnScope } : {}) + }) + const turn = ( + turnId: string, + sequence: number, + state: 'running' | 'interrupted', + userItemId: string, + turnScope?: AgentJournalTurnScope + ): AgentJournalRenderItem => ({ + itemId: turnId, + revision: 0, + sequence, + observedAt: sequence, + body: { kind: 'turn', turnId, state, userItemId }, + ...(turnScope ? { turnScope } : {}) + }) + const toolItem = ( + sequence: number, + turnScope?: AgentJournalTurnScope + ): AgentJournalRenderItem => ({ + itemId: 'tool-a', + revision: 0, + sequence, + observedAt: sequence, + body: { + kind: 'tool-call', + name: 'Bash', + input: { command: 'sleep 15' }, + state: 'running' + }, + ...(turnScope ? { turnScope } : {}) + }) + const rows = () => { + const disclosure = renderer!.root.findByType(DisclosureResult).props.disclosure + return messages.map((message, index) => disclosure.resolveRow(index, message)) + } + const render = (props: Parameters[0]) => + act(() => { + if (renderer) { + renderer.update(createElement(Harness, props)) + } else { + renderer = create(createElement(Harness, props)) + } + }) + + it("on a host that states each row's turn, the steered message and the work are one live turn", () => { + vi.useFakeTimers() + try { + vi.setSystemTime(10_000) + const t1 = { kind: 'turn', turnItemId: 't1' } as const + const thread = { kind: 'thread' } as const + // B was handed over while A's turn runs, so the host scopes it to that turn. + const whileA = [ + user('A', 1, thread), + turn('t1', 2, 'running', 'A', thread), + toolItem(3, t1), + user('B', 4, t1) + ] + render({ + messages, + enabled: true, + workingStartedAt: 5_000, + turnJournal: { items: whileA, submissions: [] } + }) + let [rowA, rowTool, rowB] = rows() + expect(rowA.turnStatus).toEqual({ startedAt: 5_000, thinking: false, workedSeconds: null }) + expect(rowB.turnStatus).toBeNull() + expect([rowA, rowTool, rowB].map((row) => row.activeTurnIsWorking)).toEqual([ + true, + true, + true + ]) + + // B's own turn opens: A takes the host's settled duration, B counts from A's end. + const whileB = [ + user('A', 1, thread), + turn('t1', 2, 'interrupted', 'A', thread), + toolItem(3, t1), + user('B', 4, t1), + turn('t2', 5, 'running', 'B', thread) + ] + render({ + messages, + enabled: true, + workingStartedAt: 22_000, + settledTurns: new Map([['A', { startedAt: 5_000, workedSeconds: 17 }]]), + turnJournal: { items: whileB, submissions: [] } + }) + ;[rowA, rowTool, rowB] = rows() + expect(rowA.turnStatus).toEqual({ startedAt: 5_000, thinking: false, workedSeconds: 17 }) + expect(rowB.turnStatus).toEqual({ startedAt: 22_000, thinking: false, workedSeconds: null }) + expect(rowB.activeTurnIsWorking).toBe(true) + expect(rowTool.activeTurnIsWorking).toBe(false) + } finally { + vi.useRealTimers() + } + }) + + it("a queued card's Steer joins the running turn: no bar of its own, and live with it", () => { + const t1 = { kind: 'turn', turnItemId: 't1' } as const + const thread = { kind: 'thread' } as const + // Steer hands the draft over under a fresh submission id while A's turn runs, so the host + // scopes the row to that turn; the submission names the card it came from. + const steerId = agentJournalSubmissionKey('hand-off-1') + const steered = [userMessage('A'), tool, userMessage(steerId)] + render({ + messages: steered, + enabled: true, + workingStartedAt: 5_000, + turnJournal: { + items: [ + user('A', 1, thread), + turn('t1', 2, 'running', 'A', thread), + toolItem(3, t1), + user(steerId, 4, t1) + ], + submissions: [ + { + clientMessageId: 'hand-off-1', + queuedMessageId: 'draft-1', + fence: 1, + payloadFingerprint: 'fp', + dispatchState: 'pending', + providerItemId: null, + reason: null, + submittedAt: 4, + resolvedAt: null + } + ] + } + }) + const disclosure = renderer!.root.findByType(DisclosureResult).props.disclosure + const [rowA, , rowSteer] = steered.map((message, index) => + disclosure.resolveRow(index, message) + ) + expect(rowA.turnStatus).not.toBeNull() + expect(rowSteer.turnStatus).toBeNull() + expect(rowSteer.activeTurnIsWorking).toBe(true) + }) + + it('on a host that states no turn, the bar and liveness follow the running record by journal order', () => { + vi.useFakeTimers() + try { + vi.setSystemTime(10_000) + const whileA = [user('A', 1), turn('t1', 2, 'running', 'A'), toolItem(3), user('B', 4)] + render({ + messages, + enabled: true, + workingStartedAt: 5_000, + turnJournal: { items: whileA, submissions: [] } + }) + let [rowA, rowTool, rowB] = rows() + expect(rowA.turnStatus).toEqual({ startedAt: 5_000, thinking: false, workedSeconds: null }) + expect(rowB.turnStatus).toBeNull() + // Liveness follows the owning turn: A's tool row stays live while B waits. + expect(rowTool.activeTurnIsWorking).toBe(true) + expect(rowB.activeTurnIsWorking).toBe(false) + + const whileB = [ + user('A', 1), + turn('t1', 2, 'interrupted', 'A'), + toolItem(3), + user('B', 4), + turn('t2', 5, 'running', 'B') + ] + render({ + messages, + enabled: true, + workingStartedAt: 22_000, + settledTurns: new Map([['A', { startedAt: 5_000, workedSeconds: 17 }]]), + turnJournal: { items: whileB, submissions: [] } + }) + ;[rowA, , rowB] = rows() + expect(rowA.turnStatus).toEqual({ startedAt: 5_000, thinking: false, workedSeconds: 17 }) + expect(rowB.turnStatus).toEqual({ startedAt: 22_000, thinking: false, workedSeconds: null }) + } finally { + vi.useRealTimers() + } + }) + }) +}) diff --git a/mobile/src/session/use-mobile-native-chat-turn-disclosure.test-support.tsx b/mobile/src/session/use-mobile-native-chat-turn-disclosure.test-support.tsx new file mode 100644 index 000000000000..919f0dc7810b --- /dev/null +++ b/mobile/src/session/use-mobile-native-chat-turn-disclosure.test-support.tsx @@ -0,0 +1,59 @@ +// The hook harness both turn-disclosure test files render. +import { createElement } from 'react' +import type { NativeChatMessage } from '../../../src/shared/native-chat-types' +import type { NativeChatTurnJournal } from '../../../src/shared/native-chat-turn-membership' +import type { NativeChatSettledTurns } from '../../../src/shared/native-chat-turn-status' +import { useMobileNativeChatTurnDisclosure } from './use-mobile-native-chat-turn-disclosure' + +/** Holds the hook's result for `findByType`: a component, since a host-element string is not an + * `ElementType` and would drop these tests out of the typecheck. */ +export function DisclosureResult(_props: { + disclosure: ReturnType +}): null { + return null +} + +export function userMessage(id: string): NativeChatMessage { + return { + id, + role: 'user', + blocks: [{ type: 'text', text: id }], + timestamp: null, + source: 'transcript' + } +} + +export function Harness({ + messages, + enabled, + isWorking = true, + settledTurns, + turnJournal, + workingStartedAt, + thinking, + lineYields, + scopeKey = 'host\0worktree\0tab-a' +}: { + messages: readonly NativeChatMessage[] + enabled: boolean + isWorking?: boolean + settledTurns?: NativeChatSettledTurns + turnJournal?: NativeChatTurnJournal + workingStartedAt?: number | null + thinking?: boolean + lineYields?: boolean + scopeKey?: string +}): React.JSX.Element { + const disclosure = useMobileNativeChatTurnDisclosure({ + messages, + enabled, + isWorking, + settledTurns, + turnJournal, + workingStartedAt, + thinking, + lineYields, + scopeKey + }) + return createElement(DisclosureResult, { disclosure }) +} diff --git a/mobile/src/session/use-mobile-native-chat-turn-disclosure.test.tsx b/mobile/src/session/use-mobile-native-chat-turn-disclosure.test.tsx index 766190a6bf07..487872c65f1a 100644 --- a/mobile/src/session/use-mobile-native-chat-turn-disclosure.test.tsx +++ b/mobile/src/session/use-mobile-native-chat-turn-disclosure.test.tsx @@ -6,56 +6,14 @@ import type { AgentJournalRenderItem, AgentJournalTurnScope } from '../../../src/shared/agent-session-journal-types' -import { agentJournalSubmissionKey } from '../../../src/shared/agent-session-journal-item-key' import type { NativeChatMessage } from '../../../src/shared/native-chat-types' import type { NativeChatTurnJournal } from '../../../src/shared/native-chat-turn-membership' -import type { NativeChatSettledTurns } from '../../../src/shared/native-chat-turn-status' import { useMobileNativeChatTurnDisclosure } from './use-mobile-native-chat-turn-disclosure' - -function userMessage(id: string): NativeChatMessage { - return { - id, - role: 'user', - blocks: [{ type: 'text', text: id }], - timestamp: null, - source: 'transcript' - } -} - -function Harness({ - messages, - enabled, - isWorking = true, - settledTurns, - turnJournal, - workingStartedAt, - thinking, - lineYields, - scopeKey = 'host\0worktree\0tab-a' -}: { - messages: readonly NativeChatMessage[] - enabled: boolean - isWorking?: boolean - settledTurns?: NativeChatSettledTurns - turnJournal?: NativeChatTurnJournal - workingStartedAt?: number | null - thinking?: boolean - lineYields?: boolean - scopeKey?: string -}): React.JSX.Element { - const disclosure = useMobileNativeChatTurnDisclosure({ - messages, - enabled, - isWorking, - settledTurns, - turnJournal, - workingStartedAt, - thinking, - lineYields, - scopeKey - }) - return createElement('result', { disclosure }) -} +import { + DisclosureResult, + Harness, + userMessage +} from './use-mobile-native-chat-turn-disclosure.test-support' describe('useMobileNativeChatTurnDisclosure', () => { let renderer: ReactTestRenderer | null = null @@ -110,7 +68,9 @@ describe('useMobileNativeChatTurnDisclosure', () => { act(() => { renderer?.update(createElement(Harness, { messages, enabled: true, isWorking: false })) }) - const first = renderer!.root.findByType('result').props.disclosure.resolveRow(0, messages[0]) + const first = renderer!.root + .findByType(DisclosureResult) + .props.disclosure.resolveRow(0, messages[0]) const refreshed = [...messages] act(() => { @@ -119,21 +79,23 @@ describe('useMobileNativeChatTurnDisclosure', () => { ) }) const second = renderer!.root - .findByType('result') + .findByType(DisclosureResult) .props.disclosure.resolveRow(0, refreshed[0]) // The row carries the key; the handler itself lives on the hook and stays // stable for the scope, so a re-render never disturbs a row's memo. expect(first.turnKey).toBe('u1') expect(second.turnKey).toBe('u1') - const firstHandler = renderer!.root.findByType('result').props.disclosure.onToggleTurn + const firstHandler = renderer!.root.findByType(DisclosureResult).props.disclosure.onToggleTurn expect(firstHandler).toBeTypeOf('function') act(() => { renderer?.update( createElement(Harness, { messages: [...refreshed], enabled: true, isWorking: false }) ) }) - expect(renderer!.root.findByType('result').props.disclosure.onToggleTurn).toBe(firstHandler) + expect(renderer!.root.findByType(DisclosureResult).props.disclosure.onToggleTurn).toBe( + firstHandler + ) } finally { vi.useRealTimers() } @@ -155,7 +117,9 @@ describe('useMobileNativeChatTurnDisclosure', () => { createElement(Harness, { messages, enabled: true, isWorking: false, settledTurns }) ) }) - const row = renderer!.root.findByType('result').props.disclosure.resolveRow(0, messages[0]) + const row = renderer!.root + .findByType(DisclosureResult) + .props.disclosure.resolveRow(0, messages[0]) expect(row.turnStatus).toEqual({ startedAt: 500, thinking: false, workedSeconds: 197 }) expect(row.turnKey).toBe('u1') } finally { @@ -177,7 +141,7 @@ describe('useMobileNativeChatTurnDisclosure', () => { scopeKey: 'host\0worktree\0tab-a' }) seen.push(disclosure.resolveRow(0, messages[0]).turnStatus) - return createElement('result', { disclosure }) + return createElement(DisclosureResult, { disclosure }) } act(() => { renderer = create(createElement(Recorder, { isWorking: true })) @@ -214,215 +178,15 @@ describe('useMobileNativeChatTurnDisclosure', () => { }) ) }) - const row = renderer!.root.findByType('result').props.disclosure.resolveRow(0, messages[0]) + const row = renderer!.root + .findByType(DisclosureResult) + .props.disclosure.resolveRow(0, messages[0]) expect(row.turnStatus).toBeNull() } finally { vi.useRealTimers() } }) - describe('keeps the live bar under the prompt that opened the running turn, not a mid-turn send', () => { - const tool: NativeChatMessage = { - id: 'tool-a', - role: 'assistant', - blocks: [ - { type: 'tool-call', name: 'Bash', input: { command: 'sleep 15' }, state: 'running' } - ], - timestamp: null, - source: 'transcript' - } - const messages = [userMessage('A'), tool, userMessage('B')] - const user = ( - id: string, - sequence: number, - turnScope?: AgentJournalTurnScope - ): AgentJournalRenderItem => ({ - itemId: id, - revision: 0, - sequence, - observedAt: sequence, - body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: id }] }, - ...(turnScope ? { turnScope } : {}) - }) - const turn = ( - turnId: string, - sequence: number, - state: 'running' | 'interrupted', - userItemId: string, - turnScope?: AgentJournalTurnScope - ): AgentJournalRenderItem => ({ - itemId: turnId, - revision: 0, - sequence, - observedAt: sequence, - body: { kind: 'turn', turnId, state, userItemId }, - ...(turnScope ? { turnScope } : {}) - }) - const toolItem = ( - sequence: number, - turnScope?: AgentJournalTurnScope - ): AgentJournalRenderItem => ({ - itemId: 'tool-a', - revision: 0, - sequence, - observedAt: sequence, - body: { - kind: 'tool-call', - name: 'Bash', - input: { command: 'sleep 15' }, - state: 'running' - }, - ...(turnScope ? { turnScope } : {}) - }) - const rows = () => { - const disclosure = renderer!.root.findByType('result').props.disclosure - return messages.map((message, index) => disclosure.resolveRow(index, message)) - } - const render = (props: Parameters[0]) => - act(() => { - if (renderer) { - renderer.update(createElement(Harness, props)) - } else { - renderer = create(createElement(Harness, props)) - } - }) - - it("on a host that states each row's turn, the steered message and the work are one live turn", () => { - vi.useFakeTimers() - try { - vi.setSystemTime(10_000) - const t1 = { kind: 'turn', turnItemId: 't1' } as const - const thread = { kind: 'thread' } as const - // B was handed over while A's turn runs, so the host scopes it to that turn. - const whileA = [ - user('A', 1, thread), - turn('t1', 2, 'running', 'A', thread), - toolItem(3, t1), - user('B', 4, t1) - ] - render({ - messages, - enabled: true, - workingStartedAt: 5_000, - turnJournal: { items: whileA, submissions: [] } - }) - let [rowA, rowTool, rowB] = rows() - expect(rowA.turnStatus).toEqual({ startedAt: 5_000, thinking: false, workedSeconds: null }) - expect(rowB.turnStatus).toBeNull() - expect([rowA, rowTool, rowB].map((row) => row.activeTurnIsWorking)).toEqual([ - true, - true, - true - ]) - - // B's own turn opens: A takes the host's settled duration, B counts from A's end. - const whileB = [ - user('A', 1, thread), - turn('t1', 2, 'interrupted', 'A', thread), - toolItem(3, t1), - user('B', 4, t1), - turn('t2', 5, 'running', 'B', thread) - ] - render({ - messages, - enabled: true, - workingStartedAt: 22_000, - settledTurns: new Map([['A', { startedAt: 5_000, workedSeconds: 17 }]]), - turnJournal: { items: whileB, submissions: [] } - }) - ;[rowA, rowTool, rowB] = rows() - expect(rowA.turnStatus).toEqual({ startedAt: 5_000, thinking: false, workedSeconds: 17 }) - expect(rowB.turnStatus).toEqual({ startedAt: 22_000, thinking: false, workedSeconds: null }) - expect(rowB.activeTurnIsWorking).toBe(true) - expect(rowTool.activeTurnIsWorking).toBe(false) - } finally { - vi.useRealTimers() - } - }) - - it("a queued card's Steer joins the running turn: no bar of its own, and live with it", () => { - const t1 = { kind: 'turn', turnItemId: 't1' } as const - const thread = { kind: 'thread' } as const - // Steer hands the draft over under a fresh submission id while A's turn runs, so the host - // scopes the row to that turn; the submission names the card it came from. - const steerId = agentJournalSubmissionKey('hand-off-1') - const steered = [userMessage('A'), tool, userMessage(steerId)] - render({ - messages: steered, - enabled: true, - workingStartedAt: 5_000, - turnJournal: { - items: [ - user('A', 1, thread), - turn('t1', 2, 'running', 'A', thread), - toolItem(3, t1), - user(steerId, 4, t1) - ], - submissions: [ - { - clientMessageId: 'hand-off-1', - queuedMessageId: 'draft-1', - fence: 1, - payloadFingerprint: 'fp', - dispatchState: 'pending', - providerItemId: null, - reason: null, - submittedAt: 4, - resolvedAt: null - } - ] - } - }) - const disclosure = renderer!.root.findByType('result').props.disclosure - const [rowA, , rowSteer] = steered.map((message, index) => - disclosure.resolveRow(index, message) - ) - expect(rowA.turnStatus).not.toBeNull() - expect(rowSteer.turnStatus).toBeNull() - expect(rowSteer.activeTurnIsWorking).toBe(true) - }) - - it('on a host that states no turn, the bar and liveness follow the running record by journal order', () => { - vi.useFakeTimers() - try { - vi.setSystemTime(10_000) - const whileA = [user('A', 1), turn('t1', 2, 'running', 'A'), toolItem(3), user('B', 4)] - render({ - messages, - enabled: true, - workingStartedAt: 5_000, - turnJournal: { items: whileA, submissions: [] } - }) - let [rowA, rowTool, rowB] = rows() - expect(rowA.turnStatus).toEqual({ startedAt: 5_000, thinking: false, workedSeconds: null }) - expect(rowB.turnStatus).toBeNull() - // Liveness follows the owning turn: A's tool row stays live while B waits. - expect(rowTool.activeTurnIsWorking).toBe(true) - expect(rowB.activeTurnIsWorking).toBe(false) - - const whileB = [ - user('A', 1), - turn('t1', 2, 'interrupted', 'A'), - toolItem(3), - user('B', 4), - turn('t2', 5, 'running', 'B') - ] - render({ - messages, - enabled: true, - workingStartedAt: 22_000, - settledTurns: new Map([['A', { startedAt: 5_000, workedSeconds: 17 }]]), - turnJournal: { items: whileB, submissions: [] } - }) - ;[rowA, , rowB] = rows() - expect(rowA.turnStatus).toEqual({ startedAt: 5_000, thinking: false, workedSeconds: 17 }) - expect(rowB.turnStatus).toEqual({ startedAt: 22_000, thinking: false, workedSeconds: null }) - } finally { - vi.useRealTimers() - } - }) - }) - /** A journal of `entries` in order, each naming the turn it belongs to (null for none). With * `statesScope` false the rows carry no scope, as an older host writes them. */ function journalOf( @@ -499,7 +263,7 @@ describe('useMobileNativeChatTurnDisclosure', () => { }) ) }) - const disclosure = renderer!.root.findByType('result').props.disclosure + const disclosure = renderer!.root.findByType(DisclosureResult).props.disclosure const [rowA, rowT1, rowB, rowT2] = messages.map((message, index) => disclosure.resolveRow(index, message) ) @@ -547,7 +311,7 @@ describe('useMobileNativeChatTurnDisclosure', () => { }) ) }) - const disclosure = renderer!.root.findByType('result').props.disclosure + const disclosure = renderer!.root.findByType(DisclosureResult).props.disclosure const [rowU1, rowWoke] = messages.map((message, index) => disclosure.resolveRow(index, message) ) @@ -610,7 +374,7 @@ describe('useMobileNativeChatTurnDisclosure', () => { }) ) }) - const disclosure = renderer!.root.findByType('result').props.disclosure + const disclosure = renderer!.root.findByType(DisclosureResult).props.disclosure const [rowW1, rowB] = messages.map((message, index) => disclosure.resolveRow(index, message) ) @@ -670,7 +434,7 @@ describe('useMobileNativeChatTurnDisclosure', () => { }) ) }) - const disclosure = renderer!.root.findByType('result').props.disclosure + const disclosure = renderer!.root.findByType(DisclosureResult).props.disclosure const list: NativeChatMessage[] = disclosure.listMessages const drawn = list.map((entry, index) => { const row = disclosure.resolveRow(index, entry) @@ -708,12 +472,12 @@ describe('useMobileNativeChatTurnDisclosure', () => { act(() => { renderer?.update(createElement(Harness, { messages, enabled: true, isWorking: false })) }) - const disclosureNow = renderer!.root.findByType('result').props.disclosure + const disclosureNow = renderer!.root.findByType(DisclosureResult).props.disclosure const row = disclosureNow.resolveRow(index, messages[index]) act(() => disclosureNow.onToggleTurn(row.turnKey)) } - const disclosure = renderer!.root.findByType('result').props.disclosure + const disclosure = renderer!.root.findByType(DisclosureResult).props.disclosure const expanded = messages.filter( (message, index) => disclosure.resolveRow(index, message).turnExpanded ) @@ -762,7 +526,7 @@ describe('useMobileNativeChatTurnDisclosure', () => { }) ) }) - const disclosure = renderer!.root.findByType('result').props.disclosure + const disclosure = renderer!.root.findByType(DisclosureResult).props.disclosure const rows = messages.map((message, index) => disclosure.resolveRow(index, message)) expect(rows.map((row) => row.activeTurnIsWorking)).toEqual([false, false, true]) expect(rows[0].turnStatus?.workedSeconds).toBe(4) @@ -793,7 +557,7 @@ describe('useMobileNativeChatTurnDisclosure', () => { }) ) }) - const disclosure = renderer!.root.findByType('result').props.disclosure + const disclosure = renderer!.root.findByType(DisclosureResult).props.disclosure const rows = messages.map((message, index) => disclosure.resolveRow(index, message)) act(() => renderer?.unmount()) return rows @@ -840,7 +604,7 @@ describe('the open reasoning block the live line discloses', () => { renderer = create(element) } }) - const latest = () => renderer!.root.findByType('result').props.disclosure + const latest = () => renderer!.root.findByType(DisclosureResult).props.disclosure it('hides only that block, and lands it open once it ends if the reader opened it live', () => { const prompt = userMessage('u1') diff --git a/mobile/tests-typecheck-baseline.txt b/mobile/tests-typecheck-baseline.txt index 9d47b7ab801d..c2eadf35e2ad 100644 --- a/mobile/tests-typecheck-baseline.txt +++ b/mobile/tests-typecheck-baseline.txt @@ -60,7 +60,6 @@ src/session/session-reply-schema.test.ts src/session/use-mobile-diff-review-send-actions.test.ts src/session/use-mobile-file-tap-handlers.test.ts src/session/use-mobile-native-chat-image-attachments.test.ts -src/session/use-mobile-native-chat-turn-disclosure.test.tsx src/session/use-mobile-pr-branch-context.test.ts src/session/use-mobile-pr-sidebar-controller.test.ts src/session/use-mobile-session-terminal-create-actions.test.ts diff --git a/src/shared/structured-agent-session-live-turn.test.ts b/src/shared/structured-agent-session-live-turn.test.ts index 5a4d1f7d43c8..389e6b2aff38 100644 --- a/src/shared/structured-agent-session-live-turn.test.ts +++ b/src/shared/structured-agent-session-live-turn.test.ts @@ -39,9 +39,13 @@ describe('isStructuredAgentSessionThinking', () => { state, ...(state === 'completed' ? { completedAt: 3 } : {}) }) - expect(isStructuredAgentSessionThinking([turnStart, withState('running')])).toBe(true) + expect(isStructuredAgentSessionThinking({ items: [turnStart, withState('running')] })).toBe( + true + ) // Ended reasoning stays the newest row while Claude streams a tool's input after it. - expect(isStructuredAgentSessionThinking([turnStart, withState('completed')])).toBe(false) + expect(isStructuredAgentSessionThinking({ items: [turnStart, withState('completed')] })).toBe( + false + ) }) it('is false once a tool call, a message or a diff lands after the reasoning', () => {