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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user