task(G1): layout data model - LayoutDefinition type, Yjs layouts map, undo-tracked CRUD in CADDocument

This commit is contained in:
Agent Zero
2026-08-29 02:39:30 +02:00
parent d51111fc2c
commit e750a45d7a
4 changed files with 162 additions and 3 deletions
+87
View File
@@ -0,0 +1,87 @@
/**
* Task G1 Layout-Datenmodell: Yjs Map layoutId → LayoutDefinition.
*
* LayoutDefinition: { id, name, viewport: { center: {x,y}, scale },
* frameBlockRef? }. CRUD über CADDocument, undo-getrackt, Persistence
* läuft automatisch über die Yjs-Doc-Serialisierung (drawing json).
*/
import { describe, it, expect } from 'vitest';
import { CADDocument } from '../src/kernel/document/CADDocument';
import { createInMemoryDoc } from './helpers/inMemoryDoc';
function mkDoc(): CADDocument {
const { ydoc } = createInMemoryDoc();
return new CADDocument(ydoc);
}
const LAYOUT = {
id: 'layout-1',
name: 'A4 Plan',
viewport: { center: { x: 100, y: 50 }, scale: 0.5 },
frameBlockRef: 'frame-a4',
};
describe('G1: Layout-CRUD', () => {
it('addLayout + getLayout: Layout landet mit allen Feldern in der Map', () => {
const doc = mkDoc();
doc.addLayout(LAYOUT);
const loaded = doc.getLayout('layout-1');
expect(loaded).toBeDefined();
expect(loaded!.name).toBe('A4 Plan');
expect(loaded!.viewport.center.x).toBe(100);
expect(loaded!.viewport.center.y).toBe(50);
expect(loaded!.viewport.scale).toBe(0.5);
expect(loaded!.frameBlockRef).toBe('frame-a4');
});
it('getAllLayouts liefert Array; leer ohne Layouts', () => {
const doc = mkDoc();
expect(doc.getAllLayouts()).toHaveLength(0);
doc.addLayout(LAYOUT);
doc.addLayout({ ...LAYOUT, id: 'layout-2', name: 'A3 Plan' });
const all = doc.getAllLayouts();
expect(all).toHaveLength(2);
expect(all.map((l) => l.name)).toContain('A3 Plan');
});
it('updateLayout ändert name und viewport shallow (id/struktur bleibt)', () => {
const doc = mkDoc();
doc.addLayout(LAYOUT);
doc.updateLayout('layout-1', { name: 'A4 geändert', viewport: { center: { x: 0, y: 0 }, scale: 2 } });
const loaded = doc.getLayout('layout-1');
expect(loaded!.name).toBe('A4 geändert');
expect(loaded!.viewport.scale).toBe(2);
expect(loaded!.frameBlockRef).toBe('frame-a4'); // unverändert erhalten
});
it('deleteLayout entfernt Layout still (unbekannte ID kein Fehler)', () => {
const doc = mkDoc();
doc.addLayout(LAYOUT);
doc.deleteLayout('layout-1');
expect(doc.getLayout('layout-1')).toBeUndefined();
expect(() => doc.deleteLayout('gibts-nicht')).not.toThrow();
});
});
describe('G1: Undo-Integration', () => {
it('Undo entfernt hinzugefügtes Layout wieder', () => {
const doc = mkDoc();
doc.addLayout(LAYOUT);
expect(doc.getLayout('layout-1')).toBeDefined();
doc.undo();
expect(doc.getLayout('layout-1')).toBeUndefined();
doc.redo();
expect(doc.getLayout('layout-1')).toBeDefined();
});
it('Layout-Undo ist EIN Schritt (nicht mit Element-Transaktion vermischt)', () => {
const doc = mkDoc();
doc.addLayout(LAYOUT);
doc.deleteLayout('layout-1');
// Zwei Schritte: undo stellt Löschung wieder her
doc.undo();
expect(doc.getLayout('layout-1')).toBeDefined();
doc.undo();
expect(doc.getLayout('layout-1')).toBeUndefined();
});
});