teqo/tests/unit/municipalityPortfolio.unit.spec.ts
Francisco Ignácio Fontoura Solla 812e3a0bee B34: editable município chips on the leaderships list.
The "Municípios" column was dead text; it is now chips that navigate to the
município, with a territory chip when a leadership covers a whole TI, and it is
editable in place. What picks inline vs. Drawer is the `pointer:` media query,
not viewport width: on a fine pointer the editor is always mounted (the "×" sits
at the chip's corner, revealed on hover/focus) and on a coarse pointer a tap
anywhere in the cell opens a Drawer with 44 px targets.

`AdvisorMunicipalityCell` is promoted to `shared/MunicipalityPortfolioCell` and
/campanha/assessores migrates onto it in the same change, so the second call site
is born with the first. One batch action only; the single-id advisor twin is
deleted with its int tests migrated. The anti-goal on always-mounted list inputs
is amended for chip-based relations on fine pointers, not reopened.

Also fixes a latent bug: `CampaignTableHead`'s `description` seam broke any RSC
caller, because its two helpers lived in a 'use client' module.
2026-07-27 22:35:19 -03:00

76 lines
2.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { citiesForTerritory } from '@/lib/bahiaTerritories'
import { municipalityCatalog } from '@/lib/municipalityCatalog'
import {
buildMunicipalityPortfolioChips,
catalogEntriesForTseZone,
searchMunicipalityPortfolio,
type MunicipalityPortfolioIndexEntry,
} from '@/lib/municipalityPortfolio'
const buildIndex = (): MunicipalityPortfolioIndexEntry[] =>
municipalityCatalog.map((entry, index) => ({
id: index + 1,
name: entry.name,
slug: entry.slug,
region: entry.region,
}))
/** The index is keyed by slug; `city` lives only in the catalog it is built from. */
const slugsForCities = (cities: readonly string[]): Set<string> =>
new Set(municipalityCatalog.filter((entry) => cities.includes(entry.city)).map((e) => e.slug))
describe('municipalityPortfolio', () => {
it('collapses a full identity territory into a single chip', () => {
const index = buildIndex()
const irece = slugsForCities(citiesForTerritory('Irecê'))
const assignedIds = index.filter((entry) => irece.has(entry.slug)).map((entry) => entry.id)
const chips = buildMunicipalityPortfolioChips(assignedIds, index)
expect(chips).toHaveLength(1)
expect(chips[0]).toMatchObject({ kind: 'territory', label: 'Irecê' })
})
it('keeps partial territory membership as municipality chips', () => {
const index = buildIndex()
const ireceSlugs = slugsForCities(['Irecê'])
const one = index.find((entry) => ireceSlugs.has(entry.slug))
expect(one).toBeTruthy()
const chips = buildMunicipalityPortfolioChips([one!.id], index)
expect(chips).toEqual([
{
kind: 'municipality',
key: `municipality:${one!.id}`,
label: one!.name,
municipalityId: one!.id,
slug: one!.slug,
},
])
})
it('drops an id the index does not know (no name, no link to render)', () => {
const index = buildIndex()
expect(buildMunicipalityPortfolioChips([999_999], index)).toEqual([])
})
it('resolves Salvador ZE to the zone municipality entry only', () => {
const entries = catalogEntriesForTseZone(3)
expect(entries).toHaveLength(1)
expect(entries[0]?.slug).toBe('salvador-ze-3')
})
it('searches municipalities, territories and zones', () => {
const index = buildIndex()
const municipalityHits = searchMunicipalityPortfolio('Feira', index, new Set())
expect(municipalityHits.some((hit) => hit.kind === 'municipality')).toBe(true)
const territoryHits = searchMunicipalityPortfolio('Irecê', index, new Set())
expect(territoryHits.some((hit) => hit.kind === 'territory' && hit.label === 'Irecê')).toBe(
true,
)
const zoneHits = searchMunicipalityPortfolio('ZE 3', index, new Set())
expect(zoneHits.some((hit) => hit.kind === 'zone' && hit.zoneNumber === 3)).toBe(true)
})
})