furniture-3d-system-Wu/tests/lib/subassembly-manager.test.ts

339 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* SubassemblyManager 单元测试
*/
import * as THREE from 'three'
import { SubassemblyManager, SubassemblyConfig, PanelInfo } from '@/lib/3d/subassembly-manager'
// Mock GSAP - 立即完成动画
jest.mock('gsap', () => {
const mockTimeline = {
to: jest.fn(function(this: { _onComplete?: () => void }, _target: unknown, config: { onComplete?: () => void }) {
// 存储 onComplete 回调
if (config?.onComplete) {
this._onComplete = config.onComplete
}
return this
}),
_onComplete: undefined as (() => void) | undefined,
}
return {
timeline: jest.fn((config?: { onComplete?: () => void }) => {
// 当timeline创建时,立即触发 onComplete
if (config?.onComplete) {
setTimeout(config.onComplete, 10)
}
return mockTimeline
}),
to: jest.fn((_target, config) => {
if (config?.onComplete) {
setTimeout(config.onComplete, 10)
}
return { kill: jest.fn() }
}),
}
})
describe('SubassemblyManager', () => {
let manager: SubassemblyManager
let scene: THREE.Scene
const createMockPanel = (id: string, order: number = 0): PanelInfo => {
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(100, 50, 20),
new THREE.MeshStandardMaterial()
)
mesh.position.set(Math.random() * 100, Math.random() * 100, Math.random() * 100)
return {
id,
name: `Panel ${id}`,
mesh,
position: mesh.position.clone(),
installOrder: order,
}
}
const createDrawerConfig = (id: string, panelCount: number = 4): SubassemblyConfig => {
const panels = Array.from({ length: panelCount }, (_, i) =>
createMockPanel(`panel-${i}`, i)
)
return {
id,
name: `Drawer ${id}`,
type: 'drawer',
panels,
explodeOffset: 30,
}
}
beforeEach(() => {
scene = new THREE.Scene()
scene.add = jest.fn()
scene.remove = jest.fn()
manager = new SubassemblyManager(scene)
})
afterEach(() => {
manager.dispose()
})
describe('创建子组件', () => {
it('应该正确创建子组件', () => {
const config = createDrawerConfig('drawer-1')
const instance = manager.createSubassembly(config)
expect(instance).toBeDefined()
expect(instance.config.id).toBe('drawer-1')
expect(instance.config.type).toBe('drawer')
expect(instance.isExpanded).toBe(false)
expect(manager.getCount()).toBe(1)
})
it('创建时应该将板件添加到组', () => {
const config = createDrawerConfig('drawer-1', 4)
const instance = manager.createSubassembly(config)
expect(instance.group.children.length).toBe(4)
})
it('重复创建应该替换旧的', () => {
const config1 = createDrawerConfig('drawer-1', 2)
const config2 = createDrawerConfig('drawer-1', 4)
manager.createSubassembly(config1)
expect(manager.getCount()).toBe(1)
manager.createSubassembly(config2)
expect(manager.getCount()).toBe(1)
const instance = manager.getSubassembly('drawer-1')
expect(instance?.config.panels.length).toBe(4)
})
it('应该保存原始位置', () => {
const config = createDrawerConfig('drawer-1')
const instance = manager.createSubassembly(config)
expect(instance.originalPositions.size).toBe(config.panels.length)
})
})
describe('移除子组件', () => {
it('应该正确移除子组件', () => {
const config = createDrawerConfig('drawer-1')
manager.createSubassembly(config)
expect(manager.getCount()).toBe(1)
const result = manager.removeSubassembly('drawer-1')
expect(result).toBe(true)
expect(manager.getCount()).toBe(0)
})
it('移除不存在的子组件应该返回 false', () => {
const result = manager.removeSubassembly('not-exist')
expect(result).toBe(false)
})
})
describe('显示/隐藏', () => {
it('hide 应该隐藏子组件', () => {
const config = createDrawerConfig('drawer-1')
const instance = manager.createSubassembly(config)
instance.group.visible = true
const result = manager.hide('drawer-1')
expect(result).toBe(true)
expect(instance.group.visible).toBe(false)
})
it('show 应该显示子组件', () => {
const config = createDrawerConfig('drawer-1')
const instance = manager.createSubassembly(config)
instance.group.visible = false
const result = manager.show('drawer-1')
expect(result).toBe(true)
expect(instance.group.visible).toBe(true)
})
it('对不存在的子组件操作应该返回 false', () => {
expect(manager.hide('not-exist')).toBe(false)
expect(manager.show('not-exist')).toBe(false)
})
})
describe('位置控制', () => {
it('setPosition 应该设置子组件位置', () => {
const config = createDrawerConfig('drawer-1')
manager.createSubassembly(config)
const newPosition = new THREE.Vector3(100, 200, 300)
const result = manager.setPosition('drawer-1', newPosition)
expect(result).toBe(true)
const instance = manager.getSubassembly('drawer-1')
expect(instance?.group.position.x).toBe(100)
expect(instance?.group.position.y).toBe(200)
expect(instance?.group.position.z).toBe(300)
})
it('对不存在的子组件设置位置应该返回 false', () => {
const result = manager.setPosition('not-exist', new THREE.Vector3())
expect(result).toBe(false)
})
})
describe('查询功能', () => {
it('getSubassembly 应该返回正确的实例', () => {
const config = createDrawerConfig('drawer-1')
manager.createSubassembly(config)
const instance = manager.getSubassembly('drawer-1')
expect(instance).toBeDefined()
expect(instance?.config.id).toBe('drawer-1')
})
it('getSubassembly 对不存在的应该返回 undefined', () => {
const instance = manager.getSubassembly('not-exist')
expect(instance).toBeUndefined()
})
it('getAllSubassemblies 应该返回所有子组件', () => {
manager.createSubassembly(createDrawerConfig('drawer-1'))
manager.createSubassembly(createDrawerConfig('drawer-2'))
const all = manager.getAllSubassemblies()
expect(all.size).toBe(2)
expect(all.has('drawer-1')).toBe(true)
expect(all.has('drawer-2')).toBe(true)
})
it('getPanels 应该返回板件列表', () => {
const config = createDrawerConfig('drawer-1', 3)
manager.createSubassembly(config)
const panels = manager.getPanels('drawer-1')
expect(panels).not.toBeNull()
expect(panels?.length).toBe(3)
})
it('getPanels 对不存在的应该返回 null', () => {
const panels = manager.getPanels('not-exist')
expect(panels).toBeNull()
})
it('findSubassemblyByPanel 应该找到包含板件的子组件', () => {
const config = createDrawerConfig('drawer-1')
manager.createSubassembly(config)
const panelId = config.panels[0].id
const subassemblyId = manager.findSubassemblyByPanel(panelId)
expect(subassemblyId).toBe('drawer-1')
})
it('findSubassemblyByPanel 对不存在的板件应该返回 null', () => {
manager.createSubassembly(createDrawerConfig('drawer-1'))
const result = manager.findSubassemblyByPanel('not-exist-panel')
expect(result).toBeNull()
})
})
describe('展开/折叠', () => {
it('expand 应该设置 isExpanded 为 true', async () => {
const config = createDrawerConfig('drawer-1')
const instance = manager.createSubassembly(config)
expect(instance.isExpanded).toBe(false)
await manager.expand('drawer-1')
expect(instance.isExpanded).toBe(true)
})
it('collapse 应该设置 isExpanded 为 false', async () => {
const config = createDrawerConfig('drawer-1')
const instance = manager.createSubassembly(config)
await manager.expand('drawer-1')
expect(instance.isExpanded).toBe(true)
await manager.collapse('drawer-1')
expect(instance.isExpanded).toBe(false)
})
it('对已展开的子组件再次展开不应该有效果', async () => {
const config = createDrawerConfig('drawer-1')
const instance = manager.createSubassembly(config)
await manager.expand('drawer-1')
const expandedState1 = instance.isExpanded
await manager.expand('drawer-1')
const expandedState2 = instance.isExpanded
expect(expandedState1).toBe(expandedState2)
})
})
describe('不同类型的子组件', () => {
it('应该支持 door 类型', () => {
const config: SubassemblyConfig = {
id: 'door-1',
name: 'Door',
type: 'door',
panels: [createMockPanel('panel-1')],
}
const instance = manager.createSubassembly(config)
expect(instance.config.type).toBe('door')
})
it('应该支持 shelf 类型', () => {
const config: SubassemblyConfig = {
id: 'shelf-1',
name: 'Shelf',
type: 'shelf',
panels: [createMockPanel('panel-1')],
}
const instance = manager.createSubassembly(config)
expect(instance.config.type).toBe('shelf')
})
it('应该支持 custom 类型', () => {
const config: SubassemblyConfig = {
id: 'custom-1',
name: 'Custom',
type: 'custom',
panels: [createMockPanel('panel-1')],
installDirection: new THREE.Vector3(1, 1, 0),
}
const instance = manager.createSubassembly(config)
expect(instance.config.type).toBe('custom')
})
})
describe('清理和销毁', () => {
it('clear 应该清空所有子组件', () => {
manager.createSubassembly(createDrawerConfig('drawer-1'))
manager.createSubassembly(createDrawerConfig('drawer-2'))
expect(manager.getCount()).toBe(2)
manager.clear()
expect(manager.getCount()).toBe(0)
})
it('dispose 应该清理所有资源', () => {
manager.createSubassembly(createDrawerConfig('drawer-1'))
manager.createSubassembly(createDrawerConfig('drawer-2'))
manager.dispose()
expect(manager.getCount()).toBe(0)
})
})
})