@reldens/skills
Version:
1,240 lines (1,161 loc) • 49.7 kB
JavaScript
/**
*
* Reldens - ClassPath Unit Tests
*
*/
const { describe, it, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert');
const ClassPath = require('../../lib/class-path');
const Skill = require('../../lib/skill');
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');
const { BaseLevelsFixtures } = require('../fixtures/levels/base-levels');
const { BaseSkillsFixtures } = require('../fixtures/skills/base-skills');
describe('ClassPath', () => {
let mockOwner;
let mockTarget;
beforeEach(() => {
mockOwner = new MockOwner();
mockTarget = new MockTarget();
TestHelpers.clearEventListeners();
});
afterEach(() => {
TestHelpers.clearEventListeners();
});
describe('Constructor and Init', () => {
it('should initialize with basic properties', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
label: 'Warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(classPath.key, 'warrior');
assert.strictEqual(classPath.label, 'Warrior');
assert.strictEqual(classPath.currentLevel, 1);
});
it('should return false when key is missing', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
let result = await classPath.init({
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(result, false);
});
it('should use key as label when label is not provided', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'mage',
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(classPath.label, 'mage');
});
it('should initialize with labelsByLevel', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let labelsByLevel = {
1: 'Novice Warrior',
3: 'Experienced Warrior',
5: 'Master Warrior'
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
label: 'Warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
labelsByLevel: labelsByLevel
});
assert.strictEqual(classPath.currentLabel, 'Novice Warrior');
});
it('should initialize with skillsByLevel', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill1 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill1', owner: mockOwner});
let skill2 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill2', owner: mockOwner});
let skillsByLevel = {
1: [skill1],
2: [skill2]
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
skillsByLevel: skillsByLevel
});
assert.ok(classPath.currentSkills['skill1']);
assert.strictEqual(classPath.currentSkills['skill2'], undefined);
});
it('should fire INIT_CLASS_PATH_END event', async () => {
let eventFired = false;
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
classPath.listenEvent(SkillsEvents.INIT_CLASS_PATH_END, () => {
eventFired = true;
}, 'init-test');
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(eventFired, true);
});
});
describe('levelUp', () => {
it('should add skills on level up', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill1 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill1', owner: mockOwner});
let skill2 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill2', owner: mockOwner});
let skillsByLevel = {
1: [skill1],
2: [skill2]
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
skillsByLevel: skillsByLevel
});
assert.strictEqual(classPath.currentSkills['skill2'], undefined);
await classPath.levelUp();
assert.ok(classPath.currentSkills['skill2']);
});
it('should update label on level up', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let labelsByLevel = {
1: 'Novice',
2: 'Experienced'
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
label: 'Warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
labelsByLevel: labelsByLevel
});
assert.strictEqual(classPath.currentLabel, 'Novice');
await classPath.levelUp();
assert.strictEqual(classPath.currentLabel, 'Experienced');
});
it('should call parent levelUp', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
await classPath.levelUp();
assert.strictEqual(classPath.currentLevel, 2);
});
});
describe('levelDown', () => {
it('should remove skills on level down', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill1 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill1', owner: mockOwner});
let skill2 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill2', owner: mockOwner});
let skillsByLevel = {
1: [skill1],
2: [skill2]
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 2,
currentExp: 100,
skillsByLevel: skillsByLevel
});
assert.ok(classPath.currentSkills['skill2']);
await classPath.levelDown();
assert.strictEqual(classPath.currentSkills['skill2'], undefined);
});
it('should update label on level down', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let labelsByLevel = {
1: 'Novice',
2: 'Experienced'
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
label: 'Warrior',
levels: levels,
currentLevel: 2,
currentExp: 100,
labelsByLevel: labelsByLevel
});
assert.strictEqual(classPath.currentLabel, 'Experienced');
await classPath.levelDown();
assert.strictEqual(classPath.currentLabel, 'Novice');
});
});
describe('addSkills', () => {
it('should add skills to currentSkills', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
let skill = new Skill({...BaseSkillsFixtures.basicSkill, key: 'newSkill', owner: mockOwner});
await classPath.addSkills([skill]);
assert.ok(classPath.currentSkills['newSkill']);
});
it('should fire ADD_SKILLS_BEFORE event', async () => {
let eventFired = false;
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
classPath.listenEvent(SkillsEvents.ADD_SKILLS_BEFORE, () => {
eventFired = true;
}, 'add-before-test');
let skill = new Skill({...BaseSkillsFixtures.basicSkill, key: 'newSkill', owner: mockOwner});
await classPath.addSkills([skill]);
assert.strictEqual(eventFired, true);
});
it('should fire ADD_SKILLS_AFTER event', async () => {
let eventFired = false;
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
classPath.listenEvent(SkillsEvents.ADD_SKILLS_AFTER, () => {
eventFired = true;
}, 'add-after-test');
let skill = new Skill({...BaseSkillsFixtures.basicSkill, key: 'newSkill', owner: mockOwner});
await classPath.addSkills([skill]);
assert.strictEqual(eventFired, true);
});
});
describe('removeSkills', () => {
it('should remove skills from currentSkills', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill = new Skill({...BaseSkillsFixtures.basicSkill, key: 'toRemove', owner: mockOwner});
let skillsByLevel = {
1: [skill]
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
skillsByLevel: skillsByLevel
});
assert.ok(classPath.currentSkills['toRemove']);
await classPath.removeSkills(['toRemove']);
assert.strictEqual(classPath.currentSkills['toRemove'], undefined);
});
it('should fire REMOVE_SKILLS_BEFORE event', async () => {
let eventFired = false;
let levels = BaseLevelsFixtures.createLevelSet();
let skill = new Skill({...BaseSkillsFixtures.basicSkill, key: 'toRemove', owner: mockOwner});
let skillsByLevel = {
1: [skill]
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
skillsByLevel: skillsByLevel
});
classPath.listenEvent(SkillsEvents.REMOVE_SKILLS_BEFORE, () => {
eventFired = true;
}, 'remove-before-test');
await classPath.removeSkills(['toRemove']);
assert.strictEqual(eventFired, true);
});
it('should fire REMOVE_SKILLS_AFTER event', async () => {
let eventFired = false;
let levels = BaseLevelsFixtures.createLevelSet();
let skill = new Skill({...BaseSkillsFixtures.basicSkill, key: 'toRemove', owner: mockOwner});
let skillsByLevel = {
1: [skill]
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
skillsByLevel: skillsByLevel
});
classPath.listenEvent(SkillsEvents.REMOVE_SKILLS_AFTER, () => {
eventFired = true;
}, 'remove-after-test');
await classPath.removeSkills(['toRemove']);
assert.strictEqual(eventFired, true);
});
});
describe('setOwnerSkills', () => {
it('should set skills for current level', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill1 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill1', owner: mockOwner});
let skill2 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill2', owner: mockOwner});
let skill3 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill3', owner: mockOwner});
let skillsByLevel = {
1: [skill1],
2: [skill2],
3: [skill3]
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 2,
currentExp: 100,
skillsByLevel: skillsByLevel
});
assert.ok(classPath.currentSkills['skill1']);
assert.ok(classPath.currentSkills['skill2']);
assert.strictEqual(classPath.currentSkills['skill3'], undefined);
});
it('should fire SET_SKILLS event', async () => {
let eventFired = false;
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
classPath.listenEvent(SkillsEvents.SET_SKILLS, () => {
eventFired = true;
}, 'set-skills-test');
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(eventFired, true);
});
it('should use provided currentSkills', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill1 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'providedSkill', owner: mockOwner});
let providedSkills = {providedSkill: skill1};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
currentSkills: providedSkills
});
assert.ok(classPath.currentSkills['providedSkill']);
});
});
describe('getCurrentLabel', () => {
it('should return label from labelsByLevel', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let labelsByLevel = {
1: 'Novice',
2: 'Expert'
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
label: 'Warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
labelsByLevel: labelsByLevel
});
let label = classPath.getCurrentLabel();
assert.strictEqual(label, 'Novice');
});
it('should loop levels when exact match not found', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let labelsByLevel = {
1: 'Novice',
5: 'Master'
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
label: 'Warrior',
levels: levels,
currentLevel: 3,
currentExp: 250,
labelsByLevel: labelsByLevel
});
let label = classPath.getCurrentLabel();
assert.strictEqual(label, 'Novice');
});
it('should return base label when labelsByLevel is false', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
label: 'Warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
let label = classPath.getCurrentLabel();
assert.strictEqual(label, 'Warrior');
});
});
describe('getSkillsByLevelKeys', () => {
it('should extract skill keys from skillsByLevel', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill1 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill1', owner: mockOwner});
let skill2 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill2', owner: mockOwner});
let skillsByLevel = {
1: [skill1],
2: [skill2]
};
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
skillsByLevel: skillsByLevel
});
assert.ok(classPath.skillsByLevelKeys);
assert.ok(Array.isArray(classPath.skillsByLevelKeys[1]));
assert.ok(classPath.skillsByLevelKeys[1].includes('skill1'));
});
it('should return false when skillsByLevel is not set', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(classPath.skillsByLevelKeys, false);
});
});
describe('Error Conditions - Invalid Inputs', () => {
it('should return false with null key', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
let result = await classPath.init({
key: null,
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(result, false);
});
it('should return false with undefined key', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
let result = await classPath.init({
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(result, false);
});
it('should handle empty skillsByLevel', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
skillsByLevel: {}
});
assert.ok(classPath.currentSkills);
assert.strictEqual(Object.keys(classPath.currentSkills).length, 0);
});
it('should handle null skillsByLevel', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
skillsByLevel: null
});
assert.ok(classPath.currentSkills);
});
it('should handle empty labelsByLevel', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
label: 'Warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
labelsByLevel: {}
});
assert.strictEqual(classPath.currentLabel, 'Warrior');
});
});
describe('Event System - Parameter Variations', () => {
it('should fire event with masterKey', async () => {
let eventFired = false;
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
let masterKey = classPath.getOwnerEventKey();
classPath.listenEvent(SkillsEvents.INIT_CLASS_PATH_END, () => {
eventFired = true;
}, 'sub-key', masterKey);
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(eventFired, true);
});
it('should fire event without any keys', async () => {
let eventFired = false;
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
classPath.listenEvent(SkillsEvents.INIT_CLASS_PATH_END, () => {
eventFired = true;
});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(eventFired, true);
});
it('should handle multiple listeners', async () => {
let count = 0;
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
classPath.listenEvent(SkillsEvents.INIT_CLASS_PATH_END, () => { count++; }, 'key1');
classPath.listenEvent(SkillsEvents.INIT_CLASS_PATH_END, () => { count++; }, 'key2');
classPath.listenEvent(SkillsEvents.INIT_CLASS_PATH_END, () => { count++; }, 'key3');
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(count, 3);
});
});
describe('Edge Cases', () => {
it('should handle numeric key', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 123,
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(classPath.key, 123);
});
it('should handle affectedProperty', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0,
affectedProperty: 'stats/hp'
});
assert.strictEqual(classPath.affectedProperty, 'stats/hp');
});
it('should handle false affectedProperty', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(classPath.affectedProperty, false);
});
});
describe('loopLevelsForLabel - Direct Unit Tests', () => {
it('should return label from labelsByLevel when found', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 3,
label: 'Warrior',
labelsByLevel: {3: 'Veteran Warrior', 5: 'Master Warrior'}
});
let label = classPath.loopLevelsForLabel(classPath.currentLevel);
assert.strictEqual(label, 'Veteran Warrior');
});
it('should return base label when not found in labelsByLevel', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 2,
label: 'Warrior',
labelsByLevel: {3: 'Veteran Warrior'}
});
let label = classPath.loopLevelsForLabel(classPath.currentLevel);
assert.strictEqual(label, 'Warrior');
});
it('should handle null labelsByLevel', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 2,
label: 'Warrior'
});
classPath.labelsByLevel = null;
assert.throws(
() => classPath.loopLevelsForLabel(classPath.currentLevel),
TypeError
);
});
it('should handle empty labelsByLevel', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 2,
label: 'Warrior',
labelsByLevel: {}
});
let label = classPath.loopLevelsForLabel(classPath.currentLevel);
assert.strictEqual(label, 'Warrior');
});
it('should handle level 0', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 0,
label: 'Novice'
});
let label = classPath.loopLevelsForLabel(0);
assert.strictEqual(label, 'Novice');
});
it('should handle negative level', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
label: 'Warrior'
});
let label = classPath.loopLevelsForLabel(-5);
assert.ok(typeof label === 'string');
});
});
describe('init - Numeric Key Behavior', () => {
it('should handle numeric key values', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 123,
levels: levels,
currentLevel: 1
});
assert.strictEqual(classPath.key, 123);
});
});
describe('init - Parent Init Validation', () => {
it('should call parent LevelsSet init', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
currentExp: 0
});
assert.strictEqual(classPath.currentLevel, 1);
assert.strictEqual(classPath.currentExp, 0);
assert.ok(classPath.levels);
});
});
describe('levelUp - Skills and Labels', () => {
it('should handle levelUp with no skills at next level', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
skillsByLevel: {1: [new Skill({...BaseSkillsFixtures.basicSkill, owner: mockOwner})]}
});
await classPath.levelUp();
assert.strictEqual(classPath.currentLevel, 2);
});
it('should handle levelUp with no label at next level', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
label: 'Warrior',
labelsByLevel: {1: 'Novice Warrior'}
});
await classPath.levelUp();
assert.strictEqual(classPath.currentLevel, 2);
assert.ok(classPath.currentLabel);
});
it('should update skills and label in correct order', async () => {
let updateOrder = [];
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
label: 'Warrior',
labelsByLevel: {2: 'Advanced Warrior'},
skillsByLevel: {2: [new Skill({...BaseSkillsFixtures.basicSkill, owner: mockOwner})]}
});
let originalAddSkills = classPath.addSkills.bind(classPath);
classPath.addSkills = async (...args) => {
updateOrder.push('skills');
return await originalAddSkills(...args);
};
await classPath.levelUp();
assert.ok(updateOrder.includes('skills'));
assert.strictEqual(classPath.currentLevel, 2);
});
it('should fire ADD_SKILLS event when skills exist at new level', async () => {
let eventFired = false;
let levels = BaseLevelsFixtures.createLevelSet();
let skill = new Skill({...BaseSkillsFixtures.basicSkill, owner: mockOwner});
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
skillsByLevel: {2: [skill]}
});
classPath.listenEvent(SkillsEvents.ADD_SKILLS_AFTER, () => {
eventFired = true;
}, 'add-skills-listener');
await classPath.levelUp();
assert.strictEqual(eventFired, true);
});
});
describe('levelDown - Skills and Labels', () => {
it('should handle levelDown from level 1', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
let result = await classPath.levelDown();
assert.strictEqual(result, undefined);
});
it('should update skills and label in correct order on levelDown', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 2,
label: 'Warrior',
labelsByLevel: {1: 'Novice Warrior', 2: 'Advanced Warrior'}
});
let initialLevel = classPath.currentLevel;
await classPath.levelDown();
assert.strictEqual(classPath.currentLevel, initialLevel - 1);
assert.ok(classPath.currentLabel);
});
it('should maintain round-trip consistency on levelDown then levelUp', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill = new Skill({...BaseSkillsFixtures.basicSkill, key: 'level-2-skill', owner: mockOwner});
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 2,
label: 'Warrior',
labelsByLevel: {2: 'Advanced Warrior'},
skillsByLevel: {2: [skill]}
});
await classPath.levelDown();
assert.strictEqual(classPath.currentLevel, 1);
await classPath.levelUp();
assert.strictEqual(classPath.currentLevel, 2);
assert.strictEqual(classPath.currentLabel, 'Advanced Warrior');
});
});
describe('addSkills - Edge Cases', () => {
it('should handle adding empty skills array', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
await classPath.addSkills([]);
assert.ok(classPath.currentSkills);
});
it('should handle adding null skills', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
await assert.rejects(
async () => await classPath.addSkills(null),
TypeError
);
});
it('should handle adding undefined skills', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
await assert.rejects(
async () => await classPath.addSkills(undefined),
TypeError
);
});
it('should handle skills with duplicate keys', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill1 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'duplicate', owner: mockOwner});
let skill2 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'duplicate', owner: mockOwner});
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
await classPath.addSkills([skill1, skill2]);
assert.strictEqual(Object.keys(classPath.currentSkills).filter(k => k === 'duplicate').length, 1);
});
it('should handle skills with invalid key property', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let invalidSkill = new Skill({...BaseSkillsFixtures.basicSkill, owner: mockOwner});
invalidSkill.key = null;
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
await classPath.addSkills([invalidSkill]);
assert.ok(classPath.currentSkills);
});
it('should fire ADD_SKILLS events with correct parameters', async () => {
let beforeEventFired = false;
let afterEventFired = false;
let receivedSkills = null;
let levels = BaseLevelsFixtures.createLevelSet();
let skill = new Skill({...BaseSkillsFixtures.basicSkill, owner: mockOwner});
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
classPath.listenEvent(SkillsEvents.ADD_SKILLS_BEFORE, (cp, skills) => {
beforeEventFired = true;
receivedSkills = skills;
}, 'before-listener');
classPath.listenEvent(SkillsEvents.ADD_SKILLS_AFTER, () => {
afterEventFired = true;
}, 'after-listener');
await classPath.addSkills([skill]);
assert.strictEqual(beforeEventFired, true);
assert.strictEqual(afterEventFired, true);
assert.ok(receivedSkills);
});
});
describe('removeSkills - Edge Cases', () => {
it('should handle removing empty skills array', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
await classPath.removeSkills([]);
assert.ok(classPath.currentSkills);
});
it('should handle removing non-existent skill', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill = new Skill({...BaseSkillsFixtures.basicSkill, owner: mockOwner});
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
await classPath.addSkills([skill]);
let nonExistentSkill = new Skill({...BaseSkillsFixtures.basicSkill, key: 'non-existent', owner: mockOwner});
await classPath.removeSkills([nonExistentSkill]);
assert.ok(classPath.currentSkills[skill.key]);
});
it('should handle removing with mixed instances and keys', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill1 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill-1', owner: mockOwner});
let skill2 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill-2', owner: mockOwner});
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
await classPath.addSkills([skill1, skill2]);
await classPath.removeSkills([skill1, 'skill-2']);
assert.strictEqual(Object.keys(classPath.currentSkills).length, 0);
});
it('should fire REMOVE_SKILLS events with correct parameters', async () => {
let beforeEventFired = false;
let afterEventFired = false;
let receivedSkills = null;
let levels = BaseLevelsFixtures.createLevelSet();
let skill = new Skill({...BaseSkillsFixtures.basicSkill, owner: mockOwner});
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
classPath.listenEvent(SkillsEvents.REMOVE_SKILLS_BEFORE, (cp, skills) => {
beforeEventFired = true;
receivedSkills = skills;
}, 'remove-before-listener');
classPath.listenEvent(SkillsEvents.REMOVE_SKILLS_AFTER, () => {
afterEventFired = true;
}, 'remove-after-listener');
await classPath.addSkills([skill]);
await classPath.removeSkills([skill]);
assert.strictEqual(beforeEventFired, true);
assert.strictEqual(afterEventFired, true);
assert.ok(receivedSkills);
});
});
describe('setOwnerSkills - Edge Cases', () => {
it('should handle currentLevel = 0', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill = new Skill({...BaseSkillsFixtures.basicSkill, owner: mockOwner});
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 0,
skillsByLevel: {1: [skill]}
});
assert.ok(classPath.currentSkills);
});
it('should handle currentLevel = negative', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: -5
});
assert.ok(classPath.currentSkills);
});
it('should accumulate skills from multiple levels', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill1 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill-1', owner: mockOwner});
let skill2 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill-2', owner: mockOwner});
let skill3 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill-3', owner: mockOwner});
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 3,
skillsByLevel: {
1: [skill1],
2: [skill2],
3: [skill3]
}
});
assert.strictEqual(Object.keys(classPath.currentSkills).length, 3);
assert.ok(classPath.currentSkills['skill-1']);
assert.ok(classPath.currentSkills['skill-2']);
assert.ok(classPath.currentSkills['skill-3']);
});
it('should NOT include skills from future levels', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill1 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'skill-1', owner: mockOwner});
let futureSkill = new Skill({...BaseSkillsFixtures.basicSkill, key: 'future-skill', owner: mockOwner});
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
skillsByLevel: {
1: [skill1],
5: [futureSkill]
}
});
assert.ok(classPath.currentSkills['skill-1']);
assert.strictEqual(classPath.currentSkills['future-skill'], undefined);
});
});
describe('getCurrentLabel - Edge Cases', () => {
it('should handle labelsByLevel is null', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
label: 'Warrior'
});
classPath.labelsByLevel = null;
assert.throws(
() => classPath.getCurrentLabel(),
TypeError
);
});
it('should handle labelsByLevel is empty', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
label: 'Warrior',
labelsByLevel: {}
});
let label = classPath.getCurrentLabel();
assert.strictEqual(label, 'Warrior');
});
it('should handle currentLevel = 0', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 0,
label: 'Novice'
});
let label = classPath.getCurrentLabel();
assert.ok(typeof label === 'string');
});
it('should handle currentLevel = negative', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: -1,
label: 'Warrior'
});
let label = classPath.getCurrentLabel();
assert.ok(typeof label === 'string');
});
it('should handle label value is null', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
label: null
});
let label = classPath.getCurrentLabel();
assert.ok(label === null || typeof label === 'string');
});
it('should handle label value is undefined', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
let label = classPath.getCurrentLabel();
assert.ok(label === undefined || typeof label === 'string');
});
});
describe('getSkillsByLevelKeys - Edge Cases', () => {
it('should handle null key property', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
skillsByLevel: {1: [{key: null}]}
});
let skills = classPath.getSkillsByLevelKeys();
assert.ok(typeof skills === 'object' || skills === false);
});
it('should handle non-array values in skillsByLevel', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1
});
classPath.skillsByLevel = {1: 'not-an-array'};
let skills = classPath.getSkillsByLevelKeys();
assert.ok(typeof skills === 'object' || skills === false);
});
it('should handle duplicate keys across levels', async () => {
let levels = BaseLevelsFixtures.createLevelSet();
let skill1 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'duplicate', owner: mockOwner});
let skill2 = new Skill({...BaseSkillsFixtures.basicSkill, key: 'duplicate', owner: mockOwner});
let classPath = new ClassPath({owner: mockOwner});
await classPath.init({
key: 'warrior',
levels: levels,
currentLevel: 1,
skillsByLevel: {
1: [skill1],
2: [skill2]
}
});
let skillsByLevelKeys = classPath.getSkillsByLevelKeys();
assert.ok(typeof skillsByLevelKeys === 'object');
assert.ok(Array.isArray(skillsByLevelKeys['1']));
assert.ok(Array.isArray(skillsByLevelKeys['2']));
});
});
});