import { cleanup, render, screen } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { DashboardRuntimeWidget, type RuntimeWidget } from '../../src/DashboardRuntimeWidget'; afterEach(() => { cleanup(); vi.useRealTimers(); vi.unstubAllGlobals(); }); const layout = { desktop: { x: 0, y: 0, w: 6, h: 4, visible: true } }; function widget(type: string, sourceType: string, extra: Record = {}): RuntimeWidget { return { id: type + '-1', type, title: 'Test ' + type, data: { sourceType, limit: 1000, ...extra }, behavior: { liveIntervalSeconds: 1 }, layouts: layout }; } function mount(value: RuntimeWidget) { return render( undefined} onFilter={() => undefined} />); } describe('DashboardRuntimeWidget adapters', () => { it('renders bounded alerts and clamps the URL to one hundred rows', async () => { const fetchMock = vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (url.startsWith('/api/v1/alerts')) return new Response(JSON.stringify({ items: [{ id: 'a1', ruleName: 'Disk warm', state: 'firing', severity: 'critical', entityName: 'disk1', reason: 'temperatuur', updatedAt: '2026-09-01T10:00:00Z' }] }), { status: 200, headers: { 'Content-Type': 'application/json' } }); return new Response(JSON.stringify({ components: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } }); }); vi.stubGlobal('fetch', fetchMock); mount(widget('alert-summary', 'alerts')); expect(await screen.findByText('Disk warm')).toBeInTheDocument(); expect(fetchMock.mock.calls.some(([input]) => String(input) === '/api/v1/alerts?limit=100')).toBe(true); expect(screen.getAllByText('1', { selector: 'dd' })).toHaveLength(2); }); it('renders incident rows and safe text without interpreting HTML', async () => { vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => String(input).startsWith('/api/v1/incidents') ? new Response(JSON.stringify({ items: [{ id: 'i1', title: 'Opslagincident', status: 'open', severity: 'warning', summary: 'Controle nodig', updatedAt: '2026-09-01T10:00:00Z' }] }), { status: 200, headers: { 'Content-Type': 'application/json' } }) : new Response(JSON.stringify({ components: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } }))); const view = mount(widget('table', 'incidents')); expect(await screen.findByText('Opslagincident')).toBeInTheDocument(); view.unmount(); mount(widget('text', 'text', { text: '# Runbook\n[Open incident](/incidents)\n' })); expect(screen.getByRole('link', { name: 'Open incident' })).toHaveAttribute('href', '/incidents'); expect(screen.queryByRole('script')).not.toBeInTheDocument(); expect(screen.getByText('')).toBeInTheDocument(); }); it('contains an unknown type locally and clears its bounded polling timer', async () => { vi.useFakeTimers(); const clear = vi.spyOn(window, 'clearInterval'); vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => String(input).startsWith('/api/v1/events') ? new Response(JSON.stringify({ items: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } }) : new Response(JSON.stringify({ components: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } }))); const view = mount(widget('made-up', 'events')); expect(screen.getByText('Onbekend widgettype')).toBeInTheDocument(); expect(screen.getByText('made-up')).toBeInTheDocument(); view.unmount(); expect(clear).toHaveBeenCalled(); }); });