8440db8c6e
Rueckmeldung Runde 3: die Reiter in der Seitenleiste gefallen nicht -
zurueck auf den Stand vor 8532b63 (Umschalter in der Kopfzeile). Statt
dessen sitzt die Begruessung jetzt zweizeilig im sonst leeren unteren
Bereich der Seitenleiste ueber "Einklappen"; ueber den Kacheln bleibt nur
die Befehlsleiste rechts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
190 lines
7.7 KiB
TypeScript
190 lines
7.7 KiB
TypeScript
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
// quick-260916-dyv: Dashboard-Seite — feste Aktionsleiste unten rechts
|
|
// (Stift/Haekchen + "Widget hinzufuegen"), Grid direkt im Container p-2,
|
|
// kein Abstands-Wrapper (mt-8) und kein Block oben rechts (top-2) mehr.
|
|
// Muster: admin/groups/groups-page.test.tsx (next-intl-Mock mit Namensraum).
|
|
|
|
const messages: Record<string, Record<string, string>> = {
|
|
widgets: {
|
|
editMode: 'Dashboard bearbeiten',
|
|
saveChanges: 'Änderungen speichern',
|
|
addWidget: 'Widget hinzufügen',
|
|
editShort: 'Bearbeiten',
|
|
done: 'Fertig',
|
|
toolbarLabel: 'Dashboard-Aktionen',
|
|
layoutLoadError: 'Dashboard konnte nicht geladen werden. Bitte laden Sie die Seite neu.',
|
|
},
|
|
};
|
|
|
|
vi.mock('next-intl', () => ({
|
|
useTranslations: (ns: string) => (key: string) => messages[ns]?.[key] ?? key,
|
|
useLocale: () => 'de',
|
|
}));
|
|
|
|
const mockStore = {
|
|
// quick-260923-ad9: Reiter — leere Liste rendert die Reiterleiste ohne
|
|
// Reiter-Knoepfe, damit diese Datei bei den bestehenden Zusammensetzungs-
|
|
// Pruefungen bleibt, statt den Reiter-Speicher selbst nachzubauen.
|
|
dashboards: [] as Array<{ id: string; name: string; position: number }>,
|
|
activeDashboardId: null as string | null,
|
|
isSwitchingDashboard: false,
|
|
layouts: { lg: [], md: [], sm: [], xs: [], xxs: [] },
|
|
widgets: [] as Array<{ id: string; widgetType: string; config: Record<string, unknown> }>,
|
|
isEditMode: false,
|
|
isDirty: false,
|
|
isLoading: false,
|
|
error: null as string | null,
|
|
setEditMode: vi.fn(),
|
|
updateLayouts: vi.fn(),
|
|
addWidget: vi.fn(),
|
|
removeWidget: vi.fn(),
|
|
loadDashboard: vi.fn(),
|
|
saveLayout: vi.fn(),
|
|
selectDashboard: vi.fn(),
|
|
createDashboard: vi.fn(),
|
|
renameDashboard: vi.fn(),
|
|
deleteDashboard: vi.fn(),
|
|
reorderDashboards: vi.fn(),
|
|
};
|
|
|
|
vi.mock('@/lib/stores/dashboard-store', () => ({
|
|
useDashboardStore: (selector?: (state: typeof mockStore) => unknown) => {
|
|
if (selector) return selector(mockStore);
|
|
return mockStore;
|
|
},
|
|
}));
|
|
|
|
vi.mock('@/components/dashboard/dashboard-grid', () => ({
|
|
DashboardGrid: (p: { isEditMode: boolean }) => (
|
|
<div data-testid="dashboard-grid" data-edit={String(p.isEditMode)} />
|
|
),
|
|
}));
|
|
|
|
// quick-260922-m1h: Der Katalog bekommt die zugaenglichen Modul-Slugs als
|
|
// Prop von dieser Seite — die Attrappe merkt sie sich, damit der Test sie
|
|
// pruefen kann, ohne den echten Dialog zu rendern.
|
|
const catalogProps: { accessibleModuleSlugs?: readonly string[] | null } = {};
|
|
|
|
vi.mock('@/components/dashboard/widget-catalog-modal', () => ({
|
|
WidgetCatalogModal: (p: { accessibleModuleSlugs: readonly string[] | null }) => {
|
|
catalogProps.accessibleModuleSlugs = p.accessibleModuleSlugs;
|
|
return null;
|
|
},
|
|
}));
|
|
|
|
vi.mock('@/components/dashboard/widgets/clock-widget', () => ({ ClockWidget: () => null }));
|
|
vi.mock('@/components/dashboard/widgets/search-widget', () => ({ SearchWidget: () => null }));
|
|
vi.mock('@/components/dashboard/widgets/calendar-widget', () => ({ CalendarWidget: () => null }));
|
|
vi.mock('@/components/dashboard/widgets/note-widget', () => ({ NoteWidget: () => null }));
|
|
vi.mock('@/components/dashboard/widgets/calculator-widget', () => ({ CalculatorWidget: () => null }));
|
|
vi.mock('@/components/dashboard/widgets/stopwatch-widget', () => ({ StopwatchWidget: () => null }));
|
|
vi.mock('@/components/dashboard/widgets/favorites-widget', () => ({ FavoritesWidget: () => null }));
|
|
vi.mock('@/components/dashboard/widgets/picture-frame-widget', () => ({ PictureFrameWidget: () => null }));
|
|
vi.mock('@/components/dashboard/widgets/xframe-widget', () => ({ XframeWidget: () => null }));
|
|
vi.mock('@/components/dashboard/widgets/proxmox-widget', () => ({ ProxmoxWidget: () => null }));
|
|
|
|
beforeEach(() => {
|
|
catalogProps.accessibleModuleSlugs = undefined;
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => ({
|
|
ok: true,
|
|
json: async () => [
|
|
{ id: 'm1', slug: 'domaincheck', name: 'Domaincheck', category: 'tools' },
|
|
{ id: 'm2', slug: 'tender-radar', name: 'Tender', category: 'tools' },
|
|
],
|
|
})),
|
|
);
|
|
mockStore.isEditMode = false;
|
|
mockStore.isLoading = false;
|
|
mockStore.error = null;
|
|
mockStore.setEditMode = vi.fn();
|
|
mockStore.loadDashboard = vi.fn();
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
describe('DashboardPage (quick-260916-dyv)', () => {
|
|
it('Test 1: Ansichtsmodus — Befehlsleiste rechts oben mit „Bearbeiten“, kein "Widget hinzufuegen", Begruessung, Grid direkt im Container p-2 (Design „Mosaik“)', async () => {
|
|
const { default: DashboardPage } = await import('./page');
|
|
render(<DashboardPage />);
|
|
|
|
const toolbar = screen.getByRole('toolbar', { name: 'Dashboard-Aktionen' });
|
|
const toggle = screen.getByRole('button', { name: 'Bearbeiten' });
|
|
expect(toggle.parentElement).toBe(toolbar);
|
|
expect(toggle).toHaveAttribute('aria-pressed', 'false');
|
|
// kein schwebender Knopf mehr unten rechts
|
|
expect(toolbar.classList.contains('fixed')).toBe(false);
|
|
expect(document.querySelector('.bottom-6')).toBeNull();
|
|
|
|
expect(screen.queryByRole('button', { name: 'Widget hinzufügen' })).toBeNull();
|
|
expect(document.querySelector('.mt-8')).toBeNull();
|
|
expect(document.querySelector('.top-2')).toBeNull();
|
|
// Runde 3: die Begruessung steht in der Seitenleiste, nicht mehr hier.
|
|
expect(screen.queryByTestId('dashboard-greeting')).toBeNull();
|
|
|
|
const grid = screen.getByTestId('dashboard-grid');
|
|
expect((grid.parentElement as HTMLElement).classList.contains('p-2')).toBe(true);
|
|
expect(grid.getAttribute('data-edit')).toBe('false');
|
|
});
|
|
|
|
it('Test 2: Bearbeitungsmodus — "Widget hinzufuegen" links neben „Fertig“ in derselben Leiste, Grid im Bearbeitungsmodus', async () => {
|
|
mockStore.isEditMode = true;
|
|
const { default: DashboardPage } = await import('./page');
|
|
render(<DashboardPage />);
|
|
|
|
const toggle = screen.getByRole('button', { name: 'Fertig' });
|
|
const add = screen.getByRole('button', { name: 'Widget hinzufügen' });
|
|
expect(add.parentElement).toBe(toggle.parentElement);
|
|
expect(add.compareDocumentPosition(toggle) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
|
|
|
expect(screen.getByTestId('dashboard-grid').getAttribute('data-edit')).toBe('true');
|
|
});
|
|
|
|
it('Test 3: Umschalten — Klick auf "Bearbeiten" ruft setEditMode(true) genau einmal', async () => {
|
|
const { default: DashboardPage } = await import('./page');
|
|
render(<DashboardPage />);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'Bearbeiten' }));
|
|
expect(mockStore.setEditMode).toHaveBeenCalledTimes(1);
|
|
expect(mockStore.setEditMode).toHaveBeenCalledWith(true);
|
|
});
|
|
});
|
|
|
|
describe('DashboardPage: zugaengliche Module fuer den Katalog (quick-260922-m1h)', () => {
|
|
it('Test 4: holt GET /modules/active mit Sitzungs-Keks und reicht die Slugs an den Katalog durch', async () => {
|
|
const { default: DashboardPage } = await import('./page');
|
|
render(<DashboardPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(catalogProps.accessibleModuleSlugs).toEqual(['domaincheck', 'tender-radar']);
|
|
});
|
|
|
|
const call = vi.mocked(fetch).mock.calls[0];
|
|
expect(String(call[0])).toContain('/modules/active');
|
|
expect(call[1]).toMatchObject({ credentials: 'include' });
|
|
});
|
|
|
|
it('Test 5: fail-closed — schlaegt der Abruf fehl, bleibt die Liste unbekannt (null)', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => {
|
|
throw new Error('Netzwerk weg');
|
|
}),
|
|
);
|
|
|
|
const { default: DashboardPage } = await import('./page');
|
|
render(<DashboardPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(vi.mocked(fetch)).toHaveBeenCalled();
|
|
});
|
|
expect(catalogProps.accessibleModuleSlugs).toBeNull();
|
|
});
|
|
});
|