2025-07-31 11:56:04 +08:00
|
|
|
import { Component } from '../../../src/ECS/Component';
|
|
|
|
|
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
2025-08-11 09:01:01 +08:00
|
|
|
import { ComponentType } from '../../../src/ECS/Core/ComponentStorage';
|
2025-07-31 11:56:04 +08:00
|
|
|
|
|
|
|
|
class Position extends Component {
|
2025-07-31 15:37:40 +08:00
|
|
|
public x: number = 0;
|
|
|
|
|
public y: number = 0;
|
|
|
|
|
|
|
|
|
|
constructor(...args: unknown[]) {
|
2025-07-31 11:56:04 +08:00
|
|
|
super();
|
2025-07-31 15:37:40 +08:00
|
|
|
const [x = 0, y = 0] = args as [number?, number?];
|
|
|
|
|
this.x = x;
|
|
|
|
|
this.y = y;
|
2025-07-31 11:56:04 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Velocity extends Component {
|
2025-07-31 15:37:40 +08:00
|
|
|
public vx: number = 0;
|
|
|
|
|
public vy: number = 0;
|
|
|
|
|
|
|
|
|
|
constructor(...args: unknown[]) {
|
2025-07-31 11:56:04 +08:00
|
|
|
super();
|
2025-07-31 15:37:40 +08:00
|
|
|
const [vx = 0, vy = 0] = args as [number?, number?];
|
|
|
|
|
this.vx = vx;
|
|
|
|
|
this.vy = vy;
|
2025-07-31 11:56:04 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Health extends Component {
|
2025-07-31 15:37:40 +08:00
|
|
|
public hp: number = 100;
|
|
|
|
|
|
|
|
|
|
constructor(...args: unknown[]) {
|
2025-07-31 11:56:04 +08:00
|
|
|
super();
|
2025-07-31 15:37:40 +08:00
|
|
|
const [hp = 100] = args as [number?];
|
|
|
|
|
this.hp = hp;
|
2025-07-31 11:56:04 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
class Shield extends Component {
|
|
|
|
|
public value: number = 50;
|
|
|
|
|
|
|
|
|
|
constructor(...args: unknown[]) {
|
|
|
|
|
super();
|
|
|
|
|
const [value = 50] = args as [number?];
|
|
|
|
|
this.value = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 11:56:04 +08:00
|
|
|
class Dead extends Component {}
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
class Weapon extends Component {
|
|
|
|
|
public damage: number = 10;
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
constructor(...args: unknown[]) {
|
|
|
|
|
super();
|
|
|
|
|
const [damage = 10] = args as [number?];
|
|
|
|
|
this.damage = damage;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('Matcher', () => {
|
|
|
|
|
describe('静态工厂方法', () => {
|
|
|
|
|
test('all() 应该创建包含所有指定组件的条件', () => {
|
|
|
|
|
const matcher = Matcher.all(Position, Velocity);
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.all).toHaveLength(2);
|
|
|
|
|
expect(condition.all).toContain(Position);
|
|
|
|
|
expect(condition.all).toContain(Velocity);
|
|
|
|
|
expect(condition.any).toHaveLength(0);
|
|
|
|
|
expect(condition.none).toHaveLength(0);
|
|
|
|
|
});
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('any() 应该创建包含任一指定组件的条件', () => {
|
|
|
|
|
const matcher = Matcher.any(Health, Shield);
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.any).toHaveLength(2);
|
|
|
|
|
expect(condition.any).toContain(Health);
|
|
|
|
|
expect(condition.any).toContain(Shield);
|
|
|
|
|
expect(condition.all).toHaveLength(0);
|
|
|
|
|
expect(condition.none).toHaveLength(0);
|
|
|
|
|
});
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('none() 应该创建排除指定组件的条件', () => {
|
|
|
|
|
const matcher = Matcher.none(Dead, Weapon);
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.none).toHaveLength(2);
|
|
|
|
|
expect(condition.none).toContain(Dead);
|
|
|
|
|
expect(condition.none).toContain(Weapon);
|
|
|
|
|
expect(condition.all).toHaveLength(0);
|
|
|
|
|
expect(condition.any).toHaveLength(0);
|
|
|
|
|
});
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('byTag() 应该创建标签查询条件', () => {
|
|
|
|
|
const matcher = Matcher.byTag(123);
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.tag).toBe(123);
|
|
|
|
|
expect(condition.all).toHaveLength(0);
|
|
|
|
|
expect(condition.any).toHaveLength(0);
|
|
|
|
|
expect(condition.none).toHaveLength(0);
|
|
|
|
|
});
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('byName() 应该创建名称查询条件', () => {
|
|
|
|
|
const matcher = Matcher.byName('TestEntity');
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.name).toBe('TestEntity');
|
|
|
|
|
expect(condition.all).toHaveLength(0);
|
|
|
|
|
expect(condition.any).toHaveLength(0);
|
|
|
|
|
expect(condition.none).toHaveLength(0);
|
|
|
|
|
});
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('byComponent() 应该创建单组件查询条件', () => {
|
|
|
|
|
const matcher = Matcher.byComponent(Position);
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.component).toBe(Position);
|
|
|
|
|
expect(condition.all).toHaveLength(0);
|
|
|
|
|
expect(condition.any).toHaveLength(0);
|
|
|
|
|
expect(condition.none).toHaveLength(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('complex() 应该创建空的复杂查询构建器', () => {
|
|
|
|
|
const matcher = Matcher.complex();
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.all).toHaveLength(0);
|
|
|
|
|
expect(condition.any).toHaveLength(0);
|
|
|
|
|
expect(condition.none).toHaveLength(0);
|
|
|
|
|
expect(condition.tag).toBeUndefined();
|
|
|
|
|
expect(condition.name).toBeUndefined();
|
|
|
|
|
expect(condition.component).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('empty() 应该创建空匹配器', () => {
|
|
|
|
|
const matcher = Matcher.empty();
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.all).toHaveLength(0);
|
|
|
|
|
expect(condition.any).toHaveLength(0);
|
|
|
|
|
expect(condition.none).toHaveLength(0);
|
|
|
|
|
expect(condition.tag).toBeUndefined();
|
|
|
|
|
expect(condition.name).toBeUndefined();
|
|
|
|
|
expect(condition.component).toBeUndefined();
|
|
|
|
|
});
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
describe('实例方法', () => {
|
|
|
|
|
test('all() 应该添加到 all 条件数组', () => {
|
|
|
|
|
const matcher = Matcher.empty();
|
|
|
|
|
matcher.all(Position, Velocity);
|
2025-07-31 15:37:40 +08:00
|
|
|
const condition = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.all).toHaveLength(2);
|
2025-07-31 15:37:40 +08:00
|
|
|
expect(condition.all).toContain(Position);
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.all).toContain(Velocity);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('any() 应该添加到 any 条件数组', () => {
|
|
|
|
|
const matcher = Matcher.empty();
|
|
|
|
|
matcher.any(Health, Shield);
|
2025-07-31 15:37:40 +08:00
|
|
|
const condition = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.any).toHaveLength(2);
|
2025-07-31 15:37:40 +08:00
|
|
|
expect(condition.any).toContain(Health);
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.any).toContain(Shield);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('none() 应该添加到 none 条件数组', () => {
|
|
|
|
|
const matcher = Matcher.empty();
|
|
|
|
|
matcher.none(Dead);
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.none).toHaveLength(1);
|
|
|
|
|
expect(condition.none).toContain(Dead);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('exclude() 应该是 none() 的别名', () => {
|
|
|
|
|
const matcher = Matcher.empty();
|
|
|
|
|
matcher.exclude(Dead, Weapon);
|
2025-07-31 15:37:40 +08:00
|
|
|
const condition = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.none).toHaveLength(2);
|
2025-07-31 15:37:40 +08:00
|
|
|
expect(condition.none).toContain(Dead);
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.none).toContain(Weapon);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('one() 应该是 any() 的别名', () => {
|
|
|
|
|
const matcher = Matcher.empty();
|
|
|
|
|
matcher.one(Health, Shield);
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.any).toHaveLength(2);
|
|
|
|
|
expect(condition.any).toContain(Health);
|
|
|
|
|
expect(condition.any).toContain(Shield);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('链式调用', () => {
|
|
|
|
|
test('应该支持复杂的链式调用', () => {
|
2025-07-31 15:37:40 +08:00
|
|
|
const matcher = Matcher.all(Position)
|
2025-08-11 09:01:01 +08:00
|
|
|
.any(Health, Shield)
|
|
|
|
|
.none(Dead)
|
|
|
|
|
.withTag(100)
|
|
|
|
|
.withName('Player');
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-07-31 15:37:40 +08:00
|
|
|
const condition = matcher.getCondition();
|
2025-08-11 09:01:01 +08:00
|
|
|
|
2025-07-31 15:37:40 +08:00
|
|
|
expect(condition.all).toContain(Position);
|
|
|
|
|
expect(condition.any).toContain(Health);
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.any).toContain(Shield);
|
2025-07-31 15:37:40 +08:00
|
|
|
expect(condition.none).toContain(Dead);
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.tag).toBe(100);
|
|
|
|
|
expect(condition.name).toBe('Player');
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('多次调用同一方法应该累积组件', () => {
|
|
|
|
|
const matcher = Matcher.empty()
|
|
|
|
|
.all(Position)
|
|
|
|
|
.all(Velocity)
|
|
|
|
|
.any(Health)
|
|
|
|
|
.any(Shield)
|
|
|
|
|
.none(Dead)
|
|
|
|
|
.none(Weapon);
|
|
|
|
|
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.all).toHaveLength(2);
|
|
|
|
|
expect(condition.any).toHaveLength(2);
|
|
|
|
|
expect(condition.none).toHaveLength(2);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('条件管理方法', () => {
|
|
|
|
|
test('withTag() 应该设置标签条件', () => {
|
|
|
|
|
const matcher = Matcher.empty().withTag(42);
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.tag).toBe(42);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('withName() 应该设置名称条件', () => {
|
|
|
|
|
const matcher = Matcher.empty().withName('Enemy');
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.name).toBe('Enemy');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('withComponent() 应该设置单组件条件', () => {
|
|
|
|
|
const matcher = Matcher.empty().withComponent(Position);
|
2025-07-31 15:37:40 +08:00
|
|
|
const condition = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-07-31 15:37:40 +08:00
|
|
|
expect(condition.component).toBe(Position);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('withoutTag() 应该移除标签条件', () => {
|
|
|
|
|
const matcher = Matcher.byTag(123).withoutTag();
|
2025-07-31 15:37:40 +08:00
|
|
|
const condition = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.tag).toBeUndefined();
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('withoutName() 应该移除名称条件', () => {
|
|
|
|
|
const matcher = Matcher.byName('Test').withoutName();
|
2025-07-31 15:37:40 +08:00
|
|
|
const condition = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.name).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('withoutComponent() 应该移除单组件条件', () => {
|
|
|
|
|
const matcher = Matcher.byComponent(Position).withoutComponent();
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.component).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('覆盖条件应该替换之前的值', () => {
|
|
|
|
|
const matcher = Matcher.byTag(100)
|
|
|
|
|
.withTag(200)
|
|
|
|
|
.withName('First')
|
|
|
|
|
.withName('Second')
|
|
|
|
|
.withComponent(Position)
|
|
|
|
|
.withComponent(Velocity);
|
|
|
|
|
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.tag).toBe(200);
|
|
|
|
|
expect(condition.name).toBe('Second');
|
|
|
|
|
expect(condition.component).toBe(Velocity);
|
2025-07-31 15:37:40 +08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
describe('工具方法', () => {
|
|
|
|
|
test('isEmpty() 应该正确判断空条件', () => {
|
|
|
|
|
const emptyMatcher = Matcher.empty();
|
|
|
|
|
expect(emptyMatcher.isEmpty()).toBe(true);
|
|
|
|
|
|
|
|
|
|
const nonEmptyMatcher = Matcher.all(Position);
|
|
|
|
|
expect(nonEmptyMatcher.isEmpty()).toBe(false);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('isEmpty() 应该检查所有条件类型', () => {
|
|
|
|
|
expect(Matcher.all(Position).isEmpty()).toBe(false);
|
|
|
|
|
expect(Matcher.any(Health).isEmpty()).toBe(false);
|
|
|
|
|
expect(Matcher.none(Dead).isEmpty()).toBe(false);
|
|
|
|
|
expect(Matcher.byTag(1).isEmpty()).toBe(false);
|
|
|
|
|
expect(Matcher.byName('test').isEmpty()).toBe(false);
|
|
|
|
|
expect(Matcher.byComponent(Position).isEmpty()).toBe(false);
|
2025-07-31 15:37:40 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('reset() 应该清空所有条件', () => {
|
|
|
|
|
const matcher = Matcher.all(Position, Velocity)
|
|
|
|
|
.any(Health, Shield)
|
|
|
|
|
.none(Dead)
|
|
|
|
|
.withTag(123)
|
|
|
|
|
.withName('Test')
|
|
|
|
|
.withComponent(Weapon);
|
|
|
|
|
|
|
|
|
|
matcher.reset();
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.all).toHaveLength(0);
|
|
|
|
|
expect(condition.any).toHaveLength(0);
|
|
|
|
|
expect(condition.none).toHaveLength(0);
|
|
|
|
|
expect(condition.tag).toBeUndefined();
|
|
|
|
|
expect(condition.name).toBeUndefined();
|
|
|
|
|
expect(condition.component).toBeUndefined();
|
|
|
|
|
expect(matcher.isEmpty()).toBe(true);
|
2025-07-31 15:37:40 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('clone() 应该创建独立的副本', () => {
|
|
|
|
|
const original = Matcher.all(Position, Velocity)
|
|
|
|
|
.any(Health)
|
|
|
|
|
.none(Dead)
|
|
|
|
|
.withTag(100)
|
|
|
|
|
.withName('Original')
|
|
|
|
|
.withComponent(Weapon);
|
|
|
|
|
|
|
|
|
|
const cloned = original.clone();
|
|
|
|
|
const originalCondition = original.getCondition();
|
|
|
|
|
const clonedCondition = cloned.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(clonedCondition.all).toEqual(originalCondition.all);
|
|
|
|
|
expect(clonedCondition.any).toEqual(originalCondition.any);
|
|
|
|
|
expect(clonedCondition.none).toEqual(originalCondition.none);
|
|
|
|
|
expect(clonedCondition.tag).toBe(originalCondition.tag);
|
|
|
|
|
expect(clonedCondition.name).toBe(originalCondition.name);
|
|
|
|
|
expect(clonedCondition.component).toBe(originalCondition.component);
|
|
|
|
|
|
|
|
|
|
// 修改克隆不应影响原对象
|
|
|
|
|
cloned.all(Shield).withTag(200);
|
|
|
|
|
|
|
|
|
|
expect(original.getCondition().all).not.toContain(Shield);
|
|
|
|
|
expect(original.getCondition().tag).toBe(100);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
2025-08-11 09:01:01 +08:00
|
|
|
|
|
|
|
|
test('toString() 应该生成可读的字符串表示', () => {
|
|
|
|
|
const matcher = Matcher.all(Position, Velocity)
|
|
|
|
|
.any(Health, Shield)
|
|
|
|
|
.none(Dead)
|
|
|
|
|
.withTag(123)
|
|
|
|
|
.withName('TestEntity')
|
|
|
|
|
.withComponent(Weapon);
|
|
|
|
|
|
|
|
|
|
const str = matcher.toString();
|
|
|
|
|
|
|
|
|
|
expect(str).toContain('all(Position, Velocity)');
|
|
|
|
|
expect(str).toContain('any(Health, Shield)');
|
|
|
|
|
expect(str).toContain('none(Dead)');
|
|
|
|
|
expect(str).toContain('tag(123)');
|
|
|
|
|
expect(str).toContain('name(TestEntity)');
|
|
|
|
|
expect(str).toContain('component(Weapon)');
|
|
|
|
|
expect(str).toContain('Matcher[');
|
|
|
|
|
expect(str).toContain(' & ');
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('toString() 应该处理空条件', () => {
|
|
|
|
|
const emptyMatcher = Matcher.empty();
|
|
|
|
|
const str = emptyMatcher.toString();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(str).toBe('Matcher[]');
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('toString() 应该处理部分条件', () => {
|
|
|
|
|
const matcher = Matcher.all(Position).withTag(42);
|
|
|
|
|
const str = matcher.toString();
|
|
|
|
|
|
|
|
|
|
expect(str).toContain('all(Position)');
|
|
|
|
|
expect(str).toContain('tag(42)');
|
|
|
|
|
expect(str).not.toContain('any(');
|
|
|
|
|
expect(str).not.toContain('none(');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('getCondition() 返回值', () => {
|
|
|
|
|
test('应该返回只读的条件副本', () => {
|
|
|
|
|
const matcher = Matcher.all(Position, Velocity);
|
|
|
|
|
const condition1 = matcher.getCondition();
|
|
|
|
|
const condition2 = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
// 应该是不同的对象实例
|
|
|
|
|
expect(condition1).not.toBe(condition2);
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
// 但内容应该相同
|
|
|
|
|
expect(condition1.all).toEqual(condition2.all);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('修改返回的条件不应影响原 Matcher', () => {
|
|
|
|
|
const matcher = Matcher.all(Position);
|
2025-07-31 15:37:40 +08:00
|
|
|
const condition = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
// 尝试修改返回的条件
|
|
|
|
|
condition.all.push(Velocity as ComponentType);
|
|
|
|
|
|
|
|
|
|
// 原 Matcher 不应被影响
|
|
|
|
|
const freshCondition = matcher.getCondition();
|
|
|
|
|
expect(freshCondition.all).toHaveLength(1);
|
|
|
|
|
expect(freshCondition.all).toContain(Position);
|
|
|
|
|
expect(freshCondition.all).not.toContain(Velocity);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
});
|
2025-07-31 15:37:40 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
describe('边界情况', () => {
|
|
|
|
|
test('应该处理空参数调用', () => {
|
|
|
|
|
const matcher = Matcher.empty();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
matcher.all();
|
|
|
|
|
matcher.any();
|
|
|
|
|
matcher.none();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
expect(condition.all).toHaveLength(0);
|
|
|
|
|
expect(condition.any).toHaveLength(0);
|
|
|
|
|
expect(condition.none).toHaveLength(0);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('应该处理重复的组件类型', () => {
|
|
|
|
|
const matcher = Matcher.all(Position, Position, Velocity);
|
|
|
|
|
const condition = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.all).toHaveLength(3);
|
|
|
|
|
expect(condition.all.filter(t => t === Position)).toHaveLength(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('应该处理标签值为0的情况', () => {
|
|
|
|
|
const matcher = Matcher.byTag(0);
|
|
|
|
|
const condition = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.tag).toBe(0);
|
|
|
|
|
expect(matcher.isEmpty()).toBe(false);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('应该处理空字符串名称', () => {
|
|
|
|
|
const matcher = Matcher.byName('');
|
|
|
|
|
const condition = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.name).toBe('');
|
|
|
|
|
expect(matcher.isEmpty()).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('reset() 应该返回自身以支持链式调用', () => {
|
|
|
|
|
const matcher = Matcher.all(Position);
|
|
|
|
|
const result = matcher.reset();
|
|
|
|
|
|
|
|
|
|
expect(result).toBe(matcher);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('所有实例方法都应该返回自身以支持链式调用', () => {
|
|
|
|
|
const matcher = Matcher.empty();
|
|
|
|
|
|
|
|
|
|
expect(matcher.all(Position)).toBe(matcher);
|
|
|
|
|
expect(matcher.any(Health)).toBe(matcher);
|
|
|
|
|
expect(matcher.none(Dead)).toBe(matcher);
|
|
|
|
|
expect(matcher.exclude(Weapon)).toBe(matcher);
|
|
|
|
|
expect(matcher.one(Shield)).toBe(matcher);
|
|
|
|
|
expect(matcher.withTag(1)).toBe(matcher);
|
|
|
|
|
expect(matcher.withName('test')).toBe(matcher);
|
|
|
|
|
expect(matcher.withComponent(Position)).toBe(matcher);
|
|
|
|
|
expect(matcher.withoutTag()).toBe(matcher);
|
|
|
|
|
expect(matcher.withoutName()).toBe(matcher);
|
|
|
|
|
expect(matcher.withoutComponent()).toBe(matcher);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
});
|
2025-07-31 15:37:40 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
describe('类型安全性', () => {
|
|
|
|
|
test('ComponentType 应该正确工作', () => {
|
|
|
|
|
// 这个测试主要是确保类型编译正确
|
|
|
|
|
const matcher = Matcher.all(Position as ComponentType<Position>);
|
|
|
|
|
const condition = matcher.getCondition();
|
|
|
|
|
|
|
|
|
|
expect(condition.all).toContain(Position);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
test('应该支持泛型组件类型', () => {
|
|
|
|
|
class GenericComponent<T> extends Component {
|
|
|
|
|
public data: T;
|
|
|
|
|
constructor(data: T, ...args: unknown[]) {
|
2025-07-31 15:37:40 +08:00
|
|
|
super();
|
2025-08-11 09:01:01 +08:00
|
|
|
this.data = data;
|
2025-07-31 15:37:40 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
class StringComponent extends GenericComponent<string> {
|
|
|
|
|
constructor(...args: unknown[]) {
|
|
|
|
|
super(args[0] as string || 'default');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class NumberComponent extends GenericComponent<number> {
|
|
|
|
|
constructor(...args: unknown[]) {
|
|
|
|
|
super(args[0] as number || 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const matcher = Matcher.all(StringComponent, NumberComponent);
|
|
|
|
|
const condition = matcher.getCondition();
|
2025-07-31 11:56:04 +08:00
|
|
|
|
2025-08-11 09:01:01 +08:00
|
|
|
expect(condition.all).toContain(StringComponent);
|
|
|
|
|
expect(condition.all).toContain(NumberComponent);
|
2025-07-31 11:56:04 +08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|