mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:53:05 +00:00
Add BahiaMap surfaces on nuclei list, detail, and geral dashboard with server-side choropleth aggregation, lazy geometry loading (B5 F1), and roadmap plans for A7 F4, B5 F3, and B6 scale debts.
206 lines
7.3 KiB
TypeScript
206 lines
7.3 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'
|
|
import { emptyNucleusChoroplethBundle } from '@/utilities/nucleusChoroplethTypes'
|
|
|
|
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'),
|
|
[],
|
|
emptyNucleusChoroplethBundle(),
|
|
)
|
|
|
|
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')
|
|
})
|
|
})
|