mirror of
https://github.com/nasa/openmct.git
synced 2025-06-17 22:58:14 +00:00
[Search] use service for filters, add spec
Add a spec for the SearchController, and use the SearchService to execute filters by supplying a filterPredicate.
This commit is contained in:
@ -27,9 +27,6 @@
|
||||
define(function () {
|
||||
"use strict";
|
||||
|
||||
var INITIAL_LOAD_NUMBER = 20,
|
||||
LOAD_INCREMENT = 20;
|
||||
|
||||
/**
|
||||
* Controller for search in Tree View.
|
||||
*
|
||||
@ -50,9 +47,8 @@ define(function () {
|
||||
var controller = this;
|
||||
this.$scope = $scope;
|
||||
this.searchService = searchService;
|
||||
this.numberToDisplay = INITIAL_LOAD_NUMBER;
|
||||
this.fullResults = [];
|
||||
this.filteredResults = [];
|
||||
this.numberToDisplay = this.RESULTS_PER_PAGE;
|
||||
this.availabileResults = 0;
|
||||
this.$scope.results = [];
|
||||
this.$scope.loading = false;
|
||||
this.pendingQuery = undefined;
|
||||
@ -61,28 +57,30 @@ define(function () {
|
||||
};
|
||||
}
|
||||
|
||||
SearchController.prototype.RESULTS_PER_PAGE = 20;
|
||||
|
||||
/**
|
||||
* Returns true if there are more results than currently displayed for the
|
||||
* for the current query and filters.
|
||||
*/
|
||||
SearchController.prototype.areMore = function () {
|
||||
return this.$scope.results.length < this.filteredResults.length;
|
||||
return this.$scope.results.length < this.availableResults;
|
||||
};
|
||||
|
||||
/**
|
||||
* Display more results for the currently displayed query and filters.
|
||||
*/
|
||||
SearchController.prototype.loadMore = function () {
|
||||
this.numberToDisplay += LOAD_INCREMENT;
|
||||
this.updateResults();
|
||||
this.numberToDisplay += this.RESULTS_PER_PAGE;
|
||||
this.dispatchSearch();
|
||||
};
|
||||
|
||||
/**
|
||||
* Search for the query string specified in scope.
|
||||
* Reset search results, then search for the query string specified in
|
||||
* scope.
|
||||
*/
|
||||
SearchController.prototype.search = function () {
|
||||
var inputText = this.$scope.ngModel.input,
|
||||
controller = this;
|
||||
var inputText = this.$scope.ngModel.input;
|
||||
|
||||
this.clearResults();
|
||||
|
||||
@ -96,50 +94,63 @@ define(function () {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pendingQuery === inputText) {
|
||||
this.dispatchSearch();
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatch a search to the search service if it hasn't already been
|
||||
* dispatched.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
SearchController.prototype.dispatchSearch = function () {
|
||||
var inputText = this.$scope.ngModel.input,
|
||||
controller = this,
|
||||
queryId = inputText + this.numberToDisplay;
|
||||
|
||||
if (this.pendingQuery === queryId) {
|
||||
return; // don't issue multiple queries for the same term.
|
||||
}
|
||||
|
||||
this.pendingQuery = inputText;
|
||||
this.pendingQuery = queryId;
|
||||
|
||||
this
|
||||
.searchService
|
||||
.query(inputText, 60) // TODO: allow filter in search service.
|
||||
.query(inputText, this.numberToDisplay, this.filterPredicate())
|
||||
.then(function (results) {
|
||||
if (controller.pendingQuery !== inputText) {
|
||||
if (controller.pendingQuery !== queryId) {
|
||||
return; // another query in progress, so skip this one.
|
||||
}
|
||||
controller.onSearchComplete(results);
|
||||
});
|
||||
};
|
||||
|
||||
SearchController.prototype.filter = SearchController.prototype.onFilterChange;
|
||||
|
||||
/**
|
||||
* Refilter results and update visible results when filters have changed.
|
||||
*/
|
||||
SearchController.prototype.onFilterChange = function () {
|
||||
this.filter();
|
||||
this.updateVisibleResults();
|
||||
this.pendingQuery = undefined;
|
||||
this.search();
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter `fullResults` based on currenly active filters, storing the result
|
||||
* in `filteredResults`.
|
||||
* Returns a predicate function that can be used to filter object models.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
SearchController.prototype.filter = function () {
|
||||
var includeTypes = this.$scope.ngModel.checked;
|
||||
|
||||
SearchController.prototype.filterPredicate = function () {
|
||||
if (this.$scope.ngModel.checkAll) {
|
||||
this.filteredResults = this.fullResults;
|
||||
return;
|
||||
}
|
||||
|
||||
this.filteredResults = this.fullResults.filter(function (hit) {
|
||||
return includeTypes[hit.object.getModel().type];
|
||||
});
|
||||
return function () {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
var includeTypes = this.$scope.ngModel.checked;
|
||||
return function (model) {
|
||||
return !!includeTypes[model.type];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clear the search results.
|
||||
@ -148,35 +159,22 @@ define(function () {
|
||||
*/
|
||||
SearchController.prototype.clearResults = function () {
|
||||
this.$scope.results = [];
|
||||
this.fullResults = [];
|
||||
this.filteredResults = [];
|
||||
this.numberToDisplay = INITIAL_LOAD_NUMBER;
|
||||
this.availableResults = 0;
|
||||
this.numberToDisplay = this.RESULTS_PER_PAGE;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Update search results from given `results`.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
SearchController.prototype.onSearchComplete = function (results) {
|
||||
this.fullResults = results.hits;
|
||||
this.filter();
|
||||
this.updateVisibleResults();
|
||||
this.availableResults = results.total;
|
||||
this.$scope.results = results.hits;
|
||||
this.$scope.loading = false;
|
||||
this.pendingQuery = undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update visible results from filtered results.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
SearchController.prototype.updateVisibleResults = function () {
|
||||
this.$scope.results =
|
||||
this.filteredResults.slice(0, this.numberToDisplay);
|
||||
};
|
||||
|
||||
return SearchController;
|
||||
});
|
||||
|
@ -4,12 +4,12 @@
|
||||
* 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.
|
||||
* '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
|
||||
* 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.
|
||||
@ -24,16 +24,14 @@
|
||||
/**
|
||||
* SearchSpec. Created by shale on 07/31/2015.
|
||||
*/
|
||||
define(
|
||||
["../../src/controllers/SearchController"],
|
||||
function (SearchController) {
|
||||
"use strict";
|
||||
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 () {
|
||||
describe('The search controller', function () {
|
||||
var mockScope,
|
||||
mockSearchService,
|
||||
mockPromise,
|
||||
@ -54,34 +52,34 @@ define(
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
[ "$watch" ]
|
||||
'$scope',
|
||||
[ '$watch' ]
|
||||
);
|
||||
mockScope.ngModel = {};
|
||||
mockScope.ngModel.input = "test input";
|
||||
mockScope.ngModel.input = 'test input';
|
||||
mockScope.ngModel.checked = {};
|
||||
mockScope.ngModel.checked['mock.type'] = true;
|
||||
mockScope.ngModel.checkAll = true;
|
||||
|
||||
mockSearchService = jasmine.createSpyObj(
|
||||
"searchService",
|
||||
[ "query" ]
|
||||
'searchService',
|
||||
[ 'query' ]
|
||||
);
|
||||
mockPromise = jasmine.createSpyObj(
|
||||
"promise",
|
||||
[ "then" ]
|
||||
'promise',
|
||||
[ 'then' ]
|
||||
);
|
||||
mockSearchService.query.andReturn(mockPromise);
|
||||
|
||||
mockTypes = [{key: 'mock.type', name: 'Mock Type', glyph: '?'}];
|
||||
|
||||
mockSearchResult = jasmine.createSpyObj(
|
||||
"searchResult",
|
||||
[ "" ]
|
||||
'searchResult',
|
||||
[ '' ]
|
||||
);
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getModel" ]
|
||||
'domainObject',
|
||||
[ 'getModel' ]
|
||||
);
|
||||
mockSearchResult.object = mockDomainObject;
|
||||
mockDomainObject.getModel.andReturn({name: 'Mock Object', type: 'mock.type'});
|
||||
@ -90,20 +88,44 @@ define(
|
||||
controller.search();
|
||||
});
|
||||
|
||||
it("sends queries to the search service", function () {
|
||||
expect(mockSearchService.query).toHaveBeenCalled();
|
||||
it('has a default number of results per page', function () {
|
||||
expect(controller.RESULTS_PER_PAGE).toBe(20);
|
||||
});
|
||||
|
||||
it("populates the results with results from the search service", function () {
|
||||
it('sends queries to the search service', function () {
|
||||
expect(mockSearchService.query).toHaveBeenCalledWith(
|
||||
'test input',
|
||||
controller.RESULTS_PER_PAGE,
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
describe('filter query function', function () {
|
||||
it('returns true when all types allowed', function () {
|
||||
mockScope.ngModel.checkAll = true;
|
||||
controller.onFilterChange();
|
||||
var filterFn = mockSearchService.query.mostRecentCall.args[2];
|
||||
expect(filterFn('askbfa')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true only for matching checked types', function () {
|
||||
mockScope.ngModel.checkAll = false;
|
||||
controller.onFilterChange();
|
||||
var filterFn = mockSearchService.query.mostRecentCall.args[2];
|
||||
expect(filterFn({type: 'mock.type'})).toBe(true);
|
||||
expect(filterFn({type: 'other.type'})).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('populates the results with results from the search service', function () {
|
||||
expect(mockPromise.then).toHaveBeenCalledWith(jasmine.any(Function));
|
||||
mockPromise.then.mostRecentCall.args[0]({hits: []});
|
||||
mockPromise.then.mostRecentCall.args[0]({hits: ['a']});
|
||||
|
||||
expect(mockScope.results).toBeDefined();
|
||||
expect(mockScope.results.length).toBe(1);
|
||||
expect(mockScope.results).toContain('a');
|
||||
});
|
||||
|
||||
it("is loading until the service's promise fufills", function () {
|
||||
// Send query
|
||||
controller.search();
|
||||
it('is loading until the service\'s promise fufills', function () {
|
||||
expect(mockScope.loading).toBeTruthy();
|
||||
|
||||
// Then resolve the promises
|
||||
@ -112,7 +134,7 @@ define(
|
||||
});
|
||||
|
||||
|
||||
it("displays only some results when there are many", function () {
|
||||
xit('displays only some results when there are many', function () {
|
||||
expect(mockPromise.then).toHaveBeenCalledWith(jasmine.any(Function));
|
||||
mockPromise.then.mostRecentCall.args[0]({hits: bigArray(100)});
|
||||
|
||||
@ -120,26 +142,35 @@ define(
|
||||
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));
|
||||
it('detects when there are more results', 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();
|
||||
hits: bigArray(controller.RESULTS_PER_PAGE),
|
||||
total: controller.RESULTS_PER_PAGE + 5
|
||||
});
|
||||
|
||||
it("can load more results", function () {
|
||||
expect(mockScope.results.length).toBe(controller.RESULTS_PER_PAGE);
|
||||
expect(controller.areMore()).toBeTruthy;
|
||||
|
||||
controller.loadMore();
|
||||
|
||||
expect(mockSearchService.query).toHaveBeenCalledWith(
|
||||
'test input',
|
||||
controller.RESULTS_PER_PAGE * 2,
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
mockPromise.then.mostRecentCall.args[0]({
|
||||
hits: bigArray(controller.RESULTS_PER_PAGE + 5),
|
||||
total: controller.RESULTS_PER_PAGE + 5
|
||||
});
|
||||
|
||||
expect(mockScope.results.length)
|
||||
.toBe(controller.RESULTS_PER_PAGE + 5);
|
||||
|
||||
expect(controller.areMore()).toBe(false);
|
||||
});
|
||||
|
||||
xit('can load more results', function () {
|
||||
var oldSize;
|
||||
|
||||
expect(mockPromise.then).toHaveBeenCalled();
|
||||
@ -157,7 +188,7 @@ define(
|
||||
expect(mockScope.results.length).toBeGreaterThan(oldSize);
|
||||
});
|
||||
|
||||
it("can re-search to load more results", function () {
|
||||
xit('can re-search to load more results', function () {
|
||||
var oldSize,
|
||||
oldCallCount;
|
||||
|
||||
@ -183,12 +214,12 @@ define(
|
||||
expect(mockScope.results.length).toBeGreaterThan(oldSize);
|
||||
});
|
||||
|
||||
it("sets the ngModel.search flag", function () {
|
||||
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 = "";
|
||||
mockScope.ngModel.input = '';
|
||||
controller.search();
|
||||
mockPromise.then.mostRecentCall.args[0]({hits: [], total: 0});
|
||||
expect(mockScope.ngModel.search).toEqual(false);
|
||||
@ -200,9 +231,8 @@ define(
|
||||
expect(mockScope.ngModel.search).toEqual(false);
|
||||
});
|
||||
|
||||
it("has a default results list to filter from", function () {
|
||||
expect(mockScope.ngModel.filter()).toBeDefined();
|
||||
it('attaches a filter function to scope', function () {
|
||||
expect(mockScope.ngModel.filter).toEqual(jasmine.any(Function));
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
Reference in New Issue
Block a user