diff --git a/bundles.json b/bundles.json
index 666e520854..de005c5961 100644
--- a/bundles.json
+++ b/bundles.json
@@ -16,6 +16,7 @@
"platform/forms",
"platform/persistence/queue",
"platform/policy",
+ "platform/entanglement",
"example/persistence",
"example/generator"
diff --git a/platform/commonUI/browse/src/creation/CreationService.js b/platform/commonUI/browse/src/creation/CreationService.js
index 4d60e6dc9f..015573c8bd 100644
--- a/platform/commonUI/browse/src/creation/CreationService.js
+++ b/platform/commonUI/browse/src/creation/CreationService.js
@@ -77,7 +77,19 @@ define(
return undefined;
}
- return parentPersistence.persist();
+ return parentPersistence.persist().then(function () {
+ // Locate and return new Object in context of parent.
+ return parent
+ .useCapability('composition')
+ .then(function (children) {
+ var i;
+ for (i = 0; i < children.length; i += 1) {
+ if (children[i].getId() === id) {
+ return children[i];
+ }
+ }
+ });
+ });
});
}
@@ -126,4 +138,4 @@ define(
return CreationService;
}
-);
\ No newline at end of file
+);
diff --git a/platform/commonUI/browse/test/creation/CreationServiceSpec.js b/platform/commonUI/browse/test/creation/CreationServiceSpec.js
index 3280350d22..277b195eaf 100644
--- a/platform/commonUI/browse/test/creation/CreationServiceSpec.js
+++ b/platform/commonUI/browse/test/creation/CreationServiceSpec.js
@@ -34,8 +34,10 @@ define(
mockQ,
mockLog,
mockParentObject,
+ mockNewObject,
mockMutationCapability,
mockPersistenceCapability,
+ mockCompositionCapability,
mockCapabilities,
creationService;
@@ -69,6 +71,10 @@ define(
"parentObject",
[ "getId", "getCapability", "useCapability" ]
);
+ mockNewObject = jasmine.createSpyObj(
+ "newObject",
+ [ "getId" ]
+ );
mockMutationCapability = jasmine.createSpyObj(
"mutation",
[ "invoke" ]
@@ -77,9 +83,14 @@ define(
"persistence",
[ "persist", "getSpace" ]
);
+ mockCompositionCapability = jasmine.createSpyObj(
+ "composition",
+ ["invoke"]
+ );
mockCapabilities = {
mutation: mockMutationCapability,
- persistence: mockPersistenceCapability
+ persistence: mockPersistenceCapability,
+ composition: mockCompositionCapability
};
mockPersistenceService.createObject.andReturn(
@@ -93,8 +104,15 @@ define(
return mockCapabilities[key].invoke(value);
});
+ mockPersistenceCapability.persist.andReturn(
+ mockPromise(true)
+ );
+
mockMutationCapability.invoke.andReturn(mockPromise(true));
mockPersistenceCapability.getSpace.andReturn("testSpace");
+ mockCompositionCapability.invoke.andReturn(
+ mockPromise([mockNewObject])
+ );
creationService = new CreationService(
mockPersistenceService,
diff --git a/platform/entanglement/README.md b/platform/entanglement/README.md
new file mode 100644
index 0000000000..f3685bad05
--- /dev/null
+++ b/platform/entanglement/README.md
@@ -0,0 +1,24 @@
+# Entanglement
+
+Entanglement is the process of moving, copying, and linking domain objects
+in such a way that their relationships are impossible to discern.
+
+This bundle provides move, copy, and link functionality. Acheiving a state of
+entanglement is left up to the end user.
+
+
+## Services implement logic
+
+Each method (move, copy, link) is implemented as a service, and each service
+provides two functions: `validate` and `perform`.
+
+`validate(object, parentCandidate)` returns true if the `object` can be
+move/copy/linked into the `parentCandidate`'s composition.
+
+`perform(object, parentObject)` move/copy/links the `object` into the
+`parentObject`'s composition.
+
+## Actions implement user interactions
+
+Actions are used to expose move/copy/link to the user. They prompt for input
+where necessary, and complete the actions.
diff --git a/platform/entanglement/bundle.json b/platform/entanglement/bundle.json
new file mode 100644
index 0000000000..7220669988
--- /dev/null
+++ b/platform/entanglement/bundle.json
@@ -0,0 +1,75 @@
+{
+ "name": "Entanglement",
+ "description": "Tools to assist you in entangling the world of WARP.",
+ "configuration": {},
+ "extensions": {
+ "actions": [
+ {
+ "key": "move",
+ "name": "Move",
+ "description": "Move object to another location.",
+ "glyph": "f",
+ "category": "contextual",
+ "implementation": "actions/MoveAction.js",
+ "depends": ["locationService", "moveService"]
+ },
+ {
+ "key": "copy",
+ "name": "Duplicate",
+ "description": "Duplicate object to another location.",
+ "glyph": "+",
+ "category": "contextual",
+ "implementation": "actions/CopyAction.js",
+ "depends": ["locationService", "copyService"]
+ },
+ {
+ "key": "link",
+ "name": "Create Link",
+ "description": "Create Link to object in another location.",
+ "glyph": "\u00E8",
+ "category": "contextual",
+ "implementation": "actions/LinkAction.js",
+ "depends": ["locationService", "linkService"]
+ }
+ ],
+ "components": [
+ ],
+ "controllers": [
+ ],
+ "capabilities": [
+ ],
+ "services": [
+ {
+ "key": "moveService",
+ "name": "Move Service",
+ "description": "Provides a service for moving objects",
+ "implementation": "services/MoveService.js",
+ "depends": ["policyService", "linkService"]
+ },
+ {
+ "key": "linkService",
+ "name": "Link Service",
+ "description": "Provides a service for linking objects",
+ "implementation": "services/LinkService.js",
+ "depends": ["policyService"]
+ },
+ {
+ "key": "copyService",
+ "name": "Copy Service",
+ "description": "Provides a service for copying objects",
+ "implementation": "services/CopyService.js",
+ "depends": ["$q", "creationService", "policyService"]
+ },
+ {
+ "key": "locationService",
+ "name": "Location Service",
+ "description": "Provides a service for prompting a user for locations.",
+ "implementation": "services/LocationService.js",
+ "depends": ["dialogService"]
+ }
+
+ ],
+ "licenses": [
+ ]
+ }
+}
diff --git a/platform/entanglement/src/actions/CopyAction.js b/platform/entanglement/src/actions/CopyAction.js
new file mode 100644
index 0000000000..aff8b94fb5
--- /dev/null
+++ b/platform/entanglement/src/actions/CopyAction.js
@@ -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 */
+define(
+ function () {
+ "use strict";
+
+
+ /**
+ * The CopyAction is available from context menus and allows a user to
+ * deep copy an object to another location of their choosing.
+ *
+ * @implements Action
+ */
+ function CopyAction(locationService, copyService, context) {
+
+ var object,
+ newParent,
+ currentParent;
+
+ if (context.selectedObject) {
+ newParent = context.domainObject;
+ object = context.selectedObject;
+ } else {
+ object = context.domainObject;
+ }
+
+ currentParent = object
+ .getCapability('context')
+ .getParent();
+
+ return {
+ perform: function () {
+
+ if (newParent) {
+ return copyService
+ .perform(object, newParent);
+ }
+
+ var dialogTitle,
+ label,
+ validateLocation;
+
+ dialogTitle = [
+ "Duplicate ",
+ object.getModel().name,
+ " to a location"
+ ].join("");
+
+ label = "Duplicate To";
+
+ validateLocation = function (newParent) {
+ return copyService
+ .validate(object, newParent);
+ };
+
+ return locationService.getLocationFromUser(
+ dialogTitle,
+ label,
+ validateLocation,
+ currentParent
+ ).then(function (newParent) {
+ return copyService
+ .perform(object, newParent);
+ });
+ }
+ };
+ }
+
+ return CopyAction;
+ }
+);
diff --git a/platform/entanglement/src/actions/LinkAction.js b/platform/entanglement/src/actions/LinkAction.js
new file mode 100644
index 0000000000..c6b4be100f
--- /dev/null
+++ b/platform/entanglement/src/actions/LinkAction.js
@@ -0,0 +1,89 @@
+/*****************************************************************************
+ * 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 () {
+ "use strict";
+
+ /**
+ * The LinkAction is available from context menus and allows a user to
+ * link an object to another location of their choosing.
+ *
+ * @implements Action
+ */
+ function LinkAction(locationService, linkService, context) {
+
+ var object,
+ newParent,
+ currentParent;
+
+ if (context.selectedObject) {
+ newParent = context.domainObject;
+ object = context.selectedObject;
+ } else {
+ object = context.domainObject;
+ }
+
+ currentParent = object
+ .getCapability('context')
+ .getParent();
+
+ return {
+ perform: function () {
+ if (newParent) {
+ return linkService
+ .perform(object, newParent);
+ }
+ var dialogTitle,
+ label,
+ validateLocation;
+
+ dialogTitle = [
+ "Link ",
+ object.getModel().name,
+ " to a new location"
+ ].join("");
+
+ label = "Link To";
+
+ validateLocation = function (newParent) {
+ return linkService
+ .validate(object, newParent);
+ };
+
+ return locationService.getLocationFromUser(
+ dialogTitle,
+ label,
+ validateLocation,
+ currentParent
+ ).then(function (newParent) {
+ return linkService
+ .perform(object, newParent);
+ });
+ }
+ };
+ }
+
+ return LinkAction;
+ }
+);
diff --git a/platform/entanglement/src/actions/MoveAction.js b/platform/entanglement/src/actions/MoveAction.js
new file mode 100644
index 0000000000..63f1517c56
--- /dev/null
+++ b/platform/entanglement/src/actions/MoveAction.js
@@ -0,0 +1,90 @@
+/*****************************************************************************
+ * 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 () {
+ "use strict";
+
+ /**
+ * The MoveAction is available from context menus and allows a user to
+ * move an object to another location of their choosing.
+ *
+ * @implements Action
+ */
+ function MoveAction(locationService, moveService, context) {
+
+ var object,
+ newParent,
+ currentParent;
+
+ if (context.selectedObject) {
+ newParent = context.domainObject;
+ object = context.selectedObject;
+ } else {
+ object = context.domainObject;
+ }
+
+ currentParent = object
+ .getCapability('context')
+ .getParent();
+
+ return {
+ perform: function () {
+ if (newParent) {
+ return moveService
+ .perform(object, newParent);
+ }
+
+ var dialogTitle,
+ label,
+ validateLocation;
+
+ dialogTitle = [
+ "Move ",
+ object.getModel().name,
+ " to a new location"
+ ].join("");
+
+ label = "Move To";
+
+ validateLocation = function (newParent) {
+ return moveService
+ .validate(object, newParent);
+ };
+
+ return locationService.getLocationFromUser(
+ dialogTitle,
+ label,
+ validateLocation,
+ currentParent
+ ).then(function (newParent) {
+ return moveService
+ .perform(object, newParent);
+ });
+ }
+ };
+ }
+
+ return MoveAction;
+ }
+);
diff --git a/platform/entanglement/src/services/CopyService.js b/platform/entanglement/src/services/CopyService.js
new file mode 100644
index 0000000000..487568c475
--- /dev/null
+++ b/platform/entanglement/src/services/CopyService.js
@@ -0,0 +1,106 @@
+/*****************************************************************************
+ * 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 () {
+ "use strict";
+
+ /**
+ * CopyService provides an interface for deep copying objects from one
+ * location to another. It also provides a method for determining if
+ * an object can be copied to a specific location.
+ */
+ function CopyService($q, creationService, policyService) {
+
+ /**
+ * duplicateObject duplicates a `domainObject` into the composition
+ * of `parent`, and then duplicates the composition of
+ * `domainObject` into the new object.
+ *
+ * This function is a recursive deep copy.
+ *
+ * @param {DomainObject} domainObject - the domain object to
+ * duplicate.
+ * @param {DomainObject} parent - the parent domain object to
+ * create the duplicate in.
+ * @returns {Promise} A promise that is fulfilled when the
+ * duplicate operation has completed.
+ */
+ function duplicateObject(domainObject, parent) {
+ var model = JSON.parse(JSON.stringify(domainObject.getModel()));
+ if (domainObject.hasCapability('composition')) {
+ model.composition = [];
+ }
+
+ return creationService
+ .createObject(model, parent)
+ .then(function (newObject) {
+ if (!domainObject.hasCapability('composition')) {
+ return;
+ }
+
+ return domainObject
+ .useCapability('composition')
+ .then(function (composees) {
+ // Duplicate composition serially to prevent
+ // write conflicts.
+ return composees.reduce(function (promise, composee) {
+ return promise.then(function () {
+ return duplicateObject(composee, newObject);
+ });
+ }, $q.when(undefined));
+ });
+ });
+ }
+
+ return {
+ /**
+ * Returns true if `object` can be copied into
+ * `parentCandidate`'s composition.
+ */
+ validate: function (object, parentCandidate) {
+ if (!parentCandidate || !parentCandidate.getId) {
+ return false;
+ }
+ if (parentCandidate.getId() === object.getId()) {
+ return false;
+ }
+ return policyService.allow(
+ "composition",
+ parentCandidate.getCapability('type'),
+ object.getCapability('type')
+ );
+ },
+ /**
+ * Wrapper, @see {@link duplicateObject} for implementation.
+ */
+ perform: function (object, parentObject) {
+ return duplicateObject(object, parentObject);
+ }
+ };
+ }
+
+ return CopyService;
+ }
+);
diff --git a/platform/entanglement/src/services/LinkService.js b/platform/entanglement/src/services/LinkService.js
new file mode 100644
index 0000000000..acecab3b8d
--- /dev/null
+++ b/platform/entanglement/src/services/LinkService.js
@@ -0,0 +1,76 @@
+/*****************************************************************************
+ * 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 () {
+ "use strict";
+
+ /**
+ * LinkService provides an interface for linking objects to additional
+ * locations. It also provides a method for determining if an object
+ * can be copied to a specific location.
+ */
+ function LinkService(policyService) {
+ return {
+ /**
+ * Returns `true` if `object` can be linked into
+ * `parentCandidate`'s composition.
+ */
+ validate: function (object, parentCandidate) {
+ if (!parentCandidate || !parentCandidate.getId) {
+ return false;
+ }
+ if (parentCandidate.getId() === object.getId()) {
+ return false;
+ }
+ if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
+ return false;
+ }
+ return policyService.allow(
+ "composition",
+ parentCandidate.getCapability('type'),
+ object.getCapability('type')
+ );
+ },
+ /**
+ * Link `object` into `parentObject`'s composition.
+ *
+ * @returns {Promise} A promise that is fulfilled when the
+ * linking operation has completed.
+ */
+ perform: function (object, parentObject) {
+ return parentObject.useCapability('mutation', function (model) {
+ if (model.composition.indexOf(object.getId()) === -1) {
+ model.composition.push(object.getId());
+ }
+ }).then(function () {
+ return parentObject.getCapability('persistence').persist();
+ });
+ }
+ };
+ }
+
+ return LinkService;
+ }
+);
diff --git a/platform/entanglement/src/services/LocationService.js b/platform/entanglement/src/services/LocationService.js
new file mode 100644
index 0000000000..3e71011503
--- /dev/null
+++ b/platform/entanglement/src/services/LocationService.js
@@ -0,0 +1,83 @@
+/*****************************************************************************
+ * 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 () {
+ "use strict";
+
+ /**
+ * The LocationService allows for easily prompting the user for a
+ * location in the root tree.
+ */
+ function LocationService(dialogService) {
+ return {
+ /** Prompt the user to select a location. Returns a promise
+ * that is resolved with a domainObject representing the
+ * location selected by the user.
+ *
+ * @param {string} title - title of location dialog
+ * @param {string} label - label for location input field
+ * @param {function} validate - function that validates
+ * selections.
+ * @param {domainObject} initialLocation - tree location to
+ * display at start
+ * @returns {Promise} promise for a domain object.
+ */
+ getLocationFromUser: function (title, label, validate, initialLocation) {
+ var formStructure,
+ formState;
+
+ formStructure = {
+ sections: [
+ {
+ name: 'Location',
+ rows: [
+ {
+ name: label,
+ control: "locator",
+ validate: validate,
+ key: 'location'
+ }
+ ]
+ }
+ ],
+ name: title
+ };
+
+ formState = {
+ location: initialLocation
+ };
+
+ return dialogService
+ .getUserInput(formStructure, formState)
+ .then(function (formState) {
+ return formState.location;
+ });
+ }
+ };
+ }
+
+ return LocationService;
+ }
+);
diff --git a/platform/entanglement/src/services/MoveService.js b/platform/entanglement/src/services/MoveService.js
new file mode 100644
index 0000000000..e91d381453
--- /dev/null
+++ b/platform/entanglement/src/services/MoveService.js
@@ -0,0 +1,83 @@
+/*****************************************************************************
+ * 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 () {
+ "use strict";
+
+ /**
+ * MoveService provides an interface for moving objects from one
+ * location to another. It also provides a method for determining if
+ * an object can be copied to a specific location.
+ */
+ function MoveService(policyService, linkService) {
+ return {
+ /**
+ * Returns `true` if `object` can be moved into
+ * `parentCandidate`'s composition.
+ */
+ validate: function (object, parentCandidate) {
+ var currentParent = object
+ .getCapability('context')
+ .getParent();
+
+ if (!parentCandidate || !parentCandidate.getId) {
+ return false;
+ }
+ if (parentCandidate.getId() === currentParent.getId()) {
+ return false;
+ }
+ if (parentCandidate.getId() === object.getId()) {
+ return false;
+ }
+ if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
+ return false;
+ }
+ return policyService.allow(
+ "composition",
+ parentCandidate.getCapability('type'),
+ object.getCapability('type')
+ );
+ },
+ /**
+ * Move `object` into `parentObject`'s composition.
+ *
+ * @returns {Promise} A promise that is fulfilled when the
+ * move operation has completed.
+ */
+ perform: function (object, parentObject) {
+ return linkService
+ .perform(object, parentObject)
+ .then(function () {
+ return object
+ .getCapability('action')
+ .perform('remove');
+ });
+ }
+ };
+ }
+
+ return MoveService;
+ }
+);
diff --git a/platform/entanglement/test/DomainObjectFactory.js b/platform/entanglement/test/DomainObjectFactory.js
new file mode 100644
index 0000000000..3f62841e42
--- /dev/null
+++ b/platform/entanglement/test/DomainObjectFactory.js
@@ -0,0 +1,158 @@
+/*****************************************************************************
+ * 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, jasmine, */
+
+define(
+ function () {
+ "use strict";
+
+ /**
+ * @typedef DomainObjectConfig
+ * @type {object}
+ * @property {string} [name] a name for the underlying jasmine spy
+ * object mockDomainObject. Used as
+ * @property {string} [id] initial id value for the domainOBject.
+ * @property {object} [model] initial values for the object's model.
+ * @property {object} [capabilities] an object containing
+ * capability definitions.
+ */
+
+ var configObjectProps = ['model', 'capabilities'];
+
+ /**
+ * Internal function for ensuring an object is an instance of a
+ * DomainObjectConfig.
+ */
+ function ensureValidConfigObject(config) {
+ if (!config || !config.hasOwnProperty) {
+ config = {};
+ }
+ if (!config.name) {
+ config.name = 'domainObject';
+ }
+ configObjectProps.forEach(function (prop) {
+ if (!config[prop] || !config[prop].hasOwnProperty) {
+ config[prop] = {};
+ }
+ });
+ return config;
+ }
+
+ /**
+ * Defines a factory function which takes a `config` object and returns
+ * a mock domainObject. The config object is an easy way to provide
+ * initial properties for the domainObject-- they can be changed at any
+ * time by directly modifying the domainObject's properties.
+ *
+ * @param {Object} [config] initial configuration for a domain object.
+ * @returns {Object} mockDomainObject
+ */
+ function domainObjectFactory(config) {
+ config = ensureValidConfigObject(config);
+
+ var domainObject = jasmine.createSpyObj(config.name, [
+ 'getId',
+ 'getModel',
+ 'getCapability',
+ 'hasCapability',
+ 'useCapability'
+ ]);
+
+ domainObject.model = JSON.parse(JSON.stringify(config.model));
+ domainObject.capabilities = config.capabilities;
+ domainObject.id = config.id;
+
+ /**
+ * getId: Returns `domainObject.id`.
+ *
+ * @returns {string} id
+ */
+ domainObject.getId.andCallFake(function () {
+ return domainObject.id;
+ });
+
+ /**
+ * getModel: Returns `domainObject.model`.
+ *
+ * @returns {object} model
+ */
+ domainObject.getModel.andCallFake(function () {
+ return domainObject.model;
+ });
+
+ /**
+ * getCapability: returns a `capability` object defined in
+ * domainObject.capabilities. Returns undefined if capability
+ * does not exist.
+ *
+ * @param {string} capability name of the capability to return.
+ * @returns {*} capability object
+ */
+ domainObject.getCapability.andCallFake(function (capability) {
+ if (config.capabilities.hasOwnProperty(capability)) {
+ return config.capabilities[capability];
+ }
+ });
+
+ /**
+ * hasCapability: return true if domainObject.capabilities has a
+ * property named `capability`, otherwise returns false.
+ *
+ * @param {string} capability name of the capability to test for
+ * existence of.
+ * @returns {boolean}
+ */
+ domainObject.hasCapability.andCallFake(function (capability) {
+ return config.capabilities.hasOwnProperty(capability);
+ });
+
+ /**
+ * useCapability: find a capability in domainObject.capabilities
+ * and call that capabilities' invoke method. If the capability
+ * does not have an invoke method, will throw an error.
+ *
+ * @param {string} capability name of a capability to invoke.
+ * @param {...*} params to pass to the capability's `invoke` method.
+ * @returns {*} result whatever was returned by `invoke`.
+ */
+ domainObject.useCapability.andCallFake(function (capability) {
+ if (config.capabilities.hasOwnProperty(capability)) {
+ if (!config.capabilities[capability].invoke) {
+ throw new Error(
+ capability + ' missing invoke function.'
+ );
+ }
+ var passThroughArgs = [].slice.call(arguments, 1);
+ return config
+ .capabilities[capability]
+ .invoke
+ .apply(null, passThroughArgs);
+ }
+ });
+
+ return domainObject;
+ }
+
+ return domainObjectFactory;
+ }
+);
\ No newline at end of file
diff --git a/platform/entanglement/test/actions/CopyActionSpec.js b/platform/entanglement/test/actions/CopyActionSpec.js
new file mode 100644
index 0000000000..4284a22e59
--- /dev/null
+++ b/platform/entanglement/test/actions/CopyActionSpec.js
@@ -0,0 +1,174 @@
+/*****************************************************************************
+ * 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,beforeEach,it,jasmine,expect */
+
+define(
+ [
+ '../../src/actions/CopyAction',
+ '../services/MockCopyService',
+ '../DomainObjectFactory'
+ ],
+ function (CopyAction, MockCopyService, domainObjectFactory) {
+ "use strict";
+
+ describe("Copy Action", function () {
+
+ var copyAction,
+ locationService,
+ locationServicePromise,
+ copyService,
+ context,
+ selectedObject,
+ selectedObjectContextCapability,
+ currentParent,
+ newParent;
+
+ beforeEach(function () {
+ selectedObjectContextCapability = jasmine.createSpyObj(
+ 'selectedObjectContextCapability',
+ [
+ 'getParent'
+ ]
+ );
+
+ selectedObject = domainObjectFactory({
+ name: 'selectedObject',
+ model: {
+ name: 'selectedObject'
+ },
+ capabilities: {
+ context: selectedObjectContextCapability
+ }
+ });
+
+ currentParent = domainObjectFactory({
+ name: 'currentParent'
+ });
+
+ selectedObjectContextCapability
+ .getParent
+ .andReturn(currentParent);
+
+ newParent = domainObjectFactory({
+ name: 'newParent'
+ });
+
+ locationService = jasmine.createSpyObj(
+ 'locationService',
+ [
+ 'getLocationFromUser'
+ ]
+ );
+
+ locationServicePromise = jasmine.createSpyObj(
+ 'locationServicePromise',
+ [
+ 'then'
+ ]
+ );
+
+ locationService
+ .getLocationFromUser
+ .andReturn(locationServicePromise);
+
+ copyService = new MockCopyService();
+ });
+
+
+ describe("with context from context-action", function () {
+ beforeEach(function () {
+ context = {
+ domainObject: selectedObject
+ };
+
+ copyAction = new CopyAction(
+ locationService,
+ copyService,
+ context
+ );
+ });
+
+ it("initializes happily", function () {
+ expect(copyAction).toBeDefined();
+ });
+
+ describe("when performed it", function () {
+ beforeEach(function () {
+ copyAction.perform();
+ });
+
+ it("prompts for location", function () {
+ expect(locationService.getLocationFromUser)
+ .toHaveBeenCalledWith(
+ "Duplicate selectedObject to a location",
+ "Duplicate To",
+ jasmine.any(Function),
+ currentParent
+ );
+ });
+
+ it("waits for location from user", function () {
+ expect(locationServicePromise.then)
+ .toHaveBeenCalledWith(jasmine.any(Function));
+ });
+
+ it("copys object to selected location", function () {
+ locationServicePromise
+ .then
+ .mostRecentCall
+ .args[0](newParent);
+
+ expect(copyService.perform)
+ .toHaveBeenCalledWith(selectedObject, newParent);
+ });
+ });
+ });
+
+ describe("with context from drag-drop", function () {
+ beforeEach(function () {
+ context = {
+ selectedObject: selectedObject,
+ domainObject: newParent
+ };
+
+ copyAction = new CopyAction(
+ locationService,
+ copyService,
+ context
+ );
+ });
+
+ it("initializes happily", function () {
+ expect(copyAction).toBeDefined();
+ });
+
+
+ it("performs copy immediately", function () {
+ copyAction.perform();
+ expect(copyService.perform)
+ .toHaveBeenCalledWith(selectedObject, newParent);
+ });
+ });
+ });
+ }
+);
diff --git a/platform/entanglement/test/actions/LinkActionSpec.js b/platform/entanglement/test/actions/LinkActionSpec.js
new file mode 100644
index 0000000000..03967a6672
--- /dev/null
+++ b/platform/entanglement/test/actions/LinkActionSpec.js
@@ -0,0 +1,174 @@
+/*****************************************************************************
+ * 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,beforeEach,it,jasmine,expect */
+
+define(
+ [
+ '../../src/actions/LinkAction',
+ '../services/MockLinkService',
+ '../DomainObjectFactory'
+ ],
+ function (LinkAction, MockLinkService, domainObjectFactory) {
+ "use strict";
+
+ describe("Link Action", function () {
+
+ var linkAction,
+ locationService,
+ locationServicePromise,
+ linkService,
+ context,
+ selectedObject,
+ selectedObjectContextCapability,
+ currentParent,
+ newParent;
+
+ beforeEach(function () {
+ selectedObjectContextCapability = jasmine.createSpyObj(
+ 'selectedObjectContextCapability',
+ [
+ 'getParent'
+ ]
+ );
+
+ selectedObject = domainObjectFactory({
+ name: 'selectedObject',
+ model: {
+ name: 'selectedObject'
+ },
+ capabilities: {
+ context: selectedObjectContextCapability
+ }
+ });
+
+ currentParent = domainObjectFactory({
+ name: 'currentParent'
+ });
+
+ selectedObjectContextCapability
+ .getParent
+ .andReturn(currentParent);
+
+ newParent = domainObjectFactory({
+ name: 'newParent'
+ });
+
+ locationService = jasmine.createSpyObj(
+ 'locationService',
+ [
+ 'getLocationFromUser'
+ ]
+ );
+
+ locationServicePromise = jasmine.createSpyObj(
+ 'locationServicePromise',
+ [
+ 'then'
+ ]
+ );
+
+ locationService
+ .getLocationFromUser
+ .andReturn(locationServicePromise);
+
+ linkService = new MockLinkService();
+ });
+
+
+ describe("with context from context-action", function () {
+ beforeEach(function () {
+ context = {
+ domainObject: selectedObject
+ };
+
+ linkAction = new LinkAction(
+ locationService,
+ linkService,
+ context
+ );
+ });
+
+ it("initializes happily", function () {
+ expect(linkAction).toBeDefined();
+ });
+
+ describe("when performed it", function () {
+ beforeEach(function () {
+ linkAction.perform();
+ });
+
+ it("prompts for location", function () {
+ expect(locationService.getLocationFromUser)
+ .toHaveBeenCalledWith(
+ "Link selectedObject to a new location",
+ "Link To",
+ jasmine.any(Function),
+ currentParent
+ );
+ });
+
+ it("waits for location from user", function () {
+ expect(locationServicePromise.then)
+ .toHaveBeenCalledWith(jasmine.any(Function));
+ });
+
+ it("links object to selected location", function () {
+ locationServicePromise
+ .then
+ .mostRecentCall
+ .args[0](newParent);
+
+ expect(linkService.perform)
+ .toHaveBeenCalledWith(selectedObject, newParent);
+ });
+ });
+ });
+
+ describe("with context from drag-drop", function () {
+ beforeEach(function () {
+ context = {
+ selectedObject: selectedObject,
+ domainObject: newParent
+ };
+
+ linkAction = new LinkAction(
+ locationService,
+ linkService,
+ context
+ );
+ });
+
+ it("initializes happily", function () {
+ expect(linkAction).toBeDefined();
+ });
+
+
+ it("performs link immediately", function () {
+ linkAction.perform();
+ expect(linkService.perform)
+ .toHaveBeenCalledWith(selectedObject, newParent);
+ });
+ });
+ });
+ }
+);
diff --git a/platform/entanglement/test/actions/MoveActionSpec.js b/platform/entanglement/test/actions/MoveActionSpec.js
new file mode 100644
index 0000000000..52a7c6e301
--- /dev/null
+++ b/platform/entanglement/test/actions/MoveActionSpec.js
@@ -0,0 +1,174 @@
+/*****************************************************************************
+ * 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,beforeEach,it,jasmine,expect */
+
+define(
+ [
+ '../../src/actions/MoveAction',
+ '../services/MockMoveService',
+ '../DomainObjectFactory'
+ ],
+ function (MoveAction, MockMoveService, domainObjectFactory) {
+ "use strict";
+
+ describe("Move Action", function () {
+
+ var moveAction,
+ locationService,
+ locationServicePromise,
+ moveService,
+ context,
+ selectedObject,
+ selectedObjectContextCapability,
+ currentParent,
+ newParent;
+
+ beforeEach(function () {
+ selectedObjectContextCapability = jasmine.createSpyObj(
+ 'selectedObjectContextCapability',
+ [
+ 'getParent'
+ ]
+ );
+
+ selectedObject = domainObjectFactory({
+ name: 'selectedObject',
+ model: {
+ name: 'selectedObject'
+ },
+ capabilities: {
+ context: selectedObjectContextCapability
+ }
+ });
+
+ currentParent = domainObjectFactory({
+ name: 'currentParent'
+ });
+
+ selectedObjectContextCapability
+ .getParent
+ .andReturn(currentParent);
+
+ newParent = domainObjectFactory({
+ name: 'newParent'
+ });
+
+ locationService = jasmine.createSpyObj(
+ 'locationService',
+ [
+ 'getLocationFromUser'
+ ]
+ );
+
+ locationServicePromise = jasmine.createSpyObj(
+ 'locationServicePromise',
+ [
+ 'then'
+ ]
+ );
+
+ locationService
+ .getLocationFromUser
+ .andReturn(locationServicePromise);
+
+ moveService = new MockMoveService();
+ });
+
+
+ describe("with context from context-action", function () {
+ beforeEach(function () {
+ context = {
+ domainObject: selectedObject
+ };
+
+ moveAction = new MoveAction(
+ locationService,
+ moveService,
+ context
+ );
+ });
+
+ it("initializes happily", function () {
+ expect(moveAction).toBeDefined();
+ });
+
+ describe("when performed it", function () {
+ beforeEach(function () {
+ moveAction.perform();
+ });
+
+ it("prompts for location", function () {
+ expect(locationService.getLocationFromUser)
+ .toHaveBeenCalledWith(
+ "Move selectedObject to a new location",
+ "Move To",
+ jasmine.any(Function),
+ currentParent
+ );
+ });
+
+ it("waits for location from user", function () {
+ expect(locationServicePromise.then)
+ .toHaveBeenCalledWith(jasmine.any(Function));
+ });
+
+ it("moves object to selected location", function () {
+ locationServicePromise
+ .then
+ .mostRecentCall
+ .args[0](newParent);
+
+ expect(moveService.perform)
+ .toHaveBeenCalledWith(selectedObject, newParent);
+ });
+ });
+ });
+
+ describe("with context from drag-drop", function () {
+ beforeEach(function () {
+ context = {
+ selectedObject: selectedObject,
+ domainObject: newParent
+ };
+
+ moveAction = new MoveAction(
+ locationService,
+ moveService,
+ context
+ );
+ });
+
+ it("initializes happily", function () {
+ expect(moveAction).toBeDefined();
+ });
+
+
+ it("performs move immediately", function () {
+ moveAction.perform();
+ expect(moveService.perform)
+ .toHaveBeenCalledWith(selectedObject, newParent);
+ });
+ });
+ });
+ }
+);
diff --git a/platform/entanglement/test/services/CopyServiceSpec.js b/platform/entanglement/test/services/CopyServiceSpec.js
new file mode 100644
index 0000000000..6d7b1d5069
--- /dev/null
+++ b/platform/entanglement/test/services/CopyServiceSpec.js
@@ -0,0 +1,272 @@
+/*****************************************************************************
+ * 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,beforeEach,it,jasmine,expect,spyOn */
+
+define(
+ [
+ '../../src/services/CopyService',
+ '../DomainObjectFactory'
+ ],
+ function (CopyService, domainObjectFactory) {
+ "use strict";
+
+ function synchronousPromise(value) {
+ var promise = {
+ then: function (callback) {
+ return synchronousPromise(callback(value));
+ }
+ };
+ spyOn(promise, 'then').andCallThrough();
+ return promise;
+ }
+
+ describe("CopyService", function () {
+ describe("validate", function () {
+
+ var policyService,
+ copyService,
+ object,
+ parentCandidate,
+ validate;
+
+ beforeEach(function () {
+ policyService = jasmine.createSpyObj(
+ 'policyService',
+ ['allow']
+ );
+ copyService = new CopyService(
+ null,
+ null,
+ policyService
+ );
+ object = domainObjectFactory({
+ name: 'object',
+ capabilities: {
+ type: { type: 'object' }
+ }
+ });
+ parentCandidate = domainObjectFactory({
+ name: 'parentCandidate',
+ capabilities: {
+ type: { type: 'parentCandidate' }
+ }
+ });
+ validate = function () {
+ return copyService.validate(object, parentCandidate);
+ };
+ });
+
+ it("does not allow invalid parentCandidate", function () {
+ parentCandidate = undefined;
+ expect(validate()).toBe(false);
+ parentCandidate = {};
+ expect(validate()).toBe(false);
+ });
+
+ it("does not allow copying into source object", function () {
+ object.id = parentCandidate.id = 'abc';
+ expect(validate()).toBe(false);
+ });
+
+ describe("defers to policyService", function () {
+ beforeEach(function () {
+ object.id = 'a';
+ parentCandidate.id = 'b';
+ });
+
+ it("calls policy service with correct args", function () {
+ validate();
+ expect(policyService.allow).toHaveBeenCalledWith(
+ "composition",
+ parentCandidate.capabilities.type,
+ object.capabilities.type
+ );
+ });
+
+ it("and returns false", function () {
+ policyService.allow.andReturn(false);
+ expect(validate()).toBe(false);
+ });
+
+ it("and returns true", function () {
+ policyService.allow.andReturn(true);
+ expect(validate()).toBe(true);
+ });
+ });
+ });
+
+ describe("perform", function () {
+
+ var mockQ,
+ creationService,
+ createObjectPromise,
+ copyService,
+ object,
+ newParent,
+ copyResult,
+ copyFinished;
+
+ describe("on domain object without composition", function () {
+ beforeEach(function () {
+ object = domainObjectFactory({
+ name: 'object',
+ id: 'abc',
+ model: {
+ name: 'some object'
+ }
+ });
+ newParent = domainObjectFactory({
+ name: 'newParent',
+ id: '456',
+ model: {
+ composition: []
+ }
+ });
+ creationService = jasmine.createSpyObj(
+ 'creationService',
+ ['createObject']
+ );
+ createObjectPromise = synchronousPromise(undefined);
+ creationService.createObject.andReturn(createObjectPromise);
+ copyService = new CopyService(null, creationService);
+ copyResult = copyService.perform(object, newParent);
+ copyFinished = jasmine.createSpy('copyFinished');
+ copyResult.then(copyFinished);
+ });
+
+ it("uses creation service", function () {
+ expect(creationService.createObject)
+ .toHaveBeenCalledWith(jasmine.any(Object), newParent);
+
+ expect(createObjectPromise.then)
+ .toHaveBeenCalledWith(jasmine.any(Function));
+ });
+
+ it("deep clones object model", function () {
+ var newModel = creationService
+ .createObject
+ .mostRecentCall
+ .args[0];
+
+ expect(newModel).toEqual(object.model);
+ expect(newModel).not.toBe(object.model);
+ });
+
+ it("returns a promise", function () {
+ expect(copyResult).toBeDefined();
+ expect(copyFinished).toHaveBeenCalled();
+ });
+
+ });
+
+ describe("on domainObject with composition", function () {
+ var childObject,
+ compositionCapability,
+ compositionPromise;
+
+ beforeEach(function () {
+ mockQ = jasmine.createSpyObj('mockQ', ['when']);
+ mockQ.when.andCallFake(synchronousPromise);
+ childObject = domainObjectFactory({
+ name: 'childObject',
+ id: 'def',
+ model: {
+ name: 'a child object'
+ }
+ });
+ compositionCapability = jasmine.createSpyObj(
+ 'compositionCapability',
+ ['invoke']
+ );
+ compositionPromise = jasmine.createSpyObj(
+ 'compositionPromise',
+ ['then']
+ );
+ compositionCapability
+ .invoke
+ .andReturn(compositionPromise);
+ object = domainObjectFactory({
+ name: 'object',
+ id: 'abc',
+ model: {
+ name: 'some object',
+ composition: ['def']
+ },
+ capabilities: {
+ composition: compositionCapability
+ }
+ });
+ newParent = domainObjectFactory({
+ name: 'newParent',
+ id: '456',
+ model: {
+ composition: []
+ }
+ });
+ creationService = jasmine.createSpyObj(
+ 'creationService',
+ ['createObject']
+ );
+ createObjectPromise = synchronousPromise(undefined);
+ creationService.createObject.andReturn(createObjectPromise);
+ copyService = new CopyService(mockQ, creationService);
+ copyResult = copyService.perform(object, newParent);
+ copyFinished = jasmine.createSpy('copyFinished');
+ copyResult.then(copyFinished);
+ });
+
+ it("uses creation service", function () {
+ expect(creationService.createObject)
+ .toHaveBeenCalledWith(jasmine.any(Object), newParent);
+
+ expect(createObjectPromise.then)
+ .toHaveBeenCalledWith(jasmine.any(Function));
+ });
+
+ it("clears model composition", function () {
+ var newModel = creationService
+ .createObject
+ .mostRecentCall
+ .args[0];
+
+ expect(newModel.composition.length).toBe(0);
+ expect(newModel.name).toBe('some object');
+ });
+
+ it("recursively clones it's children", function () {
+ expect(creationService.createObject.calls.length).toBe(1);
+ expect(compositionCapability.invoke).toHaveBeenCalled();
+ compositionPromise.then.mostRecentCall.args[0]([childObject]);
+ expect(creationService.createObject.calls.length).toBe(2);
+ });
+
+ it("returns a promise", function () {
+ expect(copyResult.then).toBeDefined();
+ expect(copyFinished).toHaveBeenCalled();
+ });
+ });
+
+ });
+ });
+ }
+);
diff --git a/platform/entanglement/test/services/LinkServiceSpec.js b/platform/entanglement/test/services/LinkServiceSpec.js
new file mode 100644
index 0000000000..0551256277
--- /dev/null
+++ b/platform/entanglement/test/services/LinkServiceSpec.js
@@ -0,0 +1,183 @@
+/*****************************************************************************
+ * 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,beforeEach,it,jasmine,expect */
+
+define(
+ [
+ '../../src/services/LinkService',
+ '../DomainObjectFactory'
+ ],
+ function (LinkService, domainObjectFactory) {
+ "use strict";
+
+ describe("LinkService", function () {
+
+ var linkService,
+ mockPolicyService;
+
+ beforeEach(function () {
+ mockPolicyService = jasmine.createSpyObj(
+ 'policyService',
+ ['allow']
+ );
+ linkService = new LinkService(mockPolicyService);
+ });
+
+ describe("validate", function () {
+
+ var object,
+ parentCandidate,
+ validate;
+
+ beforeEach(function () {
+
+ object = domainObjectFactory({
+ name: 'object'
+ });
+ parentCandidate = domainObjectFactory({
+ name: 'parentCandidate'
+ });
+ validate = function () {
+ return linkService.validate(object, parentCandidate);
+ };
+ });
+
+ it("does not allow invalid parentCandidate", function () {
+ parentCandidate = undefined;
+ expect(validate()).toBe(false);
+ parentCandidate = {};
+ expect(validate()).toBe(false);
+ });
+
+ it("does not allow parent to be object", function () {
+ parentCandidate.id = object.id = 'abc';
+ expect(validate()).toBe(false);
+ });
+
+ it("does not allow parent that contains object", function () {
+ object.id = 'abc';
+ parentCandidate.id = 'xyz';
+ parentCandidate.model.composition = ['abc'];
+ expect(validate()).toBe(false);
+ });
+
+ describe("defers to policyService", function () {
+ beforeEach(function () {
+ object.id = 'abc';
+ object.capabilities.type = { type: 'object' };
+ parentCandidate.id = 'xyz';
+ parentCandidate.capabilities.type = {
+ type: 'parentCandidate'
+ };
+ parentCandidate.model.composition = [];
+ });
+
+ it("calls policy service with correct args", function () {
+ validate();
+ expect(mockPolicyService.allow).toHaveBeenCalledWith(
+ "composition",
+ parentCandidate.capabilities.type,
+ object.capabilities.type
+ );
+ });
+
+ it("and returns false", function () {
+ mockPolicyService.allow.andReturn(true);
+ expect(validate()).toBe(true);
+ expect(mockPolicyService.allow).toHaveBeenCalled();
+ });
+
+ it("and returns true", function () {
+ mockPolicyService.allow.andReturn(false);
+ expect(validate()).toBe(false);
+ expect(mockPolicyService.allow).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe("perform", function () {
+
+ var object,
+ parentModel,
+ parentObject,
+ mutationPromise,
+ persistenceCapability;
+
+ beforeEach(function () {
+ mutationPromise = jasmine.createSpyObj(
+ 'promise',
+ ['then']
+ );
+ persistenceCapability = jasmine.createSpyObj(
+ 'persistenceCapability',
+ ['persist']
+ );
+ parentModel = {
+ composition: []
+ };
+ parentObject = domainObjectFactory({
+ name: 'parentObject',
+ model: parentModel,
+ capabilities: {
+ mutation: {
+ invoke: function (mutator) {
+ mutator(parentModel);
+ return mutationPromise;
+ }
+ },
+ persistence: persistenceCapability
+ }
+ });
+
+ object = domainObjectFactory({
+ name: 'object',
+ id: 'xyz'
+ });
+
+ parentObject.getCapability.andReturn(persistenceCapability);
+ });
+
+
+ it("modifies parent model composition", function () {
+ expect(parentModel.composition.length).toBe(0);
+ linkService.perform(object, parentObject);
+ expect(parentObject.useCapability).toHaveBeenCalledWith(
+ 'mutation',
+ jasmine.any(Function)
+ );
+ expect(parentModel.composition).toContain('xyz');
+ });
+
+ it("persists parent", function () {
+ linkService.perform(object, parentObject);
+ expect(mutationPromise.then).toHaveBeenCalled();
+ mutationPromise.then.calls[0].args[0]();
+ expect(parentObject.getCapability)
+ .toHaveBeenCalledWith('persistence');
+
+ expect(persistenceCapability.persist).toHaveBeenCalled();
+ });
+ });
+ });
+ }
+);
diff --git a/platform/entanglement/test/services/LocationServiceSpec.js b/platform/entanglement/test/services/LocationServiceSpec.js
new file mode 100644
index 0000000000..c469392f23
--- /dev/null
+++ b/platform/entanglement/test/services/LocationServiceSpec.js
@@ -0,0 +1,151 @@
+/*****************************************************************************
+ * 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,beforeEach,it,jasmine,expect */
+
+define(
+ [
+ '../../src/services/LocationService'
+ ],
+ function (LocationService) {
+ "use strict";
+
+ describe("LocationService", function () {
+ var dialogService,
+ locationService,
+ dialogServicePromise,
+ chainedPromise;
+
+ beforeEach(function () {
+ dialogService = jasmine.createSpyObj(
+ 'dialogService',
+ ['getUserInput']
+ );
+ dialogServicePromise = jasmine.createSpyObj(
+ 'dialogServicePromise',
+ ['then']
+ );
+ chainedPromise = jasmine.createSpyObj(
+ 'chainedPromise',
+ ['then']
+ );
+ dialogServicePromise.then.andReturn(chainedPromise);
+ dialogService.getUserInput.andReturn(dialogServicePromise);
+ locationService = new LocationService(dialogService);
+ });
+
+ describe("getLocationFromUser", function () {
+ var title,
+ label,
+ validate,
+ initialLocation,
+ locationResult,
+ formStructure,
+ formState;
+
+ beforeEach(function () {
+ title = "Get a location to do something";
+ label = "a location";
+ validate = function () { return true; };
+ initialLocation = { key: "a key" };
+ locationResult = locationService.getLocationFromUser(
+ title,
+ label,
+ validate,
+ initialLocation
+ );
+ formStructure = dialogService
+ .getUserInput
+ .mostRecentCall
+ .args[0];
+ formState = dialogService
+ .getUserInput
+ .mostRecentCall
+ .args[1];
+ });
+
+ it("calls through to dialogService", function () {
+ expect(dialogService.getUserInput).toHaveBeenCalledWith(
+ jasmine.any(Object),
+ jasmine.any(Object)
+ );
+ expect(formStructure.name).toBe(title);
+ });
+
+ it("returns a promise", function () {
+ expect(locationResult.then).toBeDefined();
+ });
+
+ describe("formStructure", function () {
+ var locationSection,
+ inputRow;
+
+ beforeEach(function () {
+ locationSection = formStructure.sections[0];
+ inputRow = locationSection.rows[0];
+ });
+
+ it("has a location section", function () {
+ expect(locationSection).toBeDefined();
+ expect(locationSection.name).toBe('Location');
+ });
+
+ it("has a input row", function () {
+ expect(inputRow.control).toBe('locator');
+ expect(inputRow.key).toBe('location');
+ expect(inputRow.name).toBe(label);
+ expect(inputRow.validate).toBe(validate);
+ });
+ });
+
+ describe("formState", function () {
+ it("has an initial location", function () {
+ expect(formState.location).toBe(initialLocation);
+ });
+ });
+
+ describe("resolution of dialog service promise", function () {
+ var resolution,
+ resolver,
+ dialogResult,
+ selectedLocation;
+
+ beforeEach(function () {
+ resolver =
+ dialogServicePromise.then.mostRecentCall.args[0];
+
+ selectedLocation = { key: "i'm a location key" };
+ dialogResult = {
+ location: selectedLocation
+ };
+
+ resolution = resolver(dialogResult);
+ });
+
+ it("returns selectedLocation", function () {
+ expect(resolution).toBe(selectedLocation);
+ });
+ });
+ });
+ });
+ }
+);
diff --git a/platform/entanglement/test/services/MockCopyService.js b/platform/entanglement/test/services/MockCopyService.js
new file mode 100644
index 0000000000..0e7e3517f3
--- /dev/null
+++ b/platform/entanglement/test/services/MockCopyService.js
@@ -0,0 +1,99 @@
+/*****************************************************************************
+ * 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,jasmine */
+
+define(
+ function () {
+ "use strict";
+
+ /**
+ * MockCopyService provides the same interface as the copyService,
+ * returning promises where it would normally do so. At it's core,
+ * it is a jasmine spy object, but it also tracks the promises it
+ * returns and provides shortcut methods for resolving those promises
+ * synchronously.
+ *
+ * Usage:
+ *
+ * ```javascript
+ * var copyService = new MockCopyService();
+ *
+ * // validate is a standard jasmine spy.
+ * copyService.validate.andReturn(true);
+ * var isValid = copyService.validate(object, parentCandidate);
+ * expect(isValid).toBe(true);
+ *
+ * // perform returns promises and tracks them.
+ * var whenCopied = jasmine.createSpy('whenCopied');
+ * copyService.perform(object, parentObject).then(whenCopied);
+ * expect(whenCopied).not.toHaveBeenCalled();
+ * copyService.perform.mostRecentCall.resolve('someArg');
+ * expect(whenCopied).toHaveBeenCalledWith('someArg');
+ * ```
+ */
+ function MockCopyService() {
+ // track most recent call of a function,
+ // perform automatically returns
+ var mockCopyService = jasmine.createSpyObj(
+ 'MockCopyService',
+ [
+ 'validate',
+ 'perform'
+ ]
+ );
+
+ mockCopyService.perform.andCallFake(function () {
+ var performPromise,
+ callExtensions,
+ spy;
+
+ performPromise = jasmine.createSpyObj(
+ 'performPromise',
+ ['then']
+ );
+
+ callExtensions = {
+ promise: performPromise,
+ resolve: function (resolveWith) {
+ performPromise.then.calls.forEach(function (call) {
+ call.args[0](resolveWith);
+ });
+ }
+ };
+
+ spy = this.perform;
+
+ Object.keys(callExtensions).forEach(function (key) {
+ spy.mostRecentCall[key] = callExtensions[key];
+ spy.calls[spy.calls.length - 1][key] = callExtensions[key];
+ });
+
+ return performPromise;
+ });
+
+ return mockCopyService;
+ }
+
+ return MockCopyService;
+ }
+);
diff --git a/platform/entanglement/test/services/MockLinkService.js b/platform/entanglement/test/services/MockLinkService.js
new file mode 100644
index 0000000000..58a82333d3
--- /dev/null
+++ b/platform/entanglement/test/services/MockLinkService.js
@@ -0,0 +1,99 @@
+/*****************************************************************************
+ * 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,jasmine */
+
+define(
+ function () {
+ "use strict";
+
+ /**
+ * MockLinkService provides the same interface as the linkService,
+ * returning promises where it would normally do so. At it's core,
+ * it is a jasmine spy object, but it also tracks the promises it
+ * returns and provides shortcut methods for resolving those promises
+ * synchronously.
+ *
+ * Usage:
+ *
+ * ```javascript
+ * var linkService = new MockLinkService();
+ *
+ * // validate is a standard jasmine spy.
+ * linkService.validate.andReturn(true);
+ * var isValid = linkService.validate(object, parentObject);
+ * expect(isValid).toBe(true);
+ *
+ * // perform returns promises and tracks them.
+ * var whenLinked = jasmine.createSpy('whenLinked');
+ * linkService.perform(object, parentObject).then(whenLinked);
+ * expect(whenLinked).not.toHaveBeenCalled();
+ * linkService.perform.mostRecentCall.resolve('someArg');
+ * expect(whenLinked).toHaveBeenCalledWith('someArg');
+ * ```
+ */
+ function MockLinkService() {
+ // track most recent call of a function,
+ // perform automatically returns
+ var mockLinkService = jasmine.createSpyObj(
+ 'MockLinkService',
+ [
+ 'validate',
+ 'perform'
+ ]
+ );
+
+ mockLinkService.perform.andCallFake(function () {
+ var performPromise,
+ callExtensions,
+ spy;
+
+ performPromise = jasmine.createSpyObj(
+ 'performPromise',
+ ['then']
+ );
+
+ callExtensions = {
+ promise: performPromise,
+ resolve: function (resolveWith) {
+ performPromise.then.calls.forEach(function (call) {
+ call.args[0](resolveWith);
+ });
+ }
+ };
+
+ spy = this.perform;
+
+ Object.keys(callExtensions).forEach(function (key) {
+ spy.mostRecentCall[key] = callExtensions[key];
+ spy.calls[spy.calls.length - 1][key] = callExtensions[key];
+ });
+
+ return performPromise;
+ });
+
+ return mockLinkService;
+ }
+
+ return MockLinkService;
+ }
+);
diff --git a/platform/entanglement/test/services/MockMoveService.js b/platform/entanglement/test/services/MockMoveService.js
new file mode 100644
index 0000000000..52278a49d5
--- /dev/null
+++ b/platform/entanglement/test/services/MockMoveService.js
@@ -0,0 +1,99 @@
+/*****************************************************************************
+ * 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,jasmine */
+
+define(
+ function () {
+ "use strict";
+
+ /**
+ * MockMoveService provides the same interface as the moveService,
+ * returning promises where it would normally do so. At it's core,
+ * it is a jasmine spy object, but it also tracks the promises it
+ * returns and provides shortcut methods for resolving those promises
+ * synchronously.
+ *
+ * Usage:
+ *
+ * ```javascript
+ * var moveService = new MockMoveService();
+ *
+ * // validate is a standard jasmine spy.
+ * moveService.validate.andReturn(true);
+ * var isValid = moveService.validate(object, parentCandidate);
+ * expect(isValid).toBe(true);
+ *
+ * // perform returns promises and tracks them.
+ * var whenCopied = jasmine.createSpy('whenCopied');
+ * moveService.perform(object, parentObject).then(whenCopied);
+ * expect(whenCopied).not.toHaveBeenCalled();
+ * moveService.perform.mostRecentCall.resolve('someArg');
+ * expect(whenCopied).toHaveBeenCalledWith('someArg');
+ * ```
+ */
+ function MockMoveService() {
+ // track most recent call of a function,
+ // perform automatically returns
+ var mockMoveService = jasmine.createSpyObj(
+ 'MockMoveService',
+ [
+ 'validate',
+ 'perform'
+ ]
+ );
+
+ mockMoveService.perform.andCallFake(function () {
+ var performPromise,
+ callExtensions,
+ spy;
+
+ performPromise = jasmine.createSpyObj(
+ 'performPromise',
+ ['then']
+ );
+
+ callExtensions = {
+ promise: performPromise,
+ resolve: function (resolveWith) {
+ performPromise.then.calls.forEach(function (call) {
+ call.args[0](resolveWith);
+ });
+ }
+ };
+
+ spy = this.perform;
+
+ Object.keys(callExtensions).forEach(function (key) {
+ spy.mostRecentCall[key] = callExtensions[key];
+ spy.calls[spy.calls.length - 1][key] = callExtensions[key];
+ });
+
+ return performPromise;
+ });
+
+ return mockMoveService;
+ }
+
+ return MockMoveService;
+ }
+);
diff --git a/platform/entanglement/test/services/MoveServiceSpec.js b/platform/entanglement/test/services/MoveServiceSpec.js
new file mode 100644
index 0000000000..d1ffb52bc1
--- /dev/null
+++ b/platform/entanglement/test/services/MoveServiceSpec.js
@@ -0,0 +1,189 @@
+/*****************************************************************************
+ * 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,beforeEach,it,jasmine,expect */
+define(
+ [
+ '../../src/services/MoveService',
+ '../services/MockLinkService',
+ '../DomainObjectFactory'
+ ],
+ function (MoveService, MockLinkService, domainObjectFactory) {
+ "use strict";
+
+ describe("MoveService", function () {
+
+ var moveService,
+ policyService,
+ linkService;
+
+ beforeEach(function () {
+ policyService = jasmine.createSpyObj(
+ 'policyService',
+ ['allow']
+ );
+ linkService = new MockLinkService();
+ moveService = new MoveService(policyService, linkService);
+ });
+
+ describe("validate", function () {
+ var object,
+ objectContextCapability,
+ currentParent,
+ parentCandidate,
+ validate;
+
+ beforeEach(function () {
+
+ objectContextCapability = jasmine.createSpyObj(
+ 'objectContextCapability',
+ [
+ 'getParent'
+ ]
+ );
+
+ object = domainObjectFactory({
+ name: 'object',
+ id: 'a',
+ capabilities: {
+ context: objectContextCapability,
+ type: { type: 'object' }
+ }
+ });
+
+ currentParent = domainObjectFactory({
+ name: 'currentParent',
+ id: 'b'
+ });
+
+ objectContextCapability.getParent.andReturn(currentParent);
+
+ parentCandidate = domainObjectFactory({
+ name: 'parentCandidate',
+ model: { composition: [] },
+ id: 'c',
+ capabilities: {
+ type: { type: 'parentCandidate' }
+ }
+ });
+
+ validate = function () {
+ return moveService.validate(object, parentCandidate);
+ };
+ });
+
+ it("does not allow an invalid parent", function () {
+ parentCandidate = undefined;
+ expect(validate()).toBe(false);
+ parentCandidate = {};
+ expect(validate()).toBe(false);
+ });
+
+ it("does not allow moving to current parent", function () {
+ parentCandidate.id = currentParent.id = 'xyz';
+ expect(validate()).toBe(false);
+ });
+
+ it("does not allow moving to self", function () {
+ object.id = parentCandidate.id = 'xyz';
+ expect(validate()).toBe(false);
+ });
+
+ it("does not allow moving to the same location", function () {
+ object.id = 'abc';
+ parentCandidate.model.composition = ['abc'];
+ expect(validate()).toBe(false);
+ });
+
+ describe("defers to policyService", function () {
+
+ it("calls policy service with correct args", function () {
+ validate();
+ expect(policyService.allow).toHaveBeenCalledWith(
+ "composition",
+ parentCandidate.capabilities.type,
+ object.capabilities.type
+ );
+ });
+
+ it("and returns false", function () {
+ policyService.allow.andReturn(false);
+ expect(validate()).toBe(false);
+ });
+
+ it("and returns true", function () {
+ policyService.allow.andReturn(true);
+ expect(validate()).toBe(true);
+ });
+ });
+ });
+
+ describe("perform", function () {
+
+ var object,
+ parentObject,
+ actionCapability;
+
+ beforeEach(function () {
+ actionCapability = jasmine.createSpyObj(
+ 'actionCapability',
+ ['perform']
+ );
+
+ object = domainObjectFactory({
+ name: 'object',
+ capabilities: {
+ action: actionCapability
+ }
+ });
+
+ parentObject = domainObjectFactory({
+ name: 'parentObject'
+ });
+
+ moveService.perform(object, parentObject);
+ });
+
+ it("links object to parentObject", function () {
+ expect(linkService.perform).toHaveBeenCalledWith(
+ object,
+ parentObject
+ );
+ });
+
+ it("waits for result of link", function () {
+ expect(linkService.perform.mostRecentCall.promise.then)
+ .toHaveBeenCalledWith(jasmine.any(Function));
+ });
+
+ it("removes object when link is completed", function () {
+ linkService.perform.mostRecentCall.resolve();
+ expect(object.getCapability)
+ .toHaveBeenCalledWith('action');
+ expect(actionCapability.perform)
+ .toHaveBeenCalledWith('remove');
+ });
+
+ });
+ });
+ }
+);
diff --git a/platform/entanglement/test/suite.json b/platform/entanglement/test/suite.json
new file mode 100644
index 0000000000..40c300e213
--- /dev/null
+++ b/platform/entanglement/test/suite.json
@@ -0,0 +1,9 @@
+[
+ "actions/CopyAction",
+ "actions/LinkAction",
+ "actions/MoveAction",
+ "services/CopyService",
+ "services/LinkService",
+ "services/MoveService",
+ "services/LocationService"
+]
diff --git a/pom.xml b/pom.xml
index b8cc32ca06..194594d6e6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
gov.nasa.arc.wtd
open-mct-web
Open MCT Web
- 0.7.0-SNAPSHOT
+ 0.7.1-SNAPSHOT
war