111 lines
3.7 KiB
TypeScript
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() })
|