import { describe, expect, it } from 'vitest' import { municipalityCatalog } from '@/lib/municipalityCatalog' import { formatMunicipalityConcentrationHint } from '@/utilities/municipality/municipalityLabels' import { buildMunicipalityFilterHref, buildMunicipalityFilterOptionHref, formatMunicipalityActiveFiltersSummary, isMunicipalityColumnFilterActive, toggleMunicipalityExclusiveFilterValue, toggleMunicipalityMultiFilterValue, toggleMunicipalityPriorityFilter, type MunicipalityMultiFilterParam, } from '@/utilities/municipality/municipalityListFilters' import { buildMunicipalityListHref, buildMunicipalityListWhere, buildMunicipalitySortHref, formatMunicipalityListSortSummary, municipalityListSortLabels, parseMunicipalityListParams, parseMunicipalitySortValue, resolveMunicipalityListSort, serializeMunicipalitySortValue, } from '@/utilities/municipality/municipalityListUrl' import { formatMunicipalitySignalAgeLabel, isMunicipalitySignalCold, MUNICIPALITY_COLD_SIGNAL_DAYS, municipalitySignalAgeInDays, resolveMunicipalityLastSignalAt, } from '@/utilities/municipality/municipalitySignal' describe('municipality list sort params (A11 + B15 + E9)', () => { it('omits the default deficit/desc from the URL (E9 allocation queue order)', () => { const state = parseMunicipalityListParams({ sort: 'deficit', dir: 'desc' }) expect(state).toMatchObject({ sort: 'deficit', dir: 'desc' }) expect(resolveMunicipalityListSort(state)).toEqual({ sort: 'deficit', dir: 'desc' }) expect(buildMunicipalityListHref(state, 1)).toBe('/campanha/municipios') }) it('treats an empty query as deficit desc (biggest uncovered goal first)', () => { const state = parseMunicipalityListParams({}) expect(resolveMunicipalityListSort(state)).toEqual({ sort: 'deficit', dir: 'desc' }) expect(buildMunicipalityListHref(state, 1)).toBe('/campanha/municipios') }) it('keeps sort=votos in the URL now that it is no longer the default', () => { const state = parseMunicipalityListParams({ sort: 'votos', dir: 'desc' }) expect(buildMunicipalityListHref(state, 1)).toBe('/campanha/municipios?sort=votos') }) it('opens frescor on the coldest signal (desc default) and keeps asc explicit', () => { const base = parseMunicipalityListParams({}) expect(buildMunicipalitySortHref(base, 'frescor')).toBe('/campanha/municipios?sort=frescor') const cold = parseMunicipalityListParams({ sort: 'frescor' }) expect(resolveMunicipalityListSort(cold)).toEqual({ sort: 'frescor', dir: 'desc' }) expect(buildMunicipalitySortHref(cold, 'frescor')).toBe( '/campanha/municipios?sort=frescor&dir=asc', ) }) it('keeps sort=name when sorting alphabetically (dir=asc is the name default)', () => { const state = parseMunicipalityListParams({ sort: 'name', dir: 'asc' }) expect(state.sort).toBe('name') expect(state.dir).toBe('asc') expect(buildMunicipalityListHref(state, 1)).toBe('/campanha/municipios?sort=name') }) it('keeps sort=votos&dir=asc when ascending', () => { const state = parseMunicipalityListParams({ sort: 'votos', dir: 'asc' }) expect(state).toMatchObject({ sort: 'votos', dir: 'asc' }) expect(buildMunicipalityListHref(state, 1)).toBe('/campanha/municipios?sort=votos&dir=asc') }) it('toggles deficit from the default desc to asc', () => { const base = parseMunicipalityListParams({}) const href = buildMunicipalitySortHref(base, 'deficit') expect(href).toBe('/campanha/municipios?sort=deficit&dir=asc') }) it('switches from name back to the default deficit desc (omitted URL)', () => { const base = parseMunicipalityListParams({ sort: 'name' }) expect(buildMunicipalitySortHref(base, 'deficit')).toBe('/campanha/municipios') }) it('parses mobile Ordenar values with | and rejects junk', () => { expect(parseMunicipalitySortValue('votos|desc')).toEqual({ key: 'votos', dir: 'desc' }) expect(parseMunicipalitySortValue('name|asc')).toEqual({ key: 'name', dir: 'asc' }) expect(parseMunicipalitySortValue('priority|asc')).toBeNull() expect(parseMunicipalitySortValue('votos')).toBeNull() expect(serializeMunicipalitySortValue('votos', 'desc')).toBe('votos|desc') }) it('summarizes the active sort in plain language', () => { expect(formatMunicipalityListSortSummary('votos', 'desc')).toBe('Ordenado por 2022 ↓') expect(formatMunicipalityListSortSummary('votos', 'asc')).toBe('Ordenado por 2022 ↑') expect(formatMunicipalityListSortSummary('name', 'asc')).toBe('Ordenado por nome (A–Z)') expect(formatMunicipalityListSortSummary('region', 'asc')).toBe('Ordenado por Território ↑') expect(formatMunicipalityListSortSummary('deficit', 'desc')).toBe( 'Ordenado por Cobertura (maior déficit primeiro)', ) expect(formatMunicipalityListSortSummary('frescor', 'desc')).toBe( 'Ordenado por frescor (sinal mais frio primeiro)', ) }) it('formats the concentração header hint once for tooltip/caption', () => { expect(formatMunicipalityConcentrationHint()).toMatch(/votação estadual/) expect(formatMunicipalityConcentrationHint()).toMatch(/435/) expect(formatMunicipalityConcentrationHint()).not.toMatch(/^%/) expect(formatMunicipalityConcentrationHint(12)).toMatch(/12/) }) it('disambiguates Assessores (coverage) from goal Cobertura (deficit)', () => { expect(municipalityListSortLabels.coverage).toBe('Assessores') expect(municipalityListSortLabels.deficit).toBe('Cobertura') expect(formatMunicipalityListSortSummary('coverage', 'asc')).toBe('Ordenado por Assessores ↑') }) it('opens the classe sort on reduto (desc default) and names the direction', () => { const base = parseMunicipalityListParams({}) expect(buildMunicipalitySortHref(base, 'classe')).toBe('/campanha/municipios?sort=classe') const byClass = parseMunicipalityListParams({ sort: 'classe' }) expect(resolveMunicipalityListSort(byClass)).toEqual({ sort: 'classe', dir: 'desc' }) expect(formatMunicipalityListSortSummary('classe', 'desc')).toBe( 'Ordenado por classe (reduto primeiro)', ) expect(formatMunicipalityListSortSummary('classe', 'asc')).toBe( 'Ordenado por classe (marginal primeiro)', ) }) }) describe('E10 classe territorial filter (derived, not a Payload constraint)', () => { it('parses repeated ?class= params and keeps them out of the where clause', () => { const state = parseMunicipalityListParams({ class: ['reduto', 'expansao', 'reduto'] }) expect(state.classes).toEqual(['reduto', 'expansao']) expect(isMunicipalityColumnFilterActive(state, 'class')).toBe(true) // The class lives in the committed TSE artifact, so `where` must stay clean — // `municipalityPageData` applies it in memory over the unpaginated scope. expect(buildMunicipalityListWhere(state)).toEqual({}) expect(buildMunicipalityFilterHref(state)).toBe( '/campanha/municipios?class=reduto&class=expansao', ) }) it('discards unknown classes and treats "todas" as absent', () => { expect(parseMunicipalityListParams({ class: 'perdida' }).classes).toBeUndefined() const all = parseMunicipalityListParams({ class: ['reduto', 'expansao', 'manutencao', 'marginal', 'sem_base'], }) expect(all.classes).toBeUndefined() expect(isMunicipalityColumnFilterActive(all, 'class')).toBe(false) expect(formatMunicipalityActiveFiltersSummary(all)).toBeNull() // Ticking the last unchecked box lands on that same canonical state. const allButOne = parseMunicipalityListParams({ class: ['reduto', 'expansao', 'manutencao', 'marginal'], }) expect( toggleMunicipalityMultiFilterValue(allButOne, 'class', 'sem_base').classes, ).toBeUndefined() }) it('names the class in the active-filters summary', () => { expect( formatMunicipalityActiveFiltersSummary( parseMunicipalityListParams({ class: ['reduto', 'expansao'] }), ), ).toBe('Classe reduto, expansão') }) it('toggles a class in and out while preserving the other filters', () => { const state = parseMunicipalityListParams({ region: 'Irecê', sort: 'votos' }) const on = toggleMunicipalityMultiFilterValue(state, 'class', 'reduto') expect(on.classes).toEqual(['reduto']) expect(buildMunicipalityFilterHref(on)).toBe( '/campanha/municipios?region=Irec%C3%AA&class=reduto&sort=votos', ) expect(toggleMunicipalityMultiFilterValue(on, 'class', 'reduto').classes).toBeUndefined() }) }) describe('E14 nível de envolvimento (stored: sort and filter both reach the query)', () => { it('opens the nivel sort on N4 (desc default) and names the direction', () => { const base = parseMunicipalityListParams({}) expect(buildMunicipalitySortHref(base, 'nivel')).toBe('/campanha/municipios?sort=nivel') const byLevel = parseMunicipalityListParams({ sort: 'nivel' }) expect(resolveMunicipalityListSort(byLevel)).toEqual({ sort: 'nivel', dir: 'desc' }) expect(formatMunicipalityListSortSummary('nivel', 'desc')).toBe( 'Ordenado por nível (N4 primeiro)', ) expect(formatMunicipalityListSortSummary('nivel', 'asc')).toBe( 'Ordenado por nível (N0 primeiro)', ) }) it('constrains the query by the selected levels', () => { const state = parseMunicipalityListParams({ level: ['n3', 'n4', 'n3'] }) expect(state.levels).toEqual(['n3', 'n4']) expect(isMunicipalityColumnFilterActive(state, 'level')).toBe(true) expect(buildMunicipalityListWhere(state)).toEqual({ and: [{ engagementLevel: { in: ['n3', 'n4'] } }], }) expect(buildMunicipalityFilterHref(state)).toBe('/campanha/municipios?level=n3&level=n4') }) /** * "Sem nível" is the triage queue, so it must be selectable — and absence * can't ride inside an `in`, which is why it becomes its own OR branch. */ it('expresses "sem nível" as absence, alone or beside real levels', () => { expect(buildMunicipalityListWhere(parseMunicipalityListParams({ level: 'sem_nivel' }))).toEqual( { and: [{ engagementLevel: { exists: false } }] }, ) expect( buildMunicipalityListWhere(parseMunicipalityListParams({ level: ['n0', 'sem_nivel'] })), ).toEqual({ and: [{ or: [{ engagementLevel: { in: ['n0'] } }, { engagementLevel: { exists: false } }] }], }) }) it('discards unknown levels and treats "todos" as absent', () => { expect(parseMunicipalityListParams({ level: 'n9' }).levels).toBeUndefined() const all = parseMunicipalityListParams({ level: ['n0', 'n1', 'n2', 'n3', 'n4', 'sem_nivel'], }) expect(all.levels).toBeUndefined() expect(buildMunicipalityListWhere(all)).toEqual({}) expect(isMunicipalityColumnFilterActive(all, 'level')).toBe(false) const allButOne = parseMunicipalityListParams({ level: ['n0', 'n1', 'n2', 'n3', 'n4'], }) expect( toggleMunicipalityMultiFilterValue(allButOne, 'level', 'sem_nivel').levels, ).toBeUndefined() }) it('names the level in the active-filters summary', () => { expect( formatMunicipalityActiveFiltersSummary( parseMunicipalityListParams({ level: ['n4', 'sem_nivel'] }), ), ).toBe('Nível N4, sem nível') }) }) describe('municipality list header filters (B16+)', () => { it('toggles multi region OR filters and preserves sort/dir', () => { const state = parseMunicipalityListParams({ page: '3', sort: 'votos', dir: 'asc', priority: 'alta', }) const withRegion = toggleMunicipalityMultiFilterValue(state, 'region', 'Irecê') expect(withRegion.regions).toEqual(['Irecê']) expect(withRegion.page).toBe(1) expect(buildMunicipalityFilterHref(withRegion)).toBe( '/campanha/municipios?region=Irec%C3%AA&priority=alta&sort=votos&dir=asc', ) const withTwo = toggleMunicipalityMultiFilterValue(withRegion, 'region', 'Recôncavo') expect(withTwo.regions).toEqual(['Irecê', 'Recôncavo']) expect(buildMunicipalityListHref(withTwo, 1)).toContain('region=Irec') expect(buildMunicipalityListHref(withTwo, 1)).toContain('region=Rec') const cleared = toggleMunicipalityMultiFilterValue(withRegion, 'region', 'Irecê') expect(cleared.regions).toBeUndefined() }) it('toggles the Prioritária filter in and out of the URL, keeping other filters', () => { const state = parseMunicipalityListParams({ region: 'Irecê' }) const on = toggleMunicipalityPriorityFilter(state) expect(on.priority).toBe('alta') expect(buildMunicipalityFilterHref(on)).toBe( '/campanha/municipios?region=Irec%C3%AA&priority=alta', ) const off = toggleMunicipalityPriorityFilter(on) expect(off.priority).toBeUndefined() expect(buildMunicipalityFilterHref(off)).toBe('/campanha/municipios?region=Irec%C3%AA') }) it('treats every trend selected as "todas" (no narrowing constraint)', () => { const one = parseMunicipalityListParams({ trend: 'favoravel' }) expect(one.trends).toEqual(['favoravel']) expect(isMunicipalityColumnFilterActive(one, 'trend')).toBe(true) expect(buildMunicipalityListWhere(one)).toEqual({ and: [{ 'politicalTrend.status': { in: ['favoravel'] } }], }) // "Todas" has a single encoding — absent — so the URL never carries params // that filter nothing while the funnel reports itself inactive. const all = parseMunicipalityListParams({ trend: ['favoravel', 'neutra', 'desfavoravel'] }) expect(all.trends).toBeUndefined() expect(isMunicipalityColumnFilterActive(all, 'trend')).toBe(false) expect(buildMunicipalityListWhere(all)).toEqual({}) expect(formatMunicipalityActiveFiltersSummary(all)).toBeNull() expect(buildMunicipalityFilterHref(all)).toBe('/campanha/municipios') // Ticking the last unchecked box lands on that same canonical state. const twoSelected = parseMunicipalityListParams({ trend: ['favoravel', 'neutra'] }) expect( toggleMunicipalityMultiFilterValue(twoSelected, 'trend', 'desfavoravel').trends, ).toBeUndefined() const none = toggleMunicipalityMultiFilterValue(one, 'trend', 'favoravel') expect(none.trends).toBeUndefined() expect(buildMunicipalityFilterHref(none)).toBe('/campanha/municipios') }) it('toggles coverage by re-clicking the active value', () => { const state = parseMunicipalityListParams({}) const on = toggleMunicipalityExclusiveFilterValue(state, 'coverage', 'com_assessor') expect(on.coverage).toBe('com_assessor') const off = toggleMunicipalityExclusiveFilterValue(on, 'coverage', 'com_assessor') expect(off.coverage).toBeUndefined() }) it('discards invalid filter values', () => { const state = parseMunicipalityListParams({}) expect( toggleMunicipalityMultiFilterValue(state, 'region', 'NotATerritory').regions, ).toBeUndefined() expect( toggleMunicipalityExclusiveFilterValue(state, 'coverage', 'maybe').coverage, ).toBeUndefined() expect(toggleMunicipalityMultiFilterValue(state, 'trend', 'alta').trends).toBeUndefined() expect(parseMunicipalityListParams({ slug: 'not-a-slug', advisor: 'x' }).slugs).toBeUndefined() expect(parseMunicipalityListParams({ advisor: 'x' }).advisors).toBeUndefined() }) it('parses repeated advisor and slug params as OR filters', () => { const slug = municipalityCatalog[0]?.slug expect(slug).toBeTruthy() const state = parseMunicipalityListParams({ slug: [slug!, 'also-invalid'], advisor: ['12', '12', '7'], }) expect(state.slugs).toEqual([slug]) expect(state.advisors).toEqual([7, 12]) }) it('summarizes active filters for the slim bar', () => { expect(formatMunicipalityActiveFiltersSummary(parseMunicipalityListParams({}))).toBeNull() expect( formatMunicipalityActiveFiltersSummary( parseMunicipalityListParams({ region: 'Irecê', priority: 'alta', q: 'feira' }), ), ).toBe('Prioritária · Irecê · Busca "feira"') }) }) describe('E9 frescor do sinal', () => { const now = new Date('2026-07-24T12:00:00.000Z') it('takes the latest of the staff update and the pledge dates', () => { expect( resolveMunicipalityLastSignalAt('2026-07-01T00:00:00.000Z', '2026-07-10T00:00:00.000Z'), ).toBe('2026-07-10T00:00:00.000Z') expect( resolveMunicipalityLastSignalAt('2026-07-20T00:00:00.000Z', '2026-07-10T00:00:00.000Z'), ).toBe('2026-07-20T00:00:00.000Z') }) it('falls back to whichever side exists, and stays null when neither does', () => { expect(resolveMunicipalityLastSignalAt(null, '2026-07-10T00:00:00.000Z')).toBe( '2026-07-10T00:00:00.000Z', ) expect(resolveMunicipalityLastSignalAt('2026-07-10T00:00:00.000Z', null)).toBe( '2026-07-10T00:00:00.000Z', ) expect(resolveMunicipalityLastSignalAt(null, null)).toBeNull() }) it('floors the age in days and never goes negative on a future timestamp', () => { expect(municipalitySignalAgeInDays('2026-07-24T00:00:00.000Z', now)).toBe(0) expect(municipalitySignalAgeInDays('2026-07-23T00:00:00.000Z', now)).toBe(1) expect(municipalitySignalAgeInDays('2026-07-01T00:00:00.000Z', now)).toBe(23) expect(municipalitySignalAgeInDays('2026-08-01T00:00:00.000Z', now)).toBe(0) expect(municipalitySignalAgeInDays(null, now)).toBeNull() }) it('treats "no signal at all" as cold, alongside anything past the threshold', () => { expect(isMunicipalitySignalCold(null)).toBe(true) expect(isMunicipalitySignalCold(MUNICIPALITY_COLD_SIGNAL_DAYS)).toBe(true) expect(isMunicipalitySignalCold(MUNICIPALITY_COLD_SIGNAL_DAYS - 1)).toBe(false) expect(isMunicipalitySignalCold(0)).toBe(false) }) it('labels the age in dense pt-BR copy', () => { expect(formatMunicipalitySignalAgeLabel(null)).toBe('Sem sinal') expect(formatMunicipalitySignalAgeLabel(0)).toBe('hoje') expect(formatMunicipalitySignalAgeLabel(1)).toBe('há 1 dia') expect(formatMunicipalitySignalAgeLabel(30)).toBe('há 30 dias') }) }) describe('buildMunicipalityFilterOptionHref (B16+ fast path)', () => { const [firstSlug, secondSlug, thirdSlug] = municipalityCatalog.map((entry) => entry.slug) const cases: Array<{ name: string state: Parameters[0] param: MunicipalityMultiFilterParam value: string }> = [ { name: 'adds a slug', state: parseMunicipalityListParams({}), param: 'slug', value: firstSlug!, }, { name: 'removes a selected slug', state: parseMunicipalityListParams({ slug: [firstSlug!, secondSlug!] }), param: 'slug', value: secondSlug!, }, { name: 'keeps other filters and resets the page', state: parseMunicipalityListParams({ q: 'santa', page: '3', sort: 'votos', slug: [thirdSlug!], priority: 'alta', }), param: 'slug', value: firstSlug!, }, { name: 'sorts advisor ids numerically', state: parseMunicipalityListParams({ advisor: ['20'] }), param: 'advisor', value: '5', }, { name: 'adds a region', state: parseMunicipalityListParams({ region: ['Chapada Diamantina'] }), param: 'region', value: 'Sertão do São Francisco', }, { name: 'collapses the full trend set to "todas"', state: parseMunicipalityListParams({ trend: ['favoravel', 'neutra'] }), param: 'trend', value: 'desfavoravel', }, { name: 'removes a selected trend', state: parseMunicipalityListParams({ trend: ['favoravel', 'neutra'] }), param: 'trend', value: 'neutra', }, { name: 'adds "sem nível" beside a level', state: parseMunicipalityListParams({ level: ['n0'] }), param: 'level', value: 'sem_nivel', }, { name: 'collapses the full level set to "todos"', state: parseMunicipalityListParams({ level: ['n0', 'n1', 'n2', 'n3', 'n4'] }), param: 'level', value: 'sem_nivel', }, ] it.each(cases)('matches the canonical toggle+href path: $name', ({ state, param, value }) => { expect(buildMunicipalityFilterOptionHref(state, param, value)).toBe( buildMunicipalityFilterHref(toggleMunicipalityMultiFilterValue(state, param, value)), ) }) }) describe('municipality list relation filters (B176)', () => { it('canonicalizes stateDeputy/leadership ids ascending with the sentinel last', () => { const state = parseMunicipalityListParams({ stateDeputy: ['5', 'sem_dobradinha', '3', '5', 'abc'], leadership: ['sem_lideranca', '2', '8'], }) expect(state.stateDeputies).toEqual([3, 5, 'sem_dobradinha']) expect(state.leaderships).toEqual([2, 8, 'sem_lideranca']) expect(buildMunicipalityListHref(state, 1)).toBe( '/campanha/municipios?stateDeputy=3&stateDeputy=5&stateDeputy=sem_dobradinha&leadership=2&leadership=8&leadership=sem_lideranca', ) }) it('parses parties by insertion order, deduped, with the sentinel last', () => { const state = parseMunicipalityListParams({ party: ['PSD', 'PT', 'PSD', 'sem_partido'], }) expect(state.parties).toEqual(['PSD', 'PT', 'sem_partido']) expect(buildMunicipalityListHref(state, 1)).toBe( '/campanha/municipios?party=PSD&party=PT&party=sem_partido', ) // The sentinel canonicalizes LAST regardless of where it appeared, so one // value set maps to one URL (same contract as the relationship dims). const sentinelFirst = parseMunicipalityListParams({ party: ['sem_partido', 'PT', 'PT'] }) expect(sentinelFirst.parties).toEqual(['PT', 'sem_partido']) expect(buildMunicipalityListHref(sentinelFirst, 1)).toBe( '/campanha/municipios?party=PT&party=sem_partido', ) }) it('serializes the three dimensions together with the rest of the state', () => { const state = parseMunicipalityListParams({ region: 'Irecê', stateDeputy: '4', leadership: ['sem_lideranca', '9'], party: 'PCdoB', }) const href = buildMunicipalityListHref(state, 1) expect(href).toContain('stateDeputy=4') expect(href).toContain('leadership=9&leadership=sem_lideranca') expect(href).toContain('party=PCdoB') // URL → raw plain-record params → parse, mirroring what the page's // `searchParams` object carries into `parseMunicipalityListParams`. const query = new URLSearchParams(href.split('?')[1]) const raw: Record = {} for (const key of query.keys()) { const values = query.getAll(key) raw[key] = values.length === 1 ? values[0] : values } expect(parseMunicipalityListParams(raw)).toEqual(state) }) it('builds the direct dobradinha where (in / exists), no catalog needed', () => { expect(buildMunicipalityListWhere(parseMunicipalityListParams({ stateDeputy: '4' }))).toEqual({ and: [{ stateDeputies: { in: [4] } }], }) expect( buildMunicipalityListWhere(parseMunicipalityListParams({ stateDeputy: 'sem_dobradinha' })), ).toEqual({ and: [{ stateDeputies: { exists: false } }] }) expect( buildMunicipalityListWhere( parseMunicipalityListParams({ stateDeputy: ['4', 'sem_dobradinha'] }), ), ).toEqual({ and: [{ or: [{ stateDeputies: { in: [4] } }, { stateDeputies: { exists: false } }] }], }) }) const catalog = { leadershipMunicipalityIDsByLeadership: new Map([ [1, [10, 11]], [2, [12]], ]), allLeadershipMunicipalityIDs: new Set([10, 11, 12]), stateDeputyIDsByParty: new Map([ ['PT', [4, 5]], ['PSD', [6]], ]), allPartyStateDeputyIDs: new Set([4, 5, 6]), } it('builds the reverse leadership where from the catalog (in / not_in)', () => { expect( buildMunicipalityListWhere(parseMunicipalityListParams({ leadership: ['1', '2'] }), catalog), ).toEqual({ and: [{ id: { in: [10, 11, 12] } }] }) expect( buildMunicipalityListWhere( parseMunicipalityListParams({ leadership: 'sem_lideranca' }), catalog, ), ).toEqual({ and: [{ id: { not_in: [10, 11, 12] } }] }) }) it('builds the cross party where from the catalog (in / not_in)', () => { expect( buildMunicipalityListWhere(parseMunicipalityListParams({ party: ['PT'] }), catalog), ).toEqual({ and: [{ stateDeputies: { in: [4, 5] } }] }) expect( buildMunicipalityListWhere(parseMunicipalityListParams({ party: 'sem_partido' }), catalog), ).toEqual({ and: [{ stateDeputies: { not_in: [4, 5, 6] } }] }) expect( buildMunicipalityListWhere( parseMunicipalityListParams({ party: ['PT', 'sem_partido'] }), catalog, ), ).toEqual({ and: [{ or: [{ stateDeputies: { in: [4, 5] } }, { stateDeputies: { not_in: [4, 5, 6] } }] }], }) }) it('fails closed: leadership/party filter without the catalog throws', () => { expect(() => buildMunicipalityListWhere(parseMunicipalityListParams({ leadership: '1' })), ).toThrow(/missing relation catalog/) expect(() => buildMunicipalityListWhere(parseMunicipalityListParams({ party: 'PT' }))).toThrow( /missing relation catalog/, ) // Dobradinha (direct) never needs the catalog. expect(() => buildMunicipalityListWhere(parseMunicipalityListParams({ stateDeputy: '4' })), ).not.toThrow() }) it('summarizes the three dimensions in the active-filters summary', () => { expect( formatMunicipalityActiveFiltersSummary( parseMunicipalityListParams({ stateDeputy: ['1', '2'] }), ), ).toBe('2 dobradinhas') expect( formatMunicipalityActiveFiltersSummary( parseMunicipalityListParams({ stateDeputy: ['1', 'sem_dobradinha'] }), ), ).toBe('1 dobradinha + sem dobradinha') expect( formatMunicipalityActiveFiltersSummary( parseMunicipalityListParams({ leadership: 'sem_lideranca' }), ), ).toBe('Sem liderança') expect( formatMunicipalityActiveFiltersSummary( parseMunicipalityListParams({ party: ['PT', 'PSD', 'sem_partido'] }), ), ).toBe('Partido PT, PSD + sem partido') }) it('keeps the B176 toggles on the canonical fast-path href', () => { const state = parseMunicipalityListParams({ party: 'PT' }) expect(buildMunicipalityFilterOptionHref(state, 'stateDeputy', '5')).toBe( buildMunicipalityFilterHref(toggleMunicipalityMultiFilterValue(state, 'stateDeputy', '5')), ) expect(buildMunicipalityFilterOptionHref(state, 'leadership', 'sem_lideranca')).toBe( buildMunicipalityFilterHref( toggleMunicipalityMultiFilterValue(state, 'leadership', 'sem_lideranca'), ), ) expect(buildMunicipalityFilterOptionHref(state, 'party', 'PSD')).toBe( buildMunicipalityFilterHref(toggleMunicipalityMultiFilterValue(state, 'party', 'PSD')), ) }) })