feat(aio): add search to 404 page (#19682)

The 404 page will now run a search based on the given URL to offer
suggestions for the page that the user really wanted.

PR Close #19682
This commit is contained in:
Peter Bacon Darwin 2017-10-12 10:59:25 +01:00 committed by Tobias Bosch
parent 88c46feb20
commit 91fcfcb042
6 changed files with 102 additions and 1 deletions

View File

@ -7,3 +7,4 @@
<p>We're sorry. The page you are looking for cannot be found.</p>
</div>
</div>
<aio-file-not-found-search></aio-file-not-found-search>

View File

@ -100,4 +100,12 @@ describe('site App', function() {
expect(page.getSearchResults().map(link => link.getText())).toContain('ControlValueAccessor');
});
});
describe('404 page', () => {
it('should search the index for words found in the url', () => {
page.navigateTo('http/router');
expect(page.getSearchResults().map(link => link.getText())).toContain('Http');
expect(page.getSearchResults().map(link => link.getText())).toContain('Router');
});
});
});

View File

@ -20,6 +20,7 @@ import { CodeTabsComponent } from './code/code-tabs.component';
import { ContributorListComponent } from './contributor/contributor-list.component';
import { ContributorComponent } from './contributor/contributor.component';
import { CurrentLocationComponent } from './current-location.component';
import { FileNotFoundSearchComponent } from './search/file-not-found-search.component';
import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example/live-example.component';
import { ResourceListComponent } from './resource/resource-list.component';
import { ResourceService } from './resource/resource.service';
@ -30,7 +31,8 @@ import { TocComponent } from './toc/toc.component';
*/
export const embeddedComponents: any[] = [
ApiListComponent, CodeExampleComponent, CodeTabsComponent, ContributorListComponent,
CurrentLocationComponent, LiveExampleComponent, ResourceListComponent, TocComponent
CurrentLocationComponent, FileNotFoundSearchComponent, LiveExampleComponent, ResourceListComponent,
TocComponent
];
/** Injectable class w/ property returning components that can be embedded in docs */

View File

@ -0,0 +1,49 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Subject } from 'rxjs/Subject';
import { LocationService } from 'app/shared/location.service';
import { MockLocationService } from 'testing/location.service';
import { SearchResults } from 'app/search/interfaces';
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
import { SearchService } from 'app/search/search.service';
import { FileNotFoundSearchComponent } from './file-not-found-search.component';
describe('FileNotFoundSearchComponent', () => {
let element: HTMLElement;
let fixture: ComponentFixture<FileNotFoundSearchComponent>;
let searchService: SearchService;
let searchResultSubject: Subject<SearchResults>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ FileNotFoundSearchComponent, SearchResultsComponent ],
providers: [
{ provide: LocationService, useValue: new MockLocationService('base/initial-url?some-query') },
SearchService
]
});
fixture = TestBed.createComponent(FileNotFoundSearchComponent);
searchService = TestBed.get(SearchService);
searchResultSubject = new Subject<SearchResults>();
spyOn(searchService, 'search').and.callFake(() => searchResultSubject.asObservable());
fixture.detectChanges();
element = fixture.nativeElement;
});
it('should run a search with a query built from the current url', () => {
expect(searchService.search).toHaveBeenCalledWith('base initial url');
});
it('should pass through any results to the `aio-search-results` component', () => {
const searchResultsComponent = fixture.debugElement.query(By.directive(SearchResultsComponent)).componentInstance;
expect(searchResultsComponent.searchResults).toBe(null);
const results = { query: 'base initial url', results: []};
searchResultSubject.next(results);
fixture.detectChanges();
expect(searchResultsComponent.searchResults).toEqual(results);
});
});

View File

@ -0,0 +1,23 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { LocationService } from 'app/shared/location.service';
import { SearchResults } from 'app/search/interfaces';
import { SearchService } from 'app/search/search.service';
@Component({
selector: 'aio-file-not-found-search',
template:
`<p>Let's see if any of these search results help...</p>
<aio-search-results class="embedded" [searchResults]="searchResults | async"></aio-search-results>`
})
export class FileNotFoundSearchComponent implements OnInit {
searchResults: Observable<SearchResults>;
constructor(private location: LocationService, private search: SearchService) {}
ngOnInit() {
this.searchResults = this.location.currentPath.switchMap(path => {
const query = path.split(/\W+/).join(' ');
return this.search.search(query);
});
}
}

View File

@ -29,6 +29,24 @@ aio-search-results {
}
}
aio-search-results.embedded .search-results {
padding: 0;
color: inherit;
width: auto;
max-height: 100%;
position: relative;
background-color: inherit;
box-shadow: none;
box-sizing: border-box;
.search-area a {
color: lighten($darkgray, 10);
&:hover {
color: $accentblue;
}
}
}
.search-area {
display: flex;
flex-direction: column;