QA

QA Senior React / Next.js — CultivaDash SaaS

Suite de tests automatizados generada por la skill qa-senior-react-nextjs • Proyecto: cultivadash-app • 12 Jun 2026

FAIL 43.5% coverage 17 unit tests 5 E2E specs Threshold 80%
📊
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
SeveridadArchivoDescripció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
__tests__/LoginForm.test.tsx TSX
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(); }); });
__tests__/Button.test.tsx TSX
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
e2e/login.spec.ts Playwright
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/); }); });
e2e/fixtures/auth.ts Playwright Fixture
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