@reldens/skills
Version:
588 lines (549 loc) • 21.3 kB
JavaScript
/**
*
* Reldens - Effect Unit Tests
*
*/
const { describe, it, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert');
const Effect = require('../../../lib/types/effect');
const SkillsConst = require('../../../lib/constants');
const SkillsEvents = require('../../../lib/skills-events');
const { 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('Effect', () => {
let mockOwner;
let mockTarget;
beforeEach(() => {
mockOwner = new MockOwner();
mockTarget = new MockTarget();
TestHelpers.clearEventListeners();
});
afterEach(() => {
TestHelpers.clearEventListeners();
});
describe('Constructor', () => {
it('should initialize with effect type', () => {
let effectData = {...BaseSkillsFixtures.effectSkill, owner: mockOwner};
let effect = new Effect(effectData);
assert.strictEqual(effect.type, SkillsConst.SKILL.TYPE.EFFECT);
});
it('should set isReady to false when targetEffects is missing', () => {
let effectData = {key: 'test-effect', owner: mockOwner};
let effect = new Effect(effectData);
assert.strictEqual(effect.isReady, false);
});
it('should initialize with targetEffects', () => {
let modifier = new Modifier({
key: 'atk-buff',
propertyKey: 'stats/atk',
operation: ModifierConst.OPS.INC,
value: 5
});
let effectData = {
key: 'test-effect',
owner: mockOwner,
targetEffects: [modifier]
};
let effect = new Effect(effectData);
assert.ok(Array.isArray(effect.targetEffects));
assert.strictEqual(effect.targetEffects.length, 1);
});
it('should initialize with empty targetEffects array', () => {
let effectData = {
key: 'test-effect',
owner: mockOwner,
targetEffects: []
};
let effect = new Effect(effectData);
assert.ok(Array.isArray(effect.targetEffects));
assert.strictEqual(effect.targetEffects.length, 0);
});
});
describe('runSkillLogic', () => {
it('should return false when out of range', async () => {
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 10,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = mockTarget;
let result = await effect.runSkillLogic();
assert.strictEqual(result, false);
assert.strictEqual(effect.lastState, SkillsConst.SKILL_STATES.OUT_OF_RANGE);
});
it('should apply modifiers when in range', async () => {
let modifier = new Modifier({
key: 'atk-buff',
propertyKey: 'stats/atk',
operation: ModifierConst.OPS.INC,
value: 10
});
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: [modifier]
};
let effect = new Effect(effectData);
effect.target = mockTarget;
let initialAtk = mockTarget.stats.atk;
await effect.runSkillLogic();
assert.ok(mockTarget.stats.atk !== initialAtk);
assert.strictEqual(effect.lastState, SkillsConst.SKILL_STATES.APPLIED_EFFECTS);
});
it('should set lastState to APPLYING_EFFECTS', async () => {
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = mockTarget;
await effect.runSkillLogic();
assert.strictEqual(effect.lastState, SkillsConst.SKILL_STATES.APPLIED_EFFECTS);
});
it('should fire SKILL_EFFECT_TARGET_MODIFIERS event', async () => {
let eventFired = false;
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = mockTarget;
effect.listenEvent(SkillsEvents.SKILL_EFFECT_TARGET_MODIFIERS, () => {
eventFired = true;
}, 'effect-test');
await effect.runSkillLogic();
assert.strictEqual(eventFired, true);
});
});
describe('Error Conditions', () => {
it('should handle null target', async () => {
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = null;
let result = await effect.runSkillLogic();
assert.strictEqual(result, false);
});
it('should handle undefined target', async () => {
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = undefined;
let result = await effect.runSkillLogic();
assert.strictEqual(result, false);
});
it('should handle null targetEffects', () => {
let effectData = {
key: 'test-effect',
owner: mockOwner,
targetEffects: null
};
let effect = new Effect(effectData);
assert.strictEqual(effect.isReady, false);
});
it('should handle undefined targetEffects', () => {
let effectData = {
key: 'test-effect',
owner: mockOwner
};
let effect = new Effect(effectData);
assert.strictEqual(effect.isReady, false);
});
});
describe('Edge Cases - Range', () => {
it('should handle zero range', async () => {
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = mockTarget;
await effect.runSkillLogic();
assert.strictEqual(effect.lastState, SkillsConst.SKILL_STATES.APPLIED_EFFECTS);
});
it('should handle negative range', async () => {
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: -10,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = mockTarget;
await effect.runSkillLogic();
assert.ok(typeof effect.lastState === 'string');
});
it('should handle very large range', async () => {
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 10000,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = mockTarget;
await effect.runSkillLogic();
assert.strictEqual(effect.lastState, SkillsConst.SKILL_STATES.APPLIED_EFFECTS);
});
});
describe('Edge Cases - Modifiers', () => {
it('should handle multiple modifiers', async () => {
let modifier1 = new Modifier({
key: 'atk-buff',
propertyKey: 'stats/atk',
operation: ModifierConst.OPS.INC,
value: 5
});
let modifier2 = new Modifier({
key: 'def-buff',
propertyKey: 'stats/def',
operation: ModifierConst.OPS.INC,
value: 3
});
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: [modifier1, modifier2]
};
let effect = new Effect(effectData);
effect.target = mockTarget;
let initialAtk = mockTarget.stats.atk;
let initialDef = mockTarget.stats.def;
await effect.runSkillLogic();
assert.ok(mockTarget.stats.atk !== initialAtk);
assert.ok(mockTarget.stats.def !== initialDef);
});
it('should handle modifier with DEC operation', async () => {
let modifier = new Modifier({
key: 'atk-debuff',
propertyKey: 'stats/atk',
operation: ModifierConst.OPS.DEC,
value: 5
});
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: [modifier]
};
let effect = new Effect(effectData);
effect.target = mockTarget;
let initialAtk = mockTarget.stats.atk;
await effect.runSkillLogic();
assert.ok(mockTarget.stats.atk < initialAtk);
});
it('should handle modifier with SET operation', async () => {
let modifier = new Modifier({
key: 'atk-set',
propertyKey: 'stats/atk',
operation: ModifierConst.OPS.SET,
value: 100
});
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: [modifier]
};
let effect = new Effect(effectData);
effect.target = mockTarget;
await effect.runSkillLogic();
assert.strictEqual(mockTarget.stats.atk, 100);
});
it('should handle modifier with zero value', async () => {
let modifier = new Modifier({
key: 'atk-zero',
propertyKey: 'stats/atk',
operation: ModifierConst.OPS.INC,
value: 0
});
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: [modifier]
};
let effect = new Effect(effectData);
effect.target = mockTarget;
let initialAtk = mockTarget.stats.atk;
await effect.runSkillLogic();
assert.strictEqual(mockTarget.stats.atk, initialAtk);
});
it('should handle modifier with negative value', async () => {
let modifier = new Modifier({
key: 'atk-negative',
propertyKey: 'stats/atk',
operation: ModifierConst.OPS.INC,
value: -5
});
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: [modifier]
};
let effect = new Effect(effectData);
effect.target = mockTarget;
let initialAtk = mockTarget.stats.atk;
await effect.runSkillLogic();
assert.ok(mockTarget.stats.atk !== initialAtk);
});
});
describe('Event System - Parameter Variations', () => {
it('should fire event with removeKey only', async () => {
let eventFired = false;
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = mockTarget;
effect.listenEvent(SkillsEvents.SKILL_EFFECT_TARGET_MODIFIERS, () => {
eventFired = true;
}, 'remove-key');
await effect.runSkillLogic();
assert.strictEqual(eventFired, true);
});
it('should fire event with masterKey', async () => {
let eventFired = false;
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = mockTarget;
let masterKey = effect.getOwnerEventKey();
effect.listenEvent(SkillsEvents.SKILL_EFFECT_TARGET_MODIFIERS, () => {
eventFired = true;
}, 'sub-key', masterKey);
await effect.runSkillLogic();
assert.strictEqual(eventFired, true);
});
it('should fire event without any keys', async () => {
let eventFired = false;
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = mockTarget;
effect.listenEvent(SkillsEvents.SKILL_EFFECT_TARGET_MODIFIERS, () => {
eventFired = true;
});
await effect.runSkillLogic();
assert.strictEqual(eventFired, true);
});
it('should handle multiple listeners', async () => {
let count = 0;
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
targetEffects: []
};
let effect = new Effect(effectData);
effect.target = mockTarget;
effect.listenEvent(SkillsEvents.SKILL_EFFECT_TARGET_MODIFIERS, () => { count++; }, 'key1');
effect.listenEvent(SkillsEvents.SKILL_EFFECT_TARGET_MODIFIERS, () => { count++; }, 'key2');
effect.listenEvent(SkillsEvents.SKILL_EFFECT_TARGET_MODIFIERS, () => { count++; }, 'key3');
await effect.runSkillLogic();
assert.strictEqual(count, 3);
});
});
describe('Integration with Skill Base Class', () => {
it('should work with execute method', async () => {
let modifier = new Modifier({
key: 'hp-buff',
propertyKey: 'stats/hp',
operation: ModifierConst.OPS.INC,
value: 20
});
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
castTime: 0,
targetEffects: [modifier]
};
let effect = new Effect(effectData);
let initialHp = mockTarget.stats.hp;
let result = await effect.execute(mockTarget);
assert.strictEqual(result, true);
assert.ok(mockTarget.stats.hp !== initialHp);
});
it('should handle castTime with effects', async () => {
mockTarget.stats.stamina = 100;
let modifier = new Modifier({
key: 'stamina-buff',
propertyKey: 'stats/stamina',
operation: ModifierConst.OPS.INC,
value: 10
});
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
castTime: 100,
targetEffects: [modifier]
};
let effect = new Effect(effectData);
await effect.execute(mockTarget);
await TestHelpers.sleep(150);
assert.ok(mockTarget.stats.stamina > 100);
});
it('should handle skillDelay', async () => {
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
range: 0,
castTime: 0,
skillDelay: 1000,
targetEffects: []
};
let effect = new Effect(effectData);
await effect.execute(mockTarget);
let result = effect.validate();
assert.strictEqual(effect.canActivate, false);
});
});
describe('Constructor - Null/Undefined Owner', () => {
it('should handle null owner', () => {
let effectData = {...BaseSkillsFixtures.effectSkill, owner: null};
assert.throws(
() => new Effect(effectData),
TypeError
);
});
it('should handle undefined owner', () => {
let effectData = {...BaseSkillsFixtures.effectSkill};
delete effectData.owner;
assert.throws(
() => new Effect(effectData),
TypeError
);
});
});
describe('runSkillLogic - Critical Chance on Modifiers', () => {
it('should apply critical multiplier to modifiers', async () => {
let modifier = new Modifier({
key: 'atk-buff',
propertyKey: 'stats/atk',
operation: ModifierConst.OPS.INC,
value: 10
});
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
targetEffects: [modifier],
criticalChance: 100,
criticalMultiplier: 2,
range: 0,
castTime: 0
};
let effect = new Effect(effectData);
effect.isCritical = () => true;
let initialAtk = mockTarget.stats.atk;
await effect.execute(mockTarget);
assert.ok(mockTarget.stats.atk > initialAtk + 10);
});
});
describe('runSkillLogic - Empty Modifiers Behavior', () => {
it('should handle empty modifiers array', async () => {
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
targetEffects: [],
range: 0,
castTime: 0
};
let effect = new Effect(effectData);
let result = await effect.execute(mockTarget);
assert.strictEqual(result, true);
});
it('should handle null targetEffects', async () => {
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
targetEffects: null,
range: 0
};
let effect = new Effect(effectData);
let result = await effect.execute(mockTarget);
assert.ok(typeof result === 'boolean');
});
});
describe('runSkillLogic - Modifier Order Dependency', () => {
it('should apply modifiers in order', async () => {
let modifier1 = new Modifier({
key: 'set-atk',
propertyKey: 'stats/atk',
operation: ModifierConst.OPS.SET,
value: 20
});
let modifier2 = new Modifier({
key: 'mult-atk',
propertyKey: 'stats/atk',
operation: ModifierConst.OPS.MUL,
value: 2
});
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
targetEffects: [modifier1, modifier2],
range: 0,
criticalChance: 0,
castTime: 0
};
let effect = new Effect(effectData);
await effect.execute(mockTarget);
assert.strictEqual(mockTarget.stats.atk, 40);
});
});
describe('runSkillLogic - Return Value Consistency', () => {
it('should return true on successful effect application', async () => {
let modifier = new Modifier({
key: 'hp-buff',
propertyKey: 'stats/hp',
operation: ModifierConst.OPS.INC,
value: 10
});
let effectData = {
...BaseSkillsFixtures.effectSkill,
owner: mockOwner,
targetEffects: [modifier],
range: 0,
castTime: 0
};
let effect = new Effect(effectData);
let result = await effect.execute(mockTarget);
assert.strictEqual(result, true);
});
});
});