From c56ff58dd736bb163fa682ca995ef9ae996a2f5a Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 9 Aug 2026 18:31:00 -0700 Subject: [PATCH] test(mobile): centralize renderer setup (#13408) --- .../src/accounts-route-reset-credit.test.ts | 25 +- .../components/CodexResetCreditAction.test.ts | 44 +-- .../src/components/HostProtocolGate.test.ts | 1 - .../components/MobileHomeQuickActions.test.ts | 6 +- mobile/src/components/MobileHostCard.test.ts | 6 +- mobile/src/components/MobileHostCard.test.tsx | 6 +- .../MobileMarkdown.file-links.test.ts | 1 - .../src/components/NewWorktreeModal.test.tsx | 1 - .../PickerModal.accessibility.test.ts | 1 - .../src/components/WorktreeAgentList.test.tsx | 6 +- mobile/src/components/WorktreeListRow.test.ts | 1 - .../bottom-drawer-close-lifecycle.test.ts | 4 +- .../mobile-agent-icon-gradient.test.ts | 6 +- .../pr-sidebar/PRCommentsSection.test.ts | 24 +- .../src/files/MobileFileExplorerPanel.test.ts | 37 +- .../files/MobileFileMarkdownPreview.test.ts | 23 +- mobile/src/hooks/use-now.test.ts | 12 +- .../src/host-edit-route-accessibility.test.ts | 26 +- mobile/src/host-edit-save-flow.test.ts | 27 +- .../onboarding/MobileOnboardingPage.test.ts | 6 +- .../mobile-onboarding-screen.test.ts | 1 - .../session/MobileNativeChatComposer.test.ts | 373 ++++++++---------- .../session/MobileNativeChatMessage.test.ts | 20 +- .../session/MobileNativeChatOverlay.test.ts | 28 +- .../MobileNativeChatPermission.test.ts | 31 +- ...bileNativeChatSessionOptionPickers.test.ts | 1 - .../src/session/MobileNativeChatView.test.ts | 28 +- mobile/src/session/QuickCommandsList.test.ts | 4 +- mobile/src/session/QuickCommandsSheet.test.ts | 13 +- ...mobile-native-chat-permission-send.test.ts | 1 - ...nitial-session-terminal-autocreate.test.ts | 1 - .../session/use-live-worktree-name.test.ts | 54 +-- .../use-missing-worktree-bounce.test.ts | 1 - ...mobile-attachment-input-lease-gate.test.ts | 10 - ...le-default-session-view-preference.test.ts | 1 - .../use-mobile-diff-review-controller.test.ts | 1 - ...se-mobile-diff-review-diff-loading.test.ts | 1 - ...se-mobile-diff-review-send-actions.test.ts | 16 +- .../use-mobile-file-tap-handlers.test.ts | 1 - ...use-mobile-native-chat-answer-send.test.ts | 16 +- ...use-mobile-native-chat-ask-dismiss.test.ts | 18 +- .../use-mobile-native-chat-controller.test.ts | 64 +-- ...le-native-chat-drafts-launch-draft.test.ts | 21 +- .../use-mobile-native-chat-drafts.test.ts | 21 +- ...use-mobile-native-chat-file-search.test.ts | 23 +- ...bile-native-chat-image-attachments.test.ts | 16 +- ...use-mobile-native-chat-input-lease.test.ts | 86 ++-- ...se-mobile-native-chat-message-send.test.ts | 1 - ...use-mobile-native-chat-readability.test.ts | 55 +-- .../use-mobile-native-chat-send-error.test.ts | 68 +--- ...mobile-native-chat-session-options.test.ts | 1 - .../use-mobile-native-chat-session.test.ts | 32 +- .../use-mobile-native-chat-stop.test.ts | 1 - ...mobile-native-chat-terminal-stream.test.ts | 297 ++++++-------- ...mobile-session-tabs-reconciliation.test.ts | 11 - .../use-mobile-session-view-mode.test.ts | 1 - mobile/src/session/use-quick-commands.test.ts | 16 +- ...erminal-live-input-mode-preference.test.ts | 23 +- .../use-throttled-latest-value.test.ts | 12 +- .../MobileGitHistoryList.test.tsx | 6 +- ...e-mobile-hosted-review-eligibility.test.ts | 6 +- .../terminal-webview-engine-error.test.ts | 23 +- ...rminal-live-accessory-input-commit.test.ts | 23 +- .../use-terminal-live-input-commit.test.ts | 23 +- .../use-terminal-live-input-focus.test.ts | 28 +- mobile/src/transport/client-context.test.ts | 64 +-- .../src/transport/host-status-gates.test.ts | 19 - .../settings-host-client-lifecycle.test.ts | 130 ++---- mobile/vitest.config.ts | 2 + mobile/vitest.setup.ts | 1 + 70 files changed, 508 insertions(+), 1449 deletions(-) create mode 100644 mobile/vitest.setup.ts diff --git a/mobile/src/accounts-route-reset-credit.test.ts b/mobile/src/accounts-route-reset-credit.test.ts index 76be80719..2de1e9688 100644 --- a/mobile/src/accounts-route-reset-credit.test.ts +++ b/mobile/src/accounts-route-reset-credit.test.ts @@ -157,28 +157,12 @@ const RESET_SNAPSHOT = { } } as const -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => spy.mockRestore() -} - async function renderAccountsRoute(): Promise { let renderer: ReactTestRenderer | null = null - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - await act(async () => { - renderer = create(createElement(AccountsScreen)) - await Promise.resolve() - }) - } finally { - restoreConsoleError() - } + await act(async () => { + renderer = create(createElement(AccountsScreen)) + await Promise.resolve() + }) if (!renderer) { throw new Error('Accounts route did not render') } @@ -229,7 +213,6 @@ describe('accounts route Codex reset credit', () => { let storedValues: Map beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true resetCodexResetAttemptJournalForTests() storedValues = new Map() dependencies.alert.mockReset() diff --git a/mobile/src/components/CodexResetCreditAction.test.ts b/mobile/src/components/CodexResetCreditAction.test.ts index 25e395917..0c1a6ea57 100644 --- a/mobile/src/components/CodexResetCreditAction.test.ts +++ b/mobile/src/components/CodexResetCreditAction.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { CodexResetCreditAction } from './CodexResetCreditAction' vi.mock('react-native', () => ({ @@ -19,35 +19,19 @@ const summary = { expiryLabel: 'Expires in 5d' } -function suppressRendererWarning(): () => void { - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) - }) - return () => spy.mockRestore() -} - function renderAction(busy: boolean, disabled: boolean): ReactTestRenderer { let renderer: ReactTestRenderer | null = null - const restore = suppressRendererWarning() - try { - act(() => { - renderer = create( - createElement(CodexResetCreditAction, { - summary, - scopeLabel: 'dev@example.com on the host', - busy, - disabled, - onPress: vi.fn() - }) - ) - }) - } finally { - restore() - } + act(() => { + renderer = create( + createElement(CodexResetCreditAction, { + summary, + scopeLabel: 'dev@example.com on the host', + busy, + disabled, + onPress: vi.fn() + }) + ) + }) if (!renderer) { throw new Error('Reset action did not render') } @@ -55,10 +39,6 @@ function renderAction(busy: boolean, disabled: boolean): ReactTestRenderer { } describe('CodexResetCreditAction', () => { - afterEach(() => { - vi.restoreAllMocks() - }) - it('exposes a 44pt touch target and enabled accessibility state', () => { const renderer = renderAction(false, false) const button = renderer.root.findByType('Pressable') diff --git a/mobile/src/components/HostProtocolGate.test.ts b/mobile/src/components/HostProtocolGate.test.ts index 94cae6d91..44a2265cc 100644 --- a/mobile/src/components/HostProtocolGate.test.ts +++ b/mobile/src/components/HostProtocolGate.test.ts @@ -78,7 +78,6 @@ describe('HostProtocolGate', () => { let renderer: ReactTestRenderer | null = null beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true nativeTestState.openUrl.mockClear() nativeTestState.platform.OS = 'ios' probeMounts.count = 0 diff --git a/mobile/src/components/MobileHomeQuickActions.test.ts b/mobile/src/components/MobileHomeQuickActions.test.ts index ba2e0ba75..e71e1d8ff 100644 --- a/mobile/src/components/MobileHomeQuickActions.test.ts +++ b/mobile/src/components/MobileHomeQuickActions.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { HostProfile } from '../transport/types' import { MobileHomeQuickActions } from './MobileHomeQuickActions' @@ -37,10 +37,6 @@ function host(id: string, name: string, endpoint: string): HostProfile { describe('MobileHomeQuickActions', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null diff --git a/mobile/src/components/MobileHostCard.test.ts b/mobile/src/components/MobileHostCard.test.ts index f7c494cfd..f4033e671 100644 --- a/mobile/src/components/MobileHostCard.test.ts +++ b/mobile/src/components/MobileHostCard.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { MobileHostCard } from './MobileHostCard' vi.mock('react-native', () => ({ @@ -30,10 +30,6 @@ function suppressRendererDeprecation() { describe('MobileHostCard', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null diff --git a/mobile/src/components/MobileHostCard.test.tsx b/mobile/src/components/MobileHostCard.test.tsx index 66e978a14..a92157155 100644 --- a/mobile/src/components/MobileHostCard.test.tsx +++ b/mobile/src/components/MobileHostCard.test.tsx @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { ConnectionVerdict } from '../transport/connection-health' import type { MobileConnectionPath } from '../transport/stable-logical-rpc-client' import type { ConnectionState, HostCredentialStatus, HostProfile } from '../transport/types' @@ -39,10 +39,6 @@ const loaded: HostWorktreeInfo = { describe('MobileHostCard', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null diff --git a/mobile/src/components/MobileMarkdown.file-links.test.ts b/mobile/src/components/MobileMarkdown.file-links.test.ts index a8e381865..962538a88 100644 --- a/mobile/src/components/MobileMarkdown.file-links.test.ts +++ b/mobile/src/components/MobileMarkdown.file-links.test.ts @@ -38,7 +38,6 @@ describe('MobileMarkdown file links', () => { const onOpenFile = vi.fn() beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true onOpenFile.mockClear() openURL.mockClear() }) diff --git a/mobile/src/components/NewWorktreeModal.test.tsx b/mobile/src/components/NewWorktreeModal.test.tsx index 3fb76dd31..4d8e95bec 100644 --- a/mobile/src/components/NewWorktreeModal.test.tsx +++ b/mobile/src/components/NewWorktreeModal.test.tsx @@ -58,7 +58,6 @@ describe('NewWorktreeModal repo list', () => { let renderer: ReactTestRenderer | null = null beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true setCachedRepos('host-1', repos) }) diff --git a/mobile/src/components/PickerModal.accessibility.test.ts b/mobile/src/components/PickerModal.accessibility.test.ts index 4508f2275..46610a6e6 100644 --- a/mobile/src/components/PickerModal.accessibility.test.ts +++ b/mobile/src/components/PickerModal.accessibility.test.ts @@ -24,7 +24,6 @@ describe('PickerModal accessibility', () => { let renderer: ReactTestRenderer | null = null beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true vi.spyOn(console, 'error').mockImplementation((...args) => { if (typeof args[0] !== 'string' || !args[0].includes('react-test-renderer is deprecated')) { throw new Error(String(args[0])) diff --git a/mobile/src/components/WorktreeAgentList.test.tsx b/mobile/src/components/WorktreeAgentList.test.tsx index 036f5d359..aef5161cb 100644 --- a/mobile/src/components/WorktreeAgentList.test.tsx +++ b/mobile/src/components/WorktreeAgentList.test.tsx @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { RuntimeWorktreeAgentRow } from '../../../src/shared/runtime-types' import { WorktreeAgentList } from './WorktreeAgentList' @@ -39,10 +39,6 @@ function agent(paneKey: string, parentPaneKey: string | null = null): RuntimeWor describe('WorktreeAgentList', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null diff --git a/mobile/src/components/WorktreeListRow.test.ts b/mobile/src/components/WorktreeListRow.test.ts index 9fe0aa11b..d08254126 100644 --- a/mobile/src/components/WorktreeListRow.test.ts +++ b/mobile/src/components/WorktreeListRow.test.ts @@ -127,7 +127,6 @@ describe('memoized worktree rows', () => { let renderer: ReactTestRenderer | null = null beforeEach(() => { - ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true agentSpinnerRender.mockClear() agentStateDotRender.mockClear() }) diff --git a/mobile/src/components/bottom-drawer-close-lifecycle.test.ts b/mobile/src/components/bottom-drawer-close-lifecycle.test.ts index b4362412d..94eb8a849 100644 --- a/mobile/src/components/bottom-drawer-close-lifecycle.test.ts +++ b/mobile/src/components/bottom-drawer-close-lifecycle.test.ts @@ -51,14 +51,12 @@ function mountedDrawer(renderer: ReactTestRenderer) { describe('BottomDrawer close lifecycle', () => { beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true const originalConsoleError = console.error vi.spyOn(console, 'error').mockImplementation((...args) => { const message = args[0] if ( typeof message === 'string' && - (message.includes('react-test-renderer is deprecated') || - message.includes('The current testing environment is not configured to support act')) + message.includes('The current testing environment is not configured to support act') ) { return } diff --git a/mobile/src/components/mobile-agent-icon-gradient.test.ts b/mobile/src/components/mobile-agent-icon-gradient.test.ts index 8e2781edf..c2221b627 100644 --- a/mobile/src/components/mobile-agent-icon-gradient.test.ts +++ b/mobile/src/components/mobile-agent-icon-gradient.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { MobileAgentIcon } from './MobileAgentIcon' vi.mock('react-native', () => ({ @@ -35,10 +35,6 @@ vi.mock('./AgentIcons', () => ({ describe('MobileAgentIcon OMP gradient', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null diff --git a/mobile/src/components/pr-sidebar/PRCommentsSection.test.ts b/mobile/src/components/pr-sidebar/PRCommentsSection.test.ts index d174f455c..08fc02c35 100644 --- a/mobile/src/components/pr-sidebar/PRCommentsSection.test.ts +++ b/mobile/src/components/pr-sidebar/PRCommentsSection.test.ts @@ -32,18 +32,6 @@ vi.mock('./pr-comments-styles', () => ({ prCommentsStyles: {} })) vi.mock('./mobile-pr-sidebar-styles', () => ({ mobilePrSidebarStyles: {} })) vi.mock('../../theme/mobile-theme', () => ({ colors: { textSecondary: '#999' } })) -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - const firstArg = args[0] - if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => consoleErrorSpy.mockRestore() -} - function comment(id: number): PRComment { return { id, @@ -65,14 +53,9 @@ function detailsWithComments(count: number): GitHubWorkItemDetails { async function renderComments(details: GitHubWorkItemDetails): Promise { let renderer: ReactTestRenderer | null = null - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - await act(async () => { - renderer = create(createElement(PRCommentsSection, { details, prState: 'open' })) - }) - } finally { - restoreConsoleError() - } + await act(async () => { + renderer = create(createElement(PRCommentsSection, { details, prState: 'open' })) + }) if (!renderer) { throw new Error('PRCommentsSection did not render') } @@ -107,7 +90,6 @@ describe('PRCommentsSection', () => { afterEach(() => { renderer?.unmount() renderer = null - vi.restoreAllMocks() }) it('resets pagination only when the user chooses a different audience filter', async () => { diff --git a/mobile/src/files/MobileFileExplorerPanel.test.ts b/mobile/src/files/MobileFileExplorerPanel.test.ts index 0d003e9f4..ff407ff21 100644 --- a/mobile/src/files/MobileFileExplorerPanel.test.ts +++ b/mobile/src/files/MobileFileExplorerPanel.test.ts @@ -83,18 +83,6 @@ vi.mock('../transport/client-context', () => ({ }) })) -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - const firstArg = args[0] - if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => consoleErrorSpy.mockRestore() -} - function entry(name: string, isDirectory = false): MobileDirEntry { return { name, isDirectory } } @@ -113,21 +101,16 @@ function createMockClient(entriesByPath: Record): Mock async function renderExplorer(): Promise { let renderer: ReactTestRenderer | null = null - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - await act(async () => { - renderer = create( - createElement(MobileFileExplorerPanel, { - hostId: 'host-a', - worktreeId: 'worktree-a', - name: 'Example Worktree', - embedded: true - }) - ) - }) - } finally { - restoreConsoleError() - } + await act(async () => { + renderer = create( + createElement(MobileFileExplorerPanel, { + hostId: 'host-a', + worktreeId: 'worktree-a', + name: 'Example Worktree', + embedded: true + }) + ) + }) if (!renderer) { throw new Error('MobileFileExplorerPanel did not render') } diff --git a/mobile/src/files/MobileFileMarkdownPreview.test.ts b/mobile/src/files/MobileFileMarkdownPreview.test.ts index 2afdb0b2c..8ac3770eb 100644 --- a/mobile/src/files/MobileFileMarkdownPreview.test.ts +++ b/mobile/src/files/MobileFileMarkdownPreview.test.ts @@ -33,28 +33,11 @@ vi.mock('./mobile-file-preview-styles', () => ({ type PreviewProps = Parameters[0] -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - const firstArg = args[0] - if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => consoleErrorSpy.mockRestore() -} - async function renderPreview(props: PreviewProps): Promise { let renderer: ReactTestRenderer | null = null - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - await act(async () => { - renderer = create(createElement(MobileFileMarkdownPreview, props)) - }) - } finally { - restoreConsoleError() - } + await act(async () => { + renderer = create(createElement(MobileFileMarkdownPreview, props)) + }) if (!renderer) { throw new Error('MobileFileMarkdownPreview did not render') } diff --git a/mobile/src/hooks/use-now.test.ts b/mobile/src/hooks/use-now.test.ts index c3fe56a98..14264b513 100644 --- a/mobile/src/hooks/use-now.test.ts +++ b/mobile/src/hooks/use-now.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const appState = vi.hoisted(() => ({ current: 'active', @@ -25,7 +25,6 @@ import { useNow } from './use-now' describe('useNow', () => { let renderer: ReactTestRenderer | null = null let latest = 0 - let consoleSpy: MockInstance function Harness({ enabled = true }: { enabled?: boolean }): null { latest = useNow(1_000, enabled) @@ -42,18 +41,10 @@ describe('useNow', () => { beforeEach(() => { vi.useFakeTimers() vi.setSystemTime(1_000) - globalThis.IS_REACT_ACT_ENVIRONMENT = true appState.current = 'active' appState.listener = null appState.remove.mockClear() latest = 0 - const original = console.error - consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) - }) act(() => { renderer = create(createElement(Harness)) }) @@ -63,7 +54,6 @@ describe('useNow', () => { act(() => renderer?.unmount()) renderer = null vi.useRealTimers() - consoleSpy.mockRestore() }) it('ticks while active, pauses in the background, and refreshes immediately on resume', () => { diff --git a/mobile/src/host-edit-route-accessibility.test.ts b/mobile/src/host-edit-route-accessibility.test.ts index bb5ddf4fa..d5fc20521 100644 --- a/mobile/src/host-edit-route-accessibility.test.ts +++ b/mobile/src/host-edit-route-accessibility.test.ts @@ -46,29 +46,12 @@ vi.mock('./transport/client-context', () => ({ usePrimeHosts: () => dependencies.primeHosts })) -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - const firstArg = args[0] - if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => consoleErrorSpy.mockRestore() -} - async function renderEditHostRoute(): Promise { let renderer: ReactTestRenderer | null = null - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - await act(async () => { - renderer = create(createElement(EditHostScreen)) - await Promise.resolve() - }) - } finally { - restoreConsoleError() - } + await act(async () => { + renderer = create(createElement(EditHostScreen)) + await Promise.resolve() + }) if (!renderer) { throw new Error('Edit host route did not render') } @@ -77,7 +60,6 @@ async function renderEditHostRoute(): Promise { describe('edit host route accessibility', () => { beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true dependencies.loadHosts.mockReset().mockResolvedValue([ { id: 'host-1', diff --git a/mobile/src/host-edit-save-flow.test.ts b/mobile/src/host-edit-save-flow.test.ts index aa0069a7c..a0a4561b2 100644 --- a/mobile/src/host-edit-save-flow.test.ts +++ b/mobile/src/host-edit-save-flow.test.ts @@ -56,29 +56,12 @@ const HOST_FIXTURE = { lastConnected: 1 } -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - const firstArg = args[0] - if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => consoleErrorSpy.mockRestore() -} - async function renderEditHostRoute(): Promise { let renderer: ReactTestRenderer | null = null - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - await act(async () => { - renderer = create(createElement(EditHostScreen)) - await Promise.resolve() - }) - } finally { - restoreConsoleError() - } + await act(async () => { + renderer = create(createElement(EditHostScreen)) + await Promise.resolve() + }) if (!renderer) { throw new Error('Edit host route did not render') } @@ -135,7 +118,6 @@ function findText(renderer: ReactTestRenderer, match: string): boolean { describe('edit host handleSave', () => { beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true dependencies.hostId = 'host-1' dependencies.back.mockReset() dependencies.forceReconnectHost.mockReset().mockResolvedValue(undefined) @@ -285,7 +267,6 @@ describe('edit host handleSave', () => { describe('edit host load() error states', () => { beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true dependencies.hostId = 'host-1' dependencies.back.mockReset() dependencies.forceReconnectHost.mockReset().mockResolvedValue(undefined) diff --git a/mobile/src/onboarding/MobileOnboardingPage.test.ts b/mobile/src/onboarding/MobileOnboardingPage.test.ts index a99052c3e..d62e496f0 100644 --- a/mobile/src/onboarding/MobileOnboardingPage.test.ts +++ b/mobile/src/onboarding/MobileOnboardingPage.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { MobileOnboardingPage } from './MobileOnboardingPage' vi.mock('react-native', async () => { @@ -24,10 +24,6 @@ vi.mock('lucide-react-native', () => ({ describe('MobileOnboardingPage', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null diff --git a/mobile/src/onboarding/mobile-onboarding-screen.test.ts b/mobile/src/onboarding/mobile-onboarding-screen.test.ts index d571ce28a..05dcc6b9a 100644 --- a/mobile/src/onboarding/mobile-onboarding-screen.test.ts +++ b/mobile/src/onboarding/mobile-onboarding-screen.test.ts @@ -56,7 +56,6 @@ describe('MobileOnboardingScreen', () => { let renderer: ReactTestRenderer | null = null beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true mocks.params = { hostId: 'paired-host', steps: 'session-view,notifications' } mocks.replace.mockReset() mocks.reducedMotionEnabled = false diff --git a/mobile/src/session/MobileNativeChatComposer.test.ts b/mobile/src/session/MobileNativeChatComposer.test.ts index 4ee001ca4..eaf50ba08 100644 --- a/mobile/src/session/MobileNativeChatComposer.test.ts +++ b/mobile/src/session/MobileNativeChatComposer.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { radii, spacing } from '../theme/mobile-theme' import { MobileNativeChatComposer } from './MobileNativeChatComposer' @@ -43,24 +43,9 @@ vi.mock('../components/BottomDrawer', async () => { } }) -function suppressRendererWarning(): () => void { - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) - }) - return () => spy.mockRestore() -} - describe('MobileNativeChatComposer', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null @@ -71,21 +56,16 @@ describe('MobileNativeChatComposer', () => { onChangeText: () => void, isAttaching = false ) { - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(MobileNativeChatComposer, { - value: ' hello ', - onChangeText, - onSend, - isAttaching - }) - ) - }) - } finally { - restore() - } + await act(async () => { + renderer = create( + createElement(MobileNativeChatComposer, { + value: ' hello ', + onChangeText, + onSend, + isAttaching + }) + ) + }) } function sendButton(): { props: { onPress: () => Promise } } { @@ -130,20 +110,15 @@ describe('MobileNativeChatComposer', () => { it('preserves leading whitespace so prose is not turned into a slash command', async () => { const onSend = vi.fn().mockResolvedValue(true) - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(MobileNativeChatComposer, { - value: ' /clear is prose ', - onChangeText: vi.fn(), - onSend - }) - ) - }) - } finally { - restore() - } + await act(async () => { + renderer = create( + createElement(MobileNativeChatComposer, { + value: ' /clear is prose ', + onChangeText: vi.fn(), + onSend + }) + ) + }) await act(async () => sendButton().props.onPress()) expect(onSend).toHaveBeenCalledWith(' /clear is prose') }) @@ -181,67 +156,57 @@ describe('MobileNativeChatComposer', () => { invokeAction: vi.fn(), recordCommand: vi.fn() } - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(MobileNativeChatComposer, { - value: 'run the tests', - onChangeText: vi.fn(), - onSend, - sessionOptions: { isWorking: false, controller } - }) - ) - }) - const modelPill = (): { props: { accessibilityState: { disabled: boolean } } } => - renderer!.root.find( - (node) => node.type === 'Pressable' && node.props.accessibilityLabel === 'Model, Model' - ) as { props: { accessibilityState: { disabled: boolean } } } - expect(modelPill().props.accessibilityState).toMatchObject({ disabled: false }) - // Start the send but don't await it — it stays in flight on purpose. - let pressed!: Promise - await act(async () => { - pressed = sendButton().props.onPress() - await Promise.resolve() - }) - expect(onSend).toHaveBeenCalled() - expect(modelPill().props.accessibilityState).toMatchObject({ disabled: true }) - await act(async () => { - releaseSend?.(true) - await pressed - }) - expect(modelPill().props.accessibilityState).toMatchObject({ disabled: false }) - } finally { - restore() - } + await act(async () => { + renderer = create( + createElement(MobileNativeChatComposer, { + value: 'run the tests', + onChangeText: vi.fn(), + onSend, + sessionOptions: { isWorking: false, controller } + }) + ) + }) + const modelPill = (): { props: { accessibilityState: { disabled: boolean } } } => + renderer!.root.find( + (node) => node.type === 'Pressable' && node.props.accessibilityLabel === 'Model, Model' + ) as { props: { accessibilityState: { disabled: boolean } } } + expect(modelPill().props.accessibilityState).toMatchObject({ disabled: false }) + // Start the send but don't await it — it stays in flight on purpose. + let pressed!: Promise + await act(async () => { + pressed = sendButton().props.onPress() + await Promise.resolve() + }) + expect(onSend).toHaveBeenCalled() + expect(modelPill().props.accessibilityState).toMatchObject({ disabled: true }) + await act(async () => { + releaseSend?.(true) + await pressed + }) + expect(modelPill().props.accessibilityState).toMatchObject({ disabled: false }) }) it('blocks composer submission while a session-option command is pending', async () => { const onSend = vi.fn().mockResolvedValue(true) - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(MobileNativeChatComposer, { - value: 'hello', - onChangeText: vi.fn(), - onSend, - sessionOptions: { - isWorking: false, - controller: { - snapshot: [], - pendingId: 'model', - setOption: vi.fn(), - invokeAction: vi.fn(), - recordCommand: vi.fn() - } + await act(async () => { + renderer = create( + createElement(MobileNativeChatComposer, { + value: 'hello', + onChangeText: vi.fn(), + onSend, + sessionOptions: { + isWorking: false, + controller: { + snapshot: [], + pendingId: 'model', + setOption: vi.fn(), + invokeAction: vi.fn(), + recordCommand: vi.fn() } - }) - ) - }) - } finally { - restore() - } + } + }) + ) + }) expect(sendButton().props).toMatchObject({ disabled: true }) await act(async () => sendButton().props.onPress()) expect(onSend).not.toHaveBeenCalled() @@ -268,21 +233,16 @@ describe('MobileNativeChatComposer', () => { }) it('keeps the text input editable while the send is locked', async () => { - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(MobileNativeChatComposer, { - value: 'half-typed', - onChangeText: vi.fn(), - onSend: vi.fn().mockResolvedValue(true), - disabled: true - }) - ) - }) - } finally { - restore() - } + await act(async () => { + renderer = create( + createElement(MobileNativeChatComposer, { + value: 'half-typed', + onChangeText: vi.fn(), + onSend: vi.fn().mockResolvedValue(true), + disabled: true + }) + ) + }) // Revoking `editable` on a focused field resigns first responder on iOS and // yanks the keyboard mid-typing (#10681) — the lock may only gate sending. const input = renderer!.root.find((node) => node.type === 'TextInput') as { @@ -294,25 +254,20 @@ describe('MobileNativeChatComposer', () => { it('renders a removable thumbnail for each pending image attachment', async () => { const onRemoveAttachment = vi.fn() - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(MobileNativeChatComposer, { - value: '', - onChangeText: vi.fn(), - onSend: vi.fn().mockResolvedValue(true), - attachments: [ - { id: 'img-1', path: '/tmp/a.png', previewUri: 'file:///a.png' }, - { id: 'img-2', path: '/tmp/b.png', previewUri: 'file:///b.png' } - ], - onRemoveAttachment - }) - ) - }) - } finally { - restore() - } + await act(async () => { + renderer = create( + createElement(MobileNativeChatComposer, { + value: '', + onChangeText: vi.fn(), + onSend: vi.fn().mockResolvedValue(true), + attachments: [ + { id: 'img-1', path: '/tmp/a.png', previewUri: 'file:///a.png' }, + { id: 'img-2', path: '/tmp/b.png', previewUri: 'file:///b.png' } + ], + onRemoveAttachment + }) + ) + }) const thumbs = renderer!.root.findAll((node) => node.type === 'Image') as Array<{ props: { source: { uri: string } } }> @@ -327,42 +282,32 @@ describe('MobileNativeChatComposer', () => { it('enables send with an attached image even when the text is empty', async () => { const onSend = vi.fn().mockResolvedValue(true) - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(MobileNativeChatComposer, { - value: '', - onChangeText: vi.fn(), - onSend, - attachments: [{ id: 'img-1', path: '/tmp/a.png', previewUri: 'file:///a.png' }] - }) - ) - }) - } finally { - restore() - } + await act(async () => { + renderer = create( + createElement(MobileNativeChatComposer, { + value: '', + onChangeText: vi.fn(), + onSend, + attachments: [{ id: 'img-1', path: '/tmp/a.png', previewUri: 'file:///a.png' }] + }) + ) + }) expect(sendButton().props).toMatchObject({ disabled: false }) await act(async () => sendButton().props.onPress()) expect(onSend).toHaveBeenCalledWith('') }) it('moves the caret to the insert point after an autocomplete pick, then releases control', async () => { - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(MobileNativeChatComposer, { - value: '/c', - onChangeText: vi.fn(), - onSend: vi.fn().mockResolvedValue(true), - agent: 'claude' - }) - ) - }) - } finally { - restore() - } + await act(async () => { + renderer = create( + createElement(MobileNativeChatComposer, { + value: '/c', + onChangeText: vi.fn(), + onSend: vi.fn().mockResolvedValue(true), + agent: 'claude' + }) + ) + }) const input = () => renderer!.root.find((node) => node.type === 'TextInput') as { props: { @@ -390,21 +335,16 @@ describe('MobileNativeChatComposer', () => { }) it('serves the active agent’s shared command catalog with descriptions', async () => { - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(MobileNativeChatComposer, { - value: '/', - onChangeText: vi.fn(), - onSend: vi.fn().mockResolvedValue(true), - agent: 'codex' - }) - ) - }) - } finally { - restore() - } + await act(async () => { + renderer = create( + createElement(MobileNativeChatComposer, { + value: '/', + onChangeText: vi.fn(), + onSend: vi.fn().mockResolvedValue(true), + agent: 'codex' + }) + ) + }) const input = renderer!.root.find((node) => node.type === 'TextInput') as { props: { onSelectionChange: (e: { nativeEvent: { selection: { end: number } } }) => void } } @@ -428,45 +368,40 @@ describe('MobileNativeChatComposer', () => { (node) => node.type === 'Pressable' && node.props.accessibilityLabel === 'Dictate' ) as { props: { onPress?: unknown; onPressIn?: unknown; onPressOut?: unknown } } - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(MobileNativeChatComposer, { - value: '', - onChangeText: vi.fn(), - onSend: vi.fn().mockResolvedValue(true), - onMicPress, - dictationMode: 'hold', - onMicPressIn, - onMicPressOut - }) - ) - }) - // Hold mode is walkie-talkie: press-in/out drive dictation, tap is inert. - expect(mic().props.onPress).toBeUndefined() - expect(mic().props.onPressIn).toBe(onMicPressIn) - expect(mic().props.onPressOut).toBe(onMicPressOut) + await act(async () => { + renderer = create( + createElement(MobileNativeChatComposer, { + value: '', + onChangeText: vi.fn(), + onSend: vi.fn().mockResolvedValue(true), + onMicPress, + dictationMode: 'hold', + onMicPressIn, + onMicPressOut + }) + ) + }) + // Hold mode is walkie-talkie: press-in/out drive dictation, tap is inert. + expect(mic().props.onPress).toBeUndefined() + expect(mic().props.onPressIn).toBe(onMicPressIn) + expect(mic().props.onPressOut).toBe(onMicPressOut) - await act(async () => { - renderer!.update( - createElement(MobileNativeChatComposer, { - value: '', - onChangeText: vi.fn(), - onSend: vi.fn().mockResolvedValue(true), - onMicPress, - dictationMode: 'toggle', - onMicPressIn, - onMicPressOut - }) - ) - }) - // Toggle mode: tap drives dictation, press-in/out inert. - expect(mic().props.onPress).toBe(onMicPress) - expect(mic().props.onPressIn).toBeUndefined() - expect(mic().props.onPressOut).toBeUndefined() - } finally { - restore() - } + await act(async () => { + renderer!.update( + createElement(MobileNativeChatComposer, { + value: '', + onChangeText: vi.fn(), + onSend: vi.fn().mockResolvedValue(true), + onMicPress, + dictationMode: 'toggle', + onMicPressIn, + onMicPressOut + }) + ) + }) + // Toggle mode: tap drives dictation, press-in/out inert. + expect(mic().props.onPress).toBe(onMicPress) + expect(mic().props.onPressIn).toBeUndefined() + expect(mic().props.onPressOut).toBeUndefined() }) }) diff --git a/mobile/src/session/MobileNativeChatMessage.test.ts b/mobile/src/session/MobileNativeChatMessage.test.ts index 9d33db069..bb2335c70 100644 --- a/mobile/src/session/MobileNativeChatMessage.test.ts +++ b/mobile/src/session/MobileNativeChatMessage.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestInstance, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { NativeChatMessage } from '../../../src/shared/native-chat-types' import { MAX_TOOL_DETAIL_LENGTH } from './mobile-native-chat-tool-summary' @@ -38,9 +38,6 @@ function toolMessage(blocks: NativeChatMessage['blocks']): NativeChatMessage { describe('MobileNativeChatMessage', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) afterEach(() => { act(() => renderer?.unmount()) renderer = null @@ -50,20 +47,9 @@ describe('MobileNativeChatMessage', () => { message: NativeChatMessage, props: { toolsExpanded?: boolean } = {} ): ReactTestRenderer { - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...a) => { - if (typeof a[0] === 'string' && a[0].includes('react-test-renderer is deprecated')) { - return - } - original(...a) + act(() => { + renderer = create(createElement(MobileNativeChatMessage, { message, ...props })) }) - try { - act(() => { - renderer = create(createElement(MobileNativeChatMessage, { message, ...props })) - }) - } finally { - spy.mockRestore() - } return renderer! } diff --git a/mobile/src/session/MobileNativeChatOverlay.test.ts b/mobile/src/session/MobileNativeChatOverlay.test.ts index c8e62c146..b43a7c79f 100644 --- a/mobile/src/session/MobileNativeChatOverlay.test.ts +++ b/mobile/src/session/MobileNativeChatOverlay.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { NativeChatMessage } from '../../../src/shared/native-chat-types' import { MobileNativeChatOverlay } from './MobileNativeChatOverlay' import type { MobileNativeChatController } from './use-mobile-native-chat-controller' @@ -16,17 +16,6 @@ function assistantTurn(id: string, text: string): NativeChatMessage { return { id, role: 'assistant', blocks: [{ type: 'text', text }], timestamp: 0, source: 'hook' } } -function suppressRendererWarning(): () => void { - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) - }) - return () => spy.mockRestore() -} - /** One render of the route: chat visible or not, the transcript it currently * holds, and the agent-status stream behind it. */ type Tick = { @@ -69,24 +58,15 @@ function overlayElement(tick: Tick): ReturnType { describe('MobileNativeChatOverlay streaming gate', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null }) async function render(tick: Tick): Promise { - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create(overlayElement(tick)) - }) - } finally { - restore() - } + await act(async () => { + renderer = create(overlayElement(tick)) + }) } async function update(tick: Tick): Promise { diff --git a/mobile/src/session/MobileNativeChatPermission.test.ts b/mobile/src/session/MobileNativeChatPermission.test.ts index 8e0223f19..b39188b38 100644 --- a/mobile/src/session/MobileNativeChatPermission.test.ts +++ b/mobile/src/session/MobileNativeChatPermission.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { MobileNativeChatPermission } from './MobileNativeChatPermission' vi.mock('react-native', () => ({ @@ -15,10 +15,6 @@ vi.mock('lucide-react-native', () => ({ ShieldQuestion: 'ShieldQuestion' })) describe('MobileNativeChatPermission', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null @@ -28,25 +24,14 @@ describe('MobileNativeChatPermission', () => { let resolveResponse: (accepted: boolean) => void = () => {} const response = new Promise((resolve) => (resolveResponse = resolve)) const onRespond = vi.fn(() => response) - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create( + createElement(MobileNativeChatPermission, { + permission: { title: 'Approve?', options: [{ label: 'Allow', send: '1' }] }, + onRespond + }) + ) }) - try { - await act(async () => { - renderer = create( - createElement(MobileNativeChatPermission, { - permission: { title: 'Approve?', options: [{ label: 'Allow', send: '1' }] }, - onRespond - }) - ) - }) - } finally { - consoleSpy.mockRestore() - } const button = renderer.root.findByType('Pressable') act(() => { diff --git a/mobile/src/session/MobileNativeChatSessionOptionPickers.test.ts b/mobile/src/session/MobileNativeChatSessionOptionPickers.test.ts index f5af434fb..4430c6011 100644 --- a/mobile/src/session/MobileNativeChatSessionOptionPickers.test.ts +++ b/mobile/src/session/MobileNativeChatSessionOptionPickers.test.ts @@ -149,7 +149,6 @@ describe('MobileNativeChatSessionOptionPickers', () => { } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true setOption.mockReset() setOption.mockResolvedValue(true) invokeAction.mockReset() diff --git a/mobile/src/session/MobileNativeChatView.test.ts b/mobile/src/session/MobileNativeChatView.test.ts index 21a5a7c4a..fb4072c4f 100644 --- a/mobile/src/session/MobileNativeChatView.test.ts +++ b/mobile/src/session/MobileNativeChatView.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestInstance, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { NativeChatMessage } from '../../../src/shared/native-chat-types' import { MobileNativeChatView } from './MobileNativeChatView' @@ -73,17 +73,6 @@ type Overrides = { onSend?: (text: string) => Promise } -function suppressRendererWarning(): () => void { - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) - }) - return () => spy.mockRestore() -} - function assistantTurn(id: string, text: string): NativeChatMessage { return { id, role: 'assistant', blocks: [{ type: 'text', text }], timestamp: 0, source: 'hook' } } @@ -105,24 +94,15 @@ function chatViewElement(overrides: Overrides): ReturnType describe('MobileNativeChatView', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null }) async function render(overrides: Overrides = {}): Promise { - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create(chatViewElement(overrides)) - }) - } finally { - restore() - } + await act(async () => { + renderer = create(chatViewElement(overrides)) + }) } async function update(overrides: Overrides = {}): Promise { diff --git a/mobile/src/session/QuickCommandsList.test.ts b/mobile/src/session/QuickCommandsList.test.ts index eb2e2c787..a396932ac 100644 --- a/mobile/src/session/QuickCommandsList.test.ts +++ b/mobile/src/session/QuickCommandsList.test.ts @@ -29,9 +29,7 @@ vi.mock('../components/MobileAgentIcon', () => ({ MobileAgentIcon: 'MobileAgentI describe('QuickCommandsList search', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) + beforeEach(() => {}) afterEach(() => { act(() => renderer?.unmount()) diff --git a/mobile/src/session/QuickCommandsSheet.test.ts b/mobile/src/session/QuickCommandsSheet.test.ts index 654882cd2..9951742b6 100644 --- a/mobile/src/session/QuickCommandsSheet.test.ts +++ b/mobile/src/session/QuickCommandsSheet.test.ts @@ -1,6 +1,6 @@ import { createElement, type ReactNode } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { TerminalQuickCommand } from '../../../src/shared/types' import type { RpcClient } from '../transport/rpc-client' import { MAX_QUICK_COMMANDS } from '../terminal/quick-commands' @@ -65,26 +65,15 @@ function deferred() { describe('QuickCommandsSheet', () => { let renderer: ReactTestRenderer | null = null - let consoleSpy: MockInstance - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true mocks.alert.mockReset() mocks.commands = [] mocks.persist.mockReset() - const originalConsoleError = console.error - consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) }) afterEach(() => { act(() => renderer?.unmount()) renderer = null - consoleSpy.mockRestore() }) it('keeps the sheet open when a launch is rejected', async () => { diff --git a/mobile/src/session/mobile-native-chat-permission-send.test.ts b/mobile/src/session/mobile-native-chat-permission-send.test.ts index 253f8c8b8..f74289d97 100644 --- a/mobile/src/session/mobile-native-chat-permission-send.test.ts +++ b/mobile/src/session/mobile-native-chat-permission-send.test.ts @@ -67,7 +67,6 @@ describe('useMobileNativeChatPermissionSend', () => { let respond: ((text: string) => Promise) | null = null beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true resetMobileNativeChatStaleInputForTests() resetMobileNativeChatTerminalWritesForTests() }) diff --git a/mobile/src/session/use-initial-session-terminal-autocreate.test.ts b/mobile/src/session/use-initial-session-terminal-autocreate.test.ts index 46a7bb859..5e4c8d425 100644 --- a/mobile/src/session/use-initial-session-terminal-autocreate.test.ts +++ b/mobile/src/session/use-initial-session-terminal-autocreate.test.ts @@ -52,7 +52,6 @@ describe('useInitialSessionTerminalAutoCreate', () => { } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true stateRef = { current: { autoCreatedForWorktree: null, sawSessionTabs: false } } diff --git a/mobile/src/session/use-live-worktree-name.test.ts b/mobile/src/session/use-live-worktree-name.test.ts index 45e64205d..ad15bda3c 100644 --- a/mobile/src/session/use-live-worktree-name.test.ts +++ b/mobile/src/session/use-live-worktree-name.test.ts @@ -13,18 +13,6 @@ vi.mock('expo-router', async () => { } }) -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - const firstArg = args[0] - if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => consoleErrorSpy.mockRestore() -} - describe('useLiveWorktreeName request volume', () => { let renderer: ReactTestRenderer | null = null let eventListener: ((payload: unknown) => void) | null = null @@ -54,15 +42,10 @@ describe('useLiveWorktreeName request volume', () => { return null } - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - await act(async () => { - renderer = create(createElement(Harness)) - await Promise.resolve() - }) - } finally { - restoreConsoleError() - } + await act(async () => { + renderer = create(createElement(Harness)) + await Promise.resolve() + }) } async function emitEvent(payload: unknown): Promise { @@ -75,7 +58,6 @@ describe('useLiveWorktreeName request volume', () => { beforeEach(() => { vi.useFakeTimers() - globalThis.IS_REACT_ACT_ENVIRONMENT = true eventListener = null sendRequest.mockClear().mockResolvedValue({ id: 'worktree-show', @@ -179,15 +161,10 @@ describe('useLiveWorktreeName request volume', () => { return null } - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - await act(async () => { - renderer = create(createElement(FloatingHarness)) - await Promise.resolve() - }) - } finally { - restoreConsoleError() - } + await act(async () => { + renderer = create(createElement(FloatingHarness)) + await Promise.resolve() + }) await act(async () => { await vi.advanceTimersByTimeAsync(30_000) }) @@ -214,7 +191,6 @@ describe('useLiveWorktreeName request volume', () => { return null } - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create( @@ -241,7 +217,6 @@ describe('useLiveWorktreeName request volume', () => { ) }) } finally { - restoreConsoleError() act(() => renderer?.unmount()) } @@ -263,15 +238,10 @@ describe('useLiveWorktreeName request volume', () => { return null } const mount = async (): Promise => { - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - await act(async () => { - renderer = create(createElement(VerdictHarness)) - await Promise.resolve() - }) - } finally { - restoreConsoleError() - } + await act(async () => { + renderer = create(createElement(VerdictHarness)) + await Promise.resolve() + }) } await mount() diff --git a/mobile/src/session/use-missing-worktree-bounce.test.ts b/mobile/src/session/use-missing-worktree-bounce.test.ts index 209486bc8..df1bccc56 100644 --- a/mobile/src/session/use-missing-worktree-bounce.test.ts +++ b/mobile/src/session/use-missing-worktree-bounce.test.ts @@ -27,7 +27,6 @@ describe('useMissingWorktreeBounce', () => { const bounce = vi.fn() beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true bounce.mockReset() renderer = null }) diff --git a/mobile/src/session/use-mobile-attachment-input-lease-gate.test.ts b/mobile/src/session/use-mobile-attachment-input-lease-gate.test.ts index a69d66acc..9b11ad70c 100644 --- a/mobile/src/session/use-mobile-attachment-input-lease-gate.test.ts +++ b/mobile/src/session/use-mobile-attachment-input-lease-gate.test.ts @@ -7,16 +7,7 @@ type Gate = (targetHandle: string) => Promise describe('useMobileAttachmentInputLeaseGate', () => { let renderer: ReactTestRenderer | null = null - let errorSpy: ReturnType | null = null - beforeEach(() => { - const original = console.error - errorSpy = vi.spyOn(console, 'error').mockImplementation((...args: unknown[]) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...(args as Parameters)) - }) vi.useFakeTimers() }) @@ -24,7 +15,6 @@ describe('useMobileAttachmentInputLeaseGate', () => { act(() => renderer?.unmount()) renderer = null vi.useRealTimers() - errorSpy?.mockRestore() }) function renderGate(args: { diff --git a/mobile/src/session/use-mobile-default-session-view-preference.test.ts b/mobile/src/session/use-mobile-default-session-view-preference.test.ts index eb2624efd..084164b6a 100644 --- a/mobile/src/session/use-mobile-default-session-view-preference.test.ts +++ b/mobile/src/session/use-mobile-default-session-view-preference.test.ts @@ -30,7 +30,6 @@ describe('useMobileDefaultSessionViewPreference', () => { let preference: MobileDefaultSessionViewPreference | null = null beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true vi.mocked(loadDefaultSessionView).mockReset().mockResolvedValue('terminal') vi.mocked(saveDefaultSessionView).mockReset().mockResolvedValue(undefined) }) diff --git a/mobile/src/session/use-mobile-diff-review-controller.test.ts b/mobile/src/session/use-mobile-diff-review-controller.test.ts index 7c0a80455..5056b3cb5 100644 --- a/mobile/src/session/use-mobile-diff-review-controller.test.ts +++ b/mobile/src/session/use-mobile-diff-review-controller.test.ts @@ -63,7 +63,6 @@ describe('useMobileDiffReviewController', () => { } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true loadSnapshot.mockReset() }) diff --git a/mobile/src/session/use-mobile-diff-review-diff-loading.test.ts b/mobile/src/session/use-mobile-diff-review-diff-loading.test.ts index 3245c2282..4abb74431 100644 --- a/mobile/src/session/use-mobile-diff-review-diff-loading.test.ts +++ b/mobile/src/session/use-mobile-diff-review-diff-loading.test.ts @@ -57,7 +57,6 @@ describe('useMobileDiffReviewDiffLoading', () => { } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true loadDiff.mockReset() }) diff --git a/mobile/src/session/use-mobile-diff-review-send-actions.test.ts b/mobile/src/session/use-mobile-diff-review-send-actions.test.ts index f7594fd6e..1d0619094 100644 --- a/mobile/src/session/use-mobile-diff-review-send-actions.test.ts +++ b/mobile/src/session/use-mobile-diff-review-send-actions.test.ts @@ -52,7 +52,6 @@ describe('useMobileDiffReviewSendActions', () => { let saveCommentsAndReviewState: ReturnType beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true resetMobileNativeChatStaleInputForTests() setActionError = vi.fn() setSendSheet = vi.fn() @@ -81,20 +80,9 @@ describe('useMobileDiffReviewSendActions', () => { async function mount(client: RpcClient): Promise { mountedClient = client - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness)) }) - try { - await act(async () => { - renderer = create(createElement(Harness)) - }) - } finally { - consoleSpy.mockRestore() - } } it('heals a marked terminal BEFORE submitting the notes', async () => { diff --git a/mobile/src/session/use-mobile-file-tap-handlers.test.ts b/mobile/src/session/use-mobile-file-tap-handlers.test.ts index 6f48b2a77..0cfa617f2 100644 --- a/mobile/src/session/use-mobile-file-tap-handlers.test.ts +++ b/mobile/src/session/use-mobile-file-tap-handlers.test.ts @@ -19,7 +19,6 @@ describe('useMobileFileTapHandlers', () => { let handlers: Handlers | null = null beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true push.mockClear() }) diff --git a/mobile/src/session/use-mobile-native-chat-answer-send.test.ts b/mobile/src/session/use-mobile-native-chat-answer-send.test.ts index b296ea485..64fa30aec 100644 --- a/mobile/src/session/use-mobile-native-chat-answer-send.test.ts +++ b/mobile/src/session/use-mobile-native-chat-answer-send.test.ts @@ -54,7 +54,6 @@ describe('useMobileNativeChatAnswerSend', () => { beforeEach(() => { onAccepted = vi.fn() vi.useFakeTimers() - globalThis.IS_REACT_ACT_ENVIRONMENT = true resetMobileNativeChatStaleInputForTests() resetMobileNativeChatTerminalWritesForTests() }) @@ -93,20 +92,9 @@ describe('useMobileNativeChatAnswerSend', () => { mountedClient = client mountedOnSendError = onSendError mountedAgent = agent - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { enabled: true })) }) - try { - await act(async () => { - renderer = create(createElement(Harness, { enabled: true })) - }) - } finally { - consoleSpy.mockRestore() - } } async function setEnabled(enabled: boolean): Promise { diff --git a/mobile/src/session/use-mobile-native-chat-ask-dismiss.test.ts b/mobile/src/session/use-mobile-native-chat-ask-dismiss.test.ts index 460ffca54..de9d53fb0 100644 --- a/mobile/src/session/use-mobile-native-chat-ask-dismiss.test.ts +++ b/mobile/src/session/use-mobile-native-chat-ask-dismiss.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' import type { AskPrompt } from './mobile-native-chat-ask' import { useMobileNativeChatAskDismiss } from './use-mobile-native-chat-ask-dismiss' @@ -10,7 +10,6 @@ describe('useMobileNativeChatAskDismiss', () => { let renders = 0 beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true renders = 0 }) @@ -45,20 +44,9 @@ describe('useMobileNativeChatAskDismiss', () => { } async function mount(props: Parameters[0]): Promise { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, props)) }) - try { - await act(async () => { - renderer = create(createElement(Harness, props)) - }) - } finally { - consoleSpy.mockRestore() - } } async function update(props: Parameters[0]): Promise { diff --git a/mobile/src/session/use-mobile-native-chat-controller.test.ts b/mobile/src/session/use-mobile-native-chat-controller.test.ts index a9cd12016..c04b56079 100644 --- a/mobile/src/session/use-mobile-native-chat-controller.test.ts +++ b/mobile/src/session/use-mobile-native-chat-controller.test.ts @@ -127,24 +127,12 @@ describe('useMobileNativeChatController handleNativeChatSend', () => { } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true vi.clearAllMocks() resetMobileNativeChatStaleInputForTests() captureSendOrigin.mockReturnValue(ORIGIN) - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...a) => { - if (typeof a[0] === 'string' && a[0].includes('react-test-renderer is deprecated')) { - return - } - original(...a) + act(() => { + renderer = create(createElement(Harness)) }) - try { - act(() => { - renderer = create(createElement(Harness)) - }) - } finally { - spy.mockRestore() - } }) afterEach(() => { act(() => renderer?.unmount()) @@ -364,24 +352,12 @@ describe('useMobileNativeChatController launch-draft wiring', () => { } function render(tab: unknown): void { - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...a) => { - if (typeof a[0] === 'string' && a[0].includes('react-test-renderer is deprecated')) { - return - } - original(...a) + act(() => { + renderer = create(createElement(Harness, { tab })) }) - try { - act(() => { - renderer = create(createElement(Harness, { tab })) - }) - } finally { - spy.mockRestore() - } } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true draftsArgs.length = 0 viewMode.isTabChatView = () => true sessionState.messages = [] @@ -487,25 +463,13 @@ describe('useMobileNativeChatController ask dismissal across a transcript reload } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true viewMode.isTabChatView = () => true setTranscript('ready') promptsState.ask = PROMPT promptsState.detectedAsk = PROMPT - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...a) => { - if (typeof a[0] === 'string' && a[0].includes('react-test-renderer is deprecated')) { - return - } - original(...a) + act(() => { + renderer = create(createElement(Harness)) }) - try { - act(() => { - renderer = create(createElement(Harness)) - }) - } finally { - spy.mockRestore() - } }) afterEach(() => { @@ -757,22 +721,10 @@ describe('useMobileNativeChatController streaming scope', () => { } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true viewMode.isTabChatView = () => true - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...a) => { - if (typeof a[0] === 'string' && a[0].includes('react-test-renderer is deprecated')) { - return - } - original(...a) + act(() => { + renderer = create(createElement(Harness)) }) - try { - act(() => { - renderer = create(createElement(Harness)) - }) - } finally { - spy.mockRestore() - } }) afterEach(() => { diff --git a/mobile/src/session/use-mobile-native-chat-drafts-launch-draft.test.ts b/mobile/src/session/use-mobile-native-chat-drafts-launch-draft.test.ts index 91c84814c..665d3ac1f 100644 --- a/mobile/src/session/use-mobile-native-chat-drafts-launch-draft.test.ts +++ b/mobile/src/session/use-mobile-native-chat-drafts-launch-draft.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it } from 'vitest' import type { NativeChatMessage } from '../../../src/shared/native-chat-types' import { useMobileNativeChatDrafts } from './use-mobile-native-chat-drafts' @@ -23,10 +23,6 @@ describe('useMobileNativeChatDrafts launch draft', () => { let renderer: ReactTestRenderer | null = null let state: DraftState | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null @@ -65,20 +61,9 @@ describe('useMobileNativeChatDrafts launch draft', () => { } async function mount(tabId: string): Promise { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { tabId })) }) - try { - await act(async () => { - renderer = create(createElement(Harness, { tabId })) - }) - } finally { - consoleSpy.mockRestore() - } } it('prefills the composer from a host launch draft exactly once', async () => { diff --git a/mobile/src/session/use-mobile-native-chat-drafts.test.ts b/mobile/src/session/use-mobile-native-chat-drafts.test.ts index 154ef3fb8..db8e76696 100644 --- a/mobile/src/session/use-mobile-native-chat-drafts.test.ts +++ b/mobile/src/session/use-mobile-native-chat-drafts.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { NativeChatMessage } from '../../../src/shared/native-chat-types' import { useMobileNativeChatDrafts } from './use-mobile-native-chat-drafts' @@ -30,10 +30,6 @@ describe('useMobileNativeChatDrafts', () => { let renderer: ReactTestRenderer | null = null let state: DraftState | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null @@ -69,20 +65,9 @@ describe('useMobileNativeChatDrafts', () => { } async function mount(tabId: string): Promise { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { tabId })) }) - try { - await act(async () => { - renderer = create(createElement(Harness, { tabId })) - }) - } finally { - consoleSpy.mockRestore() - } } async function switchTo(tabId: string): Promise { diff --git a/mobile/src/session/use-mobile-native-chat-file-search.test.ts b/mobile/src/session/use-mobile-native-chat-file-search.test.ts index e05000198..93db87c8b 100644 --- a/mobile/src/session/use-mobile-native-chat-file-search.test.ts +++ b/mobile/src/session/use-mobile-native-chat-file-search.test.ts @@ -15,17 +15,6 @@ function rpcSuccess(files: string[]): Awaited void { - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) - }) - return () => spy.mockRestore() -} - describe('useMobileNativeChatFileSearch', () => { let renderer: ReactTestRenderer | null = null let state: SearchState | null = null @@ -35,19 +24,13 @@ describe('useMobileNativeChatFileSearch', () => { state = useMobileNativeChatFileSearch({ client, worktreeId: 'wt-1' }) return null } - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create(createElement(Harness)) - }) - } finally { - restore() - } + await act(async () => { + renderer = create(createElement(Harness)) + }) } beforeEach(() => { vi.useFakeTimers() - globalThis.IS_REACT_ACT_ENVIRONMENT = true state = null }) diff --git a/mobile/src/session/use-mobile-native-chat-image-attachments.test.ts b/mobile/src/session/use-mobile-native-chat-image-attachments.test.ts index a57e1c752..cb8522bae 100644 --- a/mobile/src/session/use-mobile-native-chat-image-attachments.test.ts +++ b/mobile/src/session/use-mobile-native-chat-image-attachments.test.ts @@ -87,7 +87,6 @@ describe('useMobileNativeChatImageAttachments', () => { } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true pick.mockReset() // Stale markers and write locks live at module scope (they outlive the // screen), so they also outlive a test. @@ -101,20 +100,9 @@ describe('useMobileNativeChatImageAttachments', () => { }) function mount(args: HookArgs): void { - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...a) => { - if (typeof a[0] === 'string' && a[0].includes('react-test-renderer is deprecated')) { - return - } - original(...a) + act(() => { + renderer = create(createElement(Harness, { args })) }) - try { - act(() => { - renderer = create(createElement(Harness, { args })) - }) - } finally { - spy.mockRestore() - } } function update(args: HookArgs): void { diff --git a/mobile/src/session/use-mobile-native-chat-input-lease.test.ts b/mobile/src/session/use-mobile-native-chat-input-lease.test.ts index c7e60170f..7491f7450 100644 --- a/mobile/src/session/use-mobile-native-chat-input-lease.test.ts +++ b/mobile/src/session/use-mobile-native-chat-input-lease.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it } from 'vitest' import { useMobileNativeChatInputLease } from './use-mobile-native-chat-input-lease' type Lease = ReturnType @@ -9,10 +9,6 @@ describe('useMobileNativeChatInputLease', () => { let renderer: ReactTestRenderer | null = null let lease: Lease | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null @@ -25,65 +21,43 @@ describe('useMobileNativeChatInputLease', () => { } it('unlocks only after acknowledgement and clears on disconnect', async () => { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { connected: true })) }) - try { - await act(async () => { - renderer = create(createElement(Harness, { connected: true })) - }) - expect(lease?.ready).toBe(false) - expect(lease?.lockReason).toBe('waiting') - act(() => lease?.markReady('terminal')) - expect(lease?.ready).toBe(true) - expect(lease?.lockReason).toBeNull() + expect(lease?.ready).toBe(false) + expect(lease?.lockReason).toBe('waiting') + act(() => lease?.markReady('terminal')) + expect(lease?.ready).toBe(true) + expect(lease?.lockReason).toBeNull() - act(() => lease?.clear()) - expect(lease?.ready).toBe(false) - act(() => lease?.markReady('terminal')) - expect(lease?.ready).toBe(true) + act(() => lease?.clear()) + expect(lease?.ready).toBe(false) + act(() => lease?.markReady('terminal')) + expect(lease?.ready).toBe(true) - await act(async () => renderer?.update(createElement(Harness, { connected: false }))) - expect(lease?.ready).toBe(false) - expect(lease?.lockReason).toBe('disconnected') - } finally { - consoleSpy.mockRestore() - } + await act(async () => renderer?.update(createElement(Harness, { connected: false }))) + expect(lease?.ready).toBe(false) + expect(lease?.lockReason).toBe('disconnected') }) it('reports whether a clear actually dropped a lease', async () => { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { connected: true })) }) - try { - await act(async () => { - renderer = create(createElement(Harness, { connected: true })) - }) - // The route reads this to tell a real teardown from one React never sees. - expect(lease?.clear('terminal')).toBe(false) - expect(lease?.clear()).toBe(false) + // The route reads this to tell a real teardown from one React never sees. + expect(lease?.clear('terminal')).toBe(false) + expect(lease?.clear()).toBe(false) - act(() => lease?.markReady('terminal')) - let dropped: boolean | undefined - act(() => { - dropped = lease?.clear('terminal') - }) - expect(dropped).toBe(true) - expect(lease?.ready).toBe(false) - expect(lease?.clear('terminal')).toBe(false) + act(() => lease?.markReady('terminal')) + let dropped: boolean | undefined + act(() => { + dropped = lease?.clear('terminal') + }) + expect(dropped).toBe(true) + expect(lease?.ready).toBe(false) + expect(lease?.clear('terminal')).toBe(false) - act(() => lease?.markReady('other')) - expect(lease?.clear()).toBe(true) - } finally { - consoleSpy.mockRestore() - } + act(() => lease?.markReady('other')) + expect(lease?.clear()).toBe(true) }) }) diff --git a/mobile/src/session/use-mobile-native-chat-message-send.test.ts b/mobile/src/session/use-mobile-native-chat-message-send.test.ts index 0ceda5714..06b30f5cd 100644 --- a/mobile/src/session/use-mobile-native-chat-message-send.test.ts +++ b/mobile/src/session/use-mobile-native-chat-message-send.test.ts @@ -80,7 +80,6 @@ describe('useMobileNativeChatMessageSend', () => { (clearInputWrite.mock.calls[0]?.[0] ?? {}) as { clearInput?: string } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true sendWithOutcome.mockReset() sendWithOutcome.mockResolvedValue('accepted') clearInputWrite.mockReset() diff --git a/mobile/src/session/use-mobile-native-chat-readability.test.ts b/mobile/src/session/use-mobile-native-chat-readability.test.ts index 41d08c3c3..b7e2c5daa 100644 --- a/mobile/src/session/use-mobile-native-chat-readability.test.ts +++ b/mobile/src/session/use-mobile-native-chat-readability.test.ts @@ -10,7 +10,6 @@ describe('useMobileNativeChatReadability', () => { let readable = false beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true readable = false }) @@ -34,21 +33,10 @@ describe('useMobileNativeChatReadability', () => { readable = useMobileNativeChatReadability(client, worktreeId) return null } - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness)) + await Promise.resolve() }) - try { - await act(async () => { - renderer = create(createElement(Harness)) - await Promise.resolve() - }) - } finally { - consoleSpy.mockRestore() - } return sendRequest } @@ -89,32 +77,21 @@ describe('useMobileNativeChatReadability', () => { readable = useMobileNativeChatReadability(client, worktreeId) return null } - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { worktreeId: 'local-repo::/one' })) + await Promise.resolve() }) - try { - await act(async () => { - renderer = create(createElement(Harness, { worktreeId: 'local-repo::/one' })) - await Promise.resolve() - }) - expect(readable).toBe(true) + expect(readable).toBe(true) - act(() => renderer?.update(createElement(Harness, { worktreeId: 'ssh-repo::/two' }))) - expect(readable).toBe(false) - await act(async () => { - resolveNext({ - ok: true, - result: { repos: [{ id: 'ssh-repo', connectionId: 'model-a-ssh' }] } - }) - await Promise.resolve() + act(() => renderer?.update(createElement(Harness, { worktreeId: 'ssh-repo::/two' }))) + expect(readable).toBe(false) + await act(async () => { + resolveNext({ + ok: true, + result: { repos: [{ id: 'ssh-repo', connectionId: 'model-a-ssh' }] } }) - expect(readable).toBe(false) - } finally { - consoleSpy.mockRestore() - } + await Promise.resolve() + }) + expect(readable).toBe(false) }) }) diff --git a/mobile/src/session/use-mobile-native-chat-send-error.test.ts b/mobile/src/session/use-mobile-native-chat-send-error.test.ts index a22eee3a0..49ca07198 100644 --- a/mobile/src/session/use-mobile-native-chat-send-error.test.ts +++ b/mobile/src/session/use-mobile-native-chat-send-error.test.ts @@ -31,31 +31,13 @@ describe('useMobileNativeChatSendError', () => { return apiRef.current } - /** react-test-renderer logs a deprecation notice on every render; keep real errors. */ - function suppressRendererWarning(): () => void { - const original = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) - }) - return () => spy.mockRestore() - } - async function render(scopeKey: string | null = 'terminal-1'): Promise { - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create(createElement(Harness, { scopeKey })) - }) - } finally { - restore() - } + await act(async () => { + renderer = create(createElement(Harness, { scopeKey })) + }) } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true apiRef.current = null showToast.mockClear() vi.useFakeTimers() @@ -117,26 +99,16 @@ describe('useMobileNativeChatSendError', () => { await act(async () => api().show('a')) expect(api().message).toBe('a') - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer?.update(createElement(Harness, { scopeKey: 'terminal-2' })) - }) - } finally { - restore() - } + await act(async () => { + renderer?.update(createElement(Harness, { scopeKey: 'terminal-2' })) + }) expect(api().message).toBeNull() }) it('falls back to the toast when the banner is not mounted', async () => { - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create(createElement(Harness, { scopeKey: 'terminal-1', bannerMounted: false })) - }) - } finally { - restore() - } + await act(async () => { + renderer = create(createElement(Harness, { scopeKey: 'terminal-1', bannerMounted: false })) + }) // A deferred failure landing after the user left chat must still be seen. await act(async () => api().show('Delivery unconfirmed')) @@ -149,14 +121,9 @@ describe('useMobileNativeChatSendError', () => { // Captured while tab A was live; a 20s unconfirmed send resolves much later. const showFromTabA = api().show - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer?.update(createElement(Harness, { scopeKey: 'terminal-2' })) - }) - } finally { - restore() - } + await act(async () => { + renderer?.update(createElement(Harness, { scopeKey: 'terminal-2' })) + }) await act(async () => showFromTabA('Message not sent')) // The banner belongs to terminal-2 now, so A's failure must not paint there. @@ -168,14 +135,9 @@ describe('useMobileNativeChatSendError', () => { await render('terminal-1') const clearFromTabA = api().clear - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer?.update(createElement(Harness, { scopeKey: 'terminal-2' })) - }) - } finally { - restore() - } + await act(async () => { + renderer?.update(createElement(Harness, { scopeKey: 'terminal-2' })) + }) await act(async () => api().show('b')) // An accepted card action from tab A resolving late must not retire B's warning. await act(async () => clearFromTabA()) diff --git a/mobile/src/session/use-mobile-native-chat-session-options.test.ts b/mobile/src/session/use-mobile-native-chat-session-options.test.ts index 246c30219..d7cab9a4c 100644 --- a/mobile/src/session/use-mobile-native-chat-session-options.test.ts +++ b/mobile/src/session/use-mobile-native-chat-session-options.test.ts @@ -44,7 +44,6 @@ describe('useMobileNativeChatSessionOptions', () => { } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true clearMobileSessionOptionRecordsForTests() dispatchCommand.mockReset() dispatchCommand.mockResolvedValue('accepted') 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 8c685d002..dfe5f2859 100644 --- a/mobile/src/session/use-mobile-native-chat-session.test.ts +++ b/mobile/src/session/use-mobile-native-chat-session.test.ts @@ -24,7 +24,6 @@ describe('useMobileNativeChatSession', () => { let emit: (frame: unknown) => void = () => {} beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true state = null }) @@ -45,20 +44,9 @@ describe('useMobileNativeChatSession', () => { } async function mount(client: RpcClient): Promise { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { client })) }) - try { - await act(async () => { - renderer = create(createElement(Harness, { client })) - }) - } finally { - consoleSpy.mockRestore() - } } it('drops an older-page response captured before transcript replacement', async () => { @@ -445,7 +433,6 @@ describe('useMobileNativeChatSession transcriptLoading', () => { }[] = [] beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true renders.length = 0 }) @@ -482,20 +469,9 @@ describe('useMobileNativeChatSession transcriptLoading', () => { } async function mountAt(client: RpcClient | null, sessionId: string | null): Promise { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { client, sessionId })) }) - try { - await act(async () => { - renderer = create(createElement(Harness, { client, sessionId })) - }) - } finally { - consoleSpy.mockRestore() - } } it('reports loading on the very first render, before the subscription effect runs', async () => { diff --git a/mobile/src/session/use-mobile-native-chat-stop.test.ts b/mobile/src/session/use-mobile-native-chat-stop.test.ts index 2a5460b67..bdc405cb5 100644 --- a/mobile/src/session/use-mobile-native-chat-stop.test.ts +++ b/mobile/src/session/use-mobile-native-chat-stop.test.ts @@ -14,7 +14,6 @@ describe('useMobileNativeChatStop', () => { beforeEach(() => { vi.useFakeTimers() - globalThis.IS_REACT_ACT_ENVIRONMENT = true sendRequest.mockReset().mockResolvedValue({ ok: true, result: { send: { accepted: true } } diff --git a/mobile/src/session/use-mobile-native-chat-terminal-stream.test.ts b/mobile/src/session/use-mobile-native-chat-terminal-stream.test.ts index fdfb50414..ce282fcd5 100644 --- a/mobile/src/session/use-mobile-native-chat-terminal-stream.test.ts +++ b/mobile/src/session/use-mobile-native-chat-terminal-stream.test.ts @@ -17,7 +17,6 @@ describe('useMobileNativeChatTerminalStream', () => { const hasTabsRecoveryNeedRef = { current: (): boolean => false } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true subscriptionsRef.current = new Map([['terminal-1', () => {}]]) subscribingRef.current = new Set() webReadyRef.current = new Set(['terminal-1']) @@ -85,113 +84,73 @@ describe('useMobileNativeChatTerminalStream', () => { } it('replaces output with a lease-only stream while covered, then restores output', async () => { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { showNativeChat: false })) + }) + await act(async () => { + notifyWebReadyRef.current('terminal-1', false) + }) + expect(harnessRenderCount).toBe(1) + await act(async () => { + renderer?.update(createElement(Harness, { showNativeChat: true })) }) - try { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: false })) - }) - await act(async () => { - notifyWebReadyRef.current('terminal-1', false) - }) - expect(harnessRenderCount).toBe(1) - await act(async () => { - renderer?.update(createElement(Harness, { showNativeChat: true })) - }) - expect(unsubscribe).toHaveBeenNthCalledWith(1, 'terminal-1') - expect(subscribe).toHaveBeenNthCalledWith(1, 'terminal-1') - expect(initializedRef.current.has('terminal-1')).toBe(false) + expect(unsubscribe).toHaveBeenNthCalledWith(1, 'terminal-1') + expect(subscribe).toHaveBeenNthCalledWith(1, 'terminal-1') + expect(initializedRef.current.has('terminal-1')).toBe(false) - await act(async () => { - renderer?.update(createElement(Harness, { showNativeChat: false })) - }) + await act(async () => { + renderer?.update(createElement(Harness, { showNativeChat: false })) + }) - expect(unsubscribe).toHaveBeenNthCalledWith(2, 'terminal-1') - expect(subscribe).toHaveBeenNthCalledWith(2, 'terminal-1') - } finally { - consoleSpy.mockRestore() - } + expect(unsubscribe).toHaveBeenNthCalledWith(2, 'terminal-1') + expect(subscribe).toHaveBeenNthCalledWith(2, 'terminal-1') }) it('re-subscribes a covered stream torn down under chat (#10681)', async () => { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { showNativeChat: true })) + }) + subscribe.mockClear() + unsubscribe.mockClear() + // What a terminal.list prune / `end` frame does to the lease-only stream: + // the subscription is gone and the lease drops with it. + subscriptionsRef.current.delete('terminal-1') + await act(async () => { + renderer?.update(createElement(Harness, { showNativeChat: true, leaseReady: false })) }) - try { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) - }) - subscribe.mockClear() - unsubscribe.mockClear() - // What a terminal.list prune / `end` frame does to the lease-only stream: - // the subscription is gone and the lease drops with it. - subscriptionsRef.current.delete('terminal-1') - await act(async () => { - renderer?.update(createElement(Harness, { showNativeChat: true, leaseReady: false })) - }) - expect(subscribe).toHaveBeenCalledOnce() - expect(subscribe).toHaveBeenCalledWith('terminal-1') - // Pins the rearm branch specifically: without it the action falls through to - // the resume tail, which also subscribes — but drops coverage on the way. - expect(unsubscribe).not.toHaveBeenCalled() - } finally { - consoleSpy.mockRestore() - } + expect(subscribe).toHaveBeenCalledOnce() + expect(subscribe).toHaveBeenCalledWith('terminal-1') + // Pins the rearm branch specifically: without it the action falls through to + // the resume tail, which also subscribes — but drops coverage on the way. + expect(unsubscribe).not.toHaveBeenCalled() }) it('stops rearming a handle whose stream never comes back (#10681)', async () => { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { showNativeChat: true })) }) - try { + subscribe.mockClear() + // A dead PTY answers every subscribe with `subscribed`+`end`, so the stream is + // gone again on each pass. Unbounded, that is a ~10s resubscribe loop. + for (let revision = 1; revision <= 6; revision += 1) { + subscriptionsRef.current.delete('terminal-1') await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) + renderer?.update( + createElement(Harness, { + showNativeChat: true, + leaseReady: false, + streamRevision: revision + }) + ) }) - subscribe.mockClear() - // A dead PTY answers every subscribe with `subscribed`+`end`, so the stream is - // gone again on each pass. Unbounded, that is a ~10s resubscribe loop. - for (let revision = 1; revision <= 6; revision += 1) { - subscriptionsRef.current.delete('terminal-1') - await act(async () => { - renderer?.update( - createElement(Harness, { - showNativeChat: true, - leaseReady: false, - streamRevision: revision - }) - ) - }) - } - - expect(subscribe.mock.calls.length).toBeLessThanOrEqual(3) - } finally { - consoleSpy.mockRestore() } + + expect(subscribe.mock.calls.length).toBeLessThanOrEqual(3) }) it('does not charge the rearm budget for a subscribe its own gates turned away', async () => { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) - }) try { await act(async () => { renderer = create(createElement(Harness, { showNativeChat: true })) @@ -218,89 +177,66 @@ describe('useMobileNativeChatTerminalStream', () => { subscribe.mockImplementation((handle: string) => subscriptionsRef.current.set(handle, () => {}) ) - consoleSpy.mockRestore() } }) it('refills the rearm budget when terminal.list reports the handle again (#10681)', async () => { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + await act(async () => { + renderer = create(createElement(Harness, { showNativeChat: true })) }) - try { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) - }) - for (let revision = 1; revision <= 5; revision += 1) { - subscriptionsRef.current.delete('terminal-1') - await act(async () => { - renderer?.update( - createElement(Harness, { - showNativeChat: true, - leaseReady: false, - streamRevision: revision - }) - ) - }) - } - subscribe.mockClear() - // Budget is spent, so a further teardown signal alone changes nothing. - await act(async () => { - renderer?.update( - createElement(Harness, { showNativeChat: true, leaseReady: false, streamRevision: 6 }) - ) - }) - expect(subscribe).not.toHaveBeenCalled() - - // A dead-but-listed handle must not buy a new budget on every list refresh, - // or the resubscribe loop this bound exists to stop comes straight back. - await act(async () => { - notifyListedHandlesRef.current(new Set(['terminal-1'])) - notifyListedHandlesRef.current(new Set(['terminal-1'])) - }) - expect(subscribe).not.toHaveBeenCalled() - - // The handle actually went away and came back: it may have a live PTY once more. - await act(async () => { - notifyListedHandlesRef.current(new Set()) - notifyListedHandlesRef.current(new Set(['terminal-1'])) - }) - - expect(subscribe).toHaveBeenCalledWith('terminal-1') - } finally { - consoleSpy.mockRestore() - } - }) - - it('rearms on a teardown the lease cannot report (#10681)', async () => { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) - }) - try { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) - }) - subscribe.mockClear() - // `end` with no preceding `subscribed` leaves the lease untouched, so - // `leaseReady` holds its value and only the revision bump can re-run us. + for (let revision = 1; revision <= 5; revision += 1) { subscriptionsRef.current.delete('terminal-1') await act(async () => { renderer?.update( - createElement(Harness, { showNativeChat: true, leaseReady: true, streamRevision: 1 }) + createElement(Harness, { + showNativeChat: true, + leaseReady: false, + streamRevision: revision + }) ) }) - - expect(subscribe).toHaveBeenCalledWith('terminal-1') - } finally { - consoleSpy.mockRestore() } + subscribe.mockClear() + // Budget is spent, so a further teardown signal alone changes nothing. + await act(async () => { + renderer?.update( + createElement(Harness, { showNativeChat: true, leaseReady: false, streamRevision: 6 }) + ) + }) + expect(subscribe).not.toHaveBeenCalled() + + // A dead-but-listed handle must not buy a new budget on every list refresh, + // or the resubscribe loop this bound exists to stop comes straight back. + await act(async () => { + notifyListedHandlesRef.current(new Set(['terminal-1'])) + notifyListedHandlesRef.current(new Set(['terminal-1'])) + }) + expect(subscribe).not.toHaveBeenCalled() + + // The handle actually went away and came back: it may have a live PTY once more. + await act(async () => { + notifyListedHandlesRef.current(new Set()) + notifyListedHandlesRef.current(new Set(['terminal-1'])) + }) + + expect(subscribe).toHaveBeenCalledWith('terminal-1') + }) + + it('rearms on a teardown the lease cannot report (#10681)', async () => { + await act(async () => { + renderer = create(createElement(Harness, { showNativeChat: true })) + }) + subscribe.mockClear() + // `end` with no preceding `subscribed` leaves the lease untouched, so + // `leaseReady` holds its value and only the revision bump can re-run us. + subscriptionsRef.current.delete('terminal-1') + await act(async () => { + renderer?.update( + createElement(Harness, { showNativeChat: true, leaseReady: true, streamRevision: 1 }) + ) + }) + + expect(subscribe).toHaveBeenCalledWith('terminal-1') }) it('does not let a dead PTY buy a new budget with its own `subscribed` ack', async () => { @@ -411,34 +347,23 @@ describe('useMobileNativeChatTerminalStream', () => { }) it('resumes a cold-start lease-only stream when WebView readiness arrives late', async () => { - const original = console.error - const consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) + webReadyRef.current.clear() + await act(async () => { + renderer = create(createElement(Harness, { showNativeChat: true })) + }) + await act(async () => { + renderer?.update(createElement(Harness, { showNativeChat: false })) }) - try { - webReadyRef.current.clear() - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) - }) - await act(async () => { - renderer?.update(createElement(Harness, { showNativeChat: false })) - }) - expect(unsubscribe).toHaveBeenCalledOnce() - expect(subscribe).toHaveBeenCalledOnce() + expect(unsubscribe).toHaveBeenCalledOnce() + expect(subscribe).toHaveBeenCalledOnce() - webReadyRef.current.add('terminal-1') - await act(async () => { - notifyWebReadyRef.current('terminal-1', false) - }) + webReadyRef.current.add('terminal-1') + await act(async () => { + notifyWebReadyRef.current('terminal-1', false) + }) - expect(unsubscribe).toHaveBeenNthCalledWith(2, 'terminal-1') - expect(subscribe).toHaveBeenNthCalledWith(2, 'terminal-1') - } finally { - consoleSpy.mockRestore() - } + expect(unsubscribe).toHaveBeenNthCalledWith(2, 'terminal-1') + expect(subscribe).toHaveBeenNthCalledWith(2, 'terminal-1') }) }) diff --git a/mobile/src/session/use-mobile-session-tabs-reconciliation.test.ts b/mobile/src/session/use-mobile-session-tabs-reconciliation.test.ts index 2ec37155b..33c507f27 100644 --- a/mobile/src/session/use-mobile-session-tabs-reconciliation.test.ts +++ b/mobile/src/session/use-mobile-session-tabs-reconciliation.test.ts @@ -112,8 +112,6 @@ async function setAppState(state: string): Promise { describe('useMobileSessionTabsReconciliation', () => { let renderer: ReactTestRenderer | null = null - let consoleErrorSpy: ReturnType - async function mount(): Promise { await act(async () => { renderer = create(createElement(Harness)) @@ -124,14 +122,6 @@ describe('useMobileSessionTabsReconciliation', () => { beforeEach(() => { vi.useFakeTimers() vi.setSystemTime(0) - globalThis.IS_REACT_ACT_ENVIRONMENT = true - const originalConsoleError = console.error - consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) lifecycle.appState = 'active' lifecycle.focused = true lifecycle.listeners.clear() @@ -157,7 +147,6 @@ describe('useMobileSessionTabsReconciliation', () => { act(() => renderer?.unmount()) renderer = null streamListener = null - consoleErrorSpy.mockRestore() vi.useRealTimers() }) diff --git a/mobile/src/session/use-mobile-session-view-mode.test.ts b/mobile/src/session/use-mobile-session-view-mode.test.ts index 4315bda35..0eda38348 100644 --- a/mobile/src/session/use-mobile-session-view-mode.test.ts +++ b/mobile/src/session/use-mobile-session-view-mode.test.ts @@ -52,7 +52,6 @@ describe('useMobileSessionViewMode', () => { let controller: MobileSessionViewModeController | null = null beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true vi.mocked(loadDefaultSessionView).mockReset().mockResolvedValue('terminal') vi.mocked(readSessionViewOverridesPreference) .mockReset() diff --git a/mobile/src/session/use-quick-commands.test.ts b/mobile/src/session/use-quick-commands.test.ts index 66fec5bf4..a4f93b326 100644 --- a/mobile/src/session/use-quick-commands.test.ts +++ b/mobile/src/session/use-quick-commands.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { TerminalQuickCommand } from '../../../src/shared/types' import type { RpcClient } from '../transport/rpc-client' import { LogicalClientCutoverError } from '../transport/stable-logical-rpc-client' @@ -49,24 +49,10 @@ function deferred() { describe('useQuickCommands', () => { let renderer: ReactTestRenderer | null = null let state: ReturnType | null = null - let consoleSpy: MockInstance - - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - const original = console.error - consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) - }) - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null state = null - consoleSpy.mockRestore() }) async function mount(client: RpcClient, enabled = true): Promise { diff --git a/mobile/src/session/use-terminal-live-input-mode-preference.test.ts b/mobile/src/session/use-terminal-live-input-mode-preference.test.ts index d95fab236..0a98d7377 100644 --- a/mobile/src/session/use-terminal-live-input-mode-preference.test.ts +++ b/mobile/src/session/use-terminal-live-input-mode-preference.test.ts @@ -34,18 +34,6 @@ function createDeferred(): Deferred { return { promise, resolve } } -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - const firstArg = args[0] - if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => consoleErrorSpy.mockRestore() -} - function createTerminalLiveInputModePreferenceHarness(): TerminalLiveInputModePreferenceHarness { let current: ReturnType | null = null let renderer: ReactTestRenderer | null = null @@ -58,14 +46,9 @@ function createTerminalLiveInputModePreferenceHarness(): TerminalLiveInputModePr return null } - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - act(() => { - renderer = create(createElement(Harness)) - }) - } finally { - restoreConsoleError() - } + act(() => { + renderer = create(createElement(Harness)) + }) if (!current || !renderer) { throw new Error('terminal live input mode preference hook did not render') } diff --git a/mobile/src/session/use-throttled-latest-value.test.ts b/mobile/src/session/use-throttled-latest-value.test.ts index bdea29c74..29e58c8fa 100644 --- a/mobile/src/session/use-throttled-latest-value.test.ts +++ b/mobile/src/session/use-throttled-latest-value.test.ts @@ -1,12 +1,11 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { useThrottledLatestValue } from './use-throttled-latest-value' describe('useThrottledLatestValue', () => { let renderer: ReactTestRenderer | null = null let latest: string | undefined - let consoleSpy: MockInstance function Harness({ value }: { value: string | undefined }): null { latest = useThrottledLatestValue(value, 50) @@ -25,22 +24,13 @@ describe('useThrottledLatestValue', () => { beforeEach(() => { vi.useFakeTimers() - globalThis.IS_REACT_ACT_ENVIRONMENT = true latest = undefined - const original = console.error - consoleSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - original(...args) - }) }) afterEach(() => { act(() => renderer?.unmount()) renderer = null vi.useRealTimers() - consoleSpy.mockRestore() }) it('emits the first frame immediately', () => { diff --git a/mobile/src/source-control/MobileGitHistoryList.test.tsx b/mobile/src/source-control/MobileGitHistoryList.test.tsx index 50b0fa7fe..b19a069c8 100644 --- a/mobile/src/source-control/MobileGitHistoryList.test.tsx +++ b/mobile/src/source-control/MobileGitHistoryList.test.tsx @@ -1,6 +1,6 @@ import { createElement, type ReactElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { RpcClient } from '../transport/rpc-client' import type { ConnectionState } from '../transport/types' import { MobileGitHistoryList } from './MobileGitHistoryList' @@ -44,10 +44,6 @@ const compareResponse = { describe('MobileGitHistoryList', () => { let renderer: ReactTestRenderer | null = null - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null diff --git a/mobile/src/source-control/use-mobile-hosted-review-eligibility.test.ts b/mobile/src/source-control/use-mobile-hosted-review-eligibility.test.ts index 13109a610..f6e3eab88 100644 --- a/mobile/src/source-control/use-mobile-hosted-review-eligibility.test.ts +++ b/mobile/src/source-control/use-mobile-hosted-review-eligibility.test.ts @@ -1,6 +1,6 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { HostedReviewCreationEligibility } from '../../../src/shared/hosted-review' import { buildMobileHostedReviewEligibilityLoadKey, @@ -206,10 +206,6 @@ describe('eligibility request ordering', () => { let renderer: ReactTestRenderer | null = null let renderedState: MobileCreatePrEligibilityState = { kind: 'idle' } - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { act(() => renderer?.unmount()) renderer = null diff --git a/mobile/src/terminal/terminal-webview-engine-error.test.ts b/mobile/src/terminal/terminal-webview-engine-error.test.ts index 4e29c2873..2a1ba5df9 100644 --- a/mobile/src/terminal/terminal-webview-engine-error.test.ts +++ b/mobile/src/terminal/terminal-webview-engine-error.test.ts @@ -41,18 +41,6 @@ vi.mock('lucide-react-native', () => ({ RefreshCw: 'RefreshCw' })) -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - const firstArg = args[0] - if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => consoleErrorSpy.mockRestore() -} - // Why: mounting TerminalWebView arms the web-ready watchdog; tests must // unmount so the timer can't survive into later tests and fire in teardown. let activeRenderer: ReactTestRenderer | null = null @@ -62,14 +50,9 @@ function createTerminalWebViewRenderer( props: Record = {} ) { let renderer: ReactTestRenderer | null = null - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - act(() => { - renderer = create(createElement(TerminalWebView, { onEngineError, ...props })) - }) - } finally { - restoreConsoleError() - } + act(() => { + renderer = create(createElement(TerminalWebView, { onEngineError, ...props })) + }) if (!renderer) { throw new Error('TerminalWebView did not render') } diff --git a/mobile/src/terminal/use-terminal-live-accessory-input-commit.test.ts b/mobile/src/terminal/use-terminal-live-accessory-input-commit.test.ts index 1ca5216c7..2542b315a 100644 --- a/mobile/src/terminal/use-terminal-live-accessory-input-commit.test.ts +++ b/mobile/src/terminal/use-terminal-live-accessory-input-commit.test.ts @@ -25,18 +25,6 @@ function createDeferredBoolean(): DeferredBoolean { return { promise, resolve: resolvePromise } } -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - const firstArg = args[0] - if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => consoleErrorSpy.mockRestore() -} - type AccessoryInputCommitHarnessOptions = { readonly heldText?: string readonly sentText?: string @@ -105,14 +93,9 @@ function createAccessoryInputCommitHarness({ return null } - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - act(() => { - renderer = create(createElement(Harness)) - }) - } finally { - restoreConsoleError() - } + act(() => { + renderer = create(createElement(Harness)) + }) if (!commit || !renderer) { throw new Error('terminal live accessory input hook did not render') } diff --git a/mobile/src/terminal/use-terminal-live-input-commit.test.ts b/mobile/src/terminal/use-terminal-live-input-commit.test.ts index c211ff447..544d57a1b 100644 --- a/mobile/src/terminal/use-terminal-live-input-commit.test.ts +++ b/mobile/src/terminal/use-terminal-live-input-commit.test.ts @@ -20,18 +20,6 @@ type TerminalLiveInputCommitHarnessOptions = { readonly sendResult?: boolean } -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => { - const firstArg = args[0] - if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => consoleErrorSpy.mockRestore() -} - function createTerminalLiveInputCommitHarness({ sendResult = true }: TerminalLiveInputCommitHarnessOptions = {}): TerminalLiveInputCommitHarness { @@ -77,14 +65,9 @@ function createTerminalLiveInputCommitHarness({ return null } - const restoreConsoleError = suppressReactTestRendererDeprecationWarning() - try { - act(() => { - renderer = create(createElement(Harness)) - }) - } finally { - restoreConsoleError() - } + act(() => { + renderer = create(createElement(Harness)) + }) if (!handlers || !renderer) { throw new Error('terminal live input hook did not render') } diff --git a/mobile/src/terminal/use-terminal-live-input-focus.test.ts b/mobile/src/terminal/use-terminal-live-input-focus.test.ts index f1b6c5ead..92ce4e2c4 100644 --- a/mobile/src/terminal/use-terminal-live-input-focus.test.ts +++ b/mobile/src/terminal/use-terminal-live-input-focus.test.ts @@ -1,6 +1,6 @@ import { createElement, type RefObject } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { TerminalLiveInputFocusTarget, TerminalLiveInputFocusTimerRef @@ -20,17 +20,6 @@ type HarnessProps = { type FocusHandlers = ReturnType -function suppressReactTestRendererWarning(): () => void { - const originalConsoleError = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => spy.mockRestore() -} - function createFocusTarget(initiallyFocused = false): TerminalLiveInputFocusTarget & { readonly blur: ReturnType readonly focus: ReturnType @@ -67,14 +56,9 @@ function createHarness(initialProps: HarnessProps): { return null } - const restoreWarning = suppressReactTestRendererWarning() - try { - act(() => { - renderer = create(createElement(Harness, initialProps)) - }) - } finally { - restoreWarning() - } + act(() => { + renderer = create(createElement(Harness, initialProps)) + }) if (!handlers || !renderer) { throw new Error('terminal live input focus harness did not render') } @@ -113,10 +97,6 @@ function connectedProps( } describe('terminal live input focus hook', () => { - beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true - }) - afterEach(() => { vi.useRealTimers() }) diff --git a/mobile/src/transport/client-context.test.ts b/mobile/src/transport/client-context.test.ts index b08899f53..59b0a7ac5 100644 --- a/mobile/src/transport/client-context.test.ts +++ b/mobile/src/transport/client-context.test.ts @@ -71,18 +71,6 @@ type Harness = { readonly unmount: () => void } -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...args) => { - const firstArg = args[0] - if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => spy.mockRestore() -} - async function renderHarness(hostId: string): Promise { let hook: ReturnType | null = null let closeHost: ((hostId: string) => void) | null = null @@ -94,14 +82,9 @@ async function renderHarness(hostId: string): Promise { return null } - const restore = suppressReactTestRendererDeprecationWarning() - try { - await act(async () => { - renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) - }) - } finally { - restore() - } + await act(async () => { + renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) + }) if (!hook || !closeHost || !renderer) { throw new Error('harness did not render') } @@ -124,7 +107,6 @@ async function renderHarness(hostId: string): Promise { } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true connectMock.mockReset() loadHostsMock.mockReset() }) @@ -149,7 +131,6 @@ describe('useHostClient', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) @@ -169,7 +150,6 @@ describe('useHostClient', () => { expect(selectedState).toBe('disconnected') expect(connectMock).toHaveBeenCalledTimes(2) } finally { - restore() act(() => renderer?.unmount()) } }) @@ -188,7 +168,6 @@ describe('useHostClient', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) @@ -211,7 +190,6 @@ describe('useHostClient', () => { }) expect(stateByRenderTick.get(2)).toBe('connecting') } finally { - restore() act(() => renderer?.unmount()) } }) @@ -263,7 +241,6 @@ describe('useHostClient', () => { states.push(useHostClient(HOST.id).state) return null } - const restore = suppressReactTestRendererDeprecationWarning() try { act(() => { renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) @@ -277,7 +254,6 @@ describe('useHostClient', () => { expect(states.at(-1)).toBe('connecting') expect(states).not.toContain('disconnected') } finally { - restore() act(() => renderer?.unmount()) } }) @@ -296,7 +272,6 @@ describe('useHostClient', () => { states.push(useHostClient(HOST.id).state) return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) @@ -311,7 +286,6 @@ describe('useHostClient', () => { expect(states.at(-1)).toBe('connecting') expect(states).not.toContain('disconnected') } finally { - restore() act(() => renderer?.unmount()) } }) @@ -333,14 +307,9 @@ describe('useHostClient', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() - try { - act(() => { - renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) - }) - } finally { - restore() - } + act(() => { + renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) + }) expect(loadHostsMock).toHaveBeenCalledOnce() if (!closeHost || !resolveHosts || !renderer) { throw new Error('pending-open harness did not initialize') @@ -370,14 +339,9 @@ describe('useHostClient', () => { useHostClient(HOST.id) return null } - const restore = suppressReactTestRendererDeprecationWarning() - try { - act(() => { - renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) - }) - } finally { - restore() - } + act(() => { + renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) + }) expect(loadHostsMock).toHaveBeenCalledOnce() act(() => renderer?.unmount()) await act(async () => { @@ -401,7 +365,6 @@ describe('useAllHostClients', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) @@ -410,7 +373,6 @@ describe('useAllHostClients', () => { expect(connectMock).toHaveBeenCalledOnce() expect(connectMock).toHaveBeenCalledWith(host2, expect.any(Function)) } finally { - restore() act(() => renderer?.unmount()) } }) @@ -433,7 +395,6 @@ describe('useAllHostClients', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) @@ -446,7 +407,6 @@ describe('useAllHostClients', () => { 'host-997' ]) } finally { - restore() act(() => renderer?.unmount()) } }) @@ -477,7 +437,6 @@ describe('useAllHostClients', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create( @@ -504,7 +463,6 @@ describe('useAllHostClients', () => { expect(clients.get('host-c')?.closeMock).toHaveBeenCalledOnce() expect(clients.get('host-d')?.closeMock).not.toHaveBeenCalled() } finally { - restore() act(() => renderer?.unmount()) } }) @@ -520,7 +478,6 @@ describe('useAllHostClients', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) @@ -528,7 +485,6 @@ describe('useAllHostClients', () => { }) expect(connectMock).toHaveBeenCalledTimes(2) } finally { - restore() act(() => renderer?.unmount()) } }) @@ -545,7 +501,6 @@ describe('useAllHostClients', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create(createElement(RpcClientProvider, null, createElement(Probe))) @@ -559,7 +514,6 @@ describe('useAllHostClients', () => { }) expect(connectMock).toHaveBeenCalledOnce() } finally { - restore() act(() => renderer?.unmount()) } }) diff --git a/mobile/src/transport/host-status-gates.test.ts b/mobile/src/transport/host-status-gates.test.ts index 483cfcca5..f04542437 100644 --- a/mobile/src/transport/host-status-gates.test.ts +++ b/mobile/src/transport/host-status-gates.test.ts @@ -4,17 +4,6 @@ import { describe, expect, it, vi } from 'vitest' import type { RpcClient } from './rpc-client' import { useHostStatusGates, type HostStatusGates } from './host-status-gates' -function suppressReactTestRendererDeprecationWarning(): () => void { - const originalConsoleError = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => spy.mockRestore() -} - describe('useHostStatusGates', () => { it('clears every prior-host gate and ignores its late response while the client is replaced', async () => { let resolveOldStatus: ((response: unknown) => void) | null = null @@ -43,7 +32,6 @@ describe('useHostStatusGates', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create(createElement(Probe, { hostId: 'host-1', client: oldClient })) @@ -80,7 +68,6 @@ describe('useHostStatusGates', () => { expect(oldSendRequest).toHaveBeenCalledOnce() expect(newSendRequest).toHaveBeenCalledOnce() } finally { - restore() renderer?.unmount() } }) @@ -102,7 +89,6 @@ describe('useHostStatusGates', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create(createElement(Probe, { hostId: 'host-1' })) @@ -115,7 +101,6 @@ describe('useHostStatusGates', () => { expect(sendRequest).toHaveBeenCalledOnce() } finally { - restore() renderer?.unmount() } }) @@ -141,7 +126,6 @@ describe('useHostStatusGates', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create(createElement(Probe, { connState: 'connected' })) @@ -181,7 +165,6 @@ describe('useHostStatusGates', () => { statusPending: false }) } finally { - restore() renderer?.unmount() } }) @@ -204,7 +187,6 @@ describe('useHostStatusGates', () => { return null } - const restore = suppressReactTestRendererDeprecationWarning() try { await act(async () => { renderer = create(createElement(Probe, { client: firstClient })) @@ -217,7 +199,6 @@ describe('useHostStatusGates', () => { }) expect(gates).toMatchObject({ hostCapabilities: [], statusPending: true }) } finally { - restore() renderer?.unmount() } }) diff --git a/mobile/src/transport/settings-host-client-lifecycle.test.ts b/mobile/src/transport/settings-host-client-lifecycle.test.ts index 7a23c86ac..2762e7e85 100644 --- a/mobile/src/transport/settings-host-client-lifecycle.test.ts +++ b/mobile/src/transport/settings-host-client-lifecycle.test.ts @@ -151,29 +151,13 @@ function TestApp({ ) } -function suppressRendererWarning(): () => void { - const originalConsoleError = console.error - const spy = vi.spyOn(console, 'error').mockImplementation((...args) => { - if (typeof args[0] === 'string' && args[0].includes('react-test-renderer is deprecated')) { - return - } - originalConsoleError(...args) - }) - return () => spy.mockRestore() -} - async function renderScreen(screen: Screen, detailHostId?: string): Promise { let renderer: ReactTestRenderer | null = null - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create(createElement(TestApp, { screen, detailHostId })) - await Promise.resolve() - await Promise.resolve() - }) - } finally { - restore() - } + await act(async () => { + renderer = create(createElement(TestApp, { screen, detailHostId })) + await Promise.resolve() + await Promise.resolve() + }) if (!renderer) { throw new Error('settings lifecycle harness did not render') } @@ -203,7 +187,6 @@ function activeHostIds(): string[] { } beforeEach(() => { - globalThis.IS_REACT_ACT_ENVIRONMENT = true context = null routeFocus.effect = null connectMock.mockReset() @@ -235,16 +218,11 @@ describe('settings host client lifecycle', () => { } let renderer: ReactTestRenderer | null = null - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create(createElement(NavigationStack, { settingsVisible: false })) - await Promise.resolve() - await Promise.resolve() - }) - } finally { - restore() - } + await act(async () => { + renderer = create(createElement(NavigationStack, { settingsVisible: false })) + await Promise.resolve() + await Promise.resolve() + }) if (!renderer) { throw new Error('navigation stack harness did not render') } @@ -354,20 +332,15 @@ describe('settings host client lifecycle', () => { } let renderer: ReactTestRenderer | null = null - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(HostListApp, { - settingsHostIds: [retainedHostId, sharedHostId, removedHostId] - }) - ) - await Promise.resolve() - await Promise.resolve() - }) - } finally { - restore() - } + await act(async () => { + renderer = create( + createElement(HostListApp, { + settingsHostIds: [retainedHostId, sharedHostId, removedHostId] + }) + ) + await Promise.resolve() + await Promise.resolve() + }) if (!renderer) { throw new Error('host-list lifecycle harness did not render') } @@ -451,14 +424,9 @@ describe('settings host client lifecycle', () => { } let renderer: ReactTestRenderer | null = null - const restore = suppressRendererWarning() - try { - act(() => { - renderer = create(createElement(RetryApp, { settingsVisible: true })) - }) - } finally { - restore() - } + act(() => { + renderer = create(createElement(RetryApp, { settingsVisible: true })) + }) if (!renderer || !resolveInitial || !resolveRetry) { throw new Error('retry lifecycle harness did not initialize') } @@ -517,14 +485,9 @@ describe('settings host client lifecycle', () => { } let renderer: ReactTestRenderer | null = null - const restore = suppressRendererWarning() - try { - act(() => { - renderer = create(createElement(PendingApp, { visible: true })) - }) - } finally { - restore() - } + act(() => { + renderer = create(createElement(PendingApp, { visible: true })) + }) if (!renderer || !resolveFirst || !resolveSecond) { throw new Error('pending settings harness did not initialize') } @@ -580,17 +543,12 @@ describe('settings host client lifecycle', () => { } let renderer: ReactTestRenderer | null = null - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create( - createElement(ReplacementApp, { detailVisible: false, settingsVisible: false }) - ) - await Promise.resolve() - }) - } finally { - restore() - } + await act(async () => { + renderer = create( + createElement(ReplacementApp, { detailVisible: false, settingsVisible: false }) + ) + await Promise.resolve() + }) if (!renderer || !context) { throw new Error('replacement lifecycle harness did not initialize') } @@ -665,14 +623,9 @@ describe('settings host client lifecycle', () => { } let renderer: ReactTestRenderer | null = null - const restore = suppressRendererWarning() - try { - act(() => { - renderer = create(createElement(ManualApp, { selected: false })) - }) - } finally { - restore() - } + act(() => { + renderer = create(createElement(ManualApp, { selected: false })) + }) if (!renderer || !context) { throw new Error('manual connection harness did not initialize') } @@ -735,16 +688,11 @@ describe('settings host client lifecycle', () => { } let renderer: ReactTestRenderer | null = null - const restore = suppressRendererWarning() - try { - await act(async () => { - renderer = create(createElement(FocusStack)) - await Promise.resolve() - await Promise.resolve() - }) - } finally { - restore() - } + await act(async () => { + renderer = create(createElement(FocusStack)) + await Promise.resolve() + await Promise.resolve() + }) if (!renderer || !routeFocus.effect) { throw new Error('settings focus harness did not initialize') } diff --git a/mobile/vitest.config.ts b/mobile/vitest.config.ts index e39f90eed..981a1b55e 100644 --- a/mobile/vitest.config.ts +++ b/mobile/vitest.config.ts @@ -9,6 +9,8 @@ export default defineConfig({ oxc: vitestOxcConfig, test: { environment: 'node', + setupFiles: ['./vitest.setup.ts'], + onConsoleLog: (log) => !log.includes('react-test-renderer is deprecated'), // .tsx too: component tests exist (react-test-renderer + mocked react-native) and were // silently never collected, so render-level regressions shipped untested. include: ['src/**/*.test.ts', 'src/**/*.test.tsx'] diff --git a/mobile/vitest.setup.ts b/mobile/vitest.setup.ts new file mode 100644 index 000000000..740c1eb1b --- /dev/null +++ b/mobile/vitest.setup.ts @@ -0,0 +1 @@ +Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })