From d32bc5df3e2256f97367a046b4a011a174f99b5a Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Fri, 28 Apr 2017 17:18:48 -0700 Subject: [PATCH] feat(aio): add tooltip to nav node if none specified --- .../app/navigation/navigation.service.spec.ts | 55 ++++++++++++++++--- aio/src/app/navigation/navigation.service.ts | 33 +++++++++-- 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/aio/src/app/navigation/navigation.service.spec.ts b/aio/src/app/navigation/navigation.service.spec.ts index 5788939eb7..f977779ee0 100644 --- a/aio/src/app/navigation/navigation.service.spec.ts +++ b/aio/src/app/navigation/navigation.service.spec.ts @@ -80,20 +80,54 @@ describe('NavigationService', () => { it('should do WHAT(?) if the request fails'); }); + describe('node.tooltip', () => { + let view: NavigationNode[]; + + const sideNav: NavigationNode[] = [ + { title: 'a', tooltip: 'a tip' }, + { title: 'b' }, + { title: 'c!'}, + { url: 'foo' } + ]; + + beforeEach(() => { + navService.navigationViews.subscribe(views => view = views.sideNav); + backend.connectionsArray[0].mockRespond(createResponse({sideNav})); + }); + + it('should have the supplied tooltip', () => { + expect(view[0].tooltip).toEqual('a tip'); + }); + + it('should create a tooltip from title + period', () => { + expect(view[1].tooltip).toEqual('b.'); + }); + + it('should create a tooltip from title, keeping its trailing punctuation', () => { + expect(view[2].tooltip).toEqual('c!'); + }); + + it('should not create a tooltip if there is no title', () => { + expect(view[3].tooltip).toBeUndefined(); + }); + }); + describe('currentNode', () => { let currentNode: CurrentNode; let locationService: MockLocationService; - const topBarNodes: NavigationNode[] = [{ url: 'features', title: 'Features' }]; + const topBarNodes: NavigationNode[] = [ + { url: 'features', title: 'Features', tooltip: 'tip' } + ]; const sideNavNodes: NavigationNode[] = [ - { title: 'a', children: [ - { url: 'b', title: 'b', children: [ - { url: 'c', title: 'c' }, - { url: 'd', title: 'd' } + { title: 'a', tooltip: 'tip', children: [ + { url: 'b', title: 'b', tooltip: 'tip', children: [ + { url: 'c', title: 'c', tooltip: 'tip' }, + { url: 'd', title: 'd', tooltip: 'tip' } ] }, - { url: 'e', title: 'e' } + { url: 'e', title: 'e', tooltip: 'tip' } ] }, - { url: 'f', title: 'f' } + { url: 'f', title: 'f', tooltip: 'tip' } ]; const navJson = { @@ -199,6 +233,7 @@ describe('NavigationService', () => { describe('docVersions', () => { let actualDocVersions: NavigationNode[]; let docVersions: NavigationNode[]; + let expectedDocVersions: NavigationNode[]; beforeEach(() => { actualDocVersions = []; @@ -207,12 +242,16 @@ describe('NavigationService', () => { { title: 'v2', url: 'https://v2.angular.io' } ]; + expectedDocVersions = docVersions.map(v => ( + {...v, ...{ tooltip: v.title + '.'}}) + ); + navService.navigationViews.subscribe(views => actualDocVersions = views.docVersions); }); it('should extract the docVersions', () => { backend.connectionsArray[0].mockRespond(createResponse({ docVersions })); - expect(actualDocVersions).toEqual(docVersions); + expect(actualDocVersions).toEqual(expectedDocVersions); }); }); }); diff --git a/aio/src/app/navigation/navigation.service.ts b/aio/src/app/navigation/navigation.service.ts index bbb1fd27a8..73370cbc59 100644 --- a/aio/src/app/navigation/navigation.service.ts +++ b/aio/src/app/navigation/navigation.service.ts @@ -90,7 +90,7 @@ export class NavigationService { */ private getCurrentNode(navigationViews: Observable): Observable { const currentNode = combineLatest( - navigationViews.map(this.computeUrlToNavNodesMap), + navigationViews.map(views => this.computeUrlToNavNodesMap(views)), this.location.currentPath, (navMap, url) => { const urlKey = url.startsWith('api/') ? 'api' : url; @@ -110,21 +110,42 @@ export class NavigationService { private computeUrlToNavNodesMap(navigation: NavigationViews) { const navMap = new Map(); Object.keys(navigation) - .forEach(view => navigation[view].forEach(node => walkNodes(view, node))); + .forEach(view => navigation[view] + .forEach(node => this.walkNodes(view, navMap, node))); return navMap; + } - function walkNodes(view: string, node: NavigationNode, ancestors: NavigationNode[] = []) { + /** + * Add tooltip to node if it doesn't have one and have title. + * If don't want tooltip, specify `"tooltip": ""` in navigation.json + */ + private ensureHasTooltip(node: NavigationNode) { + const title = node.title; + const tooltip = node.tooltip; + if (tooltip == null && title ) { + // add period if no trailing punctuation + node.tooltip = title + (/[a-zA-Z0-9]$/.test(title) ? '.' : ''); + } + } + /** + * Walk the nodes of a navigation tree-view, + * patching them and computing their ancestor nodes + */ + private walkNodes( + view: string, navMap: Map, + node: NavigationNode, ancestors: NavigationNode[] = []) { const nodes = [node, ...ancestors]; const url = node.url; + this.ensureHasTooltip(node); - // only map to this node if it has a url associated with it + // only map to this node if it has a url if (url) { // Strip off trailing slashes from nodes in the navMap - they are not relevant to matching navMap[url.replace(/\/$/, '')] = { url, view, nodes }; } + if (node.children) { - node.children.forEach(child => walkNodes(view, child, nodes)); + node.children.forEach(child => this.walkNodes(view, navMap, child, nodes)); } } - } }