import { test } from 'node:test'; import assert from 'node:assert/strict'; import { applyEffect, applyEffects } from '../src/engine/effects.js'; const schema = { 'money': { initial: 0, min: -500, max: 1000 }, 'skills.talking': { initial: 1, min: 0, max: 10 }, 'flags.in_debt': { initial: false }, 'notable': { initial: [] }, }; const state = { money: 100, skills: { talking: 1 }, flags: { in_debt: false }, notable: [] }; test('add and set produce a new state', () => { const result = applyEffects(state, [ { path: 'money', op: 'add', value: -30 }, { path: 'flags.in_debt', op: 'set', value: true }, ], schema); assert.equal(result.state.money, 70); assert.equal(result.state.flags.in_debt, true); assert.equal(state.money, 100, 'the input state is never mutated'); }); test('push appends to a list', () => { const result = applyEffects(state, [{ path: 'notable', op: 'push', value: 'first day' }], schema); assert.deepEqual(result.state.notable, ['first day']); assert.deepEqual(state.notable, [], 'the input list is never mutated'); }); test('remove drops matching entries', () => { const seeded = { ...state, notable: ['a', 'b', 'a'] }; const result = applyEffects(seeded, [{ path: 'notable', op: 'remove', value: 'a' }], schema); assert.deepEqual(result.state.notable, ['b']); }); test('values clamp to their declared bounds', () => { const high = applyEffect(state, { path: 'skills.talking', op: 'add', value: 99 }, schema); assert.equal(high.state.skills.talking, 10); assert.equal(high.change.clamped, true); const low = applyEffect(state, { path: 'money', op: 'add', value: -9999 }, schema); assert.equal(low.state.money, -500); }); test('an unbounded path is not clamped', () => { const result = applyEffect(state, { path: 'money', op: 'add', value: 10 }, {}); assert.equal(result.state.money, 110); assert.equal(result.change.clamped, false); }); test('the change record carries before and after for every path touched', () => { const result = applyEffects(state, [ { path: 'money', op: 'add', value: 25 }, { path: 'skills.talking', op: 'add', value: 1 }, ], schema); assert.deepEqual(result.changes.map((c) => [c.path, c.from, c.to]), [ ['money', 100, 125], ['skills.talking', 1, 2], ]); }); test('adding to a path that has never been set treats it as zero', () => { const result = applyEffect({}, { path: 'money', op: 'add', value: 5 }, {}); assert.equal(result.state.money, 5); }); test('an unknown operation is a loud failure', () => { assert.throws(() => applyEffect(state, { path: 'money', op: 'multiply', value: 2 }), /unknown effect operation/); }); test('an empty effect list is a no-op', () => { const result = applyEffects(state, [], schema); assert.deepEqual(result.changes, []); assert.equal(result.state, state); });