diff --git a/aio/e2e/app.e2e-spec.ts b/aio/e2e/app.e2e-spec.ts index 83bff399ab..30a7e8b73f 100644 --- a/aio/e2e/app.e2e-spec.ts +++ b/aio/e2e/app.e2e-spec.ts @@ -92,4 +92,10 @@ describe('site App', function() { // Todo: add test to confirm tracking URL when navigate. }); + describe('search', () => { + it('should find pages when searching by a partial word in the title', () => { + page.enterSearch('ngCont'); + expect(page.getSearchResults().map(link => link.getText())).toContain('NgControl'); + }); + }); }); diff --git a/aio/e2e/app.po.ts b/aio/e2e/app.po.ts index 8de49d0f29..75d8fac9db 100644 --- a/aio/e2e/app.po.ts +++ b/aio/e2e/app.po.ts @@ -1,4 +1,4 @@ -import { browser, element, by, promise, ElementFinder } from 'protractor'; +import { browser, element, by, promise, ElementFinder, ExpectedConditions } from 'protractor'; const githubRegex = /https:\/\/github.com\/angular\/angular\//; @@ -50,6 +50,16 @@ export class SitePage { return browser.executeScript('window.scrollTo(0, document.body.scrollHeight)'); } + enterSearch(query: string) { + element(by.css('.search-container input[type=search]')).sendKeys(query); + } + + getSearchResults() { + const results = element.all(by.css('.search-results li')); + browser.wait(ExpectedConditions.presenceOf(results.first()), 8000); + return results; + } + /** * Replace the ambient Google Analytics tracker with homebrew spy * don't send commands to GA during e2e testing! diff --git a/aio/src/app/search/search-worker.js b/aio/src/app/search/search-worker.js index 85d112bbca..204a787318 100644 --- a/aio/src/app/search/search-worker.js +++ b/aio/src/app/search/search-worker.js @@ -9,7 +9,18 @@ var SEARCH_TERMS_URL = '/generated/docs/app/search-data.json'; importScripts('/assets/js/lunr.min.js'); var index; -var pages = {}; +var pages /* : SearchInfo */ = {}; + +// interface SearchInfo { +// [key: string]: PageInfo; +// } + +// interface PageInfo { +// path: string; +// type: string, +// titleWords: string; +// keyWords: string; +// } self.onmessage = handleMessage; @@ -49,15 +60,7 @@ function handleMessage(message) { // Use XHR to make a request to the server function makeRequest(url, callback) { - // The JSON file that is loaded should be an array of SearchTerms: - // - // export interface SearchTerms { - // path: string; - // type: string, - // titleWords: string; - // keyWords: string; - // } - + // The JSON file that is loaded should be an array of PageInfo: var searchDataRequest = new XMLHttpRequest(); searchDataRequest.onload = function() { callback(JSON.parse(this.responseText)); @@ -68,11 +71,11 @@ function makeRequest(url, callback) { // Create the search index from the searchInfo which contains the information about each page to be indexed -function loadIndex(searchInfo) { +function loadIndex(searchInfo /*: SearchInfo */) { return function(index) { // Store the pages data to be used in mapping query results back to pages // Add search terms from each page to the search index - searchInfo.forEach(function(page) { + searchInfo.forEach(function(page /*: PageInfo */) { index.add(page); pages[page.path] = page; }); @@ -81,7 +84,19 @@ function loadIndex(searchInfo) { // Query the index and return the processed results function queryIndex(query) { - var results = index.search(query); - // Only return the array of paths to pages - return results.map(function(hit) { return pages[hit.ref]; }); + try { + if (query.length) { + // Add a relaxed search in the title for the first word in the query + // E.g. if the search is "ngCont guide" then we search for "ngCont guide titleWords:ngCont*" + var titleQuery = 'titleWords:' + query.split(' ', 1)[0] + '*'; + var results = index.search(query + ' ' + titleQuery); + // Map the hits into info about each page to be returned as results + return results.map(function(hit) { return pages[hit.ref]; }); + } + } catch(e) { + // If the search query cannot be parsed the index throws an error + // Log it and recover + console.log(e); + } + return []; }