Ctrl K
ComunidadGuías…

jest/setup.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type {RenderInfo} from '@components/FlatList/RenderTaskQueue';

import '@shopify/flash-list/jestSetup';
import type {ReactNode} from 'react';
import type React from 'react';
import type * as RNAppLogs from 'react-native-app-logs';
import type {ReadDirItem} from 'react-native-fs';
import type * as RNKeyboardController from 'react-native-keyboard-controller';

import 'react-native-gesture-handler/jestSetup';
import type Animated from 'react-native-reanimated';

import {useMemo} from 'react';
import 'setimmediate';
import mockStorage from 'react-native-onyx/dist/storage/__mocks__';
import {TextDecoder, TextEncoder} from 'util';
import '@src/polyfills/PromiseWithResolvers';
import '@src/polyfills/requestIdleCallback';

import mockFSLibrary from './setupMockFullstoryLib';
import setupMockImages from './setupMockImages';
import setupMockLegendList from './setupMockLegendList';

// Needed for tests to have the necessary environment variables set
if (!('GITHUB_REPOSITORY' in process.env)) {
    (process.env as NodeJS.ProcessEnv).GITHUB_REPOSITORY_OWNER = 'Expensify';
    (process.env as NodeJS.ProcessEnv).GITHUB_REPOSITORY = 'Expensify/App';
}

setupMockImages();
mockFSLibrary();
setupMockLegendList();

// Polyfill necessary for Onyx.init in jest/setupAfterEnv.ts
Object.assign(global, {TextDecoder, TextEncoder});

// This mock is required as per setup instructions for react-navigation testing
// https://reactnavigation.org/docs/testing/#mocking-native-modules
jest.mock('react-native/src/private/animated/NativeAnimatedHelper');

// Gesture Handler 3 throws in __DEV__ when a GestureDetector has no GestureHandlerRootView above it. In the app every screen
// renders under the root view in App.tsx, but tests render screens on their own, so the context pretends a root view is always present.
// Jest resolves the package through `main` (lib/module), so the compiled file is the one to mock.
jest.mock('react-native-gesture-handler/lib/module/GestureHandlerRootViewContext', () => ({
    __esModule: true,
    default: jest.requireActual<typeof React>('react').createContext(true),
}));

// Mock react-native-onyx storage layer because the SQLite storage layer doesn't work in jest.
// Mocking this file in __mocks__ does not work because jest doesn't support mocking files that are not directly used in the testing project,
// and we only want to mock the storage layer, not the whole Onyx module.
jest.mock('react-native-onyx/dist/storage', () => mockStorage);

// Mock NativeEventEmitter as it is needed to provide mocks of libraries which include it
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter');

// Mock expo-task-manager
jest.mock('expo-task-manager', () => ({
    defineTask: jest.fn(),
    // Add other methods here if you use them
}));

// Mock expo-location — the jest-expo preset replaces all native module methods with jest.fn(async () => {}),
// which returns undefined instead of a proper PermissionResponse. This causes crashes when code reads .status
// from the result of requestForegroundPermissionsAsync().
jest.mock('expo-location', () => ({
    requestForegroundPermissionsAsync: jest.fn(() => Promise.resolve({status: 'granted', granted: true, canAskAgain: true, expires: 0})),
    requestBackgroundPermissionsAsync: jest.fn(() => Promise.resolve({status: 'granted', granted: true, canAskAgain: true, expires: 0})),
    getForegroundPermissionsAsync: jest.fn(() => Promise.resolve({status: 'granted', granted: true, canAskAgain: true, expires: 0})),
    getBackgroundPermissionsAsync: jest.fn(() => Promise.resolve({status: 'granted', granted: true, canAskAgain: true, expires: 0})),
    getCurrentPositionAsync: jest.fn(() => Promise.resolve({coords: {latitude: 0, longitude: 0, altitude: 0, accuracy: 0, altitudeAccuracy: 0, heading: 0, speed: 0}, timestamp: 0})),
    hasStartedLocationUpdatesAsync: jest.fn(() => Promise.resolve(false)),
    startLocationUpdatesAsync: jest.fn(() => Promise.resolve()),
    stopLocationUpdatesAsync: jest.fn(() => Promise.resolve()),
    reverseGeocodeAsync: jest.fn(() => Promise.resolve([])),
    hasServicesEnabledAsync: jest.fn(() => Promise.resolve(true)),
    PermissionStatus: {
        GRANTED: 'granted',
        DENIED: 'denied',
        UNDETERMINED: 'undetermined',
    },
    Accuracy: {
        Lowest: 1,
        Low: 2,
        Balanced: 3,
        High: 4,
        Highest: 5,
        BestForNavigation: 6,
    },
    ActivityType: {
        Other: 1,
        AutomotiveNavigation: 2,
        Fitness: 3,
        OtherNavigation: 4,
        Airborne: 5,
    },
}));

// Needed for: https://stackoverflow.com/questions/76903168/mocking-libraries-in-jest
jest.mock('react-native/Libraries/LogBox/LogBox', () => ({
    __esModule: true,
    default: {
        ignoreLogs: jest.fn(),
        ignoreAllLogs: jest.fn(),
    },
}));

const isVerbose = process.env.JEST_VERBOSE === 'true';

if (!isVerbose) {
    // Override console methods globally so module-level console calls are silenced too
    global.console = {
        ...console,
        log: jest.fn(),
        info: jest.fn(),
        debug: jest.fn(),
        warn: jest.fn(),
    } as Console;
}

// This mock is required for mocking file systems when running tests
jest.mock('react-native-fs', () => ({
    unlink: jest.fn(
        () =>
            new Promise<void>((res) => {
                res();
            }),
    ),
    readDir: jest.fn(
        () =>
            new Promise<ReadDirItem[]>((res) => {
                res([]);
            }),
    ),
    exists: jest.fn(() => Promise.resolve(false)),
    mkdir: jest.fn(() => Promise.resolve()),
    moveFile: jest.fn(() => Promise.resolve()),
    copyFile: jest.fn(() => Promise.resolve()),
    writeFile: jest.fn(() => Promise.resolve()),
    DocumentDirectoryPath: '/mock/documents',
    CachesDirectoryPath: '/mock/caches',
    LibraryDirectoryPath: '/mock/library',
}));

jest.mock('react-native-share', () => ({
    default: jest.fn(),
}));

jest.mock('react-native-reanimated', () => ({
    ...jest.requireActual<typeof Animated>('react-native-reanimated/mock'),
    createAnimatedPropAdapter: jest.fn,
    // react-native-reanimated/mock leaves dispatchCommand out (see its own "ADD ME IF NEEDED" comment). forceClearInput
    // (src/libs/ComponentUtils) dispatches it from a UI-thread worklet, so any test exercising that path needs it mocked.
    dispatchCommand: jest.fn(),
    useReducedMotion: jest.fn,
    useScrollViewOffset: jest.fn(() => 0),
    useAnimatedRef: jest.fn(() => jest.fn()),
    LayoutAnimationConfig: ({children}: {children: React.ReactNode}) => children,
    makeShareableCloneRecursive: jest.fn,
}));

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
jest.mock('react-native-worklets', () => require('react-native-worklets/src/mock'));

jest.mock('react-native-keyboard-controller', () => require<typeof RNKeyboardController>('react-native-keyboard-controller/jest'));

jest.mock('react-native-app-logs', () => require<typeof RNAppLogs>('react-native-app-logs/jest'));

jest.mock('@src/setup/telemetry', () => ({
    __esModule: true,
    default: jest.fn(),
    navigationIntegration: {
        registerNavigationContainer: jest.fn(),
    },
}));

jest.mock('../modules/background-task/src/NativeReactNativeBackgroundTask', () => ({
    defineTask: jest.fn(),
    onBackgroundTaskExecution: jest.fn(),
}));

jest.mock('../modules/hybrid-app/src/NativeReactNativeHybridApp', () => ({
    isHybridApp: jest.fn(),
    closeReactNativeApp: jest.fn(),
    completeOnboarding: jest.fn(),
    switchAccount: jest.fn(),
    clearOldDotAfterSignOut: jest.fn(),
}));

const mockUseMemo = useMemo;

// Mock lazy asset loading to be synchronous in tests
jest.mock('../src/hooks/useLazyAsset.ts', () => ({
    useMemoizedLazyAsset: jest.fn((importFn) =>
        mockUseMemo(() => {
            // Return a mock asset immediately to avoid async loading in tests
            const mockAsset = {
                src: 'mock-icon',
                testID: 'mock-asset',
                // Add common icon properties that tests might expect
                height: 20,
                width: 20,
            };

            return {
                asset: mockAsset,
                isLoaded: true,
                isLoading: false,
                hasError: false,
            };
        }, [importFn]),
    ),
    useMemoizedLazyIllustrations: jest.fn((names: readonly string[]) =>
        mockUseMemo(() => {
            // Return a Record with all requested illustration names
            const mockIllustrations: Record<string, unknown> = {};
            for (const name of names) {
                mockIllustrations[name] = {
                    src: `mock-${name}`,
                    testID: `mock-illustration-${name}`,
                    height: 20,
                    width: 20,
                };
            }
            return mockIllustrations;

            // Use a value-based dependency to avoid returning a new object caused by reference changes on names
        }, [names.join(',')]),
    ),
    useMemoizedLazyExpensifyIcons: jest.fn((names: readonly string[]) =>
        mockUseMemo(() => {
            // Return a Record with all requested icon names
            const mockIcons: Record<string, unknown> = {};
            for (const name of names) {
                mockIcons[name] = {
                    src: `mock-${name}`,
                    testID: `mock-expensify-icon-${name}`,
                    height: 20,
                    width: 20,
                };
            }
            return mockIcons;

            // Use a value-based dependency to avoid returning a new object caused by reference changes on names
        }, [names.join(',')]),
    ),
    default: jest.fn((importFn) =>
        mockUseMemo(() => {
            const mockAsset = {src: 'mock-icon', testID: 'mock-asset'};
            return {
                asset: mockAsset,
                isLoaded: true,
                isLoading: false,
                hasError: false,
            };
        }, [importFn]),
    ),
}));

// Mock icon loading functions to resolve immediately
jest.mock('../src/components/Icon/ExpensifyIconLoader.ts', () => ({
    loadExpensifyIcon: jest.fn((iconName) => {
        const mockIcon = {
            src: `mock-${iconName}`,
            testID: `mock-icon-${iconName}`,
            height: 20,
            width: 20,
        };
        return Promise.resolve({default: mockIcon});
    }),
    loadExpensifyIconsChunk: jest.fn(() => Promise.resolve({})),
    getExpensifyIconsChunk: jest.fn(() => ({})),
}));

jest.mock('../src/components/Icon/IllustrationLoader.ts', () => ({
    loadIllustration: jest.fn(() => Promise.resolve({default: {src: 'mock-illustration', height: 20, width: 20}})),
    loadIllustrationsChunk: jest.fn(() => Promise.resolve({})),
    getIllustrationsChunk: jest.fn(() => ({})),
}));

jest.mock(
    '@components/FlatList/RenderTaskQueue',
    () =>
        class SyncRenderTaskQueue {
            private handler: (info: unknown) => void = () => {};

            add(info: RenderInfo) {
                this.handler(info);
            }

            setHandler(handler: () => void) {
                this.handler = handler;
            }

            cancel() {}
        },
);

jest.mock('@libs/prepareRequestPayload/index.native.ts', () => ({
    __esModule: true,
    default: jest.fn((command: string, data: Record<string, unknown>) => {
        const formData = new FormData();

        for (const key of Object.keys(data)) {
            const value = data[key];

            if (value === undefined || value === null) {
                continue;
            }

            formData.append(key, value as string | Blob);
        }

        return Promise.resolve(formData);
    }),
}));

// This keeps the error "@rnmapbox/maps native code not available." from causing the tests to fail
jest.mock('@components/ConfirmedRoute.tsx');

jest.mock('@src/hooks/useWorkletStateMachine/runOnUISync', () => ({
    __esModule: true,
    default: jest.fn(() => jest.fn()), // Return a function that returns a function
}));

jest.mock('react-native-nitro-sqlite', () => ({
    open: jest.fn(),
}));

jest.mock('react-native-nitro-fetch', () => ({
    __esModule: true,
    fetch: (...args: Parameters<typeof fetch>) => globalThis.fetch(...args),
    prefetchOnAppStart: jest.fn(() => Promise.resolve()),
    registerTokenRefresh: jest.fn(),
    clearTokenRefresh: jest.fn(),
    removeFromAutoPrefetch: jest.fn(() => Promise.resolve()),
    removeAllFromAutoprefetch: jest.fn(() => Promise.resolve()),
}));

jest.mock('@shopify/react-native-skia', () => ({
    useFont: jest.fn(() => null),
    matchFont: jest.fn(() => null),
    listFontFamilies: jest.fn(() => []),
}));

// The native tab bar icons are drawn with Skia's imperative API, which the mock above leaves out, so the tabs get no icons.
jest.mock('@libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/tabIconRasterizer', () => jest.fn(() => undefined));

jest.mock('@sbaiahmed1/react-native-biometrics', () => ({
    isSensorAvailable: jest.fn(() => Promise.resolve({available: false})),
    createKeys: jest.fn(() => Promise.resolve({publicKey: ''})),
    deleteKeys: jest.fn(() => Promise.resolve({success: true})),
    getAllKeys: jest.fn(() => Promise.resolve({keys: []})),
    signWithOptions: jest.fn(() => Promise.resolve({success: false})),
    sha256: jest.fn(() => Promise.resolve({hash: ''})),
    InputEncoding: {Base64: 'base64', Utf8: 'utf8'},
}));

jest.mock('victory-native', () => ({
    Bar: jest.fn(() => null),
    CartesianChart: jest.fn(
        ({children}: {children?: (args: Record<string, unknown>) => ReactNode}) => children?.({points: {y: []}, chartBounds: {left: 0, right: 0, top: 0, bottom: 0}}) ?? null,
    ),
    Pie: {Chart: jest.fn(() => null)},
    PolarChart: jest.fn(() => null),
}));

// Provide a default global fetch mock for tests that do not explicitly set it up
// This avoids ReferenceError: fetch is not defined in CI when coverage is enabled
const globalWithOptionalFetch: typeof globalThis & {fetch?: unknown} = globalThis as typeof globalThis & {fetch?: unknown};
if (typeof globalWithOptionalFetch.fetch !== 'function') {
    const mockResponse = {
        ok: true,
        status: 200,
        statusText: 'OK',
        headers: {get: () => null},
        // Return a minimal shape our code expects
        json: () => Promise.resolve({jsonCode: 200}),
    };

    Object.defineProperty(globalWithOptionalFetch, 'fetch', {
        value: jest.fn(() => Promise.resolve(mockResponse)),
        writable: true,
        configurable: true,
    });
}

jest.mock('@components/ActionSheetAwareScrollView/index');
jest.mock('@components/ActionSheetAwareScrollView/index.ios');
jest.mock('@components/ActionSheetAwareScrollView/index.android');
jest.mock('@components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext');

jest.mock('@src/components/KeyboardDismissibleFlatList/KeyboardDismissibleFlatListContext');

// Mock document title hooks as no-ops in tests. The web implementation of setPageTitle uses
// setTimeout(fn, 0) which accumulates in the fake timer queue. Combined with lodash debounce
// in triggerUnreadUpdate (also timer-based), this creates excessive timer churn that causes
// heavy integration tests like SessionTest to exceed their timeout.
jest.mock('@src/hooks/useDocumentTitle', () => ({
    __esModule: true,
    default: jest.fn(),
}));
jest.mock('@src/hooks/useWorkspaceDocumentTitle', () => ({
    __esModule: true,
    default: jest.fn(),
}));
jest.mock('@src/hooks/useDomainDocumentTitle', () => ({
    __esModule: true,
    default: jest.fn(),
}));