teqo/tests/unit/campaignComponents.unit.spec.ts
Francisco Ignácio Fontoura Solla e995aefbd4 Add action-plan agenda vertical for /campanha/planos.
Ship C3 mobilization events with the actionPlan collection, list/detail/forms UI, upcoming-event blocks, access control, and migration.
2026-07-18 20:56:20 -03:00

204 lines
7.2 KiB
TypeScript

import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { createElement } from 'react'
import { renderToStaticMarkup } from 'react-dom/server'
import { describe, expect, it } from 'vitest'
import { CampaignScopeBadge } from '@/components/campaign/CampaignScopeBadge'
import { LeadershipRow } from '@/components/campaign/LeadershipRow'
import { NucleusCard } from '@/components/campaign/NucleusCard'
import { SupportStatusBadge } from '@/components/campaign/SupportStatusBadge'
import { TseZoneBadge } from '@/components/campaign/TseZoneBadge'
import { VoteEstimateCard } from '@/components/campaign/VoteEstimateCard'
import { Progress } from '@/components/ui/Progress'
import { Toggle } from '@/components/ui/Toggle'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/ToggleGroup'
import { buildGeneralDashboardViewModel } from '@/utilities/campaignDashboardViewModels'
describe('campaign visual foundation', () => {
it('exposes the current progress value to assistive technology', () => {
const html = renderToStaticMarkup(createElement(Progress, { value: 42 }))
expect(html).toContain('role="progressbar"')
expect(html).toContain('aria-valuenow="42"')
})
it.each(['sm', 'default', 'lg'] as const)(
'keeps the %s toggle at least 44 by 44 pixels',
(size) => {
const html = renderToStaticMarkup(createElement(Toggle, { size }, 'Filtro'))
expect(html).toContain('min-h-11')
expect(html).toContain('min-w-11')
},
)
it('keeps toggle group items at least 44 by 44 pixels', () => {
const html = renderToStaticMarkup(
createElement(
ToggleGroup,
{ type: 'single' },
createElement(ToggleGroupItem, { value: 'engajado' }, 'Engajado'),
),
)
expect(html).toContain('data-slot="toggle-group-item"')
expect(html).toContain('min-h-11')
expect(html).toContain('min-w-11')
})
it('renders campaign scope with a visible, accessible label', () => {
const html = renderToStaticMarkup(
createElement(CampaignScopeBadge, null, '3 núcleos sob sua coordenação'),
)
expect(html).toContain('3 núcleos sob sua coordenação')
expect(html).toContain('data-scope="campaign"')
})
it('keeps the authenticated shell fixed while its content pane scrolls', () => {
const layoutSource = readFileSync(
resolve(process.cwd(), 'src/app/(campaign)/campanha/(app)/layout.tsx'),
'utf8',
)
const sidebarSource = readFileSync(
resolve(process.cwd(), 'src/components/campaign/CampaignSidebar.tsx'),
'utf8',
)
expect(layoutSource).toContain('className="h-svh min-h-0 overflow-hidden"')
expect(layoutSource).toContain('data-slot="campaign-content-scroll"')
expect(layoutSource).toContain('min-h-0 flex-1 overflow-y-auto overscroll-contain')
expect(sidebarSource).toContain('h-svh shrink-0')
})
it('uses light sidebar tokens and readable logout states', () => {
const styles = readFileSync(resolve(process.cwd(), 'src/app/(frontend)/styles.css'), 'utf8')
const sidebarSource = readFileSync(
resolve(process.cwd(), 'src/components/campaign/CampaignSidebar.tsx'),
'utf8',
)
expect(styles).toContain('--sidebar: #fafaf9')
expect(styles).toContain('--sidebar-foreground: #1c1917')
expect(styles).toContain('--sidebar-accent-foreground: #1c1917')
expect(sidebarSource).toContain('text-sidebar-foreground')
expect(sidebarSource).toContain('hover:text-sidebar-accent-foreground')
})
it.each([
['engajado', 'Engajado'],
['a_abordar', 'A abordar'],
['em_disputa', 'Em disputa'],
['negativo', 'Negativo'],
] as const)('renders the %s support status with text and semantic state', (status, label) => {
const html = renderToStaticMarkup(createElement(SupportStatusBadge, { status }))
expect(html).toContain(label)
expect(html).toContain(`data-support-status="${status}"`)
})
it('aggregates every typed dashboard support status exhaustively', () => {
const view = buildGeneralDashboardViewModel(
[],
[
{ nucleusId: 31, supportStatus: 'engajado' },
{ nucleusId: 31, supportStatus: 'a_abordar' },
{ nucleusId: 31, supportStatus: 'em_disputa' },
{ nucleusId: 31, supportStatus: 'negativo' },
],
0,
new Date('2026-07-18T12:00:00-03:00'),
[],
)
expect(view.supportCounts).toEqual({
engajado: 1,
a_abordar: 1,
em_disputa: 1,
negativo: 1,
})
})
it('qualifies TSE zones instead of showing an ambiguous number', () => {
const html = renderToStaticMarkup(createElement(TseZoneBadge, { zoneNumber: 12 }))
expect(html).toContain('ZE 12')
expect(html).toContain('aria-label="Zona Eleitoral TSE 12"')
})
it('keeps the confirmed estimate primary and a proposal secondary', () => {
const html = renderToStaticMarkup(
createElement(VoteEstimateCard, {
confirmedEstimate: 1200,
confirmedBy: 'João S.',
confirmedAt: '12/07',
proposedEstimate: 1450,
proposedBy: 'Maria A.',
}),
)
expect(html).toContain('1.200 votos')
expect(html).toContain('Confirmada')
expect(html).toContain('Sugestão pendente')
expect(html).toContain('1.450 votos')
expect(html.indexOf('1.200 votos')).toBeLessThan(html.indexOf('1.450 votos'))
expect(html).toContain('data-slot="alert"')
expect(html).not.toContain('text-3xl')
})
it('renders nucleus territory, TSE zones, cadence, and leadership summary', () => {
const html = renderToStaticMarkup(
createElement(NucleusCard, {
name: 'Quilombo Rio das Rãs',
territory: 'Comunidade · Bom Jesus da Lapa',
tseZones: [12],
confirmedVoteEstimate: 1200,
hasPendingEstimate: true,
lastUpdateLabel: 'Última atualização há 10 dias',
isUpdateOverdue: true,
leadershipCounts: { engaged: 5, toApproach: 3, disputed: 1 },
}),
)
expect(html).toContain('Quilombo Rio das Rãs')
expect(html).toContain('Comunidade · Bom Jesus da Lapa')
expect(html).toContain('ZE 12')
expect(html).toContain('1.200 votos')
expect(html).toContain('Sugestão pendente')
expect(html).toContain('5 engajadas')
expect(html).toContain('Atualização atrasada')
})
it('makes missing TSE coverage visible as incomplete data', () => {
const html = renderToStaticMarkup(
createElement(NucleusCard, {
name: 'Núcleo em mapeamento',
territory: 'Região · Bahia',
tseZones: [],
}),
)
expect(html).toContain('Sem Zona TSE')
})
it('renders leadership identity with avatar fallback and support status', () => {
const html = renderToStaticMarkup(
createElement(LeadershipRow, {
name: 'Maria Aparecida',
phone: '71 97654-3210',
sector: 'Religioso',
supportStatus: 'engajado',
href: '/campanha/nucleos/centro?tab=leaderships&leadership=31',
rowId: 'leadership-row-31',
}),
)
expect(html).toContain('MA')
expect(html).toContain('Maria Aparecida')
expect(html).toContain('71 97654-3210')
expect(html).toContain('Religioso')
expect(html).toContain('Engajado')
expect(html).toContain('href="/campanha/nucleos/centro?tab=leaderships')
})
})