From 9d563f54380179f78f9152a671b4bd84a3ce29b3 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Fri, 16 Sep 2016 12:36:38 -0500 Subject: [PATCH] fix(urlSerializer): improve findLinkByComponentData --- src/navigation/nav-util.ts | 1 + src/navigation/test/url-serializer.spec.ts | 96 +++++++++++++++++++++- src/navigation/url-serializer.ts | 39 ++++++++- 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/src/navigation/nav-util.ts b/src/navigation/nav-util.ts index 71884f4c53..df9b2d8013 100644 --- a/src/navigation/nav-util.ts +++ b/src/navigation/nav-util.ts @@ -118,6 +118,7 @@ export interface NavLink { partsLen?: number; staticLen?: number; dataLen?: number; + dataKeys?: {[key: string]: boolean}; defaultHistory?: any[]; } diff --git a/src/navigation/test/url-serializer.spec.ts b/src/navigation/test/url-serializer.spec.ts index 2b0e4ad6f2..a8baed43b0 100644 --- a/src/navigation/test/url-serializer.spec.ts +++ b/src/navigation/test/url-serializer.spec.ts @@ -1,5 +1,5 @@ import { NavLink, NavSegment } from '../nav-util'; -import { UrlSerializer, isPartMatch, fillMatchedUrlParts, parseUrlParts, createMatchedData, normalizeLinks } from '../url-serializer'; +import { UrlSerializer, isPartMatch, fillMatchedUrlParts, parseUrlParts, createMatchedData, normalizeLinks, findLinkByComponentData } from '../url-serializer'; import { mockDeepLinkConfig, noop, MockView1, MockView2, MockView3, MockView4, MockView5 } from '../../util/mock-providers'; @@ -7,6 +7,18 @@ describe('UrlSerializer', () => { describe('serializeComponent', () => { + it('should create segement when config has multiple links to same component', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view/:param1' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view/:param1/:param2' }; + + serializer = mockSerializer([link1, link2, link3]); + serializer.createSegment = noop; + spyOn(serializer, 'createSegment'); + serializer.serializeComponent(MockView1, null); + expect(serializer.createSegment).toHaveBeenCalledWith(link1, null); + }); + it('should create segment if component found in links', () => { serializer.createSegment = noop; spyOn(serializer, 'createSegment'); @@ -533,6 +545,88 @@ describe('UrlSerializer', () => { }); + describe('findLinkByComponentData', () => { + + it('should get matching link by component w/ data and multiple links using same component, 2 matches', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view/:param1' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view/:param1/:param2' }; + + let links = normalizeLinks([link1, link2, link3]); + + let foundLink = findLinkByComponentData(links, MockView1, { + param1: false, + param2: 0, + param3: 0 + }); + expect(foundLink.name).toEqual('viewthree'); + }); + + it('should get matching link by component w/ data and multiple links using same component, 1 match', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view/:param1' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view/:param1/:param2' }; + + let links = normalizeLinks([link1, link2, link3]); + + let foundLink = findLinkByComponentData(links, MockView1, { + param1: false, + param3: 0 + }); + expect(foundLink.name).toEqual('viewtwo'); + }); + + it('should get matching link by component w/ no data and multiple links using same component', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view/:param1' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view/:param1/:param2' }; + + let links = normalizeLinks([link1, link2, link3]); + + let foundLink = findLinkByComponentData(links, MockView1, null); + expect(foundLink.name).toEqual('viewone'); + }); + + it('should get matching link by component data and link data', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view' }; + const link2 = { component: MockView2, name: 'viewtwo', segment: 'view/:param1' }; + const link3 = { component: MockView3, name: 'viewthree', segment: 'view/:param1/:param2' }; + + let links = normalizeLinks([link1, link2, link3]); + + let foundLink = findLinkByComponentData(links, MockView3, { + param1: null, + param2: false, + param3: 0, + param4: 'hello' + }); + expect(foundLink.name).toEqual('viewthree'); + }); + + it('should get matching link by component without data and link without data', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view' }; + const link2 = { component: MockView2, name: 'viewtwo', segment: 'view/:param1' }; + const link3 = { component: MockView3, name: 'viewthree', segment: 'view/:param1/:param2' }; + + let links = normalizeLinks([link1, link2, link3]); + + let foundLink = findLinkByComponentData(links, MockView1, null); + expect(foundLink.name).toEqual('viewone'); + }); + + it('should get no matching link by component without data, but link requires data', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view' }; + const link2 = { component: MockView2, name: 'viewtwo', segment: 'view/:param1' }; + const link3 = { component: MockView3, name: 'viewthree', segment: 'view/:param1/:param2' }; + + let links = normalizeLinks([link1, link2, link3]); + + let foundLink = findLinkByComponentData(links, MockView2, null); + expect(foundLink).toEqual(null); + }); + + }); + describe('normalizeLinks', () => { it('should sort with four parts, the most number of paths w/out data first', () => { diff --git a/src/navigation/url-serializer.ts b/src/navigation/url-serializer.ts index 88f11be58d..257b3ef489 100644 --- a/src/navigation/url-serializer.ts +++ b/src/navigation/url-serializer.ts @@ -59,7 +59,7 @@ export class UrlSerializer { */ serializeComponent(component: any, data: any): NavSegment { if (component) { - const link = this.links.find(l => component === l.component || component.name === l.name); + const link = findLinkByComponentData(this.links, component, data); if (link) { return this.createSegment(link, data); } @@ -203,6 +203,41 @@ export const createMatchedData = (matchedUrlParts: string[], link: NavLink): any return data; }; +export const findLinkByComponentData = (links: NavLink[], component: any, instanceData: any): NavLink => { + let foundLink: NavLink = null; + let foundLinkDataMatches = -1; + + for (var i = 0; i < links.length; i++) { + var link = links[i]; + if (link.component === component) { + // ok, so the component matched, but multiple links can point + // to the same component, so let's make sure this is the right link + var dataMatches = 0; + if (instanceData) { + var instanceDataKeys = Object.keys(instanceData); + + // this link has data + for (var j = 0; j < instanceDataKeys.length; j++) { + if (isPresent(link.dataKeys[instanceDataKeys[j]])) { + dataMatches++; + } + } + + } else if (link.dataLen) { + // this component does not have data but the link does + continue; + } + + if (dataMatches >= foundLinkDataMatches) { + foundLink = link; + foundLinkDataMatches = dataMatches; + } + } + } + + return foundLink; +}; + export const normalizeLinks = (links: NavLink[]): NavLink[] => { for (var i = 0, ilen = links.length; i < ilen; i++) { var link = links[i]; @@ -211,6 +246,7 @@ export const normalizeLinks = (links: NavLink[]): NavLink[] => { link.segment = link.name; } + link.dataKeys = {}; link.parts = link.segment.split('/'); link.partsLen = link.parts.length; @@ -222,6 +258,7 @@ export const normalizeLinks = (links: NavLink[]): NavLink[] => { if (link.parts[j].charAt(0) === ':') { link.dataLen++; stillCountingStatic = false; + link.dataKeys[link.parts[j].substring(1)] = true; } else if (stillCountingStatic) { link.staticLen++;