fix(aio): ensure that only one request for navigation.json is made
This commit is contained in:
parent
3529813ca0
commit
61ef756ef2
|
@ -1,9 +1,9 @@
|
||||||
import { ReflectiveInjector } from '@angular/core';
|
import { ReflectiveInjector } from '@angular/core';
|
||||||
import { Location, LocationStrategy } from '@angular/common';
|
import { Location, LocationStrategy } from '@angular/common';
|
||||||
import { MockLocationStrategy } from '@angular/common/testing';
|
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 { 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 { LocationService } from 'app/shared/location.service';
|
||||||
import { Logger } from 'app/shared/logger.service';
|
import { Logger } from 'app/shared/logger.service';
|
||||||
|
|
||||||
|
@ -11,6 +11,10 @@ describe('NavigationService', () => {
|
||||||
|
|
||||||
let injector: ReflectiveInjector;
|
let injector: ReflectiveInjector;
|
||||||
|
|
||||||
|
function createResponse(body: any) {
|
||||||
|
return new Response(new ResponseOptions({ body: JSON.stringify(body) }));
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
injector = ReflectiveInjector.resolveAndCreate([
|
injector = ReflectiveInjector.resolveAndCreate([
|
||||||
NavigationService,
|
NavigationService,
|
||||||
|
@ -29,6 +33,57 @@ describe('NavigationService', () => {
|
||||||
expect(service).toBeTruthy();
|
expect(service).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
xit('should fetch the navigation views', () => {});
|
describe('navigationViews', () => {
|
||||||
xit('should compute the navigation map', () => {});
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Http } from '@angular/http';
|
import { Http } from '@angular/http';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { AsyncSubject } from 'rxjs/AsyncSubject';
|
||||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
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 { Logger } from 'app/shared/logger.service';
|
||||||
import { LocationService } from 'app/shared/location.service';
|
import { LocationService } from 'app/shared/location.service';
|
||||||
|
@ -28,37 +30,37 @@ const NAVIGATION_PATH = 'content/navigation.json';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NavigationService {
|
export class NavigationService {
|
||||||
|
|
||||||
navigationViews: Observable<NavigationViews>;
|
navigationViews = this.fetchNavigation();
|
||||||
currentNode: Observable<NavigationNode>;
|
activeNodes = this.getActiveNodes();
|
||||||
activeNodes: Observable<NavigationNode[]>;
|
|
||||||
|
|
||||||
constructor(private http: Http, location: LocationService, private logger: Logger) {
|
constructor(private http: Http, private 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
private fetchNavigation(): Observable<NavigationViews> {
|
private fetchNavigation(): Observable<NavigationViews> {
|
||||||
// TODO: logging and error handling
|
const response = this.http.get(NAVIGATION_PATH)
|
||||||
return this.http.get(NAVIGATION_PATH).map(res => res.json() as NavigationViews);
|
.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 {
|
private computeNavMap(navigation: NavigationViews): NavigationMap {
|
||||||
const navMap: NavigationMap = {};
|
const navMap: NavigationMap = {};
|
||||||
Object.keys(navigation).forEach(key => {
|
Object.keys(navigation).forEach(key => navigation[key].forEach(node => walkNodes(node, null)));
|
||||||
const nodes = navigation[key];
|
|
||||||
nodes.forEach(node => walk(node, null));
|
|
||||||
});
|
|
||||||
return navMap;
|
return navMap;
|
||||||
|
|
||||||
function walk(node: NavigationNode, parent: NavigationMapItem | null) {
|
function walkNodes(node: NavigationNode, parent: NavigationMapItem | null) {
|
||||||
const item: NavigationMapItem = { node, parents: [] };
|
const item: NavigationMapItem = { node, parents: [] };
|
||||||
if (parent) {
|
if (parent) {
|
||||||
item.parents = [parent.node, ...parent.parents];
|
item.parents = [parent.node, ...parent.parents];
|
||||||
|
@ -68,7 +70,7 @@ export class NavigationService {
|
||||||
navMap[node.url] = item;
|
navMap[node.url] = item;
|
||||||
}
|
}
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
node.children.forEach(child => walk(child, item));
|
node.children.forEach(child => walkNodes(child, item));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue