163 lines
5.3 KiB
TypeScript
163 lines
5.3 KiB
TypeScript
import type { ComponentProps } from 'react';
|
|
import { fireEvent, render, screen } from '@testing-library/react';
|
|
import { DEFAULT_PHYSICAL_MAP_CONFIG } from '../../map/types';
|
|
import type { SimulationSnapshot } from '../../simulation/SimulationSession';
|
|
import { ModelControlsSidebar } from './ModelControlsSidebar';
|
|
|
|
const snapshot = {
|
|
bodies: [
|
|
{ id: 0, name: 'world', parentId: 0 },
|
|
{ id: 1, name: 'base', parentId: 0 },
|
|
{ id: 2, name: 'arm', parentId: 1 },
|
|
],
|
|
joints: [
|
|
{
|
|
id: 7,
|
|
name: 'arm_joint',
|
|
bodyId: 2,
|
|
type: 3,
|
|
value: 0.25,
|
|
min: -1,
|
|
max: 1,
|
|
limitMin: -1,
|
|
limitMax: 1,
|
|
limited: true,
|
|
limitsIgnored: false,
|
|
editable: true,
|
|
axis: [0, 0, 1],
|
|
},
|
|
],
|
|
actuators: [],
|
|
model: { nbody: 3, njnt: 1, ngeom: 2, ncam: 0, nactuator: 0, nu: 0, nq: 1, nv: 1 },
|
|
} as unknown as SimulationSnapshot;
|
|
|
|
const noop = () => {};
|
|
const asyncTrue = async () => true;
|
|
|
|
function props(
|
|
patch: Partial<ComponentProps<typeof ModelControlsSidebar>> = {},
|
|
): ComponentProps<typeof ModelControlsSidebar> {
|
|
return {
|
|
snapshot,
|
|
selection: null,
|
|
viewerSelection: null,
|
|
loading: false,
|
|
urdfMode: 'mjcf',
|
|
baseMode: 'floating',
|
|
showCollision: false,
|
|
ignoreJointLimits: false,
|
|
jointAdvanced: false,
|
|
angleUnit: 'rad',
|
|
mapSelection: { kind: 'none' },
|
|
mapSceneDirty: false,
|
|
maps: [],
|
|
showVisualMap: true,
|
|
showMapCollision: false,
|
|
editorDocument: null,
|
|
workspaceTool: null,
|
|
onWorkspaceToolChange: noop,
|
|
onSelectJoint: noop,
|
|
onUrdfMode: noop,
|
|
onBaseMode: noop,
|
|
onShowCollision: noop,
|
|
onResetJoints: noop,
|
|
onToggleJointLimits: noop,
|
|
onToggleAdvanced: noop,
|
|
onToggleAngleUnit: noop,
|
|
onActuator: noop,
|
|
onActuatorParameters: noop,
|
|
onJoint: noop,
|
|
onApplyMap: noop,
|
|
onMapDraft: noop,
|
|
onEditorPreview: noop,
|
|
onEditorDraftChange: noop,
|
|
onEditorApply: asyncTrue,
|
|
onEditorExport: noop,
|
|
onEditorConvert: asyncTrue,
|
|
onEditorBindInteraction: noop,
|
|
onEditorSelect: noop,
|
|
onEditorSessionStateChange: noop,
|
|
onMapDisplay: noop,
|
|
...patch,
|
|
};
|
|
}
|
|
|
|
describe('ModelControlsSidebar', () => {
|
|
it('没有选择时显示场景摘要,并保留基础地图快速入口', () => {
|
|
render(<ModelControlsSidebar {...props()} />);
|
|
expect(screen.getByText('未选择对象')).toBeVisible();
|
|
expect(screen.getByText('模型摘要')).toBeVisible();
|
|
expect(screen.getByText('未选择地图实例')).toBeVisible();
|
|
});
|
|
|
|
it('按照统一选择自动路由 Body 与 Joint 检查器', () => {
|
|
const view = render(
|
|
<ModelControlsSidebar {...props({ selection: { kind: 'body', bodyId: 2 } })} />,
|
|
);
|
|
expect(screen.getByText('Robot / Body')).toBeVisible();
|
|
expect(screen.getByText('arm', { selector: '[title="arm"]' })).toBeVisible();
|
|
|
|
view.rerender(
|
|
<ModelControlsSidebar {...props({ selection: { kind: 'joint', jointId: 7, bodyId: 2 } })} />,
|
|
);
|
|
expect(screen.getByText('Robot / Joint')).toBeVisible();
|
|
expect(screen.getByText('Hinge · arm')).toBeVisible();
|
|
expect(screen.getByText('关联 Actuator')).toBeVisible();
|
|
});
|
|
|
|
it('控制台与数据录制在右侧面板内切换,不创建模态窗口', () => {
|
|
const onWorkspaceToolChange = vi.fn();
|
|
render(
|
|
<ModelControlsSidebar
|
|
{...props({
|
|
selection: { kind: 'body', bodyId: 2 },
|
|
workspaceTool: 'controls',
|
|
workspaceTools: (
|
|
<div role="tabpanel" aria-label="控制台">
|
|
内嵌控制工具
|
|
</div>
|
|
),
|
|
onWorkspaceToolChange,
|
|
})}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
expect(screen.getByRole('tabpanel', { name: '控制台' })).toBeVisible();
|
|
expect(screen.getByText('内嵌控制工具')).toBeVisible();
|
|
const inspector = document.querySelector('[role="tabpanel"][aria-label="检查器"]');
|
|
expect(inspector).not.toBeNull();
|
|
expect(inspector).not.toBeVisible();
|
|
const controlsTab = screen.getByRole('tab', { name: '控制台' });
|
|
expect(controlsTab).toHaveAttribute('aria-selected', 'true');
|
|
|
|
fireEvent.keyDown(controlsTab, { key: 'ArrowRight' });
|
|
expect(onWorkspaceToolChange).toHaveBeenCalledWith('data');
|
|
fireEvent.click(screen.getByRole('tab', { name: '检查器' }));
|
|
expect(onWorkspaceToolChange).toHaveBeenCalledWith(null);
|
|
});
|
|
|
|
it('选择地图实例后自动展示地图检查器,并把全局工具移到右侧入口', () => {
|
|
const onWorkspaceToolChange = vi.fn();
|
|
render(
|
|
<ModelControlsSidebar
|
|
{...props({
|
|
selection: { kind: 'map', mapAssetId: 'terrain-a' },
|
|
activeMapAssetId: 'terrain-a',
|
|
activeMapAssetName: '随机粗糙地形',
|
|
mapSelection: {
|
|
kind: 'builtin',
|
|
config: { ...DEFAULT_PHYSICAL_MAP_CONFIG, preset: 'rough' },
|
|
},
|
|
onWorkspaceToolChange,
|
|
})}
|
|
/>,
|
|
);
|
|
expect(screen.getByText('Map / Instance')).toBeVisible();
|
|
expect(screen.getByText('随机粗糙地形', { selector: '[title="随机粗糙地形"]' })).toBeVisible();
|
|
fireEvent.click(screen.getByRole('tab', { name: '控制台' }));
|
|
fireEvent.click(screen.getByRole('tab', { name: '数据录制' }));
|
|
expect(onWorkspaceToolChange.mock.calls).toEqual([['controls'], ['data']]);
|
|
});
|
|
});
|