teqo-backup/tests/unit/cityReportRender.unit.spec.ts
Francisco Solla d46360d1dc feat(C163): relatório — frente de oposição, dobradinhas, investimentos e seções dinâmicas
- Frente de oposição: lista de pesquisa 'opposition' (fato publicado com URL+data, sem inferir culpa) renderizada na seção 2, com nota de guardrail e URLs na seção de fontes.

- Dobradinhas: lista 'alliances' (com quem Solla soma na cidade) como bloco na seção de rede, insumo direto da abordagem.

- Investimentos: lista 'investments' (Solla, estado e federal, município e região; empenho ≠ pagamento) como bloco na conjuntura.

- Seções dinâmicas: numeração por posição; Sinais recentes e Demandas e visitas omitidas quando vazias (lacuna explícita só onde é produto).

Skill, changelog e testes unit cobrindo validador e builder.
2026-09-15 18:38:56 -03:00

134 lines
3.9 KiB
TypeScript

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',
tseCityCode: '31270',
zoneNumber: null,
tseZones: [12, 13],
lastUpdateAt: null,
},
electoral: {
candidateName: 'Jorge Solla',
candidateParty: 'PT',
series: [],
tally2022: null,
ticket2022: { president: null, governor: null },
rank2022: null,
},
goal: null,
pledges: {
declaredTotal: 0,
effectiveByScenario: {},
pledgeCount: 0,
missingEstimateCount: 0,
lastPledgeAt: null,
},
leaderships: { totalCount: 0, rows: [] },
advisors: [],
signals: { totalCount: 0, rows: [] },
activities: { upcoming: [], recent: [] },
conjuncture: null,
speeches: { totalCount: 0, rows: [] },
demands: { totalCount: 0, rows: [] },
demographics: null,
},
research,
emendas: {
status: 'gap',
reason: 'Chave ausente',
detail: null,
sourceUrl: null,
consultedAt: null,
},
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/emendas_web"')
})
it('prints the gaps as callouts and the limits list', () => {
expect(html).toContain('Lacuna explícita')
expect(html).toContain('Limites')
})
})
describe('renderReportMd', () => {
const md = renderReportMd(report)
it('carries the same sections as tables and headings', () => {
expect(md).toContain('## 1. Conta eleitoral completa')
expect(md).toContain('## 2. Concorrentes no município (federal e estadual)')
expect(md).not.toContain('## 5. Sinais recentes')
expect(md).not.toContain('Demandas e visitas')
expect(md).toContain('## 6. Atividade econômica (pesquisa)')
expect(md).toContain('## 7. Transporte e conexões (pesquisa)')
expect(md).toContain('## 11. Abordagem sugerida (pesquisa)')
expect(md).toContain('## 12. Fontes e limites')
expect(md).toContain('| Ano | Votos |')
})
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')
})
})