Files

347 lines
12 KiB
TypeScript
Raw Permalink Normal View History

/**
* LayerManager Tests Layer-Verwaltung
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { LayerManager } from '../src/canvas/LayerManager';
import type { CADLayer } from '../src/types/cad.types';
function makeLayer(id: string, overrides: Partial<CADLayer> = {}): CADLayer {
return {
id,
name: id,
visible: true,
locked: false,
color: '#ffffff',
lineType: 'solid',
transparency: 0,
sortOrder: 0,
parentId: null,
...overrides,
};
}
describe('LayerManager', () => {
let lm: LayerManager;
beforeEach(() => {
lm = new LayerManager();
});
describe('addLayer & getLayer', () => {
it('should add a layer and retrieve it by id', () => {
const layer = makeLayer('layer-1', { name: 'Walls' });
lm.addLayer(layer);
expect(lm.getLayer('layer-1')).toBeDefined();
expect(lm.getLayer('layer-1')?.name).toBe('Walls');
});
it('should set first added layer as active', () => {
lm.addLayer(makeLayer('layer-1'));
expect(lm.getActiveLayerId()).toBe('layer-1');
});
it('should not set active layer if already set', () => {
lm.addLayer(makeLayer('layer-1'));
lm.addLayer(makeLayer('layer-2'));
expect(lm.getActiveLayerId()).toBe('layer-1');
});
it('should return undefined for non-existent layer', () => {
expect(lm.getLayer('non-existent')).toBeUndefined();
});
});
describe('removeLayer', () => {
it('should remove a layer', () => {
lm.addLayer(makeLayer('layer-1'));
lm.addLayer(makeLayer('layer-2'));
lm.removeLayer('layer-1');
expect(lm.getLayer('layer-1')).toBeUndefined();
expect(lm.getLayers().length).toBe(1);
});
it('should switch active layer when removing the active one', () => {
lm.addLayer(makeLayer('layer-1'));
lm.addLayer(makeLayer('layer-2'));
lm.setActiveLayer('layer-1');
lm.removeLayer('layer-1');
expect(lm.getActiveLayerId()).toBe('layer-2');
});
it('should have empty active layer id when removing last layer', () => {
lm.addLayer(makeLayer('layer-1'));
lm.removeLayer('layer-1');
expect(lm.getActiveLayerId()).toBe('');
});
});
describe('getLayers', () => {
it('should return layers sorted by sortOrder', () => {
lm.addLayer(makeLayer('layer-3', { sortOrder: 3 }));
lm.addLayer(makeLayer('layer-1', { sortOrder: 1 }));
lm.addLayer(makeLayer('layer-2', { sortOrder: 2 }));
const layers = lm.getLayers();
expect(layers[0].id).toBe('layer-1');
expect(layers[1].id).toBe('layer-2');
expect(layers[2].id).toBe('layer-3');
});
it('should return empty array when no layers', () => {
expect(lm.getLayers()).toEqual([]);
});
});
describe('getVisibleLayers', () => {
it('should return only visible and unlocked layers', () => {
lm.addLayer(makeLayer('layer-1', { visible: true, locked: false }));
lm.addLayer(makeLayer('layer-2', { visible: false, locked: false }));
lm.addLayer(makeLayer('layer-3', { visible: true, locked: true }));
lm.addLayer(makeLayer('layer-4', { visible: true, locked: false }));
const visible = lm.getVisibleLayers();
expect(visible.length).toBe(2);
const ids = visible.map(l => l.id);
expect(ids).toContain('layer-1');
expect(ids).toContain('layer-4');
});
});
describe('setActiveLayer & getActiveLayer', () => {
it('should set active layer if it exists', () => {
lm.addLayer(makeLayer('layer-1'));
lm.addLayer(makeLayer('layer-2'));
lm.setActiveLayer('layer-2');
expect(lm.getActiveLayerId()).toBe('layer-2');
expect(lm.getActiveLayer()?.id).toBe('layer-2');
});
it('should not set active layer if it does not exist', () => {
lm.addLayer(makeLayer('layer-1'));
lm.setActiveLayer('non-existent');
expect(lm.getActiveLayerId()).toBe('layer-1');
});
});
describe('toggleVisibility', () => {
it('should toggle layer visibility', () => {
lm.addLayer(makeLayer('layer-1', { visible: true }));
lm.toggleVisibility('layer-1');
expect(lm.getLayer('layer-1')?.visible).toBe(false);
lm.toggleVisibility('layer-1');
expect(lm.getLayer('layer-1')?.visible).toBe(true);
});
});
describe('toggleLock', () => {
it('should toggle layer lock state', () => {
lm.addLayer(makeLayer('layer-1', { locked: false }));
lm.toggleLock('layer-1');
expect(lm.getLayer('layer-1')?.locked).toBe(true);
lm.toggleLock('layer-1');
expect(lm.getLayer('layer-1')?.locked).toBe(false);
});
});
describe('clear', () => {
it('should clear all layers and reset active', () => {
lm.addLayer(makeLayer('layer-1'));
lm.addLayer(makeLayer('layer-2'));
lm.clear();
expect(lm.getLayers().length).toBe(0);
expect(lm.getActiveLayerId()).toBe('');
});
});
describe('renameLayer', () => {
it('should rename a layer', () => {
lm.addLayer(makeLayer('layer-1', { name: 'Old Name' }));
lm.renameLayer('layer-1', 'New Name');
expect(lm.getLayer('layer-1')?.name).toBe('New Name');
});
it('should do nothing for non-existent layer', () => {
lm.renameLayer('non-existent', 'New Name');
expect(lm.getLayer('non-existent')).toBeUndefined();
});
});
describe('updateLayer', () => {
it('should update multiple layer properties', () => {
lm.addLayer(makeLayer('layer-1', { color: '#ffffff', transparency: 0 }));
lm.updateLayer('layer-1', { color: '#ff0000', transparency: 50 });
const layer = lm.getLayer('layer-1');
expect(layer?.color).toBe('#ff0000');
expect(layer?.transparency).toBe(50);
});
});
describe('setParent', () => {
it('should set parent for a layer', () => {
lm.addLayer(makeLayer('layer-1'));
lm.addLayer(makeLayer('layer-2'));
lm.setParent('layer-2', 'layer-1');
expect(lm.getLayer('layer-2')?.parentId).toBe('layer-1');
});
it('should prevent circular references', () => {
lm.addLayer(makeLayer('layer-1'));
lm.addLayer(makeLayer('layer-2'));
lm.setParent('layer-1', 'layer-2');
lm.setParent('layer-2', 'layer-1'); // Would create cycle
expect(lm.getLayer('layer-2')?.parentId).toBe(null);
});
it('should allow setting parent to null', () => {
lm.addLayer(makeLayer('layer-1'));
lm.addLayer(makeLayer('layer-2'));
lm.setParent('layer-2', 'layer-1');
lm.setParent('layer-2', null);
expect(lm.getLayer('layer-2')?.parentId).toBe(null);
});
});
describe('getChildLayers & getLayerTree', () => {
it('should get child layers of a parent', () => {
lm.addLayer(makeLayer('parent', { sortOrder: 0 }));
lm.addLayer(makeLayer('child-1', { parentId: 'parent', sortOrder: 1 }));
lm.addLayer(makeLayer('child-2', { parentId: 'parent', sortOrder: 2 }));
lm.addLayer(makeLayer('other', { sortOrder: 3 }));
const children = lm.getChildLayers('parent');
expect(children.length).toBe(2);
});
it('should get child layers for null parent (root layers)', () => {
lm.addLayer(makeLayer('root-1', { parentId: null }));
lm.addLayer(makeLayer('root-2', { parentId: null }));
lm.addLayer(makeLayer('child', { parentId: 'root-1' }));
const roots = lm.getChildLayers(null);
expect(roots.length).toBe(2);
});
it('should build a tree structure', () => {
lm.addLayer(makeLayer('root', { sortOrder: 0 }));
lm.addLayer(makeLayer('child-a', { parentId: 'root', sortOrder: 1 }));
lm.addLayer(makeLayer('child-b', { parentId: 'root', sortOrder: 2 }));
lm.addLayer(makeLayer('grandchild', { parentId: 'child-a', sortOrder: 3 }));
const tree = lm.getLayerTree();
expect(tree.length).toBe(1);
expect(tree[0].id).toBe('root');
expect(tree[0].children.length).toBe(2);
expect(tree[0].children[0].children.length).toBe(1);
expect(tree[0].children[0].children[0].id).toBe('grandchild');
});
});
describe('moveLayer', () => {
it('should move layer to new sort order', () => {
lm.addLayer(makeLayer('layer-1', { sortOrder: 0 }));
lm.moveLayer('layer-1', 5);
expect(lm.getLayer('layer-1')?.sortOrder).toBe(5);
});
});
describe('duplicateLayer', () => {
it('should create a copy of the layer', () => {
lm.addLayer(makeLayer('layer-1', { name: 'Original', sortOrder: 1 }));
const copy = lm.duplicateLayer('layer-1');
expect(copy).not.toBeNull();
expect(copy?.id).not.toBe('layer-1');
expect(copy?.name).toContain('Kopie');
expect(copy?.sortOrder).toBe(2);
expect(lm.getLayers().length).toBe(2);
});
it('should return null for non-existent layer', () => {
expect(lm.duplicateLayer('non-existent')).toBeNull();
});
});
describe('filterLayers', () => {
beforeEach(() => {
lm.addLayer(makeLayer('layer-1', { name: 'Walls', visible: true, locked: false, color: '#ff0000' }));
lm.addLayer(makeLayer('layer-2', { name: 'Doors', visible: false, locked: true, color: '#00ff00' }));
lm.addLayer(makeLayer('layer-3', { name: 'Windows', visible: true, locked: false, color: '#ff0000' }));
});
it('should filter by visible', () => {
const result = lm.filterLayers({ visible: true });
expect(result.length).toBe(2);
});
it('should filter by locked', () => {
const result = lm.filterLayers({ locked: true });
expect(result.length).toBe(1);
expect(result[0].id).toBe('layer-2');
});
it('should filter by color', () => {
const result = lm.filterLayers({ color: '#ff0000' });
expect(result.length).toBe(2);
});
it('should filter by name contains (case insensitive)', () => {
const result = lm.filterLayers({ nameContains: 'wall' });
expect(result.length).toBe(1);
expect(result[0].id).toBe('layer-1');
});
it('should filter by multiple criteria', () => {
const result = lm.filterLayers({ visible: true, color: '#ff0000' });
expect(result.length).toBe(2);
});
});
describe('getDescendantIds', () => {
it('should get all descendant IDs recursively', () => {
lm.addLayer(makeLayer('root'));
lm.addLayer(makeLayer('child-1', { parentId: 'root' }));
lm.addLayer(makeLayer('child-2', { parentId: 'root' }));
lm.addLayer(makeLayer('grandchild-1', { parentId: 'child-1' }));
lm.addLayer(makeLayer('grandchild-2', { parentId: 'child-1' }));
const descendants = lm.getDescendantIds('root');
expect(descendants.length).toBe(4);
expect(descendants).toContain('child-1');
expect(descendants).toContain('child-2');
expect(descendants).toContain('grandchild-1');
expect(descendants).toContain('grandchild-2');
});
it('should return empty array for layer with no children', () => {
lm.addLayer(makeLayer('lonely'));
expect(lm.getDescendantIds('lonely')).toEqual([]);
});
});
describe('isLocked', () => {
it('should return true for locked layer', () => {
lm.addLayer(makeLayer('layer-1', { locked: true }));
expect(lm.isLocked('layer-1')).toBe(true);
});
it('should return false for unlocked layer', () => {
lm.addLayer(makeLayer('layer-1', { locked: false }));
expect(lm.isLocked('layer-1')).toBe(false);
});
it('should return false for non-existent layer', () => {
expect(lm.isLocked('non-existent')).toBe(false);
});
});
describe('getLayerColor', () => {
it('should return layer color', () => {
lm.addLayer(makeLayer('layer-1', { color: '#abcdef' }));
expect(lm.getLayerColor('layer-1')).toBe('#abcdef');
});
it('should return undefined for non-existent layer', () => {
expect(lm.getLayerColor('non-existent')).toBeUndefined();
});
});
});