The "Municípios" column was dead text; it is now chips that navigate to the município, with a territory chip when a leadership covers a whole TI, and it is editable in place. What picks inline vs. Drawer is the `pointer:` media query, not viewport width: on a fine pointer the editor is always mounted (the "×" sits at the chip's corner, revealed on hover/focus) and on a coarse pointer a tap anywhere in the cell opens a Drawer with 44 px targets. `AdvisorMunicipalityCell` is promoted to `shared/MunicipalityPortfolioCell` and /campanha/assessores migrates onto it in the same change, so the second call site is born with the first. One batch action only; the single-id advisor twin is deleted with its int tests migrated. The anti-goal on always-mounted list inputs is amended for chip-based relations on fine pointers, not reopened. Also fixes a latent bug: `CampaignTableHead`'s `description` seam broke any RSC caller, because its two helpers lived in a 'use client' module.
49 lines
2 KiB
TypeScript
49 lines
2 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { nextMunicipalityIdsAfterLeadershipMembership } from '@/lib/leadershipMunicipalityMembership'
|
|
import { MAX_LEADERSHIP_MUNICIPALITIES } from '@/lib/schemas/leadership'
|
|
|
|
describe('nextMunicipalityIdsAfterLeadershipMembership', () => {
|
|
it('appends only the municipalities that are not linked yet, and reports them as added', () => {
|
|
expect(nextMunicipalityIdsAfterLeadershipMembership([10, 20], [20, 30, 40], true)).toEqual({
|
|
next: [10, 20, 30, 40],
|
|
added: [30, 40],
|
|
changed: [30, 40],
|
|
})
|
|
})
|
|
|
|
it('removes the requested municipalities, ignores the ones not linked, and adds nothing', () => {
|
|
// `changed` excludes 99: it was never linked, so revalidating it would bust a
|
|
// route cache for a município this write did not touch.
|
|
expect(nextMunicipalityIdsAfterLeadershipMembership([10, 20, 30], [20, 99], false)).toEqual({
|
|
next: [10, 30],
|
|
added: [],
|
|
changed: [20],
|
|
})
|
|
})
|
|
|
|
it('returns null when the leadership is already in the desired state', () => {
|
|
expect(nextMunicipalityIdsAfterLeadershipMembership([10, 20], [10, 20], true)).toBeNull()
|
|
expect(nextMunicipalityIdsAfterLeadershipMembership([10, 20], [30], false)).toBeNull()
|
|
})
|
|
|
|
it('refuses to remove the last municipality', () => {
|
|
expect(() => nextMunicipalityIdsAfterLeadershipMembership([10], [10], false)).toThrow(
|
|
'pelo menos um município',
|
|
)
|
|
expect(() => nextMunicipalityIdsAfterLeadershipMembership([10, 20], [10, 20], false)).toThrow(
|
|
'pelo menos um município',
|
|
)
|
|
})
|
|
|
|
it('refuses a batch that would cross the cap', () => {
|
|
const current = Array.from({ length: MAX_LEADERSHIP_MUNICIPALITIES - 1 }, (_, i) => i + 1)
|
|
|
|
expect(nextMunicipalityIdsAfterLeadershipMembership(current, [900], true)?.next).toHaveLength(
|
|
MAX_LEADERSHIP_MUNICIPALITIES,
|
|
)
|
|
expect(() => nextMunicipalityIdsAfterLeadershipMembership(current, [900, 901], true)).toThrow(
|
|
`no máximo ${MAX_LEADERSHIP_MUNICIPALITIES}`,
|
|
)
|
|
})
|
|
})
|