Substitui o modelo de MunicipalityUpdate (kind/semanal/urgente/nota/sinal + signalType) por um modelo unificado: body obrigatório + polarity (boa/neutra/ruim) + urgent + adversarySignal. - Schema: polarity enum obrigatório, urgent boolean default false, adversarySignal boolean - Wizard: novo WizardUpdateBodyStep com campo de texto + polaridade + urgente - Lista: controle unificado de atualização com polaridade inline - Feed: exibe polaridade como badge colorido + indicador de urgência - V2 status strip: atualização rápida de polaridade - E11: queries atualizadas para polarity + adversarySignal - Migração: 20260807_030000_unify_municipality_update (backfill polarity=neutra, urgent de kind, adversarySignal de kind+signalType) - Testes: 18/18 integração C87, 593/593 total, 1399/1399 unitários - Fix: testes de state deputy usavam nomes fixos que colidiam entre execuções
137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||
|
||
import { WizardExpectedVotesStep } from '@/components/campaign/shared/WizardExpectedVotesStep'
|
||
import { CampaignWizardChromeProvider } from '@/components/campaign/shell/CampaignWizardChromeContext'
|
||
import { CAMPAIGN_ACTIONS_HOME } from '@/lib/campaignActionRoutes'
|
||
import { postCampaignJson } from '@/lib/campaignJsonRequest'
|
||
|
||
const pushMock = vi.fn()
|
||
|
||
vi.mock('next/navigation', () => ({
|
||
useRouter: () => ({
|
||
push: pushMock,
|
||
replace: vi.fn(),
|
||
}),
|
||
}))
|
||
|
||
vi.mock('@/lib/campaignJsonRequest', () => ({
|
||
postCampaignJson: vi.fn(),
|
||
}))
|
||
|
||
vi.mock('sonner', () => ({
|
||
toast: { success: vi.fn() },
|
||
}))
|
||
|
||
const defaultProps = {
|
||
actionSlug: 'atualizar-votos',
|
||
municipalityId: 1,
|
||
municipalityName: 'Cairu',
|
||
municipalitySlug: 'cairu',
|
||
initialExpectedVotes: { pessimistic: 100, central: 200, optimistic: 300 },
|
||
}
|
||
|
||
const renderVotesStep = (props: React.ComponentProps<typeof WizardExpectedVotesStep>) =>
|
||
render(
|
||
<CampaignWizardChromeProvider>
|
||
<WizardExpectedVotesStep {...props} />
|
||
</CampaignWizardChromeProvider>,
|
||
)
|
||
|
||
describe('WizardExpectedVotesStep', () => {
|
||
afterEach(() => {
|
||
cleanup()
|
||
vi.mocked(postCampaignJson).mockReset()
|
||
pushMock.mockReset()
|
||
})
|
||
|
||
it('applies shortcuts to the focused scenario', () => {
|
||
renderVotesStep({ ...defaultProps })
|
||
|
||
fireEvent.focus(screen.getByLabelText('Pessimista'))
|
||
fireEvent.click(screen.getByRole('button', { name: '2×' }))
|
||
|
||
expect((screen.getByLabelText('Pessimista') as HTMLInputElement).value).toBe('200')
|
||
expect((screen.getByLabelText('Média') as HTMLInputElement).value).toBe('200')
|
||
})
|
||
|
||
it('highlights the last focused scenario in compact inputs', () => {
|
||
renderVotesStep({ ...defaultProps })
|
||
|
||
const central = screen.getByLabelText('Média')
|
||
const optimistic = screen.getByLabelText('Otimista')
|
||
|
||
expect(central.className).toMatch(/border-primary/)
|
||
expect(optimistic.className).toMatch(/bg-muted/)
|
||
|
||
fireEvent.focus(optimistic)
|
||
|
||
expect(central.className).toMatch(/bg-muted/)
|
||
expect(optimistic.className).toMatch(/border-primary/)
|
||
})
|
||
|
||
it('shows violation alert and highlights inputs without navigation', () => {
|
||
renderVotesStep({ ...defaultProps })
|
||
|
||
fireEvent.change(screen.getByLabelText('Pessimista'), { target: { value: '900' } })
|
||
fireEvent.click(screen.getByRole('button', { name: 'Salvar estimativas →' }))
|
||
|
||
const step = screen.getByRole('main', { name: /Ajustar votos estimados/i })
|
||
expect(within(step).getByRole('alert').textContent).toMatch(/Pessimista/i)
|
||
expect(screen.getByLabelText('Pessimista').getAttribute('aria-invalid')).toBe('true')
|
||
expect(screen.getByLabelText('Média').getAttribute('aria-invalid')).toBe('true')
|
||
})
|
||
|
||
it('clears violation when a draft is edited', () => {
|
||
renderVotesStep({ ...defaultProps })
|
||
|
||
fireEvent.change(screen.getByLabelText('Pessimista'), { target: { value: '900' } })
|
||
fireEvent.click(screen.getByRole('button', { name: 'Salvar estimativas →' }))
|
||
|
||
const step = screen.getByRole('main', { name: /Ajustar votos estimados/i })
|
||
expect(within(step).getByRole('alert')).toBeTruthy()
|
||
|
||
fireEvent.change(screen.getByLabelText('Média'), { target: { value: '950' } })
|
||
expect(within(step).queryByRole('alert')).toBeNull()
|
||
expect(screen.getByLabelText('Pessimista').getAttribute('aria-invalid')).toBeNull()
|
||
})
|
||
|
||
it('posts batch expected votes and continues the wizard chain', async () => {
|
||
vi.mocked(postCampaignJson).mockResolvedValue({
|
||
ok: true,
|
||
payload: {
|
||
status: 'success',
|
||
savedExpectedVotes: { pessimistic: 150, central: 250, optimistic: 350 },
|
||
},
|
||
})
|
||
|
||
renderVotesStep({ ...defaultProps })
|
||
|
||
fireEvent.change(screen.getByLabelText('Pessimista'), { target: { value: '150' } })
|
||
fireEvent.change(screen.getByLabelText('Média'), { target: { value: '250' } })
|
||
fireEvent.change(screen.getByLabelText('Otimista'), { target: { value: '350' } })
|
||
fireEvent.click(screen.getByRole('button', { name: 'Salvar estimativas →' }))
|
||
|
||
await waitFor(() => {
|
||
expect(postCampaignJson).toHaveBeenCalledWith('/campanha/municipios/expected-votes', {
|
||
municipalityId: 1,
|
||
expectedVotes: { pessimistic: 150, central: 250, optimistic: 350 },
|
||
})
|
||
})
|
||
|
||
await waitFor(() => {
|
||
expect(pushMock).toHaveBeenCalledWith(
|
||
`${CAMPAIGN_ACTIONS_HOME}/mudar-tendencia?municipio=cairu&entry=update-votes`,
|
||
)
|
||
})
|
||
})
|
||
|
||
it('shows skip to the next chain step when votes is chained', () => {
|
||
renderVotesStep({ ...defaultProps, entryAction: 'register-update' })
|
||
|
||
const skip = screen.getByRole('link', { name: /^Pular$/i })
|
||
expect(skip.getAttribute('href')).toBe(
|
||
`${CAMPAIGN_ACTIONS_HOME}/atualizar-lideranca?municipio=cairu&entry=register-update`,
|
||
)
|
||
})
|
||
})
|