\ No newline at end of file
diff --git a/platform/search/res/templates/search-menu.html b/platform/search/res/templates/search-menu.html
new file mode 100644
index 0000000000..8b5f275871
--- /dev/null
+++ b/platform/search/res/templates/search-menu.html
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ *
+
+ All
+
+
+
+
+
+
+
+
+ {{ type.glyph }}
+
+ {{ type.name }}
+
+
+
+
\ No newline at end of file
diff --git a/platform/search/res/templates/search.html b/platform/search/res/templates/search.html
new file mode 100644
index 0000000000..5313cd12ed
--- /dev/null
+++ b/platform/search/res/templates/search.html
@@ -0,0 +1,116 @@
+
+
\ No newline at end of file
diff --git a/platform/search/src/controllers/ClickAwayController.js b/platform/search/src/controllers/ClickAwayController.js
new file mode 100644
index 0000000000..9b92e89cc0
--- /dev/null
+++ b/platform/search/src/controllers/ClickAwayController.js
@@ -0,0 +1,105 @@
+/*****************************************************************************
+ * 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,Promise*/
+
+/*
+ * Copied from the ClickAwayController in platform/commonUI/general
+ */
+
+define(
+ [],
+ function () {
+ "use strict";
+
+ /**
+ * A ClickAwayController is used to toggle things (such as context
+ * menus) where clicking elsewhere in the document while the toggle
+ * is in an active state is intended to dismiss the toggle.
+ *
+ * @constructor
+ * @param $scope the scope in which this controller is active
+ * @param $document the document element, injected by Angular
+ */
+ function ClickAwayController($scope, $document) {
+ var state = false,
+ clickaway;
+
+ // Track state, but also attach and detach a listener for
+ // mouseup events on the document.
+ function deactivate() {
+ state = false;
+ $document.off("mouseup", clickaway);
+ }
+
+ function activate() {
+ state = true;
+ $document.on("mouseup", clickaway);
+ }
+
+ function changeState() {
+ if (state) {
+ deactivate();
+ } else {
+ activate();
+ }
+ }
+
+ // Callback used by the document listener. Deactivates;
+ // note also $scope.$apply is invoked to indicate that
+ // the state of this controller has changed.
+ clickaway = function () {
+ deactivate();
+ $scope.$apply();
+ return false;
+ };
+
+ return {
+ /**
+ * Get the current state of the toggle.
+ * @return {boolean} true if active
+ */
+ isActive: function () {
+ return state;
+ },
+ /**
+ * Set a new state for the toggle.
+ * @return {boolean} true to activate
+ */
+ setState: function (newState) {
+ if (state !== newState) {
+ changeState();
+ }
+ },
+ /**
+ * Toggle the current state; activate if it is inactive,
+ * deactivate if it is active.
+ */
+ toggle: function () {
+ changeState();
+ }
+ };
+
+ }
+
+ return ClickAwayController;
+ }
+);
\ No newline at end of file
diff --git a/platform/search/src/controllers/SearchController.js b/platform/search/src/controllers/SearchController.js
new file mode 100644
index 0000000000..10cf056b4f
--- /dev/null
+++ b/platform/search/src/controllers/SearchController.js
@@ -0,0 +1,171 @@
+/*****************************************************************************
+ * 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*/
+
+/**
+ * Module defining SearchController. Created by shale on 07/15/2015.
+ */
+define(function () {
+ "use strict";
+
+ var INITIAL_LOAD_NUMBER = 20,
+ LOAD_INCREMENT = 20;
+
+ function SearchController($scope, searchService) {
+ // numResults is the amount of results to display. Will get increased.
+ // fullResults holds the most recent complete searchService response object
+ var numResults = INITIAL_LOAD_NUMBER,
+ fullResults = {hits: []};
+
+ // Scope variables are:
+ // Variables used only in SearchController:
+ // results, an array of searchResult objects
+ // loading, whether search() is loading
+ // ngModel.input, the text of the search query
+ // ngModel.search, a boolean of whether to display search or the tree
+ // Variables used also in SearchMenuController:
+ // ngModel.filter, the function filter defined below
+ // ngModel.types, an array of type objects
+ // ngModel.checked, a dictionary of which type filter options are checked
+ // ngModel.checkAll, a boolean of whether to search all types
+ // ngModel.filtersString, a string list of what filters on the results are active
+ $scope.results = [];
+ $scope.loading = false;
+
+
+ // Filters searchResult objects by type. Allows types that are
+ // checked. (ngModel.checked['typekey'] === true)
+ // If hits is not provided, will use fullResults.hits
+ function filter(hits) {
+ var newResults = [],
+ i = 0;
+
+ if (!hits) {
+ hits = fullResults.hits;
+ }
+
+ // If checkAll is checked, search everything no matter what the other
+ // checkboxes' statuses are. Otherwise filter the search by types.
+ if ($scope.ngModel.checkAll) {
+ newResults = fullResults.hits.slice(0, numResults);
+ } else {
+ while (newResults.length < numResults && i < hits.length) {
+ // If this is of an acceptable type, add it to the list
+ if ($scope.ngModel.checked[hits[i].object.getModel().type]) {
+ newResults.push(fullResults.hits[i]);
+ }
+ i += 1;
+ }
+ }
+
+ $scope.results = newResults;
+ return newResults;
+ }
+
+ // Make function accessible from SearchMenuController
+ $scope.ngModel.filter = filter;
+
+ // For documentation, see search below
+ function search(maxResults) {
+ var inputText = $scope.ngModel.input;
+
+ if (inputText !== '' && inputText !== undefined) {
+ // We are starting to load.
+ $scope.loading = true;
+
+ // Update whether the file tree should be displayed
+ // Hide tree only when starting search
+ $scope.ngModel.search = true;
+ }
+
+ if (!maxResults) {
+ // Reset 'load more'
+ numResults = INITIAL_LOAD_NUMBER;
+ }
+
+ // Send the query
+ searchService.query(inputText, maxResults).then(function (result) {
+ // Store all the results before splicing off the front, so that
+ // we can load more to display later.
+ fullResults = result;
+ $scope.results = filter(result.hits);
+
+ // Update whether the file tree should be displayed
+ // Reveal tree only when finishing search
+ if (inputText === '' || inputText === undefined) {
+ $scope.ngModel.search = false;
+ }
+
+ // Now we are done loading.
+ $scope.loading = false;
+ });
+ }
+
+ return {
+ /**
+ * Search the filetree. Assumes that any search text will
+ * be in ngModel.input
+ *
+ * @param maxResults (optional) The maximum number of results
+ * that this function should return. If not provided, search
+ * service default will be used.
+ */
+ search: search,
+
+ /**
+ * Checks to see if there are more search results to display. If the answer is
+ * unclear, this function will err toward saying that there are more results.
+ */
+ areMore: function () {
+ var i;
+
+ // Check to see if any of the not displayed results are of an allowed type
+ for (i = numResults; i < fullResults.hits.length; i += 1) {
+ if ($scope.ngModel.checkAll || $scope.ngModel.checked[fullResults.hits[i].object.getModel().type]) {
+ return true;
+ }
+ }
+
+ // If none of the ones at hand are correct, there still may be more if we
+ // re-search with a larger maxResults
+ return fullResults.hits.length < fullResults.total;
+ },
+
+ /**
+ * Increases the number of search results to display, and then
+ * loads them, adding to the displayed results.
+ */
+ loadMore: function () {
+ numResults += LOAD_INCREMENT;
+
+ if (numResults > fullResults.hits.length && fullResults.hits.length < fullResults.total) {
+ // Resend the query if we are out of items to display, but there are more to get
+ search(numResults);
+ } else {
+ // Otherwise just take from what we already have
+ $scope.results = filter(fullResults.hits);
+ }
+ }
+ };
+ }
+ return SearchController;
+});
diff --git a/platform/search/src/controllers/SearchMenuController.js b/platform/search/src/controllers/SearchMenuController.js
new file mode 100644
index 0000000000..e0d8c342e5
--- /dev/null
+++ b/platform/search/src/controllers/SearchMenuController.js
@@ -0,0 +1,131 @@
+/*****************************************************************************
+ * 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*/
+
+/**
+ * Module defining SearchMenuController. Created by shale on 08/17/2015.
+ */
+define(function () {
+ "use strict";
+
+ function SearchMenuController($scope, types) {
+
+ // Model variables are:
+ // ngModel.filter, the function filter defined in SearchController
+ // ngModel.types, an array of type objects
+ // ngModel.checked, a dictionary of which type filter options are checked
+ // ngModel.checkAll, a boolean of whether all of the types in ngModel.checked are checked
+ // ngModel.filtersString, a string list of what filters on the results are active
+ $scope.ngModel.types = [];
+ $scope.ngModel.checked = {};
+ $scope.ngModel.checkAll = true;
+ $scope.ngModel.filtersString = '';
+
+ // On initialization, fill the model's types with type keys
+ types.forEach(function (type) {
+ // We only want some types, the ones that are probably human readable
+ // Manually remove 'root', but not 'unknown'
+ if (type.key && type.name && type.key !== 'root') {
+ $scope.ngModel.types.push(type);
+ $scope.ngModel.checked[type.key] = false;
+ }
+ });
+
+
+ // For documentation, see updateOptions below
+ function updateOptions() {
+ var type,
+ i;
+
+ // Update all-checked status
+ if ($scope.ngModel.checkAll) {
+ for (type in $scope.ngModel.checked) {
+ if ($scope.ngModel.checked[type]) {
+ $scope.ngModel.checkAll = false;
+ }
+ }
+ }
+
+ // Update the current filters string
+ $scope.ngModel.filtersString = '';
+ if (!$scope.ngModel.checkAll) {
+ for (i = 0; i < $scope.ngModel.types.length; i += 1) {
+ // If the type key corresponds to a checked option...
+ if ($scope.ngModel.checked[$scope.ngModel.types[i].key]) {
+ // ... add it to the string list of current filter options
+ if ($scope.ngModel.filtersString === '') {
+ $scope.ngModel.filtersString += $scope.ngModel.types[i].name;
+ } else {
+ $scope.ngModel.filtersString += ', ' + $scope.ngModel.types[i].name;
+ }
+ }
+ }
+ // If there's still nothing in the filters string, there are no
+ // filters selected
+ if ($scope.ngModel.filtersString === '') {
+ $scope.ngModel.filtersString = 'NONE';
+ }
+ }
+
+ // Re-filter results
+ $scope.ngModel.filter();
+ }
+
+ // For documentation, see checkAll below
+ function checkAll() {
+ var type;
+
+ // Reset all the other options to original/default position
+ for (type in $scope.ngModel.checked) {
+ $scope.ngModel.checked[type] = false;
+ }
+
+ // Change the filters string depending on checkAll status
+ if ($scope.ngModel.checkAll) {
+ // This setting will make the filters display hidden
+ $scope.ngModel.filtersString = '';
+ } else {
+ $scope.ngModel.filtersString = 'NONE';
+ }
+
+ // Re-filter results
+ $scope.ngModel.filter();
+ }
+
+ return {
+ /**
+ * Updates the status of the checked options. Updates the filtersString
+ * with which options are checked. Re-filters the search results after.
+ * Not intended to be called by checkAll when it is toggled.
+ */
+ updateOptions: updateOptions,
+
+ /**
+ * Handles the search and filter options for when checkAll has been
+ * toggled. This is a special case, compared to the other search
+ * menu options, so is intended to be called instead of updateOptions.
+ */
+ checkAll: checkAll
+ };
+ }
+ return SearchMenuController;
+});
diff --git a/platform/search/src/GenericSearchProvider.js b/platform/search/src/services/GenericSearchProvider.js
similarity index 100%
rename from platform/search/src/GenericSearchProvider.js
rename to platform/search/src/services/GenericSearchProvider.js
diff --git a/platform/search/src/GenericSearchWorker.js b/platform/search/src/services/GenericSearchWorker.js
similarity index 87%
rename from platform/search/src/GenericSearchWorker.js
rename to platform/search/src/services/GenericSearchWorker.js
index 69e4104602..57be98b423 100644
--- a/platform/search/src/GenericSearchWorker.js
+++ b/platform/search/src/services/GenericSearchWorker.js
@@ -31,36 +31,6 @@
// {id: domainObject's ID, model: domainObject's model}
var indexedItems = [];
- // Helper function for index()
- // Checks whether an item with this ID is already indexed
- function conainsItem(id) {
- var i;
- for (i = 0; i < indexedItems.length; i += 1) {
- if (indexedItems[i].id === id) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Indexes an item to indexedItems.
- *
- * @param data An object which contains:
- * * model: The model of the domain object
- * * id: The ID of the domain object
- */
- function index(data) {
- var message;
-
- if (!conainsItem(data.id)) {
- indexedItems.push({
- id: data.id,
- model: data.model
- });
- }
- }
-
// Helper function for serach()
function convertToTerms(input) {
var terms = input;
@@ -177,7 +147,10 @@
self.onmessage = function (event) {
if (event.data.request === 'index') {
- index(event.data);
+ indexedItems.push({
+ id: event.data.id,
+ model: event.data.model
+ });
} else if (event.data.request === 'search') {
self.postMessage(search(event.data));
}
diff --git a/platform/search/src/SearchAggregator.js b/platform/search/src/services/SearchAggregator.js
similarity index 100%
rename from platform/search/src/SearchAggregator.js
rename to platform/search/src/services/SearchAggregator.js
diff --git a/platform/search/test/controllers/ClickAwayControllerSpec.js b/platform/search/test/controllers/ClickAwayControllerSpec.js
new file mode 100644
index 0000000000..96e7b6c13f
--- /dev/null
+++ b/platform/search/test/controllers/ClickAwayControllerSpec.js
@@ -0,0 +1,94 @@
+/*****************************************************************************
+ * 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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
+
+define(
+ ["../../src/controllers/ClickAwayController"],
+ function (ClickAwayController) {
+ "use strict";
+
+ describe("The click-away controller", function () {
+ var mockScope,
+ mockDocument,
+ controller;
+
+ beforeEach(function () {
+ mockScope = jasmine.createSpyObj(
+ "$scope",
+ [ "$apply" ]
+ );
+ mockDocument = jasmine.createSpyObj(
+ "$document",
+ [ "on", "off" ]
+ );
+ controller = new ClickAwayController(mockScope, mockDocument);
+ });
+
+ it("is initially inactive", function () {
+ expect(controller.isActive()).toBe(false);
+ });
+
+ it("does not listen to the document before being toggled", function () {
+ expect(mockDocument.on).not.toHaveBeenCalled();
+ });
+
+ it("tracks enabled/disabled state when toggled", function () {
+ controller.toggle();
+ expect(controller.isActive()).toBe(true);
+ controller.toggle();
+ expect(controller.isActive()).toBe(false);
+ controller.toggle();
+ expect(controller.isActive()).toBe(true);
+ controller.toggle();
+ expect(controller.isActive()).toBe(false);
+ });
+
+ it("allows active state to be explictly specified", function () {
+ controller.setState(true);
+ expect(controller.isActive()).toBe(true);
+ controller.setState(true);
+ expect(controller.isActive()).toBe(true);
+ controller.setState(false);
+ expect(controller.isActive()).toBe(false);
+ controller.setState(false);
+ expect(controller.isActive()).toBe(false);
+ });
+
+ it("registers a mouse listener when activated", function () {
+ controller.setState(true);
+ expect(mockDocument.on).toHaveBeenCalled();
+ });
+
+ it("deactivates and detaches listener on document click", function () {
+ var callback;
+ controller.setState(true);
+ callback = mockDocument.on.mostRecentCall.args[1];
+ callback();
+ expect(controller.isActive()).toEqual(false);
+ expect(mockDocument.off).toHaveBeenCalledWith("mouseup", callback);
+ });
+
+
+
+ });
+ }
+);
\ No newline at end of file
diff --git a/platform/search/test/controllers/SearchControllerSpec.js b/platform/search/test/controllers/SearchControllerSpec.js
new file mode 100644
index 0000000000..720d9bd64a
--- /dev/null
+++ b/platform/search/test/controllers/SearchControllerSpec.js
@@ -0,0 +1,208 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+/*global define,describe,it,expect,beforeEach,jasmine*/
+
+/**
+ * SearchSpec. Created by shale on 07/31/2015.
+ */
+define(
+ ["../../src/controllers/SearchController"],
+ function (SearchController) {
+ "use strict";
+
+ // These should be the same as the ones on the top of the search controller
+ var INITIAL_LOAD_NUMBER = 20,
+ LOAD_INCREMENT = 20;
+
+ describe("The search controller", function () {
+ var mockScope,
+ mockSearchService,
+ mockPromise,
+ mockSearchResult,
+ mockDomainObject,
+ mockTypes,
+ controller;
+
+ function bigArray(size) {
+ var array = [],
+ i;
+ for (i = 0; i < size; i += 1) {
+ array.push(mockSearchResult);
+ }
+ return array;
+ }
+
+
+ beforeEach(function () {
+ mockScope = jasmine.createSpyObj(
+ "$scope",
+ [ "$watch" ]
+ );
+ mockScope.ngModel = {};
+ mockScope.ngModel.input = "test input";
+ mockScope.ngModel.checked = {};
+ mockScope.ngModel.checked['mock.type'] = true;
+ mockScope.ngModel.checkAll = true;
+
+ mockSearchService = jasmine.createSpyObj(
+ "searchService",
+ [ "query" ]
+ );
+ mockPromise = jasmine.createSpyObj(
+ "promise",
+ [ "then" ]
+ );
+ mockSearchService.query.andReturn(mockPromise);
+
+ mockTypes = [{key: 'mock.type', name: 'Mock Type', glyph: '?'}];
+
+ mockSearchResult = jasmine.createSpyObj(
+ "searchResult",
+ [ "" ]
+ );
+ mockDomainObject = jasmine.createSpyObj(
+ "domainObject",
+ [ "getModel" ]
+ );
+ mockSearchResult.object = mockDomainObject;
+ mockDomainObject.getModel.andReturn({name: 'Mock Object', type: 'mock.type'});
+
+ controller = new SearchController(mockScope, mockSearchService, mockTypes);
+ controller.search();
+ });
+
+ it("sends queries to the search service", function () {
+ expect(mockSearchService.query).toHaveBeenCalled();
+ });
+
+ it("populates the results with results from the search service", function () {
+ expect(mockPromise.then).toHaveBeenCalledWith(jasmine.any(Function));
+ mockPromise.then.mostRecentCall.args[0]({hits: []});
+
+ expect(mockScope.results).toBeDefined();
+ });
+
+ it("is loading until the service's promise fufills", function () {
+ // Send query
+ controller.search();
+ expect(mockScope.loading).toBeTruthy();
+
+ // Then resolve the promises
+ mockPromise.then.mostRecentCall.args[0]({hits: []});
+ expect(mockScope.loading).toBeFalsy();
+ });
+
+
+ it("displays only some results when there are many", function () {
+ expect(mockPromise.then).toHaveBeenCalledWith(jasmine.any(Function));
+ mockPromise.then.mostRecentCall.args[0]({hits: bigArray(100)});
+
+ expect(mockScope.results).toBeDefined();
+ expect(mockScope.results.length).toBeLessThan(100);
+ });
+
+ it("detects when there are more results", function () {
+ mockScope.ngModel.checkAll = false;
+
+ expect(mockPromise.then).toHaveBeenCalledWith(jasmine.any(Function));
+ mockPromise.then.mostRecentCall.args[0]({
+ hits: bigArray(INITIAL_LOAD_NUMBER + 5),
+ total: INITIAL_LOAD_NUMBER + 5
+ });
+ // bigArray gives searchResults of type 'mock.type'
+ mockScope.ngModel.checked['mock.type'] = false;
+ mockScope.ngModel.checked['mock.type.2'] = true;
+
+ expect(controller.areMore()).toBeFalsy();
+
+ mockScope.ngModel.checked['mock.type'] = true;
+
+ expect(controller.areMore()).toBeTruthy();
+ });
+
+ it("can load more results", function () {
+ var oldSize;
+
+ expect(mockPromise.then).toHaveBeenCalled();
+ mockPromise.then.mostRecentCall.args[0]({
+ hits: bigArray(INITIAL_LOAD_NUMBER + LOAD_INCREMENT + 1),
+ total: INITIAL_LOAD_NUMBER + LOAD_INCREMENT + 1
+ });
+ // These hits and total lengths are the case where the controller
+ // DOES NOT have to re-search to load more results
+ oldSize = mockScope.results.length;
+
+ expect(controller.areMore()).toBeTruthy();
+
+ controller.loadMore();
+ expect(mockScope.results.length).toBeGreaterThan(oldSize);
+ });
+
+ it("can re-search to load more results", function () {
+ var oldSize,
+ oldCallCount;
+
+ expect(mockPromise.then).toHaveBeenCalled();
+ mockPromise.then.mostRecentCall.args[0]({
+ hits: bigArray(INITIAL_LOAD_NUMBER + LOAD_INCREMENT - 1),
+ total: INITIAL_LOAD_NUMBER + LOAD_INCREMENT + 1
+ });
+ // These hits and total lengths are the case where the controller
+ // DOES have to re-search to load more results
+ oldSize = mockScope.results.length;
+ oldCallCount = mockPromise.then.callCount;
+ expect(controller.areMore()).toBeTruthy();
+
+ controller.loadMore();
+ expect(mockPromise.then).toHaveBeenCalled();
+ // Make sure that a NEW call to search has been made
+ expect(oldCallCount).toBeLessThan(mockPromise.then.callCount);
+ mockPromise.then.mostRecentCall.args[0]({
+ hits: bigArray(INITIAL_LOAD_NUMBER + LOAD_INCREMENT + 1),
+ total: INITIAL_LOAD_NUMBER + LOAD_INCREMENT + 1
+ });
+ expect(mockScope.results.length).toBeGreaterThan(oldSize);
+ });
+
+ it("sets the ngModel.search flag", function () {
+ // Flag should be true with nonempty input
+ expect(mockScope.ngModel.search).toEqual(true);
+
+ // Flag should be flase with empty input
+ mockScope.ngModel.input = "";
+ controller.search();
+ mockPromise.then.mostRecentCall.args[0]({hits: [], total: 0});
+ expect(mockScope.ngModel.search).toEqual(false);
+
+ // Both the empty string and undefined should be 'empty input'
+ mockScope.ngModel.input = undefined;
+ controller.search();
+ mockPromise.then.mostRecentCall.args[0]({hits: [], total: 0});
+ expect(mockScope.ngModel.search).toEqual(false);
+ });
+
+ it("has a default results list to filter from", function () {
+ expect(mockScope.ngModel.filter()).toBeDefined();
+ });
+ });
+ }
+);
\ No newline at end of file
diff --git a/platform/search/test/controllers/SearchMenuControllerSpec.js b/platform/search/test/controllers/SearchMenuControllerSpec.js
new file mode 100644
index 0000000000..9e31f461c5
--- /dev/null
+++ b/platform/search/test/controllers/SearchMenuControllerSpec.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,describe,it,expect,beforeEach,jasmine*/
+
+/**
+ * SearchSpec. Created by shale on 08/17/2015.
+ */
+define(
+ ["../../src/controllers/SearchMenuController"],
+ function (SearchMenuController) {
+ "use strict";
+
+ describe("The search menu controller", function () {
+ var mockScope,
+ mockPromise,
+ mockTypes,
+ controller;
+
+ beforeEach(function () {
+ mockScope = jasmine.createSpyObj(
+ "$scope",
+ [ "" ]
+ );
+
+ mockTypes = [
+ {key: 'mock.type.1', name: 'Mock Type 1', glyph: 'a'},
+ {key: 'mock.type.2', name: 'Mock Type 2', glyph: 'b'}
+ ];
+
+ mockScope.ngModel = {};
+ mockScope.ngModel.checked = {};
+ mockScope.ngModel.checked['mock.type.1'] = false;
+ mockScope.ngModel.checked['mock.type.2'] = false;
+ mockScope.ngModel.checkAll = true;
+ mockScope.ngModel.filter = jasmine.createSpy('$scope.ngModel.filter');
+ mockScope.ngModel.filtersString = '';
+
+ controller = new SearchMenuController(mockScope, mockTypes);
+ });
+
+ it("gets types on initiliztion", function () {
+ expect(mockScope.ngModel.types).toBeDefined();
+ });
+
+ it("refilters results when options are updated", function () {
+ controller.updateOptions();
+ expect(mockScope.ngModel.filter).toHaveBeenCalled();
+
+ controller.checkAll();
+ expect(mockScope.ngModel.filter).toHaveBeenCalled();
+ });
+
+ it("updates the filters string when options are updated", function () {
+ controller.updateOptions();
+ expect(mockScope.ngModel.filtersString).toEqual('');
+
+ mockScope.ngModel.checked['mock.type.1'] = true;
+
+ controller.updateOptions();
+ expect(mockScope.ngModel.filtersString).not.toEqual('');
+ });
+
+ it("changing checkAll status updates the filter string", function () {
+ controller.checkAll();
+ expect(mockScope.ngModel.filtersString).toEqual('');
+
+ mockScope.ngModel.checkAll = false;
+
+ controller.checkAll();
+ expect(mockScope.ngModel.filtersString).toEqual('NONE');
+ });
+
+ it("checking checkAll option resets other options", function () {
+ var type;
+
+ mockScope.ngModel.checked['mock.type.1'] = true;
+ mockScope.ngModel.checked['mock.type.2'] = true;
+
+ controller.checkAll();
+
+ for (type in mockScope.ngModel.checked) {
+ expect(mockScope.ngModel.checked[type]).toBeFalsy();
+ }
+ });
+
+ it("tells the user when no options are checked", function () {
+ var type;
+
+ for (type in mockScope.ngModel.checked) {
+ mockScope.ngModel.checked[type] = false;
+ }
+ mockScope.ngModel.checkAll = false;
+
+ controller.updateOptions();
+
+ expect(mockScope.ngModel.filtersString).toEqual('NONE');
+ });
+
+ it("tells the user when options are checked", function () {
+ var type;
+
+ mockScope.ngModel.checkAll = false;
+ for (type in mockScope.ngModel.checked) {
+ mockScope.ngModel.checked[type] = true;
+ }
+
+ controller.updateOptions();
+
+ expect(mockScope.ngModel.filtersString).not.toEqual('NONE');
+ expect(mockScope.ngModel.filtersString).not.toEqual('');
+ });
+ });
+ }
+);
\ No newline at end of file
diff --git a/platform/search/test/GenericSearchProviderSpec.js b/platform/search/test/services/GenericSearchProviderSpec.js
similarity index 76%
rename from platform/search/test/GenericSearchProviderSpec.js
rename to platform/search/test/services/GenericSearchProviderSpec.js
index bec02653b8..2da7cd343b 100644
--- a/platform/search/test/GenericSearchProviderSpec.js
+++ b/platform/search/test/services/GenericSearchProviderSpec.js
@@ -25,7 +25,7 @@
* SearchSpec. Created by shale on 07/31/2015.
*/
define(
- ["../src/GenericSearchProvider"],
+ ["../../src/services/GenericSearchProvider"],
function (GenericSearchProvider) {
"use strict";
@@ -81,6 +81,11 @@ define(
);
mockWorkerService.run.andReturn(mockWorker);
+ mockCapabilityPromise = jasmine.createSpyObj(
+ "promise",
+ [ "then", "catch" ]
+ );
+
mockDomainObjects = {};
for (i = 0; i < 4; i += 1) {
mockDomainObjects[i] = (
@@ -91,6 +96,7 @@ define(
);
mockDomainObjects[i].getId.andReturn(i);
mockDomainObjects[i].getCapability.andReturn(mockCapability);
+ mockDomainObjects[i].useCapability.andReturn(mockCapabilityPromise);
}
// Give the first object children
mockDomainObjects[0].hasCapability.andReturn(true);
@@ -98,10 +104,6 @@ define(
"capability",
[ "invoke", "listen" ]
);
- mockCapabilityPromise = jasmine.createSpyObj(
- "promise",
- [ "then", "catch" ]
- );
mockCapability.invoke.andReturn(mockCapabilityPromise);
mockDomainObjects[0].getCapability.andReturn(mockCapability);
@@ -112,13 +114,34 @@ define(
expect(mockObjectService.getObjects).toHaveBeenCalled();
expect(mockObjectPromise.then).toHaveBeenCalled();
+ // Call through the root-getting part
mockObjectPromise.then.mostRecentCall.args[0](mockDomainObjects);
- //mockCapabilityPromise.then.mostRecentCall.args[0](mockDomainObjects[1]);
+ // Call through the children-getting part
+ mockTimeout.mostRecentCall.args[0]();
+ // Array argument indicates multiple children
+ mockCapabilityPromise.then.mostRecentCall.args[0]([]);
+ mockTimeout.mostRecentCall.args[0]();
+ // Call again, but for single child
+ mockCapabilityPromise.then.mostRecentCall.args[0]({});
+ mockTimeout.mostRecentCall.args[0]();
expect(mockWorker.postMessage).toHaveBeenCalled();
});
+ it("when indexing, listens for composition changes", function () {
+ var mockListener = {composition: {}};
+
+ // Call indexItems
+ mockObjectPromise.then.mostRecentCall.args[0](mockDomainObjects);
+
+ // Call through listening for changes
+ expect(mockCapability.listen).toHaveBeenCalled();
+ mockCapability.listen.mostRecentCall.args[0](mockListener);
+ expect(mockObjectService.getObjects).toHaveBeenCalled();
+ mockObjectPromise.then.mostRecentCall.args[0](mockDomainObjects);
+ });
+
it("sends search queries to the worker", function () {
var timestamp = Date.now();
provider.query(' test "query" ', timestamp, 1, 2);
@@ -131,6 +154,19 @@ define(
});
});
+ it("gives an empty result for an empty query", function () {
+ var timestamp = Date.now(),
+ queryOutput;
+
+ queryOutput = provider.query('', timestamp, 1, 2);
+ expect(queryOutput.hits).toEqual([]);
+ expect(queryOutput.total).toEqual(0);
+
+ queryOutput = provider.query();
+ expect(queryOutput.hits).toEqual([]);
+ expect(queryOutput.total).toEqual(0);
+ });
+
it("handles responses from the worker", function () {
var timestamp = Date.now(),
event = {
diff --git a/platform/search/test/GenericSearchWorkerSpec.js b/platform/search/test/services/GenericSearchWorkerSpec.js
similarity index 99%
rename from platform/search/test/GenericSearchWorkerSpec.js
rename to platform/search/test/services/GenericSearchWorkerSpec.js
index 2e17858400..b95ec5a1bb 100644
--- a/platform/search/test/GenericSearchWorkerSpec.js
+++ b/platform/search/test/services/GenericSearchWorkerSpec.js
@@ -31,7 +31,7 @@ define(
describe("The generic search worker ", function () {
// If this test fails, make sure this path is correct
- var worker = new Worker(require.toUrl('platform/search/src/GenericSearchWorker.js')),
+ var worker = new Worker(require.toUrl('platform/search/src/services/GenericSearchWorker.js')),
numObjects = 5;
beforeEach(function () {
diff --git a/platform/search/test/SearchAggregatorSpec.js b/platform/search/test/services/SearchAggregatorSpec.js
similarity index 98%
rename from platform/search/test/SearchAggregatorSpec.js
rename to platform/search/test/services/SearchAggregatorSpec.js
index cf35e2928e..3205f0f9ec 100644
--- a/platform/search/test/SearchAggregatorSpec.js
+++ b/platform/search/test/services/SearchAggregatorSpec.js
@@ -25,7 +25,7 @@
* SearchSpec. Created by shale on 07/31/2015.
*/
define(
- ["../src/SearchAggregator"],
+ ["../../src/services/SearchAggregator"],
function (SearchAggregator) {
"use strict";
diff --git a/platform/search/test/suite.json b/platform/search/test/suite.json
index 5097bde635..803949fb6f 100644
--- a/platform/search/test/suite.json
+++ b/platform/search/test/suite.json
@@ -1,5 +1,8 @@
[
- "SearchAggregator",
- "GenericSearchProvider",
- "GenericSearchWorker"
+ "controllers/SearchController",
+ "controllers/SearchMenuController",
+ "controllers/ClickAwayController",
+ "services/SearchAggregator",
+ "services/GenericSearchProvider",
+ "services/GenericSearchWorker"
]