mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
151 lines
6.9 KiB
TypeScript
151 lines
6.9 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import type { Municipality } from '@/payload-types'
|
|
import {
|
|
CITY_MUNICIPALITY_ID,
|
|
buildCityMunicipalityDoc,
|
|
cityMatchesFilter,
|
|
cityTerritorialClass,
|
|
insertCityAtNativeSortPosition,
|
|
} from '@/utilities/municipality/municipalityCityRow'
|
|
import type { MunicipalityListState } from '@/utilities/municipality/municipalityListUrl'
|
|
import type { MunicipalityTerritorialClass } from '@/utilities/municipality/municipalityTerritorialClass'
|
|
|
|
/**
|
|
* B178 — the city row of the municipality list behaves like a normal entity:
|
|
* `cityMatchesFilter` mirrors `buildMunicipalityListWhere` semantics over the
|
|
* city's virtual values, and `insertCityAtNativeSortPosition` places it under
|
|
* native sorts without re-sorting the SQL-ordered docs.
|
|
*/
|
|
|
|
const emptyState = (): MunicipalityListState => ({ page: 1 })
|
|
|
|
describe('cityMatchesFilter', () => {
|
|
it('selects the city in the default recorte', () => {
|
|
expect(cityMatchesFilter(emptyState())).toBe(true)
|
|
})
|
|
|
|
it('matches the free-text search the same way the DB name contains does', () => {
|
|
expect(cityMatchesFilter({ ...emptyState(), q: 'salvador' })).toBe(true)
|
|
expect(cityMatchesFilter({ ...emptyState(), q: 'Salvador' })).toBe(true)
|
|
expect(cityMatchesFilter({ ...emptyState(), q: 'cidade' })).toBe(true)
|
|
expect(cityMatchesFilter({ ...emptyState(), q: 'são paulo' })).toBe(false)
|
|
// A bare zone number is a zone search — the city has no zoneNumber.
|
|
expect(cityMatchesFilter({ ...emptyState(), q: '3' })).toBe(false)
|
|
})
|
|
|
|
it('matches the region filter with its own territory', () => {
|
|
expect(cityMatchesFilter({ ...emptyState(), regions: ['Metropolitano de Salvador'] })).toBe(
|
|
true,
|
|
)
|
|
expect(cityMatchesFilter({ ...emptyState(), regions: ['Chapada Diamantina'] })).toBe(false)
|
|
})
|
|
|
|
it('matches the slug filter only for its own slug', () => {
|
|
expect(cityMatchesFilter({ ...emptyState(), slugs: ['salvador'] })).toBe(true)
|
|
expect(cityMatchesFilter({ ...emptyState(), slugs: ['salvador-ze-1'] })).toBe(false)
|
|
})
|
|
|
|
it('never matches filters the city has no value for', () => {
|
|
expect(cityMatchesFilter({ ...emptyState(), advisors: [1] })).toBe(false)
|
|
expect(cityMatchesFilter({ ...emptyState(), priority: 'alta' })).toBe(false)
|
|
expect(cityMatchesFilter({ ...emptyState(), trends: ['favoravel'] })).toBe(false)
|
|
expect(cityMatchesFilter({ ...emptyState(), levels: ['n0'] })).toBe(false)
|
|
expect(cityMatchesFilter({ ...emptyState(), stateDeputies: [1] })).toBe(false)
|
|
expect(cityMatchesFilter({ ...emptyState(), leaderships: [1] })).toBe(false)
|
|
expect(cityMatchesFilter({ ...emptyState(), parties: ['PT'] })).toBe(false)
|
|
})
|
|
|
|
it('matches the absence sentinels like a municipality without those links', () => {
|
|
expect(cityMatchesFilter({ ...emptyState(), coverage: 'sem_assessor' })).toBe(true)
|
|
expect(cityMatchesFilter({ ...emptyState(), coverage: 'com_assessor' })).toBe(false)
|
|
expect(cityMatchesFilter({ ...emptyState(), levels: ['sem_nivel'] })).toBe(true)
|
|
expect(cityMatchesFilter({ ...emptyState(), stateDeputies: ['sem_dobradinha'] })).toBe(true)
|
|
expect(cityMatchesFilter({ ...emptyState(), leaderships: ['sem_lideranca'] })).toBe(true)
|
|
expect(cityMatchesFilter({ ...emptyState(), parties: ['sem_partido'] })).toBe(true)
|
|
// A mixed selection still fails: named values cannot be satisfied.
|
|
expect(cityMatchesFilter({ ...emptyState(), levels: ['n0', 'sem_nivel'] })).toBe(false)
|
|
})
|
|
|
|
it('matches the class filter only for its own aggregate class', () => {
|
|
const ownClass = cityTerritorialClass().class
|
|
expect(cityMatchesFilter({ ...emptyState(), classes: [ownClass] })).toBe(true)
|
|
const allClasses: MunicipalityTerritorialClass[] = [
|
|
'reduto',
|
|
'expansao',
|
|
'manutencao',
|
|
'marginal',
|
|
'sem_base',
|
|
]
|
|
for (const other of allClasses.filter((value) => value !== ownClass)) {
|
|
expect(cityMatchesFilter({ ...emptyState(), classes: [other] })).toBe(false)
|
|
}
|
|
})
|
|
})
|
|
|
|
const cityDoc = buildCityMunicipalityDoc()
|
|
|
|
describe('buildCityMunicipalityDoc', () => {
|
|
it('uses a sentinel id and virtual values only', () => {
|
|
expect(cityDoc.id).toBe(CITY_MUNICIPALITY_ID)
|
|
expect(cityDoc.slug).toBe('salvador')
|
|
expect(cityDoc.name).toBe('Salvador (cidade)')
|
|
expect(cityDoc.advisors).toEqual([])
|
|
expect(cityDoc.stateDeputies).toEqual([])
|
|
expect(cityDoc.engagementLevel).toBeNull()
|
|
expect(cityDoc.expectedVotes).toBeUndefined()
|
|
expect(cityDoc.lastUpdateAt).toBeNull()
|
|
})
|
|
})
|
|
|
|
const doc = (name: string, region: string) =>
|
|
({ slug: `slug-${name}`, name, region }) as Municipality
|
|
|
|
describe('insertCityAtNativeSortPosition', () => {
|
|
const docs = [
|
|
doc('Alagoinhas', 'Litoral Norte e Agreste Baiano'),
|
|
doc('Salvador — ZE 1', 'Metropolitano de Salvador'),
|
|
doc('Salvador — ZE 19', 'Metropolitano de Salvador'),
|
|
doc('Teixeira de Freitas', 'Extremo Sul'),
|
|
]
|
|
|
|
it('places the city inside the Salvador block on name asc without re-sorting the rest', () => {
|
|
const sorted = insertCityAtNativeSortPosition(docs, cityDoc, 'name', 'asc')
|
|
const names = sorted.map((entry) => entry.name)
|
|
// Node's pt-BR collation orders "Salvador — ZE N" before "Salvador
|
|
// (cidade)" (the em dash + digit collate ahead of the parenthesized word),
|
|
// so the city lands right after its zones — still inside the block, with
|
|
// every other row keeping the SQL order.
|
|
expect(names).toEqual([
|
|
'Alagoinhas',
|
|
'Salvador — ZE 1',
|
|
'Salvador — ZE 19',
|
|
'Salvador (cidade)',
|
|
'Teixeira de Freitas',
|
|
])
|
|
})
|
|
|
|
it('places the city first within the block on name desc', () => {
|
|
const sorted = insertCityAtNativeSortPosition(docs, cityDoc, 'name', 'desc')
|
|
const names = sorted.map((entry) => entry.name)
|
|
expect(names[0]).toBe('Salvador (cidade)')
|
|
expect(names.indexOf('Salvador — ZE 1')).toBeLessThan(names.indexOf('Teixeira de Freitas'))
|
|
})
|
|
|
|
it('keeps the SQL order of the other rows untouched', () => {
|
|
const sorted = insertCityAtNativeSortPosition(docs, cityDoc, 'name', 'asc')
|
|
const zoneIndex = sorted.findIndex((entry) => entry.name === 'Salvador — ZE 1')
|
|
expect(sorted[zoneIndex + 1]?.name).toBe('Salvador — ZE 19')
|
|
})
|
|
|
|
it('follows the native null ordering for null-valued keys (last on asc, first on desc)', () => {
|
|
const sorted = insertCityAtNativeSortPosition(docs, cityDoc, 'lastUpdateAt', 'asc')
|
|
expect(sorted[sorted.length - 1]?.slug).toBe(cityDoc.slug)
|
|
// Postgres puts NULLS FIRST on DESC — the city (no lastUpdateAt) sits with
|
|
// the other municipalities that have no value, not at the bottom.
|
|
const sortedDesc = insertCityAtNativeSortPosition(docs, cityDoc, 'lastUpdateAt', 'desc')
|
|
expect(sortedDesc[0]?.slug).toBe(cityDoc.slug)
|
|
const trendAsc = insertCityAtNativeSortPosition(docs, cityDoc, 'trend', 'asc')
|
|
expect(trendAsc[trendAsc.length - 1]?.slug).toBe(cityDoc.slug)
|
|
})
|
|
})
|