diff --git a/aio/src/app/navigation/navigation.service.spec.ts b/aio/src/app/navigation/navigation.service.spec.ts index 847fa91829..7e826b895e 100644 --- a/aio/src/app/navigation/navigation.service.spec.ts +++ b/aio/src/app/navigation/navigation.service.spec.ts @@ -1,9 +1,9 @@ import { ReflectiveInjector } from '@angular/core'; import { Location, LocationStrategy } from '@angular/common'; import { MockLocationStrategy } from '@angular/common/testing'; -import { Http, ConnectionBackend, RequestOptions, BaseRequestOptions } from '@angular/http'; +import { Http, ConnectionBackend, RequestOptions, BaseRequestOptions, Response, ResponseOptions } from '@angular/http'; import { MockBackend } from '@angular/http/testing'; -import { NavigationService } from 'app/navigation/navigation.service'; +import { NavigationService, NavigationViews } from 'app/navigation/navigation.service'; import { LocationService } from 'app/shared/location.service'; import { Logger } from 'app/shared/logger.service'; @@ -11,6 +11,10 @@ describe('NavigationService', () => { let injector: ReflectiveInjector; + function createResponse(body: any) { + return new Response(new ResponseOptions({ body: JSON.stringify(body) })); + } + beforeEach(() => { injector = ReflectiveInjector.resolveAndCreate([ NavigationService, @@ -29,6 +33,57 @@ describe('NavigationService', () => { expect(service).toBeTruthy(); }); - xit('should fetch the navigation views', () => {}); - xit('should compute the navigation map', () => {}); + describe('navigationViews', () => { + let service: NavigationService, backend: MockBackend; + + beforeEach(() => { + backend = injector.get(ConnectionBackend); + service = injector.get(NavigationService); + }); + + it('should make a single connection to the server', () => { + expect(backend.connectionsArray.length).toEqual(1); + expect(backend.connectionsArray[0].request.url).toEqual('content/navigation.json'); + }); + + it('should expose the server response', () => { + const viewsEvents: NavigationViews[] = []; + service.navigationViews.subscribe(views => viewsEvents.push(views)); + + expect(viewsEvents).toEqual([]); + backend.connectionsArray[0].mockRespond(createResponse({ TopBar: [ { path: 'a' }] })); + expect(viewsEvents).toEqual([{ TopBar: [ { path: 'a' }] }]); + + }); + + it('should return the same object to all subscribers', () => { + let views1: NavigationViews; + service.navigationViews.subscribe(views => views1 = views); + + let views2: NavigationViews; + service.navigationViews.subscribe(views => views2 = views); + + backend.connectionsArray[0].mockRespond(createResponse({ TopBar: [{ path: 'a' }] })); + + // modify the response so we can check that future subscriptions do not trigger another request + backend.connectionsArray[0].response.next(createResponse({ TopBar: [{ path: 'error 1' }] })); + + let views3: NavigationViews; + service.navigationViews.subscribe(views => views3 = views); + + expect(views2).toBe(views1); + expect(views3).toBe(views1); + }); + + + it('should do WHAT(?) if the request fails', () => { + console.warn('PENDING: NavigationService navigationViews should do WHAT(?) if the request fails'); + }); + }); + + describe('navigationMap', () => { + it('should compute the navigation map', () => { + console.warn('PENDING: NavigationService navigationMap should compute the navigation map'); + }); + }); }); diff --git a/aio/src/app/navigation/navigation.service.ts b/aio/src/app/navigation/navigation.service.ts index 652880c140..f6284904df 100644 --- a/aio/src/app/navigation/navigation.service.ts +++ b/aio/src/app/navigation/navigation.service.ts @@ -1,8 +1,10 @@ import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; import { Observable } from 'rxjs/Observable'; +import { AsyncSubject } from 'rxjs/AsyncSubject'; import { combineLatest } from 'rxjs/observable/combineLatest'; -import 'rxjs/add/operator/publish'; +import 'rxjs/add/operator/publishReplay'; +import 'rxjs/add/operator/publishLast'; import { Logger } from 'app/shared/logger.service'; import { LocationService } from 'app/shared/location.service'; @@ -28,37 +30,37 @@ const NAVIGATION_PATH = 'content/navigation.json'; @Injectable() export class NavigationService { - navigationViews: Observable; - currentNode: Observable; - activeNodes: Observable; + navigationViews = this.fetchNavigation(); + activeNodes = this.getActiveNodes(); - constructor(private http: Http, location: LocationService, private logger: Logger) { - - this.navigationViews = this.fetchNavigation(); - - const currentMapItem = combineLatest( - this.navigationViews.map(this.computeNavMap), - location.currentUrl, - (navMap, url) => navMap[url]); - - this.currentNode = currentMapItem.map(item => item.node).publish(); - this.activeNodes = currentMapItem.map(item => [item.node, ...item.parents]).publish(); - } + constructor(private http: Http, private location: LocationService, private logger: Logger) { } private fetchNavigation(): Observable { - // TODO: logging and error handling - return this.http.get(NAVIGATION_PATH).map(res => res.json() as NavigationViews); + const response = this.http.get(NAVIGATION_PATH) + .map(res => res.json() as NavigationViews) + .publishLast(); + response.connect(); + return response; + } + + private getActiveNodes() { + const currentMapItem = combineLatest( + this.navigationViews.map(this.computeNavMap), + this.location.currentUrl, + (navMap, url) => navMap[url]); + const activeNodes = currentMapItem + .map(item => item ? [item.node, ...item.parents] : []) + .publishReplay(); + activeNodes.connect(); + return activeNodes; } private computeNavMap(navigation: NavigationViews): NavigationMap { const navMap: NavigationMap = {}; - Object.keys(navigation).forEach(key => { - const nodes = navigation[key]; - nodes.forEach(node => walk(node, null)); - }); + Object.keys(navigation).forEach(key => navigation[key].forEach(node => walkNodes(node, null))); return navMap; - function walk(node: NavigationNode, parent: NavigationMapItem | null) { + function walkNodes(node: NavigationNode, parent: NavigationMapItem | null) { const item: NavigationMapItem = { node, parents: [] }; if (parent) { item.parents = [parent.node, ...parent.parents]; @@ -68,7 +70,7 @@ export class NavigationService { navMap[node.url] = item; } if (node.children) { - node.children.forEach(child => walk(child, item)); + node.children.forEach(child => walkNodes(child, item)); } } }