teqo/tests/unit/campaignQuickAction.unit.spec.ts
Cursor Agent d5da7b0adf
B82 — Ações rápidas em lideranças (lista + detalhe)
Registra catálogo contextual no drawer B79 para /campanha/liderancas:
lista com update-leadership; detalhe com cinco wizards e prefill de
município quando N=1. Adiciona query leadershipId no wizard A4 e
CampaignQuickActionContextSync na ficha.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 06:24:54 +00:00

392 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from 'vitest'
import { resolveActivityQuickActions } from '@/lib/activityQuickActions'
import { CAMPAIGN_WIZARD_ACTION_SLUGS } from '@/lib/campaignActionRoutes'
import { advisorQuickCreateHref } from '@/lib/campaignAdvisorQuickActions'
import { UNCOVERED_MUNICIPALITIES_LIST_HREF, homeActionsForRole } from '@/lib/campaignHomeActions'
import {
CAMPAIGN_DEMANDS_CREATE_HREF,
demandCreateHref,
isDemandDetailPath,
isDemandsListPath,
resolveDemandDetailQuickActions,
resolveDemandsListQuickActions,
} from '@/lib/campaignQuickActionDemands'
import {
matchesDobradinhasQuickActionSurface,
parseStateDeputyDetailSlug,
resolveDobradinhasQuickActions,
} from '@/lib/campaignQuickActionDobradinhas'
import {
isCampaignActionsPath,
isCampaignHomePath,
isLeaderContactsPath,
shouldMountQuickActionsDrawer,
} from '@/lib/campaignQuickActionMount'
import {
ACTIVITY_LIST_PATH,
ACTIVITY_NEW_PATH,
ACTIVITY_TOUR_COMPOSER_PATH,
ORGANIZATIONS_LIST_PATH,
ORGANIZATION_NEW_PATH,
isActivityTourComposerPath,
parseActivityQuickActionSurface,
parseOrganizationQuickActionSurface,
} from '@/lib/campaignQuickActionPaths'
import { resolveQuickActionsForPath } from '@/lib/campaignQuickActionRegistry'
import {
QUICK_ACTIONS_SNAP_COLLAPSED,
QUICK_ACTIONS_SNAP_EXPANDED,
quickActionsSnapIsExpanded,
} from '@/lib/campaignQuickActionSnap'
import { resolveOrganizationQuickActions } from '@/lib/organizationQuickActions'
describe('campaignQuickActionMount', () => {
it('treats Início as exact match only', () => {
expect(isCampaignHomePath('/campanha')).toBe(true)
expect(isCampaignHomePath('/campanha/')).toBe(true)
expect(isCampaignHomePath('/campanha/municipios')).toBe(false)
})
it('matches action wizard subtree', () => {
expect(isCampaignActionsPath('/campanha/acoes')).toBe(true)
expect(isCampaignActionsPath('/campanha/acoes/atualizar-votos')).toBe(true)
expect(isCampaignActionsPath('/campanha/municipios')).toBe(false)
})
it('matches leader contacts subtree', () => {
expect(isLeaderContactsPath('/campanha/contatos')).toBe(true)
expect(isLeaderContactsPath('/campanha/contatos/novo')).toBe(true)
expect(isLeaderContactsPath('/campanha')).toBe(false)
})
it('mounts for staff outside Início and acoes', () => {
expect(shouldMountQuickActionsDrawer('/campanha/municipios', 'coordinator')).toBe(true)
expect(shouldMountQuickActionsDrawer('/campanha/territorios', 'coordinator')).toBe(true)
expect(shouldMountQuickActionsDrawer('/campanha/demandas', 'coordinator')).toBe(true)
expect(shouldMountQuickActionsDrawer('/campanha/demandas/foo', 'coordinator')).toBe(true)
expect(shouldMountQuickActionsDrawer('/campanha', 'coordinator')).toBe(false)
expect(shouldMountQuickActionsDrawer('/campanha/acoes/registrar-sinal', 'advisor')).toBe(false)
})
it('mounts for leader only on contacts', () => {
expect(shouldMountQuickActionsDrawer('/campanha/contatos', 'leader')).toBe(true)
expect(shouldMountQuickActionsDrawer('/campanha/municipios', 'leader')).toBe(false)
})
it('skips the E13 tour composer (B84)', () => {
expect(isActivityTourComposerPath(ACTIVITY_TOUR_COMPOSER_PATH)).toBe(true)
expect(shouldMountQuickActionsDrawer(ACTIVITY_TOUR_COMPOSER_PATH, 'coordinator')).toBe(false)
})
it('mounts assessores only for unrestricted roles (B87)', () => {
expect(shouldMountQuickActionsDrawer('/campanha/assessores', 'coordinator')).toBe(true)
expect(shouldMountQuickActionsDrawer('/campanha/assessores/12', 'candidate')).toBe(true)
expect(shouldMountQuickActionsDrawer('/campanha/assessores', 'advisor')).toBe(false)
expect(shouldMountQuickActionsDrawer('/campanha/assessores/12', 'advisor')).toBe(false)
})
it('mounts on dobradinhas for staff (B83)', () => {
expect(shouldMountQuickActionsDrawer('/campanha/dobradinhas', 'coordinator')).toBe(true)
expect(shouldMountQuickActionsDrawer('/campanha/dobradinhas/foo', 'advisor')).toBe(true)
})
})
describe('campaignQuickActionPaths (activities)', () => {
it('parses list and detail surfaces', () => {
expect(parseActivityQuickActionSurface(ACTIVITY_LIST_PATH)).toEqual({ kind: 'list' })
expect(parseActivityQuickActionSurface(`${ACTIVITY_LIST_PATH}/`)).toEqual({ kind: 'list' })
expect(parseActivityQuickActionSurface('/campanha/atividades/caminhada-centro')).toEqual({
kind: 'detail',
activitySlug: 'caminhada-centro',
})
})
it('ignores nova, giros and nested edit routes', () => {
expect(parseActivityQuickActionSurface(ACTIVITY_NEW_PATH)).toBeNull()
expect(parseActivityQuickActionSurface(ACTIVITY_TOUR_COMPOSER_PATH)).toBeNull()
expect(parseActivityQuickActionSurface('/campanha/atividades/foo/editar')).toBeNull()
})
})
describe('organizationQuickActions (B88)', () => {
it('parses list and detail surfaces', () => {
expect(parseOrganizationQuickActionSurface(ORGANIZATIONS_LIST_PATH)).toEqual({ kind: 'list' })
expect(parseOrganizationQuickActionSurface(`${ORGANIZATIONS_LIST_PATH}/`)).toEqual({
kind: 'list',
})
expect(parseOrganizationQuickActionSurface('/campanha/organizacoes/sindmed')).toEqual({
kind: 'detail',
organizationSlug: 'sindmed',
})
})
it('ignores nova and nested routes', () => {
expect(parseOrganizationQuickActionSurface(ORGANIZATION_NEW_PATH)).toBeNull()
expect(parseOrganizationQuickActionSurface('/campanha/organizacoes/foo/editar')).toBeNull()
})
it('lists new-organization on the catalog page', () => {
const actions = resolveOrganizationQuickActions({ kind: 'list' }, 'coordinator', {})
expect(actions.map((action) => action.id)).toEqual(['new-organization'])
expect(actions[0]?.href).toBe(ORGANIZATION_NEW_PATH)
})
it('returns empty detail catalog without leadership filter URL', () => {
expect(
resolveOrganizationQuickActions(
{ kind: 'detail', organizationSlug: 'sindmed' },
'coordinator',
{ organizationSlug: 'sindmed' },
),
).toEqual([])
})
it('returns empty catalog for leader lockdown', () => {
expect(resolveOrganizationQuickActions({ kind: 'list' }, 'leader', {})).toEqual([])
})
})
describe('activityQuickActions (B84)', () => {
it('lists vertical verbs on the activity list', () => {
const actions = resolveActivityQuickActions({ kind: 'list' }, 'coordinator', {})
expect(actions.map((action) => action.id)).toEqual(['new-activity', 'plan-tour'])
expect(actions[0]?.href).toBe(ACTIVITY_NEW_PATH)
expect(actions[1]?.href).toBe(ACTIVITY_TOUR_COMPOSER_PATH)
})
it('prefills wizards and detail shortcuts on activity detail', () => {
const actions = resolveActivityQuickActions(
{ kind: 'detail', activitySlug: 'comicio-feira' },
'coordinator',
{ activitySlug: 'comicio-feira', municipalitySlug: 'feira-de-santana' },
)
expect(actions.some((action) => action.id === 'update-votes')).toBe(true)
expect(
actions
.find((action) => action.id === 'update-votes')
?.href?.includes('municipio=feira-de-santana'),
).toBe(true)
expect(actions.find((action) => action.id === 'edit-activity')?.href).toBe(
'/campanha/atividades/comicio-feira/editar',
)
expect(actions.find((action) => action.id === 'activity-tasks')?.href).toBe(
'/campanha/atividades/comicio-feira?tab=tasks',
)
})
it('omits wizard prefills without municipality context', () => {
const actions = resolveActivityQuickActions(
{ kind: 'detail', activitySlug: 'panfletagem' },
'advisor',
{ activitySlug: 'panfletagem' },
)
expect(actions.some((action) => action.id === 'register-demand')).toBe(false)
expect(actions.map((action) => action.id)).toEqual([
'edit-activity',
'activity-tasks',
'activity-updates',
])
})
})
describe('campaignQuickActionDobradinhas (B83)', () => {
const staffActionIds = homeActionsForRole('coordinator').map((action) => action.id)
it('recognizes list, create and detail surfaces', () => {
expect(matchesDobradinhasQuickActionSurface('/campanha/dobradinhas')).toBe(true)
expect(matchesDobradinhasQuickActionSurface('/campanha/dobradinhas/nova')).toBe(true)
expect(matchesDobradinhasQuickActionSurface('/campanha/dobradinhas/eduardo-alves')).toBe(true)
expect(matchesDobradinhasQuickActionSurface('/campanha/municipios')).toBe(false)
})
it('parses detail slug and rejects nova', () => {
expect(parseStateDeputyDetailSlug('/campanha/dobradinhas/eduardo-alves')).toBe('eduardo-alves')
expect(parseStateDeputyDetailSlug('/campanha/dobradinhas/nova')).toBeUndefined()
})
it('returns list catalog with Nova dobradinha plus staff Início actions without prefill', () => {
const actions = resolveDobradinhasQuickActions('/campanha/dobradinhas', 'coordinator')
expect(actions.map((action) => action.id)).toEqual(['new-state-deputy', ...staffActionIds])
expect(actions[0]?.href).toBe('/campanha/dobradinhas/nova')
expect(actions.find((action) => action.id === 'update-votes')?.href).toBe(
'/campanha/acoes/atualizar-votos',
)
expect(actions.find((action) => action.id === 'uncovered-municipalities')?.href).toBe(
UNCOVERED_MUNICIPALITIES_LIST_HREF,
)
})
it('returns staff actions without Nova on detail and create', () => {
for (const pathname of ['/campanha/dobradinhas/eduardo-alves', '/campanha/dobradinhas/nova']) {
const actions = resolveDobradinhasQuickActions(pathname, 'advisor')
expect(actions.map((action) => action.id)).toEqual(staffActionIds)
expect(actions.some((action) => action.id === 'new-state-deputy')).toBe(false)
}
})
it('returns empty for leader lockdown', () => {
expect(resolveDobradinhasQuickActions('/campanha/dobradinhas', 'leader')).toEqual([])
})
})
describe('campaignQuickActionRegistry', () => {
const staffActionIds = homeActionsForRole('coordinator').map((action) => action.id)
it('delegates organization routes to the B88 catalog', () => {
const actions = resolveQuickActionsForPath(ORGANIZATIONS_LIST_PATH, 'coordinator', {})
expect(actions.map((action) => action.id)).toEqual(['new-organization'])
expect(actions[0]?.href).toBe(ORGANIZATION_NEW_PATH)
})
it('returns empty organization detail catalog in the registry', () => {
expect(
resolveQuickActionsForPath('/campanha/organizacoes/sindmed', 'advisor', {
organizationSlug: 'sindmed',
}),
).toEqual([])
})
it('returns leadership list catalog on /liderancas (B82)', () => {
const actions = resolveQuickActionsForPath('/campanha/liderancas', 'coordinator', {})
expect(actions).toHaveLength(1)
expect(actions[0]?.id).toBe('update-leadership')
})
it('returns empty catalog for unregistered paths', () => {
expect(resolveQuickActionsForPath('/campanha/apoiadores', 'coordinator', {})).toEqual([])
})
it('delegates municipality list and detail catalogs (B80)', () => {
const list = resolveQuickActionsForPath('/campanha/municipios', 'coordinator', {})
expect(list).toHaveLength(6)
const detail = resolveQuickActionsForPath('/campanha/municipios/foo', 'coordinator', {
municipalitySlug: 'foo',
})
expect(detail).toHaveLength(5)
expect(detail.find((action) => action.id === 'update-votes')?.href).toBe(
'/campanha/acoes/atualizar-votos?municipio=foo',
)
})
it('delegates activity routes to the B84 catalog', () => {
const actions = resolveQuickActionsForPath(ACTIVITY_LIST_PATH, 'coordinator', {})
expect(actions.map((action) => action.id)).toEqual(['new-activity', 'plan-tour'])
})
it('delegates dobradinhas paths to the B83 catalog', () => {
const actions = resolveQuickActionsForPath('/campanha/dobradinhas', 'coordinator', {})
expect(actions.some((action) => action.id === 'new-state-deputy')).toBe(true)
})
it('builds wizard hrefs with municipality slug from context', () => {
const actions = resolveQuickActionsForPath(
'/campanha/atividades/evento-zona-1',
'coordinator',
{
municipalitySlug: 'salvador-ze-01',
},
)
const registerSignal = actions.find((action) => action.id === 'register-signal')
expect(registerSignal?.href).toBe(
`/campanha/acoes/${CAMPAIGN_WIZARD_ACTION_SLUGS['register-signal']}?municipio=salvador-ze-01`,
)
})
it('returns staff Início catalog on territorios without municipality prefill (B81)', () => {
const actions = resolveQuickActionsForPath('/campanha/territorios', 'coordinator', {})
expect(actions.map((action) => action.id)).toEqual(staffActionIds)
expect(actions.find((action) => action.id === 'update-votes')?.href).toBe(
'/campanha/acoes/atualizar-votos',
)
expect(actions.find((action) => action.id === 'uncovered-municipalities')?.href).toBe(
UNCOVERED_MUNICIPALITIES_LIST_HREF,
)
for (const action of actions) {
expect(action.href).not.toContain('municipio=')
}
})
it('returns empty catalog on territorios for leader lockdown', () => {
expect(resolveQuickActionsForPath('/campanha/territorios', 'leader', {})).toEqual([])
})
it('returns single register-demand launcher on demandas list (B85)', () => {
const actions = resolveQuickActionsForPath('/campanha/demandas', 'coordinator', {})
expect(actions.map((action) => action.id)).toEqual(['register-demand'])
expect(actions[0]?.href).toBe(CAMPAIGN_DEMANDS_CREATE_HREF)
expect(actions[0]?.href).not.toContain('municipio=')
})
it('returns A1–A5 prefilled on demand detail when municipality context is set (B85)', () => {
const actions = resolveQuickActionsForPath('/campanha/demandas/pedido-cairu', 'coordinator', {
municipalitySlug: 'cairu',
municipalityId: 42,
demandSlug: 'pedido-cairu',
})
expect(actions.map((action) => action.id)).toEqual([
'update-votes',
'register-signal',
'change-trend',
'update-leadership',
'register-demand',
])
expect(actions.find((action) => action.id === 'update-votes')?.href).toBe(
'/campanha/acoes/atualizar-votos?municipio=cairu',
)
expect(actions.find((action) => action.id === 'register-demand')?.href).toBe(
demandCreateHref(42),
)
expect(actions.some((action) => action.id === 'uncovered-municipalities')).toBe(false)
})
it('returns empty catalog on demand detail without municipality context', () => {
expect(
resolveQuickActionsForPath('/campanha/demandas/pedido-cairu', 'coordinator', {}),
).toEqual([])
})
it('delegates assessores list and detail catalogs (B87)', () => {
const list = resolveQuickActionsForPath('/campanha/assessores', 'coordinator', {})
expect(list.map((action) => action.id)).toEqual(['new-advisor'])
expect(list[0]?.href).toBe(advisorQuickCreateHref)
const detail = resolveQuickActionsForPath('/campanha/assessores/9', 'candidate', {})
expect(detail.map((action) => action.id)).toEqual(['new-advisor'])
expect(detail[0]?.href).toBe(advisorQuickCreateHref)
})
it('returns empty catalog on assessores for advisor lockdown', () => {
expect(resolveQuickActionsForPath('/campanha/assessores', 'advisor', {})).toEqual([])
})
})
describe('campaignQuickActionDemands paths', () => {
it('matches list and detail paths but not nova', () => {
expect(isDemandsListPath('/campanha/demandas')).toBe(true)
expect(isDemandsListPath('/campanha/demandas/')).toBe(true)
expect(isDemandDetailPath('/campanha/demandas/pedido-cairu')).toBe(true)
expect(isDemandDetailPath('/campanha/demandas/nova')).toBe(false)
expect(isDemandDetailPath('/campanha/demandas')).toBe(false)
})
})
describe('campaignQuickActionDemands resolvers', () => {
it('returns empty catalog for leader lockdown', () => {
expect(resolveDemandsListQuickActions('leader')).toEqual([])
expect(
resolveDemandDetailQuickActions('leader', { municipalitySlug: 'cairu', municipalityId: 1 }),
).toEqual([])
})
})
describe('campaignQuickActionSnap', () => {
it('detects expanded snap', () => {
expect(quickActionsSnapIsExpanded(QUICK_ACTIONS_SNAP_EXPANDED)).toBe(true)
expect(quickActionsSnapIsExpanded(QUICK_ACTIONS_SNAP_COLLAPSED)).toBe(false)
expect(quickActionsSnapIsExpanded(null)).toBe(false)
})
})