test(05-01): add failing tests for dashboard grid and clock widget

- DashboardGrid test: empty state, widget rendering, edit mode affordances
- ClockWidget test: timezone rendering, showDate toggle behavior

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 11:15:18 +02:00
parent 950eebbc15
commit 792b7661d8
2 changed files with 219 additions and 0 deletions
@@ -0,0 +1,151 @@
import { cleanup, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
// Mock next-intl
vi.mock('next-intl', () => ({
useTranslations: (ns: string) => (key: string) => {
const map: Record<string, Record<string, string>> = {
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
vi.mock('react-grid-layout', () => ({
Responsive: ({
children,
}: {
children: React.ReactNode;
}) => <div data-testid="responsive-grid">{children}</div>,
}));
// 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<string, unknown> }>,
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(
<DashboardGrid
layouts={{ lg: [], md: [], sm: [], xs: [], xxs: [] }}
widgets={[]}
isEditMode={false}
onLayoutChange={vi.fn()}
onRemoveWidget={vi.fn()}
/>,
);
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(
<DashboardGrid
layouts={layouts}
widgets={widgets}
isEditMode={false}
onLayoutChange={vi.fn()}
onRemoveWidget={vi.fn()}
/>,
);
// 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(
<DashboardGrid
layouts={layouts}
widgets={widgets}
isEditMode={true}
onLayoutChange={vi.fn()}
onRemoveWidget={vi.fn()}
/>,
);
// In edit mode, delete button and drag handle should exist
expect(screen.getByLabelText('Remove widget')).toBeInTheDocument();
expect(document.querySelector('.widget-drag-handle')).toBeInTheDocument();
});
});