Files
ITWorx-Pulse-Public/apps/web/tests/unit/DashboardRuntimeWidget.test.tsx
T
2026-09-30 01:14:48 +00:00

55 lines
3.7 KiB
TypeScript

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<string, unknown> = {}): 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(<DashboardRuntimeWidget widget={value} viewport="desktop" document={{}} timeRange="1h" onState={() => 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<script>alert(1)</script>' }));
expect(screen.getByRole('link', { name: 'Open incident' })).toHaveAttribute('href', '/incidents');
expect(screen.queryByRole('script')).not.toBeInTheDocument();
expect(screen.getByText('<script>alert(1)</script>')).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();
});
});