216 lines
7.8 KiB
TypeScript
216 lines
7.8 KiB
TypeScript
import { fireEvent, render, screen } from '@testing-library/react';
|
||
import { DEFAULT_PHYSICAL_MAP_CONFIG } from './types';
|
||
import { PhysicalMapPanel } from './PhysicalMapPanel';
|
||
|
||
const common = {
|
||
maps: [],
|
||
rootBodies: ['robot'],
|
||
loading: false,
|
||
nativeUrdf: false,
|
||
showVisualMap: true,
|
||
showMapCollision: false,
|
||
editorDocument: null,
|
||
onEditorPreview: () => {},
|
||
onEditorDraftChange: () => {},
|
||
onEditorApply: async () => true,
|
||
onEditorExport: () => {},
|
||
onEditorConvert: async () => true,
|
||
onEditorBindInteraction: () => {},
|
||
onEditorSelect: () => {},
|
||
onEditorSessionStateChange: () => {},
|
||
onMapDisplay: () => {},
|
||
onDraft: () => {},
|
||
};
|
||
|
||
describe('PhysicalMapPanel', () => {
|
||
it('无选中实例时显示引导空态', () => {
|
||
render(<PhysicalMapPanel {...common} value={{ kind: 'none' }} onApply={() => {}} />);
|
||
expect(screen.getByText('未选择地图实例')).toBeInTheDocument();
|
||
expect(screen.getByLabelText('地图来源')).toHaveValue('none');
|
||
});
|
||
|
||
it('已有场景草稿时无需再次改参数也可一次编译', () => {
|
||
const onApply = vi.fn();
|
||
render(
|
||
<PhysicalMapPanel
|
||
{...common}
|
||
value={{ kind: 'builtin', config: { ...DEFAULT_PHYSICAL_MAP_CONFIG, preset: 'rough' } }}
|
||
sceneDirty
|
||
onApply={onApply}
|
||
/>,
|
||
);
|
||
expect(screen.getByText(/场景草稿正在轻量预览/)).toBeInTheDocument();
|
||
const apply = screen.getByRole('button', { name: '应用并重新编译' });
|
||
expect(apply).toBeEnabled();
|
||
fireEvent.click(apply);
|
||
expect(onApply).toHaveBeenCalledTimes(1);
|
||
});
|
||
|
||
it('选择楼梯参数后请求重新编译', () => {
|
||
const onApply = vi.fn();
|
||
render(
|
||
<PhysicalMapPanel
|
||
{...common}
|
||
value={{ kind: 'builtin', config: { ...DEFAULT_PHYSICAL_MAP_CONFIG, preset: 'flat' } }}
|
||
onApply={onApply}
|
||
/>,
|
||
);
|
||
fireEvent.change(screen.getByLabelText('物理地图预设'), { target: { value: 'stairs' } });
|
||
fireEvent.change(screen.getByLabelText('台阶数量'), { target: { value: '6' } });
|
||
fireEvent.click(screen.getByRole('button', { name: '应用并重新编译' }));
|
||
expect(onApply).toHaveBeenCalledWith({
|
||
kind: 'builtin',
|
||
config: expect.objectContaining({ preset: 'stairs', stairCount: 6 }),
|
||
});
|
||
});
|
||
|
||
it('在右侧参数面板调整系统地形并提交', () => {
|
||
const onApply = vi.fn();
|
||
render(
|
||
<PhysicalMapPanel
|
||
{...common}
|
||
value={{ kind: 'builtin', config: { ...DEFAULT_PHYSICAL_MAP_CONFIG, preset: 'flat' } }}
|
||
onApply={onApply}
|
||
/>,
|
||
);
|
||
fireEvent.change(screen.getByLabelText('物理地图预设'), { target: { value: 'rough' } });
|
||
fireEvent.change(screen.getByLabelText('地形难度(0–1)'), { target: { value: '0.8' } });
|
||
fireEvent.change(screen.getByLabelText('随机种子'), { target: { value: '9' } });
|
||
fireEvent.click(screen.getByRole('button', { name: '应用并重新编译' }));
|
||
expect(onApply).toHaveBeenCalledWith({
|
||
kind: 'builtin',
|
||
config: expect.objectContaining({
|
||
preset: 'rough',
|
||
terrainDifficulty: 0.8,
|
||
seed: 9,
|
||
}),
|
||
});
|
||
});
|
||
|
||
it('参数地形属性不再承载视口变换工具', () => {
|
||
render(
|
||
<PhysicalMapPanel
|
||
{...common}
|
||
value={{ kind: 'builtin', config: { ...DEFAULT_PHYSICAL_MAP_CONFIG, preset: 'rough' } }}
|
||
onApply={() => {}}
|
||
/>,
|
||
);
|
||
expect(screen.queryByLabelText('地图变换模式')).not.toBeInTheDocument();
|
||
expect(screen.getByText(/已统一到视口浮动工具条/)).toBeInTheDocument();
|
||
});
|
||
|
||
it('表单参数与外部视口变换合并到同一份草稿', () => {
|
||
const onDraft = vi.fn();
|
||
const onApply = vi.fn();
|
||
const initial = {
|
||
kind: 'builtin' as const,
|
||
config: { ...DEFAULT_PHYSICAL_MAP_CONFIG, preset: 'rough' as const },
|
||
};
|
||
const view = render(
|
||
<PhysicalMapPanel
|
||
{...common}
|
||
value={initial}
|
||
sceneDirty
|
||
onDraft={onDraft}
|
||
onApply={onApply}
|
||
/>,
|
||
);
|
||
fireEvent.change(screen.getByLabelText('地形边长(m)'), { target: { value: '7' } });
|
||
const staged = onDraft.mock.calls.at(-1)?.[0];
|
||
expect(staged).toMatchObject({ kind: 'builtin', config: { size: 7 } });
|
||
const moved = {
|
||
...staged,
|
||
config: { ...staged.config, positionX: 2.4, positionY: -1.2 },
|
||
};
|
||
view.rerender(
|
||
<PhysicalMapPanel {...common} value={moved} sceneDirty onDraft={onDraft} onApply={onApply} />,
|
||
);
|
||
expect(screen.getByLabelText('地形边长(m)')).toHaveValue(7);
|
||
expect(screen.getByLabelText('位置 X(m)')).toHaveValue(2.4);
|
||
fireEvent.click(screen.getByRole('button', { name: '应用并重新编译' }));
|
||
expect(onApply).toHaveBeenLastCalledWith(
|
||
expect.objectContaining({
|
||
kind: 'builtin',
|
||
config: expect.objectContaining({ size: 7, positionX: 2.4, positionY: -1.2 }),
|
||
}),
|
||
);
|
||
});
|
||
|
||
it('可以移动并旋转资产地图', () => {
|
||
const onApply = vi.fn();
|
||
render(
|
||
<PhysicalMapPanel
|
||
{...common}
|
||
value={{ kind: 'builtin', config: { ...DEFAULT_PHYSICAL_MAP_CONFIG, preset: 'flat' } }}
|
||
onApply={onApply}
|
||
/>,
|
||
);
|
||
fireEvent.change(screen.getByLabelText('位置 X(m)'), { target: { value: '2.5' } });
|
||
fireEvent.change(screen.getByLabelText('位置 Y(m)'), { target: { value: '-1.5' } });
|
||
fireEvent.change(screen.getByLabelText('旋转 Z(°)'), { target: { value: '45' } });
|
||
fireEvent.click(screen.getByRole('button', { name: '应用并重新编译' }));
|
||
expect(onApply).toHaveBeenCalledWith({
|
||
kind: 'builtin',
|
||
config: expect.objectContaining({ positionX: 2.5, positionY: -1.5, yawDeg: 45 }),
|
||
});
|
||
});
|
||
|
||
it('显示工程地图、实例变换、出生点和根 Body', () => {
|
||
const onApply = vi.fn();
|
||
const onDraft = vi.fn();
|
||
render(
|
||
<PhysicalMapPanel
|
||
{...common}
|
||
value={{ kind: 'project', descriptorPath: 'maps/warehouse/map.json' }}
|
||
maps={[
|
||
{
|
||
descriptorPath: 'maps/warehouse/map.json',
|
||
schemaVersion: 1 as const,
|
||
id: 'warehouse',
|
||
name: '仓库',
|
||
spawnPoints: [{ id: 'door', name: '入口' }],
|
||
},
|
||
]}
|
||
rootBodies={['robot', 'payload']}
|
||
onDraft={onDraft}
|
||
onApply={onApply}
|
||
/>,
|
||
);
|
||
fireEvent.change(screen.getByLabelText('位置 X(m)'), { target: { value: '2.5' } });
|
||
fireEvent.change(screen.getByLabelText('位置 Y(m)'), { target: { value: '-1.5' } });
|
||
fireEvent.change(screen.getByLabelText('旋转 Z(°)'), { target: { value: '45' } });
|
||
fireEvent.change(screen.getByLabelText('地图出生点'), { target: { value: 'door' } });
|
||
expect(screen.getByLabelText('地图出生点')).toHaveValue('door');
|
||
fireEvent.change(screen.getByLabelText('机器人根 Body'), { target: { value: 'robot' } });
|
||
fireEvent.click(screen.getByRole('button', { name: '应用并重新编译' }));
|
||
expect(onApply).toHaveBeenCalledWith(
|
||
expect.objectContaining({
|
||
kind: 'project',
|
||
descriptorPath: 'maps/warehouse/map.json',
|
||
positionX: 2.5,
|
||
positionY: -1.5,
|
||
yawDeg: 45,
|
||
spawnPointId: 'door',
|
||
robotRootBody: 'robot',
|
||
}),
|
||
);
|
||
expect(onDraft).toHaveBeenLastCalledWith(
|
||
expect.objectContaining({ kind: 'project', robotRootBody: 'robot' }),
|
||
);
|
||
});
|
||
|
||
it('原生 URDF 模式只允许移除地图', () => {
|
||
render(
|
||
<PhysicalMapPanel
|
||
{...common}
|
||
value={{ kind: 'builtin', config: { ...DEFAULT_PHYSICAL_MAP_CONFIG, preset: 'flat' } }}
|
||
nativeUrdf
|
||
onApply={() => {}}
|
||
/>,
|
||
);
|
||
expect(screen.getByRole('status')).toHaveTextContent('原生 URDF');
|
||
expect(screen.getByRole('button', { name: '应用并重新编译' })).toBeDisabled();
|
||
expect(screen.getByLabelText('地图来源')).not.toBeDisabled();
|
||
});
|
||
});
|