teqo-backup/tests/unit/cityReportRender.unit.spec.ts
Francisco Solla 321ac7b872
Some checks failed
Deploy / preflight (push) Has been cancelled
Deploy / verify (push) Has been cancelled
Deploy / deploy staging (teqo-staging) (push) Has been cancelled
Deploy / requeue when main advanced (push) Has been cancelled
Deploy / deploy production (teqo-1313) (push) Has been cancelled
feat(relatorio-cidade): foca o relatório na mobilização do 2º turno
A federal foi decidida em 04/10; o documento de cidade passa a servir
inteiro à eleição de Lula. Saem conta eleitoral/cadeira de Solla,
concorrentes e pré-candidatos, emendas e entregas (sem fetch do Portal
da Transparência no builder), acervo de falas, panorama regional e
sinais/demandas; demografia, economia e transporte ficam como contexto
da visita.

A página 1 vira só mobilização: KPIs oficiais do artefato TSE 2026
(Lula/Δ, X₁/X₂, Solla), papel determinístico da cidade, quem mobiliza,
movimento local do 2º turno, riscos e o painel "O que pedir e falar"
(pedido Lula 13; guardrail de votos em jogo no lugar do defeso).

O checklist cai de 10 para 8 itens (emendas_web e quem_investe saem;
relacao_campo/disputa_local reancorados no 2º turno); listas
preCandidates/investments/emendasIndicators saem do contrato (arquivos
antigos seguem aceitos em replay). O snapshot enxuga para rede +
conjuntura + demografia (cityReportTerritory.mjs removido).

Prova: unit 5.232 verdes, int do snapshot sem PII e sem os grupos
aposentados, lint/format/typecheck/knip limpos e rebuild da fixture de
Cruz das Almas (9 seções, 16 páginas contra 21, página 1 fechando sem
fallback).
2026-10-10 16:57:20 -03:00

112 lines
3.3 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 { describe, expect, it } from 'vitest'
import { buildCityReport } from '../../scripts/lib/cityReportBlocks.mjs'
import { renderReportHtml, renderReportMd } from '../../scripts/lib/cityReportRender.mjs'
import {
normalizeResearchInput,
RESEARCH_CHECKLIST_IDS,
} from '../../scripts/lib/cityReportResearch.mjs'
const generatedAt = new Date('2026-09-15T12:00:00.000Z')
const research = normalizeResearchInput(
{
municipalitySlug: 'feira-de-santana',
researchedAt: '2026-09-14T10:00:00.000Z',
items: RESEARCH_CHECKLIST_IDS.map((id) => ({
id,
answer: id === 'prefeito' ? 'Prefeito <script>alert(1)</script>' : `Resposta de ${id}`,
sourceUrl: `https://exemplo.test/${id}`,
sourceDate: '2026-09-10',
})),
news: [],
gaps: [],
},
{ now: generatedAt },
)
const report = buildCityReport({
snapshot: {
meta: {
readAt: '2026-09-15T10:30:00.000Z',
database: '127.0.0.1:5433/teqo_1313',
codeSha: 'abc1234',
},
municipality: {
id: 42,
slug: 'feira-de-santana',
name: 'Feira de Santana',
kind: 'municipio',
city: 'Feira de Santana',
region: 'Portal do Sertão',
ibgeCode: '2910800',
},
pledges: {
declaredTotal: 0,
lastPledgeAt: null,
},
leaderships: { totalCount: 0, rows: [] },
advisors: [],
conjuncture: null,
demographics: null,
},
research,
generatedAt,
})
describe('renderReportHtml', () => {
const html = renderReportHtml(report)
it('renders the summary page anchor and both headers', () => {
expect(html).toContain('data-page="summary"')
expect(html).toContain('pág. 1')
expect(html).toContain('Índice')
})
it('escapes research text (no raw injection)', () => {
expect(html).not.toContain('<script>alert(1)</script>')
expect(html).toContain('&lt;script&gt;')
})
it('turns every URL into a clickable anchor', () => {
expect(html).toContain(
'<a href="https://exemplo.test/prefeito">https://exemplo.test/prefeito</a>',
)
expect(html).toContain('href="https://exemplo.test/segundo_turno"')
})
it('prints the gaps as callouts and the limits list', () => {
expect(html).toContain('Lacuna explícita')
expect(html).toContain('Limites')
})
it('renders the ask panel of the 2º turno (no mandate-era copy)', () => {
expect(html).toContain('<div class="panel decision">')
expect(html).toContain('O que pedir e falar')
expect(html).not.toContain('O que anunciar agora')
expect(html).not.toContain('O que NÃO anunciar')
expect(html).not.toContain('indicators')
})
})
describe('renderReportMd', () => {
const md = renderReportMd(report)
it('carries the same sections as tables and headings', () => {
expect(md).toContain('## 1. 2º turno no município (Lula × Flávio)')
expect(md).toContain('> **Papel no 2º turno: Defesa e comparecimento**')
expect(md).toContain('## 2. Rede e lideranças')
expect(md).toContain('## 9. Fontes e limites')
expect(md).not.toContain('Conta eleitoral')
expect(md).not.toContain('Concorrentes')
})
it('lists sources with URLs', () => {
expect(md).toContain('https://exemplo.test/prefeito')
expect(md).toContain('base Teqo')
})
it('states the base read date (dated snapshot)', () => {
expect(md).toContain('Base Teqo (read-only) lida em')
})
})