Skip to content

Plano: suíte de testes de componente React (Vitest + Testing Library) #3

Description

@wanderleypanosso

Contexto

O Dev Garage (Next.js 16 + React 19, App Router) ainda não tem nenhum teste
automatizado. Esta issue propõe um plano para criar uma suíte de testes de
componente React
, cobrindo os componentes em components/ — desde os de
apresentação até os com estado, efeitos e chamadas de API.

Objetivo: pegar regressões cedo (cálculo de progresso, estados de
loading/erro/vazio, updates otimistas com rollback, validação de formulário) e
deixar uma base sobre a qual novos componentes já nasçam testados.

Stack proposta

Ferramenta Papel
Vitest test runner
React Testing Library render + queries acessíveis
@testing-library/user-event interações realistas (clique, digitação)
@testing-library/jest-dom matchers (toBeInTheDocument, etc.)
jsdom ambiente DOM

Por que Vitest e não Jest: o projeto é ESM puro ("type": "module"),
Vitest é nativo de ESM/TS, mais rápido e exige menos config. O Next 16 suporta
ambos para testes de unidade/componente (Jest e Vitest); reservamos Playwright
para um eventual E2E no futuro, fora do escopo desta issue.

Fase 0 — Setup

  • Adicionar devDeps: vitest, @vitejs/plugin-react, jsdom,
    @testing-library/react, @testing-library/user-event,
    @testing-library/jest-dom, vite-tsconfig-paths.
  • vitest.config.ts: environment jsdom, globals: true, setupFiles,
    e resolução do alias @/ (via vite-tsconfig-paths).
  • vitest.setup.ts: importar @testing-library/jest-dom, cleanup()
    após cada teste e os polyfills de jsdom que o Radix exige
    (matchMedia, ResizeObserver, scrollIntoView,
    Element.prototype.hasPointerCapture/setPointerCapture).
  • Scripts no package.json: test (vitest run), test:watch
    (vitest), test:coverage (vitest run --coverage).

Estratégia de mock

  • @/lib/api-client — mockado por módulo com vi.mock, controlando
    resolve/reject por teste. Componentes não tocam fetch real.
  • sonner (toast) — mockado para asserts em toast.success/toast.error.
  • next/navigation (useRouter) — mock com push espionável.
  • next/link — passa direto (renderiza <a>); manter href para assert.
  • Helper renderWithUser() que junta render + userEvent.setup().

Componentes e casos de teste

Tier 1 — apresentação / vitória rápida

  • StatusBadge — para cada status (idea, building, shipped,
    paused) renderiza o label correto (Ideia, Construindo, No ar,
    Pausado) e a variante esperada.
  • SiteHeader — smoke test: título "Dev Garage" e link para /.
  • (bônus, unit) lib/format.formatDate — formata ISO para pt-BR.

Tier 2 — interação básica

  • ProjectCard
    • renderiza nome, descrição e tags;
    • cálculo de progresso: 0 tarefas → 0%; 3/560% (arredondamento);
    • texto "X/Y tarefas";
    • renderiza StatusBadge com o status do projeto;
    • link "Abrir" aponta para /projects/{id};
    • botão de excluir abre o ConfirmDialog e, ao confirmar, chama
      onDelete(id).
  • ConfirmDialog
    • trigger abre o diálogo com title e description;
    • confirmar chama onConfirm;
    • cancelar fecha sem chamar onConfirm;
    • respeita confirmLabel customizado.

Tier 3 — estado, efeitos e API (maior valor)

  • ProjectGrid
    • estado inicial de loading (skeletons);
    • renderiza a lista após o fetch resolver;
    • estado vazio ([]) com a mensagem "Nenhuma ideia ainda";
    • estado de erro quando o fetch rejeita;
    • contador singular/plural ("1 projeto" vs "N projetos");
    • exclusão otimista: remove o card na hora + toast.success;
    • falha na exclusão: faz rollback da lista + toast.error.
  • ProjectFormDialog
    • modo criar: título "Nova ideia", campos vazios;
    • modo editar: título "Editar projeto", campos pré-preenchidos;
    • validação: nome vazio → toast.error e nenhuma chamada de API;
    • submit criar: chama api.createProject com payload "trimado" e tags
      parseadas ("a, b, "["a","b"]); dispara onSaved + toast.success
      e fecha o diálogo;
    • submit editar: chama api.updateProject com o id do projeto;
    • estado salvando desabilita os botões e mostra "Salvando…";
    • erro de API → toast.error e o diálogo permanece aberto.
  • ProjectDetail
    • estado de loading (skeleton);
    • estado de erro / "Projeto não encontrado";
    • pronto: renderiza nome, status, data, descrição e tags;
    • cálculo de progresso das tarefas;
    • adicionar tarefa: título vazio é ignorado; válido → api.addTask,
      anexa à lista e limpa o input;
    • alternar tarefa (checkbox): update otimista + rollback em caso de falha;
    • remover tarefa: remoção otimista + rollback em caso de falha;
    • excluir projeto: chama api.deleteProject e faz router.push('/').

Convenções

  • Testes co-localizados como componente.test.tsx ao lado do componente.
  • Queries por papel/label/texto (acessibilidade primeiro), evitando seletores
    de implementação.
  • user-event no lugar de fireEvent.
  • Asserts sobre o que é renderizado + chamadas aos mocks, não sobre estado
    interno.

Cobertura e CI

  • Meta inicial de ~80% em components/ (sem perseguir 100%).
  • Workflow do GitHub Actions rodando pnpm install + pnpm test
    (e, idealmente, typecheck e lint) em cada PR.

Ordem sugerida de execução

  1. Fase 0 (setup) + Tier 1 — valida que o pipeline de teste funciona.
  2. Tier 2 — ProjectCard e ConfirmDialog.
  3. Tier 3 — ProjectGrid, ProjectFormDialog, ProjectDetail.
  4. CI no GitHub Actions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions