/** * CADDocument Tests (Task A2b). * Kern-Verhalten: CRUD, Shallow-Merge, Transaktion = 1 Undo-Schritt, * undo/redo-Stacks, deleteElements selektiv. */ import { describe, it, expect } from 'vitest'; import { CADDocument } from '../src/kernel/document/CADDocument'; import { createInMemoryDoc } from './helpers/inMemoryDoc'; import type { CADElement } from '../src/types/cad.types'; function makeEl(id: string, props: Record = {}): CADElement { return { id, type: 'rect', layerId: 'layer-1', x: 10, y: 20, width: 30, height: 40, properties: props, } as unknown as CADElement; } function makeDoc(): { doc: CADDocument } { const { ydoc } = createInMemoryDoc(); return { doc: new CADDocument(ydoc) }; } describe('CADDocument', () => { it('a) addElement → getAllElements enthaelt es', () => { const { doc } = makeDoc(); doc.addElement(makeEl('e1')); const all = doc.getAllElements(); expect(all.length).toBe(1); expect(all[0].id).toBe('e1'); expect(doc.getElement('e1')).toBeDefined(); expect(doc.getElement('missing')).toBeUndefined(); }); it('b) transact um zwei addElements = EIN Undo-Schritt entfernt BEIDE', () => { const { doc } = makeDoc(); doc.transact(() => { doc.addElement(makeEl('a')); doc.addElement(makeEl('b')); }); expect(doc.getAllElements().length).toBe(2); expect(doc.canUndo()).toBe(true); expect(doc.undo()).toBe(true); expect(doc.getAllElements().length).toBe(0); // beide weg mit einem undo! expect(doc.canUndo()).toBe(false); }); it('c) redo stellt beide Elemente wieder her', () => { const { doc } = makeDoc(); doc.transact(() => { doc.addElement(makeEl('a')); doc.addElement(makeEl('b')); }); doc.undo(); expect(doc.getAllElements().length).toBe(0); expect(doc.canRedo()).toBe(true); expect(doc.redo()).toBe(true); expect(doc.getAllElements().length).toBe(2); expect(doc.getElement('a')).toBeDefined(); expect(doc.getElement('b')).toBeDefined(); }); it('d) updateElement merged properties SHALLOW (bestehende Keys bleiben)', () => { const { doc } = makeDoc(); doc.addElement(makeEl('m', { fill: 'red', rotation: 45, stroke: 'black' })); doc.updateElement('m', { properties: { fill: 'blue' } }); const el = doc.getElement('m')!; expect(el.properties.fill).toBe('blue'); // ueberschrieben expect(el.properties.rotation).toBe(45); // erhalten expect(el.properties.stroke).toBe('black'); // erhalten }); it('d2) updateElement kann Top-Level-Felder setzen und wirft bei fehlender ID', () => { const { doc } = makeDoc(); doc.addElement(makeEl('m')); doc.updateElement('m', { x: 999 }); expect(doc.getElement('m')!.x).toBe(999); expect(() => doc.updateElement('ghost', { x: 1 })).toThrow(/nicht gefunden/); }); it('e) deleteElements entfernt nur die genannten IDs', () => { const { doc } = makeDoc(); doc.transact(() => { doc.addElement(makeEl('keep1')); doc.addElement(makeEl('del1')); doc.addElement(makeEl('del2')); doc.addElement(makeEl('keep2')); }); doc.deleteElements(['del1', 'del2', 'unbekannt']); // unbekannte still ignorieren const ids = doc.getAllElements().map((e) => e.id).sort(); expect(ids).toEqual(['keep1', 'keep2']); }); it('undo nach deleteElements stellt geloeschte Elemente wieder her', () => { const { doc } = makeDoc(); doc.addElement(makeEl('x1')); doc.addElement(makeEl('x2')); doc.deleteElements(['x1']); expect(doc.getAllElements().length).toBe(1); doc.undo(); // Delete rueckgaengig expect(doc.getAllElements().length).toBe(2); expect(doc.getElement('x1')).toBeDefined(); }); it('undo/redo liefern false auf leeren Stacks', () => { const { doc } = makeDoc(); expect(doc.undo()).toBe(false); expect(doc.redo()).toBe(false); }); });