mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +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>
89 lines
3 KiB
TypeScript
89 lines
3 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
import {
|
|
isValidFacebookPixelId,
|
|
normalizeFacebookPixelId,
|
|
trackMetaLead,
|
|
validateFacebookPixelId,
|
|
} from '@/lib/facebookPixel'
|
|
|
|
describe('facebookPixel', () => {
|
|
describe('isValidFacebookPixelId', () => {
|
|
it('accepts numeric IDs between 5 and 20 digits', () => {
|
|
expect(isValidFacebookPixelId('12345')).toBe(true)
|
|
expect(isValidFacebookPixelId('123456789012345')).toBe(true)
|
|
})
|
|
|
|
it('rejects empty, non-numeric, and out-of-range values', () => {
|
|
expect(isValidFacebookPixelId('')).toBe(false)
|
|
expect(isValidFacebookPixelId(null)).toBe(false)
|
|
expect(isValidFacebookPixelId(undefined)).toBe(false)
|
|
expect(isValidFacebookPixelId('1234')).toBe(false)
|
|
expect(isValidFacebookPixelId('123456789012345678901')).toBe(false)
|
|
expect(isValidFacebookPixelId('<script>alert(1)</script>')).toBe(false)
|
|
expect(isValidFacebookPixelId('fbq("init")')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('normalizeFacebookPixelId', () => {
|
|
it('trims whitespace and returns null for invalid values', () => {
|
|
expect(normalizeFacebookPixelId(' 123456789012345 ')).toBe('123456789012345')
|
|
expect(normalizeFacebookPixelId('')).toBe(null)
|
|
expect(normalizeFacebookPixelId(' ')).toBe(null)
|
|
expect(normalizeFacebookPixelId('abc')).toBe(null)
|
|
})
|
|
})
|
|
|
|
describe('validateFacebookPixelId', () => {
|
|
it('accepts empty values (optional field)', () => {
|
|
expect(validateFacebookPixelId('')).toBe(true)
|
|
expect(validateFacebookPixelId(null)).toBe(true)
|
|
expect(validateFacebookPixelId(undefined)).toBe(true)
|
|
})
|
|
|
|
it('accepts numeric IDs between 5 and 20 digits', () => {
|
|
expect(validateFacebookPixelId('123456789012345')).toBe(true)
|
|
})
|
|
|
|
it('rejects non-numeric and out-of-range values with the admin message', () => {
|
|
const message = 'Informe somente o ID numérico do Pixel (5 a 20 dígitos), sem HTML ou script.'
|
|
expect(validateFacebookPixelId('1234')).toBe(message)
|
|
expect(validateFacebookPixelId('<script>alert(1)</script>')).toBe(message)
|
|
})
|
|
})
|
|
|
|
describe('trackMetaLead', () => {
|
|
it('no-ops when fbq is unavailable', () => {
|
|
expect(() => trackMetaLead('123456789012345', 'Petição teste', 'event-1')).not.toThrow()
|
|
})
|
|
|
|
it('fires Lead with content_name and eventID when fbq exists', () => {
|
|
const fbq = vi.fn()
|
|
vi.stubGlobal('window', { fbq })
|
|
|
|
trackMetaLead('123456789012345', 'Petição teste', 'event-1')
|
|
|
|
expect(fbq).toHaveBeenCalledWith(
|
|
'track',
|
|
'Lead',
|
|
{ content_name: 'Petição teste' },
|
|
{ eventID: 'event-1' },
|
|
)
|
|
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
it('no-ops for invalid pixel IDs even when fbq exists', () => {
|
|
const fbq = vi.fn()
|
|
vi.stubGlobal('window', { fbq })
|
|
|
|
trackMetaLead('not-a-pixel', 'Petição teste', 'event-1')
|
|
|
|
expect(fbq).not.toHaveBeenCalled()
|
|
|
|
vi.unstubAllGlobals()
|
|
})
|
|
})
|
|
})
|