fix(quick-260922-vdk): Dashboard-Raster misst seine Breite auch aus dem Leerzustand heraus

- Messung an Ref-Rueckruf measureRef gehaengt statt an Effekt mit leerer
  Abhaengigkeitsliste: der Effekt sah den gemessenen Knoten nie, wenn er
  waehrend eines leeren Dashboards einhaengt, weil der fruehe Ruecksprung
  in den Leerzustand das <div> gar nicht rendert
- Synchrone Erstmessung in der Commit-Phase, vor dem ersten Zeichnen
- Fenster-Horcher als Netz, zusaetzlich zum ResizeObserver
- applyWidth verwirft 0 und nicht endliche Werte (T-VDK-01/T-VDK-03)
- Beobachter trennt sich im null-Zweig des Ref-Rueckrufs (T-VDK-02)
- Zwei neue Regressionstests (Test 10/11), zuerst rot nachgewiesen
- FREE_PLACEMENT_COMPACTOR, Konstanten, Leerzustand unveraendert

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-22 22:45:24 +02:00
parent 3e8c0f4ef5
commit d9f2af32d6
2 changed files with 137 additions and 12 deletions
@@ -1,6 +1,7 @@
import { Children, isValidElement } from 'react';
import { cleanup, render, screen } from '@testing-library/react';
import { act, cleanup, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { stubResizeObserver } from '@/test/fake-resize-observer';
// Mock CSS imports that vitest cannot resolve
vi.mock('react-grid-layout/css/styles.css', () => ({}));
@@ -84,6 +85,10 @@ vi.mock('@/lib/stores/dashboard-store', () => ({
afterEach(() => {
cleanup();
vi.restoreAllMocks();
// quick-260922-vdk: stubResizeObserver ersetzt den globalen ResizeObserver
// per vi.stubGlobal — vi.restoreAllMocks() setzt das nicht zurueck, ohne
// diese Zeile bliebe der gestubbte Beobachter fuer alle folgenden Dateien stehen.
vi.unstubAllGlobals();
});
describe('DashboardGrid', () => {
@@ -387,4 +392,64 @@ describe('DashboardGrid', () => {
expect(screen.queryByTitle('Drag the tile to move it')).toBeNull();
expect(document.querySelector('.widget-drag-handle')).toBeNull();
});
it('quick-260922-vdk Test 10: Leerzustand -> gefuellt misst die tatsaechliche Breite statt beim Startwert 1200 stehenzubleiben', async () => {
stubResizeObserver({ width: 1000, height: 800 });
captured.props = null;
const { DashboardGrid } = await import('./dashboard-grid');
const { rerender } = render(
<DashboardGrid
layouts={{ lg: [], md: [], sm: [], xs: [], xxs: [] }}
widgets={[]}
isEditMode={false}
onLayoutChange={vi.fn()}
onRemoveWidget={vi.fn()}
/>,
);
// Leerzustand: der gemessene Knoten ist gar nicht eingehaengt.
expect(screen.getByText('No active widgets')).toBeInTheDocument();
expect(captured.props).toBeNull();
// Erste Kachel erscheint -> der Raster-<div> wird eingehaengt -> measureRef
// misst synchron vor dem Zeichnen. Vor der Aenderung bleibt width bei 1200.
rerender(
<DashboardGrid
layouts={{ lg: [{ i: 'inst-1', x: 0, y: 0, w: 2, h: 2 }], md: [], sm: [], xs: [], xxs: [] }}
widgets={[{ id: 'inst-1', widgetType: 'clock', config: {} }]}
isEditMode={false}
onLayoutChange={vi.fn()}
onRemoveWidget={vi.fn()}
/>,
);
expect(captured.props?.width).toBe(1000);
});
it('quick-260922-vdk Test 11: Fenstergroesse aendert sich -> der Fenster-Horcher misst neu', async () => {
stubResizeObserver({ width: 1000, height: 800 });
captured.props = null;
const { DashboardGrid } = await import('./dashboard-grid');
render(
<DashboardGrid
layouts={{ lg: [{ i: 'inst-1', x: 0, y: 0, w: 2, h: 2 }], md: [], sm: [], xs: [], xxs: [] }}
widgets={[{ id: 'inst-1', widgetType: 'clock', config: {} }]}
isEditMode={false}
onLayoutChange={vi.fn()}
onRemoveWidget={vi.fn()}
/>,
);
expect(captured.props?.width).toBe(1000);
vi.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue(new DOMRect(0, 0, 1600, 800));
act(() => {
window.dispatchEvent(new Event('resize'));
});
// Vor der Aenderung gibt es keinen Fenster-Horcher: width bliebe bei 1000.
expect(captured.props?.width).toBe(1600);
});
});