refactor(quick-260922-m1h): Widget-Typen an einer Stelle, Katalog aus der Registry, Kachel kennt ihr Modul
Ein neuer Widget-Typ war an sieben Stellen einzutragen; vergass man eine, fehlte die Kachel im Katalog oder die API lehnte sie mit 400 ab. - WIDGET_TYPES/WidgetType/WIDGET_MODULE_SLUGS stehen jetzt einmal in packages/shared; Registry, Katalog und die @IsIn-Whitelist der API leiten davon ab - neun wireXWidget()-Funktionen durch ein generisches registerWidget() ersetzt (idempotent, unbekannter Typ wirft in der Entwicklung) - der Katalog fuehrt keine zweite Typliste mehr, sondern leitet sie aus der Registry ab und filtert nach Modulzugriff (fail-closed, wenn die Modulliste unbekannt ist); der Abruf von /modules/active liegt auf der Dashboard-Seite, nicht im Dialog - widget-module-map.ts liest die geteilte Tabelle statt einer Kopie, die oeffentliche Funktion bleibt unveraendert Der Katalogfilter ist Komfort (T-M1H-01) — verbindlich bleibt der serverseitige Filter in DashboardService.getWidgets. Abweichung vom Plan: apps/web hing entgegen der Planannahme noch nicht von @tessera/shared ab; die Abhaengigkeit wurde ergaenzt (Lockfile). Die Dockerfiles kopieren packages/shared bereits, der Produktionsbau von Next.js und der nest build laufen unveraendert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
|
||||
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
|
||||
@@ -47,8 +47,16 @@ vi.mock('@/components/dashboard/dashboard-grid', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
// 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: () => null,
|
||||
WidgetCatalogModal: (p: { accessibleModuleSlugs: readonly string[] | null }) => {
|
||||
catalogProps.accessibleModuleSlugs = p.accessibleModuleSlugs;
|
||||
return null;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/components/dashboard/widgets/clock-widget', () => ({ ClockWidget: () => null }));
|
||||
@@ -62,6 +70,17 @@ vi.mock('@/components/dashboard/widgets/picture-frame-widget', () => ({ PictureF
|
||||
vi.mock('@/components/dashboard/widgets/xframe-widget', () => ({ XframeWidget: () => 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;
|
||||
@@ -71,6 +90,7 @@ beforeEach(() => {
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe('DashboardPage (quick-260916-dyv)', () => {
|
||||
@@ -117,3 +137,35 @@ describe('DashboardPage (quick-260916-dyv)', () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user