Resolved merge conflicts

This commit is contained in:
Henry 2016-05-12 16:14:31 -07:00
parent 5bf750c90c
commit 44f4a82fa1
37 changed files with 741 additions and 332 deletions

View File

@ -87,10 +87,9 @@ define(
newObject; newObject;
newModel.type = this.type.getKey(); newModel.type = this.type.getKey();
newModel.location = parentObject.getId();
newObject = parentObject.useCapability('instantiation', newModel); newObject = parentObject.useCapability('instantiation', newModel);
newObject.useCapability('mutation', function(model){
model.location = parentObject.getId();
});
editorCapability = newObject.getCapability("editor"); editorCapability = newObject.getCapability("editor");
if (countEditableViews(newObject) > 0 && newObject.hasCapability('composition')) { if (countEditableViews(newObject) > 0 && newObject.hasCapability('composition')) {
@ -101,7 +100,7 @@ define(
return newObject.useCapability("action").perform("save").then(function () { return newObject.useCapability("action").perform("save").then(function () {
return editorCapability.save(); return editorCapability.save();
}, function () { }, function () {
return editorCapability.cancel() return editorCapability.cancel();
}); });
} }
}; };

View File

@ -41,9 +41,8 @@ define([
"./src/representers/EditRepresenter", "./src/representers/EditRepresenter",
"./src/representers/EditToolbarRepresenter", "./src/representers/EditToolbarRepresenter",
"./src/capabilities/EditorCapability", "./src/capabilities/EditorCapability",
"./src/capabilities/TransactionDecorator", "./src/capabilities/TransactionCapabilityDecorator",
"./src/services/TransactionService", "./src/services/TransactionService",
"./src/services/DirtyModelCache",
"text!./res/templates/library.html", "text!./res/templates/library.html",
"text!./res/templates/edit-object.html", "text!./res/templates/edit-object.html",
"text!./res/templates/edit-action-buttons.html", "text!./res/templates/edit-action-buttons.html",
@ -71,9 +70,8 @@ define([
EditRepresenter, EditRepresenter,
EditToolbarRepresenter, EditToolbarRepresenter,
EditorCapability, EditorCapability,
TransactionDecorator, TransactionCapabilityDecorator,
TransactionService, TransactionService,
DirtyModelCache,
libraryTemplate, libraryTemplate,
editObjectTemplate, editObjectTemplate,
editActionButtonsTemplate, editActionButtonsTemplate,
@ -136,8 +134,7 @@ define([
"depends": [ "depends": [
"$location", "$location",
"navigationService", "navigationService",
"$log", "$log"
"$q"
], ],
"description": "Edit this object.", "description": "Edit this object.",
"category": "view-control", "category": "view-control",
@ -270,11 +267,10 @@ define([
{ {
"type": "decorator", "type": "decorator",
"provides": "capabilityService", "provides": "capabilityService",
"implementation": TransactionDecorator, "implementation": TransactionCapabilityDecorator,
"depends": [ "depends": [
"$q", "$q",
"transactionService", "transactionService"
"dirtyModelCache"
] ]
}, },
{ {
@ -283,15 +279,7 @@ define([
"implementation": TransactionService, "implementation": TransactionService,
"depends": [ "depends": [
"$q", "$q",
"dirtyModelCache" "$log"
]
},
{
"type": "provider",
"provides": "dirtyModelCache",
"implementation": DirtyModelCache,
"depends": [
"topic"
] ]
} }
], ],
@ -324,8 +312,7 @@ define([
"description": "Provides transactional editing capabilities", "description": "Provides transactional editing capabilities",
"implementation": EditorCapability, "implementation": EditorCapability,
"depends": [ "depends": [
"transactionService", "transactionService"
"dirtyModelCache"
] ]
} }
], ],

View File

@ -46,10 +46,19 @@ define(
function returnToBrowse () { function returnToBrowse () {
var parent; var parent;
domainObject.getCapability("location").getOriginal().then(function (original) {
parent = original.getCapability("context").getParent(); //If the object existed already, navigate to refresh view
parent.getCapability("action").perform("navigate"); // with previous object state.
}); if (domainObject.getModel().persisted) {
domainObject.getCapability("action").perform("navigate");
} else {
//If the object was new, and user has cancelled, then
//navigate back to parent because nothing to show.
domainObject.getCapability("location").getOriginal().then(function (original) {
parent = original.getCapability("context").getParent();
parent.getCapability("action").perform("navigate");
});
}
} }
return this.domainObject.getCapability("editor").cancel() return this.domainObject.getCapability("editor").cancel()
.then(returnToBrowse); .then(returnToBrowse);
@ -64,7 +73,8 @@ define(
CancelAction.appliesTo = function (context) { CancelAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject; var domainObject = (context || {}).domainObject;
return domainObject !== undefined && return domainObject !== undefined &&
domainObject.getCapability("status").get("editing"); domainObject.hasCapability('editor') &&
domainObject.getCapability('editor').isEditContextRoot();
}; };
return CancelAction; return CancelAction;

View File

@ -44,7 +44,7 @@ define(
* @constructor * @constructor
* @implements {Action} * @implements {Action}
*/ */
function EditAction($location, navigationService, $log, $q, context) { function EditAction($location, navigationService, $log, context) {
var domainObject = (context || {}).domainObject; var domainObject = (context || {}).domainObject;
// We cannot enter Edit mode if we have no domain object to // We cannot enter Edit mode if we have no domain object to
@ -63,7 +63,6 @@ define(
this.domainObject = domainObject; this.domainObject = domainObject;
this.$location = $location; this.$location = $location;
this.navigationService = navigationService; this.navigationService = navigationService;
this.$q = $q;
} }
/** /**
@ -89,8 +88,11 @@ define(
var domainObject = (context || {}).domainObject, var domainObject = (context || {}).domainObject,
type = domainObject && domainObject.getCapability('type'); type = domainObject && domainObject.getCapability('type');
// Only allow creatable types to be edited // Only allow editing of types that support it and are not already
return type && type.hasFeature('creation') && !domainObject.getCapability('status').get('editing'); // being edited
return type && type.hasFeature('creation') &&
domainObject.hasCapability('editor') &&
!domainObject.getCapability('editor').isEditContextRoot();
}; };
return EditAction; return EditAction;

View File

@ -85,8 +85,9 @@ define(
SaveAction.appliesTo = function (context) { SaveAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject; var domainObject = (context || {}).domainObject;
return domainObject !== undefined && return domainObject !== undefined &&
domainObject.getModel().persisted !== undefined && domainObject.hasCapability('editor') &&
domainObject.getCapability("status").get("editing"); domainObject.getCapability('editor').isEditContextRoot() &&
domainObject.getModel().persisted !== undefined;
}; };
return SaveAction; return SaveAction;

View File

@ -135,8 +135,8 @@ define(
return copyService.perform(domainObject, parent, allowClone); return copyService.perform(domainObject, parent, allowClone);
} }
function cancelEditingAfterClone(clonedObject) { function commitEditingAfterClone(clonedObject) {
return domainObject.getCapability("editor").cancel() return domainObject.getCapability("editor").save()
.then(resolveWith(clonedObject)); .then(resolveWith(clonedObject));
} }
@ -144,7 +144,7 @@ define(
.then(doWizardSave) .then(doWizardSave)
.then(getParent) .then(getParent)
.then(cloneIntoParent) .then(cloneIntoParent)
.then(cancelEditingAfterClone) .then(commitEditingAfterClone)
.catch(resolveWith(false)); .catch(resolveWith(false));
}; };
@ -157,7 +157,8 @@ define(
SaveAsAction.appliesTo = function (context) { SaveAsAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject; var domainObject = (context || {}).domainObject;
return domainObject !== undefined && return domainObject !== undefined &&
domainObject.getCapability("status").get("editing") && domainObject.hasCapability('editor') &&
domainObject.getCapability('editor').isEditContextRoot() &&
domainObject.getModel().persisted === undefined; domainObject.getModel().persisted === undefined;
}; };

View File

@ -24,24 +24,42 @@ define(
[], [],
function () { function () {
/**
* A capability that implements an editing 'session' for a domain
* object. An editing session is initiated via a call to .edit().
* Once initiated, any persist operations will be queued pending a
* subsequent call to [.save()](@link #save) or [.cancel()](@link
* #cancel).
* @param transactionService
* @param domainObject
* @constructor
*/
function EditorCapability( function EditorCapability(
transactionService, transactionService,
dirtyModelCache,
domainObject domainObject
) { ) {
this.transactionService = transactionService; this.transactionService = transactionService;
this.dirtyModelCache = dirtyModelCache;
this.domainObject = domainObject; this.domainObject = domainObject;
} }
/**
* Initiate an editing session. This will start a transaction during
* which any persist operations will be deferred until either save()
* or cancel() are called.
*/
EditorCapability.prototype.edit = function () { EditorCapability.prototype.edit = function () {
this.transactionService.startTransaction(); this.transactionService.startTransaction();
this.domainObject.getCapability('status').set('editing', true); this.domainObject.getCapability('status').set('editing', true);
}; };
function isEditContextRoot (domainObject) {
return domainObject.getCapability('status').get('editing');
}
function isEditing (domainObject) { function isEditing (domainObject) {
return domainObject.getCapability('status').get('editing') || return isEditContextRoot(domainObject) ||
domainObject.hasCapability('context') && isEditing(domainObject.getCapability('context').getParent()); domainObject.hasCapability('context') &&
isEditing(domainObject.getCapability('context').getParent());
} }
/** /**
@ -53,6 +71,20 @@ define(
return isEditing(this.domainObject); return isEditing(this.domainObject);
}; };
/**
* Is this the root editing object (ie. the object that the user
* clicked 'edit' on)?
* @returns {*}
*/
EditorCapability.prototype.isEditContextRoot = function () {
return isEditContextRoot(this.domainObject);
};
/**
* Save any changes from this editing session. This will flush all
* pending persists and end the current transaction
* @returns {*}
*/
EditorCapability.prototype.save = function () { EditorCapability.prototype.save = function () {
var domainObject = this.domainObject; var domainObject = this.domainObject;
return this.transactionService.commit().then(function() { return this.transactionService.commit().then(function() {
@ -62,6 +94,11 @@ define(
EditorCapability.prototype.invoke = EditorCapability.prototype.edit; EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
/**
* Cancel the current editing session. This will discard any pending
* persist operations
* @returns {*}
*/
EditorCapability.prototype.cancel = function () { EditorCapability.prototype.cancel = function () {
var domainObject = this.domainObject; var domainObject = this.domainObject;
return this.transactionService.cancel().then(function(){ return this.transactionService.cancel().then(function(){
@ -70,15 +107,14 @@ define(
}); });
}; };
/**
* @returns {boolean} true if there have been any domain model
* modifications since the last persist, false otherwise.
*/
EditorCapability.prototype.dirty = function () { EditorCapability.prototype.dirty = function () {
return this.dirtyModelCache.isDirty(this.domainObject); return (this.domainObject.getModel().modified || 0) > (this.domainObject.getModel().persisted || 0);
}; };
EditorCapability.prototype.appliesTo = function(context) {
var domainObject = context.domainObject;
return domainObject && domainObject.getType().hasFeature("creation");
}
return EditorCapability; return EditorCapability;
} }
); );

View File

@ -26,23 +26,30 @@ define(
function (TransactionalPersistenceCapability) { function (TransactionalPersistenceCapability) {
'use strict'; 'use strict';
function TransactionDecorator( /**
* Wraps the [PersistenceCapability]{@link PersistenceCapability} with
* transactional capabilities.
* @param $q
* @param transactionService
* @param capabilityService
* @see TransactionalPersistenceCapability
* @constructor
*/
function TransactionCapabilityDecorator(
$q, $q,
transactionService, transactionService,
dirtyModelCache,
capabilityService capabilityService
) { ) {
this.capabilityService = capabilityService; this.capabilityService = capabilityService;
this.transactionService = transactionService; this.transactionService = transactionService;
this.dirtyModelCache = dirtyModelCache;
this.$q = $q; this.$q = $q;
} }
/** /**
* Decorate PersistenceCapability to ignore persistence calls when a * Decorate PersistenceCapability to queue persistence calls when a
* transaction is in progress. * transaction is in progress.
*/ */
TransactionDecorator.prototype.getCapabilities = function (model) { TransactionCapabilityDecorator.prototype.getCapabilities = function (model) {
var self = this, var self = this,
capabilities = this.capabilityService.getCapabilities(model), capabilities = this.capabilityService.getCapabilities(model),
persistenceCapability = capabilities.persistence; persistenceCapability = capabilities.persistence;
@ -55,7 +62,6 @@ define(
return new TransactionalPersistenceCapability( return new TransactionalPersistenceCapability(
self.$q, self.$q,
self.transactionService, self.transactionService,
self.dirtyModelCache,
original, original,
domainObject domainObject
); );
@ -63,6 +69,6 @@ define(
return capabilities; return capabilities;
}; };
return TransactionDecorator; return TransactionCapabilityDecorator;
} }
); );

View File

@ -26,44 +26,51 @@ define(
function () { function () {
'use strict'; 'use strict';
/**
* Wraps persistence capability to enable transactions. Transactions
* will cause persist calls not to be invoked immediately, but
* rather queued until [EditorCapability.save()]{@link EditorCapability#save}
* or [EditorCapability.cancel()]{@link EditorCapability#cancel} are
* called.
* @memberof platform/commonUI/edit/capabilities
* @param $q
* @param transactionService
* @param persistenceCapability
* @param domainObject
* @constructor
*/
function TransactionalPersistenceCapability( function TransactionalPersistenceCapability(
$q, $q,
transactionService, transactionService,
dirtyModelCache,
persistenceCapability, persistenceCapability,
domainObject domainObject
) { ) {
this.transactionService = transactionService; this.transactionService = transactionService;
this.dirtyModelCache = dirtyModelCache; this.persistenceCapability = persistenceCapability;
this.persistenceCapability = Object.create(persistenceCapability);
this.domainObject = domainObject; this.domainObject = domainObject;
this.$q = $q; this.$q = $q;
} }
/**
* The wrapped persist function. If a transaction is active, persist
* will be queued until the transaction is committed or cancelled.
* @returns {*}
*/
TransactionalPersistenceCapability.prototype.persist = function () { TransactionalPersistenceCapability.prototype.persist = function () {
var domainObject = this.domainObject, if (this.transactionService.isActive()) {
dirtyModelCache = this.dirtyModelCache; this.transactionService.addToTransaction(
if (this.transactionService.isActive() && !this.transactionService.isCommitting()) { this.persistenceCapability.persist.bind(this.persistenceCapability),
dirtyModelCache.markDirty(domainObject); this.persistenceCapability.refresh.bind(this.persistenceCapability)
//Using $q here because need to return something );
// from which 'catch' can be chained //Need to return a promise from this function
return this.$q.when(true); return this.$q.when(true);
} else { } else {
return this.persistenceCapability.persist().then(function (result) { return this.persistenceCapability.persist();
dirtyModelCache.markClean(domainObject);
return result;
});
} }
}; };
TransactionalPersistenceCapability.prototype.refresh = function () { TransactionalPersistenceCapability.prototype.refresh = function () {
var domainObject = this.domainObject, return this.persistenceCapability.refresh();
dirtyModelCache = this.dirtyModelCache;
return this.persistenceCapability.refresh().then(function (result) {
dirtyModelCache.markClean(domainObject);
return result;
});
}; };
TransactionalPersistenceCapability.prototype.getSpace = function () { TransactionalPersistenceCapability.prototype.getSpace = function () {

View File

@ -73,7 +73,8 @@ define(
function isEditing(context) { function isEditing(context) {
var domainObject = (context || {}).domainObject; var domainObject = (context || {}).domainObject;
return domainObject return domainObject
&& domainObject.getCapability('status').get('editing'); && domainObject.hasCapability('editor')
&& domainObject.getCapability('editor').isEditContextRoot();
} }
EditActionPolicy.prototype.allow = function (action, context) { EditActionPolicy.prototype.allow = function (action, context) {

View File

@ -34,6 +34,11 @@ define(
* from context menu of non-editable objects, when navigated object * from context menu of non-editable objects, when navigated object
* is being edited * is being edited
* @constructor * @constructor
* @param navigationService
* @param editModeBlacklist A blacklist of actions disallowed from
* context menu when navigated object is being edited
* @param nonEditContextBlacklist A blacklist of actions disallowed
* from context menu of non-editable objects, when navigated object
* @implements {Policy.<Action, ActionContext>} * @implements {Policy.<Action, ActionContext>}
*/ */
function EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist) { function EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist) {
@ -51,7 +56,7 @@ define(
navigatedObject = this.navigationService.getNavigation(), navigatedObject = this.navigationService.getNavigation(),
actionMetadata = action.getMetadata ? action.getMetadata() : {}; actionMetadata = action.getMetadata ? action.getMetadata() : {};
if (navigatedObject.getCapability("status").get("editing")) { if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()){ if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()){
//Target is within the editing context //Target is within the editing context
return this.editBlacklist.indexOf(actionMetadata.key) === -1; return this.editBlacklist.indexOf(actionMetadata.key) === -1;

View File

@ -41,12 +41,11 @@ define(
EditNavigationPolicy.prototype.isDirty = function(domainObject) { EditNavigationPolicy.prototype.isDirty = function(domainObject) {
var navigatedObject = domainObject, var navigatedObject = domainObject,
editorCapability = navigatedObject && editorCapability = navigatedObject &&
navigatedObject.getCapability("editor"), navigatedObject.getCapability("editor");
statusCapability = navigatedObject &&
navigatedObject.getCapability("status");
return statusCapability && statusCapability.get('editing') && return editorCapability &&
editorCapability && editorCapability.dirty(); editorCapability.isEditContextRoot() &&
editorCapability.dirty();
}; };
/** /**

View File

@ -35,10 +35,13 @@ define([], function () {
EditableMovePolicy.prototype.allow = function (action, context) { EditableMovePolicy.prototype.allow = function (action, context) {
var domainObject = context.domainObject, var domainObject = context.domainObject,
selectedObject = context.selectedObject, selectedObject = context.selectedObject,
key = action.getMetadata().key; key = action.getMetadata().key,
isDomainObjectEditing = domainObject.hasCapability('editor') &&
domainObject.getCapability('editor').inEditContext();
if (key === 'move' && domainObject.hasCapability('editor') && domainObject.getCapability('editor').inEditContext()) { if (key === 'move' && isDomainObjectEditing) {
return !!selectedObject && selectedObject.hasCapability('editor') && selectedObject.getCapability('editor').inEditContext(); return !!selectedObject && selectedObject.hasCapability('editor') &&
selectedObject.getCapability('editor').inEditContext();
} }
// Like all policies, allow by default. // Like all policies, allow by default.

View File

@ -136,7 +136,7 @@ define(
} }
}); });
if (representedObject.getCapability('status').get('editing')){ if (representedObject.hasCapability('editor') && representedObject.getCapability('editor').isEditContextRoot()){
setEditing(); setEditing();
} }
}; };

View File

@ -1,47 +0,0 @@
/*****************************************************************************
* 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() {
function DirtyModelCache(topic) {
this.cache = {};
}
DirtyModelCache.prototype.get = function () {
return this.cache;
};
DirtyModelCache.prototype.isDirty = function (domainObject) {
return !!this.cache[domainObject.getId()];
};
DirtyModelCache.prototype.markDirty = function (domainObject) {
this.cache[domainObject.getId()] = domainObject;
};
DirtyModelCache.prototype.markClean = function (domainObject) {
delete this.cache[domainObject.getId()];
};
return DirtyModelCache;
});

View File

@ -25,64 +25,90 @@ define(
function() { function() {
/** /**
* Implements an application-wide transaction state. Once a * Implements an application-wide transaction state. Once a
* transaction is started, calls to PersistenceCapability.persist() * transaction is started, calls to
* [PersistenceCapability.persist()]{@link PersistenceCapability#persist}
* will be deferred until a subsequent call to * will be deferred until a subsequent call to
* TransactionService.commit() is made. * [TransactionService.commit]{@link TransactionService#commit} is made.
* *
* @memberof platform/commonUI/edit/services
* @param $q * @param $q
* @constructor * @constructor
*/ */
function TransactionService($q, dirtyModelCache) { function TransactionService($q, $log) {
this.$q = $q; this.$q = $q;
this.$log = $log;
this.transaction = false; this.transaction = false;
this.committing = false;
this.cache = dirtyModelCache; this.onCommits = [];
this.onCancels = [];
} }
/**
* Starts a transaction. While a transaction is active all calls to
* [PersistenceCapability.persist](@link PersistenceCapability#persist)
* will be queued until [commit]{@link #commit} or [cancel]{@link
* #cancel} are called
*/
TransactionService.prototype.startTransaction = function () { TransactionService.prototype.startTransaction = function () {
if (this.transaction) if (this.transaction) {
console.error("Transaction already in progress") //Log error because this is a programming error if it occurs.
this.$log.error("Transaction already in progress");
}
this.transaction = true; this.transaction = true;
}; };
/**
* @returns {boolean} If true, indicates that a transaction is in progress
*/
TransactionService.prototype.isActive = function () { TransactionService.prototype.isActive = function () {
return this.transaction; return this.transaction;
}; };
TransactionService.prototype.isCommitting = function () { /**
return this.committing; * Adds provided functions to a queue to be called on
* [.commit()]{@link #commit} or
* [.cancel()]{@link #commit}
* @param onCommit A function to call on commit
* @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);
}
} else {
//Log error because this is a programming error if it occurs.
this.$log.error("No transaction in progress");
}
}; };
/** /**
* All persist calls deferred since the beginning of the transaction * All persist calls deferred since the beginning of the transaction
* will be committed. Any failures will be reported via a promise * will be committed.
* rejection. *
* @returns {*} * @returns {Promise} resolved when all persist operations have
* completed. Will reject if any commit operations fail
*/ */
TransactionService.prototype.commit = function () { TransactionService.prototype.commit = function () {
var self = this; var self = this,
cache = this.cache.get(); promises = [],
onCommit;
this.committing = true; while (this.onCommits.length > 0) { // ...using a while in case some onCommit adds to transaction
onCommit = this.onCommits.pop();
function keyToObject(key) { try { // ...also don't want to fail mid-loop...
return cache[key]; promises.push(onCommit());
} catch (e) {
this.$log.error("Error committing transaction.");
}
} }
return this.$q.all(promises).then( function () {
self.transaction = false;
function objectToPromise(object) { self.onCommits = [];
return object.getCapability('persistence').persist(); self.onCancels = [];
} });
return this.$q.all(
Object.keys(cache)
.map(keyToObject)
.map(objectToPromise))
.then(function () {
self.transaction = false;
this.committing = false;
}).catch(function() {
return this.committing = false;
});
}; };
/** /**
@ -95,23 +121,23 @@ define(
*/ */
TransactionService.prototype.cancel = function () { TransactionService.prototype.cancel = function () {
var self = this, var self = this,
cache = this.cache.get(); results = [],
onCancel;
function keyToObject(key) { while (this.onCancels.length > 0) {
return cache[key]; onCancel = this.onCancels.pop();
try {
results.push(onCancel());
} catch (error) {
this.$log.error("Error committing transaction.");
}
} }
return this.$q.all(results).then(function () {
self.transaction = false;
function objectToPromise(object) { self.onCommits = [];
return self.$q.when(object.getModel().persisted && object.getCapability('persistence').refresh()); self.onCancels = [];
} });
return this.$q.all(Object.keys(cache)
.map(keyToObject)
.map(objectToPromise))
.then(function () {
self.transaction = false;
this.committing = false;
});
}; };
return TransactionService; return TransactionService;

View File

@ -30,7 +30,9 @@ define(
mockLog, mockLog,
mockDomainObject, mockDomainObject,
mockType, mockType,
mockEditor,
actionContext, actionContext,
capabilities,
action; action;
beforeEach(function () { beforeEach(function () {
@ -40,7 +42,7 @@ define(
); );
mockNavigationService = jasmine.createSpyObj( mockNavigationService = jasmine.createSpyObj(
"navigationService", "navigationService",
[ "setNavigation", "getNavigation" ] [ "setNavigation", "getNavigation", "addListener", "removeListener" ]
); );
mockLog = jasmine.createSpyObj( mockLog = jasmine.createSpyObj(
"$log", "$log",
@ -48,14 +50,26 @@ define(
); );
mockDomainObject = jasmine.createSpyObj( mockDomainObject = jasmine.createSpyObj(
"domainObject", "domainObject",
[ "getId", "getModel", "getCapability" ] [ "getId", "getModel", "getCapability", "hasCapability", "useCapability" ]
); );
mockType = jasmine.createSpyObj( mockType = jasmine.createSpyObj(
"type", "type",
[ "hasFeature" ] [ "hasFeature" ]
); );
mockEditor = jasmine.createSpyObj(
"editorCapability",
["edit", "isEditContextRoot", "cancel"]
);
mockDomainObject.getCapability.andReturn(mockType); capabilities = {
type: mockType,
editor: mockEditor
};
mockDomainObject.getCapability.andCallFake( function (name) {
return capabilities[name];
});
mockDomainObject.hasCapability.andReturn(true);
mockType.hasFeature.andReturn(true); mockType.hasFeature.andReturn(true);
actionContext = { domainObject: mockDomainObject }; actionContext = { domainObject: mockDomainObject };
@ -68,51 +82,34 @@ define(
); );
}); });
it("is only applicable when a domain object is present", function () { it("is only applicable when an editable domain object is present", function () {
expect(EditAction.appliesTo(actionContext)).toBeTruthy(); expect(EditAction.appliesTo(actionContext)).toBeTruthy();
expect(EditAction.appliesTo({})).toBeFalsy(); expect(EditAction.appliesTo({})).toBeFalsy();
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith('editor');
// Should have checked for creatability // Should have checked for creatability
expect(mockType.hasFeature).toHaveBeenCalledWith('creation'); expect(mockType.hasFeature).toHaveBeenCalledWith('creation');
}); });
//TODO: Disabled for NEM Beta it("is only applicable to objects not already in edit mode", function () {
xit("changes URL path to edit mode when performed", function () { mockEditor.isEditContextRoot.andReturn(false);
expect(EditAction.appliesTo(actionContext)).toBe(true);
mockEditor.isEditContextRoot.andReturn(true);
expect(EditAction.appliesTo(actionContext)).toBe(false);
});
it ("cancels editing when user navigates away", function () {
action.perform(); action.perform();
expect(mockLocation.path).toHaveBeenCalledWith("/edit"); expect(mockNavigationService.addListener).toHaveBeenCalled();
mockNavigationService.addListener.mostRecentCall.args[0]();
expect(mockEditor.cancel).toHaveBeenCalled();
}); });
//TODO: Disabled for NEM Beta it ("invokes the Edit capability on the object", function () {
xit("ensures that the edited object is navigated-to", function () {
action.perform(); action.perform();
expect(mockNavigationService.setNavigation) expect(mockDomainObject.useCapability).toHaveBeenCalledWith("editor");
.toHaveBeenCalledWith(mockDomainObject);
}); });
//TODO: Disabled for NEM Beta
xit("logs a warning if constructed when inapplicable", function () {
// Verify precondition (ensure warn wasn't called during setup)
expect(mockLog.warn).not.toHaveBeenCalled();
// Should not have hit an exception...
new EditAction(
mockLocation,
mockNavigationService,
mockLog,
{}
).perform();
// ...but should have logged a warning
expect(mockLog.warn).toHaveBeenCalled();
// And should not have had other interactions
expect(mockLocation.path)
.not.toHaveBeenCalled();
expect(mockNavigationService.setNavigation)
.not.toHaveBeenCalled();
});
}); });
} }
); );

View File

@ -52,7 +52,7 @@ define(
); );
mockEditorCapability = jasmine.createSpyObj( mockEditorCapability = jasmine.createSpyObj(
"editor", "editor",
[ "save", "cancel" ] [ "save", "cancel", "isEditContextRoot" ]
); );
mockActionCapability = jasmine.createSpyObj( mockActionCapability = jasmine.createSpyObj(
"actionCapability", "actionCapability",
@ -71,7 +71,7 @@ define(
}); });
mockDomainObject.getModel.andReturn({persisted: 0}); mockDomainObject.getModel.andReturn({persisted: 0});
mockEditorCapability.save.andReturn(mockPromise(true)); mockEditorCapability.save.andReturn(mockPromise(true));
mockDomainObject.getOriginalObject.andReturn(mockDomainObject); mockEditorCapability.isEditContextRoot.andReturn(true);
action = new SaveAction(actionContext); action = new SaveAction(actionContext);
@ -97,6 +97,13 @@ define(
action.perform(); action.perform();
expect(mockEditorCapability.save).toHaveBeenCalled(); expect(mockEditorCapability.save).toHaveBeenCalled();
}); });
it("navigates to the object after saving",
function () {
action.perform();
expect(mockActionCapability.perform).toHaveBeenCalledWith("navigate");
});
}); });
} }
); );

View File

@ -78,10 +78,11 @@ define(
mockEditorCapability = jasmine.createSpyObj( mockEditorCapability = jasmine.createSpyObj(
"editor", "editor",
[ "save", "cancel" ] [ "save", "cancel", "isEditContextRoot" ]
); );
mockEditorCapability.cancel.andReturn(mockPromise(undefined)); mockEditorCapability.cancel.andReturn(mockPromise(undefined));
mockEditorCapability.save.andReturn(mockPromise(true)); mockEditorCapability.save.andReturn(mockPromise(true));
mockEditorCapability.isEditContextRoot.andReturn(true);
capabilities.editor = mockEditorCapability; capabilities.editor = mockEditorCapability;
mockActionCapability = jasmine.createSpyObj( mockActionCapability = jasmine.createSpyObj(

View File

@ -25,94 +25,150 @@ define(
function (EditorCapability) { function (EditorCapability) {
describe("The editor capability", function () { describe("The editor capability", function () {
var mockPersistence, var mockDomainObject,
mockEditableObject, capabilities,
mockDomainObject, mockParentObject,
mockCache, mockTransactionService,
mockCallback, mockStatusCapability,
model, mockParentStatus,
mockContextCapability,
capability; capability;
beforeEach(function () { function fastPromise(val) {
mockPersistence = jasmine.createSpyObj( return {
"persistence", then: function (callback) {
[ "persist" ] return callback(val);
); }
mockEditableObject = {
getModel: function () { return model; }
}; };
}
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj( mockDomainObject = jasmine.createSpyObj(
"domainObject", "domainObject",
[ "getId", "getModel", "getCapability", "useCapability" ] ["getId", "getModel", "hasCapability", "getCapability", "useCapability"]
); );
mockCache = jasmine.createSpyObj( mockParentObject = jasmine.createSpyObj(
"cache", "domainObject",
[ "saveAll", "markClean" ] ["getId", "getModel", "hasCapability", "getCapability", "useCapability"]
); );
mockCallback = jasmine.createSpy("callback"); mockTransactionService = jasmine.createSpyObj(
"transactionService",
[
"startTransaction",
"commit",
"cancel"
]
);
mockTransactionService.commit.andReturn(fastPromise());
mockTransactionService.cancel.andReturn(fastPromise());
mockDomainObject.getCapability.andReturn(mockPersistence); mockStatusCapability = jasmine.createSpyObj(
"statusCapability",
["get", "set"]
);
mockParentStatus = jasmine.createSpyObj(
"statusCapability",
["get", "set"]
);
mockContextCapability = jasmine.createSpyObj(
"contextCapability",
["getParent"]
);
mockContextCapability.getParent.andReturn(mockParentObject);
model = { someKey: "some value", x: 42 }; capabilities = {
context: mockContextCapability,
status: mockStatusCapability
};
mockDomainObject.hasCapability.andCallFake(function(name) {
return capabilities[name] !== undefined;
});
mockDomainObject.getCapability.andCallFake(function (name) {
return capabilities[name];
});
mockParentObject.getCapability.andReturn(mockParentStatus);
mockParentObject.hasCapability.andReturn(false);
capability = new EditorCapability( capability = new EditorCapability(
mockPersistence, mockTransactionService,
mockEditableObject, mockDomainObject
mockDomainObject,
mockCache
); );
}); });
//TODO: Disabled for NEM Beta it("starts a transaction when edit is invoked", function () {
xit("mutates the real domain object on nonrecursive save", function () { capability.edit();
capability.save(true).then(mockCallback); expect(mockTransactionService.startTransaction).toHaveBeenCalled();
});
// Wait for promise to resolve it("sets editing status on object", function () {
waitsFor(function () { capability.edit();
return mockCallback.calls.length > 0; expect(mockStatusCapability.set).toHaveBeenCalledWith("editing", true);
}, 250); });
runs(function () { it("uses editing status to determine editing context root", function () {
expect(mockDomainObject.useCapability) capability.edit();
.toHaveBeenCalledWith("mutation", jasmine.any(Function)); mockStatusCapability.get.andReturn(false);
// We should get the model from the editable object back expect(capability.isEditContextRoot()).toBe(false);
expect( mockStatusCapability.get.andReturn(true);
mockDomainObject.useCapability.mostRecentCall.args[1]() expect(capability.isEditContextRoot()).toBe(true);
).toEqual(model); });
it("inEditingContext returns true if parent object is being" +
" edited", function () {
mockStatusCapability.get.andReturn(false);
mockParentStatus.get.andReturn(false);
expect(capability.inEditContext()).toBe(false);
mockParentStatus.get.andReturn(true);
expect(capability.inEditContext()).toBe(true);
});
describe("save", function() {
beforeEach(function() {
capability.edit();
capability.save();
});
it("commits the transaction", function () {
expect(mockTransactionService.commit).toHaveBeenCalled();
});
it("resets the edit state", function () {
expect(mockStatusCapability.set).toHaveBeenCalledWith('editing', false);
}); });
}); });
//TODO: Disabled for NEM Beta describe("cancel", function() {
xit("tells the cache to save others", function () { beforeEach(function() {
capability.save().then(mockCallback); capability.edit();
capability.cancel();
// Wait for promise to resolve });
waitsFor(function () { it("cancels the transaction", function () {
return mockCallback.calls.length > 0; expect(mockTransactionService.cancel).toHaveBeenCalled();
}, 250); });
it("resets the edit state", function () {
runs(function () { expect(mockStatusCapability.set).toHaveBeenCalledWith('editing', false);
expect(mockCache.saveAll).toHaveBeenCalled();
}); });
}); });
//TODO: Disabled for NEM Beta describe("dirty", function() {
xit("has no interactions on cancel", function () { var model = {};
capability.cancel().then(mockCallback);
// Wait for promise to resolve beforeEach(function() {
waitsFor(function () { mockDomainObject.getModel.andReturn(model);
return mockCallback.calls.length > 0; capability.edit();
}, 250); capability.cancel();
});
it("returns true if the object has been modified since it" +
" was last persisted", function () {
model.modified = 0;
model.persisted = 0;
expect(capability.dirty()).toBe(false);
runs(function () { model.modified = 1;
expect(mockDomainObject.useCapability).not.toHaveBeenCalled(); expect(capability.dirty()).toBe(true);
expect(mockCache.markClean).not.toHaveBeenCalled();
expect(mockCache.saveAll).not.toHaveBeenCalled();
}); });
}); });
}); });
} }
); );

View File

@ -0,0 +1,59 @@
/*****************************************************************************
* 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,describe,it,expect,beforeEach,waitsFor,runs,jasmine,xit,xdescribe*/
define(
[
"../../src/capabilities/TransactionalPersistenceCapability",
"../../src/capabilities/TransactionCapabilityDecorator"
],
function (TransactionalPersistenceCapability, TransactionCapabilityDecorator) {
"use strict";
describe("The transaction capability decorator", function () {
var mockQ,
mockTransactionService,
mockCapabilityService,
provider;
beforeEach(function() {
//mockQ = jasmine.createSpyObj("$q", []);
mockQ = {};
//mockTransactionService =
// jasmine.createSpyObj("transactionService", []);
mockTransactionService = {};
mockCapabilityService = jasmine.createSpyObj("capabilityService", ["getCapabilities"]);
mockCapabilityService.getCapabilities.andReturn({
persistence: function() {}
});
provider = new TransactionCapabilityDecorator(mockQ, mockTransactionService, mockCapabilityService);
});
it("decorates the persistence capability", function() {
var capabilities = provider.getCapabilities();
expect(capabilities.persistence({}) instanceof TransactionalPersistenceCapability).toBe(true);
});
});
}
);

View File

@ -0,0 +1,92 @@
/*****************************************************************************
* 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,describe,it,expect,beforeEach,waitsFor,runs,jasmine,xit,xdescribe*/
define(
[
"../../src/capabilities/TransactionalPersistenceCapability"
],
function (TransactionalPersistenceCapability) {
"use strict";
function fastPromise(val) {
return {
then: function(callback) {
return callback(val);
}
};
}
describe("The transactional persistence decorator", function () {
var mockQ,
mockTransactionService,
mockPersistence,
mockDomainObject,
capability;
beforeEach(function() {
mockQ = jasmine.createSpyObj("$q", ["when"]);
mockQ.when.andCallFake(function (val) {
return fastPromise(val);
});
mockTransactionService = jasmine.createSpyObj(
"transactionService",
["isActive", "addToTransaction"]
);
mockPersistence = jasmine.createSpyObj(
"persistenceCapability",
["persist", "refresh"]
);
capability = new TransactionalPersistenceCapability(mockQ, mockTransactionService, mockPersistence, mockDomainObject);
});
it("if no transaction is active, passes through to persistence" +
" provider", function() {
mockTransactionService.isActive.andReturn(false);
capability.persist();
expect(mockPersistence.persist).toHaveBeenCalled();
});
it("if transaction is active, persist call is queued", function() {
mockTransactionService.isActive.andReturn(true);
capability.persist();
expect(mockTransactionService.addToTransaction).toHaveBeenCalled();
//Test that it was the persist call that was queued
mockTransactionService.addToTransaction.mostRecentCall.args[0]();
expect(mockPersistence.persist).toHaveBeenCalled();
});
it("if transaction is active, refresh call is queued as cancel" +
" function", function() {
mockTransactionService.isActive.andReturn(true);
capability.persist();
//Test that it was the persist call that was queued
mockTransactionService.addToTransaction.mostRecentCall.args[1]();
expect(mockPersistence.refresh).toHaveBeenCalled();
});
});
}
);

View File

@ -34,7 +34,7 @@ define(
mockEditAction, mockEditAction,
mockPropertiesAction, mockPropertiesAction,
mockTypeCapability, mockTypeCapability,
mockStatusCapability, mockEditorCapability,
capabilities, capabilities,
plotView, plotView,
policy; policy;
@ -48,11 +48,10 @@ define(
'getCapability' 'getCapability'
] ]
); );
mockStatusCapability = jasmine.createSpyObj('statusCapability', ['get']); mockEditorCapability = jasmine.createSpyObj('editorCapability', ['isEditContextRoot']);
mockStatusCapability.get.andReturn(false);
mockTypeCapability = jasmine.createSpyObj('type', ['getKey']); mockTypeCapability = jasmine.createSpyObj('type', ['getKey']);
capabilities = { capabilities = {
'status': mockStatusCapability, 'editor': mockEditorCapability,
'type': mockTypeCapability 'type': mockTypeCapability
}; };
@ -112,7 +111,7 @@ define(
it("disallows the edit action when object is already being" + it("disallows the edit action when object is already being" +
" edited", function () { " edited", function () {
testViews = [ editableView ]; testViews = [ editableView ];
mockStatusCapability.get.andReturn(true); mockEditorCapability.isEditContextRoot.andReturn(true);
expect(policy.allow(mockEditAction, testContext)).toBe(false); expect(policy.allow(mockEditAction, testContext)).toBe(false);
}); });

View File

@ -32,16 +32,22 @@ define(
context, context,
navigatedObject, navigatedObject,
mockDomainObject, mockDomainObject,
mockEditorCapability,
metadata, metadata,
editModeBlacklist = ["copy", "follow", "window", "link", "locate"], editModeBlacklist = ["copy", "follow", "window", "link", "locate"],
nonEditContextBlacklist = ["copy", "follow", "properties", "move", "link", "remove", "locate"]; nonEditContextBlacklist = ["copy", "follow", "properties", "move", "link", "remove", "locate"];
beforeEach(function () { beforeEach(function () {
navigatedObject = jasmine.createSpyObj("navigatedObject", ["hasCapability"]); mockEditorCapability = jasmine.createSpyObj("editorCapability", ["isEditContextRoot", "inEditContext"]);
navigatedObject = jasmine.createSpyObj("navigatedObject", ["hasCapability", "getCapability"]);
navigatedObject.getCapability.andReturn(mockEditorCapability);
navigatedObject.hasCapability.andReturn(false); navigatedObject.hasCapability.andReturn(false);
mockDomainObject = jasmine.createSpyObj("domainObject", ["hasCapability", "getCapability"]); mockDomainObject = jasmine.createSpyObj("domainObject", ["hasCapability", "getCapability"]);
mockDomainObject.hasCapability.andReturn(false); mockDomainObject.hasCapability.andReturn(false);
mockDomainObject.getCapability.andReturn(mockEditorCapability);
navigationService = jasmine.createSpyObj("navigationService", ["getNavigation"]); navigationService = jasmine.createSpyObj("navigationService", ["getNavigation"]);
navigationService.getNavigation.andReturn(navigatedObject); navigationService.getNavigation.andReturn(navigatedObject);
@ -62,6 +68,7 @@ define(
it('Allows "window" action when navigated object in edit mode,' + it('Allows "window" action when navigated object in edit mode,' +
' but selected object not in edit mode ', function() { ' but selected object not in edit mode ', function() {
navigatedObject.hasCapability.andReturn(true); navigatedObject.hasCapability.andReturn(true);
mockEditorCapability.isEditContextRoot.andReturn(true);
metadata.key = "window"; metadata.key = "window";
expect(policy.allow(mockAction, context)).toBe(true); expect(policy.allow(mockAction, context)).toBe(true);
}); });
@ -91,6 +98,8 @@ define(
it('Disallows "move" action when navigated object in edit mode,' + it('Disallows "move" action when navigated object in edit mode,' +
' but selected object not in edit mode ', function() { ' but selected object not in edit mode ', function() {
navigatedObject.hasCapability.andReturn(true); navigatedObject.hasCapability.andReturn(true);
mockEditorCapability.isEditContextRoot.andReturn(true);
mockEditorCapability.inEditContext.andReturn(false);
metadata.key = "move"; metadata.key = "move";
expect(policy.allow(mockAction, context)).toBe(false); expect(policy.allow(mockAction, context)).toBe(false);
}); });
@ -99,6 +108,9 @@ define(
' selected object in edit mode', function() { ' selected object in edit mode', function() {
navigatedObject.hasCapability.andReturn(true); navigatedObject.hasCapability.andReturn(true);
mockDomainObject.hasCapability.andReturn(true); mockDomainObject.hasCapability.andReturn(true);
mockEditorCapability.isEditContextRoot.andReturn(true);
mockEditorCapability.inEditContext.andReturn(true);
metadata.key = "copy"; metadata.key = "copy";
expect(policy.allow(mockAction, context)).toBe(false); expect(policy.allow(mockAction, context)).toBe(false);
}); });

View File

@ -33,8 +33,13 @@ define(
testMode = true; // Act as if we're in Edit mode by default testMode = true; // Act as if we're in Edit mode by default
mockDomainObject = jasmine.createSpyObj( mockDomainObject = jasmine.createSpyObj(
'domainObject', 'domainObject',
['hasCapability'] ['hasCapability', 'getCapability']
); );
mockDomainObject.getCapability.andReturn({
inEditContext: function () {
return true;
}
});
mockDomainObject.hasCapability.andCallFake(function (c) { mockDomainObject.hasCapability.andCallFake(function (c) {
return (c === 'editor') && testMode; return (c === 'editor') && testMode;
}); });

View File

@ -32,6 +32,7 @@ define(
mockDomainObject, mockDomainObject,
mockPersistence, mockPersistence,
mockStatusCapability, mockStatusCapability,
mockEditorCapability,
mockCapabilities, mockCapabilities,
representer; representer;
@ -58,11 +59,14 @@ define(
mockPersistence = mockPersistence =
jasmine.createSpyObj("persistence", ["persist"]); jasmine.createSpyObj("persistence", ["persist"]);
mockStatusCapability = mockStatusCapability =
jasmine.createSpyObj("statusCapability", ["get", "listen"]); jasmine.createSpyObj("statusCapability", ["listen"]);
mockStatusCapability.get.andReturn(false); mockEditorCapability =
jasmine.createSpyObj("editorCapability", ["isEditContextRoot"]);
mockCapabilities = { mockCapabilities = {
'persistence': mockPersistence, 'persistence': mockPersistence,
'status': mockStatusCapability 'status': mockStatusCapability,
'editor': mockEditorCapability
}; };
mockDomainObject.getModel.andReturn({}); mockDomainObject.getModel.andReturn({});
@ -82,6 +86,7 @@ define(
it("Sets edit view template on edit mode", function () { it("Sets edit view template on edit mode", function () {
mockStatusCapability.listen.mostRecentCall.args[0](['editing']); mockStatusCapability.listen.mostRecentCall.args[0](['editing']);
mockEditorCapability.isEditContextRoot.andReturn(true);
expect(mockScope.viewObjectTemplate).toEqual('edit-object'); expect(mockScope.viewObjectTemplate).toEqual('edit-object');
}); });

View File

@ -0,0 +1,127 @@
/*****************************************************************************
* 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,describe,it,expect,beforeEach,jasmine*/
define(
["../../src/services/TransactionService"],
function (TransactionService) {
"use strict";
describe("The Transaction Service", function () {
var mockQ,
mockLog,
transactionService;
function fastPromise (val) {
return {
then: function (callback) {
return fastPromise(callback(val));
}
};
}
beforeEach(function () {
mockQ = jasmine.createSpyObj("$q", ["all"]);
mockQ.all.andReturn(fastPromise());
mockLog = jasmine.createSpyObj("$log", ["error"]);
transactionService = new TransactionService(mockQ, mockLog);
});
it("isActive returns true if a transaction is in progress", function () {
expect(transactionService.isActive()).toBe(false);
transactionService.startTransaction();
expect(transactionService.isActive()).toBe(true);
});
it("addToTransaction queues onCommit and onCancel functions", function () {
var onCommit = jasmine.createSpy('onCommit'),
onCancel = jasmine.createSpy('onCancel');
transactionService.startTransaction();
transactionService.addToTransaction(onCommit, onCancel);
expect(transactionService.onCommits.length).toBe(1);
expect(transactionService.onCancels.length).toBe(1);
});
describe("commit", function () {
var onCommits;
beforeEach(function() {
onCommits = [0, 1, 2].map(function(val) {
return jasmine.createSpy("onCommit" + val);
});
transactionService.startTransaction();
onCommits.forEach(transactionService.addToTransaction.bind(transactionService));
});
it("commit calls all queued commit functions", function () {
expect(transactionService.onCommits.length).toBe(3);
transactionService.commit();
onCommits.forEach( function (spy) {
expect(spy).toHaveBeenCalled();
});
});
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);
});
});
describe("cancel", function () {
var onCancels;
beforeEach(function() {
onCancels = [0, 1, 2].map(function(val) {
return jasmine.createSpy("onCancel" + val);
});
transactionService.startTransaction();
onCancels.forEach(function (onCancel) {
transactionService.addToTransaction(undefined, onCancel);
});
});
it("cancel calls all queued cancel functions", function () {
expect(transactionService.onCancels.length).toBe(3);
transactionService.cancel();
onCancels.forEach( function (spy) {
expect(spy).toHaveBeenCalled();
});
});
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);
});
});
});
}
);

View File

@ -39,7 +39,7 @@ define(
if (!regionPart.modes){ if (!regionPart.modes){
return true; return true;
} }
if (domainObject.getCapability('status').get('editing')){ if (domainObject.hasCapability('editor') && domainObject.getCapability('editor').inEditContext()){
//If the domain object is in edit mode, only include a part //If the domain object is in edit mode, only include a part
// if it is marked editable // if it is marked editable
return regionPart.modes.indexOf('edit') !== -1; return regionPart.modes.indexOf('edit') !== -1;

View File

@ -28,7 +28,7 @@ define(
var editableRegionPolicy, var editableRegionPolicy,
mockDomainObject, mockDomainObject,
mockStatusCapability, mockEditorCapability,
mockBrowseRegionPart = { mockBrowseRegionPart = {
modes: 'browse' modes: 'browse'
}, },
@ -40,31 +40,32 @@ define(
beforeEach(function(){ beforeEach(function(){
editableRegionPolicy = new EditableRegionPolicy(); editableRegionPolicy = new EditableRegionPolicy();
mockStatusCapability = jasmine.createSpyObj("statusCapability", [ mockEditorCapability = jasmine.createSpyObj("editorCapability", [
"get" "inEditContext"
]); ]);
mockDomainObject = jasmine.createSpyObj("domainObject", [ mockDomainObject = jasmine.createSpyObj("domainObject", [
"getCapability" "hasCapability", "getCapability"
]); ]);
mockDomainObject.getCapability.andReturn(mockStatusCapability); mockDomainObject.hasCapability.andReturn(true);
mockDomainObject.getCapability.andReturn(mockEditorCapability);
}); });
it("includes only browse region parts for object not in edit mode", function() { it("includes only browse region parts for object not in edit mode", function() {
mockStatusCapability.get.andReturn(false); mockEditorCapability.inEditContext.andReturn(false);
expect(editableRegionPolicy.allow(mockBrowseRegionPart, mockDomainObject)).toBe(true); expect(editableRegionPolicy.allow(mockBrowseRegionPart, mockDomainObject)).toBe(true);
expect(editableRegionPolicy.allow(mockEditRegionPart, mockDomainObject)).toBe(false); expect(editableRegionPolicy.allow(mockEditRegionPart, mockDomainObject)).toBe(false);
}); });
it("includes only edit region parts for object in edit mode", function() { it("includes only edit region parts for object in edit mode", function() {
mockStatusCapability.get.andReturn(true); mockEditorCapability.inEditContext.andReturn(true);
expect(editableRegionPolicy.allow(mockBrowseRegionPart, mockDomainObject)).toBe(false); expect(editableRegionPolicy.allow(mockBrowseRegionPart, mockDomainObject)).toBe(false);
expect(editableRegionPolicy.allow(mockEditRegionPart, mockDomainObject)).toBe(true); expect(editableRegionPolicy.allow(mockEditRegionPart, mockDomainObject)).toBe(true);
}); });
it("includes region parts with no mode specification", function() { it("includes region parts with no mode specification", function() {
mockStatusCapability.get.andReturn(false); mockEditorCapability.inEditContext.andReturn(false);
expect(editableRegionPolicy.allow(mockAllModesRegionPart, mockDomainObject)).toBe(true); expect(editableRegionPolicy.allow(mockAllModesRegionPart, mockDomainObject)).toBe(true);
mockStatusCapability.get.andReturn(true); mockEditorCapability.inEditContext.andReturn(true);
expect(editableRegionPolicy.allow(mockAllModesRegionPart, mockDomainObject)).toBe(true); expect(editableRegionPolicy.allow(mockAllModesRegionPart, mockDomainObject)).toBe(true);
}); });

View File

@ -155,18 +155,6 @@ define(
expect(model).toEqual(refreshModel); expect(model).toEqual(refreshModel);
}); });
it("does not overwrite unpersisted changes on refresh", function () {
var refreshModel = {someOtherKey: "some other value"},
mockCallback = jasmine.createSpy();
model.modified = 2;
model.persisted = 1;
mockPersistenceService.readObject.andReturn(asPromise(refreshModel));
persistence.refresh().then(mockCallback);
expect(model).not.toEqual(refreshModel);
// Should have also indicated that no changes were actually made
expect(mockCallback).toHaveBeenCalledWith(false);
});
it("does not trigger error notification on successful" + it("does not trigger error notification on successful" +
" persistence", function () { " persistence", function () {
persistence.persist(); persistence.persist();

View File

@ -40,7 +40,7 @@ define(
} }
// Check if we are in edit mode (also check parents) // Check if we are in edit mode (also check parents)
function inEditMode(swimlane) { function inEditMode() {
return swimlane.domainObject.hasCapability('editor') && return swimlane.domainObject.hasCapability('editor') &&
swimlane.domainObject.getCapability('editor').inEditContext(); swimlane.domainObject.getCapability('editor').inEditContext();
} }
@ -174,7 +174,7 @@ define(
* @returns {boolean} true if this should be allowed * @returns {boolean} true if this should be allowed
*/ */
allowDropIn: function (id, domainObject) { allowDropIn: function (id, domainObject) {
return inEditMode(swimlane) && return inEditMode() &&
!pathContains(swimlane, id) && !pathContains(swimlane, id) &&
!contains(swimlane, id) && !contains(swimlane, id) &&
canDrop(swimlane.domainObject, domainObject); canDrop(swimlane.domainObject, domainObject);
@ -189,7 +189,7 @@ define(
allowDropAfter: function (id, domainObject) { allowDropAfter: function (id, domainObject) {
var target = expandedForDropInto() ? var target = expandedForDropInto() ?
swimlane : swimlane.parent; swimlane : swimlane.parent;
return inEditMode(swimlane) && return inEditMode() &&
target && target &&
!pathContains(target, id) && !pathContains(target, id) &&
canDrop(target.domainObject, domainObject); canDrop(target.domainObject, domainObject);

View File

@ -82,11 +82,18 @@ define(
it("persists zoom changes in Edit mode", function () { it("persists zoom changes in Edit mode", function () {
mockScope.domainObject = jasmine.createSpyObj( mockScope.domainObject = jasmine.createSpyObj(
'domainObject', 'domainObject',
['hasCapability'] ['hasCapability', 'getCapability']
); );
mockScope.domainObject.hasCapability.andCallFake(function (c) { mockScope.domainObject.hasCapability.andCallFake(function (c) {
return c === 'editor'; return c === 'editor';
}); });
mockScope.domainObject.getCapability.andCallFake(function (c) {
if (c === 'editor') {
return {
inEditContext: function () {return true;}
};
}
});
controller.zoom(1); controller.zoom(1);
expect(mockScope.commit).toHaveBeenCalled(); expect(mockScope.commit).toHaveBeenCalled();
expect(mockScope.configuration.zoomLevel) expect(mockScope.configuration.zoomLevel)

View File

@ -28,6 +28,7 @@ define(
var mockSwimlane, var mockSwimlane,
mockOtherObject, mockOtherObject,
mockActionCapability, mockActionCapability,
mockEditorCapability,
mockPersistence, mockPersistence,
mockContext, mockContext,
mockAction, mockAction,
@ -36,6 +37,8 @@ define(
beforeEach(function () { beforeEach(function () {
var mockPromise = jasmine.createSpyObj('promise', ['then']); var mockPromise = jasmine.createSpyObj('promise', ['then']);
mockEditorCapability = jasmine.createSpyObj('editorCapability', ['inEditContext']);
mockSwimlane = jasmine.createSpyObj( mockSwimlane = jasmine.createSpyObj(
"swimlane", "swimlane",
[ "highlight", "highlightBottom" ] [ "highlight", "highlightBottom" ]
@ -86,19 +89,22 @@ define(
mockSwimlane.domainObject.getCapability.andCallFake(function (c) { mockSwimlane.domainObject.getCapability.andCallFake(function (c) {
return { return {
action: mockActionCapability, action: mockActionCapability,
persistence: mockPersistence persistence: mockPersistence,
editor: mockEditorCapability
}[c]; }[c];
}); });
mockSwimlane.parent.domainObject.getCapability.andCallFake(function (c) { mockSwimlane.parent.domainObject.getCapability.andCallFake(function (c) {
return { return {
action: mockActionCapability, action: mockActionCapability,
persistence: mockPersistence persistence: mockPersistence,
editor: mockEditorCapability
}[c]; }[c];
}); });
mockOtherObject.getCapability.andCallFake(function (c) { mockOtherObject.getCapability.andCallFake(function (c) {
return { return {
action: mockActionCapability, action: mockActionCapability,
context: mockContext context: mockContext,
editor: mockEditorCapability
}[c]; }[c];
}); });
mockContext.getParent.andReturn(mockOtherObject); mockContext.getParent.andReturn(mockOtherObject);
@ -109,13 +115,14 @@ define(
}); });
it("disallows drop outside of edit mode", function () { it("disallows drop outside of edit mode", function () {
mockEditorCapability.inEditContext.andReturn(true);
// Verify precondition // Verify precondition
expect(handler.allowDropIn('d', mockSwimlane.domainObject)) expect(handler.allowDropIn('d', mockSwimlane.domainObject))
.toBeTruthy(); .toBeTruthy();
expect(handler.allowDropAfter('d', mockSwimlane.domainObject)) expect(handler.allowDropAfter('d', mockSwimlane.domainObject))
.toBeTruthy(); .toBeTruthy();
// Act as if we're not in edit mode // Act as if we're not in edit mode
mockSwimlane.domainObject.hasCapability.andReturn(false); mockEditorCapability.inEditContext.andReturn(false);
// Now, they should be disallowed // Now, they should be disallowed
expect(handler.allowDropIn('d', mockSwimlane.domainObject)) expect(handler.allowDropIn('d', mockSwimlane.domainObject))
.toBeFalsy(); .toBeFalsy();

View File

@ -53,9 +53,7 @@ define(
* @param {ViewDefinition[]} views an array of view extensions * @param {ViewDefinition[]} views an array of view extensions
*/ */
function MCTRepresentation(representations, views, representers, $q, templateLinker, $log) { function MCTRepresentation(representations, views, representers, $q, templateLinker, $log) {
var representationMap = {}, var representationMap = {};
listeners = 0;
domainObjectListener;
// Assemble all representations and views // Assemble all representations and views
// The distinction between views and representations is // The distinction between views and representations is
@ -250,10 +248,10 @@ define(
} }
/** /**
* Add a listener for status changes to the object itself. * Add a listener to the object for status changes.
*/ */
$scope.$watch("domainObject", function(domainObject, oldDomainObject) { $scope.$watch("domainObject", function (domainObject, oldDomainObject) {
if (domainObject!==oldDomainObject){ if (domainObject !== oldDomainObject){
listenForStatusChange(domainObject); listenForStatusChange(domainObject);
} }
}); });

View File

@ -103,13 +103,18 @@ define(
// the change. // the change.
if (id) { if (id) {
e.preventDefault(); e.preventDefault();
if (domainObjectType!=='folder') {
domainObject.getCapability('action').perform('edit');
}
//Use scope.apply, drop event is outside digest cycle
// and if not applied here causes visual artifacts.
scope.$apply( function() {
if (domainObjectType !== 'folder') {
domainObject.getCapability('action').perform('edit');
}
});
$q.when(action && action.perform()).then(function () { $q.when(action && action.perform()).then(function () {
broadcastDrop(id, event); broadcastDrop(id, event);
}); });
} }
} }

View File

@ -36,6 +36,7 @@ define(
testViews, testViews,
testUrls, testUrls,
mockRepresenters, mockRepresenters,
mockStatusCapability,
mockQ, mockQ,
mockLinker, mockLinker,
mockLog, mockLog,
@ -118,6 +119,8 @@ define(
mockChangeTemplate = jasmine.createSpy('changeTemplate'); mockChangeTemplate = jasmine.createSpy('changeTemplate');
mockLog = jasmine.createSpyObj("$log", LOG_FUNCTIONS); mockLog = jasmine.createSpyObj("$log", LOG_FUNCTIONS);
mockStatusCapability = jasmine.createSpyObj("statusCapability", ["listen"]);
mockScope = jasmine.createSpyObj("scope", [ "$watch", "$on" ]); mockScope = jasmine.createSpyObj("scope", [ "$watch", "$on" ]);
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS); mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS); mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
@ -128,6 +131,10 @@ define(
return testUrls[ext.key]; return testUrls[ext.key];
}); });
mockDomainObject.getCapability.andCallFake(function (c) {
return c === 'status' && mockStatusCapability;
});
mctRepresentation = new MCTRepresentation( mctRepresentation = new MCTRepresentation(
testRepresentations, testRepresentations,
testViews, testViews,

View File

@ -120,8 +120,8 @@ define([
provider = this; provider = this;
mutationTopic.listen(function (mutatedObject) { mutationTopic.listen(function (mutatedObject) {
var status = mutatedObject.getCapability('status'); var editor = mutatedObject.getCapability('editor');
if (!status || !status.get('editing')) { if (!editor || !editor.inEditContext()) {
provider.index( provider.index(
mutatedObject.getId(), mutatedObject.getId(),
mutatedObject.getModel() mutatedObject.getModel()