fix(07-12): deleteModifierByIndex 委托 deleteModifier(WR-05)

- 按索引取出修饰器后委托 deleteModifier,删除簿记收敛为一份实现
- 返回被删修饰器;索引越界或为负返回 null 且不删除任何修饰器
- 回归覆盖:迭代器产出、索引解析、final 重算、重新挂载、存盘开关清理、越界
This commit is contained in:
unanmed 2026-09-17 15:56:50 +08:00
parent 762bf2e95c
commit ab18aa3ec7
3 changed files with 62 additions and 4 deletions

View File

@ -203,6 +203,60 @@ describe('HeroAttribute modifier management', () => {
expect(attribute.deleteModifierByIndex('hp', 0)).toBeNull();
});
// 验证按索引删除后修饰器不再被迭代、索引解析为 -1 且最终属性按剩余修饰器重算
it('removes a modifier by index from the iterator and the final attribute', () => {
const attribute = createAttribute();
const first = new TestModifier(5, 10);
const second = new TestModifier(2, 0);
attribute.addModifier('hp', first);
attribute.addModifier('hp', second);
expect(attribute.getFinalAttribute('hp')).toBe(107);
expect(attribute.deleteModifierByIndex('hp', 0)).toBe(first);
expect([...attribute.iterateModifiers()]).toEqual([['hp', second]]);
expect(attribute.getModifierIndex(first)).toBe(-1);
expect(attribute.getFinalAttribute('hp')).toBe(102);
});
// 验证被按索引删除的修饰器已解除归属,可以重新挂载且不再被码 108 拒绝
it('allows an index-deleted modifier to be attached again', () => {
const attribute = createAttribute();
const first = new TestModifier(5);
attribute.addModifier('hp', first);
expect(attribute.deleteModifierByIndex('hp', 0)).toBe(first);
const result = logger.catch(() => attribute.addModifier('hp', first));
expect(result.info.map(info => info.code)).not.toContain(108);
expect(attribute.getModifierIndex(first)).toBe(0);
expect(attribute.getFinalAttribute('hp')).toBe(105);
});
// 验证索引越界或为负时返回 null 且不删除任何修饰器
it('keeps every modifier when the index is out of range', () => {
const attribute = createAttribute();
const first = new TestModifier(5, 10);
const second = new TestModifier(2, 0);
attribute.addModifier('hp', first);
attribute.addModifier('hp', second);
expect(attribute.deleteModifierByIndex('hp', -1)).toBeNull();
expect(attribute.deleteModifierByIndex('hp', 2)).toBeNull();
expect([...attribute.getModifiers('hp')]).toEqual([first, second]);
expect(attribute.getFinalAttribute('hp')).toBe(107);
});
// 验证按索引删除不存档修饰器时其存盘开关记录被一并清理
it('clears the save flag of an index-deleted modifier', () => {
const attribute = createAttribute();
const unsaved = new TestModifier(3);
attribute.addModifier('hp', unsaved, false);
expect(attribute.getModifierSaveEnabled(unsaved)).toBe(false);
expect(attribute.deleteModifierByIndex('hp', 0)).toBe(unsaved);
expect(attribute.getModifierSaveEnabled(unsaved)).toBe(true);
});
// 验证存盘开关可分别设置与查询
it('tracks the per-modifier save flag', () => {
const attribute = createAttribute();

View File

@ -233,9 +233,12 @@ export class HeroAttribute<THero> implements IHeroAttribute<THero> {
): IHeroModifier<THero[K]> | null {
const arr = this.modifier.get(name);
if (!arr) return null;
const modifier = arr.splice(index, 1);
if (modifier.length === 0) return null;
else return modifier[0] as IHeroModifier<THero[K]>;
const modifier = arr[index] as IHeroModifier<THero[K]> | undefined;
// 越界与负索引一律不删除任何修饰器,避免旧 splice 语义下误删末尾元素
if (!modifier) return null;
// 删除簿记统一由 deleteModifier 承担,故需先取出修饰器再委托以保留返回值
this.deleteModifier(name, modifier);
return modifier;
}
markDirty(name: keyof THero): void {

View File

@ -219,7 +219,8 @@ export interface IHeroAttribute<THero>
): void;
/**
* 根据修饰器索引删除指定修饰器
* 根据修饰器索引删除指定修饰器。删除簿记与 `deleteModifier` 共用同一份实现,
* 修饰器会被解绑且不再属于本属性;索引越界或为负时返回 `null`,且不删除任何修饰器
* @param name 属性名称
* @param index 修饰器索引
* @returns 被删除的修饰器