diagrams-fable-tauri/tests/components.test.tsx
2026-07-02 23:22:21 +02:00

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();
});
});