From 3fbf89bb4ed5fe42603bec03adca9b2ea804db1c Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sun, 25 Oct 2026 14:49:49 -0700 Subject: [PATCH] test: strengthen watcher and mobile async assertions (#33641) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- config/flaky-test-pattern-baseline.txt | 1 - .../parcel-watcher-crash-fuse.test.ts | 9 +++ .../parcel-watcher-owned-children.test.ts | 8 +-- .../parcel-watcher-process-dispose.test.ts | 2 +- .../parcel-watcher-process.test.ts | 27 ++++---- ...arcel-watcher-shallow-subscription.test.ts | 3 + ...parcel-watcher-supervisor-capacity.test.ts | 4 +- ...parcel-watcher-unsubscribe-timeout.test.ts | 2 +- .../filesystem-watcher-ignore.test.ts | 10 +-- .../mobile-emulator-hidden-toast.test.tsx | 2 +- .../src/components/mobile/MobileHero.test.tsx | 10 +-- .../src/components/mobile/MobilePage.test.tsx | 62 +++++++++++-------- .../mobile/WindowsFirewallNotice.test.tsx | 10 ++- .../settings/mobile-pane-search.test.ts | 11 ---- .../mobile-pairing-custom-address.test.ts | 12 ++-- 15 files changed, 88 insertions(+), 85 deletions(-) diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index d58f4ac217d..5dbb15b67b7 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -429,7 +429,6 @@ 1 test-no-real-time-wait:src/renderer/src/components/dashboard-popout/useDashboardSnapshot.test.tsx 1 test-no-real-time-wait:src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-stale-analysis-work-budget.test.tsx 1 test-no-real-time-wait:src/renderer/src/components/editor/DiagramViewerModal.test.tsx -2 test-no-real-time-wait:src/renderer/src/components/mobile/MobilePage.test.tsx 1 test-no-real-time-wait:src/renderer/src/components/native-chat/native-chat-composer-draft-lifecycle.test.tsx 6 test-no-real-time-wait:src/renderer/src/components/native-chat/native-chat-composer-draft-store.test.ts 1 test-no-real-time-wait:src/renderer/src/components/native-chat/NativeChatModelCombobox.test.tsx diff --git a/src/main/ipc/filesystem/watcher-process/parcel-watcher-crash-fuse.test.ts b/src/main/ipc/filesystem/watcher-process/parcel-watcher-crash-fuse.test.ts index 5d89dfcbca2..fa6158e5ef6 100644 --- a/src/main/ipc/filesystem/watcher-process/parcel-watcher-crash-fuse.test.ts +++ b/src/main/ipc/filesystem/watcher-process/parcel-watcher-crash-fuse.test.ts @@ -116,6 +116,15 @@ describe('WatcherProcessCrashFuse', () => { expect(fuse.isOpen(crashAt)).toBe(false) fuse.recordCrash(crashAt) expect(fuse.isOpen(crashAt + 2 * CRASH_FUSE_COOLDOWN_INITIAL_MS - 1)).toBe(true) + + const nextTrialAt = crashAt + 2 * CRASH_FUSE_COOLDOWN_INITIAL_MS + expect(fuse.isOpen(nextTrialAt)).toBe(false) + fuse.recordDeliveryHealth('healthy', nextTrialAt) + fuse.recordDeliveryHealth('unknown', nextTrialAt + CRASH_FUSE_TRIAL_STABLE_MS - 1) + const stableAt = nextTrialAt + CRASH_FUSE_TRIAL_STABLE_MS + expect(fuse.isOpen(stableAt)).toBe(false) + fuse.recordCrash(stableAt + 1) + expect(fuse.isOpen(stableAt + 1)).toBe(false) }) it('closes immediately when explicitly reset', () => { diff --git a/src/main/ipc/filesystem/watcher-process/parcel-watcher-owned-children.test.ts b/src/main/ipc/filesystem/watcher-process/parcel-watcher-owned-children.test.ts index cbb369cf3e2..a16136b1911 100644 --- a/src/main/ipc/filesystem/watcher-process/parcel-watcher-owned-children.test.ts +++ b/src/main/ipc/filesystem/watcher-process/parcel-watcher-owned-children.test.ts @@ -37,7 +37,7 @@ it('joins disposal and does not confuse disconnect/error with physical exit', as const result = first.then(disposed) c.events.emit('disconnect') c.events.emit('error', new Error('spawn or IPC failure')) - await Promise.resolve() + await new Promise((resolve) => setImmediate(resolve)) expect(disposed).not.toHaveBeenCalled() expect(logical).toHaveBeenCalledOnce() c.close() @@ -55,7 +55,7 @@ it('includes children already signaled by synchronous or retired-owner disposal' const disposed = vi.fn() const result = owner.disposeAndWait(() => {}).then(disposed) replacement.close() - await Promise.resolve() + await new Promise((resolve) => setImmediate(resolve)) expect(disposed).not.toHaveBeenCalled() old.close() await result @@ -71,7 +71,7 @@ it('retains a child after termination deadline failure and supports explicit ret expect(await result).toMatchObject({ message: 'watcher_owned_children_shutdown_incomplete' }) const completed = vi.fn() const retry = owner.disposeAndWait(() => {}).then(completed) - await Promise.resolve() + await vi.advanceTimersByTimeAsync(0) expect(completed).not.toHaveBeenCalled() c.close() await retry @@ -96,7 +96,7 @@ it('waits for other children even when logical disposal and one kill fail', asyn finished() return error }) - await Promise.resolve() + await new Promise((resolve) => setImmediate(resolve)) expect(finished).not.toHaveBeenCalled() pending.close() expect(await result).toMatchObject({ errors: [logicalFailure, expect.any(Error)] }) diff --git a/src/main/ipc/filesystem/watcher-process/parcel-watcher-process-dispose.test.ts b/src/main/ipc/filesystem/watcher-process/parcel-watcher-process-dispose.test.ts index 89b5e1a052d..0995408601e 100644 --- a/src/main/ipc/filesystem/watcher-process/parcel-watcher-process-dispose.test.ts +++ b/src/main/ipc/filesystem/watcher-process/parcel-watcher-process-dispose.test.ts @@ -32,7 +32,7 @@ describe('disposeWatcherProcessAndWait', () => { settled() throw error }) - await Promise.resolve() + await new Promise((resolve) => setImmediate(resolve)) expect(settled).not.toHaveBeenCalled() finishPool() await expect(disposal).rejects.toMatchObject({ message: 'watcher_process_shutdown_incomplete' }) diff --git a/src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts b/src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts index 6c23440ff80..dad0488b619 100644 --- a/src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts +++ b/src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts @@ -86,21 +86,6 @@ describe('subscribeViaWatcherProcess', () => { expect(callback).toHaveBeenCalledWith(null, events) }) - it('retires the failed native subscription before reporting its error', async () => { - const callback = vi.fn() - const promise = subscribeViaWatcherProcess('/repo', callback, {}) - const child = currentChild() - const id = ackSubscribe(child) - await promise - - child.emit('message', { op: 'watch-error', id, message: 'boom' }) - expect(callback).not.toHaveBeenCalled() - expect(child.kill).toHaveBeenCalledTimes(1) - child.emit('exit', 0, null) - await vi.waitFor(() => expect(callback).toHaveBeenCalledTimes(1)) - expect(callback).toHaveBeenCalledWith(expect.objectContaining({ message: 'boom' }), []) - }) - it('rejects and physically cancels a watcher error before subscription readiness', async () => { const callback = vi.fn() const promise = subscribeViaWatcherProcess('/repo', callback, {}) @@ -115,8 +100,10 @@ describe('subscribeViaWatcherProcess', () => { settled = true }) expect(child.sent.at(-1)).toEqual({ op: 'cancel-subscribe', id }) + await new Promise((resolve) => setImmediate(resolve)) expect(settled).toBe(false) child.emit('message', { op: 'cancel-requires-restart', id }) + await new Promise((resolve) => setImmediate(resolve)) expect(settled).toBe(false) expect(callback).not.toHaveBeenCalled() expect(child.kill).toHaveBeenCalledTimes(1) @@ -365,6 +352,7 @@ describe('subscribeViaWatcherProcess', () => { const isSettled = trackPromiseSettlement(pending) expect(child.kill).not.toHaveBeenCalled() expect(child.sent.at(-1)).toEqual({ op: 'cancel-subscribe', id: queuedId }) + await new Promise((resolve) => setImmediate(resolve)) expect(isSettled()).toBe(false) child.emit('message', { op: 'unsubscribed', id: queuedId }) await expect(pending).rejects.toThrow('aborted') @@ -443,6 +431,7 @@ describe('subscribeViaWatcherProcess', () => { expect(first.sent.at(-1)).toEqual({ op: 'cancel-subscribe', id: pendingId }) first.emit('message', { op: 'cancel-requires-restart', id: pendingId }) expect(first.kill).toHaveBeenCalledTimes(1) + await new Promise((resolve) => setImmediate(resolve)) expect(isSettled()).toBe(false) expect(forkMock).toHaveBeenCalledTimes(1) first.emit('exit', 0, null) @@ -695,6 +684,7 @@ describe('subscribeViaWatcherProcess', () => { const unsubscribe = subscription.unsubscribe() const isSettled = trackPromiseSettlement(unsubscribe) expect(first.kill).toHaveBeenCalledTimes(1) + await new Promise((resolve) => setImmediate(resolve)) expect(isSettled()).toBe(false) expect(rmSyncMock).toHaveBeenCalledWith('/tmp/orca-watcher-canary-supervisor-test', { recursive: true, @@ -757,12 +747,17 @@ describe('subscribeViaWatcherProcess', () => { const second = await secondPromise const firstUnsub = first.unsubscribe() + const firstSettled = trackPromiseSettlement(firstUnsub) expect(child.kill).not.toHaveBeenCalled() expect(child.sent.at(-1)).toMatchObject({ op: 'unsubscribe' }) // Both callers may gate destructive cleanup, so both must await physical exit. const secondUnsub = second.unsubscribe() + const secondSettled = trackPromiseSettlement(secondUnsub) expect(child.kill).toHaveBeenCalledTimes(1) + await new Promise((resolve) => setImmediate(resolve)) + expect(firstSettled()).toBe(false) + expect(secondSettled()).toBe(false) child.emit('exit', 0, null) await expect(firstUnsub).resolves.toBeUndefined() await expect(secondUnsub).resolves.toBeUndefined() @@ -801,7 +796,7 @@ describe('subscribeViaWatcherProcess', () => { expect(unsubscribe).toHaveBeenCalled() }) - it('rejects a pre-aborted in-process subscribe before loading Parcel', async () => { + it('rejects a pre-aborted in-process subscribe before invoking Parcel subscribe', async () => { vi.stubEnv('VITEST', 'true') const controller = new AbortController() controller.abort() diff --git a/src/main/ipc/filesystem/watcher-process/parcel-watcher-shallow-subscription.test.ts b/src/main/ipc/filesystem/watcher-process/parcel-watcher-shallow-subscription.test.ts index de40562ccae..2f21df91287 100644 --- a/src/main/ipc/filesystem/watcher-process/parcel-watcher-shallow-subscription.test.ts +++ b/src/main/ipc/filesystem/watcher-process/parcel-watcher-shallow-subscription.test.ts @@ -77,6 +77,8 @@ describe('shallow watcher subscription', () => { } ) + emit(root, 'unrelated') + emit(join(root, 'logs'), 'unrelated') emit(root, 'config') emit(join(root, 'logs'), 'HEAD') await promise @@ -84,6 +86,7 @@ describe('shallow watcher subscription', () => { expect(events).toContain(join(root, 'config')) expect(events).toContain(join(root, 'logs', 'HEAD')) expect(events).not.toContain(join(root, 'unrelated')) + expect(events).not.toContain(join(root, 'logs', 'unrelated')) await subscription.unsubscribe() } finally { await rm(root, { recursive: true, force: true }) diff --git a/src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts b/src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts index e1a26c03b2a..fade0774a9e 100644 --- a/src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts +++ b/src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts @@ -130,9 +130,11 @@ describe('WatcherProcessSupervisor capacity wait', () => { ) await Promise.resolve() expect(forkMock).toHaveBeenCalledTimes(MAX_PHYSICAL_WATCHER_CHILDREN + 1) + const secondBlockedSettled = trackPromiseSettlement(secondBlocked) firstController.abort() await expect(firstBlocked).rejects.toMatchObject({ code: 'subscribe_aborted' }) - expect(trackPromiseSettlement(secondBlocked)()).toBe(false) + await vi.advanceTimersByTimeAsync(0) + expect(secondBlockedSettled()).toBe(false) secondController.abort() await expect(secondBlocked).rejects.toMatchObject({ code: 'subscribe_aborted' }) diff --git a/src/main/ipc/filesystem/watcher-process/parcel-watcher-unsubscribe-timeout.test.ts b/src/main/ipc/filesystem/watcher-process/parcel-watcher-unsubscribe-timeout.test.ts index f49bc5717a9..1412720a1cb 100644 --- a/src/main/ipc/filesystem/watcher-process/parcel-watcher-unsubscribe-timeout.test.ts +++ b/src/main/ipc/filesystem/watcher-process/parcel-watcher-unsubscribe-timeout.test.ts @@ -163,8 +163,8 @@ describe('watcher native unsubscribe timeout', () => { await siblingPromise const result = firstSubscription.unsubscribe().catch((error: unknown) => error) - await vi.advanceTimersByTimeAsync(WATCHER_PROCESS_UNSUBSCRIBE_TIMEOUT_MS) const isSettled = trackPromiseSettlement(result) + await vi.advanceTimersByTimeAsync(WATCHER_PROCESS_UNSUBSCRIBE_TIMEOUT_MS) expect(isSettled()).toBe(false) await vi.advanceTimersByTimeAsync(WATCHER_PROCESS_EXIT_DEADLINE_MS) diff --git a/src/main/ipc/filesystem/watchers/filesystem-watcher-ignore.test.ts b/src/main/ipc/filesystem/watchers/filesystem-watcher-ignore.test.ts index 982d1f5e904..32bae3104e3 100644 --- a/src/main/ipc/filesystem/watchers/filesystem-watcher-ignore.test.ts +++ b/src/main/ipc/filesystem/watchers/filesystem-watcher-ignore.test.ts @@ -1,9 +1,5 @@ import { afterEach, describe, expect, it } from 'vitest' -import { - MACOS_FSEVENTS_EXCLUSION_PATH_LIMIT, - WATCHER_IGNORE_DIRS, - buildParcelWatcherIgnoreOptions -} from './filesystem-watcher-ignore' +import { WATCHER_IGNORE_DIRS, buildParcelWatcherIgnoreOptions } from './filesystem-watcher-ignore' const realPlatform = process.platform @@ -22,8 +18,8 @@ describe('buildParcelWatcherIgnoreOptions', () => { const option = options.ignore ?? [] // FSEvents rejects all plain exclusions above eight paths, silently restoring node_modules/.git churn. const plainPaths = option.filter((entry) => !entry.includes('*')) - expect(plainPaths.length).toBeLessThanOrEqual(MACOS_FSEVENTS_EXCLUSION_PATH_LIMIT) - expect(option).toEqual(WATCHER_IGNORE_DIRS.slice(0, MACOS_FSEVENTS_EXCLUSION_PATH_LIMIT)) + expect(plainPaths.length).toBeLessThanOrEqual(8) + expect(option).toEqual(WATCHER_IGNORE_DIRS.slice(0, 8)) const fallbackRegex = new RegExp(options.ignoreGlobs?.[0] ?? '(?!)') for (const dir of WATCHER_IGNORE_DIRS) { expect(fallbackRegex.test(dir)).toBe(true) diff --git a/src/renderer/src/components/emulator-pane/mobile-emulator-hidden-toast.test.tsx b/src/renderer/src/components/emulator-pane/mobile-emulator-hidden-toast.test.tsx index 070c75c9491..c3b3eebae20 100644 --- a/src/renderer/src/components/emulator-pane/mobile-emulator-hidden-toast.test.tsx +++ b/src/renderer/src/components/emulator-pane/mobile-emulator-hidden-toast.test.tsx @@ -17,7 +17,7 @@ afterEach(() => { }) describe('showMobileEmulatorHiddenToast', () => { - it('auto-dismisses after 30s while staying dismissible', () => { + it('requests a dismissible toast with a 30-second duration', () => { showMobileEmulatorHiddenToast({ openSettingsPage: vi.fn() as unknown as AppState['openSettingsPage'], openSettingsTarget: vi.fn() as unknown as AppState['openSettingsTarget'] diff --git a/src/renderer/src/components/mobile/MobileHero.test.tsx b/src/renderer/src/components/mobile/MobileHero.test.tsx index 94388bdeb4a..0b69ab6d81e 100644 --- a/src/renderer/src/components/mobile/MobileHero.test.tsx +++ b/src/renderer/src/components/mobile/MobileHero.test.tsx @@ -206,7 +206,7 @@ describe('HeroFlow height', () => { expect(screen.getByText('Sign in to create a Relay pairing code')).toBeInTheDocument() }) - it('disables Relay recovery immediately and delays visible retry feedback', async () => { + it('disables Relay recovery while busy and eventually shows retry feedback', async () => { renderFlow(1, { pairLoading: true, relayMintFailure: { @@ -220,7 +220,7 @@ describe('HeroFlow height', () => { await waitFor(() => expect(screen.getByText(/Creating a new pairing code/)).toBeVisible()) }) - it('offers Retry Relay when the desktop provider is missing, since a retry installs it', () => { + it('offers Retry Relay when the desktop provider is missing', () => { renderFlow(1, { relayMintFailure: { code: 'relay_provider_unavailable', @@ -257,12 +257,12 @@ describe('HeroFlow height', () => { expect(screen.queryByRole('button', { name: 'Retry Relay' })).toBeNull() }) - it('hides the mint-failure notice when a Relay QR is shown', () => { + it('shows a Relay QR without a failure notice when no failure is supplied', () => { renderFlow(1, { pairQrDataUrl: 'data:image/png;base64,qr' }) expect(screen.queryByTestId('relay-mint-failure-notice')).not.toBeInTheDocument() }) - it('renders a pairing QR at its natural integer-scaled bitmap size', () => { + it('uses the supplied QR bitmap size and adds frame padding', () => { renderFlow(1, { pairQrDataUrl: 'data:image/png;base64,qr', pairQrSize: 218 }) const image = screen.getByRole('img', { name: 'Pairing QR' }) @@ -368,7 +368,7 @@ describe('HeroFlow height', () => { expect(refresh).toHaveFocus() }) - it('lets the user name this computer in the pairing step, ahead of the code', () => { + it('places the machine-name field slot ahead of the pairing code', () => { const props: React.ComponentProps = { pairQrDataUrl: 'data:image/png;base64,AAAA', pairingUrl: 'orca://pair?code=abc', diff --git a/src/renderer/src/components/mobile/MobilePage.test.tsx b/src/renderer/src/components/mobile/MobilePage.test.tsx index 5037ac48367..a513ab21304 100644 --- a/src/renderer/src/components/mobile/MobilePage.test.tsx +++ b/src/renderer/src/components/mobile/MobilePage.test.tsx @@ -2,7 +2,7 @@ import '@testing-library/jest-dom/vitest' -import { cleanup, render, screen, waitFor } from '@testing-library/react' +import { act, cleanup, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { MobilePairingConnectionMode } from '../../../../shared/mobile-pairing-connection-mode' @@ -283,7 +283,7 @@ describe('MobilePage pairing connection mode', () => { await user.click(screen.getByRole('button', { name: 'Orca Relay' })) await waitFor(() => expect(screen.getByTestId('mode')).toHaveTextContent('automatic')) await waitFor(() => expect(screen.getByTestId('pairing-qr')).toHaveTextContent('none')) - await new Promise((resolve) => setTimeout(resolve, 20)) + await act(async () => {}) expect(getPairingQR).not.toHaveBeenCalled() expect(screen.getByTestId('can-generate')).toHaveTextContent('false') }) @@ -297,7 +297,7 @@ describe('MobilePage pairing connection mode', () => { getPairingQR.mockClear() await user.click(screen.getByRole('button', { name: 'Change address' })) - await new Promise((resolve) => setTimeout(resolve, 20)) + await act(async () => {}) expect(getPairingQR).not.toHaveBeenCalled() expect(screen.getByTestId('pairing-qr')).toHaveTextContent('none') }) @@ -350,7 +350,7 @@ describe('MobilePage pairing connection mode', () => { expect(screen.getByTestId('pairing-url')).toHaveTextContent('none') }) - it('keeps the copy fallback when the real encoder cannot render the offer', async () => { + it('keeps the copy fallback when the host reports QR encoding failure', async () => { getPairingQR.mockResolvedValue({ available: true, qrDataUrl: null, @@ -367,7 +367,7 @@ describe('MobilePage pairing connection mode', () => { expect(screen.getByTestId('pairing-url')).toHaveTextContent('copy-fallback') }) - it('surfaces Relay failure and retries with a rotated credential', async () => { + it('surfaces Relay failure and requests credential rotation on retry', async () => { getPairingQR.mockResolvedValueOnce({ available: false, reason: 'relay_mint_failed', @@ -439,12 +439,14 @@ describe('MobilePage pairing connection mode', () => { expect(getPairingQR).toHaveBeenLastCalledWith({ connectionMode: 'local-only' }) ) await waitFor(() => expect(screen.getByTestId('pairing-qr')).toHaveTextContent('base64,local')) - resolveRetry?.({ - available: true, - qrDataUrl: 'data:image/png;base64,stale-relay', - pairingUrl: 'orca://pair#stale-relay', - endpoint: 'ws://relay', - connectionMode: 'automatic' + await act(async () => { + resolveRetry?.({ + available: true, + qrDataUrl: 'data:image/png;base64,stale-relay', + pairingUrl: 'orca://pair#stale-relay', + endpoint: 'ws://relay', + connectionMode: 'automatic' + }) }) await waitFor(() => expect(screen.getByTestId('pairing-qr')).toHaveTextContent('base64,local')) expect(screen.getByTestId('mode')).toHaveTextContent('local-only') @@ -587,7 +589,11 @@ describe('MobilePage pairing connection mode', () => { await user.click(screen.getByRole('button', { name: 'Continue' })) await waitFor(() => expect(getPairingQR).toHaveBeenCalledTimes(2)) - resolveAbandonedMint?.({ available: true, qrDataUrl: 'abandoned' }) + await act(async () => { + resolveAbandonedMint?.({ available: true, qrDataUrl: 'abandoned' }) + }) + expect(screen.getByTestId('pairing-qr')).toHaveTextContent('none') + expect(screen.getByTestId('pair-loading')).toHaveTextContent('true') resolveCurrentMint?.({ available: true, qrDataUrl: 'current' }) await waitFor(() => expect(screen.getByTestId('pairing-qr')).toHaveTextContent('current')) @@ -668,7 +674,9 @@ describe('MobilePage pairing connection mode', () => { await user.click(screen.getByRole('button', { name: 'Continue' })) await waitFor(() => expect(listNetworkInterfaces).toHaveBeenCalledTimes(2)) - resolveFirstLookup?.({ interfaces: [{ name: 'Wi-Fi', address: '10.0.0.5' }] }) + await act(async () => { + resolveFirstLookup?.({ interfaces: [{ name: 'Wi-Fi', address: '10.0.0.5' }] }) + }) await waitFor(() => expect(screen.getByTestId('pair-loading')).toHaveTextContent('true')) expect(getPairingQR).not.toHaveBeenCalled() expect(screen.getByTestId('selected-address')).toHaveTextContent('none') @@ -711,7 +719,9 @@ describe('MobilePage pairing connection mode', () => { await waitFor(() => expect(listNetworkInterfaces).toHaveBeenCalledTimes(2)) // The old lookup cannot release minting before its replacement chooses the address. - resolveEntryLookup?.({ interfaces: [{ name: 'Wi-Fi', address: '10.0.0.5' }] }) + await act(async () => { + resolveEntryLookup?.({ interfaces: [{ name: 'Wi-Fi', address: '10.0.0.5' }] }) + }) await waitFor(() => expect(screen.getByTestId('pair-loading')).toHaveTextContent('true')) expect(getPairingQR).not.toHaveBeenCalled() expect(screen.getByTestId('selected-address')).toHaveTextContent('none') @@ -758,10 +768,10 @@ describe('MobilePage pairing connection mode', () => { await waitFor(() => expect(screen.getByTestId('pair-loading')).toHaveTextContent('false')) // A late abandoned visit must not roll back the settled marker and strand Step 2. - resolveAbandonedLookup?.({ interfaces: [{ name: 'Wi-Fi', address: '10.0.0.5' }] }) - await waitFor(() => - expect(screen.getByTestId('refreshing-addresses')).toHaveTextContent('false') - ) + await act(async () => { + resolveAbandonedLookup?.({ interfaces: [{ name: 'Wi-Fi', address: '10.0.0.5' }] }) + }) + expect(screen.getByTestId('refreshing-addresses')).toHaveTextContent('false') expect(screen.getByTestId('pair-loading')).toHaveTextContent('false') expect(screen.getByTestId('selected-address')).toHaveTextContent('10.0.0.9') expect(getPairingQR).toHaveBeenCalledTimes(1) @@ -797,10 +807,10 @@ describe('MobilePage pairing connection mode', () => { rotate: true }) - resolveStaleRefresh?.({ interfaces: [{ name: 'Wi-Fi', address: '10.0.0.1' }] }) - await waitFor(() => - expect(screen.getByTestId('refreshing-addresses')).toHaveTextContent('false') - ) + await act(async () => { + resolveStaleRefresh?.({ interfaces: [{ name: 'Wi-Fi', address: '10.0.0.1' }] }) + }) + expect(screen.getByTestId('refreshing-addresses')).toHaveTextContent('false') expect(screen.getByTestId('selected-address')).toHaveTextContent('10.0.0.7') expect(getPairingQR).toHaveBeenCalledTimes(2) @@ -879,11 +889,11 @@ describe('MobilePage pairing connection mode', () => { ) expect(listNetworkInterfaces).toHaveBeenCalledOnce() - resolveRefresh?.({ interfaces: [{ name: 'Ethernet', address: '10.0.0.2' }] }) + await act(async () => { + resolveRefresh?.({ interfaces: [{ name: 'Ethernet', address: '10.0.0.2' }] }) + }) - await waitFor(() => - expect(screen.getByTestId('refreshing-addresses')).toHaveTextContent('false') - ) + expect(screen.getByTestId('refreshing-addresses')).toHaveTextContent('false') expect(screen.getByTestId('selected-address')).toHaveTextContent('wss://custom.example/large') expect(getPairingQR).not.toHaveBeenCalledWith({ address: '10.0.0.2', diff --git a/src/renderer/src/components/mobile/WindowsFirewallNotice.test.tsx b/src/renderer/src/components/mobile/WindowsFirewallNotice.test.tsx index 23a99cbfdfa..4da25a412cd 100644 --- a/src/renderer/src/components/mobile/WindowsFirewallNotice.test.tsx +++ b/src/renderer/src/components/mobile/WindowsFirewallNotice.test.tsx @@ -94,7 +94,9 @@ describe('WindowsFirewallNotice', () => { const user = userEvent.setup() render() - await user.click(await screen.findByRole('button', { name: /allow phone connections/i })) + const repairButton = await screen.findByRole('button', { name: /allow phone connections/i }) + expect(repairWindowsFirewall).not.toHaveBeenCalled() + await user.click(repairButton) expect(repairWindowsFirewall).toHaveBeenCalledTimes(1) await waitFor(() => expect(getWindowsFirewallStatus).toHaveBeenCalledTimes(2)) await waitFor(() => @@ -205,9 +207,11 @@ describe('WindowsFirewallNotice', () => { getWindowsFirewallStatus }) - render() + await act(async () => { + render() + }) - await waitFor(() => expect(getWindowsFirewallStatus).toHaveBeenCalledTimes(1)) + expect(getWindowsFirewallStatus).toHaveBeenCalledTimes(1) expect(screen.queryByRole('button', { name: /allow phone connections/i })).toBeNull() }) }) diff --git a/src/renderer/src/components/settings/mobile-pane-search.test.ts b/src/renderer/src/components/settings/mobile-pane-search.test.ts index b52a18c35e0..3ff80e28083 100644 --- a/src/renderer/src/components/settings/mobile-pane-search.test.ts +++ b/src/renderer/src/components/settings/mobile-pane-search.test.ts @@ -4,17 +4,6 @@ import { getMobilePaneSearchEntries, shouldOpenMobilePairingAddress } from './mo import { matchesSettingsSearch } from './settings-search' describe('getMobilePaneSearchEntries', () => { - // Catalog order breaks search-score ties; preserve Network Interface ranking after catalog splits. - it('keeps Network Interface in its original catalog position', () => { - expect(getMobilePaneSearchEntries().map((entry) => entry.title)).toEqual([ - 'Mobile Pairing', - 'Connected Devices', - 'Network Interface', - 'When you leave the mobile app', - 'Machine name' - ]) - }) - it('keeps the shared machine-name entry searchable from the Mobile pane', () => { const entries = getMobilePaneSearchEntries() diff --git a/src/shared/mobile-pairing-custom-address.test.ts b/src/shared/mobile-pairing-custom-address.test.ts index b63e88cfa34..8704cb0139b 100644 --- a/src/shared/mobile-pairing-custom-address.test.ts +++ b/src/shared/mobile-pairing-custom-address.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from 'vitest' import { - MAX_MOBILE_PAIRING_CUSTOM_ADDRESSES, addMobilePairingCustomAddress, normalizeMobilePairingCustomAddress, normalizeMobilePairingCustomAddresses, @@ -32,13 +31,10 @@ describe('mobile pairing custom address collection', () => { ).toEqual(['first.example:6768', 'second.example:6768']) }) - it('bounds persisted entries', () => { - const addresses = Array.from( - { length: MAX_MOBILE_PAIRING_CUSTOM_ADDRESSES + 5 }, - (_, index) => `host-${index}.example:6768` - ) - expect(normalizeMobilePairingCustomAddresses(addresses)).toHaveLength( - MAX_MOBILE_PAIRING_CUSTOM_ADDRESSES + it('keeps the first 50 valid persisted addresses', () => { + const addresses = Array.from({ length: 55 }, (_, index) => `host-${index}.example:6768`) + expect(normalizeMobilePairingCustomAddresses(addresses)).toEqual( + Array.from({ length: 50 }, (_, index) => `host-${index}.example:6768`) ) })