From 608f5030b221a539d40dfe9bc6f8b99a70c451e4 Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Mon, 28 Jul 2025 17:38:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9ecs=E7=9B=AE=E5=BD=95=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E6=9B=B4=E5=A4=9A=E7=9A=84ci=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/ECS/Component.test.ts | 277 +++++++++++ tests/ECS/Core/FluentAPI.test.ts | 675 ++++++++++++++++++++++++++ tests/ECS/Systems/SystemTypes.test.ts | 323 ++++++++++++ 3 files changed, 1275 insertions(+) create mode 100644 tests/ECS/Component.test.ts create mode 100644 tests/ECS/Core/FluentAPI.test.ts create mode 100644 tests/ECS/Systems/SystemTypes.test.ts diff --git a/tests/ECS/Component.test.ts b/tests/ECS/Component.test.ts new file mode 100644 index 00000000..f55a0d35 --- /dev/null +++ b/tests/ECS/Component.test.ts @@ -0,0 +1,277 @@ +import { Component } from '../../src/ECS/Component'; +import { Entity } from '../../src/ECS/Entity'; + +// 测试组件 +class TestComponent extends Component { + public value: number = 100; + public onAddedCalled = false; + public onRemovedCalled = false; + public onEnabledCalled = false; + public onDisabledCalled = false; + public updateCalled = false; + + public override onAddedToEntity(): void { + this.onAddedCalled = true; + } + + public override onRemovedFromEntity(): void { + this.onRemovedCalled = true; + } + + public override onEnabled(): void { + this.onEnabledCalled = true; + } + + public override onDisabled(): void { + this.onDisabledCalled = true; + } + + public override update(): void { + this.updateCalled = true; + } +} + +class AnotherTestComponent extends Component { + public name: string = 'test'; +} + +describe('Component - 组件基类测试', () => { + let component: TestComponent; + let entity: Entity; + + beforeEach(() => { + // Reset component ID generator to avoid BigInt issues + Component._idGenerator = 0; + component = new TestComponent(); + entity = new Entity('TestEntity', 1); + }); + + describe('基本功能', () => { + test('应该能够创建组件实例', () => { + expect(component).toBeInstanceOf(Component); + expect(component).toBeInstanceOf(TestComponent); + expect(component.id).toBeGreaterThanOrEqual(0); + }); + + test('每个组件应该有唯一的ID', () => { + const component1 = new TestComponent(); + const component2 = new TestComponent(); + const component3 = new AnotherTestComponent(); + + expect(component1.id).not.toBe(component2.id); + expect(component2.id).not.toBe(component3.id); + expect(component1.id).not.toBe(component3.id); + }); + + test('组件ID应该递增分配', () => { + const startId = Component._idGenerator; + const component1 = new TestComponent(); + const component2 = new TestComponent(); + + expect(component2.id).toBe(component1.id + 1); + expect(component1.id).toBeGreaterThanOrEqual(startId); + }); + }); + + describe('启用状态管理', () => { + test('组件默认应该是启用的', () => { + expect(component.enabled).toBe(true); + }); + + test('设置组件禁用状态应该工作', () => { + component.enabled = false; + expect(component.enabled).toBe(false); + expect(component.onDisabledCalled).toBe(true); + }); + + test('重新启用组件应该工作', () => { + component.enabled = false; + component.onDisabledCalled = false; + component.onEnabledCalled = false; + + component.enabled = true; + expect(component.enabled).toBe(true); + expect(component.onEnabledCalled).toBe(true); + }); + + test('设置相同的状态不应该触发回调', () => { + component.enabled = true; // 已经是true + expect(component.onEnabledCalled).toBe(false); + + component.enabled = false; + component.onDisabledCalled = false; + + component.enabled = false; // 已经是false + expect(component.onDisabledCalled).toBe(false); + }); + + test('组件启用状态应该受实体状态影响', () => { + entity.addComponent(component); + expect(component.enabled).toBe(true); + + // 禁用实体应该让组件表现为禁用 + entity.enabled = false; + expect(component.enabled).toBe(false); + + // 重新启用实体 + entity.enabled = true; + expect(component.enabled).toBe(true); + }); + + test('组件自身禁用时即使实体启用也应该是禁用的', () => { + entity.addComponent(component); + + component.enabled = false; + entity.enabled = true; + + expect(component.enabled).toBe(false); + }); + + test('没有实体时组件状态应该只取决于自身', () => { + // 组件还没有添加到实体 + expect(component.enabled).toBe(true); + + component.enabled = false; + expect(component.enabled).toBe(false); + }); + }); + + describe('更新顺序', () => { + test('组件默认更新顺序应该是0', () => { + expect(component.updateOrder).toBe(0); + }); + + test('应该能够设置更新顺序', () => { + component.updateOrder = 10; + expect(component.updateOrder).toBe(10); + + component.updateOrder = -5; + expect(component.updateOrder).toBe(-5); + }); + }); + + describe('生命周期回调', () => { + test('添加到实体时应该调用onAddedToEntity', () => { + expect(component.onAddedCalled).toBe(false); + + entity.addComponent(component); + expect(component.onAddedCalled).toBe(true); + }); + + test('从实体移除时应该调用onRemovedFromEntity', () => { + entity.addComponent(component); + expect(component.onRemovedCalled).toBe(false); + + entity.removeComponent(component); + expect(component.onRemovedCalled).toBe(true); + }); + + test('启用时应该调用onEnabled', () => { + component.enabled = false; + component.onEnabledCalled = false; + + component.enabled = true; + expect(component.onEnabledCalled).toBe(true); + }); + + test('禁用时应该调用onDisabled', () => { + expect(component.onDisabledCalled).toBe(false); + + component.enabled = false; + expect(component.onDisabledCalled).toBe(true); + }); + }); + + describe('更新方法', () => { + test('update方法应该可以被调用', () => { + expect(component.updateCalled).toBe(false); + + component.update(); + expect(component.updateCalled).toBe(true); + }); + + test('基类的默认生命周期方法应该安全调用', () => { + const baseComponent = new (class extends Component {})(); + + // 这些方法不应该抛出异常 + expect(() => { + baseComponent.onAddedToEntity(); + baseComponent.onRemovedFromEntity(); + baseComponent.onEnabled(); + baseComponent.onDisabled(); + baseComponent.update(); + }).not.toThrow(); + }); + }); + + describe('实体关联', () => { + test('组件应该能够访问其所属的实体', () => { + entity.addComponent(component); + expect(component.entity).toBe(entity); + }); + + test('组件移除后entity引用行为', () => { + entity.addComponent(component); + expect(component.entity).toBe(entity); + + entity.removeComponent(component); + // 移除后entity引用可能被清空,这是正常行为 + // 具体行为取决于实现,这里只测试不会抛出异常 + expect(() => { + const _ = component.entity; + }).not.toThrow(); + }); + }); + + describe('边界情况', () => { + test('多次启用禁用应该工作正常', () => { + for (let i = 0; i < 10; i++) { + component.enabled = false; + expect(component.enabled).toBe(false); + + component.enabled = true; + expect(component.enabled).toBe(true); + } + }); + + test('极端更新顺序值应该被接受', () => { + component.updateOrder = 999999; + expect(component.updateOrder).toBe(999999); + + component.updateOrder = -999999; + expect(component.updateOrder).toBe(-999999); + }); + + test('大量组件创建应该有不同的ID', () => { + const components: Component[] = []; + const count = 1000; + + for (let i = 0; i < count; i++) { + components.push(new TestComponent()); + } + + // 检查所有ID都不同 + const ids = new Set(components.map(c => c.id)); + expect(ids.size).toBe(count); + }); + }); + + describe('继承和多态', () => { + test('不同类型的组件应该都继承自Component', () => { + const test1 = new TestComponent(); + const test2 = new AnotherTestComponent(); + + expect(test1).toBeInstanceOf(Component); + expect(test2).toBeInstanceOf(Component); + expect(test1).toBeInstanceOf(TestComponent); + expect(test2).toBeInstanceOf(AnotherTestComponent); + }); + + test('组件应该能够重写基类方法', () => { + const test = new TestComponent(); + test.update(); + + expect(test.updateCalled).toBe(true); + }); + }); +}); \ No newline at end of file diff --git a/tests/ECS/Core/FluentAPI.test.ts b/tests/ECS/Core/FluentAPI.test.ts new file mode 100644 index 00000000..73e2d975 --- /dev/null +++ b/tests/ECS/Core/FluentAPI.test.ts @@ -0,0 +1,675 @@ +import { + EntityBuilder, + SceneBuilder, + ComponentBuilder, + ECSFluentAPI, + EntityBatchOperator, + createECSAPI, + initializeECS, + ECS +} from '../../../src/ECS/Core/FluentAPI'; +import { Scene } from '../../../src/ECS/Scene'; +import { Entity } from '../../../src/ECS/Entity'; +import { Component } from '../../../src/ECS/Component'; +import { QuerySystem } from '../../../src/ECS/Core/QuerySystem'; +import { TypeSafeEventSystem } from '../../../src/ECS/Core/EventSystem'; +import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem'; +import { Matcher } from '../../../src/ECS/Utils/Matcher'; + +// 测试组件 +class TestComponent extends Component { + constructor(public value: number = 0) { + super(); + } +} + +class PositionComponent extends Component { + constructor(public x: number = 0, public y: number = 0) { + super(); + } +} + +class VelocityComponent extends Component { + constructor(public vx: number = 0, public vy: number = 0) { + super(); + } +} + +// 测试系统 +class TestSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(TestComponent)); + } + + protected override process(entities: Entity[]): void { + // 测试系统 + } +} + +describe('FluentAPI - 流式API测试', () => { + let scene: Scene; + let querySystem: QuerySystem; + let eventSystem: TypeSafeEventSystem; + + beforeEach(() => { + scene = new Scene(); + querySystem = new QuerySystem(); + eventSystem = new TypeSafeEventSystem(); + }); + + describe('EntityBuilder - 实体构建器', () => { + let builder: EntityBuilder; + + beforeEach(() => { + builder = new EntityBuilder(scene, scene.componentStorageManager); + }); + + test('应该能够创建实体构建器', () => { + expect(builder).toBeInstanceOf(EntityBuilder); + }); + + test('应该能够设置实体名称', () => { + const entity = builder.named('TestEntity').build(); + expect(entity.name).toBe('TestEntity'); + }); + + test('应该能够设置实体标签', () => { + const entity = builder.tagged(42).build(); + expect(entity.tag).toBe(42); + }); + + test('应该能够添加组件', () => { + const component = new TestComponent(100); + const entity = builder.with(component).build(); + + expect(entity.hasComponent(TestComponent)).toBe(true); + expect(entity.getComponent(TestComponent)).toBe(component); + }); + + test('应该能够添加多个组件', () => { + const comp1 = new TestComponent(100); + const comp2 = new PositionComponent(10, 20); + const comp3 = new VelocityComponent(1, 2); + + const entity = builder.withComponents(comp1, comp2, comp3).build(); + + expect(entity.hasComponent(TestComponent)).toBe(true); + expect(entity.hasComponent(PositionComponent)).toBe(true); + expect(entity.hasComponent(VelocityComponent)).toBe(true); + }); + + test('应该能够条件性添加组件', () => { + const comp1 = new TestComponent(100); + const comp2 = new PositionComponent(10, 20); + + const entity = builder + .withIf(true, comp1) + .withIf(false, comp2) + .build(); + + expect(entity.hasComponent(TestComponent)).toBe(true); + expect(entity.hasComponent(PositionComponent)).toBe(false); + }); + + test('应该能够使用工厂函数创建组件', () => { + const entity = builder + .withFactory(() => new TestComponent(200)) + .build(); + + expect(entity.hasComponent(TestComponent)).toBe(true); + expect(entity.getComponent(TestComponent)!.value).toBe(200); + }); + + test('应该能够配置组件属性', () => { + const entity = builder + .with(new TestComponent(100)) + .configure(TestComponent, (comp) => { + comp.value = 300; + }) + .build(); + + expect(entity.getComponent(TestComponent)!.value).toBe(300); + }); + + test('配置不存在的组件应该安全处理', () => { + expect(() => { + builder.configure(TestComponent, (comp) => { + comp.value = 300; + }).build(); + }).not.toThrow(); + }); + + test('应该能够设置实体启用状态', () => { + const entity1 = builder.enabled(true).build(); + const entity2 = new EntityBuilder(scene, scene.componentStorageManager).enabled(false).build(); + + expect(entity1.enabled).toBe(true); + expect(entity2.enabled).toBe(false); + }); + + test('应该能够设置实体活跃状态', () => { + const entity1 = builder.active(true).build(); + const entity2 = new EntityBuilder(scene, scene.componentStorageManager).active(false).build(); + + expect(entity1.active).toBe(true); + expect(entity2.active).toBe(false); + }); + + test('应该能够添加子实体', () => { + const childBuilder = new EntityBuilder(scene, scene.componentStorageManager) + .named('Child') + .with(new TestComponent(50)); + + const parent = builder + .named('Parent') + .withChild(childBuilder) + .build(); + + expect(parent.children.length).toBe(1); + expect(parent.children[0].name).toBe('Child'); + }); + + test('应该能够添加多个子实体', () => { + const child1 = new EntityBuilder(scene, scene.componentStorageManager).named('Child1'); + const child2 = new EntityBuilder(scene, scene.componentStorageManager).named('Child2'); + const child3 = new EntityBuilder(scene, scene.componentStorageManager).named('Child3'); + + const parent = builder + .named('Parent') + .withChildren(child1, child2, child3) + .build(); + + expect(parent.children.length).toBe(3); + expect(parent.children[0].name).toBe('Child1'); + expect(parent.children[1].name).toBe('Child2'); + expect(parent.children[2].name).toBe('Child3'); + }); + + test('应该能够使用工厂函数创建子实体', () => { + const parent = builder + .named('Parent') + .withChildFactory((parentEntity) => { + return new EntityBuilder(scene, scene.componentStorageManager) + .named(`Child_of_${parentEntity.name}`) + .with(new TestComponent(100)); + }) + .build(); + + expect(parent.children.length).toBe(1); + expect(parent.children[0].name).toBe('Child_of_Parent'); + }); + + test('应该能够条件性添加子实体', () => { + const child1 = new EntityBuilder(scene, scene.componentStorageManager).named('Child1'); + const child2 = new EntityBuilder(scene, scene.componentStorageManager).named('Child2'); + + const parent = builder + .named('Parent') + .withChildIf(true, child1) + .withChildIf(false, child2) + .build(); + + expect(parent.children.length).toBe(1); + expect(parent.children[0].name).toBe('Child1'); + }); + + test('应该能够构建实体并添加到场景', () => { + const entity = builder + .named('SpawnedEntity') + .with(new TestComponent(100)) + .spawn(); + + expect(entity.name).toBe('SpawnedEntity'); + expect(entity.scene).toBe(scene); + }); + + test('应该能够克隆构建器', () => { + const originalBuilder = builder.named('Original').with(new TestComponent(100)); + const clonedBuilder = originalBuilder.clone(); + + expect(clonedBuilder).toBeInstanceOf(EntityBuilder); + expect(clonedBuilder).not.toBe(originalBuilder); + }); + + test('流式调用应该工作正常', () => { + const entity = builder + .named('ComplexEntity') + .tagged(42) + .with(new TestComponent(100)) + .with(new PositionComponent(10, 20)) + .enabled(true) + .active(true) + .configure(TestComponent, (comp) => { + comp.value = 200; + }) + .build(); + + expect(entity.name).toBe('ComplexEntity'); + expect(entity.tag).toBe(42); + expect(entity.enabled).toBe(true); + expect(entity.active).toBe(true); + expect(entity.hasComponent(TestComponent)).toBe(true); + expect(entity.hasComponent(PositionComponent)).toBe(true); + expect(entity.getComponent(TestComponent)!.value).toBe(200); + }); + }); + + describe('SceneBuilder - 场景构建器', () => { + let builder: SceneBuilder; + + beforeEach(() => { + builder = new SceneBuilder(); + }); + + test('应该能够创建场景构建器', () => { + expect(builder).toBeInstanceOf(SceneBuilder); + }); + + test('应该能够设置场景名称', () => { + const scene = builder.named('TestScene').build(); + expect(scene.name).toBe('TestScene'); + }); + + test('应该能够添加实体', () => { + const entity = new Entity('TestEntity', 1); + const scene = builder.withEntity(entity).build(); + + expect(scene.entities.count).toBe(1); + expect(scene.findEntity('TestEntity')).toBe(entity); + }); + + test('应该能够使用实体构建器添加实体', () => { + const scene = builder + .withEntityBuilder((builder) => { + return builder + .named('BuilderEntity') + .with(new TestComponent(100)); + }) + .build(); + + expect(scene.entities.count).toBe(1); + expect(scene.findEntity('BuilderEntity')).not.toBeNull(); + }); + + test('应该能够批量添加实体', () => { + const entity1 = new Entity('Entity1', 1); + const entity2 = new Entity('Entity2', 2); + const entity3 = new Entity('Entity3', 3); + + const scene = builder + .withEntities(entity1, entity2, entity3) + .build(); + + expect(scene.entities.count).toBe(3); + }); + + test('应该能够添加系统', () => { + const system = new TestSystem(); + const scene = builder.withSystem(system).build(); + + expect(scene.systems.length).toBe(1); + expect(scene.systems[0]).toBe(system); + }); + + test('应该能够批量添加系统', () => { + const system1 = new TestSystem(); + const system2 = new TestSystem(); + + const scene = builder + .withSystems(system1, system2) + .build(); + + expect(scene.systems.length).toBe(2); + }); + + test('流式调用应该工作正常', () => { + const entity = new Entity('TestEntity', 1); + const system = new TestSystem(); + + const scene = builder + .named('ComplexScene') + .withEntity(entity) + .withSystem(system) + .withEntityBuilder((builder) => { + return builder.named('BuilderEntity'); + }) + .build(); + + expect(scene.name).toBe('ComplexScene'); + expect(scene.entities.count).toBe(2); + expect(scene.systems.length).toBe(1); + }); + }); + + describe('ComponentBuilder - 组件构建器', () => { + test('应该能够创建组件构建器', () => { + const builder = new ComponentBuilder(TestComponent, 100); + expect(builder).toBeInstanceOf(ComponentBuilder); + }); + + test('应该能够设置组件属性', () => { + const component = new ComponentBuilder(TestComponent, 100) + .set('value', 200) + .build(); + + expect(component.value).toBe(200); + }); + + test('应该能够使用配置函数', () => { + const component = new ComponentBuilder(PositionComponent, 10, 20) + .configure((comp) => { + comp.x = 30; + comp.y = 40; + }) + .build(); + + expect(component.x).toBe(30); + expect(component.y).toBe(40); + }); + + test('应该能够条件性设置属性', () => { + const component = new ComponentBuilder(TestComponent, 100) + .setIf(true, 'value', 200) + .setIf(false, 'value', 300) + .build(); + + expect(component.value).toBe(200); + }); + + test('流式调用应该工作正常', () => { + const component = new ComponentBuilder(PositionComponent, 0, 0) + .set('x', 10) + .set('y', 20) + .setIf(true, 'x', 30) + .configure((comp) => { + comp.y = 40; + }) + .build(); + + expect(component.x).toBe(30); + expect(component.y).toBe(40); + }); + }); + + describe('ECSFluentAPI - 主API', () => { + let api: ECSFluentAPI; + + beforeEach(() => { + api = new ECSFluentAPI(scene, querySystem, eventSystem); + }); + + test('应该能够创建ECS API', () => { + expect(api).toBeInstanceOf(ECSFluentAPI); + }); + + test('应该能够创建实体构建器', () => { + const builder = api.createEntity(); + expect(builder).toBeInstanceOf(EntityBuilder); + }); + + test('应该能够创建场景构建器', () => { + const builder = api.createScene(); + expect(builder).toBeInstanceOf(SceneBuilder); + }); + + test('应该能够创建组件构建器', () => { + const builder = api.createComponent(TestComponent, 100); + expect(builder).toBeInstanceOf(ComponentBuilder); + }); + + test('应该能够创建查询构建器', () => { + const builder = api.query(); + expect(builder).toBeDefined(); + }); + + test('应该能够查找实体', () => { + const entity = scene.createEntity('TestEntity'); + entity.addComponent(new TestComponent(100)); + querySystem.setEntities([entity]); + + const results = api.find(TestComponent); + expect(results.length).toBe(1); + expect(results[0]).toBe(entity); + }); + + test('应该能够查找第一个匹配的实体', () => { + const entity1 = scene.createEntity('Entity1'); + const entity2 = scene.createEntity('Entity2'); + entity1.addComponent(new TestComponent(100)); + entity2.addComponent(new TestComponent(200)); + querySystem.setEntities([entity1, entity2]); + + const result = api.findFirst(TestComponent); + expect(result).not.toBeNull(); + expect([entity1, entity2]).toContain(result); + }); + + test('查找不存在的实体应该返回null', () => { + const result = api.findFirst(TestComponent); + expect(result).toBeNull(); + }); + + test('应该能够按名称查找实体', () => { + const entity = scene.createEntity('TestEntity'); + + const result = api.findByName('TestEntity'); + expect(result).toBe(entity); + }); + + test('应该能够按标签查找实体', () => { + const entity1 = scene.createEntity('Entity1'); + const entity2 = scene.createEntity('Entity2'); + entity1.tag = 42; + entity2.tag = 42; + + const results = api.findByTag(42); + expect(results.length).toBe(2); + expect(results).toContain(entity1); + expect(results).toContain(entity2); + }); + + test('应该能够触发同步事件', () => { + let eventReceived = false; + let eventData: any = null; + + api.on('test:event', (data) => { + eventReceived = true; + eventData = data; + }); + + api.emit('test:event', { message: 'hello' }); + + expect(eventReceived).toBe(true); + expect(eventData.message).toBe('hello'); + }); + + test('应该能够触发异步事件', async () => { + let eventReceived = false; + let eventData: any = null; + + api.on('test:event', (data) => { + eventReceived = true; + eventData = data; + }); + + await api.emitAsync('test:event', { message: 'hello' }); + + expect(eventReceived).toBe(true); + expect(eventData.message).toBe('hello'); + }); + + test('应该能够一次性监听事件', () => { + let callCount = 0; + + api.once('test:event', () => { + callCount++; + }); + + api.emit('test:event', {}); + api.emit('test:event', {}); + + expect(callCount).toBe(1); + }); + + test('应该能够移除事件监听器', () => { + let callCount = 0; + + const listenerId = api.on('test:event', () => { + callCount++; + }); + + api.emit('test:event', {}); + api.off('test:event', listenerId); + api.emit('test:event', {}); + + expect(callCount).toBe(1); + }); + + test('应该能够创建批量操作器', () => { + const entity1 = new Entity('Entity1', 1); + const entity2 = new Entity('Entity2', 2); + + const batch = api.batch([entity1, entity2]); + expect(batch).toBeInstanceOf(EntityBatchOperator); + }); + + test('应该能够获取统计信息', () => { + const stats = api.getStats(); + + expect(stats).toBeDefined(); + expect(stats.entityCount).toBeDefined(); + expect(stats.systemCount).toBeDefined(); + expect(stats.componentStats).toBeDefined(); + expect(stats.queryStats).toBeDefined(); + expect(stats.eventStats).toBeDefined(); + }); + }); + + describe('EntityBatchOperator - 批量操作器', () => { + let entity1: Entity; + let entity2: Entity; + let entity3: Entity; + let batchOp: EntityBatchOperator; + + beforeEach(() => { + entity1 = new Entity('Entity1', 1); + entity2 = new Entity('Entity2', 2); + entity3 = new Entity('Entity3', 3); + batchOp = new EntityBatchOperator([entity1, entity2, entity3]); + }); + + test('应该能够创建批量操作器', () => { + expect(batchOp).toBeInstanceOf(EntityBatchOperator); + }); + + test('应该能够批量添加组件', () => { + const component = new TestComponent(100); + batchOp.addComponent(component); + + expect(entity1.hasComponent(TestComponent)).toBe(true); + expect(entity2.hasComponent(TestComponent)).toBe(true); + expect(entity3.hasComponent(TestComponent)).toBe(true); + }); + + test('应该能够批量移除组件', () => { + entity1.addComponent(new TestComponent(100)); + entity2.addComponent(new TestComponent(200)); + entity3.addComponent(new TestComponent(300)); + + batchOp.removeComponent(TestComponent); + + expect(entity1.hasComponent(TestComponent)).toBe(false); + expect(entity2.hasComponent(TestComponent)).toBe(false); + expect(entity3.hasComponent(TestComponent)).toBe(false); + }); + + test('应该能够批量设置活跃状态', () => { + batchOp.setActive(false); + + expect(entity1.active).toBe(false); + expect(entity2.active).toBe(false); + expect(entity3.active).toBe(false); + }); + + test('应该能够批量设置标签', () => { + batchOp.setTag(42); + + expect(entity1.tag).toBe(42); + expect(entity2.tag).toBe(42); + expect(entity3.tag).toBe(42); + }); + + test('应该能够批量执行操作', () => { + const names: string[] = []; + const indices: number[] = []; + + batchOp.forEach((entity, index) => { + names.push(entity.name); + indices.push(index); + }); + + expect(names).toEqual(['Entity1', 'Entity2', 'Entity3']); + expect(indices).toEqual([0, 1, 2]); + }); + + test('应该能够过滤实体', () => { + entity1.tag = 1; + entity2.tag = 2; + entity3.tag = 1; + + const filtered = batchOp.filter(entity => entity.tag === 1); + + expect(filtered.count()).toBe(2); + expect(filtered.toArray()).toContain(entity1); + expect(filtered.toArray()).toContain(entity3); + }); + + test('应该能够获取实体数组', () => { + const entities = batchOp.toArray(); + + expect(entities.length).toBe(3); + expect(entities).toContain(entity1); + expect(entities).toContain(entity2); + expect(entities).toContain(entity3); + }); + + test('应该能够获取实体数量', () => { + expect(batchOp.count()).toBe(3); + }); + + test('流式调用应该工作正常', () => { + const result = batchOp + .addComponent(new TestComponent(100)) + .setActive(false) + .setTag(42) + .forEach((entity) => { + entity.name = entity.name + '_Modified'; + }); + + expect(result).toBe(batchOp); + expect(entity1.hasComponent(TestComponent)).toBe(true); + expect(entity1.active).toBe(false); + expect(entity1.tag).toBe(42); + expect(entity1.name).toBe('Entity1_Modified'); + }); + }); + + describe('工厂函数和全局API', () => { + test('createECSAPI应该创建API实例', () => { + const api = createECSAPI(scene, querySystem, eventSystem); + expect(api).toBeInstanceOf(ECSFluentAPI); + }); + + test('initializeECS应该初始化全局ECS', () => { + initializeECS(scene, querySystem, eventSystem); + expect(ECS).toBeInstanceOf(ECSFluentAPI); + }); + + test('全局ECS应该可用', () => { + initializeECS(scene, querySystem, eventSystem); + + const builder = ECS.createEntity(); + expect(builder).toBeInstanceOf(EntityBuilder); + }); + }); +}); \ No newline at end of file diff --git a/tests/ECS/Systems/SystemTypes.test.ts b/tests/ECS/Systems/SystemTypes.test.ts new file mode 100644 index 00000000..bf10ffbd --- /dev/null +++ b/tests/ECS/Systems/SystemTypes.test.ts @@ -0,0 +1,323 @@ +import { PassiveSystem } from '../../../src/ECS/Systems/PassiveSystem'; +import { IntervalSystem } from '../../../src/ECS/Systems/IntervalSystem'; +import { ProcessingSystem } from '../../../src/ECS/Systems/ProcessingSystem'; +import { Entity } from '../../../src/ECS/Entity'; +import { Component } from '../../../src/ECS/Component'; +import { Matcher } from '../../../src/ECS/Utils/Matcher'; +import { Time } from '../../../src/Utils/Time'; + +// 测试组件 +class TestComponent extends Component { + constructor(public value: number = 0) { + super(); + } +} + +class AnotherComponent extends Component { + constructor(public name: string = 'test') { + super(); + } +} + +// 具体的被动系统实现 +class ConcretePassiveSystem extends PassiveSystem { + public processCallCount = 0; + public changeCallCount = 0; + + constructor() { + super(Matcher.empty().all(TestComponent)); + } + + protected override process(entities: Entity[]): void { + this.processCallCount++; + // 被动系统的process方法会被调用,但不做任何处理 + super.process(entities); + } + + public override onChanged(entity: Entity): void { + this.changeCallCount++; + super.onChanged(entity); + } +} + +// 具体的间隔系统实现 +class ConcreteIntervalSystem extends IntervalSystem { + public processCallCount = 0; + public lastDelta = 0; + + constructor(interval: number) { + super(Matcher.empty().all(TestComponent), interval); + } + + protected override process(entities: Entity[]): void { + this.processCallCount++; + this.lastDelta = this.getIntervalDelta(); + } +} + +// 具体的处理系统实现 +class ConcreteProcessingSystem extends ProcessingSystem { + public processSystemCallCount = 0; + public processCallCount = 0; + public changeCallCount = 0; + + constructor() { + super(Matcher.empty().all(TestComponent)); + } + + public processSystem(): void { + this.processSystemCallCount++; + } + + protected override process(entities: Entity[]): void { + this.processCallCount++; + super.process(entities); + } + + public override onChanged(entity: Entity): void { + this.changeCallCount++; + super.onChanged(entity); + } +} + +describe('System Types - 系统类型测试', () => { + let entity: Entity; + + beforeEach(() => { + entity = new Entity('TestEntity', 1); + // 重置时间系统 + Time.update(0.016); + }); + + describe('PassiveSystem - 被动系统', () => { + let passiveSystem: ConcretePassiveSystem; + + beforeEach(() => { + passiveSystem = new ConcretePassiveSystem(); + }); + + test('应该能够创建被动系统', () => { + expect(passiveSystem).toBeInstanceOf(PassiveSystem); + expect(passiveSystem).toBeInstanceOf(ConcretePassiveSystem); + }); + + test('onChanged方法不应该做任何操作', () => { + const initialChangeCount = passiveSystem.changeCallCount; + + passiveSystem.onChanged(entity); + + // 计数会增加,但实际上基类的onChanged不做任何操作 + expect(passiveSystem.changeCallCount).toBe(initialChangeCount + 1); + }); + + test('process方法不应该做任何处理', () => { + const entities = [entity]; + const initialProcessCount = passiveSystem.processCallCount; + + passiveSystem.update(); + + // 虽然process被调用了,但被动系统不做任何实际处理 + expect(passiveSystem.processCallCount).toBe(initialProcessCount + 1); + }); + + test('应该能够正常添加和移除实体', () => { + entity.addComponent(new TestComponent(100)); + + passiveSystem.add(entity); + expect(passiveSystem.entities.length).toBe(1); + + passiveSystem.remove(entity); + expect(passiveSystem.entities.length).toBe(0); + }); + + test('实体变化时应该调用onChanged', () => { + entity.addComponent(new TestComponent(100)); + passiveSystem.add(entity); + + const initialCount = passiveSystem.changeCallCount; + passiveSystem.onChanged(entity); + + expect(passiveSystem.changeCallCount).toBe(initialCount + 1); + }); + }); + + describe('IntervalSystem - 间隔系统', () => { + let intervalSystem: ConcreteIntervalSystem; + const testInterval = 0.1; // 100ms + + beforeEach(() => { + intervalSystem = new ConcreteIntervalSystem(testInterval); + }); + + test('应该能够创建间隔系统', () => { + expect(intervalSystem).toBeInstanceOf(IntervalSystem); + expect(intervalSystem).toBeInstanceOf(ConcreteIntervalSystem); + }); + + test('在间隔时间内不应该处理', () => { + const initialProcessCount = intervalSystem.processCallCount; + + // 模拟时间更新,但不足以触发间隔 + Time.update(testInterval / 2); + intervalSystem.update(); + + expect(intervalSystem.processCallCount).toBe(initialProcessCount); + }); + + test('达到间隔时间时应该处理', () => { + const initialProcessCount = intervalSystem.processCallCount; + + // 模拟时间更新,刚好达到间隔 + Time.update(testInterval); + intervalSystem.update(); + + expect(intervalSystem.processCallCount).toBe(initialProcessCount + 1); + }); + + test('超过间隔时间应该处理并记录余数', () => { + const initialProcessCount = intervalSystem.processCallCount; + const overTime = testInterval + 0.02; // 超过20ms + + Time.update(overTime); + intervalSystem.update(); + + expect(intervalSystem.processCallCount).toBe(initialProcessCount + 1); + expect(intervalSystem.lastDelta).toBe(testInterval + 0.02); + }); + + test('多次累积应该正确触发', () => { + let processCount = intervalSystem.processCallCount; + + // 多次小的时间增量 + for (let i = 0; i < 10; i++) { + Time.update(testInterval / 5); + intervalSystem.update(); + } + + // 10 * (interval/5) = 2 * interval,应该触发2次 + expect(intervalSystem.processCallCount).toBeGreaterThanOrEqual(processCount + 1); + }); + + test('getIntervalDelta应该返回正确的增量', () => { + const overTime = testInterval + 0.03; + + Time.update(overTime); + intervalSystem.update(); + + expect(intervalSystem.lastDelta).toBe(testInterval + 0.03); + }); + + test('重置后应该重新开始计时', () => { + // 第一次触发 + Time.update(testInterval); + intervalSystem.update(); + expect(intervalSystem.processCallCount).toBe(1); + + // 再次触发需要等待完整间隔 + Time.update(testInterval / 2); + intervalSystem.update(); + expect(intervalSystem.processCallCount).toBe(1); + + Time.update(testInterval / 2); + intervalSystem.update(); + expect(intervalSystem.processCallCount).toBe(2); + }); + }); + + describe('ProcessingSystem - 处理系统', () => { + let processingSystem: ConcreteProcessingSystem; + + beforeEach(() => { + processingSystem = new ConcreteProcessingSystem(); + }); + + test('应该能够创建处理系统', () => { + expect(processingSystem).toBeInstanceOf(ProcessingSystem); + expect(processingSystem).toBeInstanceOf(ConcreteProcessingSystem); + }); + + test('process方法应该调用processSystem', () => { + const initialProcessSystemCount = processingSystem.processSystemCallCount; + const initialProcessCount = processingSystem.processCallCount; + + processingSystem.update(); + + expect(processingSystem.processCallCount).toBe(initialProcessCount + 1); + expect(processingSystem.processSystemCallCount).toBe(initialProcessSystemCount + 1); + }); + + test('onChanged方法不应该做任何操作', () => { + const initialChangeCount = processingSystem.changeCallCount; + + processingSystem.onChanged(entity); + + expect(processingSystem.changeCallCount).toBe(initialChangeCount + 1); + }); + + test('每次更新都应该调用processSystem', () => { + const initialCount = processingSystem.processSystemCallCount; + + processingSystem.update(); + processingSystem.update(); + processingSystem.update(); + + expect(processingSystem.processSystemCallCount).toBe(initialCount + 3); + }); + + test('应该能够处理多个实体', () => { + const entity1 = new Entity('Entity1', 1); + const entity2 = new Entity('Entity2', 2); + + entity1.addComponent(new TestComponent(100)); + entity2.addComponent(new TestComponent(200)); + + processingSystem.add(entity1); + processingSystem.add(entity2); + + expect(processingSystem.entities.length).toBe(2); + + const initialCount = processingSystem.processSystemCallCount; + processingSystem.update(); + + // processSystem应该被调用,不管有多少实体 + expect(processingSystem.processSystemCallCount).toBe(initialCount + 1); + }); + }); + + describe('系统集成测试', () => { + test('不同类型的系统应该都继承自EntitySystem', () => { + const passive = new ConcretePassiveSystem(); + const interval = new ConcreteIntervalSystem(0.1); + const processing = new ConcreteProcessingSystem(); + + expect(passive.matcher).toBeDefined(); + expect(interval.matcher).toBeDefined(); + expect(processing.matcher).toBeDefined(); + + expect(passive.entities).toBeDefined(); + expect(interval.entities).toBeDefined(); + expect(processing.entities).toBeDefined(); + }); + + test('系统应该能够正确匹配实体', () => { + const passive = new ConcretePassiveSystem(); + const interval = new ConcreteIntervalSystem(0.1); + const processing = new ConcreteProcessingSystem(); + + const matchingEntity = new Entity('Matching', 1); + matchingEntity.addComponent(new TestComponent(100)); + + const nonMatchingEntity = new Entity('NonMatching', 2); + nonMatchingEntity.addComponent(new AnotherComponent('test')); + + // 所有系统都应该匹配TestComponent + expect(passive.matcher.isInterestedEntity(matchingEntity)).toBe(true); + expect(interval.matcher.isInterestedEntity(matchingEntity)).toBe(true); + expect(processing.matcher.isInterestedEntity(matchingEntity)).toBe(true); + + expect(passive.matcher.isInterestedEntity(nonMatchingEntity)).toBe(false); + expect(interval.matcher.isInterestedEntity(nonMatchingEntity)).toBe(false); + expect(processing.matcher.isInterestedEntity(nonMatchingEntity)).toBe(false); + }); + }); +}); \ No newline at end of file