/** * 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 = ` ` // 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() }) 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() }) 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() }) 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: title correcto, SVG ámbar (#f59e0b), fondo #fffbeb', async () => { await act(async () => { render() }) await flush() const html: string = mockOverlaysAdd.mock.calls[0][2].html expect(html).toContain('Actividad marcada como automatizable') expect(html).toContain(' { const { rerender } = render( ) await flush() expect(mockOverlaysAdd).toHaveBeenCalledTimes(2) const removeCalls1 = mockOverlaysRemove.mock.calls.length await act(async () => { rerender() }) await flush() expect(mockOverlaysRemove.mock.calls.length).toBeGreaterThan(removeCalls1) expect(mockOverlaysAdd).toHaveBeenCalledTimes(3) // 2 + 1 }) })