118 lines
5.4 KiB
TypeScript
118 lines
5.4 KiB
TypeScript
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(<Palette schemeId="classic" onQuickAdd={() => {}} />);
|
|
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(<Palette schemeId="classic" onQuickAdd={() => {}} />);
|
|
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(<Palette schemeId="classic" onQuickAdd={onQuickAdd} />);
|
|
fireEvent.doubleClick(screen.getByTestId('palette-item-valve'));
|
|
expect(onQuickAdd).toHaveBeenCalledWith('valve');
|
|
});
|
|
});
|
|
|
|
function stubController(kind: 'node' | 'edge', typeId: string | null, props: Record<string, unknown>) {
|
|
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(<PropertyPanel controller={null} selection={[]} graphVersion={0} />);
|
|
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(<PropertyPanel controller={controller} selection={['n1']} graphVersion={0} />);
|
|
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(<PropertyPanel controller={controller} selection={['n1']} graphVersion={0} />);
|
|
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(<PropertyPanel controller={controller} selection={['n1']} graphVersion={0} />);
|
|
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(<PropertyPanel controller={controller} selection={['e1']} graphVersion={0} />);
|
|
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(<PropertyPanel controller={controller} selection={['a', 'b']} graphVersion={0} />);
|
|
expect(screen.getByText('2 items selected.')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('SettingsDialog', () => {
|
|
it('exposes the arc-on-intersection toggle and reports changes', () => {
|
|
const onChange = vi.fn();
|
|
render(
|
|
<SettingsDialog open config={{ ...DEFAULT_CONFIG }} onChange={onChange} onClose={() => {}} />,
|
|
);
|
|
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(
|
|
<SettingsDialog open={false} config={{ ...DEFAULT_CONFIG }} onChange={() => {}} onClose={() => {}} />,
|
|
);
|
|
expect(container).toBeEmptyDOMElement();
|
|
});
|
|
});
|