merge: combine all features from both codebases - mobile dashboard + layer panel + notifications + shares + all fixes
This commit is contained in:
@@ -136,8 +136,9 @@ describe('StatusBar', () => {
|
||||
|
||||
it('should render cursor coordinates', () => {
|
||||
render(<StatusBar {...defaultProps} />);
|
||||
// Default unit is mm, scaleFactor=1: 123.456 → '123', 78.9 → '79'
|
||||
expect(screen.getByText(/123/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/78/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/79/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render active layer and tool', () => {
|
||||
@@ -238,6 +239,112 @@ describe('LayerPanel', () => {
|
||||
fireEvent.click(toggleBtn);
|
||||
expect(onToggleLayer).toHaveBeenCalled();
|
||||
});
|
||||
// Helper: expand a tree node by clicking its toggle button
|
||||
function expandLayer(label: string) {
|
||||
const layerNode = screen.getByText(label).closest('.tree-node');
|
||||
expect(layerNode).toBeTruthy();
|
||||
const toggle = layerNode?.querySelector('.tree-toggle');
|
||||
expect(toggle).toBeTruthy();
|
||||
fireEvent.click(toggle!);
|
||||
}
|
||||
|
||||
it('should highlight selected element in tree when selectedElementIds is provided', () => {
|
||||
const layers = [makeLayer({ id: 'layer-1', name: 'Layer 1' })];
|
||||
const elements = [
|
||||
makeElement({ id: 'elem-1', type: 'rect', layerId: 'layer-1' }),
|
||||
makeElement({ id: 'elem-2', type: 'circle', layerId: 'layer-1' }),
|
||||
];
|
||||
const { container } = render(
|
||||
<LayerPanel
|
||||
layers={layers}
|
||||
elements={elements}
|
||||
selectedElementIds={['elem-2']}
|
||||
activeLayerId="layer-1"
|
||||
onSelectLayer={() => {}}
|
||||
onAddLayer={() => {}}
|
||||
onToggleLayer={() => {}}
|
||||
/>,
|
||||
);
|
||||
expandLayer('Layer 1');
|
||||
// The element node for elem-2 should have the 'active' class
|
||||
const treeNodes = container.querySelectorAll('.tree-node');
|
||||
const elemNode = Array.from(treeNodes).find((n) => n.textContent?.includes('Kreis'));
|
||||
expect(elemNode).toBeTruthy();
|
||||
expect(elemNode?.classList.contains('active')).toBe(true);
|
||||
});
|
||||
|
||||
it('should call onSelectElement when clicking an element in the tree', () => {
|
||||
const layers = [makeLayer({ id: 'layer-1', name: 'Layer 1' })];
|
||||
const elements = [
|
||||
makeElement({ id: 'elem-1', type: 'rect', layerId: 'layer-1' }),
|
||||
];
|
||||
const onSelectElement = vi.fn();
|
||||
render(
|
||||
<LayerPanel
|
||||
layers={layers}
|
||||
elements={elements}
|
||||
activeLayerId="layer-1"
|
||||
onSelectLayer={() => {}}
|
||||
onSelectElement={onSelectElement}
|
||||
onAddLayer={() => {}}
|
||||
onToggleLayer={() => {}}
|
||||
/>,
|
||||
);
|
||||
expandLayer('Layer 1');
|
||||
fireEvent.click(screen.getByText('Rechteck'));
|
||||
expect(onSelectElement).toHaveBeenCalledWith('elem-1');
|
||||
});
|
||||
|
||||
it('should render groups as tree nodes with their elements as children', () => {
|
||||
const layers = [makeLayer({ id: 'layer-1', name: 'Layer 1' })];
|
||||
const elements = [
|
||||
makeElement({ id: 'elem-1', type: 'rect', layerId: 'layer-1' }),
|
||||
makeElement({ id: 'elem-2', type: 'circle', layerId: 'layer-1' }),
|
||||
];
|
||||
const groups = [
|
||||
{ id: 'group-1', name: 'Gruppe A', elementIds: ['elem-1', 'elem-2'], parentGroupId: null },
|
||||
];
|
||||
render(
|
||||
<LayerPanel
|
||||
layers={layers}
|
||||
elements={elements}
|
||||
groups={groups}
|
||||
activeLayerId="layer-1"
|
||||
onSelectLayer={() => {}}
|
||||
onAddLayer={() => {}}
|
||||
onToggleLayer={() => {}}
|
||||
/>,
|
||||
);
|
||||
expandLayer('Layer 1');
|
||||
expect(screen.getByText('Gruppe A')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should select all group elements when clicking a group node', () => {
|
||||
const layers = [makeLayer({ id: 'layer-1', name: 'Layer 1' })];
|
||||
const elements = [
|
||||
makeElement({ id: 'elem-1', type: 'rect', layerId: 'layer-1' }),
|
||||
makeElement({ id: 'elem-2', type: 'circle', layerId: 'layer-1' }),
|
||||
];
|
||||
const groups = [
|
||||
{ id: 'group-1', name: 'Gruppe A', elementIds: ['elem-1', 'elem-2'], parentGroupId: null },
|
||||
];
|
||||
const onSelectElement = vi.fn();
|
||||
render(
|
||||
<LayerPanel
|
||||
layers={layers}
|
||||
elements={elements}
|
||||
groups={groups}
|
||||
activeLayerId="layer-1"
|
||||
onSelectLayer={() => {}}
|
||||
onSelectElement={onSelectElement}
|
||||
onAddLayer={() => {}}
|
||||
onToggleLayer={() => {}}
|
||||
/>,
|
||||
);
|
||||
expandLayer('Layer 1');
|
||||
fireEvent.click(screen.getByText('Gruppe A'));
|
||||
expect(onSelectElement).toHaveBeenCalledWith('elem-1');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── PropertiesPanel ─────────────────────────────────
|
||||
@@ -246,29 +353,31 @@ describe('PropertiesPanel', () => {
|
||||
const layers = [makeLayer({ id: 'layer-1', name: 'Layer 1' })];
|
||||
const element = makeElement({ id: 'elem-1', type: 'rect', x: 10, y: 20, width: 100, height: 50 });
|
||||
|
||||
it('should show default values when no element selected', () => {
|
||||
it('should show empty inputs when no element selected', () => {
|
||||
render(<PropertiesPanel selectedElement={null} layers={layers} onUpdateProperty={() => {}} />);
|
||||
// Default values are in inputs with aria-labels
|
||||
expect(screen.getByLabelText('Position X')).toHaveDisplayValue('0.000 m');
|
||||
expect(screen.getByLabelText('Position Y')).toHaveDisplayValue('0.000 m');
|
||||
// When no element is selected, input fields are empty
|
||||
expect(screen.getByLabelText('Position X')).toHaveDisplayValue('');
|
||||
expect(screen.getByLabelText('Position Y')).toHaveDisplayValue('');
|
||||
});
|
||||
|
||||
it('should display element coordinates when element is selected', () => {
|
||||
it('should display element coordinates in mm by default', () => {
|
||||
render(<PropertiesPanel selectedElement={element} layers={layers} onUpdateProperty={() => {}} />);
|
||||
expect(screen.getByLabelText('Position X')).toHaveDisplayValue('10.000 m');
|
||||
expect(screen.getByLabelText('Position Y')).toHaveDisplayValue('20.000 m');
|
||||
// Default unit is mm with scaleFactor=1, so world units are displayed as mm
|
||||
expect(screen.getByLabelText('Position X')).toHaveDisplayValue('10');
|
||||
expect(screen.getByLabelText('Position Y')).toHaveDisplayValue('20');
|
||||
});
|
||||
|
||||
it('should display element dimensions', () => {
|
||||
it('should display element dimensions in mm by default', () => {
|
||||
render(<PropertiesPanel selectedElement={element} layers={layers} onUpdateProperty={() => {}} />);
|
||||
expect(screen.getByLabelText('Breite')).toHaveDisplayValue('100.00 m');
|
||||
expect(screen.getByLabelText('Tiefe')).toHaveDisplayValue('50.00 m');
|
||||
expect(screen.getByLabelText('Breite')).toHaveDisplayValue('100');
|
||||
expect(screen.getByLabelText('Tiefe')).toHaveDisplayValue('50');
|
||||
});
|
||||
|
||||
it('should call onUpdateProperty when changing a property input', () => {
|
||||
it('should call onUpdateProperty with world-unit number when changing a property input', () => {
|
||||
const onUpdateProperty = vi.fn();
|
||||
render(<PropertiesPanel selectedElement={element} layers={layers} onUpdateProperty={onUpdateProperty} />);
|
||||
fireEvent.change(screen.getByLabelText('Position X'), { target: { value: '999' } });
|
||||
expect(onUpdateProperty).toHaveBeenCalledWith('x', '999');
|
||||
// In mm mode with scaleFactor=1, input '999' → 999 world units
|
||||
expect(onUpdateProperty).toHaveBeenCalledWith('x', 999);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* InlineTextEditor Tests
|
||||
*/
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import InlineTextEditor from '../src/components/InlineTextEditor';
|
||||
|
||||
describe('InlineTextEditor', () => {
|
||||
it('should render with initial text', () => {
|
||||
render(<InlineTextEditor initialText="Hello" onSubmit={() => {}} onCancel={() => {}} />);
|
||||
const input = screen.getByDisplayValue('Hello');
|
||||
expect(input).toBeDefined();
|
||||
});
|
||||
|
||||
it('should call onSubmit with text when Enter is pressed', () => {
|
||||
const onSubmit = vi.fn();
|
||||
render(<InlineTextEditor initialText="Test" onSubmit={onSubmit} onCancel={() => {}} />);
|
||||
const input = screen.getByDisplayValue('Test');
|
||||
fireEvent.keyDown(input, { key: 'Enter' });
|
||||
expect(onSubmit).toHaveBeenCalledWith('Test');
|
||||
});
|
||||
|
||||
it('should call onCancel when Escape is pressed', () => {
|
||||
const onCancel = vi.fn();
|
||||
render(<InlineTextEditor initialText="Test" onSubmit={() => {}} onCancel={onCancel} />);
|
||||
const input = screen.getByDisplayValue('Test');
|
||||
fireEvent.keyDown(input, { key: 'Escape' });
|
||||
expect(onCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update text when typing', () => {
|
||||
const onSubmit = vi.fn();
|
||||
render(<InlineTextEditor initialText="" onSubmit={onSubmit} onCancel={() => {}} />);
|
||||
const input = screen.getByRole('textbox') as HTMLInputElement;
|
||||
fireEvent.change(input, { target: { value: 'New Text' } });
|
||||
fireEvent.keyDown(input, { key: 'Enter' });
|
||||
expect(onSubmit).toHaveBeenCalledWith('New Text');
|
||||
});
|
||||
|
||||
it('should call onSubmit when confirm button is clicked', () => {
|
||||
const onSubmit = vi.fn();
|
||||
render(<InlineTextEditor initialText="Initial" onSubmit={onSubmit} onCancel={() => {}} />);
|
||||
const confirmBtn = screen.getByText('Bestätigen (Enter)');
|
||||
fireEvent.click(confirmBtn);
|
||||
expect(onSubmit).toHaveBeenCalledWith('Initial');
|
||||
});
|
||||
|
||||
it('should call onCancel when cancel button is clicked', () => {
|
||||
const onCancel = vi.fn();
|
||||
render(<InlineTextEditor initialText="" onSubmit={() => {}} onCancel={onCancel} />);
|
||||
const cancelBtn = screen.getByText('Abbrechen (Esc)');
|
||||
fireEvent.click(cancelBtn);
|
||||
expect(onCancel).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -272,6 +272,21 @@ describe('SelectionEngine', () => {
|
||||
s.selectionEngine.cancelBoxSelect();
|
||||
expect(s.selectionEngine.isBoxSelecting()).toBe(false);
|
||||
});
|
||||
|
||||
it('hasBoxMoved should return false when box not started', () => {
|
||||
expect(s.selectionEngine.hasBoxMoved()).toBe(false);
|
||||
});
|
||||
|
||||
it('hasBoxMoved should return false when start equals end (no drag)', () => {
|
||||
s.selectionEngine.startBoxSelect(50, 50);
|
||||
expect(s.selectionEngine.hasBoxMoved()).toBe(false);
|
||||
});
|
||||
|
||||
it('hasBoxMoved should return true when box end differs from start', () => {
|
||||
s.selectionEngine.startBoxSelect(50, 50);
|
||||
s.selectionEngine.updateBoxSelect(100, 100, []);
|
||||
expect(s.selectionEngine.hasBoxMoved()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearSelection', () => {
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* Global Blocks API Service Tests
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// Mock fetch globally
|
||||
const mockFetch = vi.fn();
|
||||
vi.stubGlobal('fetch', mockFetch);
|
||||
|
||||
// Import after mock is set up
|
||||
import {
|
||||
getGlobalFolders,
|
||||
createGlobalFolder,
|
||||
renameGlobalFolder,
|
||||
deleteGlobalFolder,
|
||||
getGlobalBlocks,
|
||||
createGlobalBlock,
|
||||
renameGlobalBlock,
|
||||
deleteGlobalBlock,
|
||||
} from '../src/services/api';
|
||||
|
||||
const TOKEN = 'test-token-123';
|
||||
|
||||
function mockResponse(data: unknown, ok = true, status = 200): Response {
|
||||
return {
|
||||
ok,
|
||||
status,
|
||||
json: () => Promise.resolve(data),
|
||||
} as Response;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockFetch.mockReset();
|
||||
});
|
||||
|
||||
describe('Global Blocks API Service', () => {
|
||||
describe('getGlobalFolders', () => {
|
||||
it('should fetch all folders', async () => {
|
||||
const folders = [{ id: 'f1', name: 'Folder 1', parent_id: null, created_at: '2026-01-01' }];
|
||||
mockFetch.mockResolvedValue(mockResponse(folders));
|
||||
const result = await getGlobalFolders(TOKEN);
|
||||
expect(result).toEqual(folders);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('/api/global-folders'),
|
||||
expect.objectContaining({ headers: expect.objectContaining({ Authorization: `Bearer ${TOKEN}` }) }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should fetch root folders when parentId=null', async () => {
|
||||
const folders = [{ id: 'f1', name: 'Root', parent_id: null, created_at: '2026-01-01' }];
|
||||
mockFetch.mockResolvedValue(mockResponse(folders));
|
||||
const result = await getGlobalFolders(TOKEN, null);
|
||||
expect(result).toEqual(folders);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('parentId=null'),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should fetch sub-folders for a parent', async () => {
|
||||
const folders = [{ id: 'f2', name: 'Sub', parent_id: 'f1', created_at: '2026-01-01' }];
|
||||
mockFetch.mockResolvedValue(mockResponse(folders));
|
||||
const result = await getGlobalFolders(TOKEN, 'f1');
|
||||
expect(result).toEqual(folders);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('parentId=f1'),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw on fetch failure', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse({ error: 'fail' }, false, 500));
|
||||
await expect(getGlobalFolders(TOKEN)).rejects.toThrow('Failed to load global folders');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGlobalFolder', () => {
|
||||
it('should create a folder', async () => {
|
||||
const folder = { id: 'f1', name: 'New Folder', parent_id: null, created_at: '2026-01-01' };
|
||||
mockFetch.mockResolvedValue(mockResponse(folder, true, 201));
|
||||
const result = await createGlobalFolder(TOKEN, 'New Folder');
|
||||
expect(result).toEqual(folder);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('/api/global-folders'),
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name: 'New Folder', parent_id: null }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should create a sub-folder with parentId', async () => {
|
||||
const folder = { id: 'f2', name: 'Sub', parent_id: 'f1', created_at: '2026-01-01' };
|
||||
mockFetch.mockResolvedValue(mockResponse(folder, true, 201));
|
||||
const result = await createGlobalFolder(TOKEN, 'Sub', 'f1');
|
||||
expect(result).toEqual(folder);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
body: JSON.stringify({ name: 'Sub', parent_id: 'f1' }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renameGlobalFolder', () => {
|
||||
it('should rename a folder', async () => {
|
||||
const folder = { id: 'f1', name: 'Renamed', parent_id: null, created_at: '2026-01-01' };
|
||||
mockFetch.mockResolvedValue(mockResponse(folder));
|
||||
const result = await renameGlobalFolder(TOKEN, 'f1', 'Renamed');
|
||||
expect(result).toEqual(folder);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('/api/global-folders/f1'),
|
||||
expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ name: 'Renamed' }) }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteGlobalFolder', () => {
|
||||
it('should delete a folder', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse(null, true, 204));
|
||||
await deleteGlobalFolder(TOKEN, 'f1');
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('/api/global-folders/f1'),
|
||||
expect.objectContaining({ method: 'DELETE' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGlobalBlocks', () => {
|
||||
it('should fetch all blocks', async () => {
|
||||
const blocks = [{ id: 'b1', folder_id: null, name: 'Block 1', block_data: '{}', svg_data: null, created_at: '2026-01-01', updated_at: '2026-01-01' }];
|
||||
mockFetch.mockResolvedValue(mockResponse(blocks));
|
||||
const result = await getGlobalBlocks(TOKEN);
|
||||
expect(result).toEqual(blocks);
|
||||
});
|
||||
|
||||
it('should fetch blocks for a folder', async () => {
|
||||
const blocks = [{ id: 'b1', folder_id: 'f1', name: 'Block 1', block_data: '{}', svg_data: null, created_at: '2026-01-01', updated_at: '2026-01-01' }];
|
||||
mockFetch.mockResolvedValue(mockResponse(blocks));
|
||||
const result = await getGlobalBlocks(TOKEN, 'f1');
|
||||
expect(result).toEqual(blocks);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('folderId=f1'),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should fetch root-level blocks with folderId=null', async () => {
|
||||
const blocks = [{ id: 'b1', folder_id: null, name: 'Root Block', block_data: '{}', svg_data: null, created_at: '2026-01-01', updated_at: '2026-01-01' }];
|
||||
mockFetch.mockResolvedValue(mockResponse(blocks));
|
||||
const result = await getGlobalBlocks(TOKEN, null);
|
||||
expect(result).toEqual(blocks);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('folderId=null'),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGlobalBlock', () => {
|
||||
it('should create a block with full data', async () => {
|
||||
const block = { id: 'b1', folder_id: 'f1', name: 'Chair', block_data: '{"type":"chair"}', svg_data: '<svg/>', created_at: '2026-01-01', updated_at: '2026-01-01' };
|
||||
mockFetch.mockResolvedValue(mockResponse(block, true, 201));
|
||||
const result = await createGlobalBlock(TOKEN, {
|
||||
name: 'Chair',
|
||||
folder_id: 'f1',
|
||||
block_data: '{"type":"chair"}',
|
||||
svg_data: '<svg/>',
|
||||
});
|
||||
expect(result).toEqual(block);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
name: 'Chair',
|
||||
folder_id: 'f1',
|
||||
block_data: '{"type":"chair"}',
|
||||
svg_data: '<svg/>',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renameGlobalBlock', () => {
|
||||
it('should rename a block', async () => {
|
||||
const block = { id: 'b1', folder_id: null, name: 'Renamed', block_data: '{}', svg_data: null, created_at: '2026-01-01', updated_at: '2026-01-01' };
|
||||
mockFetch.mockResolvedValue(mockResponse(block));
|
||||
const result = await renameGlobalBlock(TOKEN, 'b1', 'Renamed');
|
||||
expect(result).toEqual(block);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('/api/global-blocks/b1'),
|
||||
expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ name: 'Renamed' }) }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteGlobalBlock', () => {
|
||||
it('should delete a block', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse(null, true, 204));
|
||||
await deleteGlobalBlock(TOKEN, 'b1');
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('/api/global-blocks/b1'),
|
||||
expect.objectContaining({ method: 'DELETE' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* GroupTool Extended Tests — getGroupElements method
|
||||
*/
|
||||
import { GroupManager } from '../src/tools/modification/GroupTool';
|
||||
|
||||
describe('GroupManager getGroupElements', () => {
|
||||
it('should return only the element itself when not in a group', () => {
|
||||
const gm = new GroupManager();
|
||||
const result = gm.getGroupElements('el1');
|
||||
expect(result).toEqual(['el1']);
|
||||
});
|
||||
|
||||
it('should return all group members when element is in a group', () => {
|
||||
const gm = new GroupManager();
|
||||
gm.createGroup(['el1', 'el2', 'el3']);
|
||||
const result = gm.getGroupElements('el1');
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result).toContain('el1');
|
||||
expect(result).toContain('el2');
|
||||
expect(result).toContain('el3');
|
||||
});
|
||||
|
||||
it('should return all group members for any member element', () => {
|
||||
const gm = new GroupManager();
|
||||
gm.createGroup(['a', 'b', 'c']);
|
||||
expect(gm.getGroupElements('b')).toContain('a');
|
||||
expect(gm.getGroupElements('b')).toContain('b');
|
||||
expect(gm.getGroupElements('b')).toContain('c');
|
||||
expect(gm.getGroupElements('c')).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should return only the element when group was dissolved', () => {
|
||||
const gm = new GroupManager();
|
||||
const group = gm.createGroup(['x', 'y']);
|
||||
gm.ungroup(group.id);
|
||||
expect(gm.getGroupElements('x')).toEqual(['x']);
|
||||
expect(gm.getGroupElements('y')).toEqual(['y']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user