diff --git a/packages-user/data-state/src/enemy/aura.test.ts b/packages-user/data-state/src/enemy/aura.test.ts index 139f30b..d2dae97 100644 --- a/packages-user/data-state/src/enemy/aura.test.ts +++ b/packages-user/data-state/src/enemy/aura.test.ts @@ -103,13 +103,17 @@ function createEnemy(attrs: Partial = {}): FakeEnemy { addSpecial: () => {}, deleteSpecial: () => {}, setAttribute: (key: string, value: unknown) => { - (values as never)[key] = value; + // 假怪物按动态字符串键读写属性,固定形状的 IEnemyAttr 无法表达字符串索引 + (values as unknown as Record)[key] = value; }, addAttribute: (key: string, value: number) => { - (values as never)[key] += value; + (values as unknown as Record)[key] += value; }, copyFrom: () => {}, - saveState: () => ({ attrs: structuredClone(values), specials: new Map() }), + saveState: () => ({ + attrs: structuredClone(values), + specials: new Map() + }), loadState: () => {} } as never; return { enemy, attrs: values }; @@ -129,7 +133,9 @@ function createSpecial(code: number, value: T): ISpecial { * @param resolve 根据计算后怪物对象查找视图的回调 */ function createContext( - resolve?: (enemy: IReadonlyEnemy) => IEnemyView | null + resolve?: ( + enemy: IReadonlyEnemy + ) => IEnemyView | null ): IEnemyContext { return { width: 8, @@ -192,8 +198,8 @@ describe('CommonAuraConverter', () => { const halo = createSpecial(25, createHalo()); const guard = createSpecial(26, undefined); - expect(converter.shouldConvert(halo, {} as never)).toBe(true); - expect(converter.shouldConvert(guard, {} as never)).toBe(false); + expect(converter.shouldConvert(halo)).toBe(true); + expect(converter.shouldConvert(guard)).toBe(false); }); // 验证转换结果为携带怪物、特殊属性与定位符的光环视图 @@ -234,10 +240,7 @@ describe('CommonAura range', () => { const { enemy } = createEnemy(); const aura = new modules.CommonAura( enemy, - createSpecial( - 25, - createHalo({ haloRange: 2, haloSquare: true }) - ), + createSpecial(25, createHalo({ haloRange: 2, haloSquare: true })), { x: 3, y: 4 } ); @@ -250,10 +253,7 @@ describe('CommonAura range', () => { const { enemy } = createEnemy(); const aura = new modules.CommonAura( enemy, - createSpecial( - 25, - createHalo({ haloRange: 2, haloSquare: false }) - ), + createSpecial(25, createHalo({ haloRange: 2, haloSquare: false })), { x: 3, y: 4 } ); @@ -276,7 +276,10 @@ describe('CommonAura apply', () => { { x: 0, y: 0 } ); - aura.apply(createWritableHandler(target.enemy, { x: 1, y: 0 }), base.enemy); + aura.apply( + createWritableHandler(target.enemy, { x: 1, y: 0 }), + base.enemy + ); expect(target.attrs.hp).toBe(30); expect(target.attrs.atk).toBe(33); @@ -293,7 +296,10 @@ describe('CommonAura apply', () => { { x: 0, y: 0 } ); - aura.apply(createWritableHandler(target.enemy, { x: 1, y: 0 }), base.enemy); + aura.apply( + createWritableHandler(target.enemy, { x: 1, y: 0 }), + base.enemy + ); expect(target.attrs).toMatchObject({ hp: 20, atk: 8, def: 5 }); }); @@ -319,12 +325,12 @@ describe('GuardAuraConverter', () => { it('converts only special code 26', () => { const converter = new modules.GuardAuraConverter(); - expect( - converter.shouldConvert(createSpecial(26, undefined), {} as never) - ).toBe(true); - expect( - converter.shouldConvert(createSpecial(25, undefined), {} as never) - ).toBe(false); + expect(converter.shouldConvert(createSpecial(26, undefined))).toBe( + true + ); + expect(converter.shouldConvert(createSpecial(25, undefined))).toBe( + false + ); }); // 验证转换结果为携带上下文、怪物、特殊属性与定位符的支援光环视图 @@ -376,9 +382,9 @@ describe('GuardAura', () => { aura.apply(createWritableHandler(target.enemy, { x: 2, y: 1 })); - expect((target.attrs.guard as Set).has(sourceLocator)).toBe( - true - ); + expect( + (target.attrs.guard as Set).has(sourceLocator) + ).toBe(true); }); // 验证支援光环不会把来源定位符添加到自身 diff --git a/packages-user/data-state/src/enemy/calculator.test.ts b/packages-user/data-state/src/enemy/calculator.test.ts index 475f86c..96dbadc 100644 --- a/packages-user/data-state/src/enemy/calculator.test.ts +++ b/packages-user/data-state/src/enemy/calculator.test.ts @@ -97,7 +97,7 @@ function createEnemy(options: FakeEnemyOptions = {}): FakeEnemy { code, value: specials.get(code), deepEqualsTo: () => false - } as ISpecial) + } as unknown as ISpecial) : null, hasSpecial: (code: number) => specials.has(code), iterateSpecials: () => [], @@ -107,10 +107,11 @@ function createEnemy(options: FakeEnemyOptions = {}): FakeEnemy { addSpecial: () => {}, deleteSpecial: () => {}, setAttribute: (key: string, value: unknown) => { - (attrs as never)[key] = value; + // 假怪物按动态字符串键读写属性,固定形状的 IEnemyAttr 无法表达字符串索引 + (attrs as unknown as Record)[key] = value; }, addAttribute: (key: string, value: number) => { - (attrs as never)[key] += value; + (attrs as unknown as Record)[key] += value; }, copyFrom: () => {}, saveState: () => ({ @@ -145,7 +146,7 @@ function createHero( return { getBaseAttribute: (name: string) => (values as never)[name], getFinalAttribute: (name: string) => (values as never)[name] - } as IReadonlyHeroAttribute; + } as unknown as IReadonlyHeroAttribute; } interface FakeStateOptions { @@ -196,12 +197,10 @@ function createContext( * 将一个计算后怪物对象包装为只提供该对象读取的假视图 * @param computed 计算后怪物对象 */ -function createView( - computed: IEnemy -): IEnemyView { +function createView(computed: IEnemy): IEnemyView { return { getComputedEnemy: () => computed - } as IEnemyView; + } as unknown as IEnemyView; } interface HandlerOptions { @@ -244,9 +243,7 @@ function specialsOf(entries: Array<[number, unknown]>): Map { /** * 创建一个计算器实例 */ -function createCalculator(): InstanceType< - TestModules['MainDamageCalculator'] -> { +function createCalculator(): InstanceType { return new modules.MainDamageCalculator(); } @@ -273,7 +270,9 @@ describe('MainDamageCalculator base and defeat branches', () => { // 验证无敌怪物在没有十字架时不可战胜,持有十字架后恢复正常计算 it('blocks 无敌 without a cross and allows it with one', () => { - const { enemy } = createEnemy({ specials: specialsOf([[20, undefined]]) }); + const { enemy } = createEnemy({ + specials: specialsOf([[20, undefined]]) + }); const calculator = createCalculator(); const blocked = calculator.calculate(createHandler({ enemy })); @@ -287,7 +286,9 @@ describe('MainDamageCalculator base and defeat branches', () => { // 验证魔攻怪物的每轮伤害不减免勇士防御 it('ignores hero defense for 魔攻', () => { - const { enemy } = createEnemy({ specials: specialsOf([[2, undefined]]) }); + const { enemy } = createEnemy({ + specials: specialsOf([[2, undefined]]) + }); const calculator = createCalculator(); const info = calculator.calculate(createHandler({ enemy })); @@ -301,12 +302,12 @@ describe('MainDamageCalculator base and defeat branches', () => { const double = createEnemy({ specials: specialsOf([[4, undefined]]) }); const triple = createEnemy({ specials: specialsOf([[5, undefined]]) }); - expect(calculator.calculate(createHandler({ enemy: double.enemy }))).toEqual( - { damage: 6, turn: 2 } - ); - expect(calculator.calculate(createHandler({ enemy: triple.enemy }))).toEqual( - { damage: 9, turn: 2 } - ); + expect( + calculator.calculate(createHandler({ enemy: double.enemy })) + ).toEqual({ damage: 6, turn: 2 }); + expect( + calculator.calculate(createHandler({ enemy: triple.enemy })) + ).toEqual({ damage: 9, turn: 2 }); }); // 验证多段伤害按特殊属性数值倍乘每轮伤害 @@ -334,9 +335,7 @@ describe('MainDamageCalculator support and additive branches', () => { }); const calculator = createCalculator(); - const info = calculator.calculate( - createHandler({ enemy, context }) - ); + const info = calculator.calculate(createHandler({ enemy, context })); expect(info).toEqual({ damage: 12, turn: 4 }); expect(calls).toEqual(['1,0']); @@ -378,7 +377,9 @@ describe('MainDamageCalculator support and additive branches', () => { // 验证先攻会额外附加一次每轮伤害 it('adds one enemy hit for 先攻', () => { - const { enemy } = createEnemy({ specials: specialsOf([[1, undefined]]) }); + const { enemy } = createEnemy({ + specials: specialsOf([[1, undefined]]) + }); const calculator = createCalculator(); const info = calculator.calculate(createHandler({ enemy })); @@ -500,7 +501,9 @@ describe('MainDamageCalculator vampire and bypass branches', () => { // 验证仇恨按 flag 字段值附加伤害且不受魔防影响 it('adds the hatred flag value for 仇恨', () => { - const { enemy } = createEnemy({ specials: specialsOf([[17, undefined]]) }); + const { enemy } = createEnemy({ + specials: specialsOf([[17, undefined]]) + }); const calculator = createCalculator(); const info = calculator.calculate( diff --git a/packages-user/data-state/src/enemy/final.test.ts b/packages-user/data-state/src/enemy/final.test.ts index b390526..7038845 100644 --- a/packages-user/data-state/src/enemy/final.test.ts +++ b/packages-user/data-state/src/enemy/final.test.ts @@ -2,10 +2,7 @@ import { beforeAll, describe, expect, it, vi } from 'vitest'; import { type IEnemyAttr, type IHeroAttr } from '@user/data-common'; import { type IEnemyHandler } from '@user/data-system'; -import { - type IEnemy, - type IReadonlyHeroAttribute -} from '@user/data-base'; +import { type IEnemy, type IReadonlyHeroAttribute } from '@user/data-base'; vi.hoisted(() => { vi.stubGlobal('main', { replayChecking: true }); @@ -86,13 +83,17 @@ function createEnemy( addSpecial: () => {}, deleteSpecial: () => {}, setAttribute: (key: string, value: unknown) => { - (values as never)[key] = value; + // 假怪物按动态字符串键读写属性,固定形状的 IEnemyAttr 无法表达字符串索引 + (values as unknown as Record)[key] = value; }, addAttribute: (key: string, value: number) => { - (values as never)[key] += value; + (values as unknown as Record)[key] += value; }, copyFrom: () => {}, - saveState: () => ({ attrs: structuredClone(values), specials: new Map() }), + saveState: () => ({ + attrs: structuredClone(values), + specials: new Map() + }), loadState: () => {} } as never; return { enemy, attrs: values }; diff --git a/packages-user/data-state/src/enemy/mapDamage.test.ts b/packages-user/data-state/src/enemy/mapDamage.test.ts index 51e3d1e..f3dca06 100644 --- a/packages-user/data-state/src/enemy/mapDamage.test.ts +++ b/packages-user/data-state/src/enemy/mapDamage.test.ts @@ -6,11 +6,9 @@ import { type IDamageSystem, type IEnemyContext, type IEnemyView, - type IMapDamageInfo, - type IReadonlyEnemyHandler + type IMapDamageInfo } from '@user/data-system'; import { - type IEnemy, type IReadonlyEnemy, type IReadonlyHeroAttribute, type ISpecial @@ -77,10 +75,19 @@ beforeAll(async () => { AmbushDamageView: mapDamageModule.AmbushDamageView, MainMapDamageConverter: mapDamageModule.MainMapDamageConverter, MainMapDamageReducer: mapDamageModule.MainMapDamageReducer, - MapDamageType: typesModule.MapDamageType, + MapDamageType: { + Unknown: typesModule.MapDamageType.Unknown, + Zone: typesModule.MapDamageType.Zone, + Layer: typesModule.MapDamageType.Layer, + Repulse: typesModule.MapDamageType.Repulse, + Between: typesModule.MapDamageType.Between + }, ManhattanRange: motaModule.ManhattanRange, RectRange: motaModule.RectRange, - FaceGroup: commonModule.FaceGroup + FaceGroup: { + Dir4: commonModule.FaceGroup.Dir4, + Dir8: commonModule.FaceGroup.Dir8 + } }; }); @@ -144,7 +151,8 @@ function createContext( ): IEnemyContext { const width = options.width ?? 6; const height = options.height ?? 6; - const enemies = options.enemies ?? new Map>(); + const enemies = + options.enemies ?? new Map>(); return { width, height, @@ -177,9 +185,9 @@ function createView( * 创建一个按固定方向表返回移动描述的假朝向处理器 * @param dirs 朝向与坐标增量对 */ -function createFace( - dirs: Array<[number, { x: number; y: number }]> -): { mapMovement: () => Array<[number, { x: number; y: number }]> } { +function createFace(dirs: Array<[number, { x: number; y: number }]>): { + mapMovement: () => Array<[number, { x: number; y: number }]>; +} { return { mapMovement: () => dirs }; } @@ -212,11 +220,15 @@ describe('ZoneDamageView', () => { zoneSquare: false, range: 1 }); - const view = new modules.ZoneDamageView(createContext(), { x: 2, y: 3 }, special); + const view = new modules.ZoneDamageView( + createContext(), + { x: 2, y: 3 }, + special + ); expect(view.getRange()).toBeInstanceOf(modules.ManhattanRange); expect(view.getRangeParam()).toEqual({ cx: 2, cy: 3, radius: 1 }); - expect(view.getDamageWithoutCheck({ x: 0, y: 0 })).toEqual( + expect(view.getDamageWithoutCheck()).toEqual( createInfo(7, modules.MapDamageType.Zone) ); }); @@ -228,11 +240,15 @@ describe('ZoneDamageView', () => { zoneSquare: true, range: 2 }); - const view = new modules.ZoneDamageView(createContext(), { x: 2, y: 3 }, special); + const view = new modules.ZoneDamageView( + createContext(), + { x: 2, y: 3 }, + special + ); expect(view.getRange()).toBeInstanceOf(modules.RectRange); expect(view.getRangeParam()).toEqual({ x: 0, y: 1, w: 5, h: 5 }); - expect(view.getDamageWithoutCheck({ x: 0, y: 0 })).toEqual( + expect(view.getDamageWithoutCheck()).toEqual( createInfo(4, modules.MapDamageType.Zone) ); }); @@ -244,7 +260,11 @@ describe('ZoneDamageView', () => { zoneSquare: false, range: 1 }); - const view = new modules.ZoneDamageView(createContext(), { x: 2, y: 3 }, special); + const view = new modules.ZoneDamageView( + createContext(), + { x: 2, y: 3 }, + special + ); expect(view.getDamageAt({ x: 2, y: 4 })).toEqual( createInfo(7, modules.MapDamageType.Zone) @@ -258,7 +278,11 @@ describe('RepulseDamageView', () => { it('uses a radius one range and returns null at the source', () => { const special = createSpecial(18, 4); const source: ITileLocator = { x: 2, y: 3 }; - const view = new modules.RepulseDamageView(createContext(), source, special); + const view = new modules.RepulseDamageView( + createContext(), + source, + special + ); expect(view.getRangeParam()).toEqual({ cx: 2, cy: 3, radius: 1 }); expect(view.getDamageWithoutCheck(source)).toBeNull(); @@ -268,7 +292,11 @@ describe('RepulseDamageView', () => { it('returns repulse damage carrying the source locator', () => { const special = createSpecial(18, 4); const source: ITileLocator = { x: 2, y: 3 }; - const view = new modules.RepulseDamageView(createContext(), source, special); + const view = new modules.RepulseDamageView( + createContext(), + source, + special + ); const info = view.getDamageWithoutCheck({ x: 3, y: 3 }); @@ -308,9 +336,12 @@ describe('LaserDamageView', () => { expect(view.getRangeParam()).toEqual({ cx: 2, cy: 3, - dir: [{ x: 0, y: 0 }, { x: 1, y: 0 }] + dir: [ + { x: 0, y: 0 }, + { x: 1, y: 0 } + ] }); - expect(view.getDamageWithoutCheck({ x: 5, y: 3 })).toEqual( + expect(view.getDamageWithoutCheck()).toEqual( createInfo(9, modules.MapDamageType.Layer) ); }); @@ -462,7 +493,7 @@ describe('AmbushDamageView', () => { const locator: ITileLocator = { x: 2, y: 3 }; const view = new modules.AmbushDamageView(createContext(), locator); - const info = view.getDamageWithoutCheck(locator); + const info = view.getDamageWithoutCheck(); expect(info?.damage).toBe(0); expect(info?.type).toBe(modules.MapDamageType.Unknown); @@ -531,18 +562,15 @@ describe('MainMapDamageReducer', () => { const caught: ITileLocator = { x: 0, y: 1 }; const reducer = new modules.MainMapDamageReducer(); - const info = reducer.reduce( - [ - createInfo(3, modules.MapDamageType.Zone), - createInfo(5, modules.MapDamageType.Repulse, { - repulse: new Set([repulse]) - }), - createInfo(5, modules.MapDamageType.Between, { - catch: new Set([caught]) - }) - ], - { x: 0, y: 0 } - ); + const info = reducer.reduce([ + createInfo(3, modules.MapDamageType.Zone), + createInfo(5, modules.MapDamageType.Repulse, { + repulse: new Set([repulse]) + }), + createInfo(5, modules.MapDamageType.Between, { + catch: new Set([caught]) + }) + ]); expect(info.damage).toBe(13); expect(info.type).toBe(modules.MapDamageType.Repulse); @@ -554,7 +582,7 @@ describe('MainMapDamageReducer', () => { it('returns zero unknown damage for an empty input', () => { const reducer = new modules.MainMapDamageReducer(); - const info = reducer.reduce([], { x: 0, y: 0 }); + const info = reducer.reduce([]); expect(info.damage).toBe(0); expect(info.type).toBe(modules.MapDamageType.Unknown); diff --git a/packages-user/data-system/src/combat/combat.test.ts b/packages-user/data-system/src/combat/combat.test.ts index cb7636b..5064322 100644 --- a/packages-user/data-system/src/combat/combat.test.ts +++ b/packages-user/data-system/src/combat/combat.test.ts @@ -139,9 +139,15 @@ class FakeScript implements ICombatScript { } } +type TestCombatFlow = import('./combat').CombatFlow< + TestEnemyAttr, + TestHeroAttr +>; +type TestHeroAttribute = import('@user/data-base').HeroAttribute; + interface CombatFixture { /** 被测战斗流程对象 */ - flow: InstanceType; + flow: TestCombatFlow; /** 数据层状态假对象 */ state: IStateBase; /** 怪物上下文假对象 */ @@ -149,7 +155,7 @@ interface CombatFixture { /** 伤害上下文假对象 */ damage: IDamageContext; /** 可修改勇士属性 */ - hero: InstanceType; + hero: TestHeroAttribute; /** 怪物视图假对象 */ view: IEnemyView; /** 计算后怪物 */ diff --git a/packages-user/data-system/src/combat/context.test.ts b/packages-user/data-system/src/combat/context.test.ts index b836014..c535d15 100644 --- a/packages-user/data-system/src/combat/context.test.ts +++ b/packages-user/data-system/src/combat/context.test.ts @@ -4,19 +4,20 @@ import { type IRange } from '@motajs/common'; import { type IEnemy, type IReadonlyEnemy, + type IReadonlyHeroAttribute, type IStateBase, type ISpecial } from '@user/data-base'; import { type IAuraConverter, type IAuraView, + type IEnemyAuraView, type IEnemyCommonQueryEffect, type IEnemyFinalEffect, type IEnemyHandler, type IEnemySpecialModifier, type IEnemySpecialQueryEffect, - type IEnemyView, - type IReadonlyHeroAttribute + type IEnemyView } from './types'; vi.hoisted(() => { @@ -127,11 +128,17 @@ function createEnemy( }); } +type TestEnemyContext = import('./context').EnemyContext< + TestEnemyAttr, + TestHeroAttr +>; +type TestHeroAttribute = import('@user/data-base').HeroAttribute; + interface ContextFixture { /** 被测怪物上下文 */ - context: InstanceType; + context: TestEnemyContext; /** 可修改勇士属性 */ - hero: InstanceType; + hero: TestHeroAttribute; /** 数据层状态假对象 */ state: IStateBase; } @@ -146,7 +153,9 @@ function createContextFixture( height: number = 3 ): ContextFixture { const state = {} as IStateBase; - const context = new modules.EnemyContext(state); + const context = new modules.EnemyContext( + state + ); context.resize(width, height); const hero = new modules.HeroAttribute({ hp: 100, @@ -255,9 +264,18 @@ class FakeConverter implements IAuraConverter { return this.codes.includes(special.code); } - convert(special: ISpecial): IAuraView | null { + convert(special: ISpecial): IEnemyAuraView { this.convertCalls.push(special.code); - return this.factory ? this.factory(special.code) : null; + if (!this.factory) { + // 接口约定 convert 必返回光环视图,但测试需覆盖“无工厂则跳过转换”的 null 分支 + return null as unknown as IEnemyAuraView; + } + // FakeAura 为最小测试替身,不携带 enemy/special/locator,无法完整满足 IEnemyAuraView + return this.factory(special.code) as unknown as IEnemyAuraView< + TestEnemyAttr, + any, + any + >; } } @@ -353,9 +371,9 @@ describe('EnemyContext registry and lookups', () => { y: 0 }); expect(fixture.context.getEnemyLocator(enemy)).toEqual({ x: 1, y: 0 }); - expect(fixture.context.getViewByComputed(view!.getComputedEnemy())).toBe( - view - ); + expect( + fixture.context.getViewByComputed(view!.getComputedEnemy()) + ).toBe(view); }); // 验证未知定位符、未知坐标与未知怪物对象均返回 null @@ -691,7 +709,10 @@ describe('EnemyContext single aura ranges', () => { fixture.context.buildup(); expect( - fixture.context.getEnemyByLoc(0, 0)!.getComputedEnemy().hasSpecial(30) + fixture.context + .getEnemyByLoc(0, 0)! + .getComputedEnemy() + .hasSpecial(30) ).toBe(true); }); }); @@ -902,7 +923,9 @@ describe('EnemyContext aura pipeline', () => { fixture.context.buildup(); - const computed = fixture.context.getEnemyByLoc(0, 0)!.getComputedEnemy(); + const computed = fixture.context + .getEnemyByLoc(0, 0)! + .getComputedEnemy(); expect(converter.convertCalls).toContain(21); expect(computed.hasSpecial(21)).toBe(true); expect(computed.getAttribute('atk')).toBe(9); @@ -957,12 +980,16 @@ describe('EnemyContext aura pipeline', () => { enemy.addSpecial(createSpecial(20) as never); fixture.context.setEnemyAt({ x: 0, y: 0 }, enemy); fixture.context.registerAuraConverter( - new FakeConverter([20], () => new FakeAura({ - priority: 10, - range: new modules.FullRange(), - param: undefined, - couldApplyBase: false - })) + new FakeConverter( + [20], + () => + new FakeAura({ + priority: 10, + range: new modules.FullRange(), + param: undefined, + couldApplyBase: false + }) + ) ); fixture.context.registerSpecialQueryEffect({ priority: 10, @@ -987,12 +1014,16 @@ describe('EnemyContext aura pipeline', () => { enemy.addSpecial(createSpecial(20) as never); fixture.context.setEnemyAt({ x: 0, y: 0 }, enemy); fixture.context.registerAuraConverter( - new FakeConverter([20], () => new FakeAura({ - priority: 10, - range: new modules.FullRange(), - param: undefined, - couldApplyBase: false - })) + new FakeConverter( + [20], + () => + new FakeAura({ + priority: 10, + range: new modules.FullRange(), + param: undefined, + couldApplyBase: false + }) + ) ); fixture.context.registerSpecialQueryEffect({ priority: 10, @@ -1019,7 +1050,13 @@ describe('EnemyContext effect stage ordering', () => { enemy.addSpecial(createSpecial(20) as never); fixture.context.setEnemyAt({ x: 0, y: 0 }, enemy); const order: string[] = []; - const seen = { queryAtk: -1, queryDef: -1, queryHp: -1, finalAtk: -1, finalDef: -1 }; + const seen = { + queryAtk: -1, + queryDef: -1, + queryHp: -1, + finalAtk: -1, + finalDef: -1 + }; const aura = new FakeAura({ priority: 1, range: new modules.FullRange(), @@ -1034,7 +1071,9 @@ describe('EnemyContext effect stage ordering', () => { return { add: () => [], delete: () => [], modify: () => false }; } }); - fixture.context.registerAuraConverter(new FakeConverter([20], () => aura)); + fixture.context.registerAuraConverter( + new FakeConverter([20], () => aura) + ); fixture.context.registerCommonQueryEffect(20, { priority: 1, apply: handler => { @@ -1196,8 +1235,12 @@ describe('EnemyContext refresh paths and DEV warnings', () => { param: undefined, onApply: handler => handler.enemy.addAttribute('atk', 50) }); - fixture.context.registerAuraConverter(new FakeConverter([20], createAura)); - fixture.context.registerAuraConverter(new FakeConverter([20], createAura)); + fixture.context.registerAuraConverter( + new FakeConverter([20], createAura) + ); + fixture.context.registerAuraConverter( + new FakeConverter([20], createAura) + ); const result = modules.logger.catch(() => fixture.context.buildup()); @@ -1246,11 +1289,15 @@ describe('EnemyContext refresh paths and DEV warnings', () => { fixture.context.bindHero(fixture.hero); fixture.context.setEnemyAt({ x: 0, y: 0 }, createEnemy('e1')); fixture.context.registerAuraConverter( - new FakeConverter([40], () => new FakeAura({ - priority: 1, - range: new modules.FullRange(), - param: undefined - })) + new FakeConverter( + [40], + () => + new FakeAura({ + priority: 1, + range: new modules.FullRange(), + param: undefined + }) + ) ); fixture.context.registerSpecialQueryEffect({ priority: 1, @@ -1278,11 +1325,15 @@ describe('EnemyContext refresh paths and DEV warnings', () => { fixture.context.bindHero(fixture.hero); fixture.context.setEnemyAt({ x: 0, y: 0 }, createEnemy('e1')); fixture.context.registerAuraConverter( - new FakeConverter([40], () => new FakeAura({ - priority: 50, - range: new modules.FullRange(), - param: undefined - })) + new FakeConverter( + [40], + () => + new FakeAura({ + priority: 50, + range: new modules.FullRange(), + param: undefined + }) + ) ); fixture.context.registerSpecialQueryEffect({ priority: 1, diff --git a/packages-user/data-system/src/combat/damage.test.ts b/packages-user/data-system/src/combat/damage.test.ts index 4d99060..09c5589 100644 --- a/packages-user/data-system/src/combat/damage.test.ts +++ b/packages-user/data-system/src/combat/damage.test.ts @@ -1,14 +1,17 @@ // 测试战斗伤害上下文与伤害系统:结果展开、handler 身份、告警码 106/107、缓存与 with、临界生成、属性到伤害联动(D-26) import { beforeAll, describe, expect, it, vi } from 'vitest'; import { type ITileLocator } from '@motajs/common'; -import { type IEnemy, type IStateBase } from '@user/data-base'; +import { + type IEnemy, + type IReadonlyEnemy, + type IStateBase +} from '@user/data-base'; import { type CriticalableHeroStatus, type IDamageCalculator, type IEnemyContext, type IEnemyDamageInfoBase, type IEnemyView, - type IReadonlyEnemy, type IReadonlyEnemyHandler } from './types'; @@ -92,9 +95,7 @@ beforeAll(async () => { /** * 固定伤害计算器:每点攻击减免固定伤害,用于验证结果展开与临界生成 */ -class FakeCalculator - implements IDamageCalculator -{ +class FakeCalculator implements IDamageCalculator { /** 每点攻击减免的伤害值 */ readonly damagePerAtk: number; /** calculate 调用次数 */ @@ -123,13 +124,19 @@ class FakeCalculator } } +type TestHeroAttribute = import('@user/data-base').HeroAttribute; +type TestEnemyContext = import('./context').EnemyContext< + TestEnemyAttr, + TestHeroAttr +>; + interface DamageFixture { /** 怪物上下文假对象 */ context: IEnemyContext; /** 固定伤害计算器 */ calculator: FakeCalculator; /** 可修改勇士属性 */ - hero: InstanceType; + hero: TestHeroAttribute; /** 怪物视图假对象 */ view: IEnemyView; /** 计算后怪物 */ @@ -234,9 +241,10 @@ function createSpecial(code: number): never { /** * 创建一个按怪物攻击力线性换算伤害的计算器,用于验证属性到伤害的联动 */ -class EnemyAttributeCalculator - implements IDamageCalculator -{ +class EnemyAttributeCalculator implements IDamageCalculator< + TestEnemyAttr, + TestHeroAttr +> { /** calculate 调用次数 */ calls: number = 0; /** 每点怪物攻击力换算的伤害 */ @@ -289,9 +297,7 @@ interface FakeConverterShape { * 创建一个可驱动光环流水线的真实怪物上下文 * @param converter 赋予上下文的测试用光环转换器 */ -function createEnemyContext( - converter: FakeConverterShape -): InstanceType { +function createEnemyContext(converter: FakeConverterShape): TestEnemyContext { const context = new modules.EnemyContext( {} as IStateBase ); @@ -507,8 +513,9 @@ describe('DamageContext critical generation', () => { fixture.hero ); - const first = context.calculateCritical(fixture.view, 'atk').next() - .value!; + const first = context + .calculateCritical(fixture.view, 'atk') + .next().value!; expect(first.baseValue).toBe(0); expect(first.nextValue).toBe(1); @@ -577,8 +584,9 @@ describe('DamageContext critical generation', () => { fixture.hero ); - const first = context.calculateCritical(fixture.view, 'atk').next() - .value!; + const first = context + .calculateCritical(fixture.view, 'atk') + .next().value!; expect(first.nextValue).toBe(1); expect(first.info.damage).toBe(90); @@ -705,10 +713,7 @@ describe('DamageSystem attribute linkage', () => { context.buildup(); expect( - context - .getEnemyByLoc(0, 0)! - .getComputedEnemy() - .getAttribute('atk') + context.getEnemyByLoc(0, 0)!.getComputedEnemy().getAttribute('atk') ).toBe(17); }); }); diff --git a/packages-user/data-system/src/combat/mapDamage.test.ts b/packages-user/data-system/src/combat/mapDamage.test.ts index fefd671..3227847 100644 --- a/packages-user/data-system/src/combat/mapDamage.test.ts +++ b/packages-user/data-system/src/combat/mapDamage.test.ts @@ -165,9 +165,10 @@ class FakeView implements IMapDamageView { /** * 固定输出伤害视图列表的测试转换器 */ -class FakeConverter - implements IMapDamageConverter -{ +class FakeConverter implements IMapDamageConverter< + TestEnemyAttr, + TestHeroAttr +> { /** 转换输出的伤害视图 */ readonly views: IMapDamageView[]; /** convert 调用次数 */ @@ -190,9 +191,7 @@ class FakeReducer implements IMapDamageReducer { /** reduce 调用次数 */ calls: number = 0; - reduce( - info: Iterable> - ): Readonly { + reduce(info: Iterable>): Readonly { this.calls++; let damage = 0; for (const item of info) { @@ -249,10 +248,9 @@ function createFixture(): MapDamageFixture { markDirty: () => {} }; enemies.set(view, locator); - const damageView = new FakeView( - 7, - [indexer.locToIndex(locator.x, locator.y)] - ); + const damageView = new FakeView(7, [ + indexer.locToIndex(locator.x, locator.y) + ]); const context = { state, indexer, @@ -264,17 +262,13 @@ function createFixture(): MapDamageFixture { getEnemyLocator: (target: IEnemy) => target === origin ? locator : null, getViewByComputed: () => view, - *iterateEnemy(): Iterable< - [ITileLocator, IEnemyView] - > { + *iterateEnemy(): Iterable<[ITileLocator, IEnemyView]> { for (const [target, targetLocator] of enemies) { yield [targetLocator, target]; } } } as never; - const damage = new modules.MapDamage( - context - ); + const damage = new modules.MapDamage(context); return { damage, context, @@ -290,7 +284,9 @@ function createFixture(): MapDamageFixture { /** * 创建一个未注册到夹具中的怪物视图,用于验证未知定位符路径 */ -function createUnknownView(fixture: MapDamageFixture): IEnemyView { +function createUnknownView( + fixture: MapDamageFixture +): IEnemyView { return { context: {} as never, reset: () => {}, @@ -309,14 +305,14 @@ describe('MapDamage sourceless damage', () => { const info = createInfo(5); fixture.damage.addMapDamage({ x: 0, y: 0 }, info); - expect( - [...fixture.damage.getSeparatedDamage({ x: 0, y: 0 })] - ).toContain(info); + expect([ + ...fixture.damage.getSeparatedDamage({ x: 0, y: 0 }) + ]).toContain(info); fixture.damage.deleteMapDamage({ x: 0, y: 0 }, info); - expect( - [...fixture.damage.getSeparatedDamage({ x: 0, y: 0 })] - ).toHaveLength(0); + expect([ + ...fixture.damage.getSeparatedDamage({ x: 0, y: 0 }) + ]).toHaveLength(0); }); // 验证删除不存在的无来源伤害是安全操作 @@ -326,9 +322,9 @@ describe('MapDamage sourceless damage', () => { fixture.damage.deleteMapDamage({ x: 3, y: 2 }, createInfo(5)); - expect( - [...fixture.damage.getSeparatedDamage({ x: 3, y: 2 })] - ).toHaveLength(0); + expect([ + ...fixture.damage.getSeparatedDamage({ x: 3, y: 2 }) + ]).toHaveLength(0); }); }); @@ -357,9 +353,9 @@ describe('MapDamage sourced conversion and reduction', () => { fixture.damage.useConverter(fixture.converter); fixture.damage.addMapDamage(fixture.locator, createInfo(3)); - expect( - [...fixture.damage.getSeparatedDamage(fixture.locator)] - ).toHaveLength(2); + expect([ + ...fixture.damage.getSeparatedDamage(fixture.locator) + ]).toHaveLength(2); const reduced = fixture.damage.getReducedDamage(fixture.locator); expect(reduced?.damage).toBe(10); @@ -445,15 +441,14 @@ describe('MapDamage sourced conversion and reduction', () => { const fixture = createFixture(); fixture.damage.useReducer(fixture.reducer); fixture.damage.useConverter(fixture.converter); - const before = [ - ...fixture.damage.getSeparatedDamage(fixture.locator) - ].length; + const before = [...fixture.damage.getSeparatedDamage(fixture.locator)] + .length; fixture.damage.deleteEnemy(createUnknownView(fixture)); - expect( - [...fixture.damage.getSeparatedDamage(fixture.locator)] - ).toHaveLength(before); + expect([ + ...fixture.damage.getSeparatedDamage(fixture.locator) + ]).toHaveLength(before); }); // 疑似 bug:deleteEnemy 应移除该怪物带来的有来源地图伤害,详见 06-TEST-FINDINGS.md #06-01-2,修复后取消 skip @@ -461,14 +456,14 @@ describe('MapDamage sourced conversion and reduction', () => { const fixture = createFixture(); fixture.damage.useReducer(fixture.reducer); fixture.damage.useConverter(fixture.converter); - expect( - [...fixture.damage.getSeparatedDamage(fixture.locator)] - ).toHaveLength(1); + expect([ + ...fixture.damage.getSeparatedDamage(fixture.locator) + ]).toHaveLength(1); fixture.damage.deleteEnemy(fixture.view); - expect( - [...fixture.damage.getSeparatedDamage(fixture.locator)] - ).toHaveLength(0); + expect([ + ...fixture.damage.getSeparatedDamage(fixture.locator) + ]).toHaveLength(0); }); });