Merge pull request #99 from fsolla/cursor/pool-i93-28926117-13a5

B93 — Empty state do wizard — continuity (visitados + última ação)
This commit is contained in:
Francisco Solla 2026-08-01 04:37:44 -03:00 committed by GitHub
commit eb80f8c32c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 451 additions and 8 deletions

View file

@ -119,6 +119,7 @@ export default async function CampaignActionWizardPage({
actionSlug={slug}
municipalityId={municipality.id}
municipalityName={municipality.name}
municipalitySlug={municipality.slug}
initialExpectedVotes={toVoteEstimateScenarioViewModel(municipality.expectedVotes)}
/>
)

View file

@ -21,6 +21,7 @@ import {
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import type { CampaignWizardActionId } from '@/lib/campaignActionRoutes'
import { wizardActionHref } from '@/lib/campaignActionRoutes'
import { recordLastActedMunicipality } from '@/lib/campaignLastActedMunicipality'
import { CAMPAIGN_HOME } from '@/lib/campaignPaths'
import {
WIZARD_LEADERSHIP_ADD_TILE_LABEL,
@ -103,11 +104,12 @@ export const WizardLeadershipStep = ({
) : undefined
const handleSaved = useCallback(() => {
recordLastActedMunicipality(municipalitySlug)
setDirty(true)
setMode({ kind: 'grid' })
router.refresh()
toast.success(WIZARD_LEADERSHIP_SAVED_TOAST)
}, [router])
}, [municipalitySlug, router])
const handleContinue = () => {
startContinueTransition(() => {

View file

@ -13,6 +13,7 @@ import { Spinner } from '@/components/ui/Spinner'
import { Textarea } from '@/components/ui/textarea'
import type { CampaignWizardActionId } from '@/lib/campaignActionRoutes'
import { wizardSignalHref } from '@/lib/campaignActionRoutes'
import { recordLastActedMunicipality } from '@/lib/campaignLastActedMunicipality'
import { CAMPAIGN_HOME } from '@/lib/campaignPaths'
import { wizardFlowTitleForSlug } from '@/lib/campaignWizardCopy'
import type { MunicipalitySignalType } from '@/lib/schemas/municipalityUpdate'
@ -49,6 +50,7 @@ export const WizardSignalBodyStep = ({
const stepTitle = `${WIZARD_SIGNAL_BODY_STEP_TITLE_PREFIX}: ${municipalitySignalTypeLabels[signalType]}`
useCampaignFormSuccessToast(state, () => {
recordLastActedMunicipality(municipalitySlug)
router.push(CAMPAIGN_HOME)
})

View file

@ -13,6 +13,7 @@ import { Spinner } from '@/components/ui/Spinner'
import { Textarea } from '@/components/ui/textarea'
import type { CampaignWizardActionId } from '@/lib/campaignActionRoutes'
import { wizardTrendHref } from '@/lib/campaignActionRoutes'
import { recordLastActedMunicipality } from '@/lib/campaignLastActedMunicipality'
import { CAMPAIGN_HOME } from '@/lib/campaignPaths'
import { wizardFlowTitleForSlug } from '@/lib/campaignWizardCopy'
import {
@ -52,6 +53,7 @@ export const WizardTrendNoteStep = ({
const stepTitle = `Mudar tendência para ${politicalTrendLabels[trendStatus]}`
useCampaignFormSuccessToast(state, () => {
recordLastActedMunicipality(municipalitySlug)
router.push(CAMPAIGN_HOME)
})

View file

@ -15,6 +15,7 @@ import { Button } from '@/components/ui/button'
import { Spinner } from '@/components/ui/Spinner'
import { wizardActionHref } from '@/lib/campaignActionRoutes'
import { postCampaignJson } from '@/lib/campaignJsonRequest'
import { recordLastActedMunicipality } from '@/lib/campaignLastActedMunicipality'
import { CAMPAIGN_HOME } from '@/lib/campaignPaths'
import {
WIZARD_VOTES_FINAL_CTA_LABEL,
@ -37,6 +38,7 @@ type WizardExpectedVotesStepProps = {
actionSlug: string
municipalityId: number
municipalityName: string
municipalitySlug: string
initialExpectedVotes: VoteEstimateScenarioViewModel
}
@ -44,6 +46,7 @@ export const WizardExpectedVotesStep = ({
actionSlug,
municipalityId,
municipalityName,
municipalitySlug,
initialExpectedVotes,
}: WizardExpectedVotesStepProps) => {
const [isPending, startTransition] = useTransition()
@ -99,6 +102,7 @@ export const WizardExpectedVotesStep = ({
}
setEstimates(payload.savedExpectedVotes)
recordLastActedMunicipality(municipalitySlug)
setPhase('saved')
})
}

View file

@ -1,6 +1,6 @@
'use client'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { HomeSearchHitRow } from '@/components/campaign/dashboard/HomeSearchHitRow'
import { HomeSearchMunicipalityVoteTrailing } from '@/components/campaign/dashboard/HomeSearchMunicipalityVoteTrailing'
@ -12,8 +12,10 @@ import type {
HomeSearchMunicipalityHit,
WizardMunicipalitySearchSuccessResponse,
} from '@/lib/campaignHomeSearchHits'
import { toHomeSearchMunicipalityHit } from '@/lib/campaignHomeSearchHits'
import { HOME_SEARCH_GENERIC_ERROR_MESSAGE } from '@/lib/campaignHomeSearchMessages'
import { postCampaignJson } from '@/lib/campaignJsonRequest'
import { getLastActedMunicipalitySlug } from '@/lib/campaignLastActedMunicipality'
import {
WIZARD_MUNICIPALITY_SEARCH_EMPTY,
WIZARD_MUNICIPALITY_SEARCH_LABEL,
@ -22,14 +24,26 @@ import {
wizardFlowTitleForSlug,
} from '@/lib/campaignWizardCopy'
import { HOME_SEARCH_QUERY_MAX_LENGTH } from '@/lib/schemas/homeSearch'
import {
listWizardContinuitySlugs,
mergeWizardMunicipalitySuggestions,
type WizardContinuitySlug,
} from '@/lib/wizardMunicipalitySuggestMerge'
import { listRecentVisits } from '@/utilities/recentVisits'
const HOME_SEARCH_ROUTE = '/campanha/home-search'
const WIZARD_MUNICIPALITY_SEARCH_INPUT_ID = 'wizardMunicipalitySearchQuery'
type WizardSuggestSuccessState = {
municipalities: HomeSearchMunicipalityHit[]
hitBySlug: ReadonlyMap<string, HomeSearchMunicipalityHit>
}
type WizardSearchResultsState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; municipalities: HomeSearchMunicipalityHit[] }
| { status: 'success'; mode: 'search'; municipalities: HomeSearchMunicipalityHit[] }
| { status: 'success'; mode: 'suggest'; suggest: WizardSuggestSuccessState }
| { status: 'error'; message: string }
type WizardMunicipalitySearchStepProps = {
@ -37,17 +51,50 @@ type WizardMunicipalitySearchStepProps = {
previousHref: string
}
const buildSuggestSuccessState = (
payload: WizardMunicipalitySearchSuccessResponse,
): WizardSuggestSuccessState => {
const scopeMunicipalities = payload.scopeMunicipalities ?? []
const hitBySlug = new Map(
scopeMunicipalities.map((doc) => [doc.slug, toHomeSearchMunicipalityHit(doc)]),
)
return {
municipalities: payload.municipalities,
hitBySlug,
}
}
export const WizardMunicipalitySearchStep = ({
actionSlug,
previousHref,
}: WizardMunicipalitySearchStepProps) => {
const { query, setRaw, isDebouncing } = useHomeSearchQuery()
const [results, setResults] = useState<WizardSearchResultsState>({ status: 'idle' })
const [continuitySlugs, setContinuitySlugs] = useState<WizardContinuitySlug[]>([])
const [continuityReady, setContinuityReady] = useState(false)
const requestSeq = useRef(0)
const suggestMode = !query.isActive
const searchMode = query.isActive
const resultsBusy = isDebouncing || results.status === 'loading'
useEffect(() => {
if (!suggestMode || results.status !== 'success' || results.mode !== 'suggest') {
setContinuitySlugs([])
setContinuityReady(false)
return
}
const scopeSlugs = new Set(results.suggest.hitBySlug.keys())
setContinuitySlugs(
listWizardContinuitySlugs({
lastActedSlug: getLastActedMunicipalitySlug(),
recentVisits: listRecentVisits(),
scopeSlugs,
}),
)
setContinuityReady(true)
}, [results, suggestMode])
useEffect(() => {
if (!suggestMode && !searchMode) {
requestSeq.current += 1
@ -77,7 +124,20 @@ export const WizardMunicipalitySearchStep = ({
return
}
setResults({ status: 'success', municipalities: payload.municipalities })
if (suggestMode) {
setResults({
status: 'success',
mode: 'suggest',
suggest: buildSuggestSuccessState(payload),
})
return
}
setResults({
status: 'success',
mode: 'search',
municipalities: payload.municipalities,
})
})().catch((error: unknown) => {
if (seq !== requestSeq.current) return
if (error instanceof DOMException && error.name === 'AbortError') return
@ -89,8 +149,29 @@ export const WizardMunicipalitySearchStep = ({
}
}, [query.debounced, searchMode, suggestMode])
const displayRows = useMemo(() => {
if (results.status !== 'success') return []
if (results.mode === 'search') {
return results.municipalities.map((hit) => ({ hit, continuityReason: undefined }))
}
if (!continuityReady) {
return results.suggest.municipalities.map((hit) => ({ hit, continuityReason: undefined }))
}
return mergeWizardMunicipalitySuggestions({
continuity: continuitySlugs,
serverHits: results.suggest.municipalities,
hitBySlug: results.suggest.hitBySlug,
})
}, [continuityReady, continuitySlugs, results])
const showEmpty =
query.isActive && results.status === 'success' && results.municipalities.length === 0
query.isActive &&
results.status === 'success' &&
results.mode === 'search' &&
results.municipalities.length === 0
return (
<CampaignWizardShell
@ -124,14 +205,14 @@ export const WizardMunicipalitySearchStep = ({
{results.message}
</p>
) : null}
{results.status === 'success' && results.municipalities.length > 0 ? (
{results.status === 'success' && displayRows.length > 0 ? (
<ul className="flex flex-col">
{results.municipalities.map((hit) => (
{displayRows.map(({ hit, continuityReason }) => (
<li key={hit.slug}>
<HomeSearchHitRow
href={wizardActionHref(actionSlug, hit.slug)}
primary={hit.name}
secondary={hit.region}
secondary={continuityReason ?? hit.region}
showPriority={hit.priority === 'alta'}
wizardNavigation
trailing={

View file

@ -30,6 +30,7 @@ import {
useSidebar,
} from '@/components/ui/Sidebar'
import { Spinner } from '@/components/ui/Spinner'
import { clearLastActedMunicipality } from '@/lib/campaignLastActedMunicipality'
import { clearCampaignPwaCaches } from '@/utilities/campaignPwaClient'
import type { CampaignUserShellView } from '@/utilities/campaignUserProfile'
import { campaignRoleLabels } from '@/utilities/campaignUserProfile'
@ -76,6 +77,7 @@ export const CampaignSidebar = ({ user }: { user: CampaignSidebarUser }) => {
if (isLoggingOut) return
setIsLoggingOut(true)
clearRecentVisits()
clearLastActedMunicipality()
clearMunicipalitySavedFilters()
await clearCampaignPwaCaches()
// logoutCampaign redirects — no need to reset the pending flag on success.

View file

@ -120,4 +120,11 @@ export type WizardMunicipalitySearchSuccessResponse = {
status: 'success'
resultKind?: 'wizard-suggest'
municipalities: HomeSearchMunicipalityHit[]
/** Present on `wizard-municipality-suggest` for client continuity merge (B93). */
scopeMunicipalities?: {
slug: string
name: string
region: string
priority: 'alta' | 'normal' | null
}[]
}

View file

@ -0,0 +1,39 @@
/** Device-local last municipality where the staff actor completed a wizard write. */
export const LAST_ACTED_MUNICIPALITY_STORAGE_KEY = 'teqo:campaign:last-acted-municipality'
const isStoredSlug = (value: unknown): value is string =>
typeof value === 'string' && value.trim().length > 0
export const getLastActedMunicipalitySlug = (): string | null => {
if (typeof window === 'undefined') return null
try {
const raw = localStorage.getItem(LAST_ACTED_MUNICIPALITY_STORAGE_KEY)
if (!raw) return null
const parsed: unknown = JSON.parse(raw)
if (isStoredSlug(parsed)) return parsed.trim()
return null
} catch {
return null
}
}
export const recordLastActedMunicipality = (slug: string): void => {
if (typeof window === 'undefined') return
const trimmed = slug.trim()
if (!trimmed) return
try {
localStorage.setItem(LAST_ACTED_MUNICIPALITY_STORAGE_KEY, JSON.stringify(trimmed))
} catch {
// Ignore quota / private-mode failures.
}
}
export const clearLastActedMunicipality = (): void => {
if (typeof window === 'undefined') return
try {
localStorage.removeItem(LAST_ACTED_MUNICIPALITY_STORAGE_KEY)
} catch {
// Ignore private-mode failures.
}
}

View file

@ -0,0 +1,120 @@
import type { HomeSearchMunicipalityHit } from '@/lib/campaignHomeSearchHits'
import { HOME_SEARCH_SUGGEST_LIMIT } from '@/lib/homeSearchSuggest'
export const WIZARD_CONTINUITY_LAST_ACTED_LABEL = 'Última ação' as const
export const WIZARD_CONTINUITY_VISITED_LABEL = 'Visitado' as const
const WIZARD_CONTINUITY_MAX_VISITED = 3
type WizardContinuitySource = 'last-acted' | 'visited'
export type WizardContinuitySlug = {
source: WizardContinuitySource
slug: string
}
export type WizardContinuityVisitInput = {
href: string
kind: 'municipality' | 'municipalityList'
}
export type WizardMunicipalityMergedRow = {
hit: HomeSearchMunicipalityHit
continuityReason?: string
}
const MUNICIPALITY_DETAIL_HREF_PREFIX = '/campanha/municipios/'
export const municipalitySlugFromRecentVisitHref = (href: string): string | null => {
if (!href.startsWith(MUNICIPALITY_DETAIL_HREF_PREFIX)) return null
const slug = href.slice(MUNICIPALITY_DETAIL_HREF_PREFIX.length).split('/')[0]?.trim()
return slug ? slug : null
}
const continuityReasonForSource = (source: WizardContinuitySource): string => {
switch (source) {
case 'last-acted':
return WIZARD_CONTINUITY_LAST_ACTED_LABEL
case 'visited':
return WIZARD_CONTINUITY_VISITED_LABEL
default: {
const _exhaustive: never = source
return _exhaustive
}
}
}
export const listWizardContinuitySlugs = (input: {
lastActedSlug: string | null
recentVisits: WizardContinuityVisitInput[]
scopeSlugs: ReadonlySet<string>
maxVisited?: number
}): WizardContinuitySlug[] => {
const maxVisited = input.maxVisited ?? WIZARD_CONTINUITY_MAX_VISITED
const continuity: WizardContinuitySlug[] = []
const seen = new Set<string>()
const pushSlug = (source: WizardContinuitySource, slug: string) => {
if (!input.scopeSlugs.has(slug) || seen.has(slug)) return
seen.add(slug)
continuity.push({ source, slug })
}
if (input.lastActedSlug) {
pushSlug('last-acted', input.lastActedSlug)
}
let visitedCount = 0
for (const entry of input.recentVisits) {
if (visitedCount >= maxVisited) break
if (entry.kind !== 'municipality') continue
const slug = municipalitySlugFromRecentVisitHref(entry.href)
if (!slug) continue
if (seen.has(slug)) continue
if (!input.scopeSlugs.has(slug)) continue
seen.add(slug)
continuity.push({ source: 'visited', slug })
visitedCount += 1
}
return continuity
}
/**
* B93 — merge client continuity (last-acted + recent visits) ahead of B92 server
* suggestions. Dedup order: geo (B94) → last-acted → visited → forgotten.
*/
export const mergeWizardMunicipalitySuggestions = (input: {
continuity: WizardContinuitySlug[]
geoSlug?: string | null
serverHits: HomeSearchMunicipalityHit[]
hitBySlug: ReadonlyMap<string, HomeSearchMunicipalityHit>
limit?: number
}): WizardMunicipalityMergedRow[] => {
const limit = input.limit ?? HOME_SEARCH_SUGGEST_LIMIT
const merged: WizardMunicipalityMergedRow[] = []
const seen = new Set<string>()
const pushHit = (slug: string, continuityReason?: string) => {
if (seen.has(slug)) return
const hit = input.hitBySlug.get(slug)
if (!hit) return
seen.add(slug)
merged.push({ hit, continuityReason })
}
const geoSlug = input.geoSlug?.trim()
if (geoSlug) {
pushHit(geoSlug)
}
for (const item of input.continuity) {
pushHit(item.slug, continuityReasonForSource(item.source))
}
for (const hit of input.serverHits) {
pushHit(hit.slug)
}
return merged.slice(0, limit)
}

View file

@ -65,5 +65,11 @@ export const loadWizardMunicipalitySuggestions = async (
status: 'success',
resultKind: 'wizard-suggest',
municipalities: ranked.map((input) => toHomeSearchMunicipalityHit(docBySlug.get(input.slug)!)),
scopeMunicipalities: municipalities.map((doc) => ({
slug: doc.slug,
name: doc.name,
region: doc.region,
priority: doc.priority ?? null,
})),
}
}

View file

@ -30,6 +30,7 @@ describe('loadWizardMunicipalitySuggestions (B92)', () => {
expect(result.resultKind).toBe('wizard-suggest')
expect(result.municipalities.length).toBeGreaterThan(0)
expect(result.municipalities.length).toBeLessThanOrEqual(8)
expect(result.scopeMunicipalities?.length).toBeGreaterThan(0)
})
it('scopes advisor suggestions to administered municipalities', async () => {

View file

@ -0,0 +1,36 @@
import { afterEach, describe, expect, it } from 'vitest'
import {
clearLastActedMunicipality,
getLastActedMunicipalitySlug,
LAST_ACTED_MUNICIPALITY_STORAGE_KEY,
recordLastActedMunicipality,
} from '@/lib/campaignLastActedMunicipality'
describe('campaignLastActedMunicipality storage', () => {
afterEach(() => {
localStorage.clear()
})
it('returns null when storage is missing or invalid', () => {
expect(getLastActedMunicipalitySlug()).toBeNull()
localStorage.setItem(LAST_ACTED_MUNICIPALITY_STORAGE_KEY, 'not-json')
expect(getLastActedMunicipalitySlug()).toBeNull()
localStorage.setItem(LAST_ACTED_MUNICIPALITY_STORAGE_KEY, JSON.stringify(''))
expect(getLastActedMunicipalitySlug()).toBeNull()
})
it('records and reads the last acted municipality slug', () => {
recordLastActedMunicipality('itabuna')
expect(getLastActedMunicipalitySlug()).toBe('itabuna')
recordLastActedMunicipality(' cairu ')
expect(getLastActedMunicipalitySlug()).toBe('cairu')
})
it('clears stored slug', () => {
recordLastActedMunicipality('valenca')
clearLastActedMunicipality()
expect(getLastActedMunicipalitySlug()).toBeNull()
expect(localStorage.getItem(LAST_ACTED_MUNICIPALITY_STORAGE_KEY)).toBeNull()
})
})

View file

@ -20,6 +20,7 @@ const defaultProps = {
actionSlug: 'atualizar-votos',
municipalityId: 1,
municipalityName: 'Cairu',
municipalitySlug: 'cairu',
initialExpectedVotes: { pessimistic: 100, central: 200, optimistic: 300 },
}

View file

@ -0,0 +1,139 @@
import { describe, expect, it } from 'vitest'
import type { HomeSearchMunicipalityHit } from '@/lib/campaignHomeSearchHits'
import { HOME_SEARCH_SUGGEST_LIMIT } from '@/lib/homeSearchSuggest'
import {
listWizardContinuitySlugs,
mergeWizardMunicipalitySuggestions,
municipalitySlugFromRecentVisitHref,
WIZARD_CONTINUITY_LAST_ACTED_LABEL,
WIZARD_CONTINUITY_VISITED_LABEL,
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('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' }),
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({
geoSlug: 'forgot-a',
continuity: [
{ source: 'last-acted', slug: 'acted' },
{ source: 'visited', slug: 'visited' },
{ source: 'visited', slug: 'forgot-a' },
],
serverHits: [
hit({ slug: 'forgot-a', name: 'Forgot A' }),
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 === '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)
})
})