Feature/editor optimization (#251)

* refactor: 编辑器/运行时架构拆分与构建系统升级

* feat(core): 层级系统重构与UI变换矩阵修复

* refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题

* fix(physics): 修复跨包组件类引用问题

* feat: 统一运行时架构与浏览器运行支持

* feat(asset): 实现浏览器运行时资产加载系统

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误

* test: 补齐核心模块测试用例,修复CI构建配置

* fix: 修复测试用例中的类型错误和断言问题

* fix: 修复 turbo build:npm 任务的依赖顺序问题

* fix: 修复 CI 构建错误并优化构建性能
This commit is contained in:
YHH
2025-12-01 22:28:51 +08:00
committed by GitHub
parent 189714c727
commit b42a7b4e43
468 changed files with 18301 additions and 9075 deletions

View File

@@ -1,691 +0,0 @@
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 {
public value: number;
constructor(...args: unknown[]) {
super();
const [value = 0] = args as [number?];
this.value = value;
}
}
class PositionComponent extends Component {
public x: number;
public y: number;
constructor(...args: unknown[]) {
super();
const [x = 0, y = 0] = args as [number?, number?];
this.x = x;
this.y = y;
}
}
class VelocityComponent extends Component {
public vx: number;
public vy: number;
constructor(...args: unknown[]) {
super();
const [vx = 0, vy = 0] = args as [number?, number?];
this.vx = vx;
this.vy = vy;
}
}
// 测试系统
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(1);
});
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 = scene.createEntity('Entity1');
entity2 = scene.createEntity('Entity2');
entity3 = scene.createEntity('Entity3');
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);
});
});
});

View File

@@ -0,0 +1,321 @@
import { EntityBuilder } from '../../../../src/ECS/Core/FluentAPI/EntityBuilder';
import { Scene } from '../../../../src/ECS/Scene';
import { Component } from '../../../../src/ECS/Component';
import { HierarchySystem } from '../../../../src/ECS/Systems/HierarchySystem';
import { ECSComponent } from '../../../../src/ECS/Decorators';
@ECSComponent('BuilderTestPosition')
class PositionComponent extends Component {
public x: number = 0;
public y: number = 0;
constructor(x: number = 0, y: number = 0) {
super();
this.x = x;
this.y = y;
}
}
@ECSComponent('BuilderTestVelocity')
class VelocityComponent extends Component {
public vx: number = 0;
public vy: number = 0;
}
@ECSComponent('BuilderTestHealth')
class HealthComponent extends Component {
public current: number = 100;
public max: number = 100;
}
// Helper function to create EntityBuilder
function createBuilder(scene: Scene): EntityBuilder {
return new EntityBuilder(scene, scene.componentStorageManager);
}
describe('EntityBuilder', () => {
let scene: Scene;
let hierarchySystem: HierarchySystem;
beforeEach(() => {
scene = new Scene({ name: 'BuilderTestScene' });
hierarchySystem = new HierarchySystem();
scene.addSystem(hierarchySystem);
});
afterEach(() => {
scene.end();
});
describe('basic building', () => {
test('should create entity with name', () => {
const builder = createBuilder(scene);
const entity = builder.named('TestEntity').build();
expect(entity.name).toBe('TestEntity');
});
test('should create entity with tag', () => {
const builder = createBuilder(scene);
const entity = builder.tagged(0x100).build();
expect(entity.tag).toBe(0x100);
});
test('should support chaining name and tag', () => {
const entity = createBuilder(scene)
.named('ChainedEntity')
.tagged(0x200)
.build();
expect(entity.name).toBe('ChainedEntity');
expect(entity.tag).toBe(0x200);
});
});
describe('component management', () => {
test('should add single component with .with()', () => {
const entity = createBuilder(scene)
.with(new PositionComponent(10, 20))
.build();
const pos = entity.getComponent(PositionComponent);
expect(pos).not.toBeNull();
expect(pos!.x).toBe(10);
expect(pos!.y).toBe(20);
});
test('should add multiple components with .withComponents()', () => {
const entity = createBuilder(scene)
.withComponents(
new PositionComponent(5, 10),
new VelocityComponent(),
new HealthComponent()
)
.build();
expect(entity.hasComponent(PositionComponent)).toBe(true);
expect(entity.hasComponent(VelocityComponent)).toBe(true);
expect(entity.hasComponent(HealthComponent)).toBe(true);
});
test('should conditionally add component with .withIf()', () => {
const shouldAdd = true;
const shouldNotAdd = false;
const entity = createBuilder(scene)
.withIf(shouldAdd, new PositionComponent())
.withIf(shouldNotAdd, new VelocityComponent())
.build();
expect(entity.hasComponent(PositionComponent)).toBe(true);
expect(entity.hasComponent(VelocityComponent)).toBe(false);
});
test('should add component using factory with .withFactory()', () => {
const entity = createBuilder(scene)
.withFactory(() => new PositionComponent(100, 200))
.build();
const pos = entity.getComponent(PositionComponent);
expect(pos).not.toBeNull();
expect(pos!.x).toBe(100);
expect(pos!.y).toBe(200);
});
test('should configure existing component with .configure()', () => {
const entity = createBuilder(scene)
.with(new PositionComponent(0, 0))
.configure(PositionComponent, (pos: PositionComponent) => {
pos.x = 999;
pos.y = 888;
})
.build();
const pos = entity.getComponent(PositionComponent);
expect(pos!.x).toBe(999);
expect(pos!.y).toBe(888);
});
test('.configure() should do nothing if component does not exist', () => {
const entity = createBuilder(scene)
.configure(PositionComponent, (pos: PositionComponent) => {
pos.x = 100;
})
.build();
expect(entity.hasComponent(PositionComponent)).toBe(false);
});
});
describe('entity state', () => {
test('should set enabled state', () => {
const disabledEntity = createBuilder(scene)
.enabled(false)
.build();
const enabledEntity = createBuilder(scene)
.enabled(true)
.build();
expect(disabledEntity.enabled).toBe(false);
expect(enabledEntity.enabled).toBe(true);
});
test('should set active state', () => {
const inactiveEntity = createBuilder(scene)
.active(false)
.build();
const activeEntity = createBuilder(scene)
.active(true)
.build();
expect(inactiveEntity.active).toBe(false);
expect(activeEntity.active).toBe(true);
});
});
describe('hierarchy building', () => {
test('should call withChild method', () => {
const childBuilder = createBuilder(scene).named('Child');
const builder = createBuilder(scene)
.named('Parent')
.withChild(childBuilder);
// withChild returns the builder for chaining
expect(builder).toBeInstanceOf(EntityBuilder);
});
test('should call withChildren method', () => {
const child1Builder = createBuilder(scene).named('Child1');
const child2Builder = createBuilder(scene).named('Child2');
const builder = createBuilder(scene)
.named('Parent')
.withChildren(child1Builder, child2Builder);
// withChildren returns the builder for chaining
expect(builder).toBeInstanceOf(EntityBuilder);
});
test('should call withChildFactory method', () => {
const builder = createBuilder(scene)
.named('Parent')
.with(new PositionComponent(100, 100))
.withChildFactory((parentEntity) => {
return createBuilder(scene)
.named('ChildFromFactory')
.with(new PositionComponent(10, 20));
});
// withChildFactory returns the builder for chaining
expect(builder).toBeInstanceOf(EntityBuilder);
});
test('should call withChildIf method', () => {
const shouldAdd = true;
const shouldNotAdd = false;
const child1Builder = createBuilder(scene).named('Child1');
const builder1 = createBuilder(scene)
.named('Parent')
.withChildIf(shouldAdd, child1Builder);
expect(builder1).toBeInstanceOf(EntityBuilder);
const child2Builder = createBuilder(scene).named('Child2');
const builder2 = createBuilder(scene)
.named('Parent2')
.withChildIf(shouldNotAdd, child2Builder);
expect(builder2).toBeInstanceOf(EntityBuilder);
});
});
describe('spawning and cloning', () => {
test('should spawn entity to scene with .spawn()', () => {
const initialCount = scene.entities.count;
const entity = createBuilder(scene)
.named('SpawnedEntity')
.with(new PositionComponent())
.spawn();
expect(scene.entities.count).toBe(initialCount + 1);
expect(scene.findEntityById(entity.id)).toBe(entity);
});
test('.build() should not add to scene automatically', () => {
const initialCount = scene.entities.count;
createBuilder(scene)
.named('BuiltEntity')
.build();
expect(scene.entities.count).toBe(initialCount);
});
test('should clone builder', () => {
const builder = createBuilder(scene)
.named('OriginalEntity')
.tagged(0x50);
const clonedBuilder = builder.clone();
expect(clonedBuilder).not.toBe(builder);
expect(clonedBuilder).toBeInstanceOf(EntityBuilder);
});
});
describe('complex building scenarios', () => {
test('should build complete entity with all options', () => {
const entity = createBuilder(scene)
.named('CompleteEntity')
.tagged(0x100)
.with(new PositionComponent(50, 75))
.with(new VelocityComponent())
.withFactory(() => new HealthComponent())
.configure(HealthComponent, (h: HealthComponent) => {
h.current = 80;
h.max = 100;
})
.enabled(true)
.active(true)
.build();
expect(entity.name).toBe('CompleteEntity');
expect(entity.tag).toBe(0x100);
expect(entity.enabled).toBe(true);
expect(entity.active).toBe(true);
expect(entity.hasComponent(PositionComponent)).toBe(true);
expect(entity.hasComponent(VelocityComponent)).toBe(true);
expect(entity.hasComponent(HealthComponent)).toBe(true);
const health = entity.getComponent(HealthComponent);
expect(health!.current).toBe(80);
expect(health!.max).toBe(100);
});
test('should support complex chaining', () => {
const builder = createBuilder(scene)
.named('Root')
.with(new PositionComponent(1, 1));
// Add child builder chain
const childBuilder = createBuilder(scene)
.named('Child')
.with(new PositionComponent(2, 2));
// Chain withChild
builder.withChild(childBuilder);
// Build and spawn
const root = builder.spawn();
expect(root.name).toBe('Root');
expect(root.hasComponent(PositionComponent)).toBe(true);
});
});
});