freshyo/apps/user-ui/tests/setup.ts
2026-09-17 23:13:32 +05:30

111 lines
3.7 KiB
TypeScript

/**
* 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<string, string>()
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() })