From f1fb012fd3cac39fa92c9dce2f5f8be3be8bb91c Mon Sep 17 00:00:00 2001 From: Kaleniuk Date: Thu, 25 Dec 2025 20:33:02 +0200 Subject: [PATCH] update tests --- .../core/test/specs/patch_manager/index.js | 92 +++++++++++++++++++ .../patch_manager/model/ModelWithPatches.js | 92 +++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 packages/core/test/specs/patch_manager/index.js create mode 100644 packages/core/test/specs/patch_manager/model/ModelWithPatches.js diff --git a/packages/core/test/specs/patch_manager/index.js b/packages/core/test/specs/patch_manager/index.js new file mode 100644 index 000000000..21aaad0ea --- /dev/null +++ b/packages/core/test/specs/patch_manager/index.js @@ -0,0 +1,92 @@ +import PatchManager, { PatchManagerEvents } from 'patch_manager'; + +describe('PatchManager', () => { + test('Records a patch during update and emits update event', () => { + const events = []; + const pm = new PatchManager({ + enabled: true, + emitter: { + trigger: (event, payload) => events.push({ event, payload }), + }, + }); + + pm.update(() => { + const patch = pm.createOrGetCurrentPatch(); + patch.changes.push({ op: 'replace', path: ['value'], value: 1 }); + patch.reverseChanges.push({ op: 'replace', path: ['value'], value: 0 }); + }); + + expect(events).toHaveLength(1); + expect(events[0].event).toBe(PatchManagerEvents.update); + expect(events[0].payload.changes).toHaveLength(1); + expect(events[0].payload.reverseChanges).toHaveLength(1); + }); + + test('Applies patches and respects the external flag', () => { + const calls = []; + const events = []; + const pm = new PatchManager({ + enabled: true, + applyPatch: (changes, options) => calls.push({ changes, options }), + emitter: { + trigger: (event) => events.push(event), + }, + }); + + const patch = { + id: 'patch-1', + changes: [{ op: 'add', path: ['value'], value: 1 }], + reverseChanges: [{ op: 'remove', path: ['value'] }], + }; + + pm.apply(patch); + + expect(calls).toHaveLength(1); + expect(calls[0]).toEqual({ + changes: patch.changes, + options: { external: false, direction: 'forward' }, + }); + expect(events).toEqual([PatchManagerEvents.update]); + + calls.length = 0; + events.length = 0; + + pm.apply(patch, { external: true }); + + expect(calls).toHaveLength(1); + expect(calls[0]).toEqual({ + changes: patch.changes, + options: { external: true, direction: 'forward' }, + }); + expect(events).toHaveLength(0); + }); + + test('Undo and redo apply reverse/forward changes', () => { + const calls = []; + const events = []; + const pm = new PatchManager({ + enabled: true, + applyPatch: (changes, options) => calls.push({ changes, options }), + emitter: { + trigger: (event) => events.push(event), + }, + }); + + const patch = { + id: 'patch-2', + changes: [{ op: 'replace', path: ['value'], value: 2 }], + reverseChanges: [{ op: 'replace', path: ['value'], value: 1 }], + }; + + pm.add(patch); + + const undoPatch = pm.undo(); + const redoPatch = pm.redo(); + + expect(undoPatch).toBe(patch); + expect(redoPatch).toBe(patch); + expect(calls[0]).toEqual({ changes: patch.reverseChanges, options: { direction: 'backward' } }); + expect(calls[1]).toEqual({ changes: patch.changes, options: { direction: 'forward' } }); + expect(events).toEqual([PatchManagerEvents.update, PatchManagerEvents.undo, PatchManagerEvents.redo]); + }); +}); diff --git a/packages/core/test/specs/patch_manager/model/ModelWithPatches.js b/packages/core/test/specs/patch_manager/model/ModelWithPatches.js new file mode 100644 index 000000000..1f0c8de02 --- /dev/null +++ b/packages/core/test/specs/patch_manager/model/ModelWithPatches.js @@ -0,0 +1,92 @@ +import PatchManager, { PatchManagerEvents } from 'patch_manager'; +import ModelWithPatches from 'patch_manager/ModelWithPatches'; + +describe('ModelWithPatches', () => { + test('set records patch with normalized path', async () => { + const events = []; + const pm = new PatchManager({ + enabled: true, + emitter: { + trigger: (event, payload) => events.push({ event, payload }), + }, + }); + + const model = new ModelWithPatches({ id: 'model-1', foo: 'bar' }); + model.em = { Patches: pm }; + model.patchObjectType = 'model'; + + model.set('foo', 'baz'); + + await Promise.resolve(); + + expect(events).toHaveLength(1); + expect(events[0].event).toBe(PatchManagerEvents.update); + + const patch = events[0].payload; + expect(patch.changes).toHaveLength(1); + expect(patch.reverseChanges).toHaveLength(1); + expect(patch.changes[0]).toMatchObject({ + op: 'replace', + path: ['model', 'model-1', 'attributes', 'foo'], + value: 'baz', + }); + expect(patch.reverseChanges[0]).toMatchObject({ + op: 'replace', + path: ['model', 'model-1', 'attributes', 'foo'], + value: 'bar', + }); + }); + + test('set skips patch recording without a patch object type', async () => { + const events = []; + const pm = new PatchManager({ + enabled: true, + emitter: { + trigger: (event, payload) => events.push({ event, payload }), + }, + }); + + const model = new ModelWithPatches({ id: 'model-2', foo: 'bar' }); + model.em = { Patches: pm }; + + model.set('foo', 'baz'); + + await Promise.resolve(); + + expect(model.get('foo')).toBe('baz'); + expect(events).toHaveLength(0); + }); + + test('apply handler changes do not create patches while tracking is suppressed', async () => { + const events = []; + let model; + + const pm = new PatchManager({ + enabled: true, + emitter: { + trigger: (event, payload) => events.push({ event, payload }), + }, + applyPatch: () => { + model.set('foo', 'applied'); + }, + }); + + model = new ModelWithPatches({ id: 'model-3', foo: 'bar' }); + model.em = { Patches: pm }; + model.patchObjectType = 'model'; + + pm.apply( + { + id: 'patch-3', + changes: [{ op: 'replace', path: ['model', 'model-3', 'attributes', 'foo'], value: 'applied' }], + reverseChanges: [{ op: 'replace', path: ['model', 'model-3', 'attributes', 'foo'], value: 'bar' }], + }, + { external: true }, + ); + + await Promise.resolve(); + + expect(model.get('foo')).toBe('applied'); + expect(events).toHaveLength(0); + }); +});