Omnibox de /campanha/municipios ganha as dimensões Dobradinha (nome do contato), Liderança (nome do contato) e Partido (sigla), com recortes "Sem dobradinha"/"Sem liderança"/"Sem partido" no precedente de "Sem nível". Params aditivos stateDeputy/leadership/party na URL congelada; buildMunicipalityListWhere ganha catálogo request-scoped fail-closed (municipalityRelationSets) que vira as leituras reversa e cruzada em where — sem migration, paginação nativa mantida; facets estendidas e o mapa compartilha o catálogo. Coluna Dobradinhas liberada ao staff (servidor já scoped B37/B157; só o gate de UI mudou). Extrato do par sentinela+exists:false em campaignListUrl (5 call sites).
591 lines
22 KiB
TypeScript
591 lines
22 KiB
TypeScript
// @vitest-environment node
|
|
|
|
/**
|
|
* Thin smokes for the five entity list loaders ahead of the Pass 2 list-system
|
|
* consolidation (W1): scope (role access), marker-narrowed search and the
|
|
* pagination shape every list page consumes ({ rows, totalDocs, totalPages }).
|
|
*/
|
|
import type { Payload } from 'payload'
|
|
import { getPayload } from 'payload'
|
|
import { beforeAll, describe, expect, it } from 'vitest'
|
|
|
|
import config from '@/payload.config'
|
|
import { loadAdvisorListPageData } from '@/utilities/advisorData'
|
|
import { loadDemandListPageData } from '@/utilities/campaignDemandData'
|
|
import { NO_PARTY_FILTER_VALUE } from '@/utilities/campaignListUrl'
|
|
import { loadLeadershipListPageData } from '@/utilities/leadership/leadershipData'
|
|
import { loadOrganizationListPageData } from '@/utilities/organizationData'
|
|
import { loadStateDeputyDetail, loadStateDeputyListPageData } from '@/utilities/stateDeputyData'
|
|
|
|
import { installCampaignFixtures } from '../helpers/campaignFixtures'
|
|
|
|
let payload: Payload
|
|
const campaignFixtures = installCampaignFixtures({
|
|
getPayload: () => payload,
|
|
setPayload: (nextPayload) => {
|
|
payload = nextPayload
|
|
},
|
|
})
|
|
|
|
beforeAll(async () => {
|
|
payload = await getPayload({ config: await config })
|
|
})
|
|
|
|
describe('loadLeadershipListPageData', () => {
|
|
it('returns marker-scoped rows with municipality ids for staff', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const municipality = await fixtures.getMunicipality()
|
|
const contact = await fixtures.createContact()
|
|
await fixtures.createLeadership({ contact: contact.id, municipalities: [municipality.id] })
|
|
|
|
const result = await loadLeadershipListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: contact.name,
|
|
})
|
|
|
|
expect(result.totalDocs).toBe(1)
|
|
expect(result.totalPages).toBe(1)
|
|
expect(result.rows).toHaveLength(1)
|
|
expect(result.rows[0]).toMatchObject({
|
|
name: contact.name,
|
|
municipalityIDs: [municipality.id],
|
|
hasAppAccess: false,
|
|
})
|
|
expect(result.filterFacets.municipalityIDs).toContain(municipality.id)
|
|
})
|
|
|
|
it('short-circuits to an empty page when no contact matches the query', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
|
|
const result = await loadLeadershipListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: fixtures.value('sem-resultado'),
|
|
})
|
|
|
|
expect(result).toEqual({
|
|
rows: [],
|
|
totalDocs: 0,
|
|
totalPages: 0,
|
|
filterFacets: { municipalityIDs: [], organizationIDs: [], stateDeputyIDs: [] },
|
|
})
|
|
})
|
|
|
|
it('returns an empty page for leaders (staff guard, no throw)', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const leader = await fixtures.createCampaignUser('leader')
|
|
|
|
const result = await loadLeadershipListPageData(payload, leader, { page: 1 })
|
|
|
|
expect(result).toEqual({
|
|
rows: [],
|
|
totalDocs: 0,
|
|
totalPages: 0,
|
|
filterFacets: { municipalityIDs: [], organizationIDs: [], stateDeputyIDs: [] },
|
|
})
|
|
})
|
|
|
|
it('filters by organization with inclusive OR within the dimension', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const municipality = await fixtures.getMunicipality()
|
|
const marker = fixtures.value('lead-org-filter')
|
|
const organizationA = await fixtures.createOrganization({ name: `Org A ${marker}` })
|
|
const organizationB = await fixtures.createOrganization({ name: `Org B ${marker}` })
|
|
const matchContact = await fixtures.createContact({ name: `Ana ${marker}` })
|
|
const missContact = await fixtures.createContact({ name: `Bruno ${marker}` })
|
|
const match = await fixtures.createLeadership({
|
|
contact: matchContact.id,
|
|
municipalities: [municipality.id],
|
|
organizations: [organizationA.id],
|
|
})
|
|
await fixtures.createLeadership({
|
|
contact: missContact.id,
|
|
municipalities: [municipality.id],
|
|
organizations: [organizationB.id],
|
|
})
|
|
|
|
const byOrganization = await loadLeadershipListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
organizations: [organizationA.id],
|
|
})
|
|
expect(byOrganization.rows.map((row) => row.id)).toEqual([match.id])
|
|
expect(byOrganization.filterFacets.organizationIDs).toContain(organizationA.id)
|
|
expect(byOrganization.filterFacets.organizationIDs).toContain(organizationB.id)
|
|
})
|
|
|
|
it('filters by supportStatus, access and municipality', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const municipalityA = await fixtures.getMunicipality()
|
|
const municipalityB = await fixtures.getMunicipality()
|
|
const marker = fixtures.value('lead-filter')
|
|
|
|
const matchContact = await fixtures.createContact({ name: `Ana ${marker}` })
|
|
const missContact = await fixtures.createContact({ name: `Bruno ${marker}` })
|
|
const match = await fixtures.createLeadership({
|
|
contact: matchContact.id,
|
|
municipalities: [municipalityA.id],
|
|
supportStatus: 'a_abordar',
|
|
})
|
|
await fixtures.createLeadership({
|
|
contact: missContact.id,
|
|
municipalities: [municipalityB.id],
|
|
supportStatus: 'engajado',
|
|
})
|
|
|
|
const byStatus = await loadLeadershipListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
statuses: ['a_abordar'],
|
|
})
|
|
expect(byStatus.rows.map((row) => row.id)).toEqual([match.id])
|
|
|
|
const byMunicipality = await loadLeadershipListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
municipalities: [municipalityA.id],
|
|
})
|
|
expect(byMunicipality.rows.map((row) => row.id)).toEqual([match.id])
|
|
|
|
const withoutAccess = await loadLeadershipListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
access: 'sem',
|
|
})
|
|
expect(withoutAccess.totalDocs).toBe(2)
|
|
expect(withoutAccess.rows.every((row) => !row.hasAppAccess)).toBe(true)
|
|
|
|
const withAccess = await loadLeadershipListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
access: 'com',
|
|
})
|
|
expect(withAccess.totalDocs).toBe(0)
|
|
})
|
|
|
|
it('sorts by contact.name in both directions via the dotted join path', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const municipality = await fixtures.getMunicipality()
|
|
const marker = fixtures.value('lead-sort')
|
|
const alpha = await fixtures.createContact({ name: `Aaa ${marker}` })
|
|
const omega = await fixtures.createContact({ name: `Zzz ${marker}` })
|
|
const first = await fixtures.createLeadership({
|
|
contact: alpha.id,
|
|
municipalities: [municipality.id],
|
|
})
|
|
const last = await fixtures.createLeadership({
|
|
contact: omega.id,
|
|
municipalities: [municipality.id],
|
|
})
|
|
|
|
const asc = await loadLeadershipListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
sort: 'name',
|
|
dir: 'asc',
|
|
})
|
|
expect(asc.rows.map((row) => row.id)).toEqual([first.id, last.id])
|
|
|
|
const desc = await loadLeadershipListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
sort: 'name',
|
|
dir: 'desc',
|
|
})
|
|
expect(desc.rows.map((row) => row.id)).toEqual([last.id, first.id])
|
|
})
|
|
|
|
it('keeps the municipality facet reachable under other filters and unions the selection', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const municipalityA = await fixtures.getMunicipality()
|
|
const municipalityB = await fixtures.getMunicipality()
|
|
const marker = fixtures.value('lead-facet')
|
|
await fixtures.createLeadership({
|
|
contact: (await fixtures.createContact({ name: `A ${marker}` })).id,
|
|
municipalities: [municipalityA.id],
|
|
supportStatus: 'a_abordar',
|
|
})
|
|
await fixtures.createLeadership({
|
|
contact: (await fixtures.createContact({ name: `B ${marker}` })).id,
|
|
municipalities: [municipalityB.id],
|
|
supportStatus: 'engajado',
|
|
})
|
|
|
|
const unfiltered = await loadLeadershipListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
})
|
|
expect(unfiltered.filterFacets.municipalityIDs).toEqual(
|
|
expect.arrayContaining([municipalityA.id, municipalityB.id]),
|
|
)
|
|
|
|
const filtered = await loadLeadershipListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
statuses: ['a_abordar'],
|
|
municipalities: [municipalityB.id],
|
|
})
|
|
// Own filter ignored for the facet — A stays; B stays because it is selected.
|
|
expect(filtered.filterFacets.municipalityIDs).toEqual(
|
|
expect.arrayContaining([municipalityA.id, municipalityB.id]),
|
|
)
|
|
})
|
|
|
|
it('does not leak out-of-scope leaderships to an advisor even with a foreign municipality filter', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const advisor = await fixtures.createCampaignUser('advisor')
|
|
const inScope = await fixtures.getMunicipality()
|
|
const outOfScope = await fixtures.getMunicipality()
|
|
await fixtures.assignMunicipalityAdvisors(inScope.id, [advisor.id])
|
|
|
|
const marker = fixtures.value('lead-scope')
|
|
const visible = await fixtures.createLeadership({
|
|
contact: (await fixtures.createContact({ name: `In ${marker}` })).id,
|
|
municipalities: [inScope.id],
|
|
})
|
|
await fixtures.createLeadership({
|
|
contact: (await fixtures.createContact({ name: `Out ${marker}` })).id,
|
|
municipalities: [outOfScope.id],
|
|
})
|
|
|
|
const foreignFilter = await loadLeadershipListPageData(payload, advisor, {
|
|
page: 1,
|
|
q: marker,
|
|
municipalities: [outOfScope.id],
|
|
})
|
|
expect(foreignFilter.totalDocs).toBe(0)
|
|
expect(foreignFilter.rows).toEqual([])
|
|
|
|
const scoped = await loadLeadershipListPageData(payload, advisor, {
|
|
page: 1,
|
|
q: marker,
|
|
})
|
|
expect(scoped.rows.map((row) => row.id)).toEqual([visible.id])
|
|
})
|
|
})
|
|
|
|
describe('loadOrganizationListPageData', () => {
|
|
it('narrows by q and kind and counts linked leaderships', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const municipality = await fixtures.getMunicipality()
|
|
const organization = await fixtures.createOrganization({
|
|
kind: 'associacao',
|
|
municipalities: [municipality.id],
|
|
})
|
|
const contact = await fixtures.createContact()
|
|
await fixtures.createLeadership({
|
|
contact: contact.id,
|
|
municipalities: [municipality.id],
|
|
organizations: [organization.id],
|
|
})
|
|
|
|
const match = await loadOrganizationListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: organization.name,
|
|
kind: 'associacao',
|
|
})
|
|
expect(match.totalDocs).toBe(1)
|
|
expect(match.rows[0]).toMatchObject({
|
|
slug: organization.slug,
|
|
kind: 'associacao',
|
|
municipalityNames: [municipality.name],
|
|
leadershipCount: 1,
|
|
})
|
|
|
|
const kindMiss = await loadOrganizationListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: organization.name,
|
|
kind: 'sindicato',
|
|
})
|
|
expect(kindMiss.totalDocs).toBe(0)
|
|
expect(kindMiss.rows).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('loadStateDeputyListPageData', () => {
|
|
it('narrows by q with zeroed relation counts for a fresh deputy', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const deputyName = fixtures.value('Deputado')
|
|
const stateDeputy = await fixtures.createStateDeputy({ name: deputyName, party: 'PT' })
|
|
|
|
const result = await loadStateDeputyListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: deputyName,
|
|
})
|
|
|
|
expect(result.totalDocs).toBe(1)
|
|
expect(result.totalPages).toBe(1)
|
|
expect(result.rows[0]).toMatchObject({
|
|
slug: stateDeputy.slug,
|
|
party: 'PT',
|
|
municipalityIDs: [],
|
|
leaderships: [],
|
|
})
|
|
})
|
|
|
|
it('resolves named leaderships linked to the deputy (not a bare count)', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const municipality = await fixtures.getMunicipality()
|
|
const deputyName = fixtures.value('Deputado')
|
|
const stateDeputy = await fixtures.createStateDeputy({ name: deputyName, party: 'PT' })
|
|
const contact = await fixtures.createContact()
|
|
const leadership = await fixtures.createLeadership({
|
|
contact: contact.id,
|
|
municipalities: [municipality.id],
|
|
})
|
|
await payload.update({
|
|
collection: 'leadership',
|
|
id: leadership.id,
|
|
data: { stateDeputies: [stateDeputy.id] },
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
const result = await loadStateDeputyListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: deputyName,
|
|
})
|
|
|
|
expect(result.rows[0]).toMatchObject({
|
|
slug: stateDeputy.slug,
|
|
leaderships: [{ id: leadership.id, name: contact.name }],
|
|
})
|
|
})
|
|
|
|
it('sorts by party in both directions, pinning where the party-less row falls', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const marker = fixtures.value('sort')
|
|
const withPsd = await fixtures.createStateDeputy({
|
|
name: `Dep ${marker} PSD`,
|
|
party: 'PSD',
|
|
})
|
|
const withPt = await fixtures.createStateDeputy({ name: `Dep ${marker} PT`, party: 'PT' })
|
|
const withoutParty = await fixtures.createStateDeputy({ name: `Dep ${marker} sem` })
|
|
|
|
// Pinned adapter behavior (Postgres default, unmodified by Payload): ASC
|
|
// sorts nulls LAST, DESC sorts nulls FIRST — the party-less row never
|
|
// lands in the middle. "Sem partido" is the direct, unambiguous path to
|
|
// find it either way.
|
|
const asc = await loadStateDeputyListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
sort: 'party',
|
|
dir: 'asc',
|
|
})
|
|
expect(asc.rows.map((row) => row.slug)).toEqual([withPsd.slug, withPt.slug, withoutParty.slug])
|
|
|
|
const desc = await loadStateDeputyListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
sort: 'party',
|
|
dir: 'desc',
|
|
})
|
|
expect(desc.rows.map((row) => row.slug)).toEqual([withoutParty.slug, withPt.slug, withPsd.slug])
|
|
})
|
|
|
|
it('filters by selected parties and by the "Sem partido" sentinel', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const marker = fixtures.value('filter')
|
|
const withPt = await fixtures.createStateDeputy({ name: `Dep ${marker} PT`, party: 'PT' })
|
|
await fixtures.createStateDeputy({ name: `Dep ${marker} PSD`, party: 'PSD' })
|
|
const withoutParty = await fixtures.createStateDeputy({ name: `Dep ${marker} sem` })
|
|
|
|
const byParty = await loadStateDeputyListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
parties: ['PT'],
|
|
})
|
|
expect(byParty.rows.map((row) => row.slug)).toEqual([withPt.slug])
|
|
|
|
const byNoParty = await loadStateDeputyListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
parties: [NO_PARTY_FILTER_VALUE],
|
|
})
|
|
expect(byNoParty.rows.map((row) => row.slug)).toEqual([withoutParty.slug])
|
|
})
|
|
|
|
it('computes the party facet from the current search, ignoring the party filter itself', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const marker = fixtures.value('facet')
|
|
await fixtures.createStateDeputy({ name: `Dep ${marker} PT`, party: 'PT' })
|
|
await fixtures.createStateDeputy({ name: `Dep ${marker} PSD`, party: 'PSD' })
|
|
await fixtures.createStateDeputy({ name: `Dep ${marker} sem` })
|
|
|
|
const unfiltered = await loadStateDeputyListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
})
|
|
expect(unfiltered.filterFacets).toEqual({ parties: ['PSD', 'PT'], hasNoParty: true })
|
|
|
|
// Selecting PT keeps every party option reachable — the facet ignores its
|
|
// own popover's filter so the other options remain visible to switch to.
|
|
const filteredByPt = await loadStateDeputyListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: marker,
|
|
parties: ['PT'],
|
|
})
|
|
expect(filteredByPt.filterFacets).toEqual({ parties: ['PSD', 'PT'], hasNoParty: true })
|
|
})
|
|
})
|
|
|
|
describe('loadStateDeputyDetail', () => {
|
|
it('resolves an oversized numeric legacy slug without coercing it to an ID', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const name = `${fixtures.value('12345678901234567890')}`
|
|
const stateDeputy = await fixtures.createStateDeputy({ name })
|
|
|
|
const result = await loadStateDeputyDetail(payload, coordinator, stateDeputy.slug)
|
|
|
|
expect(result).toMatchObject({ id: stateDeputy.id, name })
|
|
})
|
|
})
|
|
|
|
describe('loadDemandListPageData', () => {
|
|
it('lists staff demands with municipality labels, open count and status filter', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const municipality = await fixtures.getMunicipality()
|
|
const demand = await fixtures.createCampaignDemand({
|
|
municipality: municipality.id,
|
|
status: 'aberta',
|
|
})
|
|
|
|
const open = await loadDemandListPageData(payload, coordinator, {
|
|
page: 1,
|
|
status: 'aberta',
|
|
})
|
|
const row = open.rows.find((candidate) => candidate.slug === demand.slug)
|
|
expect(row).toMatchObject({
|
|
title: demand.title,
|
|
status: 'aberta',
|
|
municipalityName: municipality.name,
|
|
municipalitySlug: municipality.slug,
|
|
})
|
|
expect(open.openCount).toBeGreaterThanOrEqual(1)
|
|
expect(open.totalPages).toBeGreaterThanOrEqual(1)
|
|
|
|
const closed = await loadDemandListPageData(payload, coordinator, {
|
|
page: 1,
|
|
status: 'rejeitada',
|
|
})
|
|
expect(closed.rows.some((candidate) => candidate.slug === demand.slug)).toBe(false)
|
|
})
|
|
|
|
it('rejects leaders (demands are staff-only)', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const leader = await fixtures.createCampaignUser('leader')
|
|
|
|
await expect(loadDemandListPageData(payload, leader, { page: 1 })).rejects.toThrow(/permissão/i)
|
|
})
|
|
|
|
it('narrows by free-text search on title and requester name', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const municipality = await fixtures.getMunicipality()
|
|
const marker = fixtures.value('demand-q')
|
|
|
|
const contact = await fixtures.createContact({ name: `${marker} solicitante` })
|
|
const leadership = await fixtures.createLeadership({
|
|
contact: contact.id,
|
|
municipalities: [municipality.id],
|
|
})
|
|
|
|
const byTitle = await fixtures.createCampaignDemand({
|
|
municipality: municipality.id,
|
|
title: `${marker} banner comitê`,
|
|
})
|
|
const byRequester = await fixtures.createCampaignDemand({
|
|
municipality: municipality.id,
|
|
leadership: leadership.id,
|
|
title: `${marker} outra demanda`,
|
|
})
|
|
await fixtures.createCampaignDemand({
|
|
municipality: municipality.id,
|
|
title: `${marker} sem match`,
|
|
})
|
|
|
|
const titleHits = await loadDemandListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: 'banner comitê',
|
|
})
|
|
expect(titleHits.rows.some((row) => row.slug === byTitle.slug)).toBe(true)
|
|
expect(titleHits.rows.some((row) => row.slug === byRequester.slug)).toBe(false)
|
|
|
|
const requesterHits = await loadDemandListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: 'solicitante',
|
|
})
|
|
expect(requesterHits.rows.some((row) => row.slug === byRequester.slug)).toBe(true)
|
|
expect(requesterHits.rows.some((row) => row.slug === byTitle.slug)).toBe(false)
|
|
|
|
const noMatch = await loadDemandListPageData(payload, coordinator, {
|
|
page: 1,
|
|
q: 'zzznomatch',
|
|
})
|
|
expect(noMatch.rows.some((row) => row.slug === byTitle.slug)).toBe(false)
|
|
expect(noMatch.rows.some((row) => row.slug === byRequester.slug)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('loadAdvisorListPageData', () => {
|
|
it('lists only advisor accounts, narrowed by q, with portfolio municipalities', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const advisor = await fixtures.createCampaignUser('advisor')
|
|
// A coordinator sharing the marker must not appear (role filter).
|
|
await fixtures.createCampaignUser('coordinator', {
|
|
name: `${advisor.name} coordenando`,
|
|
})
|
|
const municipality = await fixtures.getMunicipality()
|
|
await fixtures.assignMunicipalityAdvisors(municipality.id, [advisor.id])
|
|
|
|
const result = await loadAdvisorListPageData(payload, { page: 1, q: advisor.name })
|
|
|
|
expect(result.totalDocs).toBe(1)
|
|
expect(result.rows[0]).toMatchObject({ id: advisor.id, name: advisor.name })
|
|
// Ids only: the list's chips resolve their labels from the static catalog.
|
|
expect(result.rows[0]!.municipalityIDs).toEqual([municipality.id])
|
|
})
|
|
|
|
it('narrows by municipality portfolio with OR semantics', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const advisorA = await fixtures.createCampaignUser('advisor')
|
|
const advisorB = await fixtures.createCampaignUser('advisor')
|
|
const municipalityA = await fixtures.getMunicipality()
|
|
const municipalityB = await fixtures.getMunicipality()
|
|
await fixtures.assignMunicipalityAdvisors(municipalityA.id, [advisorA.id])
|
|
await fixtures.assignMunicipalityAdvisors(municipalityB.id, [advisorB.id])
|
|
|
|
const onlyA = await loadAdvisorListPageData(payload, {
|
|
page: 1,
|
|
municipalities: [municipalityA.id],
|
|
})
|
|
expect(onlyA.totalDocs).toBe(1)
|
|
expect(onlyA.rows[0]?.id).toBe(advisorA.id)
|
|
|
|
const either = await loadAdvisorListPageData(payload, {
|
|
page: 1,
|
|
municipalities: [municipalityA.id, municipalityB.id],
|
|
})
|
|
expect(either.totalDocs).toBe(2)
|
|
expect(either.rows.map((row) => row.id).sort()).toEqual([advisorA.id, advisorB.id].sort())
|
|
|
|
const none = await loadAdvisorListPageData(payload, {
|
|
page: 1,
|
|
municipalities: [999_999],
|
|
})
|
|
expect(none.totalDocs).toBe(0)
|
|
})
|
|
})
|