teqo-backup/tests/unit/campaignHome.unit.spec.tsx
Francisco Solla dda55adc27 feat(S27): Central de Conteúdos pública — Peça voto pra Solla 1313
/conteudos + /conteudos/<slug>: catálogo filtrável (Tipo · Cidade · Região ·
Tema · Instituição + termo na URL canônica), play sob demanda (Vídeo/Áudio só
carregam no toque; Foto com preview leve), Baixar e Compartilhar do próprio
card com a mensagem de voto por tipo (wa.me sem destinatário, editável; "A
mídia" usa a folha nativa e cai em download onde o aparelho não suportar) e o
card personalizado como peça sintética do catálogo → /cards.

- leitura pública nova (contentPieceReads) cacheada na tag contentPieces com o
  gate where status=publicado + contentPieceIsPublic; a rota GET
  /conteudos/<slug>/midia reusa buildPrivateMediaResponse (range, disposition,
  private no-store, 404 uniforme) com download jorge-solla-1313-<slug>.<ext> e
  a allowlist inline do dono estendida (webp/gif/avif + áudios das peças)
- contrato público puro do catálogo (filtros/VM/download) + mensagens de voto
  (contentPieceShare); OG com fallback do global; conteudos entra nos slugs
  reservados; kill switch por peça vale na hora (lista, página, rodapé/selo);
  descoberta condicional no CampaignFooter e no header local
- UI portada classe-a-classe do design aprovado (cenas 01–08, com as cenas
  06–08 estendidas no trigger a); sem migration, sem Consent e sem access novo;
  contentMedia segue privada (sem segunda cópia de mídia)
- testes: 2 unit specs novos, 18 casos int (leitura pública + rota de mídia) e
  e2e frontendConteudos (5 casos); manifest e curado atualizados
- follow-up de engenharia: #1276 (docs/plans/escala-dry-pos-s27.md)

Closes #1255
2026-09-23 11:13:57 -03:00

104 lines
4.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { cleanup, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import HomePage from '@/app/(frontend)/(home)/page'
type MockImageProps = React.ImgHTMLAttributes<HTMLImageElement> & {
fill?: boolean
priority?: boolean
}
vi.mock('next/image', () => ({
default: ({ alt, fill: _fill, priority: _priority, ...props }: MockImageProps) => (
// eslint-disable-next-line @next/next/no-img-element
<img alt={alt} {...props} />
),
}))
// The content section is an async server component that reads posts through
// the Payload DB; the unit env has no database and an async child suspends the
// whole tree under testing-library. Its empty/full behavior is covered by e2e
// (frontend.e2e.spec.ts), so the home skeleton tests render it away.
vi.mock('@/components/CampaignContentSection', () => ({
CampaignContentSection: () => null,
}))
// The S9 capture form is a client component (radix comboboxes/checkbox) whose
// interactions are e2e-covered; the unit skeleton only pins the section shell.
vi.mock('@/components/CampaignNewsletterForm', () => ({
CampaignNewsletterCapture: () => null,
}))
// S10 — the section reads the site-settings global (server-only, Payload DB);
// the unit env has no database, and the pixel behavior is e2e-covered.
vi.mock('@/utilities/campaignHomeTracking', () => ({
getCampaignHomeMetaPixelId: async () => null,
}))
// S21/S22 — the home reads the jingle listing through the Payload DB +
// unstable_cache for the sound section and the footer discovery flag; the unit
// env has neither, and the real listing/kill-switch behavior is e2e-covered
// (frontendJingles.e2e.spec.ts).
vi.mock('@/utilities/jingleReads', () => ({
getPublishedJingleItems: async () => [],
}))
// S27 — the same for the Central de Conteúdos discovery flag (cached read of
// the content pieces listing); the real listing/kill-switch behavior is
// e2e-covered (frontendConteudos.e2e.spec.ts).
vi.mock('@/utilities/content/contentPieceReads', () => ({
hasPublishedContentPieces: async () => false,
}))
// S14 — the card section renders the client studio island (next/font local
// face + matchMedia + canvas); its behavior is e2e-covered, so the unit
// skeleton mocks both the face module and the island.
vi.mock('@/app/(frontend)/fonts', () => ({
brexterBold: { variable: 'font-brexter-mock', style: { fontFamily: 'Brexter' } },
}))
vi.mock('@/components/cards/CardsStudio', () => ({
CardsStudio: () => null,
}))
afterEach(cleanup)
describe('Campaign home', () => {
it('monta as seis seções previstas e o rodapé eleitoral', async () => {
render(await HomePage())
expect(screen.getByRole('heading', { level: 1, name: 'MAIS SAÚDE MAIS FUTURO' })).toBeTruthy()
expect(screen.getByText('3.333')).toBeTruthy()
expect(screen.getByText('1.031')).toBeTruthy()
expect(screen.getByText('3º')).toBeTruthy()
expect(screen.getByRole('heading', { name: /Eleger deputado é coisa séria/i })).toBeTruthy()
expect(
screen.getByRole('heading', {
name: /Junto com o trabalhador e do lado de quem mais precisa, sempre/i,
}),
).toBeTruthy()
// S22 — with zero published jingles the sound section shows only the radio.
expect(screen.getByRole('heading', { name: 'Sintonize com a Rádio 1313' })).toBeTruthy()
expect(screen.getByRole('heading', { name: /Mostre que você está com Solla/i })).toBeTruthy()
expect(screen.getByRole('heading', { name: /Receba as novidades da campanha/i })).toBeTruthy()
expect(screen.getByText(/CNPJ: 68\.430\.467\/0001-05/)).toBeTruthy()
})
it('usa exatamente as seis bandeiras aprovadas', async () => {
render(await HomePage())
// S6: cada bandeira aparece em duas renderizações irmãs (grade desktop e
// carrossel mobile); o teste garante que as duas seguem a mesma copy.
const expectedTitles = [
'Defender o SUS e valorizar quem cuida',
'Fim da escala 6×1 e jornada de 40h',
'Educação em tempo integral e federais no interior',
'Recomprar a Refinaria de Mataripe',
'Salário mínimo forte, emprego e moradia',
'Defesa intransigente da democracia',
]
for (const title of expectedTitles) {
expect(screen.getAllByText(title)).toHaveLength(2)
}
})
})