UNPKG

@reldens/skills

Version:
1,228 lines (1,125 loc) 49.8 kB
/** * * Reldens - LevelsSet Unit Tests * */ const { describe, it, beforeEach, afterEach } = require('node:test'); const assert = require('node:assert'); const LevelsSet = require('../../lib/levels-set'); const Level = require('../../lib/level'); 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 { BaseLevelsFixtures } = require('../fixtures/levels/base-levels'); describe('LevelsSet', () => { let mockOwner; beforeEach(() => { mockOwner = new MockOwner(); TestHelpers.clearEventListeners(); }); afterEach(() => { TestHelpers.clearEventListeners(); }); describe('Constructor', () => { it('should initialize with default properties', () => { let levelsSet = new LevelsSet(); assert.ok(levelsSet); assert.strictEqual(levelsSet.owner, false); }); it('should initialize with owner', () => { let levelsSet = new LevelsSet({owner: mockOwner}); assert.strictEqual(levelsSet.owner, mockOwner); }); }); describe('init', () => { it('should return false when owner is undefined', async () => { let levelsSet = new LevelsSet(); let result = await levelsSet.init({levels: {}}); assert.strictEqual(result, false); }); it('should return false when owner has no getPosition method', async () => { let invalidOwner = {id: 'test'}; let levelsSet = new LevelsSet({owner: invalidOwner}); let result = await levelsSet.init({levels: {}}); assert.strictEqual(result, false); }); it('should return false when levels are not provided', async () => { let levelsSet = new LevelsSet({owner: mockOwner}); let result = await levelsSet.init({}); assert.strictEqual(result, false); }); it('should initialize successfully with valid data', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0 }); assert.strictEqual(levelsSet.currentLevel, 1); assert.strictEqual(levelsSet.currentExp, 0); assert.ok(levelsSet.levels); }); it('should auto-fill levels when autoFillRanges is true', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, autoFillRanges: true }); assert.ok(levelsSet.levels[4]); assert.strictEqual(levelsSet.levels[4].key, 4); }); }); describe('levelUp', () => { it('should increase level by 1', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0 }); await levelsSet.levelUp(); assert.strictEqual(levelsSet.currentLevel, 2); }); it('should return false at max level', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 5, currentExp: 0 }); let result = await levelsSet.levelUp(); assert.strictEqual(result, false); }); it('should fire LEVEL_UP event', async () => { let eventFired = false; let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0 }); levelsSet.listenEvent(SkillsEvents.LEVEL_UP, () => { eventFired = true; }, 'test-listener'); await levelsSet.levelUp(); assert.strictEqual(eventFired, true); }); }); describe('levelDown', () => { it('should decrease level by 1', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 2, currentExp: 100 }); await levelsSet.levelDown(); assert.strictEqual(levelsSet.currentLevel, 1); }); it('should return false at level 1', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0 }); let result = await levelsSet.levelDown(); assert.strictEqual(result, false); }); }); describe('addExperience', () => { it('should add experience correctly', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, increaseLevelsWithExperience: false }); await levelsSet.addExperience(50); assert.strictEqual(levelsSet.currentExp, 50); }); it('should auto level up when experience threshold is reached', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, increaseLevelsWithExperience: true }); await levelsSet.addExperience(100); assert.strictEqual(levelsSet.currentLevel, 2); assert.strictEqual(levelsSet.currentExp, 100); }); it('should fire LEVEL_EXPERIENCE_ADDED event', async () => { let eventFired = false; let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0 }); levelsSet.listenEvent(SkillsEvents.LEVEL_EXPERIENCE_ADDED, () => { eventFired = true; }, 'test-listener-exp'); await levelsSet.addExperience(50); assert.strictEqual(eventFired, true); }); }); describe('getNextLevelExperience', () => { it('should return next level required experience', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0 }); let nextExp = levelsSet.getNextLevelExperience(); assert.strictEqual(nextExp, 100); }); it('should return current level experience at max level', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 5, currentExp: 500 }); let nextExp = levelsSet.getNextLevelExperience(); assert.strictEqual(nextExp, 500); }); }); describe('getLevelInstance', () => { it('should return level instance by key', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0 }); let level = levelsSet.getLevelInstance(2); assert.ok(level); assert.strictEqual(level.key, 2); }); it('should return false for non-existent level', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0 }); let level = levelsSet.getLevelInstance(99); assert.strictEqual(level, false); }); it('should handle string level keys', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); let level = levelsSet.getLevelInstance('2'); assert.ok(level); }); it('should handle null level key', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); let level = levelsSet.getLevelInstance(null); assert.strictEqual(level, false); }); it('should handle undefined level key', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); let level = levelsSet.getLevelInstance(undefined); assert.strictEqual(level, false); }); }); describe('Event System - Parameter Variations', () => { it('should fire INIT_LEVEL_SET_START with removeKey only', async () => { let eventFired = false; let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); levelsSet.setOwner({owner: mockOwner}); levelsSet.listenEvent(SkillsEvents.INIT_LEVEL_SET_START, () => { eventFired = true; }, 'init-start-key'); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); assert.strictEqual(eventFired, true); }); it('should fire INIT_LEVEL_SET_END with masterKey', async () => { let eventFired = false; let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); let masterKey = levelsSet.getOwnerEventKey(); levelsSet.listenEvent(SkillsEvents.INIT_LEVEL_SET_END, () => { eventFired = true; }, 'init-end-sub', masterKey); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); assert.strictEqual(eventFired, true); }); it('should fire SET_LEVELS without any keys', async () => { let eventFired = false; let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); levelsSet.listenEvent(SkillsEvents.SET_LEVELS, () => { eventFired = true; }); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); assert.strictEqual(eventFired, true); }); it('should fire LEVEL_DOWN event', async () => { let eventFired = false; let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 2, currentExp: 100}); levelsSet.listenEvent(SkillsEvents.LEVEL_DOWN, () => { eventFired = true; }, 'level-down-test'); await levelsSet.levelDown(); assert.strictEqual(eventFired, true); }); it('should fire LEVEL_APPLY_MODIFIERS event', async () => { let eventFired = false; let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); levelsSet.listenEvent(SkillsEvents.LEVEL_APPLY_MODIFIERS, () => { eventFired = true; }, 'apply-modifiers-test'); await levelsSet.levelUp(); assert.strictEqual(eventFired, true); }); it('should fire GENERATED_LEVELS event when auto-filling', async () => { let eventFired = false; let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); levelsSet.listenEvent(SkillsEvents.GENERATED_LEVELS, () => { eventFired = true; }, 'generated-levels-test'); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, autoFillRanges: true }); assert.strictEqual(eventFired, true); }); it('should handle multiple listeners on same event', async () => { let count = 0; let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); levelsSet.listenEvent(SkillsEvents.LEVEL_UP, () => { count++; }, 'listener1'); levelsSet.listenEvent(SkillsEvents.LEVEL_UP, () => { count++; }, 'listener2'); levelsSet.listenEvent(SkillsEvents.LEVEL_UP, () => { count++; }, 'listener3'); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); await levelsSet.levelUp(); assert.strictEqual(count, 3); }); }); describe('Error Conditions - Invalid Inputs', () => { it('should return false when levels is empty object', async () => { let levelsSet = new LevelsSet({owner: mockOwner}); let result = await levelsSet.init({levels: {}}); assert.strictEqual(result, false); }); it('should return false when levels is null', async () => { let levelsSet = new LevelsSet({owner: mockOwner}); let result = await levelsSet.init({levels: null, owner: mockOwner}); assert.strictEqual(result, false); }); it('should return false when levels is array', async () => { let levelsSet = new LevelsSet({owner: mockOwner}); let result = await levelsSet.init({levels: []}); assert.strictEqual(result, false); }); it('should handle null owner', async () => { let levelsSet = new LevelsSet({owner: null}); let result = await levelsSet.init({levels: {}}); assert.strictEqual(result, false); }); it('should handle undefined owner', async () => { let levelsSet = new LevelsSet({owner: undefined}); let result = await levelsSet.init({levels: {}}); assert.strictEqual(result, false); }); it('should return false in setOwner when owner is missing', () => { let levelsSet = new LevelsSet(); let result = levelsSet.setOwner({}); assert.strictEqual(result, false); }); it('should return false in setOwner when owner has no getPosition', () => { let levelsSet = new LevelsSet(); let result = levelsSet.setOwner({owner: {id: 'test'}}); assert.strictEqual(result, false); }); }); describe('Edge Cases - Auto-Fill', () => { it('should auto-fill with custom multiplier', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, autoFillRanges: true, autoFillExperienceMultiplier: 2 }); assert.ok(levelsSet.levels[4]); assert.strictEqual(levelsSet.autoFillExperienceMultiplier, 2); }); it('should handle zero multiplier', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, autoFillRanges: true, autoFillExperienceMultiplier: 0 }); assert.strictEqual(levelsSet.autoFillExperienceMultiplier, 0); }); it('should handle negative multiplier', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, autoFillRanges: true, autoFillExperienceMultiplier: -1 }); assert.strictEqual(levelsSet.autoFillExperienceMultiplier, -1); }); it('should not auto-fill when disabled', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, autoFillRanges: false }); assert.strictEqual(levelsSet.levels[4], undefined); }); }); describe('Edge Cases - Level Boundaries', () => { it('should handle currentLevel 0', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 0, currentExp: 0}); assert.strictEqual(levelsSet.currentLevel, 0); }); it('should handle negative currentLevel', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: -1, currentExp: 0}); assert.strictEqual(levelsSet.currentLevel, -1); }); it('should handle level beyond max defined level', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 100, currentExp: 0}); assert.strictEqual(levelsSet.currentLevel, 100); }); }); describe('Edge Cases - Experience', () => { it('should add negative experience', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 100, increaseLevelsWithExperience: false }); await levelsSet.addExperience(-50); assert.strictEqual(levelsSet.currentExp, 50); }); it('should handle zero experience addition', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 50, increaseLevelsWithExperience: false }); await levelsSet.addExperience(0); assert.strictEqual(levelsSet.currentExp, 50); }); it('should handle very large experience addition', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, increaseLevelsWithExperience: true, autoFillRanges: true }); await levelsSet.addExperience(10000); assert.strictEqual(levelsSet.currentLevel, 5); }); it('should handle decimal experience values', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, increaseLevelsWithExperience: false }); await levelsSet.addExperience(50.5); assert.strictEqual(levelsSet.currentExp, 50.5); }); it('should cap experience at max level when setRequiredExperienceLimit enabled', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 5, currentExp: 500, increaseLevelsWithExperience: true, setRequiredExperienceLimit: true }); await levelsSet.addExperience(1000); assert.strictEqual(levelsSet.currentExp, 500); }); it('should not cap experience when setRequiredExperienceLimit disabled', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 5, currentExp: 500, increaseLevelsWithExperience: false, setRequiredExperienceLimit: false }); await levelsSet.addExperience(1000); assert.strictEqual(levelsSet.currentExp, 1500); }); it('should not auto-level when disabled', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, increaseLevelsWithExperience: false }); await levelsSet.addExperience(100); assert.strictEqual(levelsSet.currentLevel, 1); assert.strictEqual(levelsSet.currentExp, 100); }); it('should skip multiple levels with large experience gain', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0, increaseLevelsWithExperience: true }); await levelsSet.addExperience(300); assert.strictEqual(levelsSet.currentLevel, 3); }); }); describe('Edge Cases - Modifiers', () => { it('should apply modifiers on level up', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); let initialMaxHp = mockOwner.stats.maxHp; await levelsSet.levelUp(); assert.ok(mockOwner.stats.maxHp !== initialMaxHp); }); it('should revert modifiers on level down', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 2, currentExp: 100}); let levelTwoMaxHp = mockOwner.stats.maxHp; await levelsSet.levelDown(); assert.ok(mockOwner.stats.maxHp !== levelTwoMaxHp); }); it('should return false when applying modifiers for non-existent level', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 100, currentExp: 0}); let result = await levelsSet.applyLevelModifiers(); assert.strictEqual(result, false); }); it('should handle level with no modifiers', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); let result = await levelsSet.applyLevelModifiers(); assert.strictEqual(result, false); }); it('should handle level with empty modifiers array', async () => { let levels = { 1: new Level({key: 1, modifiers: [], requiredExperience: 0}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); let result = await levelsSet.applyLevelModifiers(); assert.strictEqual(result, false); }); }); describe('Edge Cases - Owner ID', () => { it('should use default ownerIdProperty', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); assert.strictEqual(levelsSet.getOwnerId(), 'mock-owner-1'); }); it('should use custom ownerIdProperty', () => { let customOwner = { customId: 'custom-123', getPosition: () => ({x: 0, y: 0}) }; let levelsSet = new LevelsSet({owner: customOwner}); levelsSet.setOwner({owner: customOwner, ownerIdProperty: 'customId'}); assert.strictEqual(levelsSet.getOwnerId(), 'custom-123'); }); it('should handle numeric owner IDs', () => { mockOwner.id = 12345; let levelsSet = new LevelsSet({owner: mockOwner}); levelsSet.setOwner({owner: mockOwner}); assert.strictEqual(levelsSet.getOwnerId(), 12345); }); it('should handle string owner IDs', () => { mockOwner.id = 'player-uuid-abc-123'; let levelsSet = new LevelsSet({owner: mockOwner}); levelsSet.setOwner({owner: mockOwner}); assert.strictEqual(levelsSet.getOwnerId(), 'player-uuid-abc-123'); }); }); describe('Edge Cases - Sorting and Utilities', () => { it('should sort levels by key correctly', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); let sorted = levelsSet.sortLevelsBy(levels); assert.ok(Array.isArray(sorted)); assert.strictEqual(sorted[0], '1'); }); it('should sort levels by custom field', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); let sorted = levelsSet.sortLevelsBy(levels, 'requiredExperience'); assert.ok(Array.isArray(sorted)); }); it('should handle empty levels object for sorting', () => { let levelsSet = new LevelsSet({owner: mockOwner}); let sorted = levelsSet.sortLevelsBy({}); assert.ok(Array.isArray(sorted)); assert.strictEqual(sorted.length, 0); }); it('should generate unique event keys', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({levels: levels, currentLevel: 1, currentExp: 0}); let key1 = levelsSet.getOwnerUniqueEventKey('test'); await TestHelpers.sleep(1); let key2 = levelsSet.getOwnerUniqueEventKey('test'); assert.ok(key1 !== key2); }); it('should handle experience overflow', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 150, increaseLevelsWithExperience: false }); let nextExp = levelsSet.getNextLevelExperience(); assert.ok(nextExp > 0); }); }); describe('getNextLevelExperience - All Return Paths', () => { it('should return 0 when increaseLevelsWithExperience is false', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, increaseLevelsWithExperience: false }); let nextExp = levelsSet.getNextLevelExperience(); assert.strictEqual(nextExp, 100); }); it('should return 0 when no next level exists', async () => { let levels = {5: new Level({key: 5, requiredExperience: 500})}; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 5 }); let nextExp = levelsSet.getNextLevelExperience(); assert.strictEqual(nextExp, 500); }); it('should return next level required experience when next level exists', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1 }); let nextExp = levelsSet.getNextLevelExperience(); assert.strictEqual(nextExp, 100); }); it('should return required experience limit when setRequiredExperienceLimit is true', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 5, setRequiredExperienceLimit: true }); let nextExp = levelsSet.getNextLevelExperience(); assert.strictEqual(nextExp, 500); }); }); describe('getNextLevelExperience - Edge Cases', () => { it('should handle currentLevel = 0', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 0 }); let nextExp = levelsSet.getNextLevelExperience(); assert.ok(typeof nextExp === 'number'); }); it('should handle currentLevel = negative', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: -5 }); let nextExp = levelsSet.getNextLevelExperience(); assert.ok(typeof nextExp === 'number'); }); it('should handle currentLevel beyond max', async () => { let levels = {1: new Level({key: 1, requiredExperience: 0})}; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 100 }); let nextExp = levelsSet.getNextLevelExperience(); assert.strictEqual(nextExp, 0); }); it('should handle nextLevelKey is undefined', async () => { let levels = { 1: new Level({key: 1, requiredExperience: 0}), 5: new Level({key: 5, requiredExperience: 500}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 5, levelsByExperience: ['1'] }); let nextExp = levelsSet.getNextLevelExperience(); assert.ok(typeof nextExp === 'number'); }); }); describe('init - levelsByExperience Custom Ordering', () => { it('should use custom levelsByExperience when provided', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let customOrder = ['5', '3', '2', '1']; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, levelsByExperience: customOrder }); assert.deepStrictEqual(levelsSet.levelsByExperience, customOrder); }); }); describe('init - setRequiredExperienceLimit Edge Cases', () => { it('should cap experience at max level when setRequiredExperienceLimit is true', async () => { let levels = { 1: new Level({key: 1, requiredExperience: 0}), 5: new Level({key: 5, requiredExperience: 500}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 5, setRequiredExperienceLimit: true }); let nextExp = levelsSet.getNextLevelExperience(); assert.strictEqual(nextExp, 500); }); }); describe('init - Event Firing Order', () => { it('should fire INIT_LEVEL_SET_START then INIT_LEVEL_SET_END', async () => { let eventOrder = []; let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); levelsSet.listenEvent(SkillsEvents.INIT_LEVEL_SET_START, () => { eventOrder.push('START'); }, 'start-listener'); levelsSet.listenEvent(SkillsEvents.INIT_LEVEL_SET_END, () => { eventOrder.push('END'); }, 'end-listener'); await levelsSet.init({ levels: levels, currentLevel: 1 }); assert.deepStrictEqual(eventOrder, ['START', 'END']); }); }); describe('createLevels - Multiplier Variations', () => { it('should handle very large multiplier values > 100', async () => { let levels = { 1: new Level({key: 1, requiredExperience: 10}), 5: new Level({key: 5, requiredExperience: 1000}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, autoFillRanges: true, autoFillExperienceMultiplier: 150 }); assert.ok(levelsSet.levels[2]); assert.ok(levelsSet.levels[3]); assert.ok(levelsSet.levels[4]); }); it('should handle float multiplier values like 1.5', async () => { let levels = { 1: new Level({key: 1, requiredExperience: 100}), 3: new Level({key: 3, requiredExperience: 300}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, autoFillRanges: true, autoFillExperienceMultiplier: 1.5 }); assert.ok(levelsSet.levels[2]); assert.strictEqual(levelsSet.levels[2].requiredExperience, 150); }); it('should handle float multiplier values like 2.5', async () => { let levels = { 1: new Level({key: 1, requiredExperience: 100}), 3: new Level({key: 3, requiredExperience: 300}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, autoFillRanges: true, autoFillExperienceMultiplier: 2.5 }); assert.ok(levelsSet.levels[2]); assert.strictEqual(levelsSet.levels[2].requiredExperience, 250); }); }); describe('createLevels - Non-Sequential Level Keys', () => { it('should auto-fill non-sequential level keys like 1, 3, 10, 20', async () => { let levels = { 1: new Level({key: 1, requiredExperience: 10}), 3: new Level({key: 3, requiredExperience: 30}), 10: new Level({key: 10, requiredExperience: 100}), 20: new Level({key: 20, requiredExperience: 200}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, autoFillRanges: true }); assert.ok(levelsSet.levels[2]); assert.ok(levelsSet.levels[5]); assert.ok(levelsSet.levels[15]); }); }); describe('createLevels - Auto-fill with Single Level', () => { it('should handle auto-fill with only one level', async () => { let levels = { 1: new Level({key: 1, requiredExperience: 10}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, autoFillRanges: true }); assert.strictEqual(Object.keys(levelsSet.levels).length, 1); }); }); describe('levelUp - Edge Cases', () => { it('should handle levelUp from level 0', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 0 }); await levelsSet.levelUp(); assert.ok(levelsSet.currentLevel >= 0); }); it('should handle levelUp from negative level', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: -5 }); await levelsSet.levelUp(); assert.ok(typeof levelsSet.currentLevel === 'number'); }); it('should handle multiple consecutive levelUp calls', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1 }); await levelsSet.levelUp(); await levelsSet.levelUp(); await levelsSet.levelUp(); assert.strictEqual(levelsSet.currentLevel, 4); }); }); describe('levelDown - Edge Cases', () => { it('should handle levelDown from level 0', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 0 }); let result = await levelsSet.levelDown(); assert.strictEqual(result, false); }); it('should handle multiple consecutive levelDown calls', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 5 }); await levelsSet.levelDown(); await levelsSet.levelDown(); await levelsSet.levelDown(); assert.strictEqual(levelsSet.currentLevel, 2); }); it('should maintain consistency on levelDown then levelUp round-trip', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 3 }); await levelsSet.levelDown(); assert.strictEqual(levelsSet.currentLevel, 2); await levelsSet.levelUp(); assert.strictEqual(levelsSet.currentLevel, 3); }); }); describe('applyLevelModifiers - Error Handling', () => { it('should handle when Modifier.apply() throws error', async () => { let brokenModifier = new Modifier({ key: 'broken-mod', propertyKey: 'stats/atk', operation: ModifierConst.OPS.INC, value: 10 }); brokenModifier.apply = () => { throw new Error('Modifier apply error'); }; let levels = { 1: new Level({key: 1, requiredExperience: 0, modifiers: [brokenModifier]}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1 }); assert.ok(levelsSet); }); it('should handle when Modifier.revert() throws error', async () => { let brokenModifier = new Modifier({ key: 'broken-revert', propertyKey: 'stats/atk', operation: ModifierConst.OPS.INC, value: 10 }); brokenModifier.revert = () => { throw new Error('Modifier revert error'); }; let levels = { 1: new Level({key: 1, requiredExperience: 0, modifiers: [brokenModifier]}), 2: new Level({key: 2, requiredExperience: 100}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 2 }); await levelsSet.levelDown(); assert.ok(levelsSet); }); it('should handle large number of modifiers (100+)', async () => { let modifiers = []; for(let i = 0; i < 150; i++){ modifiers.push(new Modifier({ key: `mod-${i}`, propertyKey: 'stats/atk', operation: ModifierConst.OPS.INC, value: 1 })); } let levels = { 0: new Level({key: 0, requiredExperience: 0}), 1: new Level({key: 1, requiredExperience: 0, modifiers: modifiers}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 0 }); await levelsSet.levelUp(); assert.strictEqual(mockOwner.stats.atk, 160); }); it('should apply modifiers in order', async () => { let modifier1 = new Modifier({ key: 'mod-1', propertyKey: 'stats/atk', operation: ModifierConst.OPS.SET, value: 5 }); let modifier2 = new Modifier({ key: 'mod-2', propertyKey: 'stats/atk', operation: ModifierConst.OPS.MULT, value: 2 }); let levels = { 1: new Level({key: 1, requiredExperience: 0, modifiers: [modifier1, modifier2]}) }; let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1 }); assert.strictEqual(mockOwner.stats.atk, 10); }); }); describe('addExperience - Experience Thresholds', () => { it('should level up when experience exactly at threshold', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0 }); await levelsSet.addExperience(100); assert.strictEqual(levelsSet.currentLevel, 2); }); it('should not level up when experience 1 point below threshold', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 0 }); await levelsSet.addExperience(99); assert.strictEqual(levelsSet.currentLevel, 1); assert.strictEqual(levelsSet.currentExp, 99); }); }); describe('addExperience - Event Parameters Validation', () => { it('should fire LEVEL_EXPERIENCE_ADDED event with all 9 parameters', async () => { let receivedArgs = []; let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 50 }); levelsSet.listenEvent(SkillsEvents.LEVEL_EXPERIENCE_ADDED, (...args) => { receivedArgs = args; }, 'exp-listener', levelsSet.getOwnerEventKey()); await levelsSet.addExperience(25); assert.strictEqual(receivedArgs.length, 9); assert.strictEqual(receivedArgs[1], 25); assert.strictEqual(receivedArgs[2], 75); }); }); describe('addExperience - Negative Experience', () => { it('should reduce XP below 0 when negative experience added', async () => { let levels = BaseLevelsFixtures.createLevelSet(); let levelsSet = new LevelsSet({owner: mockOwner}); await levelsSet.init({ levels: levels, currentLevel: 1, currentExp: 50 }); await levelsSet.addExperience(-75); assert.ok(levelsSet.currentExp < 0); }); }); describe('sortLevelsBy - Edge Cases', () => { it('should handle when sortField does not exist on objects', () => { let levels = { 1: new Level({key: 1, requiredExperience: 100}), 2: new Level({key: 2, requiredExperience: 200}) }; let levelsSet = new LevelsSet({owner: mockOwner}); let result = levelsSet.sortLevelsBy(levels, 'nonExistentField'); assert.ok(Array.isArray(result)); }); it('should handle mixed type values in sort field', () => { let levels = { 1: {key: 1, customSort: '100'}, 2: {key: 2, customSort: 50}, 3: {key: 3, customSort: null} }; let levelsSet = new LevelsSet({owner: mockOwner}); let result = levelsSet.sortLevelsBy(levels, 'customSort'); assert.ok(Array.isArray(result)); }); it('should maintain stability in sort', () => { let levels = { 1: new Level({key: 1, requiredExperience: 100}), 2: new Level({key: 2, requiredExperience: 100}), 3: new Level({key: 3, requiredExperience: 100}) }; let levelsSet = new LevelsSet({owner: mockOwner}); let result = levelsSet.sortLevelsBy(levels, 'requiredExperience'); assert.strictEqual(result.length, 3); assert.ok(result.includes('1')); assert.ok(result.includes('2')); assert.ok(result.includes('3')); }); }); });