Files
web-cad/frontend/tests/LayerManager.test.ts

347 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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();
});
});
});