# Testing

This guide covers testing strategies and commands for OrchardGo.

## Quick Start

```bash
# Run all tests (linter + unit tests)
yarn test

# Run only unit tests
yarn test:unit

# Watch mode (runs tests on file changes)
yarn watch
```

## Test Structure

OrchardGo uses Jest for unit testing and React Testing Library for component testing.

### Test Files Location

Tests are colocated with source files:
```
src/
├── components/
│   ├── Button.tsx
│   └── Button.test.tsx
├── hooks/
│   ├── useAuth.ts
│   └── useAuth.test.ts
└── utils/
    ├── format.ts
    └── format.test.ts
```

## Unit Tests

### Running Unit Tests

```bash
# Run all unit tests
yarn test:unit

# Run tests in watch mode
yarn test:unit:watch

# Run tests with coverage
yarn test:unit --coverage

# Run specific test file
yarn test:unit Button.test.tsx

# Run tests matching pattern
yarn test:unit --testNamePattern="auth"
```

### Test Timeout

Tests have a default timeout of 10 seconds. For longer tests:

```bash
yarn test:unit --testTimeout=30000
```

### Snapshot Testing

Snapshots capture component output for regression testing.

```bash
# Update all snapshots
yarn test:update:snapshots

# Update snapshots in watch mode
yarn test:unit --updateSnapshot --watch

# Update specific snapshot
yarn test:unit Button.test.tsx --updateSnapshot
```

## Linting

Code quality is enforced through ESLint and TypeScript.

```bash
# Run linter
yarn lint

# Run type checking only
npx tsc --noEmit

# Auto-fix linting issues
npx eslint . --ext .js,.jsx,.ts,.tsx --fix
```

### What Linting Checks

- TypeScript type errors
- ESLint rules
- Code style (via eslint-config-react-ts-prettier)
- Common mistakes and anti-patterns

## Testing Best Practices

### Component Testing

```typescript
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import Button from './Button';

describe('Button', () => {
  it('should render correctly', () => {
    const { getByText } = render(<Button title="Click me" />);
    expect(getByText('Click me')).toBeTruthy();
  });

  it('should call onPress when pressed', () => {
    const onPress = jest.fn();
    const { getByText } = render(
      <Button title="Click me" onPress={onPress} />
    );

    fireEvent.press(getByText('Click me'));
    expect(onPress).toHaveBeenCalledTimes(1);
  });
});
```

### Hook Testing

```typescript
import { renderHook, act } from '@testing-library/react-hooks';
import { useCounter } from './useCounter';

describe('useCounter', () => {
  it('should increment counter', () => {
    const { result } = renderHook(() => useCounter());

    act(() => {
      result.current.increment();
    });

    expect(result.current.count).toBe(1);
  });
});
```

### Redux Testing

```typescript
import { testSaga } from 'redux-saga-test-plan';
import { fetchUserSaga } from './sagas';

describe('fetchUserSaga', () => {
  it('should fetch user successfully', () => {
    testSaga(fetchUserSaga, { payload: '123' })
      .next()
      .call(api.fetchUser, '123')
      .next({ name: 'John' })
      .put({ type: 'FETCH_USER_SUCCESS', payload: { name: 'John' } })
      .next()
      .isDone();
  });
});
```

## Mocking

### Common Mocks

OrchardGo includes mocks for:
- React Native modules
- Firebase
- Navigation
- Async Storage
- Network requests

### Custom Mocks

Define mocks in `__mocks__` directory:

```
src/
└── services/
    ├── api.ts
    └── __mocks__/
        └── api.ts
```

```typescript
// __mocks__/api.ts
export const fetchUser = jest.fn(() =>
  Promise.resolve({ id: '1', name: 'Test User' })
);
```

### Mock Date/Time

```typescript
import MockDate from 'mockdate';

beforeEach(() => {
  MockDate.set('2024-01-01T00:00:00.000Z');
});

afterEach(() => {
  MockDate.reset();
});
```

## Coverage

### Generate Coverage Report

```bash
yarn test:unit --coverage
```

### Coverage Thresholds

Jest is configured with coverage thresholds:
- Statements: 70%
- Branches: 60%
- Functions: 70%
- Lines: 70%

Coverage reports are in `coverage/` directory.

### View Coverage Report

```bash
# Generate and open HTML report
yarn test:unit --coverage
open coverage/lcov-report/index.html
```

## Debugging Tests

### Run Tests in Debug Mode

```bash
# Debug with Node debugger
node --inspect-brk node_modules/.bin/jest --runInBand

# In VS Code, use Jest Runner extension
# Or use the built-in debugger with Jest config
```

### VS Code Debug Configuration

Add to `.vscode/launch.json`:

```json
{
  "type": "node",
  "request": "launch",
  "name": "Jest Debug",
  "program": "${workspaceFolder}/node_modules/.bin/jest",
  "args": ["--runInBand", "--no-cache"],
  "console": "integratedTerminal",
  "internalConsoleOptions": "neverOpen"
}
```

### Verbose Output

```bash
# Show all test names and results
yarn test:unit --verbose

# Show console.log output
yarn test:unit --silent=false
```

## CI/CD Testing

Tests run automatically in CI/CD pipelines:
- Pre-commit hooks (via Husky)
- Pull request checks
- Jenkins builds

### Pre-commit Checks

Configured via Husky and lint-staged:
```bash
# Runs automatically on git commit
git commit -m "feat: add new feature"
# → Runs ESLint on staged files
# → Runs tests for changed files
```

## Performance Testing

### Test Performance

```bash
# Show slowest tests
yarn test:unit --verbose --detectOpenHandles
```

### Memory Leaks

```bash
# Detect memory leaks
yarn test:unit --detectLeaks --runInBand
```

## Common Issues

### "Out of Memory" Error

```bash
# Increase Node memory limit
NODE_OPTIONS=--max_old_space_size=4096 yarn test:unit
```

### "Jest has detected open handles"

This indicates async operations that didn't complete. Add timeout:

```typescript
afterEach(() => {
  jest.clearAllTimers();
  jest.useRealTimers();
});
```

### Snapshot Mismatch

```bash
# Review changes
yarn test:unit --verbose

# Update if intentional
yarn test:update:snapshots
```

## Testing Utilities

### Faker for Test Data

```typescript
import { faker } from '@faker-js/faker';

const mockUser = {
  id: faker.string.uuid(),
  name: faker.person.fullName(),
  email: faker.internet.email(),
};
```

### Redux Mock Store

```typescript
import configureMockStore from 'redux-mock-store';

const mockStore = configureMockStore();
const store = mockStore({ user: null });
```

### When for Conditional Mocking

```typescript
import { when } from 'jest-when';

when(api.fetchUser)
  .calledWith('123')
  .mockResolvedValue({ name: 'John' })
  .calledWith('456')
  .mockResolvedValue({ name: 'Jane' });
```

## Next Steps

- [Debugging](./debugging.md)
- [Scripts Reference](./scripts-reference.md)
- [Contributing Guidelines](../../CONTRIBUTING.md)
