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 { 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(); expect(screen.getByText('未选择对象')).toBeVisible(); expect(screen.getByText('模型摘要')).toBeVisible(); expect(screen.getByText('未选择地图实例')).toBeVisible(); }); it('按照统一选择自动路由 Body 与 Joint 检查器', () => { const view = render( , ); expect(screen.getByText('Robot / Body')).toBeVisible(); expect(screen.getByText('arm', { selector: '[title="arm"]' })).toBeVisible(); view.rerender( , ); expect(screen.getByText('Robot / Joint')).toBeVisible(); expect(screen.getByText('Hinge · arm')).toBeVisible(); expect(screen.getByText('关联 Actuator')).toBeVisible(); }); it('控制台与数据录制在右侧面板内切换,不创建模态窗口', () => { const onWorkspaceToolChange = vi.fn(); render( 内嵌控制工具 ), 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( , ); 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']]); }); });