@eclipse-emfcloud/model-manager
Version:
Command-based model editing with undo/redo.
193 lines • 10.3 kB
JavaScript
;
// *****************************************************************************
// 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