UNPKG

@reldens/skills

Version:
770 lines (727 loc) 28.2 kB
/** * * Reldens - PhysicalEffect Unit Tests * */ const { describe, it, beforeEach, afterEach } = require('node:test'); const assert = require('node:assert'); const PhysicalEffect = require('../../../lib/types/physical-effect'); const { Modifier, ModifierConst } = require('@reldens/modifiers'); const SkillsConst = require('../../../lib/constants'); const SkillsEvents = require('../../../lib/skills-events'); const { TestHelpers } = require('../../utils/test-helpers'); const { MockOwner } = require('../../fixtures/mocks/mock-owner'); const { MockTarget } = require('../../fixtures/mocks/mock-target'); describe('PhysicalEffect', () => { let mockOwner; let mockTarget; beforeEach(() => { mockOwner = new MockOwner(); mockOwner.executePhysicalSkill = async () => {}; mockTarget = new MockTarget(); TestHelpers.clearEventListeners(); }); afterEach(() => { TestHelpers.clearEventListeners(); }); describe('Constructor', () => { it('should initialize with physical effect type', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.type, SkillsConst.SKILL.TYPE.PHYSICAL_EFFECT); }); it('should set parent type to effect', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.parentType, SkillsConst.SKILL.TYPE.EFFECT); }); it('should set isReady to true when all properties valid', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.isReady, true); }); it('should set isReady to false when magnitude missing', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.isReady, false); }); it('should set isReady to false when objectWidth missing', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.isReady, false); }); it('should set isReady to false when objectHeight missing', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.isReady, false); }); it('should set isReady to false when executePhysicalSkill missing', () => { let ownerWithoutMethod = new MockOwner(); let skillData = { key: 'test-physical-effect', owner: ownerWithoutMethod, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.isReady, false); }); it('should set magnitude property', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 150, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.magnitude, 150); }); it('should set objectWidth property', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 20, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.objectWidth, 20); }); it('should set objectHeight property', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 10, objectHeight: 25, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.objectHeight, 25); }); it('should set validateTargetOnHit to false by default', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.validateTargetOnHit, false); }); it('should set validateTargetOnHit when provided', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [], validateTargetOnHit: true }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.validateTargetOnHit, true); }); it('should inherit effect properties', () => { let modifier = new Modifier({ key: 'speed-buff', propertyKey: 'stats/speed', operation: ModifierConst.OPS.INC, value: 10 }); let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [modifier] }; let skill = new PhysicalEffect(skillData); assert.ok(Array.isArray(skill.targetEffects)); assert.strictEqual(skill.targetEffects.length, 1); }); }); describe('runSkillLogic', () => { it('should return false when target out of range', async () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, range: 10, rangePropertyX: 'position/x', rangePropertyY: 'position/y', magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); mockOwner.position = {x: 0, y: 0}; mockTarget.position = {x: 100, y: 100}; let result = await skill.runSkillLogic(); assert.strictEqual(result, false); }); it('should execute physical skill when in range', async () => { let executed = false; mockOwner.executePhysicalSkill = async (target, skill) => { executed = true; }; let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, rangePropertyX: 'position/x', rangePropertyY: 'position/y', targetEffects: [] }; let skill = new PhysicalEffect(skillData); await skill.runSkillLogic(); assert.strictEqual(executed, true); }); it('should return false after executing physical skill', async () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); let result = await skill.runSkillLogic(); assert.strictEqual(result, false); }); it('should pass target and skill to executePhysicalSkill', async () => { let receivedTarget = null; let receivedSkill = null; mockOwner.executePhysicalSkill = async (target, skill) => { receivedTarget = target; receivedSkill = skill; }; let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, rangePropertyX: 'position/x', rangePropertyY: 'position/y', targetEffects: [] }; let skill = new PhysicalEffect(skillData); await skill.runSkillLogic(); assert.strictEqual(receivedTarget, mockTarget); assert.strictEqual(receivedSkill, skill); }); }); describe('executeOnHit', () => { it('should fire physical effect hit event', async () => { let eventFired = false; let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); skill.listenEvent(SkillsEvents.SKILL_PHYSICAL_EFFECT_HIT, async () => { eventFired = true; }, 'testListener', skill.getOwnerEventKey()); await skill.executeOnHit(mockTarget); assert.strictEqual(eventFired, true); }); it('should apply modifiers to target', async () => { let modifier = new Modifier({ key: 'atk-buff', propertyKey: 'stats/atk', operation: ModifierConst.OPS.INC, value: 20 }); let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [modifier] }; let skill = new PhysicalEffect(skillData); let initialAtk = mockTarget.stats.atk; await skill.executeOnHit(mockTarget); assert.strictEqual(mockTarget.stats.atk, initialAtk + 20); }); it('should validate target when validateTargetOnHit is true', async () => { let differentTarget = new MockTarget(); differentTarget.id = 'different-id'; let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [], validateTargetOnHit: true }; let skill = new PhysicalEffect(skillData); let result = await skill.executeOnHit(differentTarget); assert.strictEqual(result, false); }); it('should skip validation when validateTargetOnHit is false', async () => { let differentTarget = new MockTarget(); differentTarget.id = 'different-id'; let modifier = new Modifier({ key: 'def-buff', propertyKey: 'stats/def', operation: ModifierConst.OPS.INC, value: 15 }); let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [modifier], validateTargetOnHit: false }; let skill = new PhysicalEffect(skillData); let initialDef = mockTarget.stats.def; await skill.executeOnHit(differentTarget); assert.strictEqual(mockTarget.stats.def, initialDef + 15); }); it('should call parent runSkillLogic', async () => { let modifier = new Modifier({ key: 'mp-buff', propertyKey: 'stats/mp', operation: ModifierConst.OPS.INC, value: 25 }); let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, range: 0, magnitude: 100, objectWidth: 10, objectHeight: 10, rangePropertyX: 'position/x', rangePropertyY: 'position/y', targetEffects: [modifier] }; let skill = new PhysicalEffect(skillData); let initialMp = mockTarget.stats.mp; await skill.executeOnHit(mockTarget); assert.strictEqual(mockTarget.stats.mp, initialMp + 25); }); it('should handle empty targetEffects array', async () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); await skill.executeOnHit(mockTarget); }); }); describe('Integration', () => { it('should handle complete physical effect flow', async () => { let physicalSkillExecuted = false; let hitExecuted = false; mockOwner.executePhysicalSkill = async (target, skill) => { physicalSkillExecuted = true; await skill.executeOnHit(target); }; let modifier = new Modifier({ key: 'hp-buff', propertyKey: 'stats/hp', operation: ModifierConst.OPS.INC, value: 25 }); let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, rangePropertyX: 'position/x', rangePropertyY: 'position/y', targetEffects: [modifier] }; let skill = new PhysicalEffect(skillData); skill.listenEvent(SkillsEvents.SKILL_PHYSICAL_EFFECT_HIT, async () => { hitExecuted = true; }, 'hitListener', skill.getOwnerEventKey()); let initialHp = mockTarget.stats.hp; await skill.runSkillLogic(); assert.strictEqual(physicalSkillExecuted, true); assert.strictEqual(hitExecuted, true); assert.strictEqual(mockTarget.stats.hp, initialHp + 25); }); it('should apply multiple modifiers', async () => { mockOwner.executePhysicalSkill = async (target, skill) => { await skill.executeOnHit(target); }; let modifiers = [ new Modifier({ key: 'atk-buff', propertyKey: 'stats/atk', operation: ModifierConst.OPS.INC, value: 10 }), new Modifier({ key: 'def-buff', propertyKey: 'stats/def', operation: ModifierConst.OPS.INC, value: 8 }) ]; let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, rangePropertyX: 'position/x', rangePropertyY: 'position/y', targetEffects: modifiers }; let skill = new PhysicalEffect(skillData); let initialAtk = mockTarget.stats.atk; let initialDef = mockTarget.stats.def; await skill.runSkillLogic(); assert.strictEqual(mockTarget.stats.atk, initialAtk + 10); assert.strictEqual(mockTarget.stats.def, initialDef + 8); }); it('should handle critical modifiers', async () => { mockOwner.executePhysicalSkill = async (target, skill) => { await skill.executeOnHit(target); }; let modifier = new Modifier({ key: 'mp-buff', propertyKey: 'stats/mp', operation: ModifierConst.OPS.INC, value: 20 }); let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, rangePropertyX: 'position/x', rangePropertyY: 'position/y', targetEffects: [modifier], criticalChance: 100, criticalMultiplier: 2 }; let skill = new PhysicalEffect(skillData); let initialMp = mockTarget.stats.mp; await skill.runSkillLogic(); assert.strictEqual(mockTarget.stats.mp, initialMp + (20 * 2)); }); it('should broadcast effect event', async () => { let eventFired = false; mockOwner.executePhysicalSkill = async (target, skill) => { await skill.executeOnHit(target); }; let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, rangePropertyX: 'position/x', rangePropertyY: 'position/y', targetEffects: [] }; let skill = new PhysicalEffect(skillData); skill.listenEvent(SkillsEvents.SKILL_EFFECT_TARGET_MODIFIERS, async () => { eventFired = true; }, 'effectListener', skill.getOwnerEventKey()); await skill.runSkillLogic(); assert.strictEqual(eventFired, true); }); }); describe('Edge Cases', () => { it('should handle zero magnitude', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 0, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.magnitude, 0); assert.strictEqual(skill.isReady, true); }); it('should handle zero object dimensions', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 100, objectWidth: 0, objectHeight: 0, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.objectWidth, 0); assert.strictEqual(skill.objectHeight, 0); assert.strictEqual(skill.isReady, true); }); it('should handle large magnitude values', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 999999, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.magnitude, 999999); }); it('should handle decimal magnitude', () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, magnitude: 150.75, objectWidth: 10, objectHeight: 10, targetEffects: [] }; let skill = new PhysicalEffect(skillData); assert.strictEqual(skill.magnitude, 150.75); }); it('should handle null target in runSkillLogic', async () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, target: null, magnitude: 100, objectWidth: 10, objectHeight: 10, rangePropertyX: 'position/x', rangePropertyY: 'position/y', targetEffects: [] }; let skill = new PhysicalEffect(skillData); await assert.rejects(async () => { await skill.runSkillLogic(); }); }); it('should handle target without stats property', async () => { mockOwner.executePhysicalSkill = async (target, skill) => { await skill.executeOnHit(target); }; let targetWithoutStats = { id: 'test-target', getPosition: () => ({x: 0, y: 0}), position: {x: 0, y: 0} }; let modifier = new Modifier({ key: 'new-stat', propertyKey: 'stats/newStat', operation: ModifierConst.OPS.SET_VALUE, value: 100 }); let skillData = { key: 'test-physical-effect', owner: mockOwner, target: targetWithoutStats, magnitude: 100, objectWidth: 10, objectHeight: 10, rangePropertyX: 'position/x', rangePropertyY: 'position/y', targetEffects: [modifier] }; let skill = new PhysicalEffect(skillData); await assert.rejects(async () => { await skill.runSkillLogic(); }); }); it('should handle modifier operations on undefined properties', async () => { let modifier = new Modifier({ key: 'undefined-buff', propertyKey: 'stats/undefinedProp', operation: ModifierConst.OPS.INC, value: 50 }); let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [modifier] }; let skill = new PhysicalEffect(skillData); await skill.executeOnHit(mockTarget); assert.strictEqual(mockTarget.stats.undefinedProp, 50); }); }); describe('executeOnHit - lastState on Validation Failure', () => { it('should set lastState when validateTargetOnHit fails', async () => { let differentTarget = new MockTarget(); differentTarget.id = 'different-id'; let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [], validateTargetOnHit: true }; let skill = new PhysicalEffect(skillData); await skill.executeOnHit(differentTarget); assert.ok(skill.lastState); }); }); describe('executeOnHit - Parent Throws During Execution', () => { it('should handle when parent runSkillLogic throws error', async () => { let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, range: 100, targetEffects: [] }; let skill = new PhysicalEffect(skillData); mockTarget.getPosition = () => { throw new Error('Parent error'); }; await assert.rejects(async () => { await skill.executeOnHit(mockTarget); }); }); }); describe('executeOnHit - Critical Multiplier from executeOnHit Path', () => { it('should apply critical multiplier when called from executeOnHit', async () => { mockOwner.executePhysicalSkill = async (target, skill) => { await skill.executeOnHit(target); }; let modifier = new Modifier({ key: 'stamina-buff', propertyKey: 'stats/stamina', operation: ModifierConst.OPS.INC, value: 10 }); let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, rangePropertyX: 'position/x', rangePropertyY: 'position/y', targetEffects: [modifier], criticalChance: 100, criticalMultiplier: 3 }; let skill = new PhysicalEffect(skillData); skill.isCritical = () => true; let initialStamina = mockTarget.stats.stamina; await skill.runSkillLogic(); assert.strictEqual(mockTarget.stats.stamina, initialStamina + 30); }); }); describe('executeOnHit - Multiple Hits on Same Target', () => { it('should handle multiple executeOnHit calls on same target', async () => { let modifier = new Modifier({ key: 'mp-buff', propertyKey: 'stats/mp', operation: ModifierConst.OPS.INC, value: 5 }); let skillData = { key: 'test-physical-effect', owner: mockOwner, target: mockTarget, magnitude: 100, objectWidth: 10, objectHeight: 10, targetEffects: [modifier], criticalChance: 0 }; let skill = new PhysicalEffect(skillData); let initialMp = mockTarget.stats.mp; await skill.executeOnHit(mockTarget); await skill.executeOnHit(mockTarget); await skill.executeOnHit(mockTarget); assert.strictEqual(mockTarget.stats.mp, initialMp + 15); }); }); });