diff --git a/client/src/components/Settings/AccountTab.test.tsx b/client/src/components/Settings/AccountTab.test.tsx index 85bdfd21e..8bd5d180a 100644 --- a/client/src/components/Settings/AccountTab.test.tsx +++ b/client/src/components/Settings/AccountTab.test.tsx @@ -10,6 +10,12 @@ import { buildUser, buildSettings } from '../../../tests/helpers/factories'; import AccountTab from './AccountTab'; import { ToastContainer } from '../shared/Toast'; +// Enabling or disabling MFA reloads the user, and loadUser registers the +// background sync triggers: a 30 s heartbeat and window listeners that nothing +// here unregisters, so they outlive the test and can fire, and log, while the +// file's worker shuts down. Nothing here is about sync. +vi.mock('../../sync/syncTriggers', () => ({ registerSyncTriggers: vi.fn(), unregisterSyncTriggers: vi.fn() })); + beforeEach(() => { resetAllStores(); server.use( diff --git a/client/src/pages/LoginPage.test.tsx b/client/src/pages/LoginPage.test.tsx index d78530cb8..cd88cbd1c 100644 --- a/client/src/pages/LoginPage.test.tsx +++ b/client/src/pages/LoginPage.test.tsx @@ -13,6 +13,18 @@ import LoginPage from './LoginPage'; // made FE-PAGE-LOGIN-014 fail intermittently in CI while passing in isolation. vi.mock('./login/LoginWorld', () => ({ default: () => null })); +// A successful sign-in registers the background sync triggers (a 30 s heartbeat +// and window listeners that nothing here unregisters) and starts a full trip +// sync it does not wait for. Left real, both outlive the test that started them +// and can still be logging when the file's worker shuts down, which vitest +// reports as an unhandled error although every test passed. Nothing here is +// about sync. +vi.mock('../sync/syncTriggers', () => ({ registerSyncTriggers: vi.fn(), unregisterSyncTriggers: vi.fn() })); +vi.mock('../sync/tripSyncManager', async (importOriginal) => ({ + ...(await importOriginal()), + tripSyncManager: { syncAll: vi.fn(async () => {}) }, +})); + // LoginPage uses inline styles for labels (no htmlFor/id pairing). // We find inputs by placeholder text. const EMAIL_PLACEHOLDER = 'your@email.com'; diff --git a/client/tests/helpers/msw/handlers/notifications.ts b/client/tests/helpers/msw/handlers/notifications.ts index f009cee0f..aae795544 100644 --- a/client/tests/helpers/msw/handlers/notifications.ts +++ b/client/tests/helpers/msw/handlers/notifications.ts @@ -91,4 +91,9 @@ export const notificationHandlers = [ }, }); }), + + // A login and the notice host both start this fetch without waiting for it. + // Unhandled, MSW let it through to the network, where it could fail after the + // test had ended and log a warning into a worker that was shutting down. + http.get('/api/system-notices/active', () => HttpResponse.json([])), ]; diff --git a/client/tests/unit/mobile/settings/MSettingsAccount.test.tsx b/client/tests/unit/mobile/settings/MSettingsAccount.test.tsx index 850e8400a..17d6a567f 100644 --- a/client/tests/unit/mobile/settings/MSettingsAccount.test.tsx +++ b/client/tests/unit/mobile/settings/MSettingsAccount.test.tsx @@ -22,6 +22,12 @@ vi.mock('react-router', async (importOriginal) => { vi.mock('@simplewebauthn/browser', () => ({ startRegistration: vi.fn() })); +// Enabling or disabling MFA reloads the user, and loadUser registers the +// background sync triggers: a 30 s heartbeat and window listeners that nothing +// here unregisters, so they outlive the test and can fire, and log, while the +// file's worker shuts down. Nothing here is about sync. +vi.mock('../../../../src/sync/syncTriggers', () => ({ registerSyncTriggers: vi.fn(), unregisterSyncTriggers: vi.fn() })); + const webauthn = startRegistration as unknown as ReturnType; const BACKUP_KEY = 'trek_mfa_backup_codes_pending'; diff --git a/client/tests/unit/stores/authStore.test.ts b/client/tests/unit/stores/authStore.test.ts index 305bebe3b..1189fce87 100644 --- a/client/tests/unit/stores/authStore.test.ts +++ b/client/tests/unit/stores/authStore.test.ts @@ -20,6 +20,18 @@ const syncTriggers = vi.hoisted(() => ({ })); vi.mock('../../../src/sync/syncTriggers', () => syncTriggers); +// login, completeMfaLogin and register start a full trip sync they do not wait +// for. Left real, it outlives the test that started it and can still be logging +// when the file's worker shuts down, which vitest reports as an unhandled error +// although every test passed (the authStore.push spec had the same flake). The +// stub keeps the sync out and lets the tests assert that it was started. +const tripSync = vi.hoisted(() => ({ syncAll: vi.fn(async () => {}) })); +vi.mock('../../../src/sync/tripSyncManager', () => ({ tripSyncManager: tripSync })); + +// The notice store is not part of resetAllStores, and FE-STORE-AUTH-024 swaps +// its fetch for a stub; each test starts from the real, unloaded store again. +const initialNoticeState = useSystemNoticeStore.getState(); + // The user-scoped offline DB is real (fake-indexeddb); only the reopen step is // made failable so the "auth still succeeds when the DB won't open" path runs. const dbControl = vi.hoisted(() => ({ reopenFails: false, reopenedFor: null as number | string | null })); @@ -37,6 +49,7 @@ vi.mock('../../../src/db/offlineDb', async (importOriginal) => { beforeEach(() => { resetAllStores(); + useSystemNoticeStore.setState(initialNoticeState, true); vi.clearAllMocks(); dbControl.reopenFails = false; dbControl.reopenedFor = null; @@ -63,6 +76,7 @@ describe('authStore', () => { expect(state.isAuthenticated).toBe(true); expect(state.isLoading).toBe(false); expect(state.error).toBeNull(); + expect(tripSync.syncAll).toHaveBeenCalledOnce(); }); }); @@ -194,6 +208,7 @@ describe('authStore', () => { expect(state.user).toEqual(user); expect(state.isAuthenticated).toBe(true); expect(state.isLoading).toBe(false); + expect(tripSync.syncAll).toHaveBeenCalledOnce(); }); }); @@ -273,6 +288,7 @@ describe('authStore', () => { expect(state.isAuthenticated).toBe(true); expect(state.isLoading).toBe(false); expect(connect).toHaveBeenCalledOnce(); + expect(tripSync.syncAll).toHaveBeenCalledOnce(); }); });