chore(ngdocs): provide test code for lunr search in docs

This commit is contained in:
Matias Niemelä 2013-06-18 09:37:29 -04:00
parent 46dfb92afd
commit f6be59c1b9
4 changed files with 200 additions and 88 deletions

View file

@ -0,0 +1,72 @@
describe("DocsNavigationCtrl", function() {
beforeEach(module('docsApp'));
var ctrl, $scope;
beforeEach(function() {
module(function($provide) {
$provide.value('docsPages', []);
$provide.factory('docsSearch', function() {
return function(q) {
return ['one','two','three'];
};
});
});
inject(function($controller, $rootScope, $location, docsSearch) {
$scope = $rootScope.$new();
ctrl = $controller('DocsNavigationCtrl', {
$scope : $scope,
$location : $location,
docsSearch : docsSearch
});
});
});
it("should search and return data from docsSearch", function() {
$scope.search('1234')
expect($scope.results.join(',')).toBe('one,two,three');
expect($scope.hasResults).toBe(true);
});
it("should avoid searching if the search term is too short", function() {
$scope.search('1')
expect($scope.results.length).toBe(0);
expect($scope.hasResults).toBe(false);
});
it("should set the columns classname based on the total grouped results", function() {
$scope.search('1234');
expect($scope.colClassName).toBe('cols-3');
$scope.search('1');
expect($scope.colClassName).toBe(null);
});
it("should hide and clear the results when called", function() {
$scope.hasResults = true;
$scope.results = ['one'];
$scope.colClassName = '...';
$scope.hideResults();
expect($scope.hasResults).toBe(false);
expect($scope.results.length).toBe(0);
expect($scope.colClassName).toBe(null);
});
it("should hide, clear and change the path of the page when submitted", inject(function($location) {
$scope.hasResults = true;
$scope.results = {
api : [
{url : '/home'}
],
tutorial : [
{url : '/tutorial'}
]
};
$scope.submit();
expect($location.path()).toBe('/home');
expect($scope.results.length).toBe(0);
expect($scope.hasResults).toBe(false);
}));
});

View file

@ -0,0 +1,59 @@
describe("docsSearch", function() {
beforeEach(module('docsApp'));
var interceptedLunrResults;
beforeEach(function() {
interceptedLunrResults = [];
});
beforeEach(function() {
module(function($provide) {
var results = [];
results[0] = { section: 'tutorial', shortName: 'item one', keywords: 'item, one, 1' };
results[1] = { section: 'tutorial', shortName: 'item man', keywords: 'item, man' };
results[2] = { section: 'api', shortName: 'item other', keywords: 'item, other' };
results[3] = { section: 'cookbook', shortName: 'item cookbook', keywords: 'item, other' };
results[4] = { section: 'api', shortName: 'ngRepeat', keywords: 'item, other' };
$provide.value('NG_PAGES', results);
$provide.factory('lunrSearch', function() {
return function() {
return {
store : function(value) {
interceptedLunrResults.push(value);
},
search : function(q) {
var data = [];
angular.forEach(results, function(res, i) {
data.push({ ref : i });
});
return data;
}
}
};
});
});
});
it("should lookup and organize values properly", inject(function(docsSearch) {
var items = docsSearch('item');
expect(items['api'].length).toBe(2);
}));
it("should place cookbook items in the tutorial", inject(function(docsSearch) {
var items = docsSearch('item');
expect(items['tutorial'].length).toBe(3);
}));
it("should return all results without a search", inject(function(docsSearch) {
var items = docsSearch();
expect(items['tutorial'].length).toBe(3);
expect(items['api'].length).toBe(2);
}));
it("should store values with and without a ng prefix", inject(function(docsSearch) {
expect(interceptedLunrResults[4].title).toBe('ngRepeat repeat');
}));
});

View file

@ -461,6 +461,7 @@ pre ol li {
box-shadow:-6px 0 5px #555;
display:block;
border-radius:10px;
}
.docs-version-jump {
width:180px;

View file

@ -62,29 +62,31 @@ docsApp.controller.DocsVersionsCtrl = ['$scope', '$window', 'NG_VERSIONS', funct
};
}];
docsApp.controller.DocsNavigationCtrl = ['$scope', 'fullTextSearch', '$location', function($scope, fullTextSearch, $location) {
fullTextSearch.init();
docsApp.controller.DocsNavigationCtrl = ['$scope', '$location', 'docsSearch', function($scope, $location, docsSearch) {
function clearResults() {
$scope.results = [];
$scope.colClassName = null;
$scope.hasResults = false;
}
$scope.search = function(q) {
fullTextSearch.search(q, function(results) {
if(q && q.length >= 4) {
$scope.results = results;
var totalSections = 0;
for(var i in results) {
++totalSections;
}
if(totalSections > 0) {
$scope.colClassName = 'cols-' + totalSections;
$scope.hasResults = true;
}
else {
$scope.hasResults = false;
}
var MIN_SEARCH_LENGTH = 4;
if(q.length >= MIN_SEARCH_LENGTH) {
var results = docsSearch(q);
var totalSections = 0;
for(var i in results) {
++totalSections;
}
else {
$scope.hasResults = false;
if(totalSections > 0) {
$scope.colClassName = 'cols-' + totalSections;
$scope.hasResults = true;
}
if(!$scope.$$phase) $scope.$apply();
});
$scope.results = results;
}
else {
clearResults();
}
if(!$scope.$$phase) $scope.$apply();
};
$scope.submit = function() {
var result;
@ -100,82 +102,60 @@ docsApp.controller.DocsNavigationCtrl = ['$scope', 'fullTextSearch', '$location'
}
};
$scope.hideResults = function() {
$scope.hasResults = false;
clearResults();
$scope.q = '';
};
}];
docsApp.serviceFactory.fullTextSearch = ['$q', '$rootScope', 'NG_PAGES', function($q, $rootScope, NG_PAGES) {
return {
dbName : 'docs',
indexName : 'docsindex',
docsApp.serviceFactory.lunrSearch = function() {
return function(properties) {
var engine = lunr(properties);
return {
store : function(values) {
engine.add(values);
},
search : function(q) {
return engine.search(q);
}
};
};
};
init : function(onReady) {
this.init = function() {};
docsApp.serviceFactory.docsSearch = ['$rootScope','lunrSearch', 'NG_PAGES',
function($rootScope, lunrSearch, NG_PAGES) {
var self = this;
this.deferReady = $q.defer();
this.readyPromise = this.deferReady.promise;
var index = lunrSearch(function() {
this.ref('id');
this.field('title', {boost: 50});
this.field('description', { boost : 20 });
});
this.engine = lunr(function () {
this.ref('id');
this.field('title', {boost: 50});
this.field('description', { boost : 20 });
});
this.prepare();
this.onReady();
},
onReady : function() {
this.ready = true;
var self = this;
self.deferReady.resolve();
if(!$rootScope.$$phase) {
$rootScope.$apply();
}
},
whenReady : function(fn) {
if(this.ready) {
fn();
}
else {
this.init();
this.readyPromise.then(fn);
}
},
prepare : function(injector, callback) {
for(var i=0;i<NG_PAGES.length;i++) {
var page = NG_PAGES[i];
var title = page.shortName;
if(title.charAt(0) == 'n' && title.charAt(1) == 'g') {
title = title + ' ' + title.substr(2);
}
this.engine.add({
id: i,
title: title,
description: page.keywords
});
}
},
search : function(q, onReady) {
var self = this;
this.whenReady(function() {
var data = [];
var results = self.engine.search(q);
var groups = {};
angular.forEach(results, function(result) {
var item = NG_PAGES[result.ref];
var section = item.section;
if(section == 'cookbook') {
section = 'tutorial';
}
groups[section] = groups[section] || [];
if(groups[section].length < 15) {
groups[section].push(item);
}
});
onReady(groups);
});
angular.forEach(NG_PAGES, function(page, i) {
var title = page.shortName;
if(title.charAt(0) == 'n' && title.charAt(1) == 'g') {
title = title + ' ' + title.charAt(2).toLowerCase() + title.substr(3);
}
index.store({
id: i,
title: title,
description: page.keywords
});
});
return function(q) {
var results = {};
angular.forEach(index.search(q), function(result) {
var item = NG_PAGES[result.ref];
var section = item.section;
if(section == 'cookbook') {
section = 'tutorial';
}
results[section] = results[section] || [];
if(results[section].length < 15) {
results[section].push(item);
}
});
return results;
};
}];