import type { Page } from '@playwright/test' import { expect, test } from './fixtures/campaignE2EFixtures.js' /** * B18 — a named recorte of `/campanha/municipios` becomes a sidebar shortcut. * * Own file for the same reason B17 took one: `campaignMunicipalities.e2e.spec.ts` * carries deterministic failures at one worker, ledgered as P1, and a red run * there must not be read as a regression here. */ /** 416 whole municipalities — deep enough that `?page=2` is a real page. */ const SAVED_RECORTE = '/campanha/municipios?priority=alta' const SAVED_NAME = 'Recorte do teste' const RENAMED = 'Recorte renomeado' /** * A route transition on this list costs a full RSC round-trip, and in dev the * suite's two workers share one server — which overruns the 5 s default of * `expect` and produces a flake nobody reproduces at one worker. Same budget, * for the same reason, as `campaignColumnPicker.e2e.spec.ts`. */ const NAVIGATION = { timeout: 20_000 } /** * The nav link, scoped to the desktop sidebar: the same name also exists in the * mobile bottom nav, so a bare `getByRole` violates strict mode (#669, OPS83). */ const municipalityNavLink = (page: Page) => page.locator('[data-slot="sidebar"]').getByRole('link', { name: 'Municípios', exact: true }) test.describe('Filtros salvos de Municípios', () => { test('saves the current recorte, restores it from the sidebar and deletes it', async ({ campaign, page, }) => { test.slow() const { fixtures } = campaign const coordinator = await fixtures.createCampaignUser('coordinator', { name: fixtures.value('Coordenadora Filtros'), }) const password = coordinator.password const email = coordinator.email! await campaign.login(page, email, password) // The bare list has no recorte to name. await page.goto('/campanha/municipios') await expect(page.getByRole('button', { name: 'Salvar filtro', exact: true })).toHaveCount(0) await page.goto(SAVED_RECORTE) await page.getByRole('button', { name: 'Salvar filtro', exact: true }).click() const nameInput = page.getByLabel('Nome do filtro') // The suggested name is the active-filters summary the bar already shows. await expect(nameInput).not.toHaveValue('') await nameInput.fill(SAVED_NAME) await page.getByRole('button', { name: 'Salvar', exact: true }).click() const shortcut = page.getByRole('link', { name: SAVED_NAME }) await expect(shortcut).toBeVisible() await expect(shortcut).toHaveAttribute('aria-current', 'page') // A page is a position inside the recorte, not part of it. await page.goto(`${SAVED_RECORTE}&page=2`, NAVIGATION) await expect(shortcut).toHaveAttribute('aria-current', 'page') // Another recorte is not this one, and the shortcut brings it back. Use the // sidebar link — a bare `goto` here races an in-flight RSC navigation and // flakes as net::ERR_ABORTED under prod load (measured in CI 2026-07-30). await municipalityNavLink(page).click() await expect(page).toHaveURL('/campanha/municipios', NAVIGATION) await expect(shortcut).not.toHaveAttribute('aria-current', 'page') await shortcut.click() await expect(page).toHaveURL(SAVED_RECORTE, NAVIGATION) await expect(shortcut).toHaveAttribute('aria-current', 'page', NAVIGATION) // Saving a recorte already bookmarked renames it instead of adding a twin. await page.getByRole('button', { name: `Renomear o filtro salvo ${SAVED_NAME}` }).click() await expect(page.getByLabel('Nome do filtro')).toHaveValue(SAVED_NAME) await page.getByLabel('Nome do filtro').fill(RENAMED) await page.getByRole('button', { name: 'Atualizar nome' }).click() await expect(page.getByRole('link', { name: RENAMED })).toBeVisible() await expect(shortcut).toHaveCount(0) const remove = page.getByRole('button', { name: `Apagar o filtro salvo ${RENAMED}` }) await remove.click() await expect(page.getByRole('link', { name: RENAMED })).toHaveCount(0) // Emptying the group unmounts the sub-list, so focus lands on the nav link // rather than falling to . await expect(municipalityNavLink(page)).toBeFocused() // Deleting is undoable, which is why it never asks for confirmation. await page.getByRole('button', { name: 'Desfazer' }).click() await expect(page.getByRole('link', { name: RENAMED })).toBeVisible() }) test('survives a reload with shortcuts always visible', async ({ campaign, page }) => { // Three loads of a heavy list route, against a dev server shared with the // other worker — the login redirect alone can outrun the 30 s default. test.slow() const { fixtures } = campaign const coordinator = await fixtures.createCampaignUser('coordinator', { name: fixtures.value('Coordenadora Sidebar'), }) const password = coordinator.password const email = coordinator.email! await campaign.login(page, email, password) await page.goto(SAVED_RECORTE) await page.getByRole('button', { name: 'Salvar filtro', exact: true }).click() await page.getByLabel('Nome do filtro').fill(SAVED_NAME) await page.getByRole('button', { name: 'Salvar', exact: true }).click() const shortcut = page.getByRole('link', { name: SAVED_NAME }) await expect(shortcut).toBeVisible() await page.reload() await expect(shortcut).toBeVisible(NAVIGATION) await expect(page.getByRole('list', { name: 'Filtros salvos de Municípios' })).toBeVisible() await expect(page.getByRole('button', { name: /filtros salvos de Municípios/i })).toHaveCount(0) }) test.describe('C102 — saved filters live in the "Mais" drawer on mobile', () => { test.use({ viewport: { width: 390, height: 844 } }) test('saves, navigates from and deletes inside the drawer', async ({ campaign, page }) => { test.slow() const { fixtures } = campaign const coordinator = await fixtures.createCampaignUser('coordinator', { name: fixtures.value('Coordenadora Drawer Mobile'), }) const password = coordinator.password const email = coordinator.email! await campaign.login(page, email, password) await page.goto(SAVED_RECORTE) await page.getByRole('button', { name: 'Salvar filtro', exact: true }).click() await page.getByLabel('Nome do filtro').fill(SAVED_NAME) await page.getByRole('button', { name: 'Salvar', exact: true }).click() // The mobile staff top bar has no hamburger (C102) — the drawer is the home. const topBar = page.locator('[data-slot="campaign-mobile-top-bar"]') await expect(topBar.locator('[data-slot="sidebar-trigger"]')).toHaveCount(0) const bottomNav = page.locator('[aria-label="Navegação principal"]') await bottomNav.getByRole('button', { name: 'Mais' }).click() const drawer = page.locator('[data-slot="drawer-content"]') await expect(drawer).toBeVisible() await expect(drawer.getByText('Filtros salvos de Municípios')).toBeVisible() const shortcut = drawer.getByRole('link', { name: SAVED_NAME }) await expect(shortcut).toBeVisible() await expect(shortcut).toHaveAttribute('aria-current', 'page') // Tapping the row restores the recorte and closes the drawer. await shortcut.click() await expect(page).toHaveURL(SAVED_RECORTE, NAVIGATION) await expect(drawer).toBeHidden() // Delete and undo keep working in the drawer. await bottomNav.getByRole('button', { name: 'Mais' }).click() await drawer.getByRole('button', { name: `Apagar o filtro salvo ${SAVED_NAME}` }).click() await expect(drawer.getByRole('link', { name: SAVED_NAME })).toHaveCount(0) await page.getByRole('button', { name: 'Desfazer' }).click() await expect(drawer.getByRole('link', { name: SAVED_NAME })).toBeVisible() }) }) })