import { Children, isValidElement } from 'react'; import { cleanup, render, screen } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; // Mock CSS imports that vitest cannot resolve vi.mock('react-grid-layout/css/styles.css', () => ({})); vi.mock('react-resizable/css/styles.css', () => ({})); // Mock next-intl vi.mock('next-intl', () => ({ useTranslations: (ns: string) => (key: string) => { const map: Record> = { widgets: { emptyHeading: 'No active widgets', emptyBody: 'Click edit to add widgets to your dashboard.', addWidget: 'Add widget', catalogTitle: 'Add widget', deleteTooltip: 'Remove widget', 'clock.name': 'Clock', 'clock.description': 'Shows the current time', 'search.name': 'Search', 'search.description': 'Quick web search', 'calendar.name': 'Calendar', 'calendar.description': 'Upcoming events', 'note.name': 'Notes', 'note.description': 'Free-text notes with Markdown', }, dashboard: { title: 'Dashboard', editMode: 'Edit dashboard', saveChanges: 'Save changes', }, }; return map[ns]?.[key] ?? key; }, })); // Mock react-grid-layout Responsive to a simple passthrough for jsdom stability. // quick-260916-bwo: der Mock faengt zusaetzlich die Props ein, damit die // Raster-Konstanten (cols, rowHeight, margin, breakpoints) pruefbar sind. const captured = vi.hoisted(() => ({ props: null as Record | null })); vi.mock('react-grid-layout', () => ({ Responsive: (props: Record & { children: React.ReactNode }) => { captured.props = props; return
{props.children}
; }, noCompactor: () => null, })); // Mock dashboard-api vi.mock('@/lib/dashboard-api', () => ({ fetchLayout: vi.fn().mockResolvedValue({ lg: [], md: [], sm: [], xs: [], xxs: [] }), saveLayout: vi.fn().mockResolvedValue({}), fetchWidgets: vi.fn().mockResolvedValue([]), addWidget: vi.fn(), removeWidget: vi.fn(), updateWidgetConfig: vi.fn(), })); // Mock dashboard-store const mockStore = { layouts: { lg: [], md: [], sm: [], xs: [], xxs: [] }, widgets: [] as Array<{ id: string; widgetType: string; config: Record }>, isEditMode: false, isDirty: false, setEditMode: vi.fn(), updateLayouts: vi.fn(), addWidget: vi.fn(), removeWidget: vi.fn(), loadDashboard: vi.fn(), saveLayout: vi.fn(), }; vi.mock('@/lib/stores/dashboard-store', () => ({ useDashboardStore: (selector?: (state: typeof mockStore) => unknown) => { if (selector) return selector(mockStore); return mockStore; }, })); afterEach(() => { cleanup(); vi.restoreAllMocks(); }); describe('DashboardGrid', () => { it('renders empty state heading when no widgets', async () => { const { DashboardGrid } = await import('./dashboard-grid'); render( , ); expect(screen.getByText('No active widgets')).toBeInTheDocument(); }); it('renders widget instance when widgets are provided', async () => { const widgets = [ { id: 'inst-1', widgetType: 'clock', config: { timezone: 'Europe/Berlin' } }, ]; const layouts = { lg: [{ i: 'inst-1', x: 0, y: 0, w: 2, h: 2 }], md: [], sm: [], xs: [], xxs: [], }; const { DashboardGrid } = await import('./dashboard-grid'); render( , ); // Widget is rendered inside grid, keyed by instance id const grid = screen.getByTestId('responsive-grid'); expect(grid).toBeInTheDocument(); // The widget wrapper should be in the document expect(grid.querySelector('[data-widget-id="inst-1"]')).toBeInTheDocument(); }); it('shows edit affordances (delete button) when isEditMode is true', async () => { const widgets = [ { id: 'inst-2', widgetType: 'clock', config: {} }, ]; const layouts = { lg: [{ i: 'inst-2', x: 0, y: 0, w: 2, h: 2 }], md: [], sm: [], xs: [], xxs: [], }; const { DashboardGrid } = await import('./dashboard-grid'); render( , ); // In edit mode, delete button and drag handle should exist expect(screen.getByLabelText('Remove widget')).toBeInTheDocument(); expect(document.querySelector('.widget-drag-handle')).toBeInTheDocument(); }); it('quick-260916-bwo Test 4: Grid-Props — 24/20/12/8/2 Spalten, rowHeight 20, margin 8, Breakpoints unveraendert, containerPadding folgt dem margin', async () => { captured.props = null; const { DashboardGrid } = await import('./dashboard-grid'); render( , ); expect(captured.props).not.toBeNull(); expect(captured.props?.cols).toEqual({ lg: 24, md: 20, sm: 12, xs: 8, xxs: 2 }); expect(captured.props?.rowHeight).toBe(20); expect(captured.props?.margin).toEqual([8, 8]); expect(captured.props?.breakpoints).toEqual({ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }); expect(captured.props?.containerPadding).toBeUndefined(); }); it('quick-260916-bwo Test 5: Widget ohne gespeicherten Eintrag bekommt die verdoppelten Vorgaben als data-grid', async () => { captured.props = null; const { DashboardGrid } = await import('./dashboard-grid'); render( , ); const children = Children.toArray(captured.props?.children as React.ReactNode); expect(children).toHaveLength(1); const first = children[0]; expect(isValidElement(first)).toBe(true); const props = (first as React.ReactElement>).props; expect(props['data-grid']).toEqual({ x: 0, y: 0, w: 4, h: 4, minW: 4, minH: 4 }); }); });