新增更多覆盖测试
This commit is contained in:
@@ -136,6 +136,13 @@ export class ComponentPoolManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置管理器,移除所有注册的池
|
||||||
|
*/
|
||||||
|
reset(): void {
|
||||||
|
this.pools.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取池统计信息
|
* 获取池统计信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
332
tests/ECS/Core/BitMaskOptimizer.test.ts
Normal file
332
tests/ECS/Core/BitMaskOptimizer.test.ts
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
import { BitMaskOptimizer } from '../../../src/ECS/Core/BitMaskOptimizer';
|
||||||
|
|
||||||
|
describe('BitMaskOptimizer - 位掩码优化器测试', () => {
|
||||||
|
let optimizer: BitMaskOptimizer;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
optimizer = BitMaskOptimizer.getInstance();
|
||||||
|
optimizer.reset(); // 确保每个测试开始时都是干净的状态
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('单例模式测试', () => {
|
||||||
|
it('应该返回同一个实例', () => {
|
||||||
|
const instance1 = BitMaskOptimizer.getInstance();
|
||||||
|
const instance2 = BitMaskOptimizer.getInstance();
|
||||||
|
expect(instance1).toBe(instance2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('组件类型注册', () => {
|
||||||
|
it('应该能够注册组件类型', () => {
|
||||||
|
const id = optimizer.registerComponentType('Transform');
|
||||||
|
expect(id).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('重复注册相同组件应该返回相同ID', () => {
|
||||||
|
const id1 = optimizer.registerComponentType('Transform');
|
||||||
|
const id2 = optimizer.registerComponentType('Transform');
|
||||||
|
expect(id1).toBe(id2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够注册多个不同组件类型', () => {
|
||||||
|
const id1 = optimizer.registerComponentType('Transform');
|
||||||
|
const id2 = optimizer.registerComponentType('Velocity');
|
||||||
|
const id3 = optimizer.registerComponentType('Health');
|
||||||
|
|
||||||
|
expect(id1).toBe(0);
|
||||||
|
expect(id2).toBe(1);
|
||||||
|
expect(id3).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取已注册组件的类型ID', () => {
|
||||||
|
optimizer.registerComponentType('Transform');
|
||||||
|
const id = optimizer.getComponentTypeId('Transform');
|
||||||
|
expect(id).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('获取未注册组件的类型ID应该返回undefined', () => {
|
||||||
|
const id = optimizer.getComponentTypeId('UnknownComponent');
|
||||||
|
expect(id).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('单组件掩码创建', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
optimizer.registerComponentType('Transform');
|
||||||
|
optimizer.registerComponentType('Velocity');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够创建单个组件的掩码', () => {
|
||||||
|
const mask = optimizer.createSingleComponentMask('Transform');
|
||||||
|
expect(mask).toBe(1n);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('不同组件应该有不同的掩码', () => {
|
||||||
|
const transformMask = optimizer.createSingleComponentMask('Transform');
|
||||||
|
const velocityMask = optimizer.createSingleComponentMask('Velocity');
|
||||||
|
|
||||||
|
expect(transformMask).toBe(1n);
|
||||||
|
expect(velocityMask).toBe(2n);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('创建未注册组件的掩码应该抛出错误', () => {
|
||||||
|
expect(() => {
|
||||||
|
optimizer.createSingleComponentMask('UnknownComponent');
|
||||||
|
}).toThrow('Component type not registered: UnknownComponent');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('相同组件的掩码应该使用缓存', () => {
|
||||||
|
const mask1 = optimizer.createSingleComponentMask('Transform');
|
||||||
|
const mask2 = optimizer.createSingleComponentMask('Transform');
|
||||||
|
expect(mask1).toBe(mask2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('组合掩码创建', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
optimizer.registerComponentType('Transform');
|
||||||
|
optimizer.registerComponentType('Velocity');
|
||||||
|
optimizer.registerComponentType('Health');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够创建多个组件的组合掩码', () => {
|
||||||
|
const mask = optimizer.createCombinedMask(['Transform', 'Velocity']);
|
||||||
|
expect(mask).toBe(3n); // 1n | 2n = 3n
|
||||||
|
});
|
||||||
|
|
||||||
|
it('组件顺序不应该影响掩码结果', () => {
|
||||||
|
const mask1 = optimizer.createCombinedMask(['Transform', 'Velocity']);
|
||||||
|
const mask2 = optimizer.createCombinedMask(['Velocity', 'Transform']);
|
||||||
|
expect(mask1).toBe(mask2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('三个组件的组合掩码应该正确', () => {
|
||||||
|
const mask = optimizer.createCombinedMask(['Transform', 'Velocity', 'Health']);
|
||||||
|
expect(mask).toBe(7n); // 1n | 2n | 4n = 7n
|
||||||
|
});
|
||||||
|
|
||||||
|
it('空数组应该返回0掩码', () => {
|
||||||
|
const mask = optimizer.createCombinedMask([]);
|
||||||
|
expect(mask).toBe(0n);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('包含未注册组件应该抛出错误', () => {
|
||||||
|
expect(() => {
|
||||||
|
optimizer.createCombinedMask(['Transform', 'UnknownComponent']);
|
||||||
|
}).toThrow('Component type not registered: UnknownComponent');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('掩码检查功能', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
optimizer.registerComponentType('Transform');
|
||||||
|
optimizer.registerComponentType('Velocity');
|
||||||
|
optimizer.registerComponentType('Health');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够检查掩码是否包含指定组件', () => {
|
||||||
|
const mask = optimizer.createCombinedMask(['Transform', 'Velocity']);
|
||||||
|
|
||||||
|
expect(optimizer.maskContainsComponent(mask, 'Transform')).toBe(true);
|
||||||
|
expect(optimizer.maskContainsComponent(mask, 'Velocity')).toBe(true);
|
||||||
|
expect(optimizer.maskContainsComponent(mask, 'Health')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够检查掩码是否包含所有指定组件', () => {
|
||||||
|
const mask = optimizer.createCombinedMask(['Transform', 'Velocity', 'Health']);
|
||||||
|
|
||||||
|
expect(optimizer.maskContainsAllComponents(mask, ['Transform'])).toBe(true);
|
||||||
|
expect(optimizer.maskContainsAllComponents(mask, ['Transform', 'Velocity'])).toBe(true);
|
||||||
|
expect(optimizer.maskContainsAllComponents(mask, ['Transform', 'Velocity', 'Health'])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('不完全包含时maskContainsAllComponents应该返回false', () => {
|
||||||
|
const mask = optimizer.createCombinedMask(['Transform']);
|
||||||
|
|
||||||
|
expect(optimizer.maskContainsAllComponents(mask, ['Transform', 'Velocity'])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够检查掩码是否包含任一指定组件', () => {
|
||||||
|
const mask = optimizer.createCombinedMask(['Transform']);
|
||||||
|
|
||||||
|
expect(optimizer.maskContainsAnyComponent(mask, ['Transform', 'Velocity'])).toBe(true);
|
||||||
|
expect(optimizer.maskContainsAnyComponent(mask, ['Velocity', 'Health'])).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('掩码操作功能', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
optimizer.registerComponentType('Transform');
|
||||||
|
optimizer.registerComponentType('Velocity');
|
||||||
|
optimizer.registerComponentType('Health');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够向掩码添加组件', () => {
|
||||||
|
let mask = optimizer.createSingleComponentMask('Transform');
|
||||||
|
mask = optimizer.addComponentToMask(mask, 'Velocity');
|
||||||
|
|
||||||
|
expect(optimizer.maskContainsComponent(mask, 'Transform')).toBe(true);
|
||||||
|
expect(optimizer.maskContainsComponent(mask, 'Velocity')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够从掩码移除组件', () => {
|
||||||
|
let mask = optimizer.createCombinedMask(['Transform', 'Velocity']);
|
||||||
|
mask = optimizer.removeComponentFromMask(mask, 'Velocity');
|
||||||
|
|
||||||
|
expect(optimizer.maskContainsComponent(mask, 'Transform')).toBe(true);
|
||||||
|
expect(optimizer.maskContainsComponent(mask, 'Velocity')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('移除不存在的组件不应该影响掩码', () => {
|
||||||
|
const originalMask = optimizer.createSingleComponentMask('Transform');
|
||||||
|
const newMask = optimizer.removeComponentFromMask(originalMask, 'Velocity');
|
||||||
|
|
||||||
|
expect(newMask).toBe(originalMask);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('工具功能', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
optimizer.registerComponentType('Transform');
|
||||||
|
optimizer.registerComponentType('Velocity');
|
||||||
|
optimizer.registerComponentType('Health');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够将掩码转换为组件名称数组', () => {
|
||||||
|
const mask = optimizer.createCombinedMask(['Transform', 'Health']);
|
||||||
|
const componentNames = optimizer.maskToComponentNames(mask);
|
||||||
|
|
||||||
|
expect(componentNames).toContain('Transform');
|
||||||
|
expect(componentNames).toContain('Health');
|
||||||
|
expect(componentNames).not.toContain('Velocity');
|
||||||
|
expect(componentNames.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('空掩码应该返回空数组', () => {
|
||||||
|
const componentNames = optimizer.maskToComponentNames(0n);
|
||||||
|
expect(componentNames).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取掩码中组件的数量', () => {
|
||||||
|
const mask1 = optimizer.createSingleComponentMask('Transform');
|
||||||
|
const mask2 = optimizer.createCombinedMask(['Transform', 'Velocity']);
|
||||||
|
const mask3 = optimizer.createCombinedMask(['Transform', 'Velocity', 'Health']);
|
||||||
|
|
||||||
|
expect(optimizer.getComponentCount(mask1)).toBe(1);
|
||||||
|
expect(optimizer.getComponentCount(mask2)).toBe(2);
|
||||||
|
expect(optimizer.getComponentCount(mask3)).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('空掩码的组件数量应该为0', () => {
|
||||||
|
expect(optimizer.getComponentCount(0n)).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('缓存和性能优化', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
optimizer.registerComponentType('Transform');
|
||||||
|
optimizer.registerComponentType('Velocity');
|
||||||
|
optimizer.registerComponentType('Health');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够预计算常用掩码组合', () => {
|
||||||
|
const commonCombinations = [
|
||||||
|
['Transform', 'Velocity'],
|
||||||
|
['Transform', 'Health'],
|
||||||
|
['Velocity', 'Health']
|
||||||
|
];
|
||||||
|
|
||||||
|
optimizer.precomputeCommonMasks(commonCombinations);
|
||||||
|
|
||||||
|
// 验证掩码已被缓存
|
||||||
|
const stats = optimizer.getCacheStats();
|
||||||
|
expect(stats.size).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取缓存统计信息', () => {
|
||||||
|
optimizer.createSingleComponentMask('Transform');
|
||||||
|
optimizer.createCombinedMask(['Transform', 'Velocity']);
|
||||||
|
|
||||||
|
const stats = optimizer.getCacheStats();
|
||||||
|
expect(stats.size).toBeGreaterThan(0);
|
||||||
|
expect(stats.componentTypes).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够清空缓存', () => {
|
||||||
|
optimizer.createSingleComponentMask('Transform');
|
||||||
|
optimizer.clearCache();
|
||||||
|
|
||||||
|
const stats = optimizer.getCacheStats();
|
||||||
|
expect(stats.size).toBe(0);
|
||||||
|
expect(stats.componentTypes).toBe(3); // 组件类型不会被清除
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够重置优化器', () => {
|
||||||
|
optimizer.registerComponentType('NewComponent');
|
||||||
|
optimizer.createSingleComponentMask('Transform');
|
||||||
|
|
||||||
|
optimizer.reset();
|
||||||
|
|
||||||
|
const stats = optimizer.getCacheStats();
|
||||||
|
expect(stats.size).toBe(0);
|
||||||
|
expect(stats.componentTypes).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('边界情况和错误处理', () => {
|
||||||
|
it('处理大量组件类型注册', () => {
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
const id = optimizer.registerComponentType(`Component${i}`);
|
||||||
|
expect(id).toBe(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('处理大掩码值', () => {
|
||||||
|
// 注册64个组件类型
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
optimizer.registerComponentType(`Component${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mask = optimizer.createSingleComponentMask('Component63');
|
||||||
|
expect(mask).toBe(1n << 63n);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('空组件名称数组的组合掩码', () => {
|
||||||
|
const mask = optimizer.createCombinedMask([]);
|
||||||
|
expect(mask).toBe(0n);
|
||||||
|
expect(optimizer.getComponentCount(mask)).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('性能测试', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// 注册一些组件类型
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
optimizer.registerComponentType(`Component${i}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('大量掩码创建应该高效', () => {
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
optimizer.createSingleComponentMask('Component0');
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||||
|
});
|
||||||
|
|
||||||
|
it('大量掩码检查应该高效', () => {
|
||||||
|
const mask = optimizer.createCombinedMask(['Component0', 'Component1', 'Component2']);
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
optimizer.maskContainsComponent(mask, 'Component1');
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
470
tests/ECS/Core/ComponentPool.test.ts
Normal file
470
tests/ECS/Core/ComponentPool.test.ts
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
import { ComponentPool, ComponentPoolManager } from '../../../src/ECS/Core/ComponentPool';
|
||||||
|
import { Component } from '../../../src/ECS/Component';
|
||||||
|
|
||||||
|
// 测试用组件类
|
||||||
|
class TestComponent extends Component {
|
||||||
|
public value: number = 0;
|
||||||
|
public name: string = '';
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.value = 0;
|
||||||
|
this.name = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnotherTestComponent extends Component {
|
||||||
|
public data: string = '';
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.data = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ComponentPool - 组件对象池测试', () => {
|
||||||
|
let pool: ComponentPool<TestComponent>;
|
||||||
|
let createFn: () => TestComponent;
|
||||||
|
let resetFn: (component: TestComponent) => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
createFn = () => new TestComponent();
|
||||||
|
resetFn = (component: TestComponent) => component.reset();
|
||||||
|
pool = new ComponentPool(createFn, resetFn, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('基本功能测试', () => {
|
||||||
|
it('应该能够创建组件池', () => {
|
||||||
|
expect(pool).toBeDefined();
|
||||||
|
expect(pool.getAvailableCount()).toBe(0);
|
||||||
|
expect(pool.getMaxSize()).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取组件实例', () => {
|
||||||
|
const component = pool.acquire();
|
||||||
|
expect(component).toBeInstanceOf(TestComponent);
|
||||||
|
expect(component.value).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('第一次获取应该创建新实例', () => {
|
||||||
|
const component = pool.acquire();
|
||||||
|
expect(component).toBeInstanceOf(TestComponent);
|
||||||
|
expect(pool.getAvailableCount()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够释放组件回池中', () => {
|
||||||
|
const component = pool.acquire();
|
||||||
|
component.value = 42;
|
||||||
|
component.name = 'test';
|
||||||
|
|
||||||
|
pool.release(component);
|
||||||
|
|
||||||
|
expect(pool.getAvailableCount()).toBe(1);
|
||||||
|
expect(component.value).toBe(0); // 应该被重置
|
||||||
|
expect(component.name).toBe(''); // 应该被重置
|
||||||
|
});
|
||||||
|
|
||||||
|
it('从池中获取的组件应该是之前释放的', () => {
|
||||||
|
const component1 = pool.acquire();
|
||||||
|
pool.release(component1);
|
||||||
|
|
||||||
|
const component2 = pool.acquire();
|
||||||
|
expect(component2).toBe(component1); // 应该是同一个实例
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('池容量管理', () => {
|
||||||
|
it('应该能够设置最大容量', () => {
|
||||||
|
const smallPool = new ComponentPool(createFn, resetFn, 2);
|
||||||
|
expect(smallPool.getMaxSize()).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('超过最大容量的组件不应该被存储', () => {
|
||||||
|
const smallPool = new ComponentPool(createFn, resetFn, 2);
|
||||||
|
|
||||||
|
const components = [];
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
components.push(smallPool.acquire());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 释放所有组件
|
||||||
|
components.forEach(comp => smallPool.release(comp));
|
||||||
|
|
||||||
|
// 只有2个组件被存储在池中
|
||||||
|
expect(smallPool.getAvailableCount()).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该正确处理默认最大容量', () => {
|
||||||
|
const defaultPool = new ComponentPool(createFn);
|
||||||
|
expect(defaultPool.getMaxSize()).toBe(1000); // 默认值
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('重置功能测试', () => {
|
||||||
|
it('没有重置函数时应该正常工作', () => {
|
||||||
|
const poolWithoutReset = new ComponentPool<TestComponent>(createFn);
|
||||||
|
const component = poolWithoutReset.acquire();
|
||||||
|
component.value = 42;
|
||||||
|
|
||||||
|
poolWithoutReset.release(component);
|
||||||
|
|
||||||
|
// 没有重置函数,值应该保持不变
|
||||||
|
expect(component.value).toBe(42);
|
||||||
|
expect(poolWithoutReset.getAvailableCount()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('重置函数应该在释放时被调用', () => {
|
||||||
|
const mockReset = jest.fn();
|
||||||
|
const poolWithMockReset = new ComponentPool(createFn, mockReset);
|
||||||
|
|
||||||
|
const component = poolWithMockReset.acquire();
|
||||||
|
poolWithMockReset.release(component);
|
||||||
|
|
||||||
|
expect(mockReset).toHaveBeenCalledWith(component);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('预热功能', () => {
|
||||||
|
it('应该能够预填充对象池', () => {
|
||||||
|
pool.prewarm(5);
|
||||||
|
expect(pool.getAvailableCount()).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('预热不应该超过最大容量', () => {
|
||||||
|
pool.prewarm(15); // 超过最大容量10
|
||||||
|
expect(pool.getAvailableCount()).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('预热0个对象应该安全', () => {
|
||||||
|
pool.prewarm(0);
|
||||||
|
expect(pool.getAvailableCount()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('多次预热应该正确累加', () => {
|
||||||
|
pool.prewarm(3);
|
||||||
|
pool.prewarm(2);
|
||||||
|
expect(pool.getAvailableCount()).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('清空功能', () => {
|
||||||
|
it('应该能够清空对象池', () => {
|
||||||
|
pool.prewarm(5);
|
||||||
|
expect(pool.getAvailableCount()).toBe(5);
|
||||||
|
|
||||||
|
pool.clear();
|
||||||
|
expect(pool.getAvailableCount()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('空池清空应该安全', () => {
|
||||||
|
pool.clear();
|
||||||
|
expect(pool.getAvailableCount()).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('边界情况测试', () => {
|
||||||
|
it('应该处理连续的获取和释放', () => {
|
||||||
|
const components: TestComponent[] = [];
|
||||||
|
|
||||||
|
// 获取多个组件
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
components.push(pool.acquire());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 释放所有组件
|
||||||
|
components.forEach(comp => pool.release(comp));
|
||||||
|
expect(pool.getAvailableCount()).toBe(5);
|
||||||
|
|
||||||
|
// 再次获取应该复用之前的实例
|
||||||
|
const reusedComponents: TestComponent[] = [];
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
reusedComponents.push(pool.acquire());
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(pool.getAvailableCount()).toBe(0);
|
||||||
|
|
||||||
|
// 验证复用的组件确实是之前的实例
|
||||||
|
components.forEach(originalComp => {
|
||||||
|
expect(reusedComponents).toContain(originalComp);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理空池的多次获取', () => {
|
||||||
|
const component1 = pool.acquire();
|
||||||
|
const component2 = pool.acquire();
|
||||||
|
const component3 = pool.acquire();
|
||||||
|
|
||||||
|
expect(component1).not.toBe(component2);
|
||||||
|
expect(component2).not.toBe(component3);
|
||||||
|
expect(component1).not.toBe(component3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ComponentPoolManager - 组件池管理器测试', () => {
|
||||||
|
let manager: ComponentPoolManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
manager = ComponentPoolManager.getInstance();
|
||||||
|
// 重置管理器以确保测试隔离
|
||||||
|
manager.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('单例模式测试', () => {
|
||||||
|
it('应该返回同一个实例', () => {
|
||||||
|
const instance1 = ComponentPoolManager.getInstance();
|
||||||
|
const instance2 = ComponentPoolManager.getInstance();
|
||||||
|
expect(instance1).toBe(instance2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('池注册和管理', () => {
|
||||||
|
it('应该能够注册组件池', () => {
|
||||||
|
const createFn = () => new TestComponent();
|
||||||
|
const resetFn = (comp: TestComponent) => comp.reset();
|
||||||
|
|
||||||
|
manager.registerPool('TestComponent', createFn, resetFn, 20);
|
||||||
|
|
||||||
|
const stats = manager.getPoolStats();
|
||||||
|
expect(stats.has('TestComponent')).toBe(true);
|
||||||
|
expect(stats.get('TestComponent')?.maxSize).toBe(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够注册多个不同类型的池', () => {
|
||||||
|
manager.registerPool('TestComponent', () => new TestComponent());
|
||||||
|
manager.registerPool('AnotherTestComponent', () => new AnotherTestComponent());
|
||||||
|
|
||||||
|
const stats = manager.getPoolStats();
|
||||||
|
expect(stats.size).toBe(2);
|
||||||
|
expect(stats.has('TestComponent')).toBe(true);
|
||||||
|
expect(stats.has('AnotherTestComponent')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('注册池时应该使用默认参数', () => {
|
||||||
|
manager.registerPool('TestComponent', () => new TestComponent());
|
||||||
|
|
||||||
|
const stats = manager.getPoolStats();
|
||||||
|
expect(stats.get('TestComponent')?.maxSize).toBe(1000); // 默认值
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('组件获取和释放', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
manager.registerPool('TestComponent', () => new TestComponent(), (comp) => comp.reset());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取组件实例', () => {
|
||||||
|
const component = manager.acquireComponent<TestComponent>('TestComponent');
|
||||||
|
expect(component).toBeInstanceOf(TestComponent);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('获取未注册池的组件应该返回null', () => {
|
||||||
|
const component = manager.acquireComponent<TestComponent>('UnknownComponent');
|
||||||
|
expect(component).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够释放组件实例', () => {
|
||||||
|
const component = manager.acquireComponent<TestComponent>('TestComponent')!;
|
||||||
|
component.value = 42;
|
||||||
|
|
||||||
|
manager.releaseComponent('TestComponent', component);
|
||||||
|
|
||||||
|
// 验证组件被重置并返回池中
|
||||||
|
const reusedComponent = manager.acquireComponent<TestComponent>('TestComponent');
|
||||||
|
expect(reusedComponent).toBe(component);
|
||||||
|
expect(reusedComponent!.value).toBe(0); // 应该被重置
|
||||||
|
});
|
||||||
|
|
||||||
|
it('释放到未注册池应该安全处理', () => {
|
||||||
|
const component = new TestComponent();
|
||||||
|
expect(() => {
|
||||||
|
manager.releaseComponent('UnknownComponent', component);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('批量操作', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
manager.registerPool('TestComponent', () => new TestComponent());
|
||||||
|
manager.registerPool('AnotherTestComponent', () => new AnotherTestComponent());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够预热所有池', () => {
|
||||||
|
manager.prewarmAll(5);
|
||||||
|
|
||||||
|
const stats = manager.getPoolStats();
|
||||||
|
expect(stats.get('TestComponent')?.available).toBe(5);
|
||||||
|
expect(stats.get('AnotherTestComponent')?.available).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够清空所有池', () => {
|
||||||
|
manager.prewarmAll(5);
|
||||||
|
manager.clearAll();
|
||||||
|
|
||||||
|
const stats = manager.getPoolStats();
|
||||||
|
expect(stats.get('TestComponent')?.available).toBe(0);
|
||||||
|
expect(stats.get('AnotherTestComponent')?.available).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prewarmAll应该使用默认值', () => {
|
||||||
|
manager.prewarmAll(); // 默认100
|
||||||
|
|
||||||
|
const stats = manager.getPoolStats();
|
||||||
|
expect(stats.get('TestComponent')?.available).toBe(100);
|
||||||
|
expect(stats.get('AnotherTestComponent')?.available).toBe(100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('统计信息', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
manager.registerPool('TestComponent', () => new TestComponent(), undefined, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取池统计信息', () => {
|
||||||
|
const stats = manager.getPoolStats();
|
||||||
|
|
||||||
|
expect(stats.has('TestComponent')).toBe(true);
|
||||||
|
const poolStat = stats.get('TestComponent')!;
|
||||||
|
expect(poolStat.available).toBe(0);
|
||||||
|
expect(poolStat.maxSize).toBe(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取池利用率信息', () => {
|
||||||
|
manager.prewarmAll(30); // 预热30个
|
||||||
|
|
||||||
|
const utilization = manager.getPoolUtilization();
|
||||||
|
const testComponentUtil = utilization.get('TestComponent')!;
|
||||||
|
|
||||||
|
expect(testComponentUtil.used).toBe(20); // 50 - 30 = 20
|
||||||
|
expect(testComponentUtil.total).toBe(50);
|
||||||
|
expect(testComponentUtil.utilization).toBe(40); // 20/50 * 100
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取指定组件的池利用率', () => {
|
||||||
|
manager.prewarmAll(30);
|
||||||
|
|
||||||
|
const utilization = manager.getComponentUtilization('TestComponent');
|
||||||
|
expect(utilization).toBe(40); // (50-30)/50 * 100
|
||||||
|
});
|
||||||
|
|
||||||
|
it('获取未注册组件的利用率应该返回0', () => {
|
||||||
|
const utilization = manager.getComponentUtilization('UnknownComponent');
|
||||||
|
expect(utilization).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('空池的利用率应该为0', () => {
|
||||||
|
// 完全重置管理器,移除所有池
|
||||||
|
manager.reset();
|
||||||
|
const utilization = manager.getComponentUtilization('TestComponent');
|
||||||
|
expect(utilization).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('动态使用场景测试', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
manager.registerPool('TestComponent', () => new TestComponent(), (comp) => comp.reset(), 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该正确跟踪组件使用情况', () => {
|
||||||
|
// 获取5个组件
|
||||||
|
const components = [];
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const comp = manager.acquireComponent<TestComponent>('TestComponent')!;
|
||||||
|
comp.value = i;
|
||||||
|
components.push(comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 利用率 = (maxSize - available) / maxSize * 100
|
||||||
|
// 获取了5个,池中应该没有可用的(因为是从空池开始),所以利用率是 10/10 * 100 = 100%
|
||||||
|
let utilization = manager.getComponentUtilization('TestComponent');
|
||||||
|
expect(utilization).toBe(100); // 10/10 * 100
|
||||||
|
|
||||||
|
// 释放3个组件
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
manager.releaseComponent('TestComponent', components[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 现在池中有3个可用,7个在使用
|
||||||
|
utilization = manager.getComponentUtilization('TestComponent');
|
||||||
|
expect(utilization).toBe(70); // 7/10 * 100
|
||||||
|
|
||||||
|
const stats = manager.getPoolStats();
|
||||||
|
expect(stats.get('TestComponent')?.available).toBe(3); // 池中有3个可用
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理池满的情况', () => {
|
||||||
|
// 预热到满容量
|
||||||
|
manager.prewarmAll(10);
|
||||||
|
|
||||||
|
// 获取所有组件
|
||||||
|
const components = [];
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
components.push(manager.acquireComponent<TestComponent>('TestComponent')!);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(manager.getComponentUtilization('TestComponent')).toBe(100);
|
||||||
|
|
||||||
|
// 尝试释放更多组件(超过容量)
|
||||||
|
for (let i = 0; i < 15; i++) {
|
||||||
|
const extraComp = new TestComponent();
|
||||||
|
manager.releaseComponent('TestComponent', extraComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 池应该仍然是满的,不会超过容量
|
||||||
|
const stats = manager.getPoolStats();
|
||||||
|
expect(stats.get('TestComponent')?.available).toBe(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('性能测试', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
manager.registerPool('TestComponent', () => new TestComponent(), (comp) => comp.reset());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('大量组件获取和释放应该高效', () => {
|
||||||
|
const startTime = performance.now();
|
||||||
|
const components = [];
|
||||||
|
|
||||||
|
// 获取1000个组件
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
components.push(manager.acquireComponent<TestComponent>('TestComponent')!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 释放所有组件
|
||||||
|
components.forEach(comp => manager.releaseComponent('TestComponent', comp));
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('边界情况和错误处理', () => {
|
||||||
|
it('空管理器的统计信息应该正确', () => {
|
||||||
|
// 完全重置管理器以确保清洁状态
|
||||||
|
const emptyManager = ComponentPoolManager.getInstance();
|
||||||
|
emptyManager.reset();
|
||||||
|
|
||||||
|
const stats = emptyManager.getPoolStats();
|
||||||
|
const utilization = emptyManager.getPoolUtilization();
|
||||||
|
|
||||||
|
expect(stats.size).toBe(0);
|
||||||
|
expect(utilization.size).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('重复注册同一组件类型应该覆盖之前的池', () => {
|
||||||
|
manager.registerPool('TestComponent', () => new TestComponent(), undefined, 10);
|
||||||
|
manager.registerPool('TestComponent', () => new TestComponent(), undefined, 20);
|
||||||
|
|
||||||
|
const stats = manager.getPoolStats();
|
||||||
|
expect(stats.get('TestComponent')?.maxSize).toBe(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('处理极端的预热数量', () => {
|
||||||
|
manager.registerPool('TestComponent', () => new TestComponent(), undefined, 5);
|
||||||
|
|
||||||
|
// 预热超过最大容量
|
||||||
|
manager.prewarmAll(100);
|
||||||
|
|
||||||
|
const stats = manager.getPoolStats();
|
||||||
|
expect(stats.get('TestComponent')?.available).toBe(5); // 不应该超过最大容量
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
552
tests/ECS/Utils/Bits.test.ts
Normal file
552
tests/ECS/Utils/Bits.test.ts
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
import { Bits } from '../../../src/ECS/Utils/Bits';
|
||||||
|
|
||||||
|
describe('Bits - 高性能位操作类测试', () => {
|
||||||
|
let bits: Bits;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
bits = new Bits();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('基本构造和初始化', () => {
|
||||||
|
it('应该能够创建空的Bits对象', () => {
|
||||||
|
expect(bits).toBeDefined();
|
||||||
|
expect(bits.isEmpty()).toBe(true);
|
||||||
|
expect(bits.getValue()).toBe(0n);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够使用初始值创建Bits对象', () => {
|
||||||
|
const bitsWithValue = new Bits(5n); // 二进制: 101
|
||||||
|
expect(bitsWithValue.getValue()).toBe(5n);
|
||||||
|
expect(bitsWithValue.isEmpty()).toBe(false);
|
||||||
|
expect(bitsWithValue.get(0)).toBe(true); // 第0位
|
||||||
|
expect(bitsWithValue.get(1)).toBe(false); // 第1位
|
||||||
|
expect(bitsWithValue.get(2)).toBe(true); // 第2位
|
||||||
|
});
|
||||||
|
|
||||||
|
it('默认构造函数应该创建值为0的对象', () => {
|
||||||
|
const defaultBits = new Bits();
|
||||||
|
expect(defaultBits.getValue()).toBe(0n);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('位设置和清除操作', () => {
|
||||||
|
it('应该能够设置指定位置的位', () => {
|
||||||
|
bits.set(0);
|
||||||
|
expect(bits.get(0)).toBe(true);
|
||||||
|
expect(bits.getValue()).toBe(1n);
|
||||||
|
|
||||||
|
bits.set(3);
|
||||||
|
expect(bits.get(3)).toBe(true);
|
||||||
|
expect(bits.getValue()).toBe(9n); // 1001 in binary
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够清除指定位置的位', () => {
|
||||||
|
bits.set(0);
|
||||||
|
bits.set(1);
|
||||||
|
bits.set(2);
|
||||||
|
expect(bits.getValue()).toBe(7n); // 111 in binary
|
||||||
|
|
||||||
|
bits.clear(1);
|
||||||
|
expect(bits.get(1)).toBe(false);
|
||||||
|
expect(bits.getValue()).toBe(5n); // 101 in binary
|
||||||
|
});
|
||||||
|
|
||||||
|
it('重复设置同一位应该保持不变', () => {
|
||||||
|
bits.set(0);
|
||||||
|
const value1 = bits.getValue();
|
||||||
|
bits.set(0);
|
||||||
|
const value2 = bits.getValue();
|
||||||
|
expect(value1).toBe(value2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('清除未设置的位应该安全', () => {
|
||||||
|
bits.clear(5);
|
||||||
|
expect(bits.getValue()).toBe(0n);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('设置负索引应该抛出错误', () => {
|
||||||
|
expect(() => {
|
||||||
|
bits.set(-1);
|
||||||
|
}).toThrow('Bit index cannot be negative');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('清除负索引应该抛出错误', () => {
|
||||||
|
expect(() => {
|
||||||
|
bits.clear(-1);
|
||||||
|
}).toThrow('Bit index cannot be negative');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('位获取操作', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
bits.set(0);
|
||||||
|
bits.set(2);
|
||||||
|
bits.set(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够正确获取设置的位', () => {
|
||||||
|
expect(bits.get(0)).toBe(true);
|
||||||
|
expect(bits.get(2)).toBe(true);
|
||||||
|
expect(bits.get(4)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够正确获取未设置的位', () => {
|
||||||
|
expect(bits.get(1)).toBe(false);
|
||||||
|
expect(bits.get(3)).toBe(false);
|
||||||
|
expect(bits.get(5)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('获取负索引应该返回false', () => {
|
||||||
|
expect(bits.get(-1)).toBe(false);
|
||||||
|
expect(bits.get(-10)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('获取超大索引应该正确处理', () => {
|
||||||
|
expect(bits.get(1000)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('位运算操作', () => {
|
||||||
|
let otherBits: Bits;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
bits.set(0);
|
||||||
|
bits.set(2);
|
||||||
|
bits.set(4); // 10101 in binary = 21
|
||||||
|
|
||||||
|
otherBits = new Bits();
|
||||||
|
otherBits.set(1);
|
||||||
|
otherBits.set(2);
|
||||||
|
otherBits.set(3); // 1110 in binary = 14
|
||||||
|
});
|
||||||
|
|
||||||
|
it('AND运算应该正确', () => {
|
||||||
|
const result = bits.and(otherBits);
|
||||||
|
expect(result.getValue()).toBe(4n); // 10101 & 01110 = 00100 = 4
|
||||||
|
expect(result.get(2)).toBe(true);
|
||||||
|
expect(result.get(0)).toBe(false);
|
||||||
|
expect(result.get(1)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('OR运算应该正确', () => {
|
||||||
|
const result = bits.or(otherBits);
|
||||||
|
expect(result.getValue()).toBe(31n); // 10101 | 01110 = 11111 = 31
|
||||||
|
expect(result.get(0)).toBe(true);
|
||||||
|
expect(result.get(1)).toBe(true);
|
||||||
|
expect(result.get(2)).toBe(true);
|
||||||
|
expect(result.get(3)).toBe(true);
|
||||||
|
expect(result.get(4)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('XOR运算应该正确', () => {
|
||||||
|
const result = bits.xor(otherBits);
|
||||||
|
expect(result.getValue()).toBe(27n); // 10101 ^ 01110 = 11011 = 27
|
||||||
|
expect(result.get(0)).toBe(true);
|
||||||
|
expect(result.get(1)).toBe(true);
|
||||||
|
expect(result.get(2)).toBe(false); // 相同位XOR为0
|
||||||
|
expect(result.get(3)).toBe(true);
|
||||||
|
expect(result.get(4)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('NOT运算应该正确', () => {
|
||||||
|
const simpleBits = new Bits(5n); // 101 in binary
|
||||||
|
const result = simpleBits.not(8); // 限制为8位
|
||||||
|
expect(result.getValue()).toBe(250n); // ~00000101 = 11111010 = 250 (8位)
|
||||||
|
});
|
||||||
|
|
||||||
|
it('NOT运算默认64位应该正确', () => {
|
||||||
|
const simpleBits = new Bits(1n);
|
||||||
|
const result = simpleBits.not();
|
||||||
|
const expected = (1n << 64n) - 2n; // 64位全1减去最低位
|
||||||
|
expect(result.getValue()).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('包含性检查', () => {
|
||||||
|
let otherBits: Bits;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
bits.set(0);
|
||||||
|
bits.set(2);
|
||||||
|
bits.set(4); // 10101
|
||||||
|
|
||||||
|
otherBits = new Bits();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('containsAll应该正确检查包含所有位', () => {
|
||||||
|
otherBits.set(0);
|
||||||
|
otherBits.set(2); // 101
|
||||||
|
expect(bits.containsAll(otherBits)).toBe(true);
|
||||||
|
|
||||||
|
otherBits.set(1); // 111
|
||||||
|
expect(bits.containsAll(otherBits)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('intersects应该正确检查交集', () => {
|
||||||
|
otherBits.set(1);
|
||||||
|
otherBits.set(3); // 1010
|
||||||
|
expect(bits.intersects(otherBits)).toBe(false);
|
||||||
|
|
||||||
|
otherBits.set(0); // 1011
|
||||||
|
expect(bits.intersects(otherBits)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('excludes应该正确检查互斥', () => {
|
||||||
|
otherBits.set(1);
|
||||||
|
otherBits.set(3); // 1010
|
||||||
|
expect(bits.excludes(otherBits)).toBe(true);
|
||||||
|
|
||||||
|
otherBits.set(0); // 1011
|
||||||
|
expect(bits.excludes(otherBits)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('空Bits对象的包含性检查', () => {
|
||||||
|
const emptyBits = new Bits();
|
||||||
|
expect(bits.containsAll(emptyBits)).toBe(true);
|
||||||
|
expect(bits.intersects(emptyBits)).toBe(false);
|
||||||
|
expect(bits.excludes(emptyBits)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('状态检查和计数', () => {
|
||||||
|
it('isEmpty应该正确检查空状态', () => {
|
||||||
|
expect(bits.isEmpty()).toBe(true);
|
||||||
|
|
||||||
|
bits.set(0);
|
||||||
|
expect(bits.isEmpty()).toBe(false);
|
||||||
|
|
||||||
|
bits.clear(0);
|
||||||
|
expect(bits.isEmpty()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cardinality应该正确计算设置的位数量', () => {
|
||||||
|
expect(bits.cardinality()).toBe(0);
|
||||||
|
|
||||||
|
bits.set(0);
|
||||||
|
expect(bits.cardinality()).toBe(1);
|
||||||
|
|
||||||
|
bits.set(2);
|
||||||
|
bits.set(4);
|
||||||
|
expect(bits.cardinality()).toBe(3);
|
||||||
|
|
||||||
|
bits.clear(2);
|
||||||
|
expect(bits.cardinality()).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('大数值的cardinality应该正确', () => {
|
||||||
|
// 设置很多位
|
||||||
|
for (let i = 0; i < 100; i += 2) {
|
||||||
|
bits.set(i);
|
||||||
|
}
|
||||||
|
expect(bits.cardinality()).toBe(50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('清空和重置操作', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
bits.set(0);
|
||||||
|
bits.set(1);
|
||||||
|
bits.set(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clearAll应该清空所有位', () => {
|
||||||
|
expect(bits.isEmpty()).toBe(false);
|
||||||
|
bits.clearAll();
|
||||||
|
expect(bits.isEmpty()).toBe(true);
|
||||||
|
expect(bits.getValue()).toBe(0n);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clearAll后应该能重新设置位', () => {
|
||||||
|
bits.clearAll();
|
||||||
|
bits.set(5);
|
||||||
|
expect(bits.get(5)).toBe(true);
|
||||||
|
expect(bits.cardinality()).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('复制和克隆操作', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
bits.set(1);
|
||||||
|
bits.set(3);
|
||||||
|
bits.set(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('copyFrom应该正确复制另一个Bits对象', () => {
|
||||||
|
const newBits = new Bits();
|
||||||
|
newBits.copyFrom(bits);
|
||||||
|
|
||||||
|
expect(newBits.getValue()).toBe(bits.getValue());
|
||||||
|
expect(newBits.equals(bits)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clone应该创建相同的副本', () => {
|
||||||
|
const clonedBits = bits.clone();
|
||||||
|
|
||||||
|
expect(clonedBits.getValue()).toBe(bits.getValue());
|
||||||
|
expect(clonedBits.equals(bits)).toBe(true);
|
||||||
|
expect(clonedBits).not.toBe(bits); // 应该是不同的对象
|
||||||
|
});
|
||||||
|
|
||||||
|
it('修改克隆对象不应该影响原对象', () => {
|
||||||
|
const clonedBits = bits.clone();
|
||||||
|
clonedBits.set(7);
|
||||||
|
|
||||||
|
expect(bits.get(7)).toBe(false);
|
||||||
|
expect(clonedBits.get(7)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('值操作', () => {
|
||||||
|
it('getValue和setValue应该正确工作', () => {
|
||||||
|
bits.setValue(42n);
|
||||||
|
expect(bits.getValue()).toBe(42n);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setValue应该正确反映在位操作中', () => {
|
||||||
|
bits.setValue(5n); // 101 in binary
|
||||||
|
expect(bits.get(0)).toBe(true);
|
||||||
|
expect(bits.get(1)).toBe(false);
|
||||||
|
expect(bits.get(2)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setValue为0应该清空所有位', () => {
|
||||||
|
bits.set(1);
|
||||||
|
bits.set(2);
|
||||||
|
bits.setValue(0n);
|
||||||
|
expect(bits.isEmpty()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('字符串表示和解析', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
bits.set(0);
|
||||||
|
bits.set(2);
|
||||||
|
bits.set(4); // 10101 = 21
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toString应该返回可读的位表示', () => {
|
||||||
|
const str = bits.toString();
|
||||||
|
expect(str).toBe('Bits[0, 2, 4]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('空Bits的toString应该正确', () => {
|
||||||
|
const emptyBits = new Bits();
|
||||||
|
expect(emptyBits.toString()).toBe('Bits[]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toBinaryString应该返回正确的二进制表示', () => {
|
||||||
|
const binaryStr = bits.toBinaryString(8);
|
||||||
|
expect(binaryStr).toBe('00010101');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toBinaryString应该正确处理空格分隔', () => {
|
||||||
|
const binaryStr = bits.toBinaryString(16);
|
||||||
|
expect(binaryStr).toBe('00000000 00010101');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toHexString应该返回正确的十六进制表示', () => {
|
||||||
|
const hexStr = bits.toHexString();
|
||||||
|
expect(hexStr).toBe('0x15'); // 21 in hex
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fromBinaryString应该正确解析', () => {
|
||||||
|
const parsedBits = Bits.fromBinaryString('10101');
|
||||||
|
expect(parsedBits.getValue()).toBe(21n);
|
||||||
|
expect(parsedBits.equals(bits)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fromBinaryString应该处理带空格的字符串', () => {
|
||||||
|
const parsedBits = Bits.fromBinaryString('0001 0101');
|
||||||
|
expect(parsedBits.getValue()).toBe(21n);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fromHexString应该正确解析', () => {
|
||||||
|
const parsedBits = Bits.fromHexString('0x15');
|
||||||
|
expect(parsedBits.getValue()).toBe(21n);
|
||||||
|
expect(parsedBits.equals(bits)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fromHexString应该处理不带0x前缀的字符串', () => {
|
||||||
|
const parsedBits = Bits.fromHexString('15');
|
||||||
|
expect(parsedBits.getValue()).toBe(21n);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('比较操作', () => {
|
||||||
|
let otherBits: Bits;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
bits.set(0);
|
||||||
|
bits.set(2);
|
||||||
|
|
||||||
|
otherBits = new Bits();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('equals应该正确比较相等的Bits', () => {
|
||||||
|
otherBits.set(0);
|
||||||
|
otherBits.set(2);
|
||||||
|
expect(bits.equals(otherBits)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('equals应该正确比较不相等的Bits', () => {
|
||||||
|
otherBits.set(0);
|
||||||
|
otherBits.set(1);
|
||||||
|
expect(bits.equals(otherBits)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('空Bits对象应该相等', () => {
|
||||||
|
const emptyBits1 = new Bits();
|
||||||
|
const emptyBits2 = new Bits();
|
||||||
|
expect(emptyBits1.equals(emptyBits2)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('索引查找操作', () => {
|
||||||
|
it('getHighestBitIndex应该返回最高设置位的索引', () => {
|
||||||
|
bits.set(0);
|
||||||
|
bits.set(5);
|
||||||
|
bits.set(10);
|
||||||
|
expect(bits.getHighestBitIndex()).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getLowestBitIndex应该返回最低设置位的索引', () => {
|
||||||
|
bits.set(3);
|
||||||
|
bits.set(7);
|
||||||
|
bits.set(1);
|
||||||
|
expect(bits.getLowestBitIndex()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('空Bits的索引查找应该返回-1', () => {
|
||||||
|
expect(bits.getHighestBitIndex()).toBe(-1);
|
||||||
|
expect(bits.getLowestBitIndex()).toBe(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('只有一个位设置时索引查找应该正确', () => {
|
||||||
|
bits.set(5);
|
||||||
|
expect(bits.getHighestBitIndex()).toBe(5);
|
||||||
|
expect(bits.getLowestBitIndex()).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('大数值处理', () => {
|
||||||
|
it('应该能够处理超过64位的数值', () => {
|
||||||
|
bits.set(100);
|
||||||
|
expect(bits.get(100)).toBe(true);
|
||||||
|
expect(bits.cardinality()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够处理非常大的位索引', () => {
|
||||||
|
bits.set(1000);
|
||||||
|
bits.set(2000);
|
||||||
|
expect(bits.get(1000)).toBe(true);
|
||||||
|
expect(bits.get(2000)).toBe(true);
|
||||||
|
expect(bits.cardinality()).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('大数值的位运算应该正确', () => {
|
||||||
|
const largeBits1 = new Bits();
|
||||||
|
const largeBits2 = new Bits();
|
||||||
|
|
||||||
|
largeBits1.set(100);
|
||||||
|
largeBits1.set(200);
|
||||||
|
|
||||||
|
largeBits2.set(100);
|
||||||
|
largeBits2.set(150);
|
||||||
|
|
||||||
|
const result = largeBits1.and(largeBits2);
|
||||||
|
expect(result.get(100)).toBe(true);
|
||||||
|
expect(result.get(150)).toBe(false);
|
||||||
|
expect(result.get(200)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('性能测试', () => {
|
||||||
|
it('大量位设置操作应该高效', () => {
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
bits.set(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
expect(endTime - startTime).toBeLessThan(1000); // 应该在1秒内完成
|
||||||
|
expect(bits.cardinality()).toBe(10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('大量位查询操作应该高效', () => {
|
||||||
|
// 先设置一些位
|
||||||
|
for (let i = 0; i < 1000; i += 2) {
|
||||||
|
bits.set(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
let trueCount = 0;
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
if (bits.get(i)) {
|
||||||
|
trueCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||||
|
expect(trueCount).toBe(500); // 500个偶数位
|
||||||
|
});
|
||||||
|
|
||||||
|
it('位运算操作应该高效', () => {
|
||||||
|
const otherBits = new Bits();
|
||||||
|
|
||||||
|
// 设置一些位
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
bits.set(i * 2);
|
||||||
|
otherBits.set(i * 2 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
const result = bits.or(otherBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('边界情况和错误处理', () => {
|
||||||
|
it('应该处理0值的各种操作', () => {
|
||||||
|
const zeroBits = new Bits(0n);
|
||||||
|
expect(zeroBits.isEmpty()).toBe(true);
|
||||||
|
expect(zeroBits.cardinality()).toBe(0);
|
||||||
|
expect(zeroBits.getHighestBitIndex()).toBe(-1);
|
||||||
|
expect(zeroBits.getLowestBitIndex()).toBe(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理最大BigInt值', () => {
|
||||||
|
const maxBits = new Bits(BigInt(Number.MAX_SAFE_INTEGER));
|
||||||
|
expect(maxBits.isEmpty()).toBe(false);
|
||||||
|
expect(maxBits.cardinality()).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('位操作的结果应该是新对象', () => {
|
||||||
|
bits.set(0);
|
||||||
|
const otherBits = new Bits();
|
||||||
|
otherBits.set(1);
|
||||||
|
|
||||||
|
const result = bits.or(otherBits);
|
||||||
|
expect(result).not.toBe(bits);
|
||||||
|
expect(result).not.toBe(otherBits);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('连续的设置和清除操作应该正确', () => {
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
bits.set(i);
|
||||||
|
expect(bits.get(i)).toBe(true);
|
||||||
|
bits.clear(i);
|
||||||
|
expect(bits.get(i)).toBe(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(bits.isEmpty()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
190
tests/Utils/Extensions/NumberExtension.test.ts
Normal file
190
tests/Utils/Extensions/NumberExtension.test.ts
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import { NumberExtension } from '../../../src/Utils/Extensions/NumberExtension';
|
||||||
|
|
||||||
|
describe('NumberExtension - 数字扩展工具类测试', () => {
|
||||||
|
describe('toNumber 方法测试', () => {
|
||||||
|
it('应该能够转换数字类型', () => {
|
||||||
|
expect(NumberExtension.toNumber(42)).toBe(42);
|
||||||
|
expect(NumberExtension.toNumber(0)).toBe(0);
|
||||||
|
expect(NumberExtension.toNumber(-42)).toBe(-42);
|
||||||
|
expect(NumberExtension.toNumber(3.14)).toBe(3.14);
|
||||||
|
expect(NumberExtension.toNumber(-3.14)).toBe(-3.14);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够转换字符串数字', () => {
|
||||||
|
expect(NumberExtension.toNumber('42')).toBe(42);
|
||||||
|
expect(NumberExtension.toNumber('0')).toBe(0);
|
||||||
|
expect(NumberExtension.toNumber('-42')).toBe(-42);
|
||||||
|
expect(NumberExtension.toNumber('3.14')).toBe(3.14);
|
||||||
|
expect(NumberExtension.toNumber('-3.14')).toBe(-3.14);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够转换科学计数法字符串', () => {
|
||||||
|
expect(NumberExtension.toNumber('1e5')).toBe(100000);
|
||||||
|
expect(NumberExtension.toNumber('1.5e2')).toBe(150);
|
||||||
|
expect(NumberExtension.toNumber('2e-3')).toBe(0.002);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够转换十六进制字符串', () => {
|
||||||
|
expect(NumberExtension.toNumber('0xFF')).toBe(255);
|
||||||
|
expect(NumberExtension.toNumber('0x10')).toBe(16);
|
||||||
|
expect(NumberExtension.toNumber('0x0')).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够转换布尔值', () => {
|
||||||
|
expect(NumberExtension.toNumber(true)).toBe(1);
|
||||||
|
expect(NumberExtension.toNumber(false)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('undefined 和 null 应该返回0', () => {
|
||||||
|
expect(NumberExtension.toNumber(undefined)).toBe(0);
|
||||||
|
expect(NumberExtension.toNumber(null)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够处理空字符串和空白字符串', () => {
|
||||||
|
expect(NumberExtension.toNumber('')).toBe(0);
|
||||||
|
expect(NumberExtension.toNumber(' ')).toBe(0);
|
||||||
|
expect(NumberExtension.toNumber('\t')).toBe(0);
|
||||||
|
expect(NumberExtension.toNumber('\n')).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('无效的字符串应该返回NaN', () => {
|
||||||
|
expect(Number.isNaN(NumberExtension.toNumber('abc'))).toBe(true);
|
||||||
|
expect(Number.isNaN(NumberExtension.toNumber('hello'))).toBe(true);
|
||||||
|
expect(Number.isNaN(NumberExtension.toNumber('12abc'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够转换数组(第一个元素)', () => {
|
||||||
|
expect(NumberExtension.toNumber([42])).toBe(42);
|
||||||
|
expect(NumberExtension.toNumber(['42'])).toBe(42);
|
||||||
|
expect(NumberExtension.toNumber([])).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够转换Date对象(时间戳)', () => {
|
||||||
|
const date = new Date(2023, 0, 1);
|
||||||
|
const timestamp = date.getTime();
|
||||||
|
expect(NumberExtension.toNumber(date)).toBe(timestamp);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够处理BigInt转换', () => {
|
||||||
|
expect(NumberExtension.toNumber(BigInt(42))).toBe(42);
|
||||||
|
expect(NumberExtension.toNumber(BigInt(0))).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够处理Infinity和-Infinity', () => {
|
||||||
|
expect(NumberExtension.toNumber(Infinity)).toBe(Infinity);
|
||||||
|
expect(NumberExtension.toNumber(-Infinity)).toBe(-Infinity);
|
||||||
|
expect(NumberExtension.toNumber('Infinity')).toBe(Infinity);
|
||||||
|
expect(NumberExtension.toNumber('-Infinity')).toBe(-Infinity);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('对象转换应该调用valueOf或toString', () => {
|
||||||
|
const objWithValueOf = {
|
||||||
|
valueOf: () => 42
|
||||||
|
};
|
||||||
|
expect(NumberExtension.toNumber(objWithValueOf)).toBe(42);
|
||||||
|
|
||||||
|
const objWithToString = {
|
||||||
|
toString: () => '123'
|
||||||
|
};
|
||||||
|
expect(NumberExtension.toNumber(objWithToString)).toBe(123);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('复杂对象应该返回NaN', () => {
|
||||||
|
const complexObj = { a: 1, b: 2 };
|
||||||
|
expect(Number.isNaN(NumberExtension.toNumber(complexObj))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Symbol转换应该抛出错误', () => {
|
||||||
|
expect(() => {
|
||||||
|
NumberExtension.toNumber(Symbol('test'));
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理特殊数值', () => {
|
||||||
|
expect(NumberExtension.toNumber(Number.MAX_VALUE)).toBe(Number.MAX_VALUE);
|
||||||
|
expect(NumberExtension.toNumber(Number.MIN_VALUE)).toBe(Number.MIN_VALUE);
|
||||||
|
expect(NumberExtension.toNumber(Number.MAX_SAFE_INTEGER)).toBe(Number.MAX_SAFE_INTEGER);
|
||||||
|
expect(NumberExtension.toNumber(Number.MIN_SAFE_INTEGER)).toBe(Number.MIN_SAFE_INTEGER);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理parseFloat可解析的字符串', () => {
|
||||||
|
// NumberExtension.toNumber使用Number(),不支持parseFloat的部分解析
|
||||||
|
expect(Number.isNaN(NumberExtension.toNumber('42.5px'))).toBe(true);
|
||||||
|
expect(Number.isNaN(NumberExtension.toNumber('100%'))).toBe(true);
|
||||||
|
expect(Number.isNaN(NumberExtension.toNumber('3.14em'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('边界情况测试', () => {
|
||||||
|
// 非常大的数字
|
||||||
|
expect(NumberExtension.toNumber('1e308')).toBe(1e308);
|
||||||
|
|
||||||
|
// 非常小的数字
|
||||||
|
expect(NumberExtension.toNumber('1e-308')).toBe(1e-308);
|
||||||
|
|
||||||
|
// 精度问题
|
||||||
|
expect(NumberExtension.toNumber('0.1')).toBe(0.1);
|
||||||
|
expect(NumberExtension.toNumber('0.2')).toBe(0.2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理带符号的字符串', () => {
|
||||||
|
expect(NumberExtension.toNumber('+42')).toBe(42);
|
||||||
|
expect(NumberExtension.toNumber('+3.14')).toBe(3.14);
|
||||||
|
expect(NumberExtension.toNumber('-0')).toBe(-0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理八进制字符串(不推荐使用)', () => {
|
||||||
|
// 注意:现代JavaScript中八进制字面量是不推荐的
|
||||||
|
expect(NumberExtension.toNumber('010')).toBe(10); // 被当作十进制处理
|
||||||
|
});
|
||||||
|
|
||||||
|
it('性能测试 - 大量转换应该高效', () => {
|
||||||
|
const testValues = [42, '123', true, null, undefined, '3.14'];
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
testValues.forEach(value => {
|
||||||
|
NumberExtension.toNumber(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('类型兼容性测试', () => {
|
||||||
|
it('应该与Number()函数行为一致', () => {
|
||||||
|
const testValues = [
|
||||||
|
42, '42', true, false, '', ' ',
|
||||||
|
'3.14', 'abc', [], [42], {}, Infinity, -Infinity
|
||||||
|
];
|
||||||
|
|
||||||
|
testValues.forEach(value => {
|
||||||
|
const extensionResult = NumberExtension.toNumber(value);
|
||||||
|
const nativeResult = Number(value);
|
||||||
|
|
||||||
|
if (Number.isNaN(extensionResult) && Number.isNaN(nativeResult)) {
|
||||||
|
// 两个都是NaN,认为相等
|
||||||
|
expect(true).toBe(true);
|
||||||
|
} else {
|
||||||
|
expect(extensionResult).toBe(nativeResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 特殊处理null和undefined的情况
|
||||||
|
expect(NumberExtension.toNumber(null)).toBe(0);
|
||||||
|
expect(NumberExtension.toNumber(undefined)).toBe(0);
|
||||||
|
expect(Number(null)).toBe(0);
|
||||||
|
expect(Number(undefined)).toBeNaN();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该正确处理特殊的相等性', () => {
|
||||||
|
// -0 和 +0
|
||||||
|
expect(Object.is(NumberExtension.toNumber('-0'), -0)).toBe(true);
|
||||||
|
expect(Object.is(NumberExtension.toNumber('+0'), +0)).toBe(true);
|
||||||
|
|
||||||
|
// NaN
|
||||||
|
expect(Number.isNaN(NumberExtension.toNumber('notANumber'))).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
296
tests/Utils/Extensions/TypeUtils.test.ts
Normal file
296
tests/Utils/Extensions/TypeUtils.test.ts
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
import { TypeUtils } from '../../../src/Utils/Extensions/TypeUtils';
|
||||||
|
|
||||||
|
describe('TypeUtils - 类型工具类测试', () => {
|
||||||
|
// 测试用的类和对象
|
||||||
|
class TestClass {
|
||||||
|
constructor(public value: number = 0) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnotherTestClass {
|
||||||
|
constructor(public name: string = '') {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestFunction() {
|
||||||
|
// @ts-ignore
|
||||||
|
this.prop = 'test';
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('getType 方法测试', () => {
|
||||||
|
it('应该能够获取基本类型对象的构造函数', () => {
|
||||||
|
expect(TypeUtils.getType(42)).toBe(Number);
|
||||||
|
expect(TypeUtils.getType('hello')).toBe(String);
|
||||||
|
expect(TypeUtils.getType(true)).toBe(Boolean);
|
||||||
|
expect(TypeUtils.getType(false)).toBe(Boolean);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取数组的构造函数', () => {
|
||||||
|
expect(TypeUtils.getType([])).toBe(Array);
|
||||||
|
expect(TypeUtils.getType([1, 2, 3])).toBe(Array);
|
||||||
|
expect(TypeUtils.getType(new Array())).toBe(Array);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取对象的构造函数', () => {
|
||||||
|
expect(TypeUtils.getType({})).toBe(Object);
|
||||||
|
expect(TypeUtils.getType(new Object())).toBe(Object);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取Date对象的构造函数', () => {
|
||||||
|
expect(TypeUtils.getType(new Date())).toBe(Date);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取RegExp对象的构造函数', () => {
|
||||||
|
expect(TypeUtils.getType(/test/)).toBe(RegExp);
|
||||||
|
expect(TypeUtils.getType(new RegExp('test'))).toBe(RegExp);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取Error对象的构造函数', () => {
|
||||||
|
expect(TypeUtils.getType(new Error())).toBe(Error);
|
||||||
|
expect(TypeUtils.getType(new TypeError())).toBe(TypeError);
|
||||||
|
expect(TypeUtils.getType(new ReferenceError())).toBe(ReferenceError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取Map和Set的构造函数', () => {
|
||||||
|
expect(TypeUtils.getType(new Map())).toBe(Map);
|
||||||
|
expect(TypeUtils.getType(new Set())).toBe(Set);
|
||||||
|
expect(TypeUtils.getType(new WeakMap())).toBe(WeakMap);
|
||||||
|
expect(TypeUtils.getType(new WeakSet())).toBe(WeakSet);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取Promise的构造函数', () => {
|
||||||
|
const promise = Promise.resolve(42);
|
||||||
|
expect(TypeUtils.getType(promise)).toBe(Promise);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取自定义类实例的构造函数', () => {
|
||||||
|
const instance = new TestClass(42);
|
||||||
|
expect(TypeUtils.getType(instance)).toBe(TestClass);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够区分不同的自定义类', () => {
|
||||||
|
const testInstance = new TestClass(42);
|
||||||
|
const anotherInstance = new AnotherTestClass('test');
|
||||||
|
|
||||||
|
expect(TypeUtils.getType(testInstance)).toBe(TestClass);
|
||||||
|
expect(TypeUtils.getType(anotherInstance)).toBe(AnotherTestClass);
|
||||||
|
expect(TypeUtils.getType(testInstance)).not.toBe(AnotherTestClass);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取函数构造的对象的构造函数', () => {
|
||||||
|
// @ts-ignore
|
||||||
|
const instance = new TestFunction();
|
||||||
|
expect(TypeUtils.getType(instance)).toBe(TestFunction);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取内置类型包装器的构造函数', () => {
|
||||||
|
expect(TypeUtils.getType(new Number(42))).toBe(Number);
|
||||||
|
expect(TypeUtils.getType(new String('hello'))).toBe(String);
|
||||||
|
expect(TypeUtils.getType(new Boolean(true))).toBe(Boolean);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取Symbol的构造函数', () => {
|
||||||
|
const sym = Symbol('test');
|
||||||
|
expect(TypeUtils.getType(sym)).toBe(Symbol);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取BigInt的构造函数', () => {
|
||||||
|
const bigInt = BigInt(42);
|
||||||
|
expect(TypeUtils.getType(bigInt)).toBe(BigInt);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理具有修改过构造函数的对象', () => {
|
||||||
|
const obj = {};
|
||||||
|
// @ts-ignore - 测试边界情况
|
||||||
|
obj.constructor = TestClass;
|
||||||
|
expect(TypeUtils.getType(obj)).toBe(TestClass);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理继承关系', () => {
|
||||||
|
class Parent {
|
||||||
|
constructor(public value: number = 0) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
constructor(value: number = 0, public name: string = '') {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const childInstance = new Child(42, 'test');
|
||||||
|
expect(TypeUtils.getType(childInstance)).toBe(Child);
|
||||||
|
expect(TypeUtils.getType(childInstance)).not.toBe(Parent);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理原型链修改的情况', () => {
|
||||||
|
class Original {}
|
||||||
|
class Modified {}
|
||||||
|
|
||||||
|
const instance = new Original();
|
||||||
|
// 修改原型链
|
||||||
|
Object.setPrototypeOf(instance, Modified.prototype);
|
||||||
|
|
||||||
|
// 构造函数属性应该仍然指向Modified
|
||||||
|
expect(TypeUtils.getType(instance)).toBe(Modified);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理null原型的对象', () => {
|
||||||
|
const nullProtoObj = Object.create(null);
|
||||||
|
// 没有constructor属性的对象会返回undefined
|
||||||
|
const result = TypeUtils.getType(nullProtoObj);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理ArrayBuffer和TypedArray', () => {
|
||||||
|
expect(TypeUtils.getType(new ArrayBuffer(8))).toBe(ArrayBuffer);
|
||||||
|
expect(TypeUtils.getType(new Uint8Array(8))).toBe(Uint8Array);
|
||||||
|
expect(TypeUtils.getType(new Int32Array(8))).toBe(Int32Array);
|
||||||
|
expect(TypeUtils.getType(new Float64Array(8))).toBe(Float64Array);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理生成器函数和生成器对象', () => {
|
||||||
|
function* generatorFunction() {
|
||||||
|
yield 1;
|
||||||
|
yield 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const generator = generatorFunction();
|
||||||
|
expect(TypeUtils.getType(generator)).toBe(Object.getPrototypeOf(generator).constructor);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理async函数返回的Promise', () => {
|
||||||
|
async function asyncFunction() {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
const asyncResult = asyncFunction();
|
||||||
|
expect(TypeUtils.getType(asyncResult)).toBe(Promise);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('边界情况和错误处理', () => {
|
||||||
|
it('应该处理undefined输入', () => {
|
||||||
|
expect(() => {
|
||||||
|
TypeUtils.getType(undefined);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理null输入', () => {
|
||||||
|
expect(() => {
|
||||||
|
TypeUtils.getType(null);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理构造函数被删除的对象', () => {
|
||||||
|
const obj = new TestClass();
|
||||||
|
// @ts-ignore - 测试边界情况
|
||||||
|
delete obj.constructor;
|
||||||
|
|
||||||
|
// 应该回退到原型链上的constructor
|
||||||
|
expect(TypeUtils.getType(obj)).toBe(TestClass);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该处理constructor属性被重写为非函数的情况', () => {
|
||||||
|
const obj = {};
|
||||||
|
// @ts-ignore - 测试边界情况
|
||||||
|
obj.constructor = 'not a function';
|
||||||
|
|
||||||
|
expect(TypeUtils.getType(obj)).toBe('not a function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('性能测试', () => {
|
||||||
|
it('大量类型获取应该高效', () => {
|
||||||
|
const testObjects = [
|
||||||
|
42, 'string', true, [], {}, new Date(), new TestClass(),
|
||||||
|
new Map(), new Set(), Symbol('test'), BigInt(42)
|
||||||
|
];
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
testObjects.forEach(obj => {
|
||||||
|
TypeUtils.getType(obj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('实际使用场景测试', () => {
|
||||||
|
it('应该能够用于类型检查', () => {
|
||||||
|
function isInstanceOf(obj: any, Constructor: any): boolean {
|
||||||
|
return TypeUtils.getType(obj) === Constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(isInstanceOf(42, Number)).toBe(true);
|
||||||
|
expect(isInstanceOf('hello', String)).toBe(true);
|
||||||
|
expect(isInstanceOf([], Array)).toBe(true);
|
||||||
|
expect(isInstanceOf(new TestClass(), TestClass)).toBe(true);
|
||||||
|
expect(isInstanceOf(new TestClass(), AnotherTestClass)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够用于多态类型识别', () => {
|
||||||
|
class Animal {
|
||||||
|
constructor(public name: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Dog extends Animal {
|
||||||
|
constructor(name: string, public breed: string) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cat extends Animal {
|
||||||
|
constructor(name: string, public color: string) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const animals = [
|
||||||
|
new Dog('Buddy', 'Golden Retriever'),
|
||||||
|
new Cat('Whiskers', 'Orange'),
|
||||||
|
new Animal('Generic')
|
||||||
|
];
|
||||||
|
|
||||||
|
const types = animals.map(animal => TypeUtils.getType(animal));
|
||||||
|
expect(types).toEqual([Dog, Cat, Animal]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够用于工厂模式', () => {
|
||||||
|
class ComponentFactory {
|
||||||
|
static create(type: any, ...args: any[]) {
|
||||||
|
return new type(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getTypeName(instance: any): string {
|
||||||
|
return TypeUtils.getType(instance).name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testInstance = ComponentFactory.create(TestClass, 42);
|
||||||
|
expect(testInstance).toBeInstanceOf(TestClass);
|
||||||
|
expect(testInstance.value).toBe(42);
|
||||||
|
expect(ComponentFactory.getTypeName(testInstance)).toBe('TestClass');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够用于序列化/反序列化的类型信息', () => {
|
||||||
|
function serialize(obj: any): string {
|
||||||
|
return JSON.stringify({
|
||||||
|
type: TypeUtils.getType(obj).name,
|
||||||
|
data: obj
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeFromSerialized(serialized: string): string {
|
||||||
|
return JSON.parse(serialized).type;
|
||||||
|
}
|
||||||
|
|
||||||
|
const testObj = new TestClass(42);
|
||||||
|
const serialized = serialize(testObj);
|
||||||
|
const typeName = getTypeFromSerialized(serialized);
|
||||||
|
|
||||||
|
expect(typeName).toBe('TestClass');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
309
tests/Utils/Timers/Timer.test.ts
Normal file
309
tests/Utils/Timers/Timer.test.ts
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
import { Timer } from '../../../src/Utils/Timers/Timer';
|
||||||
|
import { ITimer } from '../../../src/Utils/Timers/ITimer';
|
||||||
|
import { Time } from '../../../src/Utils/Time';
|
||||||
|
|
||||||
|
// Mock Time.deltaTime
|
||||||
|
jest.mock('../../../src/Utils/Time', () => ({
|
||||||
|
Time: {
|
||||||
|
deltaTime: 0.016 // 默认16ms,约60FPS
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Timer - 定时器测试', () => {
|
||||||
|
let timer: Timer;
|
||||||
|
let mockCallback: jest.Mock;
|
||||||
|
let mockContext: any;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
timer = new Timer();
|
||||||
|
mockCallback = jest.fn();
|
||||||
|
mockContext = { id: 'test-context' };
|
||||||
|
|
||||||
|
// 重置deltaTime
|
||||||
|
(Time as any).deltaTime = 0.016;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
timer.unload();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('基本初始化和属性', () => {
|
||||||
|
it('应该能够创建定时器实例', () => {
|
||||||
|
expect(timer).toBeInstanceOf(Timer);
|
||||||
|
expect(timer.isDone).toBe(false);
|
||||||
|
expect(timer.elapsedTime).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够初始化定时器', () => {
|
||||||
|
timer.initialize(1.0, false, mockContext, mockCallback);
|
||||||
|
|
||||||
|
expect(timer.context).toBe(mockContext);
|
||||||
|
expect(timer._timeInSeconds).toBe(1.0);
|
||||||
|
expect(timer._repeats).toBe(false);
|
||||||
|
expect(timer._onTime).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该能够获取泛型上下文', () => {
|
||||||
|
interface TestContext {
|
||||||
|
id: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const testContext: TestContext = { id: 'test', value: 42 };
|
||||||
|
timer.initialize(1.0, false, testContext, mockCallback);
|
||||||
|
|
||||||
|
const context = timer.getContext<TestContext>();
|
||||||
|
expect(context.id).toBe('test');
|
||||||
|
expect(context.value).toBe(42);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('定时器tick逻辑', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
timer.initialize(1.0, false, mockContext, mockCallback);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该正确累加经过时间', () => {
|
||||||
|
(Time as any).deltaTime = 0.5;
|
||||||
|
timer.tick(); // 先累加时间
|
||||||
|
expect(timer.elapsedTime).toBe(0.5);
|
||||||
|
expect(mockCallback).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('当经过时间超过目标时间时应该触发回调', () => {
|
||||||
|
// 第一次tick:累加时间到1.1,但还没检查触发条件
|
||||||
|
(Time as any).deltaTime = 1.1;
|
||||||
|
timer.tick();
|
||||||
|
expect(timer.elapsedTime).toBe(1.1);
|
||||||
|
expect(mockCallback).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// 第二次tick:检查条件并触发
|
||||||
|
(Time as any).deltaTime = 0.1;
|
||||||
|
timer.tick();
|
||||||
|
expect(mockCallback).toHaveBeenCalledWith(timer);
|
||||||
|
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||||
|
expect(timer.isDone).toBe(true); // 非重复定时器应该完成
|
||||||
|
});
|
||||||
|
|
||||||
|
it('应该在触发后调整剩余时间', () => {
|
||||||
|
// 第一次累加到1.5
|
||||||
|
(Time as any).deltaTime = 1.5;
|
||||||
|
timer.tick();
|
||||||
|
expect(timer.elapsedTime).toBe(1.5);
|
||||||
|
|
||||||
|
// 第二次检查并触发:1.5 - 1.0 = 0.5,然后加上当前的deltaTime
|
||||||
|
(Time as any).deltaTime = 0.3;
|
||||||
|
timer.tick();
|
||||||
|
expect(timer.elapsedTime).toBe(0.8); // 0.5 + 0.3
|
||||||
|
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('重复定时器', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
timer.initialize(1.0, true, mockContext, mockCallback);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('重复定时器不应该自动标记为完成', () => {
|
||||||
|
// 累加时间超过目标
|
||||||
|
(Time as any).deltaTime = 1.1;
|
||||||
|
timer.tick();
|
||||||
|
|
||||||
|
// 检查并触发,但不应该标记为完成
|
||||||
|
(Time as any).deltaTime = 0.1;
|
||||||
|
const isDone = timer.tick();
|
||||||
|
|
||||||
|
expect(isDone).toBe(false);
|
||||||
|
expect(timer.isDone).toBe(false);
|
||||||
|
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('重复定时器可以多次触发', () => {
|
||||||
|
// 第一次触发
|
||||||
|
(Time as any).deltaTime = 1.1;
|
||||||
|
timer.tick(); // 累加时间
|
||||||
|
(Time as any).deltaTime = 0.1;
|
||||||
|
timer.tick(); // 触发
|
||||||
|
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// 第二次触发 - 从剩余的0.1开始
|
||||||
|
(Time as any).deltaTime = 0.9; // 0.1 + 0.9 = 1.0
|
||||||
|
timer.tick(); // 累加到1.0
|
||||||
|
(Time as any).deltaTime = 0.1;
|
||||||
|
timer.tick(); // 检查并触发
|
||||||
|
expect(mockCallback).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('定时器控制方法', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
timer.initialize(1.0, false, mockContext, mockCallback);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reset应该重置经过时间', () => {
|
||||||
|
(Time as any).deltaTime = 0.5;
|
||||||
|
timer.tick();
|
||||||
|
expect(timer.elapsedTime).toBe(0.5);
|
||||||
|
|
||||||
|
timer.reset();
|
||||||
|
expect(timer.elapsedTime).toBe(0);
|
||||||
|
expect(timer.isDone).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stop应该标记定时器为完成', () => {
|
||||||
|
timer.stop();
|
||||||
|
expect(timer.isDone).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('停止的定时器不应该触发回调', () => {
|
||||||
|
timer.stop();
|
||||||
|
(Time as any).deltaTime = 2.0;
|
||||||
|
const isDone = timer.tick();
|
||||||
|
|
||||||
|
expect(isDone).toBe(true);
|
||||||
|
expect(mockCallback).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('上下文绑定', () => {
|
||||||
|
it('回调应该正确绑定到上下文', () => {
|
||||||
|
let contextValue = 0;
|
||||||
|
const testContext = {
|
||||||
|
value: 42,
|
||||||
|
callback: function(this: any, timer: ITimer) {
|
||||||
|
contextValue = this.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
timer.initialize(1.0, false, testContext, testContext.callback);
|
||||||
|
|
||||||
|
// 触发定时器
|
||||||
|
(Time as any).deltaTime = 1.1;
|
||||||
|
timer.tick(); // 累加时间
|
||||||
|
(Time as any).deltaTime = 0.1;
|
||||||
|
timer.tick(); // 触发
|
||||||
|
|
||||||
|
expect(contextValue).toBe(42);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('内存管理', () => {
|
||||||
|
it('unload应该清空对象引用', () => {
|
||||||
|
timer.initialize(1.0, false, mockContext, mockCallback);
|
||||||
|
|
||||||
|
timer.unload();
|
||||||
|
|
||||||
|
expect(timer.context).toBeNull();
|
||||||
|
expect(timer._onTime).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('边界情况', () => {
|
||||||
|
it('零秒定时器应该立即触发', () => {
|
||||||
|
timer.initialize(0, false, mockContext, mockCallback);
|
||||||
|
|
||||||
|
// 第一次累加时间
|
||||||
|
(Time as any).deltaTime = 0.001;
|
||||||
|
timer.tick();
|
||||||
|
expect(timer.elapsedTime).toBe(0.001);
|
||||||
|
|
||||||
|
// 第二次检查并触发(elapsedTime > 0)
|
||||||
|
timer.tick();
|
||||||
|
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('负数时间定时器应该立即触发', () => {
|
||||||
|
timer.initialize(-1, false, mockContext, mockCallback);
|
||||||
|
|
||||||
|
(Time as any).deltaTime = 0.001;
|
||||||
|
timer.tick(); // 累加时间,elapsedTime = 0.001 > -1
|
||||||
|
timer.tick(); // 检查并触发
|
||||||
|
|
||||||
|
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('极小deltaTime应该正确累积', () => {
|
||||||
|
timer.initialize(0.1, false, mockContext, mockCallback);
|
||||||
|
(Time as any).deltaTime = 0.05;
|
||||||
|
|
||||||
|
// 第一次不触发
|
||||||
|
timer.tick();
|
||||||
|
expect(mockCallback).not.toHaveBeenCalled();
|
||||||
|
expect(timer.elapsedTime).toBe(0.05);
|
||||||
|
|
||||||
|
// 第二次累加到0.1,但条件是 > 0.1 才触发,所以不触发
|
||||||
|
timer.tick();
|
||||||
|
expect(timer.elapsedTime).toBe(0.1);
|
||||||
|
expect(mockCallback).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// 第三次累加到0.11,但在检查之前elapsedTime还是0.1,所以不触发
|
||||||
|
(Time as any).deltaTime = 0.01; // 0.1 + 0.01 = 0.11 > 0.1
|
||||||
|
timer.tick();
|
||||||
|
expect(timer.elapsedTime).toBe(0.11);
|
||||||
|
expect(mockCallback).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// 第四次检查并触发(elapsedTime = 0.11 > 0.1)
|
||||||
|
(Time as any).deltaTime = 0.01; // 保持相同的deltaTime
|
||||||
|
timer.tick();
|
||||||
|
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('性能测试', () => {
|
||||||
|
it('大量tick调用应该高效', () => {
|
||||||
|
timer.initialize(1000, false, mockContext, mockCallback);
|
||||||
|
(Time as any).deltaTime = 0.016;
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
timer.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
expect(endTime - startTime).toBeLessThan(100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('实际使用场景', () => {
|
||||||
|
it('延迟执行功能', () => {
|
||||||
|
let executed = false;
|
||||||
|
|
||||||
|
timer.initialize(1.0, false, null, () => {
|
||||||
|
executed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 累加时间但不触发
|
||||||
|
(Time as any).deltaTime = 0.9;
|
||||||
|
timer.tick();
|
||||||
|
expect(executed).toBe(false);
|
||||||
|
|
||||||
|
// 继续累加到超过目标时间
|
||||||
|
(Time as any).deltaTime = 0.2; // 总共1.1 > 1.0
|
||||||
|
timer.tick();
|
||||||
|
expect(executed).toBe(false); // 还没检查触发条件
|
||||||
|
|
||||||
|
// 下一次tick才会检查并触发
|
||||||
|
timer.tick();
|
||||||
|
expect(executed).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('重复任务执行', () => {
|
||||||
|
let counter = 0;
|
||||||
|
timer.initialize(0.5, true, null, () => {
|
||||||
|
counter++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第一次触发 - 需要超过0.5
|
||||||
|
(Time as any).deltaTime = 0.6;
|
||||||
|
timer.tick(); // 累加到0.6,检查 0.6 > 0.5,触发,剩余0.1
|
||||||
|
timer.tick(); // 再加0.6变成0.7,检查 0.1 <= 0.5不触发,最后加0.6变成0.7
|
||||||
|
expect(counter).toBe(1);
|
||||||
|
|
||||||
|
// 第二次触发条件
|
||||||
|
(Time as any).deltaTime = 0.3; // 0.7 + 0.3 = 1.0 > 0.5 应该触发
|
||||||
|
timer.tick(); // 检查并触发第二次
|
||||||
|
expect(counter).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user