/** * GroupTool Tests – GroupManager: create, ungroup, nest, query */ import { describe, it, expect, beforeEach } from 'vitest'; import { GroupManager } from '../src/tools/modification/GroupTool'; import type { ElementGroup } from '../src/tools/modification/GroupTool'; describe('GroupManager', () => { let gm: GroupManager; beforeEach(() => { gm = new GroupManager(); }); describe('createGroup', () => { it('should create a group with element IDs', () => { const group = gm.createGroup(['el-1', 'el-2', 'el-3']); expect(group.id).toBeDefined(); expect(group.elementIds).toEqual(['el-1', 'el-2', 'el-3']); expect(group.parentGroupId).toBeNull(); }); it('should create a group with a custom name', () => { const group = gm.createGroup(['el-1'], 'My Group'); expect(group.name).toBe('My Group'); }); it('should create a group with default name when no name provided', () => { const group = gm.createGroup(['el-1']); expect(group.name).toContain('Group'); }); it('should create multiple groups with unique IDs', () => { const g1 = gm.createGroup(['el-1']); const g2 = gm.createGroup(['el-2']); expect(g1.id).not.toBe(g2.id); }); }); describe('ungroup', () => { it('should remove a group and return its element IDs', () => { const group = gm.createGroup(['el-1', 'el-2']); const ids = gm.ungroup(group.id); expect(ids).toEqual(['el-1', 'el-2']); expect(gm.getGroup(group.id)).toBeUndefined(); }); it('should return empty array for non-existent group', () => { const ids = gm.ungroup('non-existent'); expect(ids).toEqual([]); }); }); describe('getGroup', () => { it('should retrieve a group by ID', () => { const group = gm.createGroup(['el-1']); const retrieved = gm.getGroup(group.id); expect(retrieved).toBeDefined(); expect(retrieved!.id).toBe(group.id); }); it('should return undefined for non-existent group', () => { expect(gm.getGroup('non-existent')).toBeUndefined(); }); }); describe('getGroups', () => { it('should return all groups', () => { gm.createGroup(['el-1']); gm.createGroup(['el-2']); expect(gm.getGroups().length).toBe(2); }); it('should return empty array when no groups', () => { expect(gm.getGroups()).toEqual([]); }); }); describe('getGroupedElements', () => { it('should return set of all element IDs in all groups', () => { gm.createGroup(['el-1', 'el-2']); gm.createGroup(['el-3']); const ids = gm.getGroupedElements(); expect(ids.size).toBe(3); expect(ids.has('el-1')).toBe(true); expect(ids.has('el-2')).toBe(true); expect(ids.has('el-3')).toBe(true); }); it('should return empty set when no groups', () => { expect(gm.getGroupedElements().size).toBe(0); }); }); describe('getGroupForElement', () => { it('should return group ID for an element in a group', () => { const group = gm.createGroup(['el-1', 'el-2']); expect(gm.getGroupForElement('el-1')).toBe(group.id); expect(gm.getGroupForElement('el-2')).toBe(group.id); }); it('should return null for element not in any group', () => { expect(gm.getGroupForElement('el-lonely')).toBeNull(); }); }); describe('moveGroup', () => { it('should return element IDs that need to be moved', () => { const group = gm.createGroup(['el-1', 'el-2']); const ids = gm.moveGroup(group.id, 10, 20); expect(ids).toEqual(['el-1', 'el-2']); }); it('should return empty array for non-existent group', () => { const ids = gm.moveGroup('non-existent', 10, 20); expect(ids).toEqual([]); }); }); describe('setParent (nested groups)', () => { it('should set parent group for nesting', () => { const parent = gm.createGroup(['el-1']); const child = gm.createGroup(['el-2']); gm.setParent(child.id, parent.id); expect(gm.getGroup(child.id)!.parentGroupId).toBe(parent.id); }); it('should prevent circular references', () => { const g1 = gm.createGroup(['el-1']); const g2 = gm.createGroup(['el-2']); gm.setParent(g1.id, g2.id); // Trying to set g2's parent to g1 would create a cycle: g1 -> g2 -> g1 gm.setParent(g2.id, g1.id); expect(gm.getGroup(g2.id)!.parentGroupId).toBeNull(); }); it('should allow setting parent to null', () => { const parent = gm.createGroup(['el-1']); const child = gm.createGroup(['el-2']); gm.setParent(child.id, parent.id); gm.setParent(child.id, null); expect(gm.getGroup(child.id)!.parentGroupId).toBeNull(); }); it('should do nothing for non-existent group', () => { gm.setParent('non-existent', null); expect(gm.getGroup('non-existent')).toBeUndefined(); }); }); describe('clear', () => { it('should clear all groups', () => { gm.createGroup(['el-1']); gm.createGroup(['el-2']); gm.clear(); expect(gm.getGroups().length).toBe(0); }); }); describe('toJSON & fromJSON', () => { it('should serialize groups to JSON', () => { gm.createGroup(['el-1', 'el-2'], 'Group A'); gm.createGroup(['el-3'], 'Group B'); const json = gm.toJSON(); expect(json.length).toBe(2); expect(json[0].name).toBe('Group A'); expect(json[1].name).toBe('Group B'); }); it('should restore groups from JSON', () => { const groups: ElementGroup[] = [ { id: 'grp-1', name: 'Restored A', elementIds: ['el-1'], parentGroupId: null }, { id: 'grp-2', name: 'Restored B', elementIds: ['el-2', 'el-3'], parentGroupId: 'grp-1' }, ]; gm.fromJSON(groups); expect(gm.getGroups().length).toBe(2); expect(gm.getGroup('grp-1')!.name).toBe('Restored A'); expect(gm.getGroup('grp-2')!.parentGroupId).toBe('grp-1'); }); it('should clear existing groups when restoring from JSON', () => { gm.createGroup(['el-old']); gm.fromJSON([ { id: 'grp-new', name: 'New', elementIds: ['el-new'], parentGroupId: null }, ]); expect(gm.getGroups().length).toBe(1); expect(gm.getGroup('grp-new')).toBeDefined(); }); }); });