@eclipse-emfcloud/model-manager
Version:
Command-based model editing with undo/redo.
779 lines • 36.7 kB
JavaScript
"use strict";
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 });
// *****************************************************************************
// 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
// *****************************************************************************
const chai_1 = __importStar(require("chai"));
const chai_as_promised_1 = __importDefault(require("chai-as-promised"));
const sinon_1 = __importDefault(require("sinon"));
const sinon_chai_1 = __importDefault(require("sinon-chai"));
const patch_command_1 = require("../patch-command");
const assert_1 = require("assert");
const chai_like_1 = __importDefault(require("chai-like"));
const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
const core_1 = require("../../core");
chai_1.default.use(chai_like_1.default);
chai_1.default.use(chai_as_promised_1.default);
chai_1.default.use(sinon_chai_1.default);
describe('PatchCommand', () => {
let sandbox;
beforeEach(() => {
sandbox = sinon_1.default.createSandbox();
});
afterEach(() => {
sandbox.restore();
});
it('Simple execute/undo/redo', async () => {
const patch = [
{
op: 'replace',
path: '/id',
value: 'updated-test-id',
},
];
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
expectedDocument.id = 'updated-test-id';
const command = new patch_command_1.PatchCommand('Test Command', 'document', patch);
// Execute
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
const executeResult = await command.execute(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
(0, chai_1.expect)(executeResult).to.not.be.undefined;
if (executeResult) {
// Should contain a single 'replace' operation (and maybe some optional 'test' operations)
const operations = executeResult.filter((operation) => operation.op !== 'test');
(0, chai_1.expect)(operations).to.be.an('array').of.length(1);
(0, chai_1.expect)(operations[0].op).to.equal('replace');
}
// Undo
(0, chai_1.expect)(command.canUndo(document)).to.be.true;
command.undo(document);
(0, chai_1.expect)(document).to.deep.equal(testDocument);
// Redo
(0, chai_1.expect)(command.canRedo(document)).to.be.true;
command.redo(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
});
it('Invalid patch', async () => {
const invalidPatch = [
{
op: 'remove',
path: '/wrong-id',
},
];
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
const invalidCommand = new patch_command_1.PatchCommand('Test Command', 'document', invalidPatch);
// Currently, we don't validate commands, so canExecute is always true
await (0, chai_1.expect)(invalidCommand.canExecute(document)).to.eventually.be.true;
const executeResult = await invalidCommand.execute(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
(0, chai_1.expect)(executeResult).to.not.be.undefined;
(0, chai_1.expect)(executeResult).to.be.an('array').empty;
});
it('Command lifecycle', async () => {
const patch = [
{
op: 'replace',
path: '/id',
value: 'updated-test-id',
},
];
const document = (0, cloneDeep_1.default)(testDocument);
const command = new patch_command_1.PatchCommand('Test Command', 'document', patch);
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
(0, chai_1.expect)(command.canUndo(document)).to.be.false;
(0, chai_1.expect)(command.canRedo(document)).to.be.false;
await command.execute(document);
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.false;
(0, chai_1.expect)(command.canUndo(document)).to.be.true;
(0, chai_1.expect)(command.canRedo(document)).to.be.false;
command.undo(document);
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.false;
(0, chai_1.expect)(command.canUndo(document)).to.be.false;
(0, chai_1.expect)(command.canRedo(document)).to.be.true;
command.redo(document);
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.false;
(0, chai_1.expect)(command.canUndo(document)).to.be.true;
(0, chai_1.expect)(command.canRedo(document)).to.be.false;
});
it('Command lifecycle (Invalid)', async () => {
const patch = [
{
op: 'replace',
path: '/id',
value: 'updated-test-id',
},
];
const document = (0, cloneDeep_1.default)(testDocument);
const command = new patch_command_1.PatchCommand('Test Command', 'document', patch);
// Undo command before it is executed: throws an error
let success = false;
try {
command.undo(document);
success = true;
}
catch (error) {
// Okay
}
if (success) {
(0, assert_1.fail)('Command should not be undoable');
}
// Redo command before it is executed: throws an error
success = false;
try {
command.redo(document);
success = true;
}
catch (error) {
// Okay
}
if (success) {
(0, assert_1.fail)('Command should not be redoable');
}
await command.execute(document);
// Execute command twice: throws an error
success = false;
try {
await command.execute(document);
success = true;
}
catch (error) {
// Okay
}
if (success) {
(0, assert_1.fail)('Command should not be executable');
}
// Redo command before it is undone: throws an error
success = false;
try {
command.redo(document);
success = true;
}
catch (error) {
// Okay
}
if (success) {
(0, assert_1.fail)('Command should not be redoable');
}
command.undo(document);
// Execute command twice: throws an error (even after undo)
success = false;
try {
await command.execute(document);
success = true;
}
catch (error) {
// Okay
}
if (success) {
(0, assert_1.fail)('Command should not be executable');
}
// Undo command twice: throws an error
success = false;
try {
command.undo(document);
success = true;
}
catch (error) {
// Okay
}
if (success) {
(0, assert_1.fail)('Command should not be undoable');
}
});
describe('MultiPatchCommand', () => {
it('1 Sub command', async () => {
const patch = [
{
op: 'replace',
path: '/id',
value: 'updated-test-id',
},
];
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
expectedDocument.id = 'updated-test-id';
const command = new patch_command_1.MultiPatchCommand('Test Multi Command', {
modelId: 'test',
patch,
});
const getModel = (modelId) => modelId == 'test' ? document : undefined;
// Execute
await (0, chai_1.expect)(command.canExecute(getModel)).to.eventually.be.true;
const executeResult = await command.execute(getModel);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
(0, chai_1.expect)(executeResult).to.not.be.undefined;
if (executeResult) {
const modelToPatch = (0, core_1.groupByModelId)(executeResult);
(0, chai_1.expect)(modelToPatch.size).to.equal(1);
const resultPatch = modelToPatch.get('test');
(0, chai_1.expect)(resultPatch).to.not.be.undefined;
if (resultPatch !== undefined) {
const operations = resultPatch.filter((operation) => operation.op !== 'test');
// Should contain a single 'replace' operation (and maybe some optional 'test' operations)
(0, chai_1.expect)(operations).to.be.an('array').of.length(1);
(0, chai_1.expect)(operations[0].op).to.equal('replace');
}
}
// Undo
await (0, chai_1.expect)(command.canUndo(getModel)).to.eventually.be.true;
await command.undo(getModel);
(0, chai_1.expect)(document).to.deep.equal(testDocument);
// Redo
await (0, chai_1.expect)(command.canRedo(getModel)).to.eventually.be.true;
await command.redo(getModel);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
});
it('2 Sub commands', async () => {
const patch1 = [
{
op: 'replace',
path: '/id',
value: 'updated-test-id',
},
];
const patch2 = [
{
op: 'add',
path: '/children/-',
value: {
type: 'child-node',
value: 890,
valid: false,
},
},
];
const document1 = (0, cloneDeep_1.default)(testDocument);
const document2 = (0, cloneDeep_1.default)(secondTestDocument);
const expectedDocument1 = (0, cloneDeep_1.default)(testDocument);
expectedDocument1.id = 'updated-test-id';
const expectedDocument2 = (0, cloneDeep_1.default)(secondTestDocument);
expectedDocument2.children.push({
type: 'child-node',
value: 890,
valid: false,
});
const command = new patch_command_1.MultiPatchCommand('Test Multi Command', {
modelId: 'document1',
patch: patch1,
}, {
modelId: 'document2',
patch: patch2,
});
const getModel = (modelId) => {
switch (modelId) {
case 'document1':
return document1;
case 'document2':
return document2;
default:
return undefined;
}
};
// Execute
// PatchCommand is synchronous, so MultiPatchCommand is, too
await (0, chai_1.expect)(command.canExecute(getModel)).to.eventually.be.true;
const executeResult = await command.execute(getModel);
(0, chai_1.expect)(document1).to.deep.equal(expectedDocument1);
(0, chai_1.expect)(document2).to.deep.equal(expectedDocument2);
(0, chai_1.expect)(executeResult).to.not.be.undefined;
if (executeResult) {
const modelToPatch = (0, core_1.groupByModelId)(executeResult);
(0, chai_1.expect)(modelToPatch.size).to.equal(2);
const resultPatch1 = modelToPatch.get('document1');
(0, chai_1.expect)(resultPatch1).to.not.be.undefined;
if (resultPatch1 !== undefined) {
const operations = resultPatch1.filter((operation) => operation.op !== 'test');
// Should contain a single 'replace' operation (and maybe some optional 'test' operations)
(0, chai_1.expect)(operations).to.be.an('array').of.length(1);
(0, chai_1.expect)(operations[0].op).to.equal('replace');
}
const resultPatch2 = modelToPatch.get('document2');
(0, chai_1.expect)(resultPatch2).to.not.be.undefined;
if (resultPatch2 !== undefined) {
const operations = resultPatch2.filter((operation) => operation.op !== 'test');
// Should contain a single 'replace' operation (and maybe some optional 'test' operations)
(0, chai_1.expect)(operations).to.be.an('array').of.length(1);
(0, chai_1.expect)(operations[0].op).to.equal('add');
}
}
// Undo
await (0, chai_1.expect)(command.canUndo(getModel)).to.eventually.be.true;
await command.undo(getModel);
(0, chai_1.expect)(document1).to.deep.equal(testDocument);
(0, chai_1.expect)(document2).to.deep.equal(secondTestDocument);
// Redo
await (0, chai_1.expect)(command.canRedo(getModel)).to.eventually.be.true;
await command.redo(getModel);
(0, chai_1.expect)(document1).to.deep.equal(expectedDocument1);
(0, chai_1.expect)(document2).to.deep.equal(expectedDocument2);
});
it('2 Sub commands - 1 model', async () => {
const patch1 = [
{
op: 'replace',
path: '/id',
value: 'updated-test-id',
},
];
const patch2 = [
{
op: 'add',
path: '/children/-',
value: {
type: 'child-node',
value: 90,
valid: true,
},
},
];
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
expectedDocument.id = 'updated-test-id';
expectedDocument.children.push({
type: 'child-node',
value: 90,
valid: true,
});
const command = new patch_command_1.MultiPatchCommand('Test Multi Command', {
modelId: 'document',
patch: patch1,
}, {
modelId: 'document',
patch: patch2,
});
const getModel = (modelId) => modelId === 'document' ? document : undefined;
// Execute
await (0, chai_1.expect)(command.canExecute(getModel)).to.eventually.be.true;
const executeResult = await command.execute(getModel);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
(0, chai_1.expect)(executeResult).to.not.be.undefined;
if (executeResult) {
const modelToPatch = (0, core_1.groupByModelId)(executeResult);
(0, chai_1.expect)(modelToPatch.size).to.equal(1);
const resultPatch = modelToPatch.get('document');
(0, chai_1.expect)(resultPatch).to.not.be.undefined;
if (resultPatch !== undefined) {
const operations = resultPatch.filter((operation) => operation.op !== 'test');
// Should contain one 'replace' operation and one 'add' operation
// (and maybe some optional 'test' operations)
(0, chai_1.expect)(operations).to.be.an('array').of.length(2);
(0, chai_1.expect)(operations[0].op).to.equal('replace');
(0, chai_1.expect)(operations[1].op).to.equal('add');
}
}
// Undo
await (0, chai_1.expect)(command.canUndo(getModel)).to.eventually.be.true;
await command.undo(getModel);
(0, chai_1.expect)(document).to.deep.equal(testDocument);
// Redo
await (0, chai_1.expect)(command.canRedo(getModel)).to.eventually.be.true;
await command.redo(getModel);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
});
});
describe('createModelUpdaterCommand', () => {
it('Simple execute/undo/redo', async () => {
const updater = (workingCopy, modelId) => {
(0, chai_1.expect)(modelId).to.be.equal('document');
workingCopy.id = 'updated-test-id';
};
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
expectedDocument.id = 'updated-test-id';
const command = (0, patch_command_1.createModelUpdaterCommand)('Test Command', 'document', updater);
// Execute
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
const executeResult = await command.execute(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
(0, chai_1.expect)(executeResult).to.not.be.undefined;
if (executeResult) {
// Should contain a single 'replace' operation (and maybe some optional 'test' operations)
const operations = executeResult.filter((operation) => operation.op !== 'test');
(0, chai_1.expect)(operations).to.be.an('array').of.length(1);
(0, chai_1.expect)(operations[0].op).to.equal('replace');
}
// Undo
(0, chai_1.expect)(command.canUndo(document)).to.be.true;
command.undo(document);
(0, chai_1.expect)(document).to.deep.equal(testDocument);
// Redo
(0, chai_1.expect)(command.canRedo(document)).to.be.true;
command.redo(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
});
it('Execute/undo/redo when modifying attribute to undefined', async () => {
const updater = (workingCopy, modelId) => {
(0, chai_1.expect)(modelId).to.be.equal('document');
workingCopy.label = undefined;
if (workingCopy.children[0]) {
workingCopy.children[0].value = undefined;
}
};
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (() => {
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
expectedDocument.label = undefined;
if (expectedDocument.children[0]) {
expectedDocument.children[0].value = undefined;
}
return JSON.parse(JSON.stringify(expectedDocument));
})();
const command = (0, patch_command_1.createModelUpdaterCommand)('Test Command setting undefined attributes', 'document', updater);
// Execute
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
const executeResult = await command.execute(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
(0, chai_1.expect)(executeResult).to.not.be.undefined;
if (executeResult) {
// Should contain two 'remove' operations (and maybe some optional 'test' operations)
const operations = executeResult.filter((operation) => operation.op !== 'test');
(0, chai_1.expect)(operations).to.be.an('array').of.length(2);
(0, chai_1.expect)(operations[0].op).to.equal('remove');
(0, chai_1.expect)(operations[1].op).to.equal('remove');
}
// Undo
(0, chai_1.expect)(command.canUndo(document)).to.be.true;
command.undo(document);
(0, chai_1.expect)(document).to.deep.equal(testDocument);
// Redo
(0, chai_1.expect)(command.canRedo(document)).to.be.true;
command.redo(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
});
it('Execute/undo/redo when adding undefined value in array', async () => {
const updater = (workingCopy, modelId) => {
(0, chai_1.expect)(modelId).to.be.equal('document');
workingCopy.children.push(undefined);
};
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (() => {
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
expectedDocument.children.push(undefined);
return JSON.parse(JSON.stringify(expectedDocument));
})();
const command = (0, patch_command_1.createModelUpdaterCommand)('Test Command adding an undefined value in array', 'document', updater);
// Execute
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
const executeResult = await command.execute(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
(0, chai_1.expect)(executeResult).to.not.be.undefined;
if (executeResult) {
// Should contain one 'add' operations (and maybe some optional 'test' operations)
const operations = executeResult.filter((operation) => operation.op !== 'test');
(0, chai_1.expect)(operations).to.be.an('array').of.length(1);
(0, chai_1.expect)(operations[0].op).to.equal('add');
}
// Undo
(0, chai_1.expect)(command.canUndo(document)).to.be.true;
command.undo(document);
(0, chai_1.expect)(document).to.deep.equal(testDocument);
// Redo
(0, chai_1.expect)(command.canRedo(document)).to.be.true;
command.redo(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
});
it('Execute/undo/redo when modifying model to be recursive', async () => {
const updater = (workingCopy, modelId) => {
(0, chai_1.expect)(modelId).to.be.equal('document');
workingCopy.children.push(workingCopy);
};
const document = (0, cloneDeep_1.default)(testDocument);
const command = (0, patch_command_1.createModelUpdaterCommand)('Test Command adding an undefined value in array', 'document', updater);
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
try {
await command.execute(document);
}
catch (e) {
// success
return;
}
(0, assert_1.fail)('execution should not be possible as the model is recursive');
});
it('Execute/undo/redo when modifying model to contain doubly linked references', async () => {
const updater = (workingCopy, modelId) => {
(0, chai_1.expect)(modelId).to.be.equal('document');
if (workingCopy.children[0] && workingCopy.children[1]) {
workingCopy.children[0].link = workingCopy.children[1];
workingCopy.children[1].link = workingCopy.children[0];
}
else {
(0, assert_1.fail)('Unexpected test document');
}
};
const document = (0, cloneDeep_1.default)(testDocument);
const command = (0, patch_command_1.createModelUpdaterCommand)('Test Command adding an undefined value in array', 'document', updater);
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
try {
await command.execute(document);
}
catch (e) {
// success
return;
}
(0, assert_1.fail)('execution should not be possible as the model is doubly linked');
});
it('Execute/undo/redo empty change', async () => {
const updater = (workingCopy, modelId) => {
(0, chai_1.expect)(modelId).to.be.equal('document');
(0, chai_1.expect)(workingCopy).to.be.equal(document);
// Don't make any change. That's the point
};
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
const command = (0, patch_command_1.createModelUpdaterCommand)('Empty Command', 'document', updater);
// Execute
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
const executeResult = await command.execute(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
(0, chai_1.expect)(executeResult).to.not.be.undefined;
if (executeResult) {
// Should contain a single 'replace' operation (and maybe some optional 'test' operations)
const operations = executeResult.filter((operation) => operation.op !== 'test');
(0, chai_1.expect)(operations).to.be.an('array').of.length(0);
}
// Undo
(0, chai_1.expect)(command.canUndo(document)).to.be.true;
command.undo(document);
(0, chai_1.expect)(document).to.deep.equal(testDocument);
// Redo
(0, chai_1.expect)(command.canRedo(document)).to.be.true;
command.redo(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
});
describe('Options', () => {
it('Non-executable', async () => {
const updater = (model, modelId) => {
(0, chai_1.expect)(model).to.be.equal(document);
(0, chai_1.expect)(modelId).to.be.equal('document');
};
const canExecute = (model, modelId) => {
(0, chai_1.expect)(model).to.be.equal(document);
(0, chai_1.expect)(modelId).to.be.equal('document');
return Promise.resolve('test reason');
};
const document = (0, cloneDeep_1.default)(testDocument);
const command = (0, patch_command_1.createModelUpdaterCommand)('Test Command', 'document', updater, { canExecute });
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.false;
await (0, chai_1.expect)(command.execute(document)).to.eventually.be.rejectedWith(/PatchCommand.*cannot be executed: test reason/);
});
it('Strict undo predicate', async () => {
const document = (0, cloneDeep_1.default)(testDocument);
const updater = (workingCopy) => {
workingCopy.id = 'updated-id';
};
const command = (0, patch_command_1.createModelUpdaterCommand)('Undo Preconditions', 'document', updater
// Default mode is 'strict', so test that, too
);
await command.execute(document);
// Simulate an interfering change as from a trigger
document.id = 'broken';
let thrown;
try {
await command.undo(document);
}
catch (error) {
thrown = error;
}
(0, chai_1.expect)(thrown)
.to.be.an('Error')
.with.property('message')
.contains('Test operation failed');
});
it('Lax undo predicate', async () => {
const debug = sandbox.stub(console, 'debug');
const document = (0, cloneDeep_1.default)(testDocument);
const updater = (workingCopy) => {
workingCopy.id = 'updated-id';
};
const command = (0, patch_command_1.createModelUpdaterCommand)('Undo Preconditions', 'document', updater, { preconditionsMode: 'lax' });
await command.execute(document);
// Simulate an interfering change as from a trigger
document.id = 'broken';
await command.undo(document);
(0, chai_1.expect)(debug).to.have.been.calledWithMatch('model-manager/patch-command', 'Inapplicable undo/redo patch.', Error);
});
});
describe('With Result', () => {
it('success', async () => {
const updaterWithResult = (workingCopy, modelId) => {
(0, chai_1.expect)(modelId).to.be.equal('document');
workingCopy.id = 'updated-test-id';
return { message: 'Hello, world.' };
};
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
expectedDocument.id = 'updated-test-id';
const command = (0, patch_command_1.createModelUpdaterCommandWithResult)('Test Command', 'document', updaterWithResult);
(0, chai_1.expect)(command.result).to.be.like({ status: 'pending' });
// Execute
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
await command.execute(document);
(0, chai_1.expect)(command.result).to.exist;
(0, chai_1.expect)(command.result).to.be.like({
status: 'ready',
value: { message: 'Hello, world.' },
});
});
it('fails with Error', async () => {
const updaterWithResult = () => {
throw new Error('💣');
};
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
expectedDocument.id = 'updated-test-id';
const command = (0, patch_command_1.createModelUpdaterCommandWithResult)('Test Command', 'document', updaterWithResult);
let error;
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
try {
await command.execute(document);
}
catch (e) {
error = e;
}
(0, chai_1.expect)(command.result).to.exist;
(0, chai_1.expect)(command.result).to.be.like({
status: 'failed',
error,
});
});
it('fails with string', async () => {
const updaterWithResult = () => {
throw '💣';
};
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
expectedDocument.id = 'updated-test-id';
const command = (0, patch_command_1.createModelUpdaterCommandWithResult)('Test Command', 'document', updaterWithResult);
let error;
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
try {
await command.execute(document);
}
catch (e) {
error = e;
}
(0, chai_1.expect)(command.result).to.exist;
(0, chai_1.expect)(command.result).to.be.like({
status: 'failed',
error,
});
});
});
});
describe('createModelPatchCommand', () => {
it('Simple execute/undo/redo', async () => {
const patchFunction = (workingCopy, modelId) => {
(0, chai_1.expect)(modelId).to.be.equal('document');
(0, chai_1.expect)(workingCopy).to.be.equal(document);
return [
{
op: 'replace',
path: '/id',
value: 'updated-test-id',
},
];
};
const document = (0, cloneDeep_1.default)(testDocument);
const expectedDocument = (0, cloneDeep_1.default)(testDocument);
expectedDocument.id = 'updated-test-id';
const command = (0, patch_command_1.createModelPatchCommand)('Test Command', 'document', patchFunction);
// Execute
await (0, chai_1.expect)(command.canExecute(document)).to.eventually.be.true;
const executeResult = await command.execute(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
(0, chai_1.expect)(executeResult).to.not.be.undefined;
if (executeResult) {
// Should contain a single 'replace' operation (and maybe some optional 'test' operations)
const operations = executeResult.filter((operation) => operation.op !== 'test');
(0, chai_1.expect)(operations).to.be.an('array').of.length(1);
(0, chai_1.expect)(operations[0].op).to.equal('replace');
}
// Undo
(0, chai_1.expect)(command.canUndo(document)).to.be.true;
command.undo(document);
(0, chai_1.expect)(document).to.deep.equal(testDocument);
// Redo
(0, chai_1.expect)(command.canRedo(document)).to.be.true;
command.redo(document);
(0, chai_1.expect)(document).to.deep.equal(expectedDocument);
});
});
});
const testDocument = {
id: 'test-id',
label: 'Test Document',
children: [
{
type: 'child-node',
value: 13,
valid: true,
},
{
type: 'child-node',
value: 175,
valid: false,
},
{
type: 'child-node',
value: 1,
valid: true,
},
],
};
const secondTestDocument = {
id: 'second-test-id',
label: 'Second Test Document',
children: [
{
type: 'child-node',
value: 130,
valid: true,
},
{
type: 'child-node',
value: 775,
valid: false,
},
],
};
//# sourceMappingURL=patch-command.spec.js.map