From 4d932efcb358d8e9608457be07f6c2c5c2cbcb5e Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Tue, 25 Aug 2026 21:29:58 -0300 Subject: [PATCH] fix(campanha): restaura submitter no FormData do card de demanda (#921) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O dispatch manual do C139 montava new FormData(form) sem o submitter; os botões de transição (name=status value=aprovada|rejeitada|…) nunca viajavam e transitionDemandFormAction rejeitava toda decisão por status vazio. submitterFrom (cast honesto nativeEvent as SubmitEvent) + segundo arg do construtor nos handlers de transição e custo; C139 intacto. Teste unit novo pina o glue FormData→status (SubmitEvent real, não fireEvent.submit). E2e 'advisor opens a demand and decides it' verde local (isolado e em runs de arquivo inteiro). --- .../changelog/2026-08-25-e2e-demand-decide.md | 19 ++++ ...desbloquear-deploy-decisao-demanda-impl.md | 73 +++++++++++++ .../campaign/demand/DemandWorkflowCard.tsx | 15 ++- tests/unit/demandWorkflowCard.unit.spec.tsx | 100 ++++++++++++++++++ 4 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 docs/changelog/2026-08-25-e2e-demand-decide.md create mode 100644 docs/plans/desbloquear-deploy-decisao-demanda-impl.md create mode 100644 tests/unit/demandWorkflowCard.unit.spec.tsx diff --git a/docs/changelog/2026-08-25-e2e-demand-decide.md b/docs/changelog/2026-08-25-e2e-demand-decide.md new file mode 100644 index 00000000..aa1bf5b8 --- /dev/null +++ b/docs/changelog/2026-08-25-e2e-demand-decide.md @@ -0,0 +1,19 @@ +# 2026-08-25 — E2E-DEMAND-DECIDE: decisão de demanda volta a gravar (submeter perdido na regressão C139) + +A decisão de demanda em `/campanha` parou de gravar desde o react-audit família 4 (e273e9ec): +o dispatch manual do C139 montava `new FormData(form)` sem o submitter, e os botões de +transição (`name="status" value={target}`) nunca viajavam — `transitionDemandFormAction` +lia `status` vazio e toda decisão caía em erro de validação. 3 deploys manuais falharam no +e2e full do verify ("advisor opens a demand and decides it"). + +Correção local no único formulário do padrão com botões nomeados (`DemandWorkflowCard.tsx`): +helper `submitterFrom` (cast honesto de `nativeEvent as SubmitEvent`, comentado) e +`new FormData(form, submitter)` nos handlers de transição e custo (no-op de segurança no +custo — botão sem name/value hoje). C139 intacto: `preventDefault` + `startTransition` + +`useActionState` preservados, nota digitada não some em erro de validação. + +Teste novo `tests/unit/demandWorkflowCard.unit.spec.tsx` pina o glue FormData→status +(vermelho pré-fix, verde pós-fix): `SubmitEvent` real em vez de `fireEvent.submit` +(testing-library mapeia submit para `Event` simples, sem submitter). E2E determinístico +verde local (isolado via filtro posicional `spec.ts:764` e 3× em runs de arquivo inteiro). +Descoberta de tooling registrada como #923 (`--no-deps` + `-g` não filtra em run mode). diff --git a/docs/plans/desbloquear-deploy-decisao-demanda-impl.md b/docs/plans/desbloquear-deploy-decisao-demanda-impl.md new file mode 100644 index 00000000..eca2eab5 --- /dev/null +++ b/docs/plans/desbloquear-deploy-decisao-demanda-impl.md @@ -0,0 +1,73 @@ +# Impl: Desbloquear deploy: decisão de demanda em /campanha parou de gravar (regressão pós-react-audit) + +Status: rascunho +Atualizado em: 2026-08-25 +Issue: #921 +Intenção: docs/plans/desbloquear-deploy-decisao-demanda.md +Appetite restante: ~0,25–0,5 dia (fix + teste unit + e2e local; deploy é do humano pós-merge) + +## Leitura da intenção + +- **Outcome:** decidir demanda volta a gravar — nota + Aprovar/Rejeitar persistem e a tela exibe "Esta demanda já foi decidida."; e2e `tests/e2e/campaignMunicipalities.e2e.spec.ts:764` verde local e no verify; deploy manual verde concluído (humano, pós-merge). +- **O que NÃO negociar:** C139 intacto (dispatch manual `startTransition`+`useActionState` em todos os formulários — nota digitada preservada em erro de validação); nenhum outro formulário muda; nada de tocar retries/workers do e2e; nenhum skip/remoção de teste; sem migration, sem Consent, sem UI. +- **O que reavaliar:** nada de produto. Em engenharia: uniformizar o submitter no formulário de custo (questão a) e adicionar teste unit do glue (questão b) — decidido abaixo. + +## Abordagem recomendada + +```mermaid +flowchart TD + A["Botão Aprovar/Rejeitar (type=submit, name=status, value=target)"] --> B["handleTransitionSubmit (DemandWorkflowCard.tsx:70)"] + B --> C["new FormData(event.currentTarget, (event.nativeEvent as SubmitEvent).submitter)"] + C --> D["transitionDemandFormAction lê requiredFormText(formData, 'status') — presente"] + D --> E["transitionCampaignDemand grava + revalidatePath"] + E --> F["Botões somem; 'Esta demanda já foi decidida.' (linha 157)"] +``` + +**Opções consideradas:** A (FormData com submitter) | B (status via refs/state) | C (hidden input sincronizado) +**Recomendação:** **Fix A** — o construtor `new FormData(form, submitter)` do WHATWG anexa os pares name/value do botão submetido; é a correção mínima no ponto exato da regressão, sem novo estado e sem tocar no dispatch manual do C139. +**Rejeitadas:** B (mais código, zero benefício); C (mais estado, nova classe de bug); reverter e273e9ec (perde C139); converter ~20 formulários (explosão de escopo); tornar `status` opcional na server action (enfraquece validação). + +### Componentes / mudanças + +- **`DemandWorkflowCard.tsx`** (`src/components/campaign/demand/DemandWorkflowCard.tsx`): único arquivo de produto alterado. Extrair helper de módulo `submitterFrom(event: FormEvent): HTMLElement | null` que devolve `(event.nativeEvent as SubmitEvent).submitter` (cast necessário: React tipa `FormEvent` como `SyntheticEvent` → `nativeEvent: Event`, mas o evento nativo real de submit é sempre `SubmitEvent`, cujo `.submitter` é `HTMLElement | null`). Usar em `handleTransitionSubmit` (linhas 70–73) e `handleCostSubmit` (linhas 74–77): `new FormData(event.currentTarget, submitterFrom(event))`. O `Button` de `src/components/ui/button.tsx` repassa `...props` ao `