mirror of
https://github.com/nasa/openmct.git
synced 2025-02-01 00:45:41 +00:00
Merge pull request #874 from nasa/persist-on-mutation-825
[Persistence] Persist on mutation
This commit is contained in:
commit
f2d44114fa
@ -200,7 +200,6 @@ define([
|
||||
"name": "Remove",
|
||||
"description": "Remove this object from its containing object.",
|
||||
"depends": [
|
||||
"$q",
|
||||
"navigationService"
|
||||
]
|
||||
},
|
||||
|
@ -40,19 +40,11 @@ define(
|
||||
var self = this,
|
||||
editAction = this.domainObject.getCapability('action').getActions("edit")[0];
|
||||
|
||||
// Persist changes to the domain object
|
||||
function doPersist() {
|
||||
var persistence =
|
||||
self.domainObject.getCapability('persistence');
|
||||
return persistence.persist();
|
||||
}
|
||||
|
||||
// Link these objects
|
||||
function doLink() {
|
||||
var composition = self.domainObject &&
|
||||
self.domainObject.getCapability('composition');
|
||||
return composition && composition.add(self.selectedObject)
|
||||
.then(doPersist);
|
||||
return composition && composition.add(self.selectedObject);
|
||||
}
|
||||
|
||||
if (editAction) {
|
||||
|
@ -50,12 +50,6 @@ define(
|
||||
domainObject = this.domainObject,
|
||||
dialogService = this.dialogService;
|
||||
|
||||
// Persist modifications to this domain object
|
||||
function doPersist() {
|
||||
var persistence = domainObject.getCapability('persistence');
|
||||
return persistence && persistence.persist();
|
||||
}
|
||||
|
||||
// Update the domain object model based on user input
|
||||
function updateModel(userInput, dialog) {
|
||||
return domainObject.useCapability('mutation', function (model) {
|
||||
@ -73,11 +67,9 @@ define(
|
||||
dialog.getFormStructure(),
|
||||
dialog.getInitialFormValue()
|
||||
).then(function (userInput) {
|
||||
// Update the model, if user input was provided
|
||||
return userInput && updateModel(userInput, dialog);
|
||||
}).then(function (result) {
|
||||
return result && doPersist();
|
||||
});
|
||||
// Update the model, if user input was provided
|
||||
return userInput && updateModel(userInput, dialog);
|
||||
});
|
||||
}
|
||||
|
||||
return type && showDialog(type);
|
||||
@ -94,9 +86,7 @@ define(
|
||||
creatable = type && type.hasFeature('creation');
|
||||
|
||||
// Only allow creatable types to be edited
|
||||
return domainObject &&
|
||||
domainObject.hasCapability("persistence") &&
|
||||
creatable;
|
||||
return domainObject && creatable;
|
||||
};
|
||||
|
||||
return PropertiesAction;
|
||||
|
@ -39,9 +39,8 @@ define(
|
||||
* @constructor
|
||||
* @implements {Action}
|
||||
*/
|
||||
function RemoveAction($q, navigationService, context) {
|
||||
function RemoveAction(navigationService, context) {
|
||||
this.domainObject = (context || {}).domainObject;
|
||||
this.$q = $q;
|
||||
this.navigationService = navigationService;
|
||||
}
|
||||
|
||||
@ -51,8 +50,7 @@ define(
|
||||
* fulfilled when the action has completed.
|
||||
*/
|
||||
RemoveAction.prototype.perform = function () {
|
||||
var $q = this.$q,
|
||||
navigationService = this.navigationService,
|
||||
var navigationService = this.navigationService,
|
||||
domainObject = this.domainObject;
|
||||
/*
|
||||
* Check whether an object ID matches the ID of the object being
|
||||
@ -71,15 +69,6 @@ define(
|
||||
model.composition = model.composition.filter(isNotObject);
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoke persistence on a domain object. This will be called upon
|
||||
* the removed object's parent (as its composition will have changed.)
|
||||
*/
|
||||
function doPersist(domainObj) {
|
||||
var persistence = domainObj.getCapability('persistence');
|
||||
return persistence && persistence.persist();
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks current object and ascendants of current
|
||||
* object with object being removed, if the current
|
||||
@ -119,15 +108,10 @@ define(
|
||||
// navigates to existing object up tree
|
||||
checkObjectNavigation(object, parent);
|
||||
|
||||
return $q.when(
|
||||
parent.useCapability('mutation', doMutate)
|
||||
).then(function () {
|
||||
return doPersist(parent);
|
||||
});
|
||||
return parent.useCapability('mutation', doMutate);
|
||||
}
|
||||
|
||||
return $q.when(domainObject)
|
||||
.then(removeFromContext);
|
||||
return removeFromContext(domainObject);
|
||||
};
|
||||
|
||||
// Object needs to have a parent for Remove to be applicable
|
||||
|
@ -81,6 +81,10 @@ define(
|
||||
return this.persistenceCapability.getSpace();
|
||||
};
|
||||
|
||||
TransactionalPersistenceCapability.prototype.persisted = function () {
|
||||
return this.persistenceCapability.persisted();
|
||||
};
|
||||
|
||||
return TransactionalPersistenceCapability;
|
||||
}
|
||||
);
|
||||
|
@ -50,17 +50,13 @@ define(
|
||||
this.listenHandle = undefined;
|
||||
|
||||
// Mutate and persist a new version of a domain object's model.
|
||||
function doPersist(model) {
|
||||
function doMutate(model) {
|
||||
var domainObject = self.domainObject;
|
||||
|
||||
// First, mutate; then, persist.
|
||||
return $q.when(domainObject.useCapability("mutation", function () {
|
||||
return model;
|
||||
})).then(function (result) {
|
||||
// Only persist when mutation was successful
|
||||
return result &&
|
||||
domainObject.getCapability("persistence").persist();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
// Handle changes to model and/or view configuration
|
||||
@ -80,14 +76,14 @@ define(
|
||||
].join(" "));
|
||||
|
||||
// Update the configuration stored in the model, and persist.
|
||||
if (domainObject && domainObject.hasCapability("persistence")) {
|
||||
if (domainObject) {
|
||||
// Configurations for specific views are stored by
|
||||
// key in the "configuration" field of the model.
|
||||
if (self.key && configuration) {
|
||||
model.configuration = model.configuration || {};
|
||||
model.configuration[self.key] = configuration;
|
||||
}
|
||||
doPersist(model);
|
||||
doMutate(model);
|
||||
}
|
||||
}
|
||||
|
||||
|
48
platform/commonUI/edit/src/services/NestedTransaction.js
Normal file
48
platform/commonUI/edit/src/services/NestedTransaction.js
Normal file
@ -0,0 +1,48 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
define(['./Transaction'], function (Transaction) {
|
||||
/**
|
||||
* A nested transaction is a transaction which takes place in the context
|
||||
* of a larger parent transaction. It becomes part of the parent
|
||||
* transaction when (and only when) committed.
|
||||
* @param parent
|
||||
* @constructor
|
||||
* @extends {platform/commonUI/edit/services.Transaction}
|
||||
* @memberof platform/commonUI/edit/services
|
||||
*/
|
||||
function NestedTransaction(parent) {
|
||||
this.parent = parent;
|
||||
Transaction.call(this, parent.$log);
|
||||
}
|
||||
|
||||
NestedTransaction.prototype = Object.create(Transaction.prototype);
|
||||
|
||||
NestedTransaction.prototype.commit = function () {
|
||||
this.parent.add(
|
||||
Transaction.prototype.commit.bind(this),
|
||||
Transaction.prototype.cancel.bind(this)
|
||||
);
|
||||
return Promise.resolve(true);
|
||||
};
|
||||
|
||||
return NestedTransaction;
|
||||
});
|
96
platform/commonUI/edit/src/services/Transaction.js
Normal file
96
platform/commonUI/edit/src/services/Transaction.js
Normal file
@ -0,0 +1,96 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
define([], function () {
|
||||
/**
|
||||
* A Transaction represents a set of changes that are intended to
|
||||
* be kept or discarded as a unit.
|
||||
* @param $log Angular's `$log` service, for logging messages
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit/services
|
||||
*/
|
||||
function Transaction($log) {
|
||||
this.$log = $log;
|
||||
this.callbacks = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a change to the current transaction, as expressed by functions
|
||||
* to either keep or discard the change.
|
||||
* @param {Function} commit called when the transaction is committed
|
||||
* @param {Function} cancel called when the transaction is cancelled
|
||||
* @returns {Function) a function which may be called to remove this
|
||||
* pair of callbacks from the transaction
|
||||
*/
|
||||
Transaction.prototype.add = function (commit, cancel) {
|
||||
var callback = { commit: commit, cancel: cancel };
|
||||
this.callbacks.push(callback);
|
||||
return function () {
|
||||
this.callbacks = this.callbacks.filter(function (c) {
|
||||
return c !== callback;
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of changes in the current transaction.
|
||||
* @returns {number} the size of the current transaction
|
||||
*/
|
||||
Transaction.prototype.size = function () {
|
||||
return this.callbacks.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Keep all changes associated with this transaction.
|
||||
* @method {platform/commonUI/edit/services.Transaction#commit}
|
||||
* @returns {Promise} a promise which will resolve when all callbacks
|
||||
* have been handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Discard all changes associated with this transaction.
|
||||
* @method {platform/commonUI/edit/services.Transaction#cancel}
|
||||
* @returns {Promise} a promise which will resolve when all callbacks
|
||||
* have been handled.
|
||||
*/
|
||||
|
||||
['commit', 'cancel'].forEach(function (method) {
|
||||
Transaction.prototype[method] = function () {
|
||||
var promises = [];
|
||||
var callback;
|
||||
|
||||
while (this.callbacks.length > 0) {
|
||||
callback = this.callbacks.shift();
|
||||
try {
|
||||
promises.push(callback[method]());
|
||||
} catch (e) {
|
||||
this.$log
|
||||
.error("Error trying to " + method + " transaction.");
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
return Transaction;
|
||||
});
|
@ -21,8 +21,8 @@
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
['./Transaction', './NestedTransaction'],
|
||||
function (Transaction, NestedTransaction) {
|
||||
/**
|
||||
* Implements an application-wide transaction state. Once a
|
||||
* transaction is started, calls to
|
||||
@ -37,10 +37,7 @@ define(
|
||||
function TransactionService($q, $log) {
|
||||
this.$q = $q;
|
||||
this.$log = $log;
|
||||
this.transaction = false;
|
||||
|
||||
this.onCommits = [];
|
||||
this.onCancels = [];
|
||||
this.transactions = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,18 +47,18 @@ define(
|
||||
* #cancel} are called
|
||||
*/
|
||||
TransactionService.prototype.startTransaction = function () {
|
||||
if (this.transaction) {
|
||||
//Log error because this is a programming error if it occurs.
|
||||
this.$log.error("Transaction already in progress");
|
||||
}
|
||||
this.transaction = true;
|
||||
var transaction = this.isActive() ?
|
||||
new NestedTransaction(this.transactions[0]) :
|
||||
new Transaction(this.$log);
|
||||
|
||||
this.transactions.push(transaction);
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {boolean} If true, indicates that a transaction is in progress
|
||||
*/
|
||||
TransactionService.prototype.isActive = function () {
|
||||
return this.transaction;
|
||||
return this.transactions.length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -72,24 +69,20 @@ define(
|
||||
* @param onCancel A function to call on cancel
|
||||
*/
|
||||
TransactionService.prototype.addToTransaction = function (onCommit, onCancel) {
|
||||
if (this.transaction) {
|
||||
this.onCommits.push(onCommit);
|
||||
if (onCancel) {
|
||||
this.onCancels.push(onCancel);
|
||||
}
|
||||
if (this.isActive()) {
|
||||
return this.activeTransaction().add(onCommit, onCancel);
|
||||
} else {
|
||||
//Log error because this is a programming error if it occurs.
|
||||
this.$log.error("No transaction in progress");
|
||||
}
|
||||
};
|
||||
|
||||
return function () {
|
||||
this.onCommits = this.onCommits.filter(function (callback) {
|
||||
return callback !== onCommit;
|
||||
});
|
||||
this.onCancels = this.onCancels.filter(function (callback) {
|
||||
return callback !== onCancel;
|
||||
});
|
||||
}.bind(this);
|
||||
/**
|
||||
* Get the transaction at the top of the stack.
|
||||
* @private
|
||||
*/
|
||||
TransactionService.prototype.activeTransaction = function () {
|
||||
return this.transactions[this.transactions.length - 1];
|
||||
};
|
||||
|
||||
/**
|
||||
@ -100,24 +93,8 @@ define(
|
||||
* completed. Will reject if any commit operations fail
|
||||
*/
|
||||
TransactionService.prototype.commit = function () {
|
||||
var self = this,
|
||||
promises = [],
|
||||
onCommit;
|
||||
|
||||
while (this.onCommits.length > 0) { // ...using a while in case some onCommit adds to transaction
|
||||
onCommit = this.onCommits.pop();
|
||||
try { // ...also don't want to fail mid-loop...
|
||||
promises.push(onCommit());
|
||||
} catch (e) {
|
||||
this.$log.error("Error committing transaction.");
|
||||
}
|
||||
}
|
||||
return this.$q.all(promises).then(function () {
|
||||
self.transaction = false;
|
||||
|
||||
self.onCommits = [];
|
||||
self.onCancels = [];
|
||||
});
|
||||
var transaction = this.transactions.pop();
|
||||
return transaction ? transaction.commit() : Promise.reject();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -129,28 +106,17 @@ define(
|
||||
* @returns {*}
|
||||
*/
|
||||
TransactionService.prototype.cancel = function () {
|
||||
var self = this,
|
||||
results = [],
|
||||
onCancel;
|
||||
|
||||
while (this.onCancels.length > 0) {
|
||||
onCancel = this.onCancels.pop();
|
||||
try {
|
||||
results.push(onCancel());
|
||||
} catch (error) {
|
||||
this.$log.error("Error cancelling transaction.");
|
||||
}
|
||||
}
|
||||
return this.$q.all(results).then(function () {
|
||||
self.transaction = false;
|
||||
|
||||
self.onCommits = [];
|
||||
self.onCancels = [];
|
||||
});
|
||||
var transaction = this.transactions.pop();
|
||||
return transaction ? transaction.cancel() : Promise.reject();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the size (the number of commit/cancel callbacks) of
|
||||
* the active transaction.
|
||||
* @returns {number} size of the active transaction
|
||||
*/
|
||||
TransactionService.prototype.size = function () {
|
||||
return this.onCommits.length;
|
||||
return this.isActive() ? this.activeTransaction().size() : 0;
|
||||
};
|
||||
|
||||
return TransactionService;
|
||||
|
@ -30,7 +30,6 @@ define(
|
||||
mockParent,
|
||||
mockContext,
|
||||
mockComposition,
|
||||
mockPersistence,
|
||||
mockActionCapability,
|
||||
mockEditAction,
|
||||
mockType,
|
||||
@ -68,7 +67,6 @@ define(
|
||||
};
|
||||
mockContext = jasmine.createSpyObj("context", ["getParent"]);
|
||||
mockComposition = jasmine.createSpyObj("composition", ["invoke", "add"]);
|
||||
mockPersistence = jasmine.createSpyObj("persistence", ["persist"]);
|
||||
mockType = jasmine.createSpyObj("type", ["hasFeature", "getKey"]);
|
||||
mockActionCapability = jasmine.createSpyObj("actionCapability", ["getActions"]);
|
||||
mockEditAction = jasmine.createSpyObj("editAction", ["perform"]);
|
||||
@ -84,7 +82,6 @@ define(
|
||||
|
||||
capabilities = {
|
||||
composition: mockComposition,
|
||||
persistence: mockPersistence,
|
||||
action: mockActionCapability,
|
||||
type: mockType
|
||||
};
|
||||
@ -107,11 +104,6 @@ define(
|
||||
.toHaveBeenCalledWith(mockDomainObject);
|
||||
});
|
||||
|
||||
it("persists changes afterward", function () {
|
||||
action.perform();
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("enables edit mode for objects that have an edit action", function () {
|
||||
mockActionCapability.getActions.andReturn([mockEditAction]);
|
||||
action.perform();
|
||||
|
@ -43,7 +43,6 @@ define(
|
||||
},
|
||||
hasFeature: jasmine.createSpy('hasFeature')
|
||||
},
|
||||
persistence: jasmine.createSpyObj("persistence", ["persist"]),
|
||||
mutation: jasmine.createSpy("mutation")
|
||||
};
|
||||
model = {};
|
||||
@ -78,25 +77,18 @@ define(
|
||||
action = new PropertiesAction(dialogService, context);
|
||||
});
|
||||
|
||||
it("persists when an action is performed", function () {
|
||||
action.perform();
|
||||
expect(capabilities.persistence.persist)
|
||||
.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not persist any changes upon cancel", function () {
|
||||
input = undefined;
|
||||
action.perform();
|
||||
expect(capabilities.persistence.persist)
|
||||
.not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("mutates an object when performed", function () {
|
||||
action.perform();
|
||||
expect(capabilities.mutation).toHaveBeenCalled();
|
||||
capabilities.mutation.mostRecentCall.args[0]({});
|
||||
});
|
||||
|
||||
it("does not muate object upon cancel", function () {
|
||||
input = undefined;
|
||||
action.perform();
|
||||
expect(capabilities.mutation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("is only applicable when a domain object is in context", function () {
|
||||
expect(PropertiesAction.appliesTo(context)).toBeTruthy();
|
||||
expect(PropertiesAction.appliesTo({})).toBeFalsy();
|
||||
|
@ -37,7 +37,6 @@ define(
|
||||
mockGrandchildContext,
|
||||
mockRootContext,
|
||||
mockMutation,
|
||||
mockPersistence,
|
||||
mockType,
|
||||
actionContext,
|
||||
model,
|
||||
@ -53,8 +52,6 @@ define(
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
["getId", "getCapability"]
|
||||
@ -88,7 +85,6 @@ define(
|
||||
mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]);
|
||||
mockRootContext = jasmine.createSpyObj("context", ["getParent"]);
|
||||
mockMutation = jasmine.createSpyObj("mutation", ["invoke"]);
|
||||
mockPersistence = jasmine.createSpyObj("persistence", ["persist"]);
|
||||
mockType = jasmine.createSpyObj("type", ["hasFeature"]);
|
||||
mockNavigationService = jasmine.createSpyObj(
|
||||
"navigationService",
|
||||
@ -109,7 +105,6 @@ define(
|
||||
|
||||
capabilities = {
|
||||
mutation: mockMutation,
|
||||
persistence: mockPersistence,
|
||||
type: mockType
|
||||
};
|
||||
model = {
|
||||
@ -118,7 +113,7 @@ define(
|
||||
|
||||
actionContext = { domainObject: mockDomainObject };
|
||||
|
||||
action = new RemoveAction(mockQ, mockNavigationService, actionContext);
|
||||
action = new RemoveAction(mockNavigationService, actionContext);
|
||||
});
|
||||
|
||||
it("only applies to objects with parents", function () {
|
||||
@ -154,9 +149,6 @@ define(
|
||||
// Should have removed "test" - that was our
|
||||
// mock domain object's id.
|
||||
expect(result.composition).toEqual(["a", "b"]);
|
||||
|
||||
// Finally, should have persisted
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes parent of object currently navigated to", function () {
|
||||
|
@ -30,7 +30,6 @@ define(
|
||||
mockScope,
|
||||
testRepresentation,
|
||||
mockDomainObject,
|
||||
mockPersistence,
|
||||
mockStatusCapability,
|
||||
mockEditorCapability,
|
||||
mockCapabilities,
|
||||
@ -56,15 +55,12 @@ define(
|
||||
"useCapability",
|
||||
"hasCapability"
|
||||
]);
|
||||
mockPersistence =
|
||||
jasmine.createSpyObj("persistence", ["persist"]);
|
||||
mockStatusCapability =
|
||||
jasmine.createSpyObj("statusCapability", ["listen"]);
|
||||
mockEditorCapability =
|
||||
jasmine.createSpyObj("editorCapability", ["isEditContextRoot"]);
|
||||
|
||||
mockCapabilities = {
|
||||
'persistence': mockPersistence,
|
||||
'status': mockStatusCapability,
|
||||
'editor': mockEditorCapability
|
||||
};
|
||||
@ -96,7 +92,7 @@ define(
|
||||
expect(representer.listenHandle).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("mutates and persists upon observed changes", function () {
|
||||
it("mutates upon observed changes", function () {
|
||||
mockScope.model = { someKey: "some value" };
|
||||
mockScope.configuration = { someConfiguration: "something" };
|
||||
|
||||
@ -108,9 +104,6 @@ define(
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
// ... and should have persisted the mutation
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
|
||||
// Finally, check that the provided mutation function
|
||||
// includes both model and configuration
|
||||
expect(
|
||||
|
@ -0,0 +1,78 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,describe,it,expect,beforeEach,jasmine*/
|
||||
|
||||
define(["../../src/services/NestedTransaction"], function (NestedTransaction) {
|
||||
var TRANSACTION_METHODS = ['add', 'commit', 'cancel', 'size'];
|
||||
|
||||
describe("A NestedTransaction", function () {
|
||||
var mockTransaction,
|
||||
nestedTransaction;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTransaction =
|
||||
jasmine.createSpyObj('transaction', TRANSACTION_METHODS);
|
||||
nestedTransaction = new NestedTransaction(mockTransaction);
|
||||
});
|
||||
|
||||
it("exposes a Transaction's interface", function () {
|
||||
TRANSACTION_METHODS.forEach(function (method) {
|
||||
expect(nestedTransaction[method])
|
||||
.toEqual(jasmine.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe("when callbacks are added", function () {
|
||||
var mockCommit,
|
||||
mockCancel,
|
||||
remove;
|
||||
|
||||
beforeEach(function () {
|
||||
mockCommit = jasmine.createSpy('commit');
|
||||
mockCancel = jasmine.createSpy('cancel');
|
||||
remove = nestedTransaction.add(mockCommit, mockCancel);
|
||||
});
|
||||
|
||||
it("does not interact with its parent transaction", function () {
|
||||
TRANSACTION_METHODS.forEach(function (method) {
|
||||
expect(mockTransaction[method])
|
||||
.not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the transaction is committed", function () {
|
||||
beforeEach(function () {
|
||||
nestedTransaction.commit();
|
||||
});
|
||||
|
||||
it("adds to its parent transaction", function () {
|
||||
expect(mockTransaction.add).toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -57,8 +57,7 @@ define(
|
||||
|
||||
transactionService.startTransaction();
|
||||
transactionService.addToTransaction(onCommit, onCancel);
|
||||
expect(transactionService.onCommits.length).toBe(1);
|
||||
expect(transactionService.onCancels.length).toBe(1);
|
||||
expect(transactionService.size()).toBe(1);
|
||||
});
|
||||
|
||||
it("size function returns size of commit and cancel queues", function () {
|
||||
@ -85,7 +84,7 @@ define(
|
||||
});
|
||||
|
||||
it("commit calls all queued commit functions", function () {
|
||||
expect(transactionService.onCommits.length).toBe(3);
|
||||
expect(transactionService.size()).toBe(3);
|
||||
transactionService.commit();
|
||||
onCommits.forEach(function (spy) {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
@ -95,8 +94,8 @@ define(
|
||||
it("commit resets active state and clears queues", function () {
|
||||
transactionService.commit();
|
||||
expect(transactionService.isActive()).toBe(false);
|
||||
expect(transactionService.onCommits.length).toBe(0);
|
||||
expect(transactionService.onCancels.length).toBe(0);
|
||||
expect(transactionService.size()).toBe(0);
|
||||
expect(transactionService.size()).toBe(0);
|
||||
});
|
||||
|
||||
});
|
||||
@ -116,7 +115,7 @@ define(
|
||||
});
|
||||
|
||||
it("cancel calls all queued cancel functions", function () {
|
||||
expect(transactionService.onCancels.length).toBe(3);
|
||||
expect(transactionService.size()).toBe(3);
|
||||
transactionService.cancel();
|
||||
onCancels.forEach(function (spy) {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
@ -126,8 +125,7 @@ define(
|
||||
it("cancel resets active state and clears queues", function () {
|
||||
transactionService.cancel();
|
||||
expect(transactionService.isActive()).toBe(false);
|
||||
expect(transactionService.onCommits.length).toBe(0);
|
||||
expect(transactionService.onCancels.length).toBe(0);
|
||||
expect(transactionService.size()).toBe(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
110
platform/commonUI/edit/test/services/TransactionSpec.js
Normal file
110
platform/commonUI/edit/test/services/TransactionSpec.js
Normal file
@ -0,0 +1,110 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,describe,it,expect,beforeEach,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/services/Transaction"],
|
||||
function (Transaction) {
|
||||
|
||||
describe("A Transaction", function () {
|
||||
var mockLog,
|
||||
transaction;
|
||||
|
||||
beforeEach(function () {
|
||||
mockLog = jasmine.createSpyObj(
|
||||
'$log',
|
||||
['warn', 'info', 'error', 'debug']
|
||||
);
|
||||
transaction = new Transaction(mockLog);
|
||||
});
|
||||
|
||||
it("initially has a size of zero", function () {
|
||||
expect(transaction.size()).toEqual(0);
|
||||
});
|
||||
|
||||
describe("when callbacks are added", function () {
|
||||
var mockCommit,
|
||||
mockCancel,
|
||||
remove;
|
||||
|
||||
beforeEach(function () {
|
||||
mockCommit = jasmine.createSpy('commit');
|
||||
mockCancel = jasmine.createSpy('cancel');
|
||||
remove = transaction.add(mockCommit, mockCancel);
|
||||
});
|
||||
|
||||
it("reports a new size", function () {
|
||||
expect(transaction.size()).toEqual(1);
|
||||
});
|
||||
|
||||
it("returns a function to remove those callbacks", function () {
|
||||
expect(remove).toEqual(jasmine.any(Function));
|
||||
remove();
|
||||
expect(transaction.size()).toEqual(0);
|
||||
});
|
||||
|
||||
describe("and the transaction is committed", function () {
|
||||
beforeEach(function () {
|
||||
transaction.commit();
|
||||
});
|
||||
|
||||
it("triggers the commit callback", function () {
|
||||
expect(mockCommit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not trigger the cancel callback", function () {
|
||||
expect(mockCancel).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the transaction is cancelled", function () {
|
||||
beforeEach(function () {
|
||||
transaction.cancel();
|
||||
});
|
||||
|
||||
it("triggers the cancel callback", function () {
|
||||
expect(mockCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not trigger the commit callback", function () {
|
||||
expect(mockCommit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and an exception is encountered during commit", function () {
|
||||
beforeEach(function () {
|
||||
mockCommit.andCallFake(function () {
|
||||
throw new Error("test error");
|
||||
});
|
||||
transaction.commit();
|
||||
});
|
||||
|
||||
it("logs an error", function () {
|
||||
expect(mockLog.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -46,6 +46,7 @@ define([
|
||||
"./src/capabilities/MutationCapability",
|
||||
"./src/capabilities/DelegationCapability",
|
||||
"./src/capabilities/InstantiationCapability",
|
||||
"./src/runs/TransactingMutationListener",
|
||||
"./src/services/Now",
|
||||
"./src/services/Throttle",
|
||||
"./src/services/Topic",
|
||||
@ -78,6 +79,7 @@ define([
|
||||
MutationCapability,
|
||||
DelegationCapability,
|
||||
InstantiationCapability,
|
||||
TransactingMutationListener,
|
||||
Now,
|
||||
Throttle,
|
||||
Topic,
|
||||
@ -417,6 +419,12 @@ define([
|
||||
}
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
{
|
||||
"implementation": TransactingMutationListener,
|
||||
"depends": ["topic", "transactionService"]
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "PERSISTENCE_SPACE",
|
||||
|
@ -49,8 +49,7 @@ define(
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a domain object to the composition of the field.
|
||||
* This mutates but does not persist the modified object.
|
||||
* Add a domain object to the composition of this domain object.
|
||||
*
|
||||
* If no index is given, this is added to the end of the composition.
|
||||
*
|
||||
|
@ -113,11 +113,16 @@ define(
|
||||
domainObject = this.domainObject,
|
||||
model = domainObject.getModel(),
|
||||
modified = model.modified,
|
||||
persisted = model.persisted,
|
||||
persistenceService = this.persistenceService,
|
||||
persistenceFn = model.persisted !== undefined ?
|
||||
persistenceFn = persisted !== undefined ?
|
||||
this.persistenceService.updateObject :
|
||||
this.persistenceService.createObject;
|
||||
|
||||
if (persisted !== undefined && persisted === modified) {
|
||||
return this.$q.when(true);
|
||||
}
|
||||
|
||||
// Update persistence timestamp...
|
||||
domainObject.useCapability("mutation", function (m) {
|
||||
m.persisted = modified;
|
||||
@ -178,6 +183,15 @@ define(
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if this domain object has been persisted at some
|
||||
* point.
|
||||
* @returns {boolean} true if the object has been persisted
|
||||
*/
|
||||
PersistenceCapability.prototype.persisted = function () {
|
||||
return this.domainObject.getModel().persisted !== undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the key for this domain object in the given space.
|
||||
*
|
||||
|
54
platform/core/src/runs/TransactingMutationListener.js
Normal file
54
platform/core/src/runs/TransactingMutationListener.js
Normal file
@ -0,0 +1,54 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([], function () {
|
||||
/**
|
||||
* Listens for mutation on domain objects and triggers persistence when
|
||||
* it occurs.
|
||||
* @param {Topic} topic the `topic` service; used to listen for mutation
|
||||
* @memberof platform/core
|
||||
*/
|
||||
function TransactingMutationListener(topic, transactionService) {
|
||||
var mutationTopic = topic('mutation');
|
||||
mutationTopic.listen(function (domainObject) {
|
||||
var persistence = domainObject.getCapability('persistence');
|
||||
var wasActive = transactionService.isActive();
|
||||
if (persistence.persisted()) {
|
||||
if (!wasActive) {
|
||||
transactionService.startTransaction();
|
||||
}
|
||||
|
||||
transactionService.addToTransaction(
|
||||
persistence.persist.bind(persistence),
|
||||
persistence.refresh.bind(persistence)
|
||||
);
|
||||
|
||||
if (!wasActive) {
|
||||
transactionService.commit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return TransactingMutationListener;
|
||||
});
|
120
platform/core/test/runs/TransactingMutationListenerSpec.js
Normal file
120
platform/core/test/runs/TransactingMutationListenerSpec.js
Normal file
@ -0,0 +1,120 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
["../../src/runs/TransactingMutationListener"],
|
||||
function (TransactingMutationListener) {
|
||||
|
||||
describe("TransactingMutationListener", function () {
|
||||
var mockTopic,
|
||||
mockMutationTopic,
|
||||
mockTransactionService,
|
||||
mockDomainObject,
|
||||
mockPersistence;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTopic = jasmine.createSpy('topic');
|
||||
mockMutationTopic =
|
||||
jasmine.createSpyObj('mutation', ['listen']);
|
||||
mockTransactionService =
|
||||
jasmine.createSpyObj('transactionService', [
|
||||
'isActive',
|
||||
'startTransaction',
|
||||
'addToTransaction',
|
||||
'commit'
|
||||
]);
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
['getId', 'getCapability', 'getModel']
|
||||
);
|
||||
mockPersistence = jasmine.createSpyObj(
|
||||
'persistence',
|
||||
['persist', 'refresh', 'persisted']
|
||||
);
|
||||
|
||||
mockTopic.andCallFake(function (t) {
|
||||
return (t === 'mutation') && mockMutationTopic;
|
||||
});
|
||||
|
||||
mockDomainObject.getCapability.andCallFake(function (c) {
|
||||
return (c === 'persistence') && mockPersistence;
|
||||
});
|
||||
|
||||
mockPersistence.persisted.andReturn(true);
|
||||
|
||||
return new TransactingMutationListener(
|
||||
mockTopic,
|
||||
mockTransactionService
|
||||
);
|
||||
});
|
||||
|
||||
it("listens for mutation", function () {
|
||||
expect(mockMutationTopic.listen)
|
||||
.toHaveBeenCalledWith(jasmine.any(Function));
|
||||
});
|
||||
|
||||
[false, true].forEach(function (isActive) {
|
||||
var verb = isActive ? "is" : "isn't";
|
||||
|
||||
function onlyWhenInactive(expectation) {
|
||||
return isActive ? expectation.not : expectation;
|
||||
}
|
||||
|
||||
describe("when a transaction " + verb + " active", function () {
|
||||
var innerVerb = isActive ? "does" : "doesn't";
|
||||
|
||||
beforeEach(function () {
|
||||
mockTransactionService.isActive.andReturn(isActive);
|
||||
});
|
||||
|
||||
describe("and mutation occurs", function () {
|
||||
beforeEach(function () {
|
||||
mockMutationTopic.listen.mostRecentCall
|
||||
.args[0](mockDomainObject);
|
||||
});
|
||||
|
||||
|
||||
it(innerVerb + " start a new transaction", function () {
|
||||
onlyWhenInactive(
|
||||
expect(mockTransactionService.startTransaction)
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("adds to the active transaction", function () {
|
||||
expect(mockTransactionService.addToTransaction)
|
||||
.toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it(innerVerb + " immediately commit", function () {
|
||||
onlyWhenInactive(
|
||||
expect(mockTransactionService.commit)
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -76,17 +76,12 @@ define(
|
||||
* completes.
|
||||
*/
|
||||
LocationCapability.prototype.setPrimaryLocation = function (location) {
|
||||
var capability = this;
|
||||
return this.domainObject.useCapability(
|
||||
'mutation',
|
||||
function (model) {
|
||||
model.location = location;
|
||||
}
|
||||
).then(function () {
|
||||
return capability.domainObject
|
||||
.getCapability('persistence')
|
||||
.persist();
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -63,14 +63,7 @@ define(
|
||||
);
|
||||
}
|
||||
|
||||
return parentObject.getCapability('composition').add(object)
|
||||
.then(function (objectInNewContext) {
|
||||
return parentObject.getCapability('persistence')
|
||||
.persist()
|
||||
.then(function () {
|
||||
return objectInNewContext;
|
||||
});
|
||||
});
|
||||
return parentObject.getCapability('composition').add(object);
|
||||
};
|
||||
|
||||
return LinkService;
|
||||
|
@ -33,7 +33,6 @@ define(
|
||||
|
||||
describe("instantiated with domain object", function () {
|
||||
var locationCapability,
|
||||
persistencePromise,
|
||||
mutationPromise,
|
||||
mockQ,
|
||||
mockInjector,
|
||||
@ -49,10 +48,6 @@ define(
|
||||
return domainObjectFactory({id: 'root'});
|
||||
}
|
||||
},
|
||||
persistence: jasmine.createSpyObj(
|
||||
'persistenceCapability',
|
||||
['persist']
|
||||
),
|
||||
mutation: jasmine.createSpyObj(
|
||||
'mutationCapability',
|
||||
['invoke']
|
||||
@ -65,11 +60,6 @@ define(
|
||||
mockObjectService =
|
||||
jasmine.createSpyObj("objectService", ["getObjects"]);
|
||||
|
||||
persistencePromise = new ControlledPromise();
|
||||
domainObject.capabilities.persistence.persist.andReturn(
|
||||
persistencePromise
|
||||
);
|
||||
|
||||
mutationPromise = new ControlledPromise();
|
||||
domainObject.capabilities.mutation.invoke.andCallFake(
|
||||
function (mutator) {
|
||||
@ -103,22 +93,17 @@ define(
|
||||
expect(locationCapability.isOriginal()).toBe(false);
|
||||
});
|
||||
|
||||
it("can persist location", function () {
|
||||
var persistResult = locationCapability
|
||||
it("can mutate location", function () {
|
||||
var result = locationCapability
|
||||
.setPrimaryLocation('root'),
|
||||
whenComplete = jasmine.createSpy('whenComplete');
|
||||
|
||||
persistResult.then(whenComplete);
|
||||
result.then(whenComplete);
|
||||
|
||||
expect(domainObject.model.location).not.toBeDefined();
|
||||
mutationPromise.resolve();
|
||||
expect(domainObject.model.location).toBe('root');
|
||||
|
||||
expect(whenComplete).not.toHaveBeenCalled();
|
||||
expect(domainObject.capabilities.persistence.persist)
|
||||
.toHaveBeenCalled();
|
||||
|
||||
persistencePromise.resolve();
|
||||
expect(whenComplete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -139,20 +139,12 @@ define(
|
||||
parentModel,
|
||||
parentObject,
|
||||
compositionPromise,
|
||||
persistencePromise,
|
||||
addPromise,
|
||||
compositionCapability,
|
||||
persistenceCapability;
|
||||
compositionCapability;
|
||||
|
||||
beforeEach(function () {
|
||||
compositionPromise = new ControlledPromise();
|
||||
persistencePromise = new ControlledPromise();
|
||||
addPromise = new ControlledPromise();
|
||||
persistenceCapability = jasmine.createSpyObj(
|
||||
'persistenceCapability',
|
||||
['persist']
|
||||
);
|
||||
persistenceCapability.persist.andReturn(persistencePromise);
|
||||
compositionCapability = jasmine.createSpyObj(
|
||||
'compositionCapability',
|
||||
['invoke', 'add']
|
||||
@ -172,7 +164,6 @@ define(
|
||||
return new ControlledPromise();
|
||||
}
|
||||
},
|
||||
persistence: persistenceCapability,
|
||||
composition: compositionCapability
|
||||
}
|
||||
});
|
||||
@ -197,15 +188,6 @@ define(
|
||||
.toHaveBeenCalledWith(object);
|
||||
});
|
||||
|
||||
it("persists parent", function () {
|
||||
linkService.perform(object, parentObject);
|
||||
expect(addPromise.then).toHaveBeenCalled();
|
||||
addPromise.resolve(linkedObject);
|
||||
expect(parentObject.getCapability)
|
||||
.toHaveBeenCalledWith('persistence');
|
||||
expect(persistenceCapability.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns object representing new link", function () {
|
||||
var returnPromise, whenComplete;
|
||||
returnPromise = linkService.perform(object, parentObject);
|
||||
@ -213,7 +195,6 @@ define(
|
||||
returnPromise.then(whenComplete);
|
||||
|
||||
addPromise.resolve(linkedObject);
|
||||
persistencePromise.resolve();
|
||||
compositionPromise.resolve([linkedObject]);
|
||||
expect(whenComplete).toHaveBeenCalledWith(linkedObject);
|
||||
});
|
||||
|
@ -49,17 +49,11 @@ define(
|
||||
var domainObject = this.domainObject,
|
||||
now = this.now;
|
||||
|
||||
function doPersist() {
|
||||
var persistence = domainObject.getCapability('persistence');
|
||||
return persistence && persistence.persist();
|
||||
}
|
||||
|
||||
function setTimestamp(model) {
|
||||
model.timestamp = now();
|
||||
}
|
||||
|
||||
return domainObject.useCapability('mutation', setTimestamp)
|
||||
.then(doPersist);
|
||||
return domainObject.useCapability('mutation', setTimestamp);
|
||||
};
|
||||
|
||||
return AbstractStartTimerAction;
|
||||
|
@ -27,7 +27,6 @@ define(
|
||||
describe("A timer's start/restart action", function () {
|
||||
var mockNow,
|
||||
mockDomainObject,
|
||||
mockPersistence,
|
||||
testModel,
|
||||
action;
|
||||
|
||||
@ -45,14 +44,7 @@ define(
|
||||
'domainObject',
|
||||
['getCapability', 'useCapability']
|
||||
);
|
||||
mockPersistence = jasmine.createSpyObj(
|
||||
'persistence',
|
||||
['persist']
|
||||
);
|
||||
|
||||
mockDomainObject.getCapability.andCallFake(function (c) {
|
||||
return (c === 'persistence') && mockPersistence;
|
||||
});
|
||||
mockDomainObject.useCapability.andCallFake(function (c, v) {
|
||||
if (c === 'mutation') {
|
||||
testModel = v(testModel) || testModel;
|
||||
@ -67,18 +59,16 @@ define(
|
||||
});
|
||||
});
|
||||
|
||||
it("updates the model with a timestamp and persists", function () {
|
||||
it("updates the model with a timestamp", function () {
|
||||
mockNow.andReturn(12000);
|
||||
action.perform();
|
||||
expect(testModel.timestamp).toEqual(12000);
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not truncate milliseconds", function () {
|
||||
mockNow.andReturn(42321);
|
||||
action.perform();
|
||||
expect(testModel.timestamp).toEqual(42321);
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ define(
|
||||
describe("A timer's restart action", function () {
|
||||
var mockNow,
|
||||
mockDomainObject,
|
||||
mockPersistence,
|
||||
testModel,
|
||||
testContext,
|
||||
action;
|
||||
@ -46,14 +45,7 @@ define(
|
||||
'domainObject',
|
||||
['getCapability', 'useCapability', 'getModel']
|
||||
);
|
||||
mockPersistence = jasmine.createSpyObj(
|
||||
'persistence',
|
||||
['persist']
|
||||
);
|
||||
|
||||
mockDomainObject.getCapability.andCallFake(function (c) {
|
||||
return (c === 'persistence') && mockPersistence;
|
||||
});
|
||||
mockDomainObject.useCapability.andCallFake(function (c, v) {
|
||||
if (c === 'mutation') {
|
||||
testModel = v(testModel) || testModel;
|
||||
@ -70,11 +62,10 @@ define(
|
||||
action = new RestartTimerAction(mockNow, testContext);
|
||||
});
|
||||
|
||||
it("updates the model with a timestamp and persists", function () {
|
||||
it("updates the model with a timestamp", function () {
|
||||
mockNow.andReturn(12000);
|
||||
action.perform();
|
||||
expect(testModel.timestamp).toEqual(12000);
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("applies only to timers with a target time", function () {
|
||||
|
@ -27,7 +27,6 @@ define(
|
||||
describe("A timer's start action", function () {
|
||||
var mockNow,
|
||||
mockDomainObject,
|
||||
mockPersistence,
|
||||
testModel,
|
||||
testContext,
|
||||
action;
|
||||
@ -46,14 +45,7 @@ define(
|
||||
'domainObject',
|
||||
['getCapability', 'useCapability', 'getModel']
|
||||
);
|
||||
mockPersistence = jasmine.createSpyObj(
|
||||
'persistence',
|
||||
['persist']
|
||||
);
|
||||
|
||||
mockDomainObject.getCapability.andCallFake(function (c) {
|
||||
return (c === 'persistence') && mockPersistence;
|
||||
});
|
||||
mockDomainObject.useCapability.andCallFake(function (c, v) {
|
||||
if (c === 'mutation') {
|
||||
testModel = v(testModel) || testModel;
|
||||
@ -70,11 +62,10 @@ define(
|
||||
action = new StartTimerAction(mockNow, testContext);
|
||||
});
|
||||
|
||||
it("updates the model with a timestamp and persists", function () {
|
||||
it("updates the model with a timestamp", function () {
|
||||
mockNow.andReturn(12000);
|
||||
action.perform();
|
||||
expect(testModel.timestamp).toEqual(12000);
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("applies only to timers without a target time", function () {
|
||||
|
@ -35,7 +35,6 @@ define(
|
||||
*/
|
||||
function TimelineDragHandler(domainObject, objectLoader) {
|
||||
var timespans = {},
|
||||
persists = {},
|
||||
mutations = {},
|
||||
compositions = {},
|
||||
dirty = {};
|
||||
@ -56,8 +55,6 @@ define(
|
||||
timespans[id] = timespan;
|
||||
// And its mutation capability
|
||||
mutations[id] = object.getCapability('mutation');
|
||||
// Also cache the persistence capability for later
|
||||
persists[id] = object.getCapability('persistence');
|
||||
// And the composition, for bulk moves
|
||||
compositions[id] = object.getModel().composition || [];
|
||||
});
|
||||
@ -71,19 +68,14 @@ define(
|
||||
}
|
||||
|
||||
// Persist changes for objects by id (when dragging ends)
|
||||
function doPersist(id) {
|
||||
var persistence = persists[id],
|
||||
mutation = mutations[id];
|
||||
function finalMutate(id) {
|
||||
var mutation = mutations[id];
|
||||
if (mutation) {
|
||||
// Mutate just to update the timestamp (since we
|
||||
// explicitly don't do this during the drag to
|
||||
// avoid firing a ton of refreshes.)
|
||||
mutation.mutate(function () {});
|
||||
}
|
||||
if (persistence) {
|
||||
// Persist the changes
|
||||
persistence.persist();
|
||||
}
|
||||
}
|
||||
|
||||
// Use the object loader to get objects which have timespans
|
||||
@ -105,7 +97,7 @@ define(
|
||||
*/
|
||||
persist: function () {
|
||||
// Persist every dirty object...
|
||||
Object.keys(dirty).forEach(doPersist);
|
||||
Object.keys(dirty).forEach(finalMutate);
|
||||
// Clear out the dirty list
|
||||
dirty = {};
|
||||
},
|
||||
|
@ -35,7 +35,6 @@ define(
|
||||
var domainObject = swimlane && swimlane.domainObject,
|
||||
model = (domainObject && domainObject.getModel()) || {},
|
||||
mutator = domainObject && domainObject.getCapability('mutation'),
|
||||
persister = domainObject && domainObject.getCapability('persistence'),
|
||||
type = domainObject && domainObject.getCapability('type'),
|
||||
dropHandler = new TimelineSwimlaneDropHandler(swimlane);
|
||||
|
||||
@ -48,7 +47,7 @@ define(
|
||||
mutator.mutate(function (m) {
|
||||
m.relationships = m.relationships || {};
|
||||
m.relationships[ACTIVITY_RELATIONSHIP] = value;
|
||||
}).then(persister.persist);
|
||||
});
|
||||
}
|
||||
}
|
||||
// ...otherwise, use as a getter
|
||||
@ -63,7 +62,7 @@ define(
|
||||
// Update the link
|
||||
mutator.mutate(function (m) {
|
||||
m.link = value;
|
||||
}).then(persister.persist);
|
||||
});
|
||||
}
|
||||
return model.link;
|
||||
}
|
||||
@ -84,7 +83,7 @@ define(
|
||||
}
|
||||
|
||||
// Activities should have the Activity Modes and Activity Link dialog
|
||||
if (type && type.instanceOf("activity") && mutator && persister) {
|
||||
if (type && type.instanceOf("activity") && mutator) {
|
||||
swimlane.modes = modes;
|
||||
swimlane.link = link;
|
||||
}
|
||||
|
@ -29,16 +29,6 @@ define(
|
||||
* @constructor
|
||||
*/
|
||||
function TimelineSwimlaneDropHandler(swimlane) {
|
||||
// Utility function; like $q.when, but synchronous (to reduce
|
||||
// performance impact when wrapping synchronous values)
|
||||
function asPromise(value) {
|
||||
return (value && value.then) ? value : {
|
||||
then: function (callback) {
|
||||
return asPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check if we are in edit mode (also check parents)
|
||||
function inEditMode() {
|
||||
return swimlane.domainObject.hasCapability('editor') &&
|
||||
@ -75,16 +65,7 @@ define(
|
||||
|
||||
// Initiate mutation of a domain object
|
||||
function doMutate(domainObject, mutator) {
|
||||
return asPromise(
|
||||
domainObject.useCapability("mutation", mutator)
|
||||
).then(function () {
|
||||
// Persist the results of mutation
|
||||
var persistence = domainObject.getCapability("persistence");
|
||||
if (persistence) {
|
||||
// Persist the changes
|
||||
persistence.persist();
|
||||
}
|
||||
});
|
||||
return domainObject.useCapability("mutation", mutator);
|
||||
}
|
||||
|
||||
// Check if this swimlane is in a state where a drop-after will
|
||||
|
@ -32,7 +32,6 @@ define(
|
||||
mockDomainObjects,
|
||||
mockTimespans,
|
||||
mockMutations,
|
||||
mockPersists,
|
||||
mockCallback,
|
||||
handler;
|
||||
|
||||
@ -66,7 +65,6 @@ define(
|
||||
mockDomainObj.useCapability.andReturn(asPromise(mockTimespans[id]));
|
||||
mockDomainObj.getCapability.andCallFake(function (c) {
|
||||
return {
|
||||
persistence: mockPersists[id],
|
||||
mutation: mockMutations[id]
|
||||
}[c];
|
||||
});
|
||||
@ -76,17 +74,12 @@ define(
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimespans = {};
|
||||
mockPersists = {};
|
||||
mockMutations = {};
|
||||
['a', 'b', 'c', 'd', 'e', 'f'].forEach(function (id, index) {
|
||||
mockTimespans[id] = jasmine.createSpyObj(
|
||||
'timespan-' + id,
|
||||
['getStart', 'getEnd', 'getDuration', 'setStart', 'setEnd', 'setDuration']
|
||||
);
|
||||
mockPersists[id] = jasmine.createSpyObj(
|
||||
'persistence-' + id,
|
||||
['persist']
|
||||
);
|
||||
mockMutations[id] = jasmine.createSpyObj(
|
||||
'mutation-' + id,
|
||||
['mutate']
|
||||
@ -209,20 +202,6 @@ define(
|
||||
expect(mockTimespans.c.setStart).toHaveBeenCalledWith(1000);
|
||||
});
|
||||
|
||||
it("persists mutated objects", function () {
|
||||
handler.start('a', 20);
|
||||
handler.end('b', 50);
|
||||
handler.duration('c', 30);
|
||||
handler.persist();
|
||||
expect(mockPersists.a.persist).toHaveBeenCalled();
|
||||
expect(mockPersists.b.persist).toHaveBeenCalled();
|
||||
expect(mockPersists.c.persist).toHaveBeenCalled();
|
||||
expect(mockPersists.d.persist).not.toHaveBeenCalled();
|
||||
expect(mockPersists.e.persist).not.toHaveBeenCalled();
|
||||
expect(mockPersists.f.persist).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -50,10 +50,6 @@ define(
|
||||
'mutation',
|
||||
['mutate']
|
||||
);
|
||||
mockCapabilities.persistence = jasmine.createSpyObj(
|
||||
'persistence',
|
||||
['persist']
|
||||
);
|
||||
mockCapabilities.type = jasmine.createSpyObj(
|
||||
'type',
|
||||
['instanceOf']
|
||||
@ -115,11 +111,6 @@ define(
|
||||
.toHaveBeenCalledWith(jasmine.any(Function));
|
||||
mockCapabilities.mutation.mutate.mostRecentCall.args[0](testModel);
|
||||
expect(testModel.relationships.modes).toEqual(['abc', 'xyz']);
|
||||
|
||||
// Verify that persistence is called when promise resolves
|
||||
expect(mockCapabilities.persistence.persist).not.toHaveBeenCalled();
|
||||
mockPromise.then.mostRecentCall.args[0]();
|
||||
expect(mockCapabilities.persistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("mutates modes when used as a setter", function () {
|
||||
@ -128,11 +119,6 @@ define(
|
||||
.toHaveBeenCalledWith(jasmine.any(Function));
|
||||
mockCapabilities.mutation.mutate.mostRecentCall.args[0](testModel);
|
||||
expect(testModel.link).toEqual("http://www.noaa.gov");
|
||||
|
||||
// Verify that persistence is called when promise resolves
|
||||
expect(mockCapabilities.persistence.persist).not.toHaveBeenCalled();
|
||||
mockPromise.then.mostRecentCall.args[0]();
|
||||
expect(mockCapabilities.persistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not mutate modes when unchanged", function () {
|
||||
|
@ -29,7 +29,6 @@ define(
|
||||
mockOtherObject,
|
||||
mockActionCapability,
|
||||
mockEditorCapability,
|
||||
mockPersistence,
|
||||
mockContext,
|
||||
mockAction,
|
||||
handler;
|
||||
@ -76,7 +75,6 @@ define(
|
||||
["getId", "getCapability", "useCapability", "hasCapability"]
|
||||
);
|
||||
mockActionCapability = jasmine.createSpyObj("action", ["perform", "getActions"]);
|
||||
mockPersistence = jasmine.createSpyObj("persistence", ["persist"]);
|
||||
mockContext = jasmine.createSpyObj('context', ['getParent']);
|
||||
|
||||
mockActionCapability.getActions.andReturn([mockAction]);
|
||||
@ -89,14 +87,12 @@ define(
|
||||
mockSwimlane.domainObject.getCapability.andCallFake(function (c) {
|
||||
return {
|
||||
action: mockActionCapability,
|
||||
persistence: mockPersistence,
|
||||
editor: mockEditorCapability
|
||||
}[c];
|
||||
});
|
||||
mockSwimlane.parent.domainObject.getCapability.andCallFake(function (c) {
|
||||
return {
|
||||
action: mockActionCapability,
|
||||
persistence: mockPersistence,
|
||||
editor: mockEditorCapability
|
||||
}[c];
|
||||
});
|
||||
@ -162,8 +158,6 @@ define(
|
||||
mockSwimlane.domainObject.useCapability.mostRecentCall
|
||||
.args[1](testModel);
|
||||
expect(testModel.composition).toEqual(['c', 'd']);
|
||||
// Finally, should also have persisted
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("inserts after as a peer when highlighted at the bottom", function () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user