/** * Shared Jest setup for apps/user-ui. * * Only the native/context dependencies that cannot run under a plain Jest * environment are stubbed here. Everything else (components, stores, hooks) * stays real so tests exercise the actual wiring. * * Note: no `typeof import('react-native')` type expressions below. Loading the * RN ambient globals from a type position here reverses their precedence with * @types/node's, which flips `setTimeout` from `number` to `NodeJS.Timeout` and * fails typecheck in unrelated app files (packages/ui/src/components/ * bottom-dropdown.tsx). */ import type { ReactNode } from 'react' jest.mock('expo-router', () => ({ useRouter: () => ({ push: jest.fn(), replace: jest.fn(), back: jest.fn(), navigate: jest.fn(), }), router: { push: jest.fn(), replace: jest.fn(), back: jest.fn(), }, useLocalSearchParams: () => ({}), useGlobalSearchParams: () => ({}), useFocusEffect: () => {}, Link: ({ children }: { children?: ReactNode }) => children ?? null, })) /** * Refresh plumbing, irrelevant to what these tests assert. * `useMarkDataFetchers` runs `useFocusEffect`, which throws without a * NavigationContainer. These are mocked by their own module paths rather than * partially mocking the `common-ui` index: several index components import * back from `common-ui` (button.tsx -> common-ui), so spreading * `jest.requireActual('common-ui')` walks a half-initialised module and throws. */ jest.mock('common-ui/hooks/useMarkDataFetchers', () => ({ __esModule: true, useMarkDataFetchers: () => {}, })) jest.mock('common-ui/hooks/useManualRefresh', () => ({ __esModule: true, default: () => {}, })) // Registered here (not in the test files) so it is in place before any app // module imports axios. Implementations are set in tests/mocks/api.ts. jest.mock('axios') /** * Auth is out of scope for these tests. `useUserDetails` throws without an * AuthProvider (which would drag in secure-store, tRPC login and redirect * handling), so the context is stubbed with the signed-out shape. */ jest.mock('@/src/contexts/AuthContext', () => ({ AuthProvider: ({ children }: { children?: ReactNode }) => children ?? null, useAuth: () => ({ user: null, userDetails: null, isAuthenticated: false, isLoading: false, token: null, }), useUserDetails: () => null, })) /** * Cart hooks import this deep path directly (not via the common-ui index), and * the real service talks to expo-secure-store. In-memory map instead; the cart * starts empty, which is the cold-start path these tests exercise. */ jest.mock('common-ui/src/services/StorageServiceCasual', () => { const store = new Map() return { StorageServiceCasual: { getItem: jest.fn(async (key: string) => store.get(key) ?? null), setItem: jest.fn(async (key: string, value: string) => { store.set(key, value) return true }), removeItem: jest.fn(async (key: string) => { store.delete(key) return true }), clearAll: jest.fn(async (keys: string[]) => { keys.forEach((key) => store.delete(key)) return true }), }, } }) /** * Freeze the clock for every test. The home page is time-sensitive — it drops * slots whose deliveryTime is not after `dayjs()` and flags slots "closing * soon" within 4 hours — while the slot mocks carry fixed deliveryTime values. * A drifting wall clock therefore makes those assertions flaky. * * Frozen at suite start rather than a hard-coded instant, so the fixed slot * dates in the mocks stay in the future. Use `jest.advanceTimersByTime()` / * `jest.setSystemTime()` in a test that needs to move through time. */ jest.useFakeTimers({ now: Date.now() })