# Testing Pattern

Todo widget debe tener tests mínimos para garantizar calidad de producción.

---

## Regla de Cobertura Mínima

**Obligatorio:** Al menos 1 archivo de test por cada hook custom en `src/hooks/`.

| Hook | Test Requerido |
|------|----------------|
| `useAccounts.ts` | `__tests__/useAccounts.test.ts` |
| `useTransactions.ts` | `__tests__/useTransactions.test.ts` |

---

## Stack de Testing

- **Vitest** - Test runner (compatible con Vite)
- **@testing-library/react** - Testing de hooks y componentes
- **MSW** (opcional) - Mock de API calls

---

## Estructura de Tests

```
src/
└── hooks/
    ├── useAccounts.ts
    ├── useTransactions.ts
    └── __tests__/
        ├── useAccounts.test.ts
        └── useTransactions.test.ts
```

---

## Template de Test para Hooks con TanStack Query

```typescript
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactNode } from 'react';
import { useAccounts } from '../useAccounts';

// Wrapper con QueryClient para tests
const createTestQueryClient = () => new QueryClient({
  defaultOptions: {
    queries: {
      retry: false,           // No reintentar en tests
      gcTime: 0,              // No cachear entre tests
      staleTime: 0,           // Siempre considerar stale
    },
  },
});

const createWrapper = () => {
  const queryClient = createTestQueryClient();
  return ({ children }: { children: ReactNode }) => (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
};

describe('useAccounts', () => {
  it('should return loading state initially', () => {
    const { result } = renderHook(() => useAccounts(), {
      wrapper: createWrapper(),
    });

    expect(result.current.isLoading).toBe(true);
    expect(result.current.data).toBeUndefined();
  });

  it('should return data after loading', async () => {
    const { result } = renderHook(() => useAccounts(), {
      wrapper: createWrapper(),
    });

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.data).toBeDefined();
    expect(Array.isArray(result.current.data)).toBe(true);
  });

  it('should return accounts with required properties', async () => {
    const { result } = renderHook(() => useAccounts(), {
      wrapper: createWrapper(),
    });

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    if (result.current.data?.length) {
      const account = result.current.data[0];
      expect(account).toHaveProperty('id');
      expect(account).toHaveProperty('name');
      // Agregar más propiedades según el tipo Account
    }
  });

  it('should handle refetch', async () => {
    const { result } = renderHook(() => useAccounts(), {
      wrapper: createWrapper(),
    });

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    // Trigger refetch
    result.current.refetch();

    // Should go back to fetching state
    await waitFor(() => {
      expect(result.current.isFetching).toBe(true);
    });

    // Should complete again
    await waitFor(() => {
      expect(result.current.isFetching).toBe(false);
    });
  });
});
```

---

## Template de Test para Hooks con Zustand

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

describe('useUIStore', () => {
  // Reset store before each test
  beforeEach(() => {
    useUIStore.setState({
      selectedId: null,
      isModalOpen: false,
      filters: {}
    });
  });

  it('should have initial state', () => {
    const { result } = renderHook(() => useUIStore());

    expect(result.current.selectedId).toBeNull();
    expect(result.current.isModalOpen).toBe(false);
  });

  it('should update selectedId', () => {
    const { result } = renderHook(() => useUIStore());

    act(() => {
      result.current.setSelectedId('account-123');
    });

    expect(result.current.selectedId).toBe('account-123');
  });

  it('should toggle modal', () => {
    const { result } = renderHook(() => useUIStore());

    act(() => {
      result.current.openModal();
    });
    expect(result.current.isModalOpen).toBe(true);

    act(() => {
      result.current.closeModal();
    });
    expect(result.current.isModalOpen).toBe(false);
  });
});
```

---

## Ejecutar Tests

```bash
# Ejecutar todos los tests
npm run test

# Ejecutar con coverage
npm run test:coverage

# Ejecutar en modo watch (desarrollo)
npm run test:watch

# Ejecutar un archivo específico
npm run test -- useAccounts.test.ts
```

---

## Configuración de Vitest

El archivo `vitest.config.ts` ya debe estar configurado. Verificar que incluya:

```typescript
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./tests/setup.ts'],
    include: ['src/**/*.test.{ts,tsx}'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
    },
  },
});
```

---

## Checklist de Tests

- [ ] ¿Existe `__tests__/` folder en `src/hooks/`?
- [ ] ¿Cada hook custom tiene un archivo `.test.ts`?
- [ ] ¿Los tests verifican estado inicial (loading)?
- [ ] ¿Los tests verifican datos después de carga?
- [ ] ¿Los tests pasan? (`npm run test`)

---

## Anti-patrones

### Tests que solo verifican que no crashea

```typescript
// Malo: no verifica nada útil
it('should work', () => {
  const { result } = renderHook(() => useAccounts());
  expect(result).toBeDefined();
});
```

### Tests que verifican comportamiento

```typescript
// Bueno: verifica comportamiento real
it('should return loading initially then data', async () => {
  const { result } = renderHook(() => useAccounts(), { wrapper });

  expect(result.current.isLoading).toBe(true);

  await waitFor(() => expect(result.current.isLoading).toBe(false));

  expect(result.current.data.length).toBeGreaterThan(0);
});
```

---

## Ver También

- `modyo://docs/widgets/patterns-tanstack-query`
- `modyo://docs/widgets/patterns-zustand`
- [Vitest Documentation](https://vitest.dev/)
