Skip to main content

Codecov Test Coverage Integration

Coverage configuration for Sanctiv - optimized for startup stage.

Philosophy

Test business logic, not UI. Maestro E2E covers user journeys.
What We TestHowCoverage Target
Pure functions (utils)Jest unit tests80%+ enforced
Data mappersJest unit tests80%+ enforced
User journeysMaestro E2EN/A (not tracked)
UI componentsMaestro E2ENot unit tested
Current Status:
  • ✅ Path-specific thresholds on critical code
  • ✅ Global threshold = 0 (don’t block PRs for UI code)
  • ✅ Codecov tracks trends without blocking merges
  • ✅ Component-based coverage tracking

Quick Start

Run Tests Locally

# Run all tests with coverage report
bun run test:coverage

# Run tests in watch mode
bun run test:watch

# Run once without coverage
bun run test

View Coverage Report

After running test:coverage, open coverage/lcov-report/index.html in a browser for detailed line-by-line coverage visualization.

Configuration

jest.config.js - Path-Specific Thresholds

coverageThreshold: {
  // Global: No minimum (don't block PRs for UI code)
  global: { branches: 0, functions: 0, lines: 0, statements: 0 },
  
  // Critical paths: MUST maintain 80% coverage
  "src/utils/cn.ts": { lines: 80, ... },
  "src/utils/pickerUtils.ts": { lines: 80, ... },
  "src/services/supabase/journal.mappers.ts": { lines: 80, ... },
}

codecov.yml - Trend Tracking

coverage:
  status:
    project:
      default:
        informational: true   # Track trends, don't block PRs
      utils:
        paths: ["src/utils/cn.ts", "src/utils/pickerUtils.ts"]
        target: 80%
        informational: false  # This CAN block
      mappers:
        paths: ["src/services/supabase/*mappers*.ts"]
        target: 80%
        informational: false  # This CAN block

What’s Ignored (Not Tracked)

  • UI components (src/components/**, src/screens/**)
  • Design system (src/design-system/components/**)
  • Flow engine components (src/flow-engine/components/**)
  • Type definitions, generated files, configs

What to Test

Currently Tested (100% coverage)

FileTests
src/utils/cn.ts5 tests
src/utils/pickerUtils.ts13 tests
src/services/supabase/journal.mappers.ts15 tests

Next Priority (when needed)

FileWhy
src/services/supabase/library.mappers.tsData integrity
src/state/authStore.tsAuth logic critical
src/state/journalStore.tsCore feature

Skip (Covered by Maestro E2E)

  • src/components/** - UI rendering
  • src/screens/** - Full user flows
  • src/design-system/** - Visual components

Writing Tests

Test File Structure

Create tests in __tests__ directories:
src/
  utils/
    cn.ts
    __tests__/
      cn.test.ts
  services/supabase/
    journal.mappers.ts
    __tests__/
      journal.mappers.test.ts

Example: Testing Pure Functions

// src/utils/__tests__/pickerUtils.test.ts
import { filterSelectedOptions } from "../pickerUtils";

describe("filterSelectedOptions", () => {
  it("should filter out selected options", () => {
    const options = [{ id: "a", label: "A" }, { id: "b", label: "B" }];
    const result = filterSelectedOptions(options, ["a"]);
    expect(result).toHaveLength(1);
    expect(result[0].id).toBe("b");
  });
});

Example: Testing Data Mappers

// src/services/supabase/__tests__/journal.mappers.test.ts
import { mapDBToApp, mapAppToDB } from "../journal.mappers";

describe("mapDBToApp", () => {
  it("should extract orgId from data field", () => {
    const dbEntry = { id: "1", data: { orgId: "org-123" }, ... };
    const result = mapDBToApp(dbEntry);
    expect(result.orgId).toBe("org-123");
  });

  it("should use fallback when orgId missing", () => {
    const dbEntry = { id: "1", data: {}, ... };
    const result = mapDBToApp(dbEntry);
    expect(result.orgId).toBe("local-org");
  });
});

Example: Testing Zustand Stores

// src/state/__tests__/authStore.test.ts
import { useAuthStore } from "../authStore";

describe("authStore", () => {
  beforeEach(() => {
    useAuthStore.setState({ user: null, session: null });
  });

  it("should set user on login", () => {
    const mockUser = { id: "user-1", email: "[email protected]" };
    useAuthStore.getState().setUser(mockUser);
    expect(useAuthStore.getState().user).toEqual(mockUser);
  });
});

Troubleshooting

Coverage Threshold Failures

Issue: Jest: "global" coverage threshold not met Solution: Thresholds are progressive. Current phase allows low coverage:
// jest.config.js - Current (Phase 1)
coverageThreshold: { global: { branches: 0, ... } }

// Future (Phase 4)
coverageThreshold: { global: { branches: 50, ... } }

Codecov Upload Fails

Issue: Upload step fails in GitHub Actions Checklist:
  1. CODECOV_TOKEN set in GitHub Secrets
  2. ✅ Token matches Codecov dashboard
  3. coverage/lcov.info file generated
  4. ✅ Workflow has correct permissions

React Native Module Errors

Issue: Cannot find module 'react-native/...' Solution: Handled by transformIgnorePatterns in jest.config.js. If issues persist:
# Clear cache
bun run test --clearCache

# Reinstall dependencies
rm -rf node_modules && bun install

Best Practices

1. Test Behavior, Not Implementation

// ✅ Good - tests behavior
it("should filter out already-selected emotions", () => {...})

// ❌ Bad - tests implementation
it("should call Array.filter", () => {...})

2. One Assertion Per Test

// ✅ Good - focused test
it("should extract user ID", () => {
  expect(result.userId).toBe("user-123");
});

// ❌ Bad - multiple concerns
it("should map entry", () => {
  expect(result.userId).toBe("user-123");
  expect(result.orgId).toBe("org-456");
  expect(result.content).toBe("...");
});

3. Use Descriptive Names

// ✅ Good - describes scenario + expected outcome
it("should use fallback orgId when data field is null")

// ❌ Bad - vague
it("handles null")

4. Arrange-Act-Assert Pattern

it("should filter selected options", () => {
  // Arrange
  const options = [{ id: "1" }, { id: "2" }];
  const selected = ["1"];

  // Act
  const result = filterSelectedOptions(options, selected);

  // Assert
  expect(result).toHaveLength(1);
});

Codecov Dashboard

Viewing Coverage

  1. Go to codecov.io
  2. Sign in with GitHub
  3. Select Sanctiv/sanctiv-app
  4. View:
    • Coverage % - Overall project coverage
    • Components - Per-area coverage breakdown
    • Files - Line-by-line coverage
    • Commits - Coverage trends over time

PR Comments

Codecov automatically comments on PRs showing:
  • Overall coverage change (+/- from base)
  • Patch coverage (new code only)
  • Uncovered lines in diff
  • Component breakdown
  • TESTING.md - E2E testing with Maestro
  • codecov.yml - Full Codecov configuration (must be at repository root per Codecov requirements)
  • jest.config.js - Jest configuration for mobile app

References