278 lines
9.4 KiB
TypeScript
278 lines
9.4 KiB
TypeScript
|
|
/**
|
||
|
|
* 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);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|