- unit: campaignListUrl (canonicalize/clamp/redirect), supporterUi, actionPlanUi (matriz tab->where), 5 parsers de entidade - int: campaignEntityListData smokes (escopo por role, busca por marker, shape de paginacao) - fixtures: suporte a stateDeputy (create + cleanup) - docs: contagens reais (45/47/7), Praca ~96 hits, conflito C8 F4 resolvido (10 ladders restantes -> W4d)
151 lines
5.1 KiB
TypeScript
151 lines
5.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
allParamValues,
|
|
buildListHref,
|
|
firstValue,
|
|
inspectRawListParams,
|
|
normalizedText,
|
|
resolveListUrl,
|
|
strictDecimalInteger,
|
|
type RawSearchParams,
|
|
} from '@/utilities/campaignListUrl'
|
|
|
|
describe('firstValue', () => {
|
|
it('returns the first cell of a repeated param and passes scalars through', () => {
|
|
expect(firstValue(['a', 'b'])).toBe('a')
|
|
expect(firstValue('x')).toBe('x')
|
|
expect(firstValue(undefined)).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('allParamValues', () => {
|
|
it('flattens, trims, dedupes and drops empty cells', () => {
|
|
expect(allParamValues([' a ', 'b', 'a', ''])).toEqual(['a', 'b'])
|
|
expect(allParamValues('solo')).toEqual(['solo'])
|
|
expect(allParamValues(undefined)).toEqual([])
|
|
expect(allParamValues([' '])).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('normalizedText', () => {
|
|
it('trims and turns empty/non-string into undefined', () => {
|
|
expect(normalizedText(' salvador ')).toBe('salvador')
|
|
expect(normalizedText('')).toBeUndefined()
|
|
expect(normalizedText(' ')).toBeUndefined()
|
|
expect(normalizedText(undefined)).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('strictDecimalInteger', () => {
|
|
it('accepts only positive decimal integers without leading zeros', () => {
|
|
expect(strictDecimalInteger('12')).toBe(12)
|
|
expect(strictDecimalInteger('1')).toBe(1)
|
|
expect(strictDecimalInteger('0')).toBeUndefined()
|
|
expect(strictDecimalInteger('01')).toBeUndefined()
|
|
expect(strictDecimalInteger('-3')).toBeUndefined()
|
|
expect(strictDecimalInteger('1.5')).toBeUndefined()
|
|
expect(strictDecimalInteger('abc')).toBeUndefined()
|
|
expect(strictDecimalInteger(undefined)).toBeUndefined()
|
|
})
|
|
|
|
it('rejects unsafe integers', () => {
|
|
expect(strictDecimalInteger('9007199254740993')).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('inspectRawListParams', () => {
|
|
const paramNameSet = new Set(['q', 'page'])
|
|
|
|
it('flags unsupported params and excludes them from the serialized query', () => {
|
|
const result = inspectRawListParams({ q: 'x', foo: '1' }, paramNameSet)
|
|
expect(result.hasUnsupportedParams).toBe(true)
|
|
expect(result.query).toBe('q=x')
|
|
})
|
|
|
|
it('serializes repeated values in order and skips undefined', () => {
|
|
const result = inspectRawListParams({ q: ['a', 'b'], page: undefined }, paramNameSet)
|
|
expect(result.hasUnsupportedParams).toBe(false)
|
|
expect(result.query).toBe('q=a&q=b')
|
|
})
|
|
})
|
|
|
|
// A minimal list-state fixture mirroring how supporter/actionPlan lists wire
|
|
// resolveListUrl: q + page, canonical order q-then-page, page omitted at 1.
|
|
type FixtureState = { page: number; q?: string }
|
|
|
|
const fixtureParamNameSet = new Set(['q', 'page'])
|
|
|
|
const parseFixture = (params: RawSearchParams): FixtureState => {
|
|
const q = normalizedText(firstValue(params.q))
|
|
return {
|
|
page: strictDecimalInteger(firstValue(params.page)) ?? 1,
|
|
...(q ? { q } : {}),
|
|
}
|
|
}
|
|
|
|
const buildFixtureSearchParams = (state: FixtureState, page = state.page): URLSearchParams => {
|
|
const params = new URLSearchParams()
|
|
if (state.q) params.set('q', state.q)
|
|
if (page > 1) params.set('page', String(page))
|
|
return params
|
|
}
|
|
|
|
const resolveFixtureUrl = (params: RawSearchParams, totalPages?: number) =>
|
|
resolveListUrl({
|
|
params,
|
|
paramNameSet: fixtureParamNameSet,
|
|
parse: parseFixture,
|
|
buildSearchParams: buildFixtureSearchParams,
|
|
basePath: '/campanha/lista',
|
|
totalPages,
|
|
})
|
|
|
|
describe('resolveListUrl', () => {
|
|
it('passes a canonical URL through without a redirect', () => {
|
|
const result = resolveFixtureUrl({ q: 'x', page: '2' })
|
|
expect(result.state).toEqual({ page: 2, q: 'x' })
|
|
expect(result.href).toBe('/campanha/lista?q=x&page=2')
|
|
expect(result.redirectHref).toBeUndefined()
|
|
})
|
|
|
|
it('redirects when raw params are in non-canonical order', () => {
|
|
const result = resolveFixtureUrl({ page: '2', q: 'x' })
|
|
expect(result.redirectHref).toBe('/campanha/lista?q=x&page=2')
|
|
})
|
|
|
|
it('redirects on unsupported params, dropping them', () => {
|
|
const result = resolveFixtureUrl({ q: 'x', foo: '1' })
|
|
expect(result.state).toEqual({ page: 1, q: 'x' })
|
|
expect(result.redirectHref).toBe('/campanha/lista?q=x')
|
|
})
|
|
|
|
it('clamps page to totalPages and redirects', () => {
|
|
const result = resolveFixtureUrl({ page: '9' }, 3)
|
|
expect(result.state.page).toBe(3)
|
|
expect(result.redirectHref).toBe('/campanha/lista?page=3')
|
|
})
|
|
|
|
it('does not clamp when totalPages is 0 (empty result set)', () => {
|
|
const result = resolveFixtureUrl({ page: '9' }, 0)
|
|
expect(result.state.page).toBe(9)
|
|
expect(result.redirectHref).toBeUndefined()
|
|
})
|
|
|
|
it('normalizes page=1 out of the URL', () => {
|
|
const result = resolveFixtureUrl({ page: '1' })
|
|
expect(result.href).toBe('/campanha/lista')
|
|
expect(result.redirectHref).toBe('/campanha/lista')
|
|
})
|
|
})
|
|
|
|
describe('buildListHref', () => {
|
|
it('omits the query entirely when the builder emits no params', () => {
|
|
expect(buildListHref({ page: 1 }, buildFixtureSearchParams, '/campanha/lista', 1)).toBe(
|
|
'/campanha/lista',
|
|
)
|
|
expect(buildListHref({ page: 1 }, buildFixtureSearchParams, '/campanha/lista', 4)).toBe(
|
|
'/campanha/lista?page=4',
|
|
)
|
|
})
|
|
})
|