import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import { Palette } from '../src/ui/Palette'; import { PropertyPanel } from '../src/ui/PropertyPanel'; import { SettingsDialog } from '../src/ui/SettingsDialog'; import { DEFAULT_CONFIG, type EditorController } from '../src/editor/paper'; import { defaultNodeProps } from '../src/model/nodeTypes'; import { defaultEdgeProps } from '../src/model/graph'; describe('Palette', () => { it('renders grouped node types with draggable items', () => { render( {}} />); expect(screen.getByText('Sources & supply')).toBeInTheDocument(); expect(screen.getByText('Valves & fittings')).toBeInTheDocument(); const pump = screen.getByTestId('palette-item-pump'); expect(pump).toHaveAttribute('draggable', 'true'); }); it('filters items by search query', () => { render( {}} />); fireEvent.change(screen.getByPlaceholderText('Search nodes…'), { target: { value: 'boiler' } }); expect(screen.getByTestId('palette-item-boiler')).toBeInTheDocument(); expect(screen.queryByTestId('palette-item-pump')).not.toBeInTheDocument(); }); it('quick-adds a node on double click', () => { const onQuickAdd = vi.fn(); render(); fireEvent.doubleClick(screen.getByTestId('palette-item-valve')); expect(onQuickAdd).toHaveBeenCalledWith('valve'); }); }); function stubController(kind: 'node' | 'edge', typeId: string | null, props: Record) { const setCellProp = vi.fn(); const controller = { cellKind: () => kind, cellTypeId: () => typeId, getCellProps: () => ({ ...props }), setCellProp, } as unknown as EditorController; return { controller, setCellProp }; } describe('PropertyPanel', () => { it('shows a hint when nothing is selected', () => { render(); expect(screen.getByText(/Select a node or an edge/)).toBeInTheDocument(); }); it('renders grouped editors for a node', () => { const { controller } = stubController('node', 'pump', defaultNodeProps('pump')); render(); expect(screen.getByTestId('prop-group-general')).toBeInTheDocument(); expect(screen.getByTestId('prop-group-presentation')).toBeInTheDocument(); expect(screen.getByTestId('prop-group-physics')).toBeInTheDocument(); expect(screen.getByTestId('prop-title')).toHaveValue('Pump'); expect(screen.getByTestId('prop-head')).toHaveValue(32); // catalogueItem picker renders catalogue options expect(screen.getByTestId('prop-medium')).toBeInTheDocument(); }); it('commits an edited numeric property through the controller', () => { const { controller, setCellProp } = stubController('node', 'pump', defaultNodeProps('pump')); render(); const head = screen.getByTestId('prop-head'); fireEvent.change(head, { target: { value: '48' } }); fireEvent.blur(head); expect(setCellProp).toHaveBeenCalledWith('n1', 'head', 48); }); it('rejects invalid values and shows the error instead of committing', () => { const { controller, setCellProp } = stubController('node', 'pump', defaultNodeProps('pump')); render(); const head = screen.getByTestId('prop-head'); fireEvent.change(head, { target: { value: '-5' } }); fireEvent.blur(head); expect(setCellProp).not.toHaveBeenCalled(); expect(screen.getByText(/must be ≥ 0/)).toBeInTheDocument(); }); it('renders edge properties with marker enums and read-only sim results', () => { const { controller } = stubController('edge', null, { ...defaultEdgeProps(), simFlow: 12.5 }); render(); expect(screen.getByText('Pipe / line')).toBeInTheDocument(); expect(screen.getByTestId('prop-targetMarker')).toHaveValue('arrow'); expect(screen.getByTestId('prop-simFlow')).toHaveTextContent('12.5'); // multi catalogue items editor renders checkboxes expect(screen.getByTestId('prop-insulation')).toBeInTheDocument(); }); it('shows a count for multi-selection', () => { const { controller } = stubController('node', 'pump', {}); render(); expect(screen.getByText('2 items selected.')).toBeInTheDocument(); }); }); describe('SettingsDialog', () => { it('exposes the arc-on-intersection toggle and reports changes', () => { const onChange = vi.fn(); render( {}} />, ); const arc = screen.getByTestId('setting-arc'); expect(arc).toBeChecked(); fireEvent.click(arc); expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ arcOnIntersections: false })); }); it('renders nothing when closed', () => { const { container } = render( {}} onClose={() => {}} />, ); expect(container).toBeEmptyDOMElement(); }); });