+
+
+
Search Results
-
+
{{area.name}} ({{area.pages.length}})
-
-
+
+
+
No results found.
-
+
diff --git a/aio/src/app/search/search-results/search-results.component.spec.ts b/aio/src/app/search/search-results/search-results.component.spec.ts
index 3ecc276de8..d191efd890 100644
--- a/aio/src/app/search/search-results/search-results.component.spec.ts
+++ b/aio/src/app/search/search-results/search-results.component.spec.ts
@@ -9,9 +9,7 @@ import { MockSearchService } from 'testing/search.service';
describe('SearchResultsComponent', () => {
let component: SearchResultsComponent;
let fixture: ComponentFixture
;
- let searchService: SearchService;
let searchResults: Subject;
- let currentAreas: SearchArea[];
/** Get all text from component element */
function getText() { return fixture.debugElement.nativeElement.innerText; }
@@ -48,17 +46,15 @@ describe('SearchResultsComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(SearchResultsComponent);
component = fixture.componentInstance;
- searchService = fixture.debugElement.injector.get(SearchService);
- searchResults = searchService.searchResults as Subject;
+ searchResults = TestBed.get(SearchService).searchResults;
fixture.detectChanges();
- component.searchAreas.subscribe(areas => currentAreas = areas);
});
it('should map the search results into groups based on their containing folder', () => {
const results = getTestResults(3);
searchResults.next({ query: '', results: results});
- expect(currentAreas).toEqual([
+ expect(component.searchAreas).toEqual([
{ name: 'api', pages: [
{ path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' }
], priorityPages: [] },
@@ -74,7 +70,7 @@ describe('SearchResultsComponent', () => {
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '' },
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' },
]});
- expect(currentAreas).toEqual([
+ expect(component.searchAreas).toEqual([
{ name: 'tutorial', pages: [
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' },
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '' },
@@ -86,7 +82,7 @@ describe('SearchResultsComponent', () => {
const results = getTestResults(5);
searchResults.next({ query: '', results: results });
- expect(currentAreas).toEqual([
+ expect(component.searchAreas).toEqual([
{ name: 'api', pages: [
{ path: 'api/c', title: 'API C', type: '', keywords: '', titleWords: '' },
{ path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' },
@@ -116,7 +112,7 @@ describe('SearchResultsComponent', () => {
];
searchResults.next({ query: '', results: results });
- expect(currentAreas).toEqual(expected);
+ expect(component.searchAreas).toEqual(expected);
});
it('should put search results with no containing folder into the default area (other)', () => {
@@ -125,7 +121,7 @@ describe('SearchResultsComponent', () => {
];
searchResults.next({ query: '', results: results });
- expect(currentAreas).toEqual([
+ expect(component.searchAreas).toEqual([
{ name: 'other', pages: [
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
], priorityPages: [] }
@@ -137,73 +133,30 @@ describe('SearchResultsComponent', () => {
{ path: 'news', title: undefined, type: 'marketing', keywords: '', titleWords: '' }
];
- searchResults.next({ query: '', results: results });
- expect(currentAreas).toEqual([]);
+ searchResults.next({ query: 'something', results: results });
+ expect(component.searchAreas).toEqual([]);
});
- it('should emit an "resultSelected" event when a search result anchor is clicked', () => {
- let selectedResult: SearchResult;
- component.resultSelected.subscribe((result: SearchResult) => selectedResult = result);
- const results = [
- { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
- ];
+ it('should emit a "resultSelected" event when a search result anchor is clicked', () => {
+ const searchResult = { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' };
+ let selected: SearchResult;
+ component.resultSelected.subscribe(result => selected = result);
- searchResults.next({ query: '', results: results });
+ searchResults.next({ query: 'something', results: [searchResult] });
fixture.detectChanges();
- const anchor = fixture.debugElement.query(By.css('a'));
+ expect(selected).toBeUndefined();
- anchor.triggerEventHandler('click', {});
- expect(selectedResult).toEqual({ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' });
- });
-
- it('should clear the results when a search result is clicked', () => {
- const results = [
- { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
- ];
-
- searchResults.next({ query: '', results: results });
- fixture.detectChanges();
const anchor = fixture.debugElement.query(By.css('a'));
anchor.triggerEventHandler('click', {});
-
fixture.detectChanges();
- expect(fixture.debugElement.queryAll(By.css('a'))).toEqual([]);
- });
-
- describe('hideResults', () => {
- it('should clear the results', () => {
- const results = [
- { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
- ];
-
- searchResults.next({ query: '', results: results });
- fixture.detectChanges();
- component.hideResults();
- fixture.detectChanges();
- expect(getText()).toBe('');
- });
+ expect(selected).toEqual(searchResult);
});
describe('when no query results', () => {
-
it('should display "not found" message', () => {
searchResults.next({ query: 'something', results: [] });
fixture.detectChanges();
expect(getText()).toContain('No results');
});
-
- it('should not display "not found" message after hideResults()', () => {
- searchResults.next({ query: 'something', results: [] });
- fixture.detectChanges();
- component.hideResults();
- fixture.detectChanges();
- expect(getText()).toBe('');
- });
-
- it('should not display "not found" message when query is empty', () => {
- searchResults.next({ query: '', results: [] });
- fixture.detectChanges();
- expect(getText()).toBe('');
- });
});
});
diff --git a/aio/src/app/search/search-results/search-results.component.ts b/aio/src/app/search/search-results/search-results.component.ts
index b5e4fabaa2..6ff179bf82 100644
--- a/aio/src/app/search/search-results/search-results.component.ts
+++ b/aio/src/app/search/search-results/search-results.component.ts
@@ -1,5 +1,6 @@
-import { Component, ChangeDetectionStrategy, EventEmitter, HostListener, OnInit, Output } from '@angular/core';
-import { ReplaySubject } from 'rxjs/ReplaySubject';
+import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { Subscription } from 'rxjs/Subscription';
import { SearchResult, SearchResults, SearchService } from '../search.service';
@@ -15,50 +16,41 @@ export interface SearchArea {
@Component({
selector: 'aio-search-results',
templateUrl: './search-results.component.html',
- changeDetection: ChangeDetectionStrategy.OnPush
})
-export class SearchResultsComponent implements OnInit {
+export class SearchResultsComponent implements OnInit, OnDestroy {
+ private resultsSubscription: Subscription;
readonly defaultArea = 'other';
readonly topLevelFolders = ['guide', 'tutorial'];
- notFound = false;
-
+ /**
+ * Emitted when the user selects a search result
+ */
@Output()
resultSelected = new EventEmitter();
/**
* A mapping of the search results grouped into areas
*/
- searchAreas = new ReplaySubject(1);
- hasAreas = this.searchAreas.map(areas => areas.length > 0);
+ searchAreas: SearchArea[] = [];
constructor(private searchService: SearchService) {}
ngOnInit() {
- this.searchService.searchResults.subscribe(search => this.searchAreas.next(this.processSearchResults(search)));
+ this.resultsSubscription = this.searchService.searchResults
+ .subscribe(search => this.searchAreas = this.processSearchResults(search));
}
- onResultSelected(result: SearchResult) {
- this.resultSelected.emit(result);
- this.hideResults();
+ ngOnDestroy() {
+ this.resultsSubscription.unsubscribe();
}
- @HostListener('document:keyup', ['$event.which'])
- onKeyUp(keyCode: number) {
- if (keyCode === 27) {
- this.hideResults();
- }
- }
-
- hideResults() {
- this.searchAreas.next([]);
- this.notFound = false;
+ onResultSelected(page: SearchResult) {
+ this.resultSelected.emit(page);
}
// Map the search results into groups by area
private processSearchResults(search: SearchResults) {
- this.notFound = search.query.trim() && search.results.length === 0;
const searchAreaMap = {};
search.results.forEach(result => {
if (!result.title) { return; } // bad data; should fix
@@ -72,7 +64,7 @@ export class SearchResultsComponent implements OnInit {
const priorityPages = pages.length > 10 ? searchAreaMap[name].slice(0, 5) : [];
pages = pages.sort(compareResults);
return { name, pages, priorityPages };
- });
+ });
}
// Split the search result path and use the top level folder, if there is one, as the area name.
diff --git a/aio/src/app/search/search.service.ts b/aio/src/app/search/search.service.ts
index f3840435e0..7e37b38521 100644
--- a/aio/src/app/search/search.service.ts
+++ b/aio/src/app/search/search.service.ts
@@ -6,7 +6,7 @@ can be found in the LICENSE file at http://angular.io/license
import { NgZone, Injectable, Type } from '@angular/core';
import { Observable } from 'rxjs/Observable';
-import { Subject } from 'rxjs/Subject';
+import { ReplaySubject } from 'rxjs/ReplaySubject';
import 'rxjs/add/operator/publishLast';
import 'rxjs/add/operator/concatMap';
import { WebWorkerClient } from 'app/shared/web-worker';
@@ -29,7 +29,7 @@ export interface SearchResult {
export class SearchService {
private worker: WebWorkerClient;
private ready: Observable;
- private resultsSubject = new Subject();
+ private resultsSubject = new ReplaySubject(1);
readonly searchResults = this.resultsSubject.asObservable();
constructor(private zone: NgZone) {}