339 lines
10 KiB
TypeScript
339 lines
10 KiB
TypeScript
/**
|
||
* 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)
|
||
})
|
||
})
|
||
})
|