diff --git a/LICENSES.md b/LICENSES.md index 6e270d3937..e7ab92507b 100644 --- a/LICENSES.md +++ b/LICENSES.md @@ -476,6 +476,44 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --- +### Zepto + +#### Info + +* Link: http://zeptojs.com/ + +* Version: 1.1.6 + +* Authors: Thomas Fuchs + +* Description: DOM manipulation + +#### License + +Copyright (c) 2010-2016 Thomas Fuchs +http://zeptojs.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- + ### Json.NET #### Info diff --git a/bower.json b/bower.json index b4bf273af2..7285aad9e2 100644 --- a/bower.json +++ b/bower.json @@ -17,6 +17,7 @@ "screenfull": "^3.0.0", "node-uuid": "^1.4.7", "comma-separated-values": "^3.6.4", - "FileSaver.js": "^0.0.2" + "FileSaver.js": "^0.0.2", + "zepto": "^1.1.6" } } diff --git a/main.js b/main.js index db0b3ed84e..33e45c0125 100644 --- a/main.js +++ b/main.js @@ -33,7 +33,8 @@ requirejs.config({ "saveAs": "bower_components/FileSaver.js/FileSaver.min", "screenfull": "bower_components/screenfull/dist/screenfull.min", "text": "bower_components/text/text", - "uuid": "bower_components/node-uuid/uuid" + "uuid": "bower_components/node-uuid/uuid", + "zepto": "bower_components/zepto/zepto.min" }, "shim": { "angular": { @@ -44,6 +45,9 @@ requirejs.config({ }, "moment-duration-format": { "deps": [ "moment" ] + }, + "zepto": { + "exports": "Zepto" } } }); diff --git a/platform/commonUI/general/bundle.js b/platform/commonUI/general/bundle.js index f3d6d98f61..02512fc9fd 100644 --- a/platform/commonUI/general/bundle.js +++ b/platform/commonUI/general/bundle.js @@ -49,6 +49,7 @@ define([ "./src/directives/MCTScroll", "./src/directives/MCTSplitPane", "./src/directives/MCTSplitter", + "./src/directives/MCTTree", "text!./res/templates/bottombar.html", "text!./res/templates/controls/action-button.html", "text!./res/templates/controls/input-filter.html", @@ -97,6 +98,7 @@ define([ MCTScroll, MCTSplitPane, MCTSplitter, + MCTTree, bottombarTemplate, actionButtonTemplate, inputFilterTemplate, @@ -389,6 +391,11 @@ define([ { "key": "mctSplitter", "implementation": MCTSplitter + }, + { + "key": "mctTree", + "implementation": MCTTree, + "depends": [ '$parse', 'gestureService' ] } ], "constants": [ @@ -535,6 +542,16 @@ define([ "copyright": "Copyright (c) Nicolas Gallagher and Jonathan Neal", "license": "license-mit", "link": "https://github.com/necolas/normalize.css/blob/v1.1.2/LICENSE.md" + }, + { + "name": "Zepto", + "version": "1.1.6", + "description": "DOM manipulation", + "author": "Thomas Fuchs", + "website": "http://zeptojs.com/", + "copyright": "Copyright (c) 2010-2016 Thomas Fuchs", + "license": "license-mit", + "link": "https://github.com/madrobby/zepto/blob/master/MIT-LICENSE" } ] } diff --git a/platform/commonUI/general/res/templates/subtree.html b/platform/commonUI/general/res/templates/subtree.html index 637b73a8c4..abc7a0d34a 100644 --- a/platform/commonUI/general/res/templates/subtree.html +++ b/platform/commonUI/general/res/templates/subtree.html @@ -19,18 +19,6 @@ this source code distribution or the Licensing information page available at runtime from the About dialog for additional information. --> - + + + diff --git a/platform/commonUI/general/res/templates/tree/node.html b/platform/commonUI/general/res/templates/tree/node.html new file mode 100644 index 0000000000..d6012b5ea9 --- /dev/null +++ b/platform/commonUI/general/res/templates/tree/node.html @@ -0,0 +1,4 @@ + + + + diff --git a/platform/commonUI/general/res/templates/tree/toggle.html b/platform/commonUI/general/res/templates/tree/toggle.html new file mode 100644 index 0000000000..289f781f95 --- /dev/null +++ b/platform/commonUI/general/res/templates/tree/toggle.html @@ -0,0 +1,2 @@ + + diff --git a/platform/commonUI/general/res/templates/tree/tree-label.html b/platform/commonUI/general/res/templates/tree/tree-label.html new file mode 100644 index 0000000000..8c6dcb2e86 --- /dev/null +++ b/platform/commonUI/general/res/templates/tree/tree-label.html @@ -0,0 +1,8 @@ + +
+
+
+
+
+
+
diff --git a/platform/commonUI/general/res/templates/tree/wait-node.html b/platform/commonUI/general/res/templates/tree/wait-node.html new file mode 100644 index 0000000000..79dffa73b5 --- /dev/null +++ b/platform/commonUI/general/res/templates/tree/wait-node.html @@ -0,0 +1,27 @@ + +
  • + + + Loading... + +
  • diff --git a/platform/commonUI/general/src/directives/MCTTree.js b/platform/commonUI/general/src/directives/MCTTree.js new file mode 100644 index 0000000000..47f8eecbc6 --- /dev/null +++ b/platform/commonUI/general/src/directives/MCTTree.js @@ -0,0 +1,54 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +define([ + 'angular', + '../ui/TreeView' +], function (angular, TreeView) { + function MCTTree($parse, gestureService) { + function link(scope, element, attrs) { + var treeView = new TreeView(gestureService), + expr = $parse(attrs.mctModel), + unobserve = treeView.observe(function (domainObject) { + if (domainObject !== expr(scope.$parent)) { + expr.assign(scope.$parent, domainObject); + scope.$apply(); + } + }); + + element.append(angular.element(treeView.elements())); + + scope.$parent.$watch(attrs.mctModel, treeView.value.bind(treeView)); + scope.$watch('mctObject', treeView.model.bind(treeView)); + scope.$on('$destroy', unobserve); + } + + return { + restrict: "E", + link: link, + scope: { mctObject: "=" } + }; + } + + return MCTTree; +}); diff --git a/platform/commonUI/general/src/ui/ToggleView.js b/platform/commonUI/general/src/ui/ToggleView.js new file mode 100644 index 0000000000..c347ac3837 --- /dev/null +++ b/platform/commonUI/general/src/ui/ToggleView.js @@ -0,0 +1,65 @@ +/***************************************************************************** + * 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([ + 'zepto', + 'text!../../res/templates/tree/toggle.html' +], function ($, toggleTemplate) { + function ToggleView(state) { + this.expanded = !!state; + this.callbacks = []; + this.el = $(toggleTemplate); + this.el.on('click', function () { + this.value(!this.expanded); + }.bind(this)); + } + + ToggleView.prototype.value = function (state) { + this.expanded = state; + + if (state) { + this.el.addClass('expanded'); + } else { + this.el.removeClass('expanded'); + } + + this.callbacks.forEach(function (callback) { + callback(state); + }); + }; + + ToggleView.prototype.observe = function (callback) { + this.callbacks.push(callback); + return function () { + this.callbacks = this.callbacks.filter(function (c) { + return c !== callback; + }); + }.bind(this); + }; + + ToggleView.prototype.elements = function () { + return this.el; + }; + + return ToggleView; +}); diff --git a/platform/commonUI/general/src/ui/TreeLabelView.js b/platform/commonUI/general/src/ui/TreeLabelView.js new file mode 100644 index 0000000000..75e8efcc29 --- /dev/null +++ b/platform/commonUI/general/src/ui/TreeLabelView.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([ + 'zepto', + 'text!../../res/templates/tree/tree-label.html' +], function ($, labelTemplate) { + 'use strict'; + + function TreeLabelView(gestureService) { + this.el = $(labelTemplate); + this.gestureService = gestureService; + } + + function getGlyph(domainObject) { + var type = domainObject.getCapability('type'); + return type.getGlyph(); + } + + function isLink(domainObject) { + var location = domainObject.getCapability('location'); + return location.isLink(); + } + + TreeLabelView.prototype.updateView = function (domainObject) { + var titleEl = this.el.find('.t-title-label'), + glyphEl = this.el.find('.t-item-icon-glyph'), + iconEl = this.el.find('.t-item-icon'); + + titleEl.text(domainObject ? domainObject.getModel().name : ""); + glyphEl.text(domainObject ? getGlyph(domainObject) : ""); + + if (domainObject && isLink(domainObject)) { + iconEl.addClass('l-icon-link'); + } else { + iconEl.removeClass('l-icon-link'); + } + }; + + TreeLabelView.prototype.model = function (domainObject) { + if (this.unlisten) { + this.unlisten(); + delete this.unlisten; + } + + if (this.activeGestures) { + this.activeGestures.destroy(); + delete this.activeGestures; + } + + this.updateView(domainObject); + + if (domainObject) { + this.unlisten = domainObject.getCapability('mutation') + .listen(this.updateView.bind(this, domainObject)); + + this.activeGestures = this.gestureService.attachGestures( + this.elements(), + domainObject, + [ 'info', 'menu', 'drag' ] + ); + } + }; + + TreeLabelView.prototype.elements = function () { + return this.el; + }; + + return TreeLabelView; +}); diff --git a/platform/commonUI/general/src/ui/TreeNodeView.js b/platform/commonUI/general/src/ui/TreeNodeView.js new file mode 100644 index 0000000000..2b02a2c1f9 --- /dev/null +++ b/platform/commonUI/general/src/ui/TreeNodeView.js @@ -0,0 +1,133 @@ +/***************************************************************************** + * 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([ + 'zepto', + 'text!../../res/templates/tree/node.html', + './ToggleView', + './TreeLabelView' +], function ($, nodeTemplate, ToggleView, TreeLabelView) { + 'use strict'; + + function TreeNodeView(gestureService, subtreeFactory, selectFn) { + this.li = $('
  • '); + + this.toggleView = new ToggleView(false); + this.toggleView.observe(function (state) { + if (state) { + if (!this.subtreeView) { + this.subtreeView = subtreeFactory(); + this.subtreeView.model(this.activeObject); + this.li.find('.tree-item-subtree').eq(0) + .append($(this.subtreeView.elements())); + } + $(this.subtreeView.elements()).removeClass('hidden'); + } else if (this.subtreeView) { + $(this.subtreeView.elements()).addClass('hidden'); + } + }.bind(this)); + + this.labelView = new TreeLabelView(gestureService); + + $(this.labelView.elements()).on('click', function () { + selectFn(this.activeObject); + }.bind(this)); + + this.li.append($(nodeTemplate)); + this.li.find('span').eq(0) + .append($(this.toggleView.elements())) + .append($(this.labelView.elements())); + + this.model(undefined); + } + + TreeNodeView.prototype.model = function (domainObject) { + if (this.unlisten) { + this.unlisten(); + } + + this.activeObject = domainObject; + + if (domainObject && domainObject.hasCapability('composition')) { + $(this.toggleView.elements()).addClass('has-children'); + } else { + $(this.toggleView.elements()).removeClass('has-children'); + } + + this.labelView.model(domainObject); + if (this.subtreeView) { + this.subtreeView.model(domainObject); + } + }; + + function getIdPath(domainObject) { + function getId(domainObject) { + return domainObject.getId(); + } + + return domainObject ? + domainObject.getCapability('context').getPath().map(getId) : + []; + } + + TreeNodeView.prototype.value = function (domainObject) { + var activeIdPath = getIdPath(this.activeObject), + selectedIdPath = getIdPath(domainObject); + + if (this.onSelectionPath) { + this.li.find('.tree-item').eq(0).removeClass('selected'); + if (this.subtreeView) { + this.subtreeView.value(undefined); + } + } + + this.onSelectionPath = + !!domainObject && + !!this.activeObject && + (activeIdPath.length <= selectedIdPath.length) && + activeIdPath.every(function (id, index) { + return selectedIdPath[index] === id; + }); + + if (this.onSelectionPath) { + if (activeIdPath.length === selectedIdPath.length) { + this.li.find('.tree-item').eq(0).addClass('selected'); + } else { + // Expand to reveal the selection + this.toggleView.value(true); + this.subtreeView.value(domainObject); + } + } + }; + + /** + * + * @returns {HTMLElement[]} + */ + TreeNodeView.prototype.elements = function () { + return this.li; + }; + + + return TreeNodeView; +}); diff --git a/platform/commonUI/general/src/ui/TreeView.js b/platform/commonUI/general/src/ui/TreeView.js new file mode 100644 index 0000000000..b09256388d --- /dev/null +++ b/platform/commonUI/general/src/ui/TreeView.js @@ -0,0 +1,140 @@ +/***************************************************************************** + * 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([ + 'zepto', + './TreeNodeView', + 'text!../../res/templates/tree/wait-node.html' +], function ($, TreeNodeView, spinnerTemplate) { + 'use strict'; + + function TreeView(gestureService, selectFn) { + this.ul = $(''); + this.nodeViews = []; + this.callbacks = []; + this.selectFn = selectFn || this.value.bind(this); + this.gestureService = gestureService; + this.pending = false; + } + + TreeView.prototype.newTreeView = function () { + return new TreeView(this.gestureService, this.selectFn); + }; + + TreeView.prototype.setSize = function (sz) { + var nodeView; + + while (this.nodeViews.length < sz) { + nodeView = new TreeNodeView( + this.gestureService, + this.newTreeView.bind(this), + this.selectFn + ); + this.nodeViews.push(nodeView); + this.ul.append($(nodeView.elements())); + } + + while (this.nodeViews.length > sz) { + nodeView = this.nodeViews.pop(); + $(nodeView.elements()).remove(); + } + }; + + TreeView.prototype.loadComposition = function (domainObject) { + var self = this; + + function addNode(domainObject, index) { + self.nodeViews[index].model(domainObject); + } + + function addNodes(domainObjects) { + if (self.pending) { + self.pending = false; + self.nodeViews = []; + self.ul.empty(); + } + + if (domainObject === self.activeObject) { + self.setSize(domainObjects.length); + domainObjects.forEach(addNode); + self.updateNodeViewSelection(); + } + } + + domainObject.useCapability('composition') + .then(addNodes); + }; + + TreeView.prototype.model = function (domainObject) { + if (this.unlisten) { + this.unlisten(); + } + + this.activeObject = domainObject; + this.ul.empty(); + + if (domainObject && domainObject.hasCapability('composition')) { + this.pending = true; + this.ul.append($(spinnerTemplate)); + this.unlisten = domainObject.getCapability('mutation') + .listen(this.loadComposition.bind(this)); + this.loadComposition(domainObject); + } else { + this.setSize(0); + } + }; + + TreeView.prototype.updateNodeViewSelection = function () { + this.nodeViews.forEach(function (nodeView) { + nodeView.value(this.selectedObject); + }.bind(this)); + }; + + TreeView.prototype.value = function (domainObject) { + this.selectedObject = domainObject; + this.updateNodeViewSelection(); + this.callbacks.forEach(function (callback) { + callback(domainObject); + }); + }; + + TreeView.prototype.observe = function (callback) { + this.callbacks.push(callback); + return function () { + this.callbacks = this.callbacks.filter(function (c) { + return c !== callback; + }); + }.bind(this); + }; + + /** + * + * @returns {HTMLElement[]} + */ + TreeView.prototype.elements = function () { + return this.ul; + }; + + + return TreeView; +}); diff --git a/platform/commonUI/general/test/directives/MCTTreeSpec.js b/platform/commonUI/general/test/directives/MCTTreeSpec.js new file mode 100644 index 0000000000..597c4c55b7 --- /dev/null +++ b/platform/commonUI/general/test/directives/MCTTreeSpec.js @@ -0,0 +1,95 @@ +/***************************************************************************** + * 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,jasmine,it,expect*/ + +define([ + '../../src/directives/MCTTree' +], function (MCTTree) { + describe("The mct-tree directive", function () { + var mockParse, + mockGestureService, + mockExpr, + mctTree; + + beforeEach(function () { + mockGestureService = jasmine.createSpyObj( + 'gestureService', + [ 'attachGestures' ] + ); + mockParse = jasmine.createSpy('$parse'); + mockExpr = jasmine.createSpy('expr'); + mockExpr.assign = jasmine.createSpy('assign'); + mockParse.andReturn(mockExpr); + + mctTree = new MCTTree(mockParse, mockGestureService); + }); + + it("is applicable as an element", function () { + expect(mctTree.restrict).toEqual("E"); + }); + + it("two-way binds to mctObject", function () { + expect(mctTree.scope).toEqual({ mctObject: "=" }); + }); + + describe("link", function () { + var mockScope, + mockElement, + testAttrs; + + beforeEach(function () { + mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']); + mockElement = jasmine.createSpyObj('element', ['append']); + testAttrs = { mctModel: "some-expression" }; + mockScope.$parent = + jasmine.createSpyObj('$scope', ['$watch', '$on']); + mctTree.link(mockScope, mockElement, testAttrs); + }); + + it("populates the mct-tree element", function () { + expect(mockElement.append).toHaveBeenCalled(); + }); + + it("watches for mct-model's expression in the parent", function () { + expect(mockScope.$parent.$watch).toHaveBeenCalledWith( + testAttrs.mctModel, + jasmine.any(Function) + ); + }); + + it("watches for changes to mct-object", function () { + expect(mockScope.$watch).toHaveBeenCalledWith( + "mctObject", + jasmine.any(Function) + ); + }); + + it("listens for the $destroy event", function () { + expect(mockScope.$on).toHaveBeenCalledWith( + "$destroy", + jasmine.any(Function) + ); + }); + }); + }); + +}); diff --git a/platform/commonUI/general/test/ui/TreeViewSpec.js b/platform/commonUI/general/test/ui/TreeViewSpec.js new file mode 100644 index 0000000000..370e5c8e10 --- /dev/null +++ b/platform/commonUI/general/test/ui/TreeViewSpec.js @@ -0,0 +1,274 @@ +/***************************************************************************** + * 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,jasmine,it,expect*/ + +define([ + '../../src/ui/TreeView', + 'zepto' +], function (TreeView, $) { + 'use strict'; + + describe("TreeView", function () { + var mockGestureService, + mockGestureHandle, + mockDomainObject, + mockMutation, + mockUnlisten, + testCapabilities, + treeView; + + function makeMockDomainObject(id, model, capabilities) { + var mockDomainObject = jasmine.createSpyObj( + 'domainObject-' + id, + [ + 'getId', + 'getModel', + 'getCapability', + 'hasCapability', + 'useCapability' + ] + ); + mockDomainObject.getId.andReturn(id); + mockDomainObject.getModel.andReturn(model); + mockDomainObject.hasCapability.andCallFake(function (c) { + return !!(capabilities[c]); + }); + mockDomainObject.getCapability.andCallFake(function (c) { + return capabilities[c]; + }); + mockDomainObject.useCapability.andCallFake(function (c) { + return capabilities[c] && capabilities[c].invoke(); + }); + return mockDomainObject; + } + + beforeEach(function () { + mockGestureService = jasmine.createSpyObj( + 'gestureService', + [ 'attachGestures' ] + ); + + mockGestureHandle = jasmine.createSpyObj('gestures', ['destroy']); + + mockGestureService.attachGestures.andReturn(mockGestureHandle); + + mockMutation = jasmine.createSpyObj('mutation', ['listen']); + mockUnlisten = jasmine.createSpy('unlisten'); + mockMutation.listen.andReturn(mockUnlisten); + + testCapabilities = { mutation: mockMutation }; + + mockDomainObject = + makeMockDomainObject('parent', {}, testCapabilities); + + treeView = new TreeView(mockGestureService); + }); + + describe("elements", function () { + var elements; + + beforeEach(function () { + elements = treeView.elements(); + }); + + it("is an unordered list", function () { + expect(elements[0].tagName.toLowerCase()) + .toEqual('ul'); + }); + }); + + describe("model", function () { + var mockComposition; + + function makeGenericCapabilities() { + var mockContext = + jasmine.createSpyObj('context', [ 'getPath' ]), + mockType = + jasmine.createSpyObj('type', [ 'getGlyph' ]), + mockLocation = + jasmine.createSpyObj('location', [ 'isLink' ]), + mockMutation = + jasmine.createSpyObj('mutation', [ 'listen' ]); + return { + context: mockContext, + type: mockType, + mutation: mockMutation, + location: mockLocation + }; + } + + function waitForCompositionCallback() { + var calledBack = false; + testCapabilities.composition.invoke().then(function (c) { + calledBack = true; + }); + waitsFor(function () { + return calledBack; + }); + } + + beforeEach(function () { + mockComposition = ['a', 'b', 'c'].map(function (id) { + var testCapabilities = makeGenericCapabilities(), + mockChild = + makeMockDomainObject(id, {}, testCapabilities); + + testCapabilities.context.getPath + .andReturn([mockDomainObject, mockChild]); + + return mockChild; + }); + + testCapabilities.composition = + jasmine.createSpyObj('composition', ['invoke']); + testCapabilities.composition.invoke + .andReturn(Promise.resolve(mockComposition)); + + treeView.model(mockDomainObject); + waitForCompositionCallback(); + }); + + it("adds one node per composition element", function () { + expect(treeView.elements()[0].childElementCount) + .toEqual(mockComposition.length); + }); + + it("listens for mutation", function () { + expect(testCapabilities.mutation.listen) + .toHaveBeenCalledWith(jasmine.any(Function)); + }); + + describe("when mutation occurs", function () { + beforeEach(function () { + mockComposition.pop(); + testCapabilities.mutation.listen + .mostRecentCall.args[0](mockDomainObject); + waitForCompositionCallback(); + }); + + it("continues to show one node per composition element", function () { + expect(treeView.elements()[0].childElementCount) + .toEqual(mockComposition.length); + }); + }); + + describe("when replaced with a non-compositional domain object", function () { + beforeEach(function () { + delete testCapabilities.composition; + treeView.model(mockDomainObject); + }); + + it("stops listening for mutation", function () { + expect(mockUnlisten).toHaveBeenCalled(); + }); + + it("removes all tree nodes", function () { + expect(treeView.elements()[0].childElementCount) + .toEqual(0); + }); + }); + + describe("when selection state changes", function () { + var selectionIndex = 1; + + beforeEach(function () { + treeView.value(mockComposition[selectionIndex]); + }); + + it("communicates selection state to an appropriate node", function () { + var selected = $(treeView.elements()[0]).find('.selected'); + expect(selected.length).toEqual(1); + }); + }); + + describe("when children contain children", function () { + beforeEach(function () { + var newCapabilities = makeGenericCapabilities(), + gcCapabilities = makeGenericCapabilities(), + mockNewChild = + makeMockDomainObject('d', {}, newCapabilities), + mockGrandchild = + makeMockDomainObject('gc', {}, gcCapabilities), + calledBackInner = false; + + newCapabilities.composition = + jasmine.createSpyObj('composition', [ 'invoke' ]); + newCapabilities.composition.invoke + .andReturn(Promise.resolve([mockGrandchild])); + mockComposition.push(mockNewChild); + + newCapabilities.context.getPath.andReturn([ + mockDomainObject, + mockNewChild + ]); + gcCapabilities.context.getPath.andReturn([ + mockDomainObject, + mockNewChild, + mockGrandchild + ]); + + testCapabilities.mutation.listen + .mostRecentCall.args[0](mockDomainObject); + waitForCompositionCallback(); + runs(function () { + // Select the innermost object to force expansion, + // such that we can verify the subtree is present. + treeView.value(mockGrandchild); + newCapabilities.composition.invoke().then(function () { + calledBackInner = true; + }); + }); + waitsFor(function () { + return calledBackInner; + }); + }); + + it("creates inner trees", function () { + expect($(treeView.elements()[0]).find('ul').length) + .toEqual(1); + }); + }); + }); + + describe("observe", function () { + var mockCallback, + unobserve; + + beforeEach(function () { + mockCallback = jasmine.createSpy('callback'); + unobserve = treeView.observe(mockCallback); + }); + + it("notifies listeners when value is changed", function () { + treeView.value(mockDomainObject); + expect(mockCallback).toHaveBeenCalledWith(mockDomainObject); + }); + + it("does not notify listeners when deactivated", function () { + unobserve(); + treeView.value(mockDomainObject); + expect(mockCallback).not.toHaveBeenCalled(); + }); + }); + }); + +}); diff --git a/test-main.js b/test-main.js index 13f1bf367d..6e4729f050 100644 --- a/test-main.js +++ b/test-main.js @@ -53,7 +53,8 @@ requirejs.config({ "saveAs": "bower_components/FileSaver.js/FileSaver.min", "screenfull": "bower_components/screenfull/dist/screenfull.min", "text": "bower_components/text/text", - "uuid": "bower_components/node-uuid/uuid" + "uuid": "bower_components/node-uuid/uuid", + "zepto": "bower_components/zepto/zepto.min" }, "shim": { @@ -65,6 +66,9 @@ requirejs.config({ }, "moment-duration-format": { "deps": [ "moment" ] + }, + "zepto": { + "exports": "Zepto" } },