新增protobuf依赖(为网络和序列化做准备)
更新readme
This commit is contained in:
278
tests/Utils/Serialization/ProtobufDecorators.test.ts
Normal file
278
tests/Utils/Serialization/ProtobufDecorators.test.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* Protobuf装饰器测试
|
||||
*/
|
||||
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import {
|
||||
ProtoSerializable,
|
||||
ProtoField,
|
||||
ProtoFieldType,
|
||||
ProtoFloat,
|
||||
ProtoInt32,
|
||||
ProtoString,
|
||||
ProtoBool,
|
||||
ProtobufRegistry,
|
||||
isProtoSerializable,
|
||||
getProtoName
|
||||
} from '../../../src/Utils/Serialization/ProtobufDecorators';
|
||||
|
||||
// 测试组件
|
||||
@ProtoSerializable('TestPosition')
|
||||
class TestPositionComponent extends Component {
|
||||
@ProtoFloat(1)
|
||||
public x: number = 0;
|
||||
|
||||
@ProtoFloat(2)
|
||||
public y: number = 0;
|
||||
|
||||
@ProtoFloat(3)
|
||||
public z: number = 0;
|
||||
|
||||
constructor(x: number = 0, y: number = 0, z: number = 0) {
|
||||
super();
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
|
||||
@ProtoSerializable('TestPlayer')
|
||||
class TestPlayerComponent extends Component {
|
||||
@ProtoString(1)
|
||||
public name: string = '';
|
||||
|
||||
@ProtoInt32(2)
|
||||
public level: number = 1;
|
||||
|
||||
@ProtoInt32(3)
|
||||
public health: number = 100;
|
||||
|
||||
@ProtoBool(4)
|
||||
public isAlive: boolean = true;
|
||||
|
||||
constructor(name: string = '', level: number = 1) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.level = level;
|
||||
}
|
||||
}
|
||||
|
||||
// 没有装饰器的组件
|
||||
class PlainComponent extends Component {
|
||||
public data: string = 'test';
|
||||
}
|
||||
|
||||
// 测试字段编号冲突的组件
|
||||
const createConflictingComponent = () => {
|
||||
try {
|
||||
@ProtoSerializable('Conflict')
|
||||
class ConflictComponent extends Component {
|
||||
@ProtoFloat(1)
|
||||
public x: number = 0;
|
||||
|
||||
@ProtoFloat(1) // 故意使用相同的字段编号
|
||||
public y: number = 0;
|
||||
}
|
||||
return ConflictComponent;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
describe('ProtobufDecorators', () => {
|
||||
let registry: ProtobufRegistry;
|
||||
|
||||
beforeEach(() => {
|
||||
// 获取注册表实例
|
||||
registry = ProtobufRegistry.getInstance();
|
||||
});
|
||||
|
||||
describe('@ProtoSerializable装饰器', () => {
|
||||
it('应该正确标记组件为可序列化', () => {
|
||||
const component = new TestPositionComponent(10, 20, 30);
|
||||
|
||||
expect(isProtoSerializable(component)).toBe(true);
|
||||
expect(getProtoName(component)).toBe('TestPosition');
|
||||
});
|
||||
|
||||
it('应该在注册表中注册组件定义', () => {
|
||||
expect(registry.hasProtoDefinition('TestPosition')).toBe(true);
|
||||
expect(registry.hasProtoDefinition('TestPlayer')).toBe(true);
|
||||
});
|
||||
|
||||
it('应该正确处理没有装饰器的组件', () => {
|
||||
const component = new PlainComponent();
|
||||
|
||||
expect(isProtoSerializable(component)).toBe(false);
|
||||
expect(getProtoName(component)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('@ProtoField装饰器', () => {
|
||||
it('应该正确定义字段', () => {
|
||||
const definition = registry.getComponentDefinition('TestPosition');
|
||||
|
||||
expect(definition).toBeDefined();
|
||||
expect(definition!.fields.size).toBe(3);
|
||||
|
||||
const xField = definition!.fields.get('x');
|
||||
expect(xField).toEqual({
|
||||
fieldNumber: 1,
|
||||
type: ProtoFieldType.FLOAT,
|
||||
repeated: false,
|
||||
optional: false,
|
||||
name: 'x'
|
||||
});
|
||||
|
||||
const yField = definition!.fields.get('y');
|
||||
expect(yField).toEqual({
|
||||
fieldNumber: 2,
|
||||
type: ProtoFieldType.FLOAT,
|
||||
repeated: false,
|
||||
optional: false,
|
||||
name: 'y'
|
||||
});
|
||||
});
|
||||
|
||||
it('应该支持不同的字段类型', () => {
|
||||
const definition = registry.getComponentDefinition('TestPlayer');
|
||||
|
||||
expect(definition).toBeDefined();
|
||||
expect(definition!.fields.size).toBe(4);
|
||||
|
||||
const nameField = definition!.fields.get('name');
|
||||
expect(nameField!.type).toBe(ProtoFieldType.STRING);
|
||||
|
||||
const levelField = definition!.fields.get('level');
|
||||
expect(levelField!.type).toBe(ProtoFieldType.INT32);
|
||||
|
||||
const healthField = definition!.fields.get('health');
|
||||
expect(healthField!.type).toBe(ProtoFieldType.INT32);
|
||||
|
||||
const isAliveField = definition!.fields.get('isAlive');
|
||||
expect(isAliveField!.type).toBe(ProtoFieldType.BOOL);
|
||||
});
|
||||
|
||||
it('应该检测字段编号冲突', () => {
|
||||
const result = createConflictingComponent();
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect((result as Error).message).toContain('字段编号 1 已被字段');
|
||||
});
|
||||
|
||||
it('应该验证字段编号有效性', () => {
|
||||
expect(() => {
|
||||
class InvalidFieldComponent extends Component {
|
||||
@ProtoField(0) // 无效的字段编号
|
||||
public invalid: number = 0;
|
||||
}
|
||||
}).toThrow('字段编号必须大于0');
|
||||
|
||||
expect(() => {
|
||||
class InvalidFieldComponent extends Component {
|
||||
@ProtoField(-1) // 无效的字段编号
|
||||
public invalid: number = 0;
|
||||
}
|
||||
}).toThrow('字段编号必须大于0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('便捷装饰器', () => {
|
||||
it('ProtoFloat应该设置正确的字段类型', () => {
|
||||
@ProtoSerializable('FloatTest')
|
||||
class FloatTestComponent extends Component {
|
||||
@ProtoFloat(1)
|
||||
public value: number = 0;
|
||||
}
|
||||
|
||||
const definition = registry.getComponentDefinition('FloatTest');
|
||||
const field = definition!.fields.get('value');
|
||||
expect(field!.type).toBe(ProtoFieldType.FLOAT);
|
||||
});
|
||||
|
||||
it('ProtoInt32应该设置正确的字段类型', () => {
|
||||
@ProtoSerializable('Int32Test')
|
||||
class Int32TestComponent extends Component {
|
||||
@ProtoInt32(1)
|
||||
public value: number = 0;
|
||||
}
|
||||
|
||||
const definition = registry.getComponentDefinition('Int32Test');
|
||||
const field = definition!.fields.get('value');
|
||||
expect(field!.type).toBe(ProtoFieldType.INT32);
|
||||
});
|
||||
|
||||
it('ProtoString应该设置正确的字段类型', () => {
|
||||
@ProtoSerializable('StringTest')
|
||||
class StringTestComponent extends Component {
|
||||
@ProtoString(1)
|
||||
public value: string = '';
|
||||
}
|
||||
|
||||
const definition = registry.getComponentDefinition('StringTest');
|
||||
const field = definition!.fields.get('value');
|
||||
expect(field!.type).toBe(ProtoFieldType.STRING);
|
||||
});
|
||||
|
||||
it('ProtoBool应该设置正确的字段类型', () => {
|
||||
@ProtoSerializable('BoolTest')
|
||||
class BoolTestComponent extends Component {
|
||||
@ProtoBool(1)
|
||||
public value: boolean = false;
|
||||
}
|
||||
|
||||
const definition = registry.getComponentDefinition('BoolTest');
|
||||
const field = definition!.fields.get('value');
|
||||
expect(field!.type).toBe(ProtoFieldType.BOOL);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProtobufRegistry', () => {
|
||||
it('应该正确生成proto定义', () => {
|
||||
const protoDefinition = registry.generateProtoDefinition();
|
||||
|
||||
expect(protoDefinition).toContain('syntax = "proto3";');
|
||||
expect(protoDefinition).toContain('package ecs;');
|
||||
expect(protoDefinition).toContain('message TestPosition');
|
||||
expect(protoDefinition).toContain('message TestPlayer');
|
||||
expect(protoDefinition).toContain('float x = 1;');
|
||||
expect(protoDefinition).toContain('float y = 2;');
|
||||
expect(protoDefinition).toContain('string name = 1;');
|
||||
expect(protoDefinition).toContain('int32 level = 2;');
|
||||
expect(protoDefinition).toContain('bool isAlive = 4;');
|
||||
});
|
||||
|
||||
it('应该正确管理组件注册', () => {
|
||||
const allComponents = registry.getAllComponents();
|
||||
|
||||
expect(allComponents.size).toBeGreaterThanOrEqual(2);
|
||||
expect(allComponents.has('TestPosition')).toBe(true);
|
||||
expect(allComponents.has('TestPlayer')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('字段选项', () => {
|
||||
it('应该支持repeated字段', () => {
|
||||
@ProtoSerializable('RepeatedTest')
|
||||
class RepeatedTestComponent extends Component {
|
||||
@ProtoField(1, ProtoFieldType.INT32, { repeated: true })
|
||||
public values: number[] = [];
|
||||
}
|
||||
|
||||
const definition = registry.getComponentDefinition('RepeatedTest');
|
||||
const field = definition!.fields.get('values');
|
||||
expect(field!.repeated).toBe(true);
|
||||
});
|
||||
|
||||
it('应该支持optional字段', () => {
|
||||
@ProtoSerializable('OptionalTest')
|
||||
class OptionalTestComponent extends Component {
|
||||
@ProtoField(1, ProtoFieldType.STRING, { optional: true })
|
||||
public optionalValue?: string;
|
||||
}
|
||||
|
||||
const definition = registry.getComponentDefinition('OptionalTest');
|
||||
const field = definition!.fields.get('optionalValue');
|
||||
expect(field!.optional).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user