You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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.
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
Fase 0 (setup) + Tier 1 — valida que o pipeline de teste funciona.
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 deapresentaçã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
toBeInTheDocument, etc.)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
vitest,@vitejs/plugin-react,jsdom,@testing-library/react,@testing-library/user-event,@testing-library/jest-dom,vite-tsconfig-paths.vitest.config.ts: environmentjsdom,globals: true,setupFiles,e resolução do alias
@/(viavite-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).package.json:test(vitest run),test:watch(
vitest),test:coverage(vitest run --coverage).Estratégia de mock
@/lib/api-client— mockado por módulo comvi.mock, controlandoresolve/reject por teste. Componentes não tocam
fetchreal.sonner(toast) — mockado para asserts emtoast.success/toast.error.next/navigation(useRouter) — mock compushespionável.next/link— passa direto (renderiza<a>); manterhrefpara assert.renderWithUser()que juntarender+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/.lib/format.formatDate— formata ISO para pt-BR.Tier 2 — interação básica
ProjectCard0tarefas →0%;3/5→60%(arredondamento);StatusBadgecom o status do projeto;/projects/{id};ConfirmDialoge, ao confirmar, chamaonDelete(id).ConfirmDialogtitleedescription;onConfirm;onConfirm;confirmLabelcustomizado.Tier 3 — estado, efeitos e API (maior valor)
ProjectGrid[]) com a mensagem "Nenhuma ideia ainda";toast.success;toast.error.ProjectFormDialogtoast.errore nenhuma chamada de API;api.createProjectcom payload "trimado" e tagsparseadas (
"a, b, "→["a","b"]); disparaonSaved+toast.successe fecha o diálogo;
api.updateProjectcom oiddo projeto;toast.errore o diálogo permanece aberto.ProjectDetailapi.addTask,anexa à lista e limpa o input;
api.deleteProjecte fazrouter.push('/').Convenções
componente.test.tsxao lado do componente.de implementação.
user-eventno lugar defireEvent.interno.
Cobertura e CI
components/(sem perseguir 100%).pnpm install+pnpm test(e, idealmente,
typecheckelint) em cada PR.Ordem sugerida de execução
ProjectCardeConfirmDialog.ProjectGrid,ProjectFormDialog,ProjectDetail.