migrate pure-logic tests to vitest (phase 1)

This commit is contained in:
Samuel Newman
2026-06-29 18:11:31 +03:00
parent dd85eb0b07
commit a70caf1829
25 changed files with 280 additions and 63 deletions
+9
View File
@@ -0,0 +1,9 @@
/*
* Stub for expo-application. The real package transitively imports the `expo`
* package, whose native winter runtime does bare CJS requires that don't
* resolve under Vitest. #/env only reads nativeBuildVersion.
*
* Aliased in place of expo-application via resolve.alias in vitest.config.ts.
*/
export const nativeApplicationVersion = '1.0.0'
export const nativeBuildVersion = '1'
+33
View File
@@ -0,0 +1,33 @@
/*
* Stub for the bare `expo` package. Importing the real entry runs Expo's
* "winter" runtime side-effect, which does bare CJS requires (./TextDecoder
* etc) that don't resolve under Vitest's node environment. Node already
* provides TextDecoder and friends, so we just need the named exports
* test-reachable code pulls from `expo`.
*
* Aliased in place of `expo` via resolve.alias in vitest.config.ts.
*/
import {vi} from 'vitest'
export function requireNativeModule(moduleName: string) {
if (moduleName === 'ExpoPlatformInfo') {
return {getIsReducedMotionEnabled: () => false}
}
if (moduleName === 'BottomSheet') {
return {dismissAll: () => {}}
}
return {}
}
export function requireOptionalNativeModule() {
return null
}
export const requireNativeView = vi.fn(() => () => null)
export class NativeModule {}
export class SharedObject {}
export class SharedRef {}
export const useEvent = vi.fn()
export const useEventListener = vi.fn()
+24
View File
@@ -0,0 +1,24 @@
/*
* Stub for @bsky.app/react-native-mmkv. The real package binds to a native
* module and imports `react-native`, neither of which works under Vitest's
* node environment. Tests that exercise storage logic mock this module's MMKV
* with their own in-memory implementation; this stub just provides a class
* shape so transitive importers (e.g. #/storage) load without booting RN.
*
* Aliased in place of @bsky.app/react-native-mmkv via resolve.alias in
* vitest.config.ts.
*/
export class MMKV {
getString() {
return undefined
}
set() {}
delete() {}
getAllKeys(): string[] {
return []
}
clearAll() {}
addOnValueChangedListener() {
return {remove() {}}
}
}
+27
View File
@@ -0,0 +1,27 @@
/*
* Stub for `react-native` used in unit tests. react-native's real entry is
* Flow-typed and boots a native runtime, neither of which works (or is needed)
* under Vitest's node environment. This is aliased in place of `react-native`
* via resolve.alias in vitest.config.ts.
*
* Only the surface our test-reachable modules actually touch is implemented:
* - Image.getSize (src/lib/media/manip.ts)
* - NativeModules / Platform (@bsky.app/react-native-mmkv)
* Extend as new transitive usages appear.
*/
import {vi} from 'vitest'
export const Image = {getSize: vi.fn()}
export const NativeModules: Record<string, unknown> = {}
export const Platform = {
OS: 'ios' as const,
select: <T>(o: {ios?: T; android?: T; native?: T; default?: T}): T =>
(o.ios ?? o.native ?? o.default) as T,
}
export const Dimensions = {
get: () => ({width: 390, height: 844, scale: 3, fontScale: 1}),
addEventListener: () => ({remove() {}}),
}
+16
View File
@@ -0,0 +1,16 @@
/*
* Stub for @sentry/react-native. The real package is CJS and requires
* `react-native` internally, which boots the native runtime under node. Many
* test-reachable modules pull it in transitively via #/logger. We only need
* the surface sentryTransport touches (src/logger/transports/sentry.ts).
*
* Aliased in place of @sentry/react-native via resolve.alias in
* vitest.config.ts. Tests that assert on Sentry calls (logger.test.ts) still
* vi.mock it directly, which takes precedence.
*/
import {vi} from 'vitest'
export const addBreadcrumb = vi.fn()
export const captureException = vi.fn()
export const captureMessage = vi.fn()
export const init = vi.fn()
+28 -8
View File
@@ -1,16 +1,36 @@
import {vi} from 'vitest'
/*
* The only place `react-native` enters the test module graph is a single
* transitive import in src/lib/media/manip.ts (`import {Image} from
* 'react-native'`). react-native's entry is Flow-typed, which esbuild cannot
* parse, so we stub it. manip.ts only touches RNImage.getSize; Platform is
* included for any other incidental use.
* Note: `react-native` itself is stubbed via resolve.alias in
* vitest.config.ts (-> vitest/react-native-stub.ts), not here.
*/
vi.mock('react-native', () => ({
Image: {getSize: vi.fn()},
/*
* expo-modules-core reads its native EventEmitter / NativeModule classes off
* globalThis.expo, which only exists in a real Expo runtime. Several utility
* modules pull it in transitively (via expo-application, expo-localization,
* etc). We only need the handful of helpers tests actually touch.
*/
vi.mock('expo-modules-core', () => ({
requireNativeModule: vi.fn((moduleName: string) => {
if (moduleName === 'ExpoPlatformInfo') {
return {getIsReducedMotionEnabled: () => false}
}
if (moduleName === 'BottomSheet') {
return {dismissAll: () => {}}
}
return {}
}),
requireNativeViewManager: vi.fn(() => () => null),
requireOptionalNativeModule: vi.fn(() => null),
createPermissionHook: () => () => [true],
NativeModule: class {},
SharedObject: class {},
SharedRef: class {},
EventEmitter: class {},
Platform: {
OS: 'ios',
select: (o: Record<string, unknown>) => o.ios ?? o.default,
isDOMAvailable: false,
select: (o: Record<string, unknown>) => o.ios,
},
}))