mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-12 22:13:05 +00:00
feat(C151): marca atividade como evento público
Adiciona o checkbox publicEvent ("Evento público", default false, indexado)
à collection activity para registrar a intenção de expor o compromisso na
futura agenda pública do site — sem consumo agora e sem mudar agenda, lista,
detalhe ou o espelho do Google.
- Activity.ts: campo checkbox + snapshot do guard de liderança
- zod/formData/view model do form + ActivityOverlay (ao lado de "Todo o dia")
- migration add_activity_public_event + types regenerados
- unit (overlay), int (parse/persistência/reabertura) e e2e (roundtrip)
This commit is contained in:
parent
bf4a4d9807
commit
6d676609cd
14 changed files with 14364 additions and 0 deletions
1
docs/changelog/2026-09-12-c151.md
Normal file
1
docs/changelog/2026-09-12-c151.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
**C151 — Atividade: marcar evento como público (2026-09-12):** o staff agora marca/desmarca "Evento público" no modal de atividade (criar e editar), uma flag durável e consultável que registra a INTENÇÃO de expor o compromisso na futura agenda pública do site — a exposição em si é item PUB posterior. Campo `publicEvent` (checkbox, default `false`, indexado) na collection `activity` existente, com o mesmo padrão de `deputyPresent`/`allDay`; migration `20260912_224719_add_activity_public_event`. O parser de FormData, o zod de entrada, o `ActivityFormViewModel`/`activityFormSelect` e o `ActivityOverlay` (checkbox ao lado de "Todo o dia", default desmarcado, volta correto ao reabrir) acompanham o campo; o snapshot do guard de liderança em `Activity.ts` inclui a flag (defesa em profundidade). Nada no site público, na agenda, na lista, no detalhe ou no espelho do Google (C114) muda: `publicEvent` fica fora de `SYNC_RELEVANT_ACTIVITY_FIELDS` e dos selects de agenda/lista/detalhe, sem filtro/badge novo. Testes: unit do overlay (render, submit e prefill), int de parse e persistência/reabertura, e o e2e `campaignActivity` cobre marcar→salvar→reabrir. Plano: `docs/plans/c151-atividade-evento-publico-impl.md`.
|
||||
116
docs/plans/c151-atividade-evento-publico-impl.md
Normal file
116
docs/plans/c151-atividade-evento-publico-impl.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# Impl: Atividade: marcar evento como público (base para exposição no site)
|
||||
|
||||
Status: aprovado
|
||||
Atualizado em: 2026-09-12
|
||||
Issue: #936
|
||||
Intenção: docs/plans/c151-atividade-evento-publico.md
|
||||
Appetite restante: herdado (~0,5 dia eng) — cabe sem corte.
|
||||
|
||||
## Leitura da intenção
|
||||
|
||||
- **Outcome:** o staff marca/desmarca "Evento público" no modal de atividade; a marcação é
|
||||
durável, volta correta ao reabrir e fica consultável — sem nenhuma tela mudar de comportamento.
|
||||
- **O que NÃO negociar:** exposição pública é item futuro (PUB), nada no `(frontend)`; sem filtro/badge
|
||||
na lista; sem collection paralela; `leader` segue lockdown (não vê o campo); o espelho do Google
|
||||
(C114) não é influenciado em v1.
|
||||
- **O que reavaliar:** a hipótese do plano de intenção ("a marcação vive na atividade existente")
|
||||
confirma-se — nenhuma surpresa de acoplamento na exploração.
|
||||
|
||||
## Abordagem recomendada
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
O[ActivityOverlay<br/>checkbox publicEvent] -->|FormData| P[parseSharedActivityFormData]
|
||||
P --> Z[activityCreate/UpdateSchema]
|
||||
Z --> A[create/updateActivityRecord]
|
||||
A --> C[collection activity<br/>publicEvent checkbox]
|
||||
C -->|migration| DB[(activity.public_event)]
|
||||
A -.nao inclui.-> G[sync Google C114]
|
||||
```
|
||||
|
||||
**Opções consideradas:** A) campo boolean na `activity` existente (draft) | B) collection/entidade
|
||||
"evento público" separada | C) global/config paralela.
|
||||
|
||||
**Recomendação:** A — a intenção fixa a flag na atividade existente; é um checkbox de negócio, indexado,
|
||||
sem access especial (quem edita a atividade marca).
|
||||
|
||||
**Rejeitadas:** B porque duplica cadastro e cria divergência de dados (anti-goal explícito da intenção);
|
||||
C porque não há escopo por item — a flag é por atividade.
|
||||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **`publicEvent`** (`src/collections/Activity.ts`): `type: 'checkbox'`, `label: 'Evento público'`,
|
||||
`defaultValue: false`, `index: true`, `admin.description` "Quando marcado, este evento poderá ser
|
||||
publicado no site do candidato (futuro)." — espelha `deputyPresent`/`allDay` (sem `access` de field:
|
||||
qualquer staff com linha pode marcar).
|
||||
- **`activityStaffFieldSnapshot`** (`src/collections/Activity.ts`): incluir
|
||||
`publicEvent: Boolean(doc.publicEvent)` no snapshot do guard de liderança — mantém a defesa em
|
||||
profundidade (liderança, se algum dia alcançar o write path, não marca campo de staff). Access
|
||||
`canReadActivity`/`canUpdateActivity` já negam `leader`; isto é só cinto de segurança barato.
|
||||
- **`src/lib/schemas/activity.ts`**: adicionar `publicEvent: z.boolean().optional()` em
|
||||
`activityFieldsSchema` (herdado por create/update `.partial()`).
|
||||
- **`src/utilities/activityFormData.ts`**: `publicEvent: checkboxFormValue(formData, 'publicEvent')`
|
||||
em `parseSharedActivityFormData` — sem isso o checkbox é silenciosamente descartado no submit.
|
||||
- **`src/utilities/activityViewModels.ts`**: `ActivityFormViewModel.publicEvent: boolean` +
|
||||
`activityFormSelect.publicEvent` + `toActivityFormViewModel`. **Não** tocar em
|
||||
agenda/list/detail selects/view models (nada de badge/coluna/consumo novo).
|
||||
- **`src/components/campaign/activity/ActivityOverlay.tsx`**: checkbox "Evento público" na
|
||||
`scheduleSection`, logo após "Todo o dia" (posição do rascunho UI aprovado), `name="publicEvent"`,
|
||||
`defaultChecked={isEdit ? editDraft?.publicEvent : false}`, `aria-label="Evento público"`.
|
||||
- **Migration:** `pnpm migrate:create add_activity_public_event` — copia exata do precedente
|
||||
`20260810_010844_add_activity_all_day` (`ADD COLUMN "public_event" boolean DEFAULT false` +
|
||||
índice btree `activity_public_event_idx`). Depois `pnpm generate:types` (`src/payload-types.ts`).
|
||||
- **Google Calendar (C114):** `SYNC_RELEVANT_ACTIVITY_FIELDS` **não** recebe `publicEvent` — decisão
|
||||
v1 da intenção; editar só a flag não re-dispara sync nem carimba `lastMirroredChangeAt`.
|
||||
- **UI:** Impeccable B (encaixe no modal). Reusa `Checkbox` + `Field` do próprio overlay; sem seção
|
||||
nova, sem shell novo. Shape já aprovado no rascunho `c151-atividade-evento-publico-ui-draft.html`.
|
||||
|
||||
### Dados → forma (se aplicável)
|
||||
|
||||
- Não aplicável: nenhuma superfície de dados neste item (a intenção já decidiu "não apresentar dados").
|
||||
|
||||
## Fases verificáveis
|
||||
|
||||
1. **Tracer schema+server** — campo + migration + zod + parser + select/view model do form;
|
||||
int test prova persistência e reabertura (`loadActivityEditDraftRecord` devolve `publicEvent`).
|
||||
2. **UI** — checkbox no `scheduleSection`; unit test do overlay (render no create e no edit, submit
|
||||
manda `publicEvent=on` só quando marcado).
|
||||
3. **Gates** — `pnpm gate:fast`; `pnpm test` (unit+int) na entrega; push via `pnpm push`.
|
||||
|
||||
## Rabbit holes / Não escopo (engenharia)
|
||||
|
||||
- Expor no site público, agenda pública, SEO ou share kit (item PUB futuro).
|
||||
- Badge/coluna/filtro na lista de atividades, na agenda ou no detalhe.
|
||||
- Ligar a flag ao espelho do Google (C114) — decidido fora em v1.
|
||||
- Tornar `publicEvent` system field (`canSetActivitySystemField`) ou restricted access — não pedido.
|
||||
- Marcação em lote / spreadsheet mode.
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- **Risco:** checkbox silenciosamente descartado no submit se o parser/zod não acompanhar. **Mitigação:**
|
||||
unit test do overlay + int test de parse/persistência.
|
||||
- **Risco:** a flag vazar para abas/visões como comportamento novo. **Mitigação:** mudanças restritas a
|
||||
form select/view model; nenhum agenda/list/detail select alterado.
|
||||
- **Risco:** migration sem regenerar tipos → erro de `tsc`. **Mitigação:** `migrate:create` +
|
||||
`generate:types` + `pnpm typecheck` no gate.
|
||||
|
||||
## Débitos triados (simplify)
|
||||
|
||||
- **Já resolvido:** o `migrate:create` arrastou drift de `supporter_import_batch.actor_id`
|
||||
(snapshot stale dos hand-written 20260824) com `down` rollback-inseguro — removido, a migration
|
||||
só mexe em `public_event`; changelog da entrega criado; racional C126 do `farFuture` no int;
|
||||
status do plano finalizado.
|
||||
- **Descartado (YAGNI):** índice parcial em vez de btree pleno para um boolean de 2 valores — sem
|
||||
medição que justifique; a intenção pediu "indexado" e o precedente `all_day` usa btree pleno.
|
||||
- **Defer (gatilho):** extrair um `OverlayCheckboxField` local para os três checkboxes idênticos do
|
||||
overlay (`deputyPresent`/`allDay`/`publicEvent`) — só quando um 4º checkbox ou mudança real de UX
|
||||
no bloco justificar.
|
||||
- **Defer (gatilho):** teste de regressão pinando `publicEvent` fora de `SYNC_RELEVANT_ACTIVITY_FIELDS`
|
||||
e dos selects de agenda/lista — hoje a type honesty (view models sem o campo) cobre agenda/lista/
|
||||
detalhe; adicionar o pin quando o item PUB ligar a flag a um consumidor.
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [x] Aceite de produto da intenção ainda coberto (marca/desmarca, durável, reabre correto, nada muda)
|
||||
- [x] Invariantes AGENTS/engineering-standards (leader lockdown, copy pt-BR/identificadores EN)
|
||||
- [x] Testes de domínio previstos (unit do overlay + int de persistência/parse + e2e de roundtrip)
|
||||
|
|
@ -57,6 +57,7 @@ const activityStaffFieldSnapshot = (doc: Record<string, unknown>) => ({
|
|||
municipality: relationshipId(doc.municipality),
|
||||
locality: trimmedText(doc.locality),
|
||||
deputyPresent: Boolean(doc.deputyPresent),
|
||||
publicEvent: Boolean(doc.publicEvent),
|
||||
organizations: relationshipIds(doc.organizations),
|
||||
responsible: JSON.stringify(
|
||||
parseActivityResponsibleEntries(doc.responsible)
|
||||
|
|
@ -514,6 +515,21 @@ export const Activity: CollectionConfig = {
|
|||
description: 'Compromisso que ocupa um ou mais dias inteiros, sem horário.',
|
||||
},
|
||||
},
|
||||
{
|
||||
// C151 — marks a staff intent to expose this activity as a public event
|
||||
// in the candidate's website (a future PUB item). The flag is durable and
|
||||
// consultable; nothing consumes it yet and it does not affect the agenda,
|
||||
// the list, or the Google Calendar mirror.
|
||||
name: 'publicEvent',
|
||||
type: 'checkbox',
|
||||
label: 'Evento público',
|
||||
defaultValue: false,
|
||||
index: true,
|
||||
admin: {
|
||||
description:
|
||||
'Quando marcado, este evento poderá ser publicado no site do candidato (futuro).',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'startAt',
|
||||
type: 'date',
|
||||
|
|
|
|||
|
|
@ -352,6 +352,20 @@ const ActivityOverlayForm = ({
|
|||
<span>Todo o dia</span>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
orientation="horizontal"
|
||||
className={
|
||||
sheet ? 'min-h-11 gap-2 rounded-none border-0 px-0' : 'min-h-11 rounded-lg border p-3'
|
||||
}
|
||||
>
|
||||
<Checkbox
|
||||
name="publicEvent"
|
||||
defaultChecked={isEdit ? editDraft?.publicEvent : false}
|
||||
aria-label="Evento público"
|
||||
/>
|
||||
<span>Evento público</span>
|
||||
</Field>
|
||||
|
||||
{sheet ? startEndFields : <div className="grid gap-3 sm:grid-cols-2">{startEndFields}</div>}
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ const activityFieldsSchema = z.object({
|
|||
description: trimmedOptionalText(4000),
|
||||
deputyPresent: z.boolean().optional(),
|
||||
allDay: z.boolean().optional(),
|
||||
publicEvent: z.boolean().optional(),
|
||||
startAt: z.string().datetime().optional().nullable(),
|
||||
endAt: z.string().datetime().optional().nullable(),
|
||||
municipality: positiveRelationshipId,
|
||||
|
|
|
|||
14127
src/migrations/20260912_224719_add_activity_public_event.json
Normal file
14127
src/migrations/20260912_224719_add_activity_public_event.json
Normal file
File diff suppressed because it is too large
Load diff
13
src/migrations/20260912_224719_add_activity_public_event.ts
Normal file
13
src/migrations/20260912_224719_add_activity_public_event.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "activity" ADD COLUMN "public_event" boolean DEFAULT false;
|
||||
CREATE INDEX "activity_public_event_idx" ON "activity" USING btree ("public_event");`)
|
||||
}
|
||||
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DROP INDEX "activity_public_event_idx";
|
||||
ALTER TABLE "activity" DROP COLUMN "public_event";`)
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ import * as migration_20260823_161905_add_municipality_update_deliberation from
|
|||
import * as migration_20260824_000000_reconcile_allocation_decision_rationale_nullable from './20260824_000000_reconcile_allocation_decision_rationale_nullable';
|
||||
import * as migration_20260824_010000_make_supporter_import_batch_actor_nullable from './20260824_010000_make_supporter_import_batch_actor_nullable';
|
||||
import * as migration_20260912_052226_add_google_calendar_oauth_connection from './20260912_052226_add_google_calendar_oauth_connection';
|
||||
import * as migration_20260912_224719_add_activity_public_event from './20260912_224719_add_activity_public_event';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
|
|
@ -402,4 +403,9 @@ export const migrations = [
|
|||
down: migration_20260912_052226_add_google_calendar_oauth_connection.down,
|
||||
name: '20260912_052226_add_google_calendar_oauth_connection'
|
||||
},
|
||||
{
|
||||
up: migration_20260912_224719_add_activity_public_event.up,
|
||||
down: migration_20260912_224719_add_activity_public_event.down,
|
||||
name: '20260912_224719_add_activity_public_event'
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -776,6 +776,10 @@ export interface Activity {
|
|||
* Compromisso que ocupa um ou mais dias inteiros, sem horário.
|
||||
*/
|
||||
allDay?: boolean | null;
|
||||
/**
|
||||
* Quando marcado, este evento poderá ser publicado no site do candidato (futuro).
|
||||
*/
|
||||
publicEvent?: boolean | null;
|
||||
startAt?: string | null;
|
||||
endAt?: string | null;
|
||||
municipality: number | Municipality;
|
||||
|
|
@ -1915,6 +1919,7 @@ export interface ActivitySelect<T extends boolean = true> {
|
|||
description?: T;
|
||||
deputyPresent?: T;
|
||||
allDay?: T;
|
||||
publicEvent?: T;
|
||||
startAt?: T;
|
||||
endAt?: T;
|
||||
municipality?: T;
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ const parseSharedActivityFormData = (formData: FormData) => {
|
|||
description: optionalFormText(formData, 'description'),
|
||||
deputyPresent: checkboxFormValue(formData, 'deputyPresent'),
|
||||
allDay,
|
||||
publicEvent: checkboxFormValue(formData, 'publicEvent'),
|
||||
startAt: parseScheduleFormField(formData, 'startAt', allDay),
|
||||
endAt: parseScheduleFormField(formData, 'endAt', allDay),
|
||||
municipality: requiredRelationshipFormValue(formData, 'municipality'),
|
||||
|
|
|
|||
|
|
@ -228,6 +228,7 @@ export const activityFormSelect = {
|
|||
description: true,
|
||||
deputyPresent: true,
|
||||
allDay: true,
|
||||
publicEvent: true,
|
||||
startAt: true,
|
||||
endAt: true,
|
||||
municipality: true,
|
||||
|
|
@ -253,6 +254,7 @@ export type ActivityFormViewModel = {
|
|||
description: string | null
|
||||
deputyPresent: boolean
|
||||
allDay: boolean
|
||||
publicEvent: boolean
|
||||
startAt: string | null
|
||||
endAt: string | null
|
||||
municipalityId: number | null
|
||||
|
|
@ -274,6 +276,7 @@ export const toActivityFormViewModel = (activity: Activity): ActivityFormViewMod
|
|||
description: activity.description ?? null,
|
||||
deputyPresent: Boolean(activity.deputyPresent),
|
||||
allDay: Boolean(activity.allDay),
|
||||
publicEvent: Boolean(activity.publicEvent),
|
||||
startAt: activity.startAt ?? null,
|
||||
endAt: activity.endAt ?? null,
|
||||
municipalityId: relationshipId(activity.municipality),
|
||||
|
|
|
|||
|
|
@ -412,6 +412,8 @@ test.describe('Agenda — calendário operacional', () => {
|
|||
// regression): saving must actually persist.
|
||||
await editModal.getByLabel('Hora de Término').selectOption('15')
|
||||
await editModal.getByLabel('Local (opcional)').fill(newLocality)
|
||||
// C151 — the public-event intent is marked in the same save.
|
||||
await editModal.getByLabel('Evento público').click()
|
||||
await editModal.getByRole('button', { name: 'Salvar alterações' }).click()
|
||||
|
||||
// Save closes the overlay, never navigates; the calendar reflects the edit.
|
||||
|
|
@ -426,6 +428,8 @@ test.describe('Agenda — calendário operacional', () => {
|
|||
await page.getByText(title, { exact: true }).first().click()
|
||||
const detailModal = page.getByRole('dialog', { name: 'Editar atividade' })
|
||||
await expect(detailModal.getByLabel('Título *')).toBeVisible({ timeout: 30_000 })
|
||||
// C151 — the mark survives the save and comes back on reopening the overlay.
|
||||
await expect(detailModal.getByLabel('Evento público')).toHaveAttribute('aria-checked', 'true')
|
||||
await detailModal.getByRole('link', { name: 'Ver detalhes' }).click()
|
||||
await expect(page).toHaveURL(/\/campanha\/atividades\/[^/?]+(?:\?tab=overview)?$/, {
|
||||
timeout: 30_000,
|
||||
|
|
|
|||
|
|
@ -647,6 +647,57 @@ describe('activity domain', () => {
|
|||
expect(parsed.endAt).toBe(allDayEndInstant('2026-08-12'))
|
||||
})
|
||||
|
||||
it('parses the public event flag from the activity form (C151)', () => {
|
||||
const formData = new FormData()
|
||||
formData.set('title', 'Comício público')
|
||||
formData.set('status', 'confirmado')
|
||||
formData.set('municipality', '1')
|
||||
formData.set('tagsJson', '[]')
|
||||
formData.set('demandsJson', '[]')
|
||||
|
||||
// Unchecked checkbox is absent from FormData — parses as false.
|
||||
expect(parseActivityCreateFormData(formData).publicEvent).toBe(false)
|
||||
|
||||
formData.set('publicEvent', 'on')
|
||||
expect(parseActivityCreateFormData(formData).publicEvent).toBe(true)
|
||||
})
|
||||
|
||||
it('persists the public event flag and returns it when reopening the overlay (C151)', async () => {
|
||||
const fixtures = campaignFixtures()
|
||||
const coordinator = await fixtures.createCampaignUser('coordinator')
|
||||
const [markedMunicipality, ordinaryMunicipality] = await Promise.all([
|
||||
fixtures.getMunicipality(),
|
||||
fixtures.getMunicipality(),
|
||||
])
|
||||
// Far-future startAt keeps these rows OUTSIDE the Google sync engine's
|
||||
// 90/365-day scan window, avoiding the C126 parallel-flake class.
|
||||
const farFuture = new Date(Date.now() + 400 * 86_400_000).toISOString()
|
||||
|
||||
const marked = await createActivityRecord(payload, coordinator, {
|
||||
...validActivityInput(markedMunicipality.id),
|
||||
title: fixtures.value('Evento público marcado'),
|
||||
startAt: farFuture,
|
||||
publicEvent: true,
|
||||
})
|
||||
fixtures.own('activity', marked.id)
|
||||
|
||||
const ordinary = await createActivityRecord(payload, coordinator, {
|
||||
...validActivityInput(ordinaryMunicipality.id),
|
||||
title: fixtures.value('Atividade interna'),
|
||||
startAt: farFuture,
|
||||
})
|
||||
fixtures.own('activity', ordinary.id)
|
||||
|
||||
expect(marked.publicEvent).toBe(true)
|
||||
expect(ordinary.publicEvent).toBe(false)
|
||||
expect((await loadActivityEditDraftRecord(payload, coordinator, marked.id)).publicEvent).toBe(
|
||||
true,
|
||||
)
|
||||
expect((await loadActivityEditDraftRecord(payload, coordinator, ordinary.id)).publicEvent).toBe(
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
it('loads only accessible events matching the agenda range and filters', async () => {
|
||||
const fixtures = campaignFixtures()
|
||||
const advisor = await fixtures.createCampaignUser('advisor')
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ const EDIT_VIEW_MODEL: ActivityFormViewModel = {
|
|||
description: 'Panfletagem no centro histórico',
|
||||
deputyPresent: true,
|
||||
allDay: false,
|
||||
publicEvent: true,
|
||||
startAt: '2026-08-07T16:00:00.000Z',
|
||||
endAt: '2026-08-07T17:00:00.000Z',
|
||||
municipalityId: 12,
|
||||
|
|
@ -170,6 +171,7 @@ describe('ActivityOverlay — criação (modal central)', () => {
|
|||
expect(screen.getByText('Demandas')).toBeTruthy()
|
||||
expect(screen.getByLabelText('Descrição')).toBeTruthy()
|
||||
expect(screen.getByLabelText('Deputado presente')).toBeTruthy()
|
||||
expect(screen.getByLabelText('Evento público')).toBeTruthy()
|
||||
expect(screen.getByRole('combobox', { name: /Organizações apoiadoras/ })).toBeTruthy()
|
||||
})
|
||||
|
||||
|
|
@ -218,6 +220,7 @@ describe('ActivityOverlay — criação (modal central)', () => {
|
|||
target: { value: 'Café da manhã com lideranças' },
|
||||
})
|
||||
fireEvent.click(screen.getByLabelText('Deputado presente'))
|
||||
fireEvent.click(screen.getByLabelText('Evento público'))
|
||||
fireEvent.change(screen.getByRole('combobox', { name: 'Minuto de Término' }), {
|
||||
target: { value: '45' },
|
||||
})
|
||||
|
|
@ -232,6 +235,7 @@ describe('ActivityOverlay — criação (modal central)', () => {
|
|||
expect(formData.get('locality')).toBe('Centro histórico')
|
||||
expect(formData.get('description')).toBe('Café da manhã com lideranças')
|
||||
expect(formData.get('deputyPresent')).toBe('on')
|
||||
expect(formData.get('publicEvent')).toBe('on')
|
||||
expect(formData.get('status')).toBe('confirmado')
|
||||
expect(formData.get('allDay')).toBeNull()
|
||||
expect(formData.get('id')).toBeNull()
|
||||
|
|
@ -365,6 +369,7 @@ describe('ActivityOverlay — edição (modal central)', () => {
|
|||
expect((screen.getByLabelText('Descrição') as HTMLInputElement).value).toBe(
|
||||
'Panfletagem no centro histórico',
|
||||
)
|
||||
expect(screen.getByLabelText('Evento público').getAttribute('aria-checked')).toBe('true')
|
||||
expect((screen.getByLabelText('Título *') as HTMLInputElement).readOnly).toBe(true)
|
||||
expect(screen.getByText('Comício')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Ver detalhes' }).getAttribute('href')).toBe(
|
||||
|
|
@ -380,6 +385,7 @@ describe('ActivityOverlay — edição (modal central)', () => {
|
|||
const formData = mocks.updateOverlay.mock.calls[0][0] as FormData
|
||||
expect(formData.get('id')).toBe('42')
|
||||
expect(formData.get('title')).toBe('Comício na feira')
|
||||
expect(formData.get('publicEvent')).toBe('on')
|
||||
expect(formData.get('startAt')).toBe('2026-08-07T13:00')
|
||||
expect(formData.get('endAt')).toBe('2026-08-07T14:30')
|
||||
expect(onSaved).toHaveBeenCalledTimes(1)
|
||||
|
|
|
|||
Loading…
Reference in a new issue