Antes de la última revisión de etapa 4. Sprint 1. Previo al ajuste de look n feel de app, reportes, nombre y colores.
This commit is contained in:
157
tests/features/workspace/ActivityPanel.test.tsx
Normal file
157
tests/features/workspace/ActivityPanel.test.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Tests de la sección "Automatización" del ActivityPanel.
|
||||
*
|
||||
* Los mocks del store usan vi.hoisted() para crear referencias estables:
|
||||
* si el mock devuelve una nueva referencia de activities en cada render,
|
||||
* el useEffect de ActivityPanel entra en loop infinito.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent, act } from '@testing-library/react'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
|
||||
// ─── Mocks con referencias estables vía vi.hoisted ───────────────────────────
|
||||
|
||||
const processStoreMock = vi.hoisted(() => {
|
||||
const activity = {
|
||||
id: 'act-1', processId: 'p1', bpmnElementId: 'task1', name: 'Facturación',
|
||||
type: 'task' as const,
|
||||
directCostFixed: 200, executionTimeMinutes: 60, assignedResources: [],
|
||||
automatable: false, automatedCostFixed: 0, automatedTimeMinutes: 0,
|
||||
}
|
||||
return {
|
||||
activities: [activity], // referencia estable — no cambia entre renders
|
||||
resources: [],
|
||||
updateActivity: vi.fn(),
|
||||
subscribe: vi.fn(), // requerido por simulation-store.ts al importarse
|
||||
}
|
||||
})
|
||||
|
||||
const simulationStoreMock = vi.hoisted(() => ({
|
||||
resultActual: null as null | object,
|
||||
resultAutomated: null as null | object,
|
||||
invalidateResults: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/store/process-store', () => ({
|
||||
useProcessStore: () => processStoreMock,
|
||||
// Zustand también expone .subscribe() como método estático del hook
|
||||
useProcessStore_subscribe: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/store/simulation-store', () => ({
|
||||
useSimulationStore: () => simulationStoreMock,
|
||||
}))
|
||||
|
||||
import { ActivityPanel } from '@/features/workspace/ActivityPanel'
|
||||
|
||||
// ─── Helper ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function renderPanel(selectedElementId = 'task1') {
|
||||
return render(
|
||||
<TooltipProvider>
|
||||
<ActivityPanel selectedElementId={selectedElementId} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Tests ────────────────────────────────────────────────────────────────────
|
||||
|
||||
beforeEach(() => {
|
||||
// Resetear el mock de updateActivity y restaurar la actividad base
|
||||
processStoreMock.updateActivity.mockReset()
|
||||
processStoreMock.updateActivity.mockResolvedValue(undefined)
|
||||
processStoreMock.activities[0].automatable = false
|
||||
processStoreMock.activities[0].automatedCostFixed = 0
|
||||
processStoreMock.activities[0].automatedTimeMinutes = 0
|
||||
simulationStoreMock.resultActual = null
|
||||
simulationStoreMock.resultAutomated = null
|
||||
simulationStoreMock.invalidateResults.mockReset()
|
||||
})
|
||||
|
||||
describe('ActivityPanel — sección Automatización', () => {
|
||||
it('sección Automatización está presente y toggle en OFF por defecto', () => {
|
||||
renderPanel()
|
||||
expect(screen.getByText('¿Es automatizable?')).toBeInTheDocument()
|
||||
const toggle = screen.getByRole('switch', { name: /automatizable/i })
|
||||
expect(toggle).toHaveAttribute('aria-checked', 'false')
|
||||
})
|
||||
|
||||
it('Toggle ON: aria-checked cambia a true', async () => {
|
||||
renderPanel()
|
||||
const toggle = screen.getByRole('switch', { name: /automatizable/i })
|
||||
|
||||
await act(async () => { fireEvent.click(toggle) })
|
||||
|
||||
expect(toggle).toHaveAttribute('aria-checked', 'true')
|
||||
})
|
||||
|
||||
it('Toggle ON con ambos campos en 0: helper ámbar está en el DOM y visible (opacity-100)', async () => {
|
||||
renderPanel()
|
||||
const toggle = screen.getByRole('switch', { name: /automatizable/i })
|
||||
|
||||
await act(async () => { fireEvent.click(toggle) })
|
||||
|
||||
// El helper siempre está en el DOM — verificamos que el texto existe y el contenedor es visible
|
||||
const helperText = screen.getByText(/Transparencia metodológica/i)
|
||||
expect(helperText).toBeInTheDocument()
|
||||
// El contenedor padre tiene opacity-100 cuando showHelper=true
|
||||
const helperContainer = helperText.closest('[aria-hidden]')
|
||||
expect(helperContainer).toHaveAttribute('aria-hidden', 'false')
|
||||
})
|
||||
|
||||
it('Toggle ON con costo > 0: helper ámbar está en el DOM pero oculto (aria-hidden=true)', async () => {
|
||||
renderPanel()
|
||||
const toggle = screen.getByRole('switch', { name: /automatizable/i })
|
||||
await act(async () => { fireEvent.click(toggle) })
|
||||
|
||||
const costoInput = screen.getByLabelText(/Costo automatizado/i)
|
||||
await act(async () => {
|
||||
fireEvent.change(costoInput, { target: { value: '50' } })
|
||||
})
|
||||
|
||||
// El texto sigue en el DOM (transición CSS, no unmount)
|
||||
const helperText = screen.getByText(/Transparencia metodológica/i)
|
||||
expect(helperText).toBeInTheDocument()
|
||||
// Pero el contenedor está marcado aria-hidden=true (oculto visualmente)
|
||||
const helperContainer = helperText.closest('[aria-hidden]')
|
||||
expect(helperContainer).toHaveAttribute('aria-hidden', 'true')
|
||||
})
|
||||
|
||||
it('Editar costo automatizado y guardar llama updateActivity con el valor correcto', async () => {
|
||||
renderPanel()
|
||||
const toggle = screen.getByRole('switch', { name: /automatizable/i })
|
||||
await act(async () => { fireEvent.click(toggle) })
|
||||
|
||||
const costoInput = screen.getByLabelText(/Costo automatizado/i)
|
||||
await act(async () => {
|
||||
fireEvent.change(costoInput, { target: { value: '25' } })
|
||||
})
|
||||
|
||||
const guardarBtn = screen.getByRole('button', { name: /Guardar/i })
|
||||
await act(async () => { fireEvent.click(guardarBtn) })
|
||||
|
||||
expect(processStoreMock.updateActivity).toHaveBeenCalledOnce()
|
||||
const arg = processStoreMock.updateActivity.mock.calls[0][0]
|
||||
expect(arg.automatedCostFixed).toBe(25)
|
||||
expect(arg.automatable).toBe(true)
|
||||
})
|
||||
|
||||
it('Guardar llama updateActivity (la invalidación reactiva es responsabilidad del store)', async () => {
|
||||
renderPanel()
|
||||
const toggle = screen.getByRole('switch', { name: /automatizable/i })
|
||||
await act(async () => { fireEvent.click(toggle) })
|
||||
|
||||
const tiempoInput = screen.getByLabelText(/Tiempo automatizado/i)
|
||||
await act(async () => {
|
||||
fireEvent.change(tiempoInput, { target: { value: '5' } })
|
||||
})
|
||||
|
||||
const guardarBtn = screen.getByRole('button', { name: /Guardar/i })
|
||||
await act(async () => { fireEvent.click(guardarBtn) })
|
||||
|
||||
expect(processStoreMock.updateActivity).toHaveBeenCalledOnce()
|
||||
const arg = processStoreMock.updateActivity.mock.calls[0][0]
|
||||
expect(arg.automatedTimeMinutes).toBe(5)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user