Files
inq-roi-simulador-web/tests/features/workspace/BpmnCanvas.test.tsx
Marcos Benítez 3834c9f1c1 feat(sprint-1.5): PDFs Actual/Auto con identidad InQ + badge naranja (Etapas 8-9)
Etapa 8 — PDFs Actual y Automatizado:
- Página 1: header denso + metadata strip + grid 2×2 KPI cards con paleta
  #FEF3E2 (naranja claro InQ), sin gradiente (jerarquía visual preservada)
- Página 3: tabla con header naranja claro #FFF8ED + #92400E, em dash en
  filas no-automatizables, título ANÁLISIS DE COMPOSICIÓN uppercase
- Página 4: NOTA METODOLÓGICA uppercase + caja contenedora slate-50
- 526 tests verdes (+ 15 tests nuevos)

Etapa 9 — Badge  en BpmnCanvas:
- Reemplaza badge legacy (Bot SVG ámbar #fffbeb) por círculo naranja
  sólido #F59845 con símbolo  blanco, 14×14px, border white 1.5px
- Usa var(--inq-orange) definido en globals.css (sin hex hardcodeados)
- Elimina los 3 hex legacy del audit Etapa 1: #f59e0b, #fffbeb, #e2e8f0
- 5 tests E2E de lifecycle del badge + 2 screenshots

Política de iniciativa refinada post-Sprint 1.5 (CLAUDE.md actualizado)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:03:37 -03:00

135 lines
5.4 KiB
TypeScript

/**
* Tests del overlay de automatización en BpmnCanvas.
*
* bpmn-js se mockea completamente. El elementRegistry.get() devuelve
* elementos con width=100 para verificar el posicionamiento left=width-12=88.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, act } from '@testing-library/react'
// ─── Mock de bpmn-js ──────────────────────────────────────────────────────────
const mockOverlaysAdd = vi.fn()
const mockOverlaysRemove = vi.fn()
const mockCanvasZoom = vi.fn()
const mockRegForEach = vi.fn()
// elementRegistry.get() devuelve un elemento con width=100 (nodo estándar de bpmn-js)
const mockRegGet = vi.fn().mockReturnValue({ width: 100, height: 80 })
vi.mock('bpmn-js/lib/Viewer', () => ({
default: class MockViewer {
importXML = vi.fn().mockResolvedValue({ warnings: [] })
get = vi.fn().mockImplementation((svc: string) => {
if (svc === 'overlays') return { add: mockOverlaysAdd, remove: mockOverlaysRemove }
if (svc === 'canvas') return { zoom: mockCanvasZoom, addMarker: vi.fn(), removeMarker: vi.fn() }
if (svc === 'elementRegistry') return { forEach: mockRegForEach, get: mockRegGet }
if (svc === 'eventBus') return { on: vi.fn(), off: vi.fn() }
return {}
})
destroy = vi.fn()
},
}))
import { BpmnCanvas } from '@/features/workspace/BpmnCanvas'
// ─── Fixture ──────────────────────────────────────────────────────────────────
const SIMPLE_XML = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" targetNamespace="test">
<process id="p"><startEvent id="s"/></process>
</definitions>`
// Con width=100 de mockRegGet, left = 100 - 12 = 88
const EXPECTED_LEFT = 88
const EXPECTED_TOP = -8
beforeEach(() => {
vi.clearAllMocks()
mockRegGet.mockReturnValue({ width: 100, height: 80 })
})
async function flush() {
await act(async () => { await new Promise((r) => setTimeout(r, 0)) })
}
// ─── Tests ────────────────────────────────────────────────────────────────────
describe('BpmnCanvas — overlay de automatización', () => {
it('sin actividades automatizables: overlays.add NO se llama, remove sí', async () => {
await act(async () => {
render(<BpmnCanvas xml={SIMPLE_XML} automatableElementIds={[]} />)
})
await flush()
expect(mockOverlaysAdd).not.toHaveBeenCalled()
expect(mockOverlaysRemove).toHaveBeenCalledWith({ type: 'automation-indicator' })
})
it('posición: usa elementRegistry para calcular left=width-12, top=-8 (esquina superior derecha)', async () => {
await act(async () => {
render(<BpmnCanvas xml={SIMPLE_XML} automatableElementIds={['task_a']} />)
})
await flush()
expect(mockOverlaysAdd).toHaveBeenCalledOnce()
const position = mockOverlaysAdd.mock.calls[0][2].position
// top: -8 — badge 8px por encima del borde superior del nodo
expect(position.top).toBe(EXPECTED_TOP)
// left: width-12 = 88 — badge en la región derecha, 8px fuera del borde derecho (20-12=8px sticking out)
expect(position.left).toBe(EXPECTED_LEFT)
// No usa right (semántica confusa en diagram-js: right:-8 → left=8+width, enteramente fuera)
expect(position.right).toBeUndefined()
})
it('con 2 actividades automatizables: overlays.add llamado 2 veces con posición correcta', async () => {
await act(async () => {
render(<BpmnCanvas xml={SIMPLE_XML} automatableElementIds={['task_a', 'task_b']} />)
})
await flush()
expect(mockOverlaysAdd).toHaveBeenCalledTimes(2)
for (const call of mockOverlaysAdd.mock.calls) {
expect(call[2].position).toEqual({ top: EXPECTED_TOP, left: EXPECTED_LEFT })
}
})
it('HTML del overlay: badge ⚡ naranja InQ (Etapa 9) — sin Bot SVG legacy', async () => {
await act(async () => {
render(<BpmnCanvas xml={SIMPLE_XML} automatableElementIds={['task_1']} />)
})
await flush()
const html: string = mockOverlaysAdd.mock.calls[0][2].html
expect(html).toContain('Actividad marcada como automatizable')
expect(html).toContain('⚡')
expect(html).toContain('var(--inq-orange)') // fondo naranja InQ
expect(html).toContain('border:1.5px solid white')
expect(html).not.toContain('<svg') // Bot SVG eliminado
expect(html).not.toContain('#f59e0b') // hex ámbar eliminado
expect(html).not.toContain('#fffbeb') // fondo legacy eliminado
})
it('al quitar una actividad: remove() vuelve a llamarse y add() refleja la lista nueva', async () => {
const { rerender } = render(
<BpmnCanvas xml={SIMPLE_XML} automatableElementIds={['task_a', 'task_b']} />
)
await flush()
expect(mockOverlaysAdd).toHaveBeenCalledTimes(2)
const removeCalls1 = mockOverlaysRemove.mock.calls.length
await act(async () => {
rerender(<BpmnCanvas xml={SIMPLE_XML} automatableElementIds={['task_a']} />)
})
await flush()
expect(mockOverlaysRemove.mock.calls.length).toBeGreaterThan(removeCalls1)
expect(mockOverlaysAdd).toHaveBeenCalledTimes(3) // 2 + 1
})
})