72 lines
4.9 KiB
TypeScript
72 lines
4.9 KiB
TypeScript
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { DashboardEditor, validateDashboardLayouts, validateWidget, type EditorWidget } from '../../src/DashboardEditor';
|
|
|
|
afterEach(() => { cleanup(); vi.unstubAllGlobals(); });
|
|
|
|
function layouts(x: number, y: number, w = 6, h = 4) {
|
|
return {
|
|
desktop: { x, y, w, h, visible: true }, tablet: { x: Math.min(x, 2), y, w: Math.min(w, 8), h, visible: true },
|
|
mobile: { x: 0, y, w: 1, h, visible: true }, wallboard: { x, y, w, h, visible: true },
|
|
};
|
|
}
|
|
|
|
function widget(id: string, title: string, x: number, y: number, locked = false): EditorWidget {
|
|
return { id, type: 'stat', title, data: { sourceType: 'inventory', limit: 10 }, visualization: { decimals: 0, thresholds: [] }, behavior: { locked, hidden: false, liveIntervalSeconds: 30 }, layouts: layouts(x, y) };
|
|
}
|
|
|
|
const document = { schemaVersion: 2, id: '00000000-0000-4000-8000-000000000001', slug: 'test', name: 'Test', scope: 'personal', variables: [], widgets: [widget('00000000-0000-4000-8000-000000000011', 'CPU', 0, 0), widget('00000000-0000-4000-8000-000000000012', 'Geheugen', 10, 8)], settings: { defaultTimeRange: '1h' } };
|
|
|
|
describe('DashboardEditor spatial composition', () => {
|
|
it('keeps viewport layouts independent and saves x/y/w/h, thresholds and an internal link', async () => {
|
|
let savedBody: Record<string, unknown> | undefined;
|
|
vi.stubGlobal('fetch', vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
|
savedBody = JSON.parse(String(init?.body));
|
|
return new Response(JSON.stringify({ dashboard: { revision: 2 } }), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
}));
|
|
const onSaved = vi.fn();
|
|
const user = userEvent.setup();
|
|
render(<DashboardEditor dashboardId="dashboard-1" revision={1} document={document} onExit={() => undefined} onSaved={onSaved} />);
|
|
|
|
await user.click(screen.getByRole('button', { name: 'Widget naar rechts: CPU' }));
|
|
expect(screen.getByLabelText('Kolom (x) (Desktop)')).toHaveValue(1);
|
|
fireEvent.change(screen.getByLabelText('Hoogte in rijen'), { target: { value: '6' } });
|
|
await user.selectOptions(screen.getByLabelText('Layout'), 'tablet');
|
|
expect(screen.getByLabelText('Kolom (x) (Tablet)')).toHaveValue(0);
|
|
fireEvent.change(screen.getByLabelText('Kolom (x) (Tablet)'), { target: { value: '2' } });
|
|
await user.selectOptions(screen.getByLabelText('Layout'), 'desktop');
|
|
expect(screen.getByLabelText('Kolom (x) (Desktop)')).toHaveValue(1);
|
|
|
|
await user.click(screen.getByRole('button', { name: 'Drempel toevoegen' }));
|
|
const threshold = screen.getByLabelText('Drempels').querySelector<HTMLElement>('.config-threshold-row')!;
|
|
fireEvent.change(within(threshold).getByLabelText('Waarde'), { target: { value: '85' } });
|
|
fireEvent.change(within(threshold).getByLabelText('Kleur'), { target: { value: '#ff8800' } });
|
|
await user.type(screen.getByLabelText('Interne drilldownlink'), '/incidents');
|
|
await user.click(screen.getByRole('button', { name: 'Opslaan' }));
|
|
|
|
await waitFor(() => expect(onSaved).toHaveBeenCalled());
|
|
const widgets = savedBody?.widgets as EditorWidget[];
|
|
expect((widgets[0].layouts?.desktop as Record<string, unknown>)).toMatchObject({ x: 1, y: 0, w: 6, h: 6 });
|
|
expect((widgets[0].layouts?.tablet as Record<string, unknown>)).toMatchObject({ x: 2, y: 0, w: 6, h: 4 });
|
|
expect(widgets[0].visualization?.thresholds).toEqual([{ value: 85, color: '#ff8800', label: '' }]);
|
|
expect(widgets[0].behavior?.link).toBe('/incidents');
|
|
}, 15_000);
|
|
|
|
it('prevents every spatial control from mutating a locked widget', () => {
|
|
const lockedDocument = { ...document, widgets: [widget('00000000-0000-4000-8000-000000000021', 'Vergrendeld', 2, 3, true)] };
|
|
render(<DashboardEditor dashboardId="dashboard-1" revision={1} document={lockedDocument} onExit={() => undefined} onSaved={() => undefined} />);
|
|
expect(screen.getByRole('button', { name: 'Widget naar links: Vergrendeld' })).toBeDisabled();
|
|
expect(screen.getByRole('button', { name: 'Widget omhoog: Vergrendeld' })).toBeDisabled();
|
|
expect(screen.getByRole('slider', { name: 'Breedte aanpassen: Vergrendeld' })).toBeDisabled();
|
|
expect(screen.getByLabelText('Kolom (x) (Desktop)')).toBeDisabled();
|
|
expect(screen.getByLabelText('Hoogte in rijen')).toBeDisabled();
|
|
});
|
|
|
|
it('rejects unsafe links, malformed thresholds and colliding persisted layouts', () => {
|
|
expect(validateWidget({ ...widget('x', 'Onveilig', 0, 0), visualization: { thresholds: [{ value: 80, color: 'red' }] }, behavior: { link: 'https://example.test', liveIntervalSeconds: 30 } })).toMatchObject({ 'visualization.thresholds': expect.any(String), 'behavior.link': expect.any(String) });
|
|
expect(validateDashboardLayouts([widget('a', 'A', 0, 0), widget('b', 'B', 0, 0)])).toBe(false);
|
|
});
|
|
});
|