diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index ebf219f8..15549fe9 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente --- +**S6** — home de campanha (jorgesolla1313.com.br) sem carrossel no desktop: as seções "Por que essa eleição importa" (3 cards) e "Nossa caminhada" (6 cards) mostram agora todo o conteúdo de uma vez em ≥1024px — grade estática 3 colunas (problem: 3×437px, bandeiras: 3×2 com altura por conteúdo, padding `p-4`), sem auto-avanço, sem chips/indicadores e sem `aria-current`. Mobile (<1024px) 100% preservado: carrossel com auto-avanço ~4s, chips sincronizados, swipe — componente e geometria mobile intocados. Padrão S1 (renderização-irmã com toggle CSS `hidden lg:grid`/`lg:hidden`): grade server-rendered (zero JS no desktop) + carrossel client embrulhado; os dois cards extraídos do `CampaignCarousel` para `CampaignProblemCard`/`CampaignFlagCard` (presentacionais, fonte única do markup, `size='spacious'` só na grade) e o contrato da grade (data-grid + aria-label + 3 colunas) vive num `CampaignCardGrid` local do page. O carrossel ficou byte-equivalente (só re-roteia os cards; unit tests intocados) e ganhou guard de `display:none` no auto-avanço — o timer nunca roda no desktop (chain morto numa subárvore oculta). Geometria: a seção flags recalibrada no desktop (`height: clamp(418px, calc(807.7px - 28.13vw), 520px)`, âncoras medidas em 1024/1280/1440+, respiro ≥30px, `overflow: hidden` para zoom de texto ≥125% não pintar sobre o footer); a seção problem manteve a geometria (respiro ~31px). Zero schema/migration/Consent; mesma copy/imagens/ordem; S1 e o resto da home intocados. Verificação: unit 2318 + int 791 verdes, e2e frontend 11/11 (novo teste de grade desktop com invariantes 3×1/3×2, ausência de regiões/chips/`aria-current`, respiro ≥24 e `scrollHeight ≤ clientHeight` em 1024/1280/1440/1920; teste de carrossel migrado para viewport mobile 390px com auto-avanço + chips; swipe/mobileSpacing intactos), lint/format/typecheck/knip/cycles/build verdes. `/simplify` 3 reviewers: `lg:` órfãos removidos do carrossel, grade extraída em `CampaignCardGrid`, `overflow: hidden` (zoom ≥125%), guard do timer, `document.fonts.ready` + eixo x + pino de clip no e2e, âncoras no comentário do clamp. Débitos: Issue #58 registrada (S6-FOLLOWUP — CLI do Playwright 1.58.2 ignora `--grep`/posicionais e o `test:e2e:affected` roda a suíte completa; `depends: [S6]`); defer com gatilho para os literais de gap da grade (recalibração Penpot do carrossel); descartados contratos de tipo dos cards (armadilha knip), aria-labels duplicados (copy pinada no e2e) e pino de tablet 768–1023. Impl plan: `docs/plans/home-campanha-sem-carrossel-desktop-impl.md`. + **S5** — separação visual da seção "Acompanhe de perto" da prova social na home de campanha: o `
` sai de `bg-white` (idêntico à faixa de números, seções coladas) para `border-y border-(--campaign-line) bg-(--campaign-band)` — banda `#ebe9e9` da paleta + hairlines de 12%, o desenho aprovado no wireframe (seção 7: zona de cor própria com `border-top/bottom: 1px solid var(--line)`) e no rascunho UI do gate (Cena 1/3 recomendadas). Zero mudança de conteúdo/dados/comportamento: cards continuam brancos e saltam na banda; fail-closed intacto (sem posts visíveis a seção some e Prova → Problema voltam contíguos — teste e2e existente segue verde); nenhum token novo; sem migration/Consent/access. Única mudança de runtime: 1 linha de className; + asserções `toHaveCSS` no e2e full-state da seção (fundo `#ebe9e9` exato — pega token quebrado em vez de só "≠ branco", hairline top `1px` com cor `rgb(0 0 0 / 12%)` exata, cards brancos) — sem screenshot diff (repo não usa regressão visual). Verificação: tsc/lint/format/unit 2318 verdes; e2e frontend: empty-state (contiguidade) e bento passaram com as asserções novas (2/2 runs que as alcançaram); o teste full-state falhou localmente no clique pós-minhas-asserções por cold-compile dev sob load ~68 — baseline com as mudanças stashed falha idêntico (falha pré-existente do ambiente, não regressão); build verde (migrations ok). knip local = limitação P3 conhecida (não carrega `payload.config.ts`); CI valida. **S5** — separação visual da seção "Acompanhe de perto" da prova social na home de campanha: o `
` sai de `bg-white` (idêntico à faixa de números, seções coladas) para `border-y border-(--campaign-line) bg-(--campaign-band)` — banda `#ebe9e9` da paleta + hairlines de 12%, o desenho aprovado no wireframe (seção 7: zona de cor própria com `border-top/bottom: 1px solid var(--line)`) e no rascunho UI do gate (Cena 1/3 recomendadas). Zero mudança de conteúdo/dados/comportamento: cards continuam brancos e saltam na banda; fail-closed intacto (sem posts visíveis a seção some e Prova → Problema voltam contíguos — teste e2e existente segue verde); nenhum token novo; sem migration/Consent/access. Única mudança de runtime: 1 linha de className; + asserções de estilo computado no e2e full-state da seção (fundo ≠ branco ≠ prova, hairline top presente, cards brancos) — sem screenshot diff (repo não usa regressão visual). Verificação: tsc/lint/format/unit 2318 verdes; e2e frontend: empty-state (contiguidade) e bento passaram com as asserções novas (2/2 runs que as alcançaram); o teste full-state falhou localmente no clique pós-minhas-asserções por cold-compile dev sob load ~68 — baseline com as mudanças stashed falha idêntico (falha pré-existente do ambiente, não regressão); build verde (migrations ok). knip local = limitação P3 conhecida (não carrega `payload.config.ts`); CI valida. diff --git a/docs/changelog/2026-08-18-s6.md b/docs/changelog/2026-08-18-s6.md new file mode 100644 index 00000000..9356bd68 --- /dev/null +++ b/docs/changelog/2026-08-18-s6.md @@ -0,0 +1 @@ +**S6** — home de campanha (jorgesolla1313.com.br) sem carrossel no desktop: as seções "Por que essa eleição importa" (3 cards) e "Nossa caminhada" (6 cards) mostram agora todo o conteúdo de uma vez em ≥1024px — grade estática 3 colunas (problem: 3×437px, bandeiras: 3×2 com altura por conteúdo, padding `p-4`), sem auto-avanço, sem chips/indicadores e sem `aria-current`. Mobile (<1024px) 100% preservado: carrossel com auto-avanço ~4s, chips sincronizados, swipe — componente e geometria mobile intocados. Padrão S1 (renderização-irmã com toggle CSS `hidden lg:grid`/`lg:hidden`): grade server-rendered (zero JS no desktop) + carrossel client embrulhado; os dois cards extraídos do `CampaignCarousel` para `CampaignProblemCard`/`CampaignFlagCard` (presentacionais, fonte única do markup, `size='spacious'` só na grade) e o contrato da grade (data-grid + aria-label + 3 colunas) vive num `CampaignCardGrid` local do page. O carrossel ficou byte-equivalente (só re-roteia os cards; unit tests intocados) e ganhou guard de `display:none` no auto-avanço — o timer nunca roda no desktop (chain morto numa subárvore oculta). Geometria: a seção flags recalibrada no desktop (`height: clamp(418px, calc(807.7px - 28.13vw), 520px)`, âncoras medidas em 1024/1280/1440+, respiro ≥30px, `overflow: hidden` para zoom de texto ≥125% não pintar sobre o footer); a seção problem manteve a geometria (respiro ~31px). Zero schema/migration/Consent; mesma copy/imagens/ordem; S1 e o resto da home intocados. Verificação: unit 2318 + int 791 verdes, e2e frontend 11/11 (novo teste de grade desktop com invariantes 3×1/3×2, ausência de regiões/chips/`aria-current`, respiro ≥24 e `scrollHeight ≤ clientHeight` em 1024/1280/1440/1920; teste de carrossel migrado para viewport mobile 390px com auto-avanço + chips; swipe/mobileSpacing intactos), lint/format/typecheck/knip/cycles/build verdes. `/simplify` 3 reviewers: `lg:` órfãos removidos do carrossel, grade extraída em `CampaignCardGrid`, `overflow: hidden` (zoom ≥125%), guard do timer, `document.fonts.ready` + eixo x + pino de clip no e2e, âncoras no comentário do clamp. Débitos: Issue #58 registrada (S6-FOLLOWUP — CLI do Playwright 1.58.2 ignora `--grep`/posicionais e o `test:e2e:affected` roda a suíte completa; `depends: [S6]`); defer com gatilho para os literais de gap da grade (recalibração Penpot do carrossel); descartados contratos de tipo dos cards (armadilha knip), aria-labels duplicados (copy pinada no e2e) e pino de tablet 768–1023. Impl plan: `docs/plans/home-campanha-sem-carrossel-desktop-impl.md`. diff --git a/docs/plans/home-campanha-sem-carrossel-desktop-impl.md b/docs/plans/home-campanha-sem-carrossel-desktop-impl.md new file mode 100644 index 00000000..3ceba19c --- /dev/null +++ b/docs/plans/home-campanha-sem-carrossel-desktop-impl.md @@ -0,0 +1,162 @@ +# Impl: S6 — Home de campanha sem carrossel no desktop + +Status: aprovado +Atualizado em: 2026-08-18 +Issue: #34 +Intenção: docs/plans/home-campanha-sem-carrossel-desktop.md +Appetite restante: herdado (~0,5–1 dia eng; só layout, sem dados novos) + +## Leitura da intenção + +- **Outcome:** no desktop (≥1024px) as seções "Por que essa eleição importa" (3 cards) e + "Nossa caminhada" (6 cards) mostram todo o conteúdo de uma vez — grade estática, sem + auto-avanço, sem rolagem oculta, sem indicadores/chips; mobile (<1024px) 100% preservado. +- **O que NÃO negociar:** mobile intocado (comportamento e geometria); mesma copy/imagens/ordem; + sem redesenho de cards; seção "Acompanhe de perto" (S1) fora do escopo; zero schema/migration/ + Consent/dados. +- **Questões em aberto da intenção (assumidas, validar no gate):** chips somem no desktop (A); + cards de bandeira com altura por conteúdo e respiro (A); breakpoint `lg` = 1024px (A); a seção + dos 6 cards é "Nossa caminhada" (confirmado — eyebrow atual no site). +- **O que reavaliar:** a hipótese "variante estática em `lg` dentro do `CampaignCarousel`" — + **rejeitada**: o `CampaignCarousel` é o módulo pinado à geometria Penpot de auto-avanço + (precedente S1: "not a variant of `CampaignCarousel`"); o padrão consagrado no repo para + "bento/grid no desktop + carrossel no mobile" é renderização-irmã com toggle CSS (`hidden md:grid` + - `md:hidden` em `CampaignContentSection`). Ver Decisão 1. + +## Abordagem recomendada + +```mermaid +flowchart LR + subgraph page[page.tsx — server] + A[problemItems/flagItems] --> B[Grade estática desktop
hidden lg:grid] + A --> C[Carrossel mobile existente
lg:hidden] + end + B --> D[CampaignProblemCard / CampaignFlagCard
components presentacionais compartilhados] + C --> D + E[styles.css
@media min-width 64rem
.campaign-flags height recalibrada] --> B +``` + +**Opções consideradas:** A | B | C | D +**Recomendação:** **A — renderizações-irmãs no `page.tsx`** (grade estática server-rendered +`hidden lg:grid` + carrossel atual embrulhado em `lg:hidden`), com os dois cards extraídos para +components presentacionais compartilhados — exatamente o padrão S1 (`CampaignContentSection` + +`CampaignArticleCard`). +**Rejeitadas:** + +- **B) track único vira grade via CSS dentro do `CampaignCarousel`** (`lg:grid lg:overflow-visible` + - matchMedia para suprimir JS/a11y no desktop). O S1 já rejeitou estender esse módulo para um + layout diferente ("pinned to the Penpot auto-advancing geometry"); a troca de semântica a11y + (`aria-roledescription="carrossel"`, "N de M") não é expressável em CSS — exigiria estado de + matchMedia com flip pós-hidratação — e o componente é pinado por unit + e2e de swipe. +- **C) desmontar o carrossel no desktop** (wrapper client com matchMedia). Custo: componente + wrapper novo + dupla renderização pós-hidratação para matar um timer invisível de ~1 re-render/ + 4s numa subárvore `display:none` — o mesmo custo que o S1 já aceita (carrossel montado oculto + no desktop, sem timer por não ter auto-avanço). Não vale o movimento. +- **D) duplicar o JSX dos cards inline no `page.tsx`** (grade copiada, carrossel intocado). + Cria duas fontes de verdade do markup do card (~50 linhas) com risco de drift em qualquer + mudança visual futura. O S1 extraiu o card (`CampaignArticleCard`) — a extração é o dono do + concern. + +### Componentes / mudanças + +- **`CampaignProblemCard`** (`src/components/CampaignProblemCard.tsx`, server/presentacional): + o artigo do card `problem` (imagem `fill`/`imageFrame`, gradiente, título+corpo overlay) — + extraído verbatim do `li` do carrossel; recebe `item` (`CampaignCarouselItem`). +- **`CampaignFlagCard`** (`src/components/CampaignFlagCard.tsx`, server/presentacional): o artigo + do card `flags` (título+corpo) — extraído verbatim; recebe `item` + prop `size: 'compact' | +'spacious'` (padding `px-3.5 py-2.5` no carrossel — mobile intocado — e `p-4` na grade, + conforme o rascunho UI aprovado). +- **`CampaignCarousel`** (`src/components/CampaignCarousel.tsx`): SÓ re-roteia o markup dos cards + para os dois components extraídos; classes, estado, timers e a11y byte-idênticos. Unit tests + existentes permanecem sem mudança. +- **`page.tsx`** (`src/app/(frontend)/(home)/page.tsx`): dentro de cada wrapper absoluto existente + (`.campaign-problem-carousel` / `.campaign-flags-carousel`), renderiza: + - grade desktop: `
@@ -163,11 +206,24 @@ export default async function HomePage() { Ministério da Saúde e da saúde da Bahia.

- + gapClassName="gap-[14px]" + > + {flagItems.map((item) => ( +
  • + +
  • + ))} + +
    + +
    diff --git a/src/app/(frontend)/styles.css b/src/app/(frontend)/styles.css index ec6f6f6b..1400734d 100644 --- a/src/app/(frontend)/styles.css +++ b/src/app/(frontend)/styles.css @@ -460,7 +460,18 @@ body, } } +/* Desktop grid recalibration (S6): the flags section grows so both grid rows + of content-height cards fit with breathing room. Fluid clamp between the + measured anchors: ~520px at 1024px (narrow columns, 3-line card bodies) → + ~418px from ~1385px up (columns saturate at the content max width). + `overflow: hidden` keeps text-zoom growth (≥125%) inside the band instead + of painting the cards over the footer. */ @media (min-width: 64rem) { + .campaign-flags { + height: clamp(418px, calc(807.7px - 28.13vw), 520px); + overflow: hidden; + } + [data-theme='campaign-site'] { --campaign-desktop-portrait-width: clamp(465px, calc(26.45vw + 194.15px), 702px); --campaign-desktop-portrait-height: clamp(517px, calc(29.41vw + 215.84px), 781px); diff --git a/src/components/CampaignCarousel.tsx b/src/components/CampaignCarousel.tsx index 8c17af1c..25ae74a4 100644 --- a/src/components/CampaignCarousel.tsx +++ b/src/components/CampaignCarousel.tsx @@ -1,6 +1,7 @@ 'use client' -import Image from 'next/image' +import { CampaignFlagCard } from '@/components/CampaignFlagCard' +import { CampaignProblemCard } from '@/components/CampaignProblemCard' import { useCallback, useEffect, useRef, useState } from 'react' const AUTO_ADVANCE_MS = 4_000 @@ -83,6 +84,9 @@ export const CampaignCarousel = ({ ariaLabel, items, variant }: CampaignCarousel useEffect(() => { if (prefersReducedMotion || temporarilyPaused || items.length < 2) return + // S6: on desktop the carousel lives inside an `lg:hidden` wrapper — its + // auto-advance chain must not keep re-rendering a display:none subtree. + if (trackRef.current && window.getComputedStyle(trackRef.current).display === 'none') return const timer = window.setTimeout(() => { scrollToItem((activeIndex + 1) % items.length) @@ -142,7 +146,7 @@ export const CampaignCarousel = ({ ariaLabel, items, variant }: CampaignCarousel
    {items.map((item, index) => { const isActive = index === activeIndex @@ -176,7 +180,7 @@ export const CampaignCarousel = ({ ariaLabel, items, variant }: CampaignCarousel onScroll={syncActiveItem} data-carousel-track className={`scrollbar-hide m-0 flex list-none snap-x snap-mandatory overflow-x-auto scroll-smooth p-0 ${ - variant === 'problem' ? 'gap-2.5 lg:gap-[21px]' : 'mt-5 gap-2.5 lg:gap-[14px]' + variant === 'problem' ? 'gap-2.5' : 'mt-5 gap-2.5' }`} > {items.map((item, index) => ( @@ -187,45 +191,14 @@ export const CampaignCarousel = ({ ariaLabel, items, variant }: CampaignCarousel aria-current={index === activeIndex ? 'true' : undefined} className={`m-0 shrink-0 snap-start ${ variant === 'problem' - ? 'h-[437px] w-[calc(100vw_-_38px)] max-w-[355px] lg:w-[355px]' - : 'h-[100px] w-[calc(100vw_-_66px)] max-w-[327px] lg:w-[327px]' + ? 'h-[437px] w-[calc(100vw_-_38px)] max-w-[355px]' + : 'h-[100px] w-[calc(100vw_-_66px)] max-w-[327px]' }`} > {variant === 'problem' ? ( -
    - {item.image && item.imageFrame ? ( - {item.imageAlt - ) : item.image ? ( - {item.imageAlt - ) : null} -
    -
    -

    - {item.title} -

    -

    {item.body}

    -
    -
    + ) : ( -
    -

    - {item.title} -

    -

    {item.body}

    -
    + )} ))} diff --git a/src/components/CampaignFlagCard.tsx b/src/components/CampaignFlagCard.tsx new file mode 100644 index 00000000..1e7ad306 --- /dev/null +++ b/src/components/CampaignFlagCard.tsx @@ -0,0 +1,24 @@ +type CampaignFlagCardItem = { + id: string + title: string + body: string +} + +type CampaignFlagCardProps = { + item: CampaignFlagCardItem + size?: 'compact' | 'spacious' +} + +export const CampaignFlagCard = ({ item, size = 'compact' }: CampaignFlagCardProps) => { + const padding = size === 'spacious' ? 'p-4' : 'px-3.5 py-2.5' + return ( +
    +

    + {item.title} +

    +

    {item.body}

    +
    + ) +} diff --git a/src/components/CampaignProblemCard.tsx b/src/components/CampaignProblemCard.tsx new file mode 100644 index 00000000..3ff602f5 --- /dev/null +++ b/src/components/CampaignProblemCard.tsx @@ -0,0 +1,44 @@ +import Image from 'next/image' + +export type CampaignProblemCardItem = { + id: string + title: string + body: string + image?: string + imageAlt?: string + imageFrame?: { + width: number + height: number + className: string + } +} + +export const CampaignProblemCard = ({ item }: { item: CampaignProblemCardItem }) => ( +
    + {item.image && item.imageFrame ? ( + {item.imageAlt + ) : item.image ? ( + {item.imageAlt + ) : null} +
    +
    +

    + {item.title} +

    +

    {item.body}

    +
    +
    +) diff --git a/tests/e2e/frontend.e2e.spec.ts b/tests/e2e/frontend.e2e.spec.ts index 6414c62e..ee533365 100644 --- a/tests/e2e/frontend.e2e.spec.ts +++ b/tests/e2e/frontend.e2e.spec.ts @@ -350,9 +350,93 @@ test.describe('Frontend', () => { } }) - test('auto-advances and keeps carousel controls synchronized', async ({ page }) => { + test('shows the campaign message sections as static grids on desktop', async ({ page }) => { await page.goto('/') + const problemGrid = page.locator('[data-home-section="problem"] [data-grid="problem"]') + await expect(problemGrid).toBeVisible() + await expect(problemGrid.getByRole('listitem')).toHaveCount(3) + const problemBoxes = await problemGrid + .getByRole('listitem') + .evaluateAll((items) => items.map((item) => item.getBoundingClientRect())) + expect(new Set(problemBoxes.map((box) => Math.round(box.y))).size).toBe(1) + expect(new Set(problemBoxes.map((box) => Math.round(box.x))).size).toBe(3) + expect(problemBoxes.every((box) => box.width > 0 && box.height > 0)).toBe(true) + + const flagsGrid = page.locator('[data-home-section="flags"] [data-grid="flags"]') + await expect(flagsGrid).toBeVisible() + await expect(flagsGrid.getByRole('listitem')).toHaveCount(6) + const flagsBoxes = await flagsGrid + .getByRole('listitem') + .evaluateAll((items) => items.map((item) => item.getBoundingClientRect())) + expect(new Set(flagsBoxes.map((box) => Math.round(box.x))).size).toBe(3) + expect(new Set(flagsBoxes.map((box) => Math.round(box.y))).size).toBe(2) + + // No carousel chrome on desktop: regions and chips are display:none (out + // of the a11y tree) and the hidden carousel never starts its auto-advance + // chain, so no aria-current state exists anywhere in the grids. + await expect( + page.getByRole('region', { name: 'Bandeiras que tornam esta eleição decisiva' }), + ).toHaveCount(0) + await expect(page.getByRole('region', { name: 'Bandeiras da campanha' })).toHaveCount(0) + await expect(page.locator('[data-carousel-chips]')).toBeHidden() + await expect(problemGrid.locator('[aria-current]')).toHaveCount(0) + await expect(flagsGrid.locator('[aria-current]')).toHaveCount(0) + + // The grids stay inside their sections with breathing room at every + // desktop width, no card is clipped (no hidden scroll) and nothing + // overflows the page horizontally. Fonts settle first: the flags cards + // are content-height, so a fallback-font measurement could differ from + // the real layout. + await page.evaluate(() => document.fonts.ready) + for (const width of [1024, 1280, 1440, 1920]) { + await page.setViewportSize({ width, height: 900 }) + const spacing = await page.evaluate(() => { + const section = (selector: string) => { + const element = document.querySelector(selector) + if (!element) throw new Error(`Seção ausente: ${selector}`) + return element.getBoundingClientRect() + } + const gridBottom = (selector: string) => { + const grid = document.querySelector(selector) + if (!grid) throw new Error(`Grade ausente: ${selector}`) + const cards = Array.from(grid.querySelectorAll('li')) + return Math.max(...cards.map((card) => card.getBoundingClientRect().bottom)) + } + const gridClipped = (selector: string) => { + const grid = document.querySelector(selector) + if (!grid) throw new Error(`Grade ausente: ${selector}`) + return grid.scrollHeight - grid.clientHeight + } + const problem = section('[data-home-section="problem"]') + const flags = section('[data-home-section="flags"]') + const scrollContainer = document.querySelector('[data-theme="campaign-site"]') + return { + problemBottomGap: + problem.bottom - gridBottom('[data-home-section="problem"] [data-grid]'), + flagsBottomGap: flags.bottom - gridBottom('[data-home-section="flags"] [data-grid]'), + problemClipped: gridClipped('[data-home-section="problem"] [data-grid]'), + flagsClipped: gridClipped('[data-home-section="flags"] [data-grid]'), + horizontalOverflow: scrollContainer + ? scrollContainer.scrollWidth - scrollContainer.clientWidth + : 0, + } + }) + expect(spacing.problemBottomGap).toBeGreaterThanOrEqual(24) + expect(spacing.flagsBottomGap).toBeGreaterThanOrEqual(24) + expect(spacing.problemClipped).toBeLessThanOrEqual(1) + expect(spacing.flagsClipped).toBeLessThanOrEqual(1) + expect(spacing.horizontalOverflow).toBeLessThanOrEqual(1) + } + }) + + test('keeps carousel auto-advance and chips on mobile', async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }) + await page.goto('/') + + await expect(page.locator('[data-home-section="problem"] [data-grid]')).toBeHidden() + await expect(page.locator('[data-home-section="flags"] [data-grid]')).toBeHidden() + const problemCarousel = page.getByRole('region', { name: 'Bandeiras que tornam esta eleição decisiva', }) diff --git a/tests/unit/campaignHome.unit.spec.tsx b/tests/unit/campaignHome.unit.spec.tsx index 1b6036c5..e2952dcf 100644 --- a/tests/unit/campaignHome.unit.spec.tsx +++ b/tests/unit/campaignHome.unit.spec.tsx @@ -45,11 +45,18 @@ describe('Campaign home', () => { it('usa exatamente as seis bandeiras aprovadas', async () => { render(await HomePage()) - expect(screen.getByText('Defender o SUS e valorizar quem cuida')).toBeTruthy() - expect(screen.getByText('Fim da escala 6×1 e jornada de 40h')).toBeTruthy() - expect(screen.getByText('Educação em tempo integral e federais no interior')).toBeTruthy() - expect(screen.getByText('Recomprar a Refinaria de Mataripe')).toBeTruthy() - expect(screen.getByText('Salário mínimo forte, emprego e moradia')).toBeTruthy() - expect(screen.getByText('Defesa intransigente da democracia')).toBeTruthy() + // S6: cada bandeira aparece em duas renderizações irmãs (grade desktop e + // carrossel mobile); o teste garante que as duas seguem a mesma copy. + const expectedTitles = [ + 'Defender o SUS e valorizar quem cuida', + 'Fim da escala 6×1 e jornada de 40h', + 'Educação em tempo integral e federais no interior', + 'Recomprar a Refinaria de Mataripe', + 'Salário mínimo forte, emprego e moradia', + 'Defesa intransigente da democracia', + ] + for (const title of expectedTitles) { + expect(screen.getAllByText(title)).toHaveLength(2) + } }) })