Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ElthonJohan/comunitea/llms.txt

Use this file to discover all available pages before exploring further.

ComuniTEA’s test suite uses Jest with the jest-expo preset and React Native Testing Library to verify that core modules initialize correctly and that the application’s data contracts are sound. The current suite focuses on smoke tests — fast, low-overhead tests that confirm modules load without errors and export the values and shapes expected by the rest of the app. The suite is designed to grow incrementally with the project; new feature tests slot directly into the existing configuration without any setup changes.

Test Framework

ToolVersionRole
jest^29.7.0Test runner
jest-expo~57.0.2Expo-aware Jest preset — handles Metro transforms and native mocks
@testing-library/react-native^13.3.3Component and hook testing utilities
react-test-renderer19.0.0Required peer for React Native Testing Library
@types/jest^29.5.14TypeScript types for describe, it, expect, etc.
The Jest configuration lives directly in package.json under the "jest" key:
"jest": {
  "preset": "jest-expo",
  "testMatch": [
    "**/__tests__/**/*.test.{ts,tsx}"
  ],
  "transformIgnorePatterns": [
    "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|lucide-react-native)"
  ],
  "moduleNameMapper": {
    "^@/(.*)$": "<rootDir>/$1",
    "^react-native-reanimated$": "<rootDir>/node_modules/react-native-reanimated/mock",
    "^react-native-worklets(.*)$": "<rootDir>/__mocks__/react-native-worklets.js"
  },
  "setupFilesAfterEnv": []
}

Running Tests

1

Run the full test suite

npm test
Runs all files matching **/__tests__/**/*.test.{ts,tsx}. Exits with code 0 even if no tests are discovered (see the --passWithNoTests note below).
2

Run in watch mode

npm run test:watch
Starts Jest in interactive watch mode. Re-runs affected tests on every file save. Ideal for TDD workflows.
The test script uses the --passWithNoTests flag: jest --passWithNoTests. This is intentional — the test suite starts minimal and expands alongside the project. CI pipelines will not fail on a fresh branch that hasn’t added tests yet.

Module Mocks

React Native’s native modules are not available in the Node.js environment that Jest runs in. ComuniTEA handles this at two levels.

Built-in Reanimated Mock

react-native-reanimated ships an official Jest mock. The moduleNameMapper points to it directly:
"^react-native-reanimated$": "<rootDir>/node_modules/react-native-reanimated/mock"

Worklets Mock

react-native-worklets (a peer dependency of Reanimated 4) has no built-in mock and will crash Jest with a native module error. A manual mock is provided at __mocks__/react-native-worklets.js:
"^react-native-worklets(.*)$": "<rootDir>/__mocks__/react-native-worklets.js"

Babel Config — Reanimated Disabled in Tests

babel.config.js disables the Reanimated Babel plugin when NODE_ENV=test, preventing the worklets transform from running during the Jest pass:
// babel.config.js
module.exports = function (api) {
    api.cache.using(() => process.env.NODE_ENV);
    const isTest = process.env.NODE_ENV === 'test';

    return {
        presets: [
            [
                'babel-preset-expo',
                {
                    // The reanimated plugin requires react-native-worklets
                    // which is not available in the Jest (pure Node.js) environment.
                    reanimated: !isTest,
                },
            ],
        ],
    };
};

Per-Test Native Module Mocks

Tests that import modules with native dependencies declare jest.mock() calls at the top of the file. The smoke test file demonstrates the full set of mocks required to load the Supabase client and audio subsystem:
jest.mock('@react-native-async-storage/async-storage', () =>
    require('@react-native-async-storage/async-storage/jest/async-storage-mock')
);
jest.mock('@react-native-community/netinfo', () => ({
    fetch: jest.fn().mockResolvedValue({ isConnected: true, isInternetReachable: true }),
    addEventListener: jest.fn(() => jest.fn()),
}));
jest.mock('react-native-url-polyfill/auto', () => {});
jest.mock('expo-speech', () => ({ speak: jest.fn(), stop: jest.fn() }));
jest.mock('expo-audio', () => ({
    createAudioPlayer: jest.fn(() => ({
        remove: jest.fn(),
        play: jest.fn(),
        pause: jest.fn(),
        addListener: jest.fn(() => ({ remove: jest.fn() })),
    })),
    setAudioModeAsync: jest.fn(),
}));
jest.mock('expo-file-system/legacy', () => ({
    documentDirectory: '/tmp/',
    cacheDirectory: '/tmp/',
    writeAsStringAsync: jest.fn().mockResolvedValue(undefined),
    deleteAsync: jest.fn().mockResolvedValue(undefined),
    readAsStringAsync: jest.fn().mockResolvedValue(''),
    EncodingType: { Base64: 'base64' },
}));

Path Aliases in Tests

The moduleNameMapper in the Jest config maps the @/ prefix for test imports, alongside native module mocks:
"moduleNameMapper": {
  "^@/(.*)$": "<rootDir>/$1",
  "^react-native-reanimated$": "<rootDir>/node_modules/react-native-reanimated/mock",
  "^react-native-worklets(.*)$": "<rootDir>/__mocks__/react-native-worklets.js"
}
This means require('@/lib/supabase') inside a test file resolves to <rootDir>/lib/supabase. Note that the Jest ^@/(.*)$ mapper uses a different prefix convention from the TypeScript path aliases in tsconfig.json (@lib/*, @features/*, etc.), which are resolved by the Metro bundler and TypeScript compiler but not by Jest. In test files, use the @/ prefix or relative paths (e.g., ../../lib/supabase) to ensure correct resolution. No separate Babel plugin or Jest transform is needed for this mapper.

Transform Ignore Patterns

By default Jest does not transform node_modules. ComuniTEA’s transformIgnorePatterns opts several packages back into the Babel transform pipeline because they ship ES module or JSX source that Node.js cannot execute natively:
node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|lucide-react-native)
Packages that are transformed (whitelisted from the ignore pattern):
Package groupReason
react-native, jest-react-nativeCore RN source ships untransformed JSX
@react-native(-community)?Community modules use JSX/ESM
expo, expo-*, @expo/*, @expo-google-fonts/*Expo packages use JSX and ESM syntax
react-navigation, @react-navigation/*Uses JSX
lucide-react-nativeShips ESM icons
react-native-svg, native-baseJSX source

Smoke Tests

The first test suite lives at __tests__/smoke/core.test.tsPhase 0 smoke tests that verify the core modules load and export valid data shapes.
// __tests__/smoke/core.test.ts

describe('Smoke tests — módulos core', () => {

  it('supabase client se inicializa correctamente con variables de entorno', () => {
      const { supabase } = require('../../lib/supabase');
      expect(supabase).toBeDefined();
      expect(typeof supabase.from).toBe('function');
      expect(typeof supabase.auth).toBe('object');
  });

  it('constantes de vocabulario exportan datos válidos', () => {
      const { VOCABULARY } = require('../../constants/Vocabulary');
      expect(typeof VOCABULARY).toBe('object');
      expect(Object.keys(VOCABULARY).length).toBeGreaterThan(0);
      // Each category must have at least one item with id and label
      const firstKey = Object.keys(VOCABULARY)[0];
      const firstItem = VOCABULARY[firstKey][0];
      expect(typeof firstItem.id).toBe('string');
      expect(typeof firstItem.label).toBe('string');
  });

  it('constantes de categorías exportan datos válidos', () => {
      const { BASIC_CATEGORIES } = require('../../constants/Categories');
      expect(Array.isArray(BASIC_CATEGORIES)).toBe(true);
      expect(BASIC_CATEGORIES.length).toBeGreaterThan(0);
      const first = BASIC_CATEGORIES[0];
      expect(typeof first.id).toBe('string');
      expect(typeof first.label).toBe('string');
  });

  it('supabase.ts lanza error sin variables de entorno', () => {
      const original_url = process.env.EXPO_PUBLIC_SUPABASE_URL;
      const original_key = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY;

      delete process.env.EXPO_PUBLIC_SUPABASE_URL;
      delete process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY;

      // Limpiar el módulo en caché para forzar re-ejecución
      jest.resetModules();

      expect(() => {
          require('../../lib/supabase');
      }).toThrow('[ComuniTEA]');

      // Restaurar
      process.env.EXPO_PUBLIC_SUPABASE_URL = original_url;
      process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY = original_key;
      jest.resetModules();
  });

});
The four smoke tests cover:

Supabase Client Initialization

Verifies that lib/supabase exports a valid Supabase client with .from() and .auth when EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY are present in the environment.

Vocabulary Constants

Confirms that constants/Vocabulary.ts exports a non-empty VOCABULARY object where each entry contains at least one item with string id and label fields.

Category Constants

Confirms that constants/Categories.ts exports a non-empty BASIC_CATEGORIES array where each element has string id and label fields.

Missing Env Var Guard

Verifies that lib/supabase throws an error prefixed with [ComuniTEA] when the Supabase environment variables are absent, catching misconfigured deployments at startup.
Environment variables for the Supabase client are set at the top of the test file before any require() calls:
process.env.EXPO_PUBLIC_SUPABASE_URL = 'https://test.supabase.co';
process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY = 'test-anon-key';

TypeScript in Tests

All test files use .ts / .tsx extensions and are fully type-checked by @types/jest. The jest-expo preset wires up the necessary TypeScript transform through Babel. The tsconfig.json include glob (**/*.ts, **/*.tsx) covers the __tests__/ directory automatically — no extra tsconfig entry is needed for test files.

Testing Guidelines for Contributors

Where to Place Test Files

All tests live under __tests__/ and must match the pattern **/*.test.{ts,tsx}:
__tests__/
├── smoke/
│   └── core.test.ts        # Phase 0 smoke tests
├── components/             # Component tests (React Native Testing Library)
│   └── ActionGridItem.test.tsx
├── hooks/                  # Hook tests (renderHook)
│   └── useSpeech.test.ts
└── stores/                 # Zustand store tests
    └── appThemeStore.test.ts

Mocking Supabase Calls

For feature tests that involve Supabase queries, mock the client at the module level so tests are fast and deterministic:
jest.mock('../../lib/supabase', () => ({
    supabase: {
        from: jest.fn(() => ({
            select: jest.fn().mockReturnThis(),
            eq: jest.fn().mockReturnThis(),
            single: jest.fn().mockResolvedValue({ data: { id: '1' }, error: null }),
        })),
        auth: {
            getUser: jest.fn().mockResolvedValue({ data: { user: null }, error: null }),
        },
    },
}));

Testing Hooks with React Native Testing Library

Use renderHook from @testing-library/react-native to test custom hooks in isolation:
import { renderHook, act } from '@testing-library/react-native';
import { useSpeech } from '../../hooks/useSpeech';

it('useSpeech initializes without crashing', () => {
    const { result } = renderHook(() => useSpeech());
    expect(result.current).toBeDefined();
});

Testing Zustand Stores

Reset Zustand store state between tests to avoid cross-test pollution:
import { appThemeStore } from '../../stores/appThemeStore';

beforeEach(() => {
    appThemeStore.setState({ theme: 'sage' });
});

it('theme switches to rojo', () => {
    act(() => appThemeStore.getState().setTheme('rojo'));
    expect(appThemeStore.getState().theme).toBe('rojo');
});
Because babel.config.js disables the Reanimated transform when NODE_ENV=test, any component that uses useAnimatedStyle or useSharedValue will work with the mock provided by moduleNameMapper without any additional setup in individual test files.

Build docs developers (and LLMs) love