import { test } from 'node:test'; import assert from 'node:assert/strict'; import { getPath, setPath, hasPath, parsePath, listPaths } from '../src/engine/paths.js'; const state = { money: 100, skills: { talking: 2 }, relationships: { chief: { likes_you: 50 } } }; test('reads nested paths', () => { assert.equal(getPath(state, 'money'), 100); assert.equal(getPath(state, 'skills.talking'), 2); assert.equal(getPath(state, 'relationships.chief.likes_you'), 50); }); test('missing paths read as undefined, not as a throw', () => { assert.equal(getPath(state, 'skills.welding'), undefined); assert.equal(getPath(state, 'nothing.at.all'), undefined); assert.equal(hasPath(state, 'skills.welding'), false); assert.equal(hasPath(state, 'skills.talking'), true); }); test('writes without mutating the original', () => { const next = setPath(state, 'skills.talking', 5); assert.equal(next.skills.talking, 5); assert.equal(state.skills.talking, 2, 'original state must be untouched'); assert.equal(next.relationships, state.relationships, 'untouched branches are shared'); }); test('creates missing intermediate objects', () => { const next = setPath({}, 'flags.deep.nested', true); assert.equal(next.flags.deep.nested, true); }); test('rejects prototype-polluting paths', () => { assert.throws(() => parsePath('__proto__.polluted'), /forbidden/); assert.throws(() => parsePath('constructor'), /forbidden/); assert.throws(() => setPath({}, 'a.prototype.b', 1), /forbidden/); }); test('rejects malformed paths', () => { assert.throws(() => parsePath(''), /non-empty/); assert.throws(() => parsePath('money.'), /invalid path segment/); assert.throws(() => parsePath('a b'), /invalid path segment/); assert.throws(() => parsePath(42), /non-empty string/); }); test('lists leaf paths', () => { assert.deepEqual(listPaths(state), ['money', 'skills.talking', 'relationships.chief.likes_you']); });