@reldens/skills
Version:
1,303 lines (1,195 loc) • 49.1 kB
JavaScript
/**
*
* Reldens - Skill Unit Tests
*
*/
const { describe, it, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert');
const Skill = require('../../lib/skill');
const SkillsConst = require('../../lib/constants');
const SkillsEvents = require('../../lib/skills-events');
const { Condition, Modifier, ModifierConst } = require('@reldens/modifiers');
const { TestHelpers } = require('../utils/test-helpers');
const { MockOwner } = require('../fixtures/mocks/mock-owner');
const { MockTarget } = require('../fixtures/mocks/mock-target');
const { BaseSkillsFixtures } = require('../fixtures/skills/base-skills');
describe('Skill', () => {
let mockOwner;
let mockTarget;
beforeEach(() => {
mockOwner = new MockOwner();
mockTarget = new MockTarget();
TestHelpers.clearEventListeners();
});
afterEach(() => {
TestHelpers.clearEventListeners();
});
describe('Constructor', () => {
it('should initialize with basic properties', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
assert.strictEqual(skill.key, 'test-basic-skill');
assert.strictEqual(skill.owner, mockOwner);
assert.strictEqual(skill.type, SkillsConst.SKILL.TYPE.BASE);
assert.strictEqual(skill.isReady, true);
assert.strictEqual(skill.canActivate, true);
});
it('should set isReady to false when key is missing', () => {
let skill = new Skill({owner: mockOwner});
assert.strictEqual(skill.isReady, false);
});
it('should set isReady to false when owner is missing', () => {
let skill = new Skill({key: 'test-skill', owner: {}});
assert.strictEqual(skill.isReady, false);
});
it('should set isReady to false when owner has no getPosition method', () => {
let invalidOwner = {id: 'test'};
let skill = new Skill({key: 'test-skill', owner: invalidOwner});
assert.strictEqual(skill.isReady, false);
});
it('should initialize with default values', () => {
let skillData = {key: 'test', owner: mockOwner};
let skill = new Skill(skillData);
assert.strictEqual(skill.skillDelay, 0);
assert.strictEqual(skill.castTime, 0);
assert.strictEqual(skill.range, 0);
assert.strictEqual(skill.usesLimit, 0);
assert.strictEqual(skill.uses, 0);
assert.strictEqual(skill.allowSelfTarget, false);
});
it('should handle custom properties', () => {
let skillData = {
key: 'custom-skill',
owner: mockOwner,
skillDelay: 1000,
castTime: 500,
range: 50,
usesLimit: 5,
criticalChance: 20,
criticalMultiplier: 1.5
};
let skill = new Skill(skillData);
assert.strictEqual(skill.skillDelay, 1000);
assert.strictEqual(skill.castTime, 500);
assert.strictEqual(skill.range, 50);
assert.strictEqual(skill.usesLimit, 5);
assert.strictEqual(skill.criticalChance, 20);
assert.strictEqual(skill.criticalMultiplier, 1.5);
});
});
describe('validate', () => {
it('should return false when skill is not ready', () => {
let skill = new Skill({owner: mockOwner});
let result = skill.validate();
assert.strictEqual(result, false);
});
it('should return false when canActivate is false', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
skill.canActivate = false;
let result = skill.validate();
assert.strictEqual(result, false);
});
it('should return false when owner is casting', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
mockOwner.isCasting = true;
let result = skill.validate();
assert.strictEqual(result, false);
});
it('should return false when uses limit is reached', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner, usesLimit: 3};
let skill = new Skill(skillData);
skill.uses = 3;
let result = skill.validate();
assert.strictEqual(result, false);
});
it('should return true for valid skill', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
let result = skill.validate();
assert.strictEqual(result, true);
});
it('should set canActivate to false when skillDelay is greater than 0', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner, skillDelay: 1000};
let skill = new Skill(skillData);
skill.validate();
assert.strictEqual(skill.canActivate, false);
});
});
describe('validateConditions', () => {
it('should return true when no conditions are set', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
let result = skill.validateConditions();
assert.strictEqual(result, true);
});
it('should return false when condition is not valid', () => {
let condition = new Condition({
key: 'hp-check',
propertyKey: 'stats/hp',
conditional: 'gt',
value: 200
});
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerConditions: [condition]
};
let skill = new Skill(skillData);
let result = skill.validateConditions();
assert.strictEqual(result, false);
});
it('should return true when all conditions are valid', () => {
let condition = new Condition({
key: 'hp-check',
propertyKey: 'stats/hp',
conditional: 'gt',
value: 50
});
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerConditions: [condition]
};
let skill = new Skill(skillData);
let result = skill.validateConditions();
assert.strictEqual(result, true);
});
});
describe('isInRange', () => {
it('should return true for infinite range (0)', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner, range: 0};
let skill = new Skill(skillData);
let result = skill.isInRange(
{x: 0, y: 0},
{x: 1000, y: 1000}
);
assert.strictEqual(result, true);
});
it('should return true when target is in range', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner, range: 50};
let skill = new Skill(skillData);
let result = skill.isInRange(
{x: 100, y: 100},
{x: 110, y: 110}
);
assert.strictEqual(result, true);
});
it('should return false when target is out of range', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner, range: 10};
let skill = new Skill(skillData);
let result = skill.isInRange(
{x: 0, y: 0},
{x: 100, y: 100}
);
assert.strictEqual(result, false);
});
});
describe('execute', () => {
it('should return false when skill is not ready', async () => {
let skill = new Skill({owner: mockOwner});
let result = await skill.execute(mockTarget);
assert.strictEqual(result, false);
});
it('should return false when target is undefined', async () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
let result = await skill.execute();
assert.strictEqual(result, false);
});
it('should execute skill logic successfully', async () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
let result = await skill.execute(mockTarget);
assert.strictEqual(result, true);
assert.strictEqual(skill.uses, 1);
});
it('should increment uses counter', async () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
await skill.execute(mockTarget);
assert.strictEqual(skill.uses, 1);
await skill.execute(mockTarget);
assert.strictEqual(skill.uses, 2);
});
});
describe('isCritical', () => {
it('should return false when criticalChance is 0', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner, criticalChance: 0};
let skill = new Skill(skillData);
let result = skill.isCritical();
assert.strictEqual(result, false);
});
it('should return boolean value when criticalChance is greater than 0', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner, criticalChance: 50};
let skill = new Skill(skillData);
let result = skill.isCritical();
assert.strictEqual(typeof result, 'boolean');
});
});
describe('applyCriticalValue', () => {
it('should return normal value when not critical', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: 0
};
let skill = new Skill(skillData);
let result = skill.applyCriticalValue(100);
assert.strictEqual(result, 100);
});
it('should apply criticalMultiplier when critical', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: 100,
criticalMultiplier: 2
};
let skill = new Skill(skillData);
let originalIsCritical = skill.isCritical.bind(skill);
skill.isCritical = () => true;
let result = skill.applyCriticalValue(100);
assert.strictEqual(result, 200);
skill.isCritical = originalIsCritical;
});
it('should apply criticalFixedValue when critical', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: 100,
criticalMultiplier: 1,
criticalFixedValue: 50
};
let skill = new Skill(skillData);
skill.isCritical = () => true;
let result = skill.applyCriticalValue(100);
assert.strictEqual(result, 150);
});
});
describe('getOwnerId', () => {
it('should return owner id', () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
assert.strictEqual(skill.getOwnerId(), 'mock-owner-1');
});
it('should use custom ownerIdProperty', () => {
let customOwner = {customId: 'custom-123', getPosition: () => ({x: 0, y: 0})};
let skillData = {
key: 'test',
owner: customOwner,
ownerIdProperty: 'customId'
};
let skill = new Skill(skillData);
assert.strictEqual(skill.getOwnerId(), 'custom-123');
});
});
describe('Events', () => {
it('should fire SKILL_BEFORE_EXECUTE event', async () => {
let eventFired = false;
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
skill.listenEvent(SkillsEvents.SKILL_BEFORE_EXECUTE, () => {
eventFired = true;
}, 'test-listener');
await skill.execute(mockTarget);
assert.strictEqual(eventFired, true);
});
it('should fire SKILL_AFTER_EXECUTE event', async () => {
let eventFired = false;
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
skill.listenEvent(SkillsEvents.SKILL_AFTER_EXECUTE, () => {
eventFired = true;
}, 'test-listener-after');
await skill.execute(mockTarget);
assert.strictEqual(eventFired, true);
});
it('should fire event with masterKey parameter', async () => {
let eventFired = false;
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
let masterKey = skill.getOwnerEventKey();
skill.listenEvent(SkillsEvents.SKILL_BEFORE_EXECUTE, () => {
eventFired = true;
}, 'sub-key', masterKey);
await skill.execute(mockTarget);
assert.strictEqual(eventFired, true);
});
it('should fire event without any keys', async () => {
let eventFired = false;
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
skill.listenEvent(SkillsEvents.SKILL_BEFORE_EXECUTE, () => {
eventFired = true;
});
await skill.execute(mockTarget);
assert.strictEqual(eventFired, true);
});
it('should handle multiple listeners on same event', async () => {
let count = 0;
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
skill.listenEvent(SkillsEvents.SKILL_BEFORE_EXECUTE, () => { count++; }, 'key1');
skill.listenEvent(SkillsEvents.SKILL_BEFORE_EXECUTE, () => { count++; }, 'key2');
skill.listenEvent(SkillsEvents.SKILL_BEFORE_EXECUTE, () => { count++; }, 'key3');
await skill.execute(mockTarget);
assert.strictEqual(count, 3);
});
it('should pass correct arguments to event listeners', async () => {
let receivedSkill = null;
let receivedTarget = null;
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
skill.listenEvent(SkillsEvents.SKILL_BEFORE_EXECUTE, (s, t) => {
receivedSkill = s;
receivedTarget = t;
}, 'arg-test');
await skill.execute(mockTarget);
assert.strictEqual(receivedSkill, skill);
assert.strictEqual(receivedTarget, mockTarget);
});
it('should fire VALIDATE_BEFORE event', () => {
let eventFired = false;
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
skill.listenEvent(SkillsEvents.VALIDATE_BEFORE, () => {
eventFired = true;
}, 'validate-before-test');
skill.validate();
assert.strictEqual(eventFired, true);
});
it('should fire VALIDATE_SUCCESS event', () => {
let eventFired = false;
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
skill.listenEvent(SkillsEvents.VALIDATE_SUCCESS, () => {
eventFired = true;
}, 'validate-success-test');
skill.validate();
assert.strictEqual(eventFired, true);
});
it('should fire VALIDATE_FAIL event when condition fails', () => {
let eventFired = false;
let failedCondition = null;
let condition = new Condition({
key: 'impossible',
propertyKey: 'stats/hp',
conditional: 'gt',
value: 999
});
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerConditions: [condition]
};
let skill = new Skill(skillData);
skill.listenEvent(SkillsEvents.VALIDATE_FAIL, (s, c) => {
eventFired = true;
failedCondition = c;
}, 'validate-fail-test');
skill.validate();
assert.strictEqual(eventFired, true);
assert.strictEqual(failedCondition, condition);
});
it('should fire all execution events in correct order', async () => {
let eventOrder = [];
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
skill.listenEvent(SkillsEvents.SKILL_BEFORE_EXECUTE, () => {
eventOrder.push('BEFORE_EXECUTE');
}, 'order1');
skill.listenEvent(SkillsEvents.SKILL_BEFORE_RUN_LOGIC, () => {
eventOrder.push('BEFORE_RUN_LOGIC');
}, 'order2');
skill.listenEvent(SkillsEvents.SKILL_AFTER_RUN_LOGIC, () => {
eventOrder.push('AFTER_RUN_LOGIC');
}, 'order3');
skill.listenEvent(SkillsEvents.SKILL_AFTER_EXECUTE, () => {
eventOrder.push('AFTER_EXECUTE');
}, 'order4');
await skill.execute(mockTarget);
assert.deepStrictEqual(eventOrder, [
'BEFORE_EXECUTE',
'BEFORE_RUN_LOGIC',
'AFTER_RUN_LOGIC',
'AFTER_EXECUTE'
]);
});
});
describe('Error Conditions - validateConditions', () => {
it('should return false with invalid condition type', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerConditions: [{not: 'a condition'}]
};
let skill = new Skill(skillData);
let result = skill.validateConditions();
assert.strictEqual(result, false);
});
it('should return false with null condition', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerConditions: [null]
};
let skill = new Skill(skillData);
let result = skill.validateConditions();
assert.strictEqual(result, false);
});
it('should return false with undefined in conditions array', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerConditions: [undefined]
};
let skill = new Skill(skillData);
let result = skill.validateConditions();
assert.strictEqual(result, false);
});
it('should handle mixed valid and invalid conditions', () => {
let validCondition = new Condition({
key: 'valid',
propertyKey: 'stats/hp',
conditional: 'gt',
value: 0
});
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerConditions: [validCondition, {invalid: true}]
};
let skill = new Skill(skillData);
let result = skill.validateConditions();
assert.strictEqual(result, false);
});
});
describe('Edge Cases - Range Validation', () => {
it('should handle negative range values', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
range: -50
};
let skill = new Skill(skillData);
let result = skill.isInRange({x: 0, y: 0}, {x: 100, y: 100});
assert.strictEqual(typeof result, 'boolean');
});
it('should handle exact range boundary', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
range: 10
};
let skill = new Skill(skillData);
let result = skill.isInRange({x: 0, y: 0}, {x: 10, y: 0});
assert.strictEqual(typeof result, 'boolean');
});
it('should handle decimal positions', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
range: 50
};
let skill = new Skill(skillData);
let result = skill.isInRange({x: 0.5, y: 0.5}, {x: 10.7, y: 10.3});
assert.strictEqual(typeof result, 'boolean');
});
it('should validateRange return false with missing range properties', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
range: 50
};
let skill = new Skill(skillData);
let result = skill.validateRange(mockTarget);
assert.strictEqual(result, false);
});
it('should validateRange work with valid range properties', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
range: 50,
rangePropertyX: 'position/x',
rangePropertyY: 'position/y'
};
let skill = new Skill(skillData);
let result = skill.validateRange(mockTarget);
assert.strictEqual(typeof result, 'boolean');
});
});
describe('Edge Cases - Critical Hits', () => {
it('should handle 100% critical chance', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: 100
};
let skill = new Skill(skillData);
let criticalHits = 0;
for(let i = 0; i < 10; i++){
if(skill.isCritical()){
criticalHits++;
}
}
assert.ok(criticalHits > 0);
});
it('should handle negative critical chance', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: -50
};
let skill = new Skill(skillData);
let result = skill.isCritical();
assert.strictEqual(result, false);
});
it('should apply negative critical multiplier', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: 100,
criticalMultiplier: -2
};
let skill = new Skill(skillData);
skill.isCritical = () => true;
let result = skill.applyCriticalValue(100);
assert.strictEqual(result, -200);
});
it('should apply zero critical multiplier', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: 100,
criticalMultiplier: 0
};
let skill = new Skill(skillData);
skill.isCritical = () => true;
let result = skill.applyCriticalValue(100);
assert.strictEqual(result, 0);
});
it('should handle both multiplier and fixed value', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: 100,
criticalMultiplier: 2,
criticalFixedValue: 50
};
let skill = new Skill(skillData);
skill.isCritical = () => true;
let result = skill.applyCriticalValue(100);
assert.strictEqual(result, 250);
});
it('should getCriticalDiff return correct difference', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: 100,
criticalMultiplier: 2
};
let skill = new Skill(skillData);
skill.isCritical = () => true;
let diff = skill.getCriticalDiff(100);
assert.strictEqual(diff, 100);
});
});
describe('Edge Cases - Uses and Limits', () => {
it('should handle uses at exactly limit', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
usesLimit: 5
};
let skill = new Skill(skillData);
skill.uses = 5;
let result = skill.validate();
assert.strictEqual(result, false);
});
it('should handle uses beyond limit', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
usesLimit: 5
};
let skill = new Skill(skillData);
skill.uses = 10;
let result = skill.validate();
assert.strictEqual(result, false);
});
it('should increment uses on each execution', async () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
assert.strictEqual(skill.uses, 0);
await skill.execute(mockTarget);
assert.strictEqual(skill.uses, 1);
await skill.execute(mockTarget);
assert.strictEqual(skill.uses, 2);
await skill.execute(mockTarget);
assert.strictEqual(skill.uses, 3);
});
it('should handle negative usesLimit', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
usesLimit: -1
};
let skill = new Skill(skillData);
let result = skill.validate();
assert.strictEqual(result, true);
});
});
describe('Edge Cases - Skill Delay and Timers', () => {
it('should set canActivate to false with positive delay', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
skillDelay: 1000
};
let skill = new Skill(skillData);
skill.validate();
assert.strictEqual(skill.canActivate, false);
assert.ok(skill.skillActivationTimer);
});
it('should handle zero skill delay', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
skillDelay: 0
};
let skill = new Skill(skillData);
skill.validate();
assert.strictEqual(skill.canActivate, true);
});
it('should handle negative skill delay', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
skillDelay: -1000
};
let skill = new Skill(skillData);
skill.validate();
assert.strictEqual(skill.canActivate, true);
});
it('should restore canActivate after delay', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
skillDelay: 100
};
let skill = new Skill(skillData);
skill.validate();
assert.strictEqual(skill.canActivate, false);
await TestHelpers.sleep(150);
assert.strictEqual(skill.canActivate, true);
});
});
describe('Edge Cases - Owner Effects', () => {
it('should apply owner effects when provided', async () => {
let modifier = new Modifier({
key: 'hp-cost',
propertyKey: 'stats/hp',
operation: ModifierConst.OPS.DEC,
value: 10
});
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerEffects: [modifier]
};
let skill = new Skill(skillData);
let initialHp = mockOwner.stats.hp;
await skill.execute(mockTarget);
assert.ok(mockOwner.stats.hp !== initialHp);
});
it('should fire SKILL_APPLY_OWNER_EFFECTS event', async () => {
let eventFired = false;
let modifier = new Modifier({
key: 'mp-cost',
propertyKey: 'stats/mp',
operation: ModifierConst.OPS.DEC,
value: 5
});
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerEffects: [modifier]
};
let skill = new Skill(skillData);
skill.listenEvent(SkillsEvents.SKILL_APPLY_OWNER_EFFECTS, () => {
eventFired = true;
}, 'owner-effects-test');
await skill.execute(mockTarget);
assert.strictEqual(eventFired, true);
});
it('should handle empty ownerEffects array', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerEffects: []
};
let skill = new Skill(skillData);
let result = await skill.execute(mockTarget);
assert.strictEqual(typeof result, 'boolean');
});
});
describe('Error Conditions - Missing Required Data', () => {
it('should handle missing target on execute', async () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
let result = await skill.execute();
assert.strictEqual(result, false);
});
it('should handle null target', async () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
let result = await skill.execute(null);
assert.strictEqual(result, false);
});
it('should handle undefined target', async () => {
let skillData = {...BaseSkillsFixtures.basicSkill, owner: mockOwner};
let skill = new Skill(skillData);
let result = await skill.execute(undefined);
assert.strictEqual(result, false);
});
it('should work with fixed target from props', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
target: mockTarget
};
let skill = new Skill(skillData);
let result = await skill.execute();
assert.strictEqual(result, true);
});
});
describe('Async Execution and Timing', () => {
it('should handle cast time correctly', async () => {
let castStarted = false;
let castFinished = false;
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
castTime: 100
};
let skill = new Skill(skillData);
skill.listenEvent(SkillsEvents.SKILL_BEFORE_CAST, () => {
castStarted = true;
}, 'cast-start');
skill.listenEvent(SkillsEvents.SKILL_AFTER_CAST, () => {
castFinished = true;
}, 'cast-finish');
await skill.execute(mockTarget);
assert.strictEqual(castStarted, true);
assert.strictEqual(mockOwner.isCasting, true);
await TestHelpers.sleep(150);
assert.strictEqual(castFinished, true);
assert.strictEqual(mockOwner.isCasting, false);
});
it('should not set isCasting with zero cast time', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
castTime: 0
};
let skill = new Skill(skillData);
await skill.execute(mockTarget);
assert.strictEqual(mockOwner.isCasting, false);
});
});
describe('isValidRange', () => {
it('should return false when rangeAutomaticValidation is false', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
rangeAutomaticValidation: false,
rangePropertyX: 'x',
rangePropertyY: 'y'
};
let skill = new Skill(skillData);
let result = await skill.isValidRange(mockTarget);
assert.strictEqual(result, false);
});
it('should return false when rangePropertyX is missing', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
rangeAutomaticValidation: true,
rangePropertyY: 'y'
};
let skill = new Skill(skillData);
let result = await skill.isValidRange(mockTarget);
assert.strictEqual(result, false);
});
it('should return false when rangePropertyY is missing', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
rangeAutomaticValidation: true,
rangePropertyX: 'x'
};
let skill = new Skill(skillData);
let result = await skill.isValidRange(mockTarget);
assert.strictEqual(result, false);
});
it('should return true when all properties set and target out of range', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
rangeAutomaticValidation: true,
rangePropertyX: 'position/x',
rangePropertyY: 'position/y',
range: 1
};
let skill = new Skill(skillData);
let result = await skill.isValidRange(mockTarget);
assert.strictEqual(result, true);
});
it('should return false when target is in range', async () => {
mockTarget.getPosition = () => ({x: 100, y: 100});
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
rangeAutomaticValidation: true,
rangePropertyX: 'position/x',
rangePropertyY: 'position/y',
range: 100
};
let skill = new Skill(skillData);
let result = await skill.isValidRange(mockTarget);
assert.strictEqual(result, false);
});
});
describe('onExecuteConditions', () => {
it('should return true by default', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner
};
let skill = new Skill(skillData);
let result = skill.onExecuteConditions();
assert.strictEqual(result, true);
});
it('should be called during execute', async () => {
let conditionCalled = false;
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner
};
let skill = new Skill(skillData);
skill.onExecuteConditions = () => {
conditionCalled = true;
return true;
};
await skill.execute(mockTarget);
assert.strictEqual(conditionCalled, true);
});
it('should prevent execution when returns false', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner
};
let skill = new Skill(skillData);
skill.onExecuteConditions = () => false;
let result = await skill.execute(mockTarget);
assert.strictEqual(result, false);
});
});
describe('onExecuteRewards', () => {
it('should be callable without errors', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner
};
let skill = new Skill(skillData);
await skill.onExecuteRewards();
assert.ok(true);
});
it('should be called during execute', async () => {
let rewardsCalled = false;
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner
};
let skill = new Skill(skillData);
skill.onExecuteRewards = async () => {
rewardsCalled = true;
};
await skill.execute(mockTarget);
assert.strictEqual(rewardsCalled, true);
});
});
describe('getOwnerUniqueEventKey', () => {
it('should generate unique key with timestamp', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner
};
let skill = new Skill(skillData);
let key1 = skill.getOwnerUniqueEventKey();
assert.ok(key1.includes('skills.ownerId'));
assert.ok(key1.includes('.uKey.'));
});
it('should append suffix when provided', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner
};
let skill = new Skill(skillData);
let key = skill.getOwnerUniqueEventKey('testSuffix');
assert.ok(key.endsWith('.testSuffix'));
});
it('should use owner eventUniqueKey when available', () => {
let customOwner = {
...mockOwner,
eventUniqueKey: () => 'custom-unique-key'
};
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: customOwner
};
let skill = new Skill(skillData);
let key = skill.getOwnerUniqueEventKey();
assert.ok(key.includes('custom-unique-key'));
});
});
describe('execute with autoValidation', () => {
it('should validate range when autoValidation is true', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
rangeAutomaticValidation: true,
rangePropertyX: 'position/x',
rangePropertyY: 'position/y',
range: 1
};
let skill = new Skill(skillData);
let result = await skill.execute(mockTarget);
assert.strictEqual(result, false);
});
});
describe('Constructor with null owner', () => {
it('should handle explicitly null owner', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: null
};
assert.throws(() => new Skill(skillData), TypeError);
});
});
describe('Constructor with false values', () => {
it('should handle false allowSelfTarget', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
allowSelfTarget: false
};
let skill = new Skill(skillData);
assert.strictEqual(skill.allowSelfTarget, false);
});
it('should handle false rangeAutomaticValidation', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
rangeAutomaticValidation: false
};
let skill = new Skill(skillData);
assert.strictEqual(skill.rangeAutomaticValidation, false);
});
});
describe('validate with multiple conditions', () => {
it('should return false when both canActivate false and owner casting', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
skillDelay: 100
};
let skill = new Skill(skillData);
skill.validate();
mockOwner.isCasting = true;
let result = skill.validate();
assert.strictEqual(result, false);
});
});
describe('validateRange with target properties', () => {
it('should work with rangeTargetPropertyX', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
rangePropertyX: 'position/x',
rangePropertyY: 'position/y',
rangeTargetPropertyX: 'targetX',
rangeTargetPropertyY: 'targetY',
range: 10
};
let skill = new Skill(skillData);
mockTarget.targetX = 105;
mockTarget.targetY = 105;
let result = skill.validateRange(mockTarget);
assert.strictEqual(result, true);
});
it('should handle target with undefined positions', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
rangePropertyX: 'position/x',
rangePropertyY: 'position/y',
range: 10
};
let skill = new Skill(skillData);
let badTarget = {getPosition: () => ({x: undefined, y: 110}), position: {x: undefined, y: 110}};
assert.throws(() => skill.validateRange(badTarget), Error);
});
it('should handle target with NaN positions', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
rangePropertyX: 'position/x',
rangePropertyY: 'position/y',
range: 10
};
let skill = new Skill(skillData);
let badTarget = {getPosition: () => ({x: NaN, y: 110}), position: {x: NaN, y: 110}};
let result = skill.validateRange(badTarget);
assert.strictEqual(result, false);
});
});
describe('isInRange same position', () => {
it('should return true when distance is 0', () => {
mockTarget.getPosition = () => ({x: 100, y: 100});
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
range: 10
};
let skill = new Skill(skillData);
let result = skill.isInRange(mockOwner.getPosition(), mockTarget.getPosition());
assert.strictEqual(result, true);
});
});
describe('execute with fixed target', () => {
it('should not overwrite fixed target', async () => {
let fixedTarget = TestHelpers.createMockTarget('fixed-target');
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
target: fixedTarget
};
let skill = new Skill(skillData);
await skill.execute(mockTarget);
assert.strictEqual(skill.target, mockTarget);
});
});
describe('execute with ownerEffects variations', () => {
it('should handle ownerEffects as empty array', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerEffects: []
};
let skill = new Skill(skillData);
await skill.execute(mockTarget);
assert.ok(true);
});
it('should handle ownerEffects as null', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
ownerEffects: null
};
let skill = new Skill(skillData);
await skill.execute(mockTarget);
assert.ok(true);
});
});
describe('applySkillLogicOnTarget with negative castTime', () => {
it('should handle negative castTime', async () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
castTime: -100
};
let skill = new Skill(skillData);
await skill.execute(mockTarget);
assert.strictEqual(mockOwner.isCasting, false);
});
});
describe('applyModifiers with avoidCritical', () => {
it('should not apply critical when avoidCritical is true', () => {
let modifier = new Modifier({
key: 'test-mod',
propertyKey: 'stats/hp',
operation: ModifierConst.OPS.INC,
value: 10
});
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: 100
};
let skill = new Skill(skillData);
skill.applyModifiers({mod: modifier}, mockTarget, true);
assert.ok(skill.lastAppliedModifiers);
});
it('should populate lastAppliedModifiers correctly', () => {
let modifier = new Modifier({
key: 'test-mod',
propertyKey: 'stats/hp',
operation: ModifierConst.OPS.INC,
value: 10
});
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner
};
let skill = new Skill(skillData);
skill.applyModifiers({mod: modifier}, mockTarget, false);
assert.ok(skill.lastAppliedModifiers['stats/hp']);
});
});
describe('getCriticalDiff edge cases', () => {
it('should return 0 when not critical', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: 0
};
let skill = new Skill(skillData);
let diff = skill.getCriticalDiff(100);
assert.strictEqual(diff, 0);
});
it('should handle negative normalValue', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalMultiplier: 2
};
let skill = new Skill(skillData);
let result = skill.applyCriticalValue(-10);
assert.ok(typeof result === 'number');
});
it('should handle zero normalValue', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalMultiplier: 2
};
let skill = new Skill(skillData);
let result = skill.applyCriticalValue(0);
assert.strictEqual(result, 0);
});
});
describe('isCritical with floating point', () => {
it('should handle floating point criticalChance', () => {
let skillData = {
...BaseSkillsFixtures.basicSkill,
owner: mockOwner,
criticalChance: 50.5
};
let skill = new Skill(skillData);
let result = skill.isCritical();
assert.ok(typeof result === 'boolean');
});
});
});