Cobertura global
Objetivo: ≥ 80% • Estado actual tras primer scan
Statements
43.5%
100 / 230 cubiertos
Branches
35.7%
35 / 98 ramas cubiertas
Functions
44.3%
25 / 56 funciones cubiertas
Lines
43.5%
8 / 9 archivos bajo umbral
17
Test cases Jest + RTL generados para 5 componentes
7
Archivos Playwright E2E scaffoldeados (5 rutas + config + fixtures)
2
Gaps CRÍTICOS detectados en authService y loginForm
Cobertura por archivo
8 / 9 bajo umbral| Archivo | Lines | Branches | Fns | Estado |
|---|---|---|---|---|
| authService.ts | 14.3% | 12.5% | 20% | CRÍTICO |
| campaignService.ts | 23.3% | 21.4% | 33.3% | ALTO |
| DateRangePicker.tsx | 27.3% | 25% | 20% | ALTO |
| MetricCard.tsx | 32% | 20% | 33.3% | ALTO |
| CampaignTable.tsx | 35.7% | 25% | 25% | MEDIO |
| Button.tsx | 60% | 50% | 60% | MEDIO |
| LoginForm.tsx | 60% | 50% | 57.1% | ALTO |
| useMetrics.ts | 77.8% | 62.5% | 80% | MEDIO |
| formatters.ts | 90.9% | 80% | 100% | PASS |
Gaps detectados
2 críticos • 5 altos| Severidad | Archivo | Descripción |
|---|---|---|
| CRÍTICO | authService.ts | Lines 14.3% — ruta auth sin tests |
| CRÍTICO | authService.ts | Branches 12.5% — 14 ramas sin cubrir |
| ALTO | campaignService.ts | Lines 23.3% — lógica de negocio sin tests |
| ALTO | LoginForm.tsx | Lines 60% — manejo de errores sin cubrir |
| ALTO | LoginForm.tsx | Branches 50% — validación Zod sin tests |
| MEDIO | DateRangePicker.tsx | Lines 27.3% — presets no cubiertos |
| MEDIO | CampaignTable.tsx | Branches 25% — filtrado sin tests |
Recomendaciones priorizadas
1
Añadir tests a authService.ts primero
src/lib/api/authService.ts — cobertura 14.3%, ruta crítica
2
Cubrir 14 ramas en authService
Condicionales de token, refresh, logout
3
Completar cobertura de LoginForm
Edge cases: email inválido, password vacío, error 401
4
Tests para campaignService CRUD
Happy paths + manejo de errores HTTP
5
Completar tests de DateRangePicker
Verificar presets 7d, 30d, 90d y callback onChange
Tests unitarios generados (Jest + RTL)
5 componentes • 17 test cases • accesibilidad incluida
Button.test.tsx3 tests
LoginForm.test.tsx4 tests
CampaignTable.test.tsx4 tests
MetricCard.test.tsx2 tests
DateRangePicker.test.tsx4 tests
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { waitFor } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
import { LoginForm } from '../components/LoginForm';
expect.extend(toHaveNoViolations);
describe('LoginForm', () => {
// Basic render tests
it('renders without crashing', () => {
render(<LoginForm />);
});
it('renders expected content', () => {
render(<LoginForm />);
// expect(screen.getByRole('...')).toBeInTheDocument();
});
// Interaction tests
it('handles user interaction', async () => {
const user = userEvent.setup();
const handleClick = jest.fn();
render(<LoginForm onClick={handleClick} />);
const button = screen.getByRole('button');
await user.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
// State management
it('updates state correctly', async () => {
render(<LoginForm />);
await waitFor(() => {
// assert state changes in UI
});
});
// Accessibility
it('has no accessibility violations', async () => {
const { container } = render(<LoginForm />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { axe, toHaveNoViolations } from 'jest-axe';
import { Button } from '../components/Button';
expect.extend(toHaveNoViolations);
describe('Button', () => {
// Basic render tests
it('renders without crashing', () => {
render(<Button />);
});
it('renders expected content', () => {
render(<Button />);
// TODO: Add specific content assertions
// expect(screen.getByRole('...')).toBeInTheDocument();
});
// Interaction tests
it('handles user interaction', async () => {
const user = userEvent.setup();
const handleClick = jest.fn();
render(<Button onClick={handleClick} />);
const button = screen.getByRole('button');
await user.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
// Accessibility
it('has no accessibility violations', async () => {
const { container } = render(<Button />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
E2E Playwright scaffoldeado (Next.js App Router)
5 rutas detectadas • 7 archivos generados
/
home.spec.ts • 4 tests
/login
login.spec.ts • 4 tests
/dashboard
dashboard.spec.ts • 3 tests
/campaigns
campaigns.spec.ts • 3 tests
/settings
settings.spec.ts • 3 tests
import { test, expect } from '@playwright/test';
test.describe('/login', () => {
test('loads successfully', async ({ page }) => {
await page.goto('/login');
await expect(page).toHaveURL(//login/);
// TODO: Add specific content assertions
});
test('has correct title', async ({ page }) => {
await page.goto('/login');
await expect(page).toHaveTitle(/.*/);
});
test('redirects unauthenticated users', async ({ page }) => {
await page.goto('/login');
// await expect(page).toHaveURL('/login');
});
test('allows authenticated access', async ({ page }) => {
// Set up auth fixture
await page.goto('/login');
await expect(page).toHaveURL(//login/);
});
});
import { test as base } from '@playwright/test';
export const test = base.extend({
authenticatedPage: async ({ page }, use) => {
// Set up authentication state
await page.goto('/login');
await page.fill('[name="email"]',
'admin@cultivadash.io');
await page.fill('[name="password"]',
'secure-password-123');
await page.click('button[type="submit"]');
await page.waitForURL('/dashboard');
await use(page);
},
unauthenticatedPage: async ({ page }, use) => {
// Ensure no auth cookies
await page.context().clearCookies();
await use(page);
},
});
Pipeline CI/CD — GitHub Actions
cultivadash-app.yml
checkout & install deps
0:12
npm run lint
0:08
npm test -- --coverage
1:34
coverage_analyzer.py --threshold 80 --strict
0:03
npx playwright test
--
upload playwright-report
--
FAIL — Coverage below threshold
Lines 43.5% < 80% • 8 archivos bajo umbral • PR bloqueado hasta mejora
Comandos de la skill
CLI rápido# 1. Generar test stubs para componentes
python3 scripts/test_suite_generator.py \
src/components/ \
--output __tests__/ \
--include-a11y
# Output: 5 archivos .test.tsx, 17 test cases
# Button.test.tsx CampaignTable.test.tsx
# LoginForm.test.tsx MetricCard.test.tsx
# DateRangePicker.test.tsx
# 2. Analizar cobertura Istanbul
python3 scripts/coverage_analyzer.py \
coverage/coverage-final.json \
--threshold 80 \
--format html \
--output coverage-report.html
# Output: FAIL 43.5% < 80% → 5 recomendaciones
# 3. Scaffoldear E2E desde rutas Next.js
python3 scripts/e2e_test_scaffolder.py \
src/app/ \
--output e2e/ \
--include-pom
# Output: 7 archivos Playwright
# login.spec.ts dashboard.spec.ts
# campaigns.spec.ts settings.spec.ts
# home.spec.ts playwright.config.ts
# fixtures/auth.ts