From 1ccb9516bcfa577d32b5a910b922df7196732eff Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Tue, 4 Aug 2026 03:36:35 -0700 Subject: [PATCH] test(mobile): model the real re-show ordering in the streaming-gate tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overlay regression test replayed the transcript before the stream text on the way back from the terminal view. That ordering is backwards: the session withholds `messages` until a fresh read settles (an RPC round trip) while the throttled stream text returns in ~50ms — and with the transcript already back, a gate that got discarded on the toggle still passes. Replay the real order, which pins the gate's lifetime as intended. Swaps the hidden-gap duplicate case for the in-view one (a tool frame clears the assistant text mid-turn), which is where the hold actually earns its keep; the hidden-gap direction stays covered at the gate level. --- mobile/src/session/MobileNativeChatOverlay.test.ts | 14 ++++++++++---- .../mobile-native-chat-streaming-gate.test.ts | 6 ++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/mobile/src/session/MobileNativeChatOverlay.test.ts b/mobile/src/session/MobileNativeChatOverlay.test.ts index a4910cd5afd..2fd3da51f36 100644 --- a/mobile/src/session/MobileNativeChatOverlay.test.ts +++ b/mobile/src/session/MobileNativeChatOverlay.test.ts @@ -136,19 +136,25 @@ describe('MobileNativeChatOverlay streaming gate', () => { await update({ show: false, messages: [], streamLive: true }) expect(streaming()).toBe('hidden') - // Back on chat: the throttled status trails the first re-render by a tick. - await update({ messages: prior, streamLive: true }) + // Back on chat the session withholds its transcript until a fresh read + // settles, so the throttled stream text returns a round trip ahead of it. + await update({ messages: [], streamLive: true }) + await update({ messages: [], streamingText: 'Done.', streamLive: true }) await update({ messages: prior, streamingText: 'Done.', streamLive: true }) expect(streaming()).toBe('Done.') }) - it('still hides the bubble when the reply landed while chat was hidden', async () => { + it('hides a repeated part whose own turn landed during a mid-turn gap', async () => { + // Between parts the status frame carries no assistant text (a tool call), so + // the stream goes textless while the turn is still live and the part that + // just finished lands in the transcript. Re-anchoring on that tick would + // adopt it as history and render it a second time. const prior = [assistantTurn('a1', 'Done.')] await render({ messages: prior }) await update({ messages: prior, streamingText: 'Done.', streamLive: true }) + expect(streaming()).toBe('Done.') - await update({ show: false, messages: [], streamLive: true }) const landed = [...prior, assistantTurn('a2', 'Done.')] await update({ messages: landed, streamLive: true }) await update({ messages: landed, streamingText: 'Done.', streamLive: true }) diff --git a/mobile/src/session/mobile-native-chat-streaming-gate.test.ts b/mobile/src/session/mobile-native-chat-streaming-gate.test.ts index c7d88a14120..3770028b2b3 100644 --- a/mobile/src/session/mobile-native-chat-streaming-gate.test.ts +++ b/mobile/src/session/mobile-native-chat-streaming-gate.test.ts @@ -81,15 +81,17 @@ describe('deriveMobileNativeChatStreaming', () => { it('keeps the segment baseline through textless ticks while the turn is live', () => { // Chat is hidden mid-stream: the transcript unsubscribes and the status - // stops reaching the gate, but the turn has not ended. + // stops reaching the gate, but the turn has not ended. Coming back, the + // stream text returns before the re-read transcript does. const prior = [assistant('a1', 'Done.')] const { results } = run([ { folded: prior }, { folded: prior, text: 'Done.', live: true }, { folded: [], live: true }, + { folded: [], text: 'Done.', live: true }, { folded: prior, text: 'Done.', live: true } ]) - expect(results).toEqual([null, 'Done.', null, 'Done.']) + expect(results).toEqual([null, 'Done.', null, 'Done.', 'Done.']) }) it('still hides after a hidden gap once the reply landed as its own turn', () => {