UNPKG

@eclipse-emfcloud/model-manager

Version:

Command-based model editing with undo/redo.

193 lines 10.3 kB
"use strict"; // ***************************************************************************** // Copyright (C) 2023-2024 STMicroelectronics. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: MIT License which is // available at https://opensource.org/licenses/MIT. // // SPDX-License-Identifier: EPL-2.0 OR MIT // ***************************************************************************** var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const chai_1 = __importStar(require("chai")); const chai_like_1 = __importDefault(require("chai-like")); const crypto_1 = require("crypto"); const sinon_1 = require("sinon"); const patch_1 = require("../../patch"); const command_stack_1 = require("../command-stack"); chai_1.default.use(chai_like_1.default); const command = { label: 'do it' }; const fakeDelta = () => Promise.resolve(new Map([[command, [{ op: 'add', path: '/root/a', value: 'test-value' }]]])); const fakeCommandStack = (sandbox) => { const result = {}; result.execute = sandbox.fake.returns(fakeDelta()); result.executeAndAppend = sandbox.fake.returns(fakeDelta()); result.undo = sandbox.fake.returns(fakeDelta()); result.redo = sandbox.fake.returns(fakeDelta()); result.flush = sandbox.fake.returns([command]); result.canExecute = sandbox.fake.returns(Promise.resolve(true)); result.canRedo = sandbox.fake.returns(Promise.resolve(true)); result.canUndo = sandbox.fake.returns(Promise.resolve(true)); result.getUndoCommand = sandbox.fake.returns(command); result.getRedoCommand = sandbox.fake.returns(command); result.markSaved = sandbox.fake(); result.getDirtyModelIds = sandbox.fake.returns(['dirty']); result.isDirty = sandbox.fake.returns(true); result.subscribe = sandbox.fake.returns({ close: sandbox.fake(), }); return result; }; /** Cast an object's method as a verifiable Sinon spy. */ const verify = (func) => func; describe('CommandStackImpl', () => { let sandbox; let core; let stack; let id; let editingContext; beforeEach(() => { sandbox = (0, sinon_1.createSandbox)(); core = fakeCommandStack(sandbox); id = 'stack.' + (0, crypto_1.randomInt)(1024 * 1024).toString(16); editingContext = id; stack = new command_stack_1.CommandStackImpl(core, { id }); }); afterEach(() => { sandbox.restore(); }); it('execute()', async () => { const result = await stack.execute(command); (0, chai_1.expect)(verify(core.execute).calledWithExactly(command, editingContext), 'not delegated for its editing context').to.be.true; (0, chai_1.expect)(result, 'wrong return result').is.equal(await verify(core.execute).lastCall.returnValue); }); it('executeAndAppend()', async () => { const result = await stack.executeAndAppend(command); (0, chai_1.expect)(verify(core.executeAndAppend).calledWithExactly(editingContext, command), 'not delegated for its editing context').to.be.true; (0, chai_1.expect)(result, 'wrong return result').is.equal(await verify(core.executeAndAppend).lastCall.returnValue); }); it('canExecute()', async () => { const result = await stack.canExecute(command); (0, chai_1.expect)(verify(core.canExecute).calledWith(command, editingContext), 'not delegated for its editing context').to.be.true; (0, chai_1.expect)(result, 'wrong return result').is.equal(await verify(core.canExecute).lastCall.returnValue); }); it('canUndo()', async () => { const result = await stack.canUndo(); (0, chai_1.expect)(verify(core.canUndo).calledWith(editingContext), 'not delegated for its editing context').to.be.true; (0, chai_1.expect)(result, 'wrong return result').is.equal(await verify(core.canUndo).lastCall.returnValue); }); it('canRedo()', async () => { const result = await stack.canRedo(); (0, chai_1.expect)(verify(core.canRedo).calledWith(editingContext), 'not delegated for its editing context').to.be.true; (0, chai_1.expect)(result, 'wrong return result').is.equal(await verify(core.canRedo).lastCall.returnValue); }); it('getUndoCommand()', () => { const result = stack.getUndoCommand(); (0, chai_1.expect)(verify(core.getUndoCommand).calledWith(editingContext), 'not delegated for its editing context').to.be.true; (0, chai_1.expect)(result, 'wrong return result').is.equal(verify(core.getUndoCommand).lastCall.returnValue); }); it('getRedoCommand()', () => { const result = stack.getRedoCommand(); (0, chai_1.expect)(verify(core.getRedoCommand).calledWith(editingContext), 'not delegated for its editing context').to.be.true; (0, chai_1.expect)(result, 'wrong return result').is.equal(verify(core.getRedoCommand).lastCall.returnValue); }); it('undo()', async () => { const result = await stack.undo(); (0, chai_1.expect)(verify(core.undo).calledWith(editingContext), 'not delegated for its editing context').to.be.true; (0, chai_1.expect)(result, 'wrong return result').is.equal(await verify(core.undo).lastCall.returnValue); }); it('redo()', async () => { const result = await stack.redo(); (0, chai_1.expect)(verify(core.redo).calledWith(editingContext), 'not delegated for its editing context').to.be.true; (0, chai_1.expect)(result, 'wrong return result').is.equal(await verify(core.redo).lastCall.returnValue); }); it('flush()', () => { const result = stack.flush(); (0, chai_1.expect)(verify(core.flush).calledWith(editingContext), 'not delegated for its editing context').to.be.true; (0, chai_1.expect)(result, 'wrong return result').is.equal(verify(core.flush).lastCall.returnValue); }); it('markSaved()', () => { stack.markSaved(); (0, chai_1.expect)(verify(core.markSaved).calledWith(editingContext)); }); it('getDirtyModels()', () => { const result = stack.getDirtyModelIds(); (0, chai_1.expect)(verify(core.getDirtyModelIds).calledWith(editingContext)); (0, chai_1.expect)(result, 'wrong return result').is.equal(verify(core.getDirtyModelIds).lastCall.returnValue); }); it('isDirty()', () => { const result = stack.isDirty(); (0, chai_1.expect)(verify(core.getDirtyModelIds).calledWith(editingContext)); (0, chai_1.expect)(result, 'wrong return result').is.equal(verify(core.isDirty).lastCall.returnValue); }); it('subscribe()', () => { const result = stack.subscribe(); (0, chai_1.expect)(verify(core.subscribe)).calledWith(editingContext); const coreSub = verify(core.subscribe).lastCall.returnValue; (0, chai_1.expect)(coreSub, 'core subscription has no changed call-back').to.haveOwnProperty('onContextChanged'); (0, chai_1.expect)(coreSub, 'core subscription has no dirty call-back').to.haveOwnProperty('onDirtyStateChanged'); const onCommandStackChanged = sandbox.stub(); const onDirtyStateChanged = sandbox.stub(); const command = new patch_1.PatchCommand('label', 'model', []); const dirtyStateChanges = new Map(); // The subscription doesn't break when its callbacks aren't set coreSub.onContextChanged?.('', 'undone', command); coreSub.onDirtyStateChanged?.('', dirtyStateChanges); result.onCommandStackChanged = onCommandStackChanged; result.onDirtyStateChanged = onDirtyStateChanged; coreSub.onContextChanged?.('', 'undone', command); (0, chai_1.expect)(onCommandStackChanged).to.have.been.calledWithExactly('undone', command); coreSub.onDirtyStateChanged?.('', dirtyStateChanges); (0, chai_1.expect)(onDirtyStateChanged).to.have.been.calledWithExactly(dirtyStateChanges); result.close(); (0, chai_1.expect)(verify(coreSub.close)).to.have.been.called; }); it('getCoreCommandStack()', () => { const underlying = stack.getCoreCommandStack(); (0, chai_1.expect)(underlying, 'wrong core stack').to.be.equal(core); }); describe('options', () => { it('default options', async () => { stack = new command_stack_1.CommandStackImpl(core, { id }); await stack.execute(command); (0, chai_1.expect)(verify(core.flush)).not.to.have.been.called; }); it('keepHistory option false', async () => { stack = new command_stack_1.CommandStackImpl(core, { id, keepHistory: false }); await stack.execute(command); (0, chai_1.expect)(verify(core.flush)).to.have.been.called; }); }); }); //# sourceMappingURL=command-stack-impl.spec.js.map