mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 08:53:11 +00:00
* deploy: limpeza pós-deploy do build cache e tags locais antigas (INF3/F2) O deploy compila no homeserver e acumulava build cache + tags locais antigas no disco raiz (incidente 19/08: 45G de cache). Após o smoke: docker builder prune -f + remoção das tags locais teqo-1313 exceto o SHA em uso (best-effort, registry preserva o rollback). * feat(S9): captura de novidades da campanha na home pública (form + CTA do hero) Seção 'Receba as novidades da campanha' acima do rodapé: nome + WhatsApp obrigatórios, e-mail/estado/cidade/comentário opcionais, toggle pré-selecionado 'Quero fazer parte do time' gravado como subscription.campaignLevel (time|esporadico). CTA secundário do hero vira o atalho (#novidades, scroll suave). Contact + Subscription em withPayloadTransaction com consentimento novo fail-closed por chave estável 'campanha-novidades' — nada é gravado sem o texto aprovado no admin. Migration 20260819_192605 (state de contact nullable + coluna campaign_level). Impl plan, changelog, int + e2e. * plans(C141,C143,C142): permissões granulares por assessor + responsáveis de demanda + UI por perfil * plans(C141,C143,C142): permissões granulares por assessor + responsáveis de demanda + UI por perfil * feat(S8): seção 'Nossa história' na home — vídeo da trajetória embutido do YouTube (Closes #91) * docs(S8): resolução do conflito com S9 (história → newsletter) + nota do guard e2e dev-only * feat(S10): pixel do Meta na página pública de campanha (campo admin em SiteSettings + PageView na home + Lead no sucesso da captura) Campo tracking.facebookPixelId no global SiteSettings (mesma validação/normalização do precedente Petition, regra centralizada em facebookPixel.ts); home (grupo (home)) injeta o base code + PageView quando o ID está configurado (fail-closed) e o CampaignNewsletterForm dispara exatamente um Lead no sucesso do S9. Migration 20260819_213947_add_site_settings_facebook_pixel_id. Closes #94 * refactor(S10): simplify pass — Lead wiring unit-tested e raças de estado compartilhado do e2e resolvidas e2e: stub hermético de connect.facebook.net no guard compartilhado (specs paralelos renderizam a home com o pixel); advisory lock no Postgres serializando o login do usuário de teste (Payload perde sessão em login concorrente read-modify-write — classe documentada no webauthn); seedTestUser vira create-if-missing (sem janela de usuário ausente nem reset de senha); Lead do form protegido do catch de erro (tracking nunca falha uma captura commitada); descrição do campo compartilhada; unidade nova campaignNewsletterLead cobre exatamente um Lead no sucesso. * feat(C141): perfis de permissão por assessor — Visão × Edição configuráveis, enforcement fail-closed (Closes #105) * chore(C141): regenera changelog agregado após rebase com S10 * plans(C144): options write-scoped para formulários de escrita pós-C141 --------- Co-authored-by: Francisco Solla <admin@solla.dev>
98 lines
3.2 KiB
TypeScript
98 lines
3.2 KiB
TypeScript
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { CampaignNewsletterCapture } from '@/components/CampaignNewsletterForm'
|
|
|
|
/**
|
|
* S10 — the `Lead` wiring of the S9 capture form: a successful submission
|
|
* with a configured pixel fires exactly one `trackMetaLead` (content_name
|
|
* `novidades-da-campanha`, UUID eventID); without a pixel, or when the
|
|
* submission fails, nothing fires. The e2e covers the base-code render; the
|
|
* real submission stays out of e2e on purpose (the `campanha-novidades`
|
|
* consent key is shared with the parallel `campaignNewsletter` spec).
|
|
*/
|
|
const submitCampaignNewsletterMock = vi.fn()
|
|
const trackMetaLeadMock = vi.fn()
|
|
|
|
vi.mock('@/app/(frontend)/actions/submitCampaignNewsletter', () => ({
|
|
submitCampaignNewsletter: (...args: unknown[]) => submitCampaignNewsletterMock(...args),
|
|
}))
|
|
|
|
vi.mock('@/lib/facebookPixel', () => ({
|
|
trackMetaLead: (...args: unknown[]) => trackMetaLeadMock(...args),
|
|
}))
|
|
|
|
const PIXEL_ID = '123456789012345'
|
|
|
|
beforeAll(() => {
|
|
// Radix comboboxes (StateSelect/CitySelect) need ResizeObserver in jsdom.
|
|
vi.stubGlobal(
|
|
'ResizeObserver',
|
|
class {
|
|
observe() {}
|
|
unobserve() {}
|
|
disconnect() {}
|
|
},
|
|
)
|
|
})
|
|
|
|
const fillAndSubmit = async () => {
|
|
// FormInput propagates to react-hook-form through the native `input` event.
|
|
fireEvent.input(screen.getByPlaceholderText('Nome completo*'), {
|
|
target: { value: 'Maria Teste' },
|
|
})
|
|
fireEvent.input(screen.getByPlaceholderText('WhatsApp (com DDD)*'), {
|
|
target: { value: '71987654321' },
|
|
})
|
|
await act(async () => {
|
|
fireEvent.click(screen.getByRole('button', { name: 'QUERO RECEBER NOVIDADES' }))
|
|
})
|
|
}
|
|
|
|
describe('Campaign newsletter Lead wiring', () => {
|
|
beforeEach(() => {
|
|
submitCampaignNewsletterMock.mockReset()
|
|
trackMetaLeadMock.mockReset()
|
|
submitCampaignNewsletterMock.mockResolvedValue(undefined)
|
|
})
|
|
|
|
afterEach(() => {
|
|
cleanup()
|
|
})
|
|
|
|
it('fires exactly one Lead on a successful capture with a configured pixel', async () => {
|
|
render(<CampaignNewsletterCapture pixelId={PIXEL_ID} />)
|
|
await fillAndSubmit()
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('heading', { name: 'Inscrição confirmada' })).toBeTruthy()
|
|
})
|
|
expect(trackMetaLeadMock).toHaveBeenCalledTimes(1)
|
|
expect(trackMetaLeadMock).toHaveBeenCalledWith(
|
|
PIXEL_ID,
|
|
'novidades-da-campanha',
|
|
expect.any(String),
|
|
)
|
|
})
|
|
|
|
it('never fires a Lead without a configured pixel', async () => {
|
|
render(<CampaignNewsletterCapture />)
|
|
await fillAndSubmit()
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('heading', { name: 'Inscrição confirmada' })).toBeTruthy()
|
|
})
|
|
expect(trackMetaLeadMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('never fires a Lead when the capture fails', async () => {
|
|
submitCampaignNewsletterMock.mockRejectedValue(new Error('boom'))
|
|
render(<CampaignNewsletterCapture pixelId={PIXEL_ID} />)
|
|
await fillAndSubmit()
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Falha ao enviar. Tente novamente.')).toBeTruthy()
|
|
})
|
|
expect(trackMetaLeadMock).not.toHaveBeenCalled()
|
|
})
|
|
})
|