@reldens/modifiers
Version:
1,223 lines (1,130 loc) • 44.4 kB
JavaScript
/**
*
* Reldens - Modifier Unit Tests
*
*/
const { describe, it, beforeEach } = require('node:test');
const assert = require('node:assert');
const Modifier = require('../../lib/modifier');
const Condition = require('../../lib/condition');
const ModifierConst = require('../../lib/constants');
const { TestHelpers } = require('../fixtures/test-helpers');
describe('Modifier', () => {
let target;
beforeEach(() => {
target = TestHelpers.createMockTarget();
});
describe('Constructor', () => {
it('should create modifier with all required properties', () => {
let modifier = new Modifier({
key: 'test-modifier',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 20
});
assert.strictEqual(modifier.key, 'test-modifier');
assert.strictEqual(modifier.propertyKey, 'attack');
assert.strictEqual(modifier.operation, ModifierConst.OPS.INC);
assert.strictEqual(modifier.value, 20);
});
it('should accept property_key as alternative to propertyKey', () => {
let modifier = new Modifier({
key: 'test',
property_key: 'health',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.strictEqual(modifier.propertyKey, 'health');
});
it('should set basePropertyKey to propertyKey by default', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.strictEqual(modifier.basePropertyKey, 'attack');
});
it('should accept custom basePropertyKey', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
basePropertyKey: 'maxHealth',
operation: ModifierConst.OPS.INC_P,
value: 50
});
assert.strictEqual(modifier.basePropertyKey, 'maxHealth');
});
it('should default type to INT', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.strictEqual(modifier.type, ModifierConst.TYPES.INT);
});
it('should accept STRING type', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'status',
operation: ModifierConst.OPS.SET,
type: ModifierConst.TYPES.STRING,
value: 'poisoned'
});
assert.strictEqual(modifier.type, ModifierConst.TYPES.STRING);
});
it('should parse value based on type', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: '25'
});
assert.strictEqual(modifier.value, 25);
assert.strictEqual(typeof modifier.value, 'number');
});
it('should accept target in constructor', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10,
target: target
});
assert.strictEqual(modifier.target, target);
});
it('should accept conditions array', () => {
let condition = new Condition({
key: 'level-check',
propertyKey: 'level',
conditional: ModifierConst.COMPARE.GE,
value: 5
});
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10,
conditions: [condition]
});
assert.strictEqual(modifier.conditions.length, 1);
});
it('should accept min and max values', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 50,
minValue: 0,
maxValue: 100
});
assert.strictEqual(modifier.minValue, 0);
assert.strictEqual(modifier.maxValue, 100);
});
it('should accept min and max properties', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 50,
minProperty: 'minHealth',
maxProperty: 'maxHealth'
});
assert.strictEqual(modifier.minProperty, 'minHealth');
assert.strictEqual(modifier.maxProperty, 'maxHealth');
});
});
describe('determineState', () => {
it('should set state to READY when all required props are present', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.strictEqual(modifier.state, ModifierConst.MOD_READY);
});
it('should set state to MISSING_KEY when key is missing', () => {
let modifier = new Modifier({
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.strictEqual(modifier.state, ModifierConst.MOD_MISSING_KEY);
});
it('should set state to MISSING_PROPERTY_KEY when propertyKey is missing', () => {
let modifier = new Modifier({
key: 'test',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.strictEqual(modifier.state, ModifierConst.MOD_MISSING_PROPERTY_KEY);
});
it('should set state to MISSING_OPERATION when operation is missing', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
value: 10
});
assert.strictEqual(modifier.state, ModifierConst.MOD_MISSING_OPERATION);
});
it('should set state to MISSING_VALUE when value is missing', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC
});
assert.strictEqual(modifier.state, ModifierConst.MOD_MISSING_VALUE);
});
});
describe('apply - INC Operation', () => {
it('should increase property value', () => {
let modifier = new Modifier({
key: 'attack-boost',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 20
});
modifier.apply(target);
assert.strictEqual(target.attack, 70);
});
it('should set state to APPLIED after successful application', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10
});
modifier.apply(target);
assert.strictEqual(modifier.state, ModifierConst.MOD_APPLIED);
});
it('should return true on successful application', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10
});
let result = modifier.apply(target);
assert.strictEqual(result, true);
});
});
describe('apply - DEC Operation', () => {
it('should decrease property value', () => {
let modifier = new Modifier({
key: 'attack-penalty',
propertyKey: 'attack',
operation: ModifierConst.OPS.DEC,
value: 15
});
modifier.apply(target);
assert.strictEqual(target.attack, 35);
});
});
describe('apply - MUL Operation', () => {
it('should multiply property value', () => {
let modifier = new Modifier({
key: 'damage-multiplier',
propertyKey: 'attack',
operation: ModifierConst.OPS.MUL,
value: 2
});
modifier.apply(target);
assert.strictEqual(target.attack, 100);
});
});
describe('apply - DIV Operation', () => {
it('should divide property value', () => {
let modifier = new Modifier({
key: 'weakness',
propertyKey: 'attack',
operation: ModifierConst.OPS.DIV,
value: 2
});
modifier.apply(target);
assert.strictEqual(target.attack, 25);
});
});
describe('apply - INC_P Operation', () => {
it('should increase by percentage', () => {
let modifier = new Modifier({
key: 'percent-boost',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC_P,
value: 50
});
modifier.apply(target);
assert.strictEqual(target.attack, 75);
});
});
describe('apply - DEC_P Operation', () => {
it('should decrease by percentage', () => {
let modifier = new Modifier({
key: 'percent-penalty',
propertyKey: 'health',
operation: ModifierConst.OPS.DEC_P,
value: 50
});
modifier.apply(target);
assert.strictEqual(target.health, 50);
});
});
describe('apply - SET Operation', () => {
it('should set property to specific value', () => {
let modifier = new Modifier({
key: 'set-level',
propertyKey: 'level',
operation: ModifierConst.OPS.SET,
value: 20
});
modifier.apply(target);
assert.strictEqual(target.level, 20);
});
});
describe('revert', () => {
it('should revert INC operation', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 20
});
modifier.apply(target);
assert.strictEqual(target.attack, 70);
modifier.revert(target);
assert.strictEqual(target.attack, 50);
});
it('should revert DEC operation', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.DEC,
value: 15
});
modifier.apply(target);
assert.strictEqual(target.attack, 35);
modifier.revert(target);
assert.strictEqual(target.attack, 50);
});
it('should revert MUL operation', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.MUL,
value: 2
});
modifier.apply(target);
modifier.revert(target);
assert.strictEqual(target.attack, 50);
});
it('should revert INC_P operation', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC_P,
value: 50
});
modifier.apply(target);
let boostedHealth = target.health;
modifier.revert(target);
assert.strictEqual(target.health, 100);
});
it('should set state to REVERTED after successful revert', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10
});
modifier.apply(target);
modifier.revert(target);
assert.strictEqual(modifier.state, ModifierConst.MOD_REVERTED);
});
it('should revert SET operation to false', () => {
target.status = 'normal';
let modifier = new Modifier({
key: 'test',
propertyKey: 'status',
operation: ModifierConst.OPS.SET,
type: ModifierConst.TYPES.STRING,
value: 'poisoned'
});
modifier.apply(target);
assert.strictEqual(target.status, 'poisoned');
modifier.revert(target);
assert.strictEqual(target.status, false);
});
});
describe('Conditions', () => {
it('should apply modifier when condition is valid', () => {
let condition = new Condition({
key: 'level-check',
propertyKey: 'level',
conditional: ModifierConst.COMPARE.GE,
value: 5
});
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 20,
conditions: [condition]
});
let result = modifier.apply(target);
assert.strictEqual(result, true);
assert.strictEqual(target.attack, 70);
});
it('should not apply modifier when condition is invalid', () => {
let condition = new Condition({
key: 'level-check',
propertyKey: 'level',
conditional: ModifierConst.COMPARE.GT,
value: 20
});
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 20,
conditions: [condition]
});
let result = modifier.apply(target);
assert.strictEqual(result, false);
assert.strictEqual(target.attack, 50);
});
it('should validate all conditions', () => {
let condition1 = new Condition({
key: 'level-check',
propertyKey: 'level',
conditional: ModifierConst.COMPARE.GE,
value: 5
});
let condition2 = new Condition({
key: 'health-check',
propertyKey: 'health',
conditional: ModifierConst.COMPARE.GT,
value: 50
});
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 20,
conditions: [condition1, condition2]
});
let result = modifier.apply(target);
assert.strictEqual(result, true);
assert.strictEqual(target.attack, 70);
});
it('should fail if any condition is invalid', () => {
let condition1 = new Condition({
key: 'level-check',
propertyKey: 'level',
conditional: ModifierConst.COMPARE.GE,
value: 5
});
let condition2 = new Condition({
key: 'health-check',
propertyKey: 'health',
conditional: ModifierConst.COMPARE.GT,
value: 200
});
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 20,
conditions: [condition1, condition2]
});
let result = modifier.apply(target);
assert.strictEqual(result, false);
assert.strictEqual(target.attack, 50);
});
});
describe('Value Limits', () => {
it('should respect minValue limit', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.DEC,
value: 150,
minValue: 0
});
modifier.apply(target);
assert.strictEqual(target.health, 0);
});
it('should respect maxValue limit', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 100,
maxValue: 150
});
modifier.apply(target);
assert.strictEqual(target.health, 150);
});
it('should respect maxProperty limit', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 100,
maxProperty: 'maxHealth'
});
modifier.apply(target);
assert.strictEqual(target.health, 150);
});
it('should not limit when value is within range', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 20,
minValue: 0,
maxValue: 200
});
modifier.apply(target);
assert.strictEqual(target.health, 120);
});
});
describe('Nested Property Modifications', () => {
it('should modify nested property', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'stats/strength',
operation: ModifierConst.OPS.INC,
value: 10
});
modifier.apply(target);
assert.strictEqual(target.stats.strength, 30);
});
it('should modify deeply nested property', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'stats/combat/attack',
operation: ModifierConst.OPS.INC,
value: 25
});
modifier.apply(target);
assert.strictEqual(target.stats.combat.attack, 125);
});
});
describe('Base Property Operations', () => {
it('should use basePropertyKey for calculation when useBasePropertyToGetValue is true', () => {
target.currentHealth = 50;
target.baseHealth = 100;
let modifier = new Modifier({
key: 'test',
propertyKey: 'currentHealth',
basePropertyKey: 'baseHealth',
operation: ModifierConst.OPS.INC_P,
value: 50
});
modifier.apply(target, true, false);
assert.strictEqual(target.currentHealth, 150);
});
it('should apply to basePropertyKey when applyOnBaseProperty is true', () => {
target.currentHealth = 50;
target.baseHealth = 100;
let modifier = new Modifier({
key: 'test',
propertyKey: 'currentHealth',
basePropertyKey: 'baseHealth',
operation: ModifierConst.OPS.INC,
value: 25
});
modifier.apply(target, false, true);
assert.strictEqual(target.baseHealth, 75);
assert.strictEqual(target.currentHealth, 50);
});
});
describe('Error Handling', () => {
it('should return false when no target is provided', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10
});
let result = modifier.apply();
assert.strictEqual(result, false);
});
it('should set state to UNDEFINED_TARGET when no target', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10
});
modifier.apply();
assert.strictEqual(modifier.state, ModifierConst.MOD_UNDEFINED_TARGET);
});
});
describe('Target Override', () => {
it('should use provided target over constructor target', () => {
let originalTarget = TestHelpers.createMockTarget({attack: 30});
let newTarget = TestHelpers.createMockTarget({attack: 60});
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10,
target: originalTarget
});
modifier.apply(newTarget);
assert.strictEqual(newTarget.attack, 70);
assert.strictEqual(modifier.target, newTarget);
});
});
describe('NULL and Undefined Handling (Bug Fix Validation)', () => {
it('should handle NULL minValue (no limit applied)', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.DEC,
value: 150,
minValue: null
});
modifier.apply(target);
assert.strictEqual(target.health, -50);
assert.strictEqual(modifier.minValue, null);
});
it('should handle NULL maxValue (no limit applied)', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 500,
maxValue: null
});
modifier.apply(target);
assert.strictEqual(target.health, 600);
assert.strictEqual(modifier.maxValue, null);
});
it('should treat undefined minValue as false', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.DEC,
value: 150
});
modifier.apply(target);
assert.strictEqual(target.health, -50);
assert.strictEqual(modifier.minValue, false);
});
it('should treat undefined maxValue as false', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 500
});
modifier.apply(target);
assert.strictEqual(target.health, 600);
assert.strictEqual(modifier.maxValue, false);
});
it('should distinguish between maxValue=0 (hard limit) and maxValue=null (no limit)', () => {
let modifier1 = new Modifier({
key: 'test1',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 50,
maxValue: 0
});
modifier1.apply(target);
assert.strictEqual(target.health, 0);
let target2 = TestHelpers.createMockTarget();
let modifier2 = new Modifier({
key: 'test2',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 50,
maxValue: null
});
modifier2.apply(target2);
assert.strictEqual(target2.health, 150);
});
it('should handle database-like objects with NULL minValue/maxValue', () => {
let databaseRow = {
key: 'hp-boost',
property_key: 'health',
operation: 1,
value: '50',
minValue: null,
maxValue: null
};
let modifier = new Modifier(databaseRow);
modifier.apply(target);
assert.strictEqual(target.health, 150);
assert.strictEqual(modifier.minValue, null);
assert.strictEqual(modifier.maxValue, null);
});
});
describe('Invalid Operation Values', () => {
it('should handle string operation by converting to number', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: '1',
value: 50
});
modifier.apply(target);
assert.strictEqual(target.health, 150);
});
it('should handle invalid operation code returning original value', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: 999,
value: 50
});
modifier.apply(target);
assert.strictEqual(target.health, 100);
});
it('should handle NULL operation by converting to 0', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: null,
value: 50
});
assert.strictEqual(modifier.operation, 0);
});
});
describe('Invalid Value Types', () => {
it('should handle NaN value for INT type', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: NaN
});
assert.ok(Number.isNaN(modifier.value));
});
it('should handle Infinity value', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: Infinity
});
modifier.apply(target);
assert.strictEqual(target.health, Infinity);
});
it('should handle empty string value by converting to 0', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: ''
});
modifier.apply(target);
assert.strictEqual(target.health, 100);
});
it('should handle NULL value by converting to 0', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: null
});
modifier.apply(target);
assert.strictEqual(target.health, 100);
});
it('should handle undefined value by converting to NaN', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: undefined
});
assert.ok(Number.isNaN(modifier.value));
});
});
describe('Property Access Errors', () => {
it('should throw error when accessing non-existent property', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'nonExistentProperty',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.throws(() => {
modifier.apply(target);
});
});
it('should throw error when accessing nested property on undefined parent', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'stats/undefined/property',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.throws(() => {
modifier.apply(target);
});
});
it('should return false when target is null', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.strictEqual(modifier.apply(null), false);
assert.strictEqual(modifier.state, ModifierConst.MOD_UNDEFINED_TARGET);
});
it('should return false when target is undefined', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.strictEqual(modifier.apply(undefined), false);
assert.strictEqual(modifier.state, ModifierConst.MOD_UNDEFINED_TARGET);
});
});
describe('METHOD Operation Errors', () => {
it('should return false when METHOD operation has non-existent method', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.METHOD,
value: 'nonExistentMethod'
});
let result = modifier.apply(target);
assert.strictEqual(result, false);
assert.strictEqual(modifier.state, ModifierConst.MOD_MODIFIER_ERROR);
});
it('should return false when METHOD operation has non-function property', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.METHOD,
value: 'key'
});
let result = modifier.apply(target);
assert.strictEqual(result, false);
assert.strictEqual(modifier.state, ModifierConst.MOD_MODIFIER_ERROR);
});
});
describe('Condition Validation Errors', () => {
it('should handle invalid condition instance (not Condition class)', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10,
conditions: [{key: 'fake', propertyKey: 'level', conditional: 'eq', value: 10}]
});
let result = modifier.apply(target);
assert.strictEqual(result, false);
assert.strictEqual(modifier.state, ModifierConst.MOD_MISSING_CONDITION_INSTANCE);
});
it('should handle empty conditions array same as no conditions', () => {
let modifier1 = new Modifier({
key: 'test1',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10,
conditions: []
});
let result1 = modifier1.apply(target);
assert.strictEqual(result1, true);
let target2 = TestHelpers.createMockTarget();
let modifier2 = new Modifier({
key: 'test2',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10
});
let result2 = modifier2.apply(target2);
assert.strictEqual(result2, true);
assert.strictEqual(target.attack, target2.attack);
});
it('should fail when any condition in array is invalid', () => {
let validCondition = new Condition({
key: 'valid',
propertyKey: 'level',
conditional: ModifierConst.COMPARE.GT,
value: 5
});
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 10,
conditions: [validCondition, {fake: 'condition'}]
});
let result = modifier.apply(target);
assert.strictEqual(result, false);
});
});
describe('Extreme Values', () => {
it('should handle very large numbers without overflow', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: Number.MAX_SAFE_INTEGER
});
modifier.apply(target);
assert.strictEqual(target.health, 100+Number.MAX_SAFE_INTEGER);
});
it('should handle very small negative numbers', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.DEC,
value: Number.MAX_SAFE_INTEGER
});
modifier.apply(target);
assert.strictEqual(target.health, 100-Number.MAX_SAFE_INTEGER);
});
it('should handle zero as value', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 0
});
modifier.apply(target);
assert.strictEqual(target.health, 100);
});
});
describe('Limit Edge Cases', () => {
it('should handle minValue greater than maxValue', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 50,
minValue: 200,
maxValue: 100
});
modifier.apply(target);
assert.strictEqual(target.health, 100);
});
it('should handle minValue equal to maxValue', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 50,
minValue: 75,
maxValue: 75
});
modifier.apply(target);
assert.strictEqual(target.health, 75);
});
it('should handle negative minValue and maxValue', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.DEC,
value: 150,
minValue: -100,
maxValue: -50
});
modifier.apply(target);
assert.strictEqual(target.health, -50);
});
it('should throw when maxProperty points to non-existent property', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.INC,
value: 100,
maxProperty: 'nonExistentProperty'
});
assert.throws(() => {
modifier.apply(target);
});
});
it('should not apply minProperty limit when value is 0 (falsy)', () => {
target.minHealth = 0;
let modifier = new Modifier({
key: 'test',
propertyKey: 'health',
operation: ModifierConst.OPS.DEC,
value: 150,
minProperty: 'minHealth'
});
modifier.apply(target);
assert.strictEqual(target.health, -50);
});
});
describe('Base Property Edge Cases', () => {
it('should throw when basePropertyKey does not exist', () => {
target.currentHealth = 50;
let modifier = new Modifier({
key: 'test',
propertyKey: 'currentHealth',
basePropertyKey: 'nonExistentBase',
operation: ModifierConst.OPS.INC_P,
value: 50
});
assert.throws(() => {
modifier.apply(target, true, false);
});
});
it('should calculate percentage of zero as zero', () => {
target.currentHealth = 50;
target.baseHealth = 0;
let modifier = new Modifier({
key: 'test',
propertyKey: 'currentHealth',
basePropertyKey: 'baseHealth',
operation: ModifierConst.OPS.INC_P,
value: 50
});
modifier.apply(target, true, false);
assert.strictEqual(target.currentHealth, 0);
});
it('should handle basePropertyKey with null value (null coerces to 0)', () => {
target.currentHealth = 50;
target.baseHealth = null;
let modifier = new Modifier({
key: 'test',
propertyKey: 'currentHealth',
basePropertyKey: 'baseHealth',
operation: ModifierConst.OPS.INC_P,
value: 50
});
modifier.apply(target, true, false);
assert.strictEqual(target.currentHealth, 0);
});
});
describe('String Type Edge Cases', () => {
it('should handle empty string for STRING type', () => {
target.status = 'normal';
let modifier = new Modifier({
key: 'test',
propertyKey: 'status',
operation: ModifierConst.OPS.SET,
type: ModifierConst.TYPES.STRING,
value: ''
});
modifier.apply(target);
assert.strictEqual(target.status, '');
});
it('should handle NULL for STRING type by converting to string', () => {
target.status = 'normal';
let modifier = new Modifier({
key: 'test',
propertyKey: 'status',
operation: ModifierConst.OPS.SET,
type: ModifierConst.TYPES.STRING,
value: null
});
modifier.apply(target);
assert.strictEqual(target.status, 'null');
});
it('should handle undefined for STRING type by converting to string', () => {
target.status = 'normal';
let modifier = new Modifier({
key: 'test',
propertyKey: 'status',
operation: ModifierConst.OPS.SET,
type: ModifierConst.TYPES.STRING,
value: undefined
});
modifier.apply(target);
assert.strictEqual(target.status, 'undefined');
});
it('should handle object for STRING type by converting to string', () => {
target.status = 'normal';
let modifier = new Modifier({
key: 'test',
propertyKey: 'status',
operation: ModifierConst.OPS.SET,
type: ModifierConst.TYPES.STRING,
value: {foo: 'bar'}
});
modifier.apply(target);
assert.strictEqual(target.status, '[object Object]');
});
});
describe('Revert Edge Cases', () => {
it('should handle revert with conditions when conditionsOnRevert is false', () => {
let condition = new Condition({
key: 'level-check',
propertyKey: 'level',
conditional: ModifierConst.COMPARE.GT,
value: 100
});
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 20,
conditions: [condition],
conditionsOnRevert: false
});
modifier.apply(target);
assert.strictEqual(target.attack, 50);
let revertResult = modifier.revert(target);
assert.strictEqual(revertResult, true);
assert.strictEqual(target.attack, 30);
});
it('should handle revert with conditions when conditionsOnRevert is true', () => {
let condition = new Condition({
key: 'level-check',
propertyKey: 'level',
conditional: ModifierConst.COMPARE.GT,
value: 100
});
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
operation: ModifierConst.OPS.INC,
value: 20,
conditions: [condition],
conditionsOnRevert: true
});
modifier.apply(target);
assert.strictEqual(target.attack, 50);
let revertResult = modifier.revert(target);
assert.strictEqual(revertResult, false);
assert.strictEqual(target.attack, 50);
});
it('should handle revert on SET_N operation', () => {
target.status = 'normal';
let modifier = new Modifier({
key: 'test',
propertyKey: 'status',
operation: ModifierConst.OPS.SET_N,
type: ModifierConst.TYPES.STRING,
value: 'poisoned'
});
modifier.apply(target);
assert.strictEqual(target.status, 'poisoned');
modifier.revert(target);
assert.strictEqual(target.status, false);
});
});
describe('Constructor Property Preference', () => {
it('should prefer propertyKey over property_key when both provided', () => {
let modifier = new Modifier({
key: 'test',
propertyKey: 'attack',
property_key: 'health',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.strictEqual(modifier.propertyKey, 'attack');
});
it('should use property_key when propertyKey is missing', () => {
let modifier = new Modifier({
key: 'test',
property_key: 'health',
operation: ModifierConst.OPS.INC,
value: 10
});
assert.strictEqual(modifier.propertyKey, 'health');
});
});
});