diff --git a/mobile/src/session/MobileNativeChatView.test.ts b/mobile/src/session/MobileNativeChatView.test.ts index e8325dbb125..4ea87922a9a 100644 --- a/mobile/src/session/MobileNativeChatView.test.ts +++ b/mobile/src/session/MobileNativeChatView.test.ts @@ -269,11 +269,19 @@ describe('MobileNativeChatView', () => { expect(header.props.accessibilityLabel).toBe('Couldn’t load earlier messages. Tap to retry') }) - it('keeps the header tappable-but-idle while a page is in flight', async () => { - await render({ hasMore: true, loadingEarlier: true, onLoadEarlier: vi.fn() }) + // An in-flight retry outranks the failure it is retrying, so the header shows + // the spinner rather than a stale error the user has already acted on. + it('disables the header and hides the failure while a retry is in flight', async () => { + await render({ + hasMore: true, + loadingEarlier: true, + loadEarlierError: 'Couldn’t load earlier messages', + onLoadEarlier: vi.fn() + }) const header = loadEarlierHeader() expect(header.props.disabled).toBe(true) + expect(headerText(header)).not.toContain('Couldn’t load earlier messages') expect(headerText(header)).not.toContain('Tap to retry') }) }) diff --git a/mobile/src/session/use-mobile-native-chat-session.test.ts b/mobile/src/session/use-mobile-native-chat-session.test.ts index 0461b074164..f0a7b0b5d96 100644 --- a/mobile/src/session/use-mobile-native-chat-session.test.ts +++ b/mobile/src/session/use-mobile-native-chat-session.test.ts @@ -8,6 +8,9 @@ import { type MobileNativeChatSession } from './use-mobile-native-chat-session' +/** Hands a pending `sendRequest` promise's settlers to the test body. */ +type PageSettle = { resolve: (response: unknown) => void; reject: (reason: unknown) => void } + function message(id: string): NativeChatMessage { return { id, @@ -255,10 +258,19 @@ describe('useMobileNativeChatSession', () => { expect(state?.loadingEarlier).toBe(false) }) - it('drops a failure from a page the session swapped out from under', async () => { - let rejectEarlier: (reason: unknown) => void = () => {} + // Both stale-failure guards have to hold: a swapped-out page can fail by + // rejecting or by resolving not-ok, and each has its own `requestIsCurrent()`. + it.each([ + ['a rejected request', (settle: PageSettle) => settle.reject(new Error('disconnected'))], + ['a not-ok response', (settle: PageSettle) => settle.resolve({ ok: false, error: 'offline' })] + ])('drops %s from a page the session swapped out from under', async (_label, settleEarlier) => { + const settle: PageSettle = { resolve: () => {}, reject: () => {} } const sendRequest = vi.fn( - () => new Promise((_resolve, reject) => (rejectEarlier = reject)) + () => + new Promise((resolve, reject) => { + settle.resolve = resolve + settle.reject = reject + }) ) as unknown as RpcClient['sendRequest'] const subscribe: RpcClient['subscribe'] = vi.fn((_method, _params, onData) => { emit = onData @@ -277,7 +289,7 @@ describe('useMobileNativeChatSession', () => { emit({ type: 'replacement', messages: [message('fresh')], hasMore: true, beforeOffset: 0 }) ) await act(async () => { - rejectEarlier(new Error('disconnected')) + settleEarlier(settle) await Promise.resolve() })