const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const vm = require('node:vm'); test('retains all stage tracks when group removal deletes their animation bindings', () => { const source = fs.readFileSync(path.join(__dirname, '..', 'AnimationDirector.js'), 'utf8').replace( ' Plugin.register(PLUGIN_ID, {', ' globalThis.replacementTest = { captureActorAnimationBindings, removeActorGeometry, rebindActorAnimations };\n Plugin.register(PLUGIN_ID, {' ); const animations = []; class Group { static all = []; constructor(name, uuid, parent = 'root') { Object.assign(this, { name, uuid, parent }); Group.all.push(this); } remove() { for (const child of Group.all.filter((group) => group.parent === this)) child.remove(); for (const animation of animations) animation.removeAnimator(this.uuid); Group.all = Group.all.filter((group) => group !== this); } } const root = new Group('actor1_body', 'old-body'); new Group('actor1_head', 'old-head', root); new Group('actor1_removed', 'old-removed', root); new Group('actor2_body', 'other-body'); for (let stage = 1; stage <= 2; stage++) { const animators = {}; for (const group of Group.all) { const track = { uuid: group.uuid, _name: group.name, keyframes: [{ time: stage, rotation: [1, 2, 3] }] }; Object.defineProperty(track, 'name', { get() { return this._name; } }); animators[group.uuid] = track; } animations.push({ animators, saved: true, removeAnimator(uuid) { delete this.animators[uuid]; } }); } const originalTracks = animations.map((animation) => ({ ...animation.animators })); const context = { Group, Animation: { all: animations }, Plugin: { register() {} }, console }; vm.runInNewContext(source, context); const snapshot = context.replacementTest.captureActorAnimationBindings(1); context.replacementTest.removeActorGeometry(1); assert.equal(animations[0].animators['old-body'], undefined); new Group('actor1_body', 'new-body'); new Group('actor1_head', 'new-head'); const result = context.replacementTest.rebindActorAnimations(1, snapshot); assert.equal(result.rebound, 4); assert.equal(result.missing, 2); animations.forEach((animation, index) => { assert.equal(animation.animators['new-body'], originalTracks[index]['old-body']); assert.equal(animation.animators['new-head'], originalTracks[index]['old-head']); assert.equal(animation.animators['other-body'], originalTracks[index]['other-body']); assert.equal(animation.animators['old-removed'], originalTracks[index]['old-removed']); assert.equal(animation.animators['new-body'].uuid, 'new-body'); assert.equal(animation.animators['old-body'], undefined); assert.equal(animation.saved, false); assert.deepEqual(animation.animators['new-body'].keyframes, [{ time: index + 1, rotation: [1, 2, 3] }]); }); });