mirror of
https://github.com/motajs/template.git
synced 2026-10-02 08:30:16 +08:00
test(06-03): cover Enemy special CRUD and clone/copyFrom (stage 2)
- enemy.test.ts: addSpecial/get/has/iterate, duplicate warn 96, delete by object/code, clone/copyFrom independence - special.test.ts: clone value independence and deepEqualsTo code/value rules, NonePropertySpecial clone
This commit is contained in:
parent
965868c219
commit
ff752713b9
@ -1,6 +1,6 @@
|
||||
// 测试 Enemy 数据模型:属性单元、特殊属性增删查、克隆与复制行为
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { type IEnemy } from './types';
|
||||
import { type IEnemy, type ISpecial } from './types';
|
||||
|
||||
vi.hoisted(() => {
|
||||
vi.stubGlobal('main', { replayChecking: true });
|
||||
@ -67,6 +67,19 @@ function createEnemy(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个可序列化的数值特殊属性,仅使用内联配置
|
||||
* @param code 特殊属性代码
|
||||
* @param value 特殊属性数值
|
||||
*/
|
||||
function createSpecial(code: number, value: number): ISpecial<number> {
|
||||
return new modules.CommonSerializableSpecial<number>(code, value, {
|
||||
getSpecialName: () => `special-${code}`,
|
||||
getDescription: () => `description-${code}`,
|
||||
fromLegacyEnemy: () => value
|
||||
});
|
||||
}
|
||||
|
||||
describe('Enemy attribute units', () => {
|
||||
// 验证 getAttribute 按单个属性键返回构造时写入的初值
|
||||
it('reads each attribute key independently', () => {
|
||||
@ -112,3 +125,107 @@ describe('Enemy attribute units', () => {
|
||||
expect(enemy.cloneAttributes()).not.toBe(enemy.cloneAttributes());
|
||||
});
|
||||
});
|
||||
|
||||
describe('Enemy special composition', () => {
|
||||
// 验证 addSpecial 后可按代码查询、判断存在并迭代取出
|
||||
it('adds a special and resolves it by code', () => {
|
||||
const enemy = createEnemy();
|
||||
const special = createSpecial(6, 3);
|
||||
|
||||
enemy.addSpecial(special);
|
||||
|
||||
expect(enemy.hasSpecial(6)).toBe(true);
|
||||
expect(enemy.getSpecial<number>(6)).toBe(special);
|
||||
expect([...enemy.iterateSpecials()].map(item => item.code)).toEqual([
|
||||
6
|
||||
]);
|
||||
expect(enemy.hasSpecial(7)).toBe(false);
|
||||
expect(enemy.getSpecial(7)).toBeNull();
|
||||
});
|
||||
|
||||
// 验证重复 code 告警 96 且不替换既有特殊属性
|
||||
it('warns 96 and keeps the existing special on a duplicate code', () => {
|
||||
const enemy = createEnemy();
|
||||
enemy.addSpecial(createSpecial(6, 3));
|
||||
|
||||
const result = modules.logger.catch(() =>
|
||||
enemy.addSpecial(createSpecial(6, 9))
|
||||
);
|
||||
|
||||
expect(result.info.map(info => info.code)).toContain(96);
|
||||
expect(enemy.getSpecial<number>(6)!.getValue()).toBe(3);
|
||||
expect([...enemy.iterateSpecials()]).toHaveLength(1);
|
||||
});
|
||||
|
||||
// 验证 deleteSpecial 支持按对象与按代码删除且未知代码安全无操作
|
||||
it('deletes specials by object or code and ignores unknown codes', () => {
|
||||
const enemy = createEnemy();
|
||||
const first = createSpecial(6, 3);
|
||||
enemy.addSpecial(first);
|
||||
enemy.addSpecial(createSpecial(7, 5));
|
||||
|
||||
enemy.deleteSpecial(first);
|
||||
|
||||
expect(enemy.hasSpecial(6)).toBe(false);
|
||||
expect(enemy.getSpecial(6)).toBeNull();
|
||||
|
||||
enemy.deleteSpecial(7);
|
||||
enemy.deleteSpecial(99);
|
||||
|
||||
expect([...enemy.iterateSpecials()]).toHaveLength(0);
|
||||
});
|
||||
|
||||
// 验证 clone 复制属性与克隆后的特殊属性且与来源互相独立
|
||||
it('clones attributes and specials into an independent enemy', () => {
|
||||
const source = createEnemy('source', 1, {
|
||||
hp: 20,
|
||||
atk: 8,
|
||||
tags: ['base']
|
||||
});
|
||||
source.addSpecial(createSpecial(6, 3));
|
||||
|
||||
const clone = source.clone();
|
||||
|
||||
expect(clone).not.toBe(source);
|
||||
expect(clone.id).toBe('source');
|
||||
expect(clone.code).toBe(1);
|
||||
expect(clone.getSpecial<number>(6)).not.toBe(source.getSpecial(6));
|
||||
expect(clone.getAttribute('tags')).not.toBe(
|
||||
source.getAttribute('tags')
|
||||
);
|
||||
|
||||
clone.setAttribute('hp', 99);
|
||||
clone.getSpecial<number>(6)!.setValue(9);
|
||||
clone.getAttribute('tags').push('cloned');
|
||||
|
||||
expect(source.getAttribute('hp')).toBe(20);
|
||||
expect(source.getSpecial<number>(6)!.getValue()).toBe(3);
|
||||
expect(source.getAttribute('tags')).toEqual(['base']);
|
||||
});
|
||||
|
||||
// 验证 copyFrom 以来源的属性与克隆后的特殊属性整体替换当前对象
|
||||
it('copies attributes and specials from another enemy', () => {
|
||||
const target = createEnemy('target', 2, { hp: 1, atk: 1, tags: [] });
|
||||
target.addSpecial(createSpecial(7, 5));
|
||||
const source = createEnemy('source', 1, {
|
||||
hp: 77,
|
||||
atk: 5,
|
||||
tags: ['copied']
|
||||
});
|
||||
source.addSpecial(createSpecial(6, 3));
|
||||
|
||||
target.copyFrom(source);
|
||||
|
||||
expect(target.id).toBe('target');
|
||||
expect(target.code).toBe(2);
|
||||
expect(target.getAttribute('hp')).toBe(77);
|
||||
expect(target.getAttribute('tags')).toEqual(['copied']);
|
||||
expect(target.hasSpecial(7)).toBe(false);
|
||||
expect(target.getSpecial<number>(6)).not.toBe(source.getSpecial(6));
|
||||
expect(target.getSpecial<number>(6)!.getValue()).toBe(3);
|
||||
|
||||
source.setAttribute('hp', 1);
|
||||
|
||||
expect(target.getAttribute('hp')).toBe(77);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// 测试 special 数据模型:可序列化特殊属性的数值/名称/描述单元,以及无属性特殊属性
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { type ICommonSpecialConfig } from './special';
|
||||
import { type ISpecial } from './types';
|
||||
|
||||
vi.hoisted(() => {
|
||||
vi.stubGlobal('main', { replayChecking: true });
|
||||
@ -62,6 +63,30 @@ function makeNoneConfig(): ICommonSpecialConfig<void> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个内联配置的可序列化数值特殊属性实例
|
||||
* @param code 特殊属性代码
|
||||
* @param value 特殊属性数值
|
||||
*/
|
||||
function createValueSpecial(code: number, value: number): ISpecial<number> {
|
||||
return modules.defineCommonSerializableSpecial<number>(
|
||||
code,
|
||||
value,
|
||||
makeCommonConfig()
|
||||
)(undefined as never);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个内联配置的无属性特殊属性实例
|
||||
* @param code 特殊属性代码
|
||||
*/
|
||||
function createNoneSpecial(code: number): ISpecial<void> {
|
||||
return modules.defineNonePropertySpecial(
|
||||
code,
|
||||
makeNoneConfig()
|
||||
)(undefined as never);
|
||||
}
|
||||
|
||||
describe('CommonSerializableSpecial units', () => {
|
||||
// 验证 setValue 更新数值且 getValue 返回最新数值
|
||||
it('writes and reads the serializable value', () => {
|
||||
@ -134,3 +159,45 @@ describe('NonePropertySpecial units', () => {
|
||||
expect(special.getDescription()).toBe('怪物首先攻击。');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Special clone and deep equality', () => {
|
||||
// 验证可序列化特殊属性的 clone 深拷贝数值且与来源互相独立
|
||||
it('clones a serializable special without aliasing its value', () => {
|
||||
const source = createValueSpecial(6, 3);
|
||||
const clone = source.clone();
|
||||
|
||||
expect(clone).not.toBe(source);
|
||||
expect(clone.code).toBe(6);
|
||||
expect(clone.getValue()).toBe(3);
|
||||
|
||||
clone.setValue(9);
|
||||
|
||||
expect(source.getValue()).toBe(3);
|
||||
expect(clone.getValue()).toBe(9);
|
||||
});
|
||||
|
||||
// 验证可序列化特殊属性按 code 与深比较数值判定相等
|
||||
it('compares serializable specials by code and value', () => {
|
||||
expect(
|
||||
createValueSpecial(6, 3).deepEqualsTo(createValueSpecial(6, 3))
|
||||
).toBe(true);
|
||||
expect(
|
||||
createValueSpecial(6, 3).deepEqualsTo(createValueSpecial(6, 5))
|
||||
).toBe(false);
|
||||
expect(
|
||||
createValueSpecial(6, 3).deepEqualsTo(createValueSpecial(7, 3))
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
// 验证无属性特殊属性的 clone 独立且 deepEqualsTo 仅按 code 判定
|
||||
it('clones a none-property special and compares by code only', () => {
|
||||
const source = createNoneSpecial(1);
|
||||
const clone = source.clone();
|
||||
|
||||
expect(clone).not.toBe(source);
|
||||
expect(clone.code).toBe(1);
|
||||
expect(clone.getValue()).toBeUndefined();
|
||||
expect(clone.deepEqualsTo(source)).toBe(true);
|
||||
expect(clone.deepEqualsTo(createNoneSpecial(2))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user