teqo/tests/unit/wizardMunicipalitySuggestMerge.unit.spec.ts
2026-08-01 07:44:00 +00:00

163 lines
5.6 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { HomeSearchMunicipalityHit } from '@/lib/campaignHomeSearchHits'
import { HOME_SEARCH_SUGGEST_LIMIT } from '@/lib/homeSearchSuggest'
import {
formatWizardGeoSecondary,
listWizardContinuitySlugs,
mergeWizardMunicipalitySuggestions,
municipalitySlugFromRecentVisitHref,
WIZARD_CONTINUITY_LAST_ACTED_LABEL,
WIZARD_CONTINUITY_VISITED_LABEL,
WIZARD_GEO_NEARBY_REASON,
type WizardContinuityVisitInput,
} from '@/lib/wizardMunicipalitySuggestMerge'
const hit = (
partial: Partial<HomeSearchMunicipalityHit> & Pick<HomeSearchMunicipalityHit, 'slug' | 'name'>,
): HomeSearchMunicipalityHit => ({
kind: 'municipality',
region: 'Região',
priority: null,
votePosition2022: null,
...partial,
})
const visit = (
overrides: Partial<WizardContinuityVisitInput> = {},
): WizardContinuityVisitInput => ({
href: '/campanha/municipios/cairu',
kind: 'municipality',
...overrides,
})
describe('formatWizardGeoSecondary', () => {
it('formats reason without distance', () => {
expect(formatWizardGeoSecondary(undefined)).toBe(WIZARD_GEO_NEARBY_REASON)
expect(formatWizardGeoSecondary('Costa do Dendê')).toBe(
`Costa do Dendê · ${WIZARD_GEO_NEARBY_REASON}`,
)
})
it('formats reason with distance', () => {
expect(formatWizardGeoSecondary('Costa do Dendê', 1.2)).toBe(
`Costa do Dendê · ${WIZARD_GEO_NEARBY_REASON} (~1,2 km)`,
)
expect(formatWizardGeoSecondary(undefined, 0.4)).toBe(
`${WIZARD_GEO_NEARBY_REASON} (~menos de 1 km)`,
)
})
})
describe('municipalitySlugFromRecentVisitHref', () => {
it('extracts municipality slug from detail href', () => {
expect(municipalitySlugFromRecentVisitHref('/campanha/municipios/itabuna')).toBe('itabuna')
expect(municipalitySlugFromRecentVisitHref('/campanha/municipios')).toBeNull()
expect(municipalitySlugFromRecentVisitHref('/campanha/atividades/foo')).toBeNull()
})
})
describe('listWizardContinuitySlugs', () => {
const scope = new Set(['itabuna', 'cairu', 'valenca'])
it('orders last-acted before visited and filters out of scope', () => {
const continuity = listWizardContinuitySlugs({
lastActedSlug: 'itabuna',
recentVisits: [
visit({ href: '/campanha/municipios/cairu' }),
visit({ href: '/campanha/municipios/fora' }),
visit({ href: '/campanha/municipios/valenca' }),
],
scopeSlugs: scope,
})
expect(continuity).toEqual([
{ source: 'last-acted', slug: 'itabuna' },
{ source: 'visited', slug: 'cairu' },
{ source: 'visited', slug: 'valenca' },
])
})
it('deduplicates visited slug that matches last-acted', () => {
const continuity = listWizardContinuitySlugs({
lastActedSlug: 'cairu',
recentVisits: [visit({ href: '/campanha/municipios/cairu' })],
scopeSlugs: new Set(['cairu']),
})
expect(continuity).toEqual([{ source: 'last-acted', slug: 'cairu' }])
})
it('ignores non-municipality visits and caps visited count', () => {
const continuity = listWizardContinuitySlugs({
lastActedSlug: null,
recentVisits: [
visit({ href: '/campanha/municipios', kind: 'municipalityList' }),
visit({ href: '/campanha/municipios/a' }),
visit({ href: '/campanha/municipios/b' }),
visit({ href: '/campanha/municipios/c' }),
visit({ href: '/campanha/municipios/d' }),
],
scopeSlugs: new Set(['a', 'b', 'c', 'd']),
maxVisited: 3,
})
expect(continuity.map((item) => item.slug)).toEqual(['a', 'b', 'c'])
})
})
describe('mergeWizardMunicipalitySuggestions', () => {
const hitBySlug = new Map(
[
hit({ slug: 'geo', name: 'Geo' }),
hit({ slug: 'acted', name: 'Acted' }),
hit({ slug: 'visited', name: 'Visited' }),
hit({ slug: 'forgot-a', name: 'Forgot A', region: 'Costa do Dendê' }),
hit({ slug: 'forgot-b', name: 'Forgot B' }),
].map((row) => [row.slug, row]),
)
it('dedupes in geo > continuity > server order with reason labels', () => {
const merged = mergeWizardMunicipalitySuggestions({
geo: { slug: 'forgot-a', name: 'Forgot A', distanceKm: 2.5 },
continuity: [
{ source: 'last-acted', slug: 'acted' },
{ source: 'visited', slug: 'visited' },
{ source: 'visited', slug: 'forgot-a' },
],
serverHits: [
hit({ slug: 'forgot-a', name: 'Forgot A', region: 'Costa do Dendê' }),
hit({ slug: 'forgot-b', name: 'Forgot B' }),
],
hitBySlug,
})
expect(merged.map((row) => row.hit.slug)).toEqual(['forgot-a', 'acted', 'visited', 'forgot-b'])
expect(merged.find((row) => row.hit.slug === 'forgot-a')?.hit.region).toContain(
WIZARD_GEO_NEARBY_REASON,
)
expect(merged.find((row) => row.hit.slug === 'forgot-a')?.hit.region).toContain('~2,5 km')
expect(merged.find((row) => row.hit.slug === 'acted')?.continuityReason).toBe(
WIZARD_CONTINUITY_LAST_ACTED_LABEL,
)
expect(merged.find((row) => row.hit.slug === 'visited')?.continuityReason).toBe(
WIZARD_CONTINUITY_VISITED_LABEL,
)
expect(merged.find((row) => row.hit.slug === 'forgot-b')?.continuityReason).toBeUndefined()
})
it('caps the merged list at HOME_SEARCH_SUGGEST_LIMIT', () => {
const manyHits = Array.from({ length: 10 }, (_, index) =>
hit({ slug: `m-${index}`, name: `M ${index}` }),
)
const manyBySlug = new Map(manyHits.map((row) => [row.slug, row]))
const merged = mergeWizardMunicipalitySuggestions({
continuity: [],
serverHits: manyHits,
hitBySlug: manyBySlug,
})
expect(merged).toHaveLength(HOME_SEARCH_SUGGEST_LIMIT)
})
})