From 44709e0dca1159b5b130beea0dcfee7915741de0 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Thu, 21 Jul 2016 14:50:38 -0700 Subject: [PATCH] fix(router): handle urls with only secondary top-level segments --- modules/@angular/router/src/recognize.ts | 5 +++- modules/@angular/router/src/url_tree.ts | 18 +++++++++----- .../@angular/router/test/recognize.spec.ts | 16 +++++++++++++ .../router/test/url_serializer.spec.ts | 24 ++++++++++++++++++- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/modules/@angular/router/src/recognize.ts b/modules/@angular/router/src/recognize.ts index 9788f8219d..8c9a74b93e 100644 --- a/modules/@angular/router/src/recognize.ts +++ b/modules/@angular/router/src/recognize.ts @@ -41,8 +41,9 @@ class InheritedFromParent { export function recognize(rootComponentType: Type, config: Routes, urlTree: UrlTree, url: string): Observable { try { + const rootSegment = split(urlTree.root, [], [], config).segment; const children = - processSegment(config, urlTree.root, InheritedFromParent.empty(null), PRIMARY_OUTLET); + processSegment(config, rootSegment, InheritedFromParent.empty(null), PRIMARY_OUTLET); const root = new ActivatedRouteSnapshot( [], Object.freeze({}), {}, PRIMARY_OUTLET, rootComponentType, null, urlTree.root, -1, InheritedResolve.empty); @@ -127,6 +128,8 @@ function processPathsWithParamsAgainstRoute( const childConfig = getChildConfig(route); const {segment, slicedPath} = split(rawSegment, consumedPaths, rawSlicedPath, childConfig); + // console.log("raw", rawSegment) + // console.log(segment.toString(), childConfig) const snapshot = new ActivatedRouteSnapshot( consumedPaths, Object.freeze(merge(inherited.allParams, parameters)), diff --git a/modules/@angular/router/src/url_tree.ts b/modules/@angular/router/src/url_tree.ts index c7db4b6d71..965fc8cb78 100644 --- a/modules/@angular/router/src/url_tree.ts +++ b/modules/@angular/router/src/url_tree.ts @@ -195,8 +195,10 @@ export function serializePaths(segment: UrlSegment): string { } function serializeSegment(segment: UrlSegment, root: boolean): string { - if (segment.children[PRIMARY_OUTLET] && root) { - const primary = serializeSegment(segment.children[PRIMARY_OUTLET], false); + if (segment.hasChildren() && root) { + const primary = segment.children[PRIMARY_OUTLET] ? + serializeSegment(segment.children[PRIMARY_OUTLET], false) : + ''; const children: string[] = []; forEach(segment.children, (v: UrlSegment, k: string) => { if (k !== PRIMARY_OUTLET) { @@ -307,7 +309,10 @@ class UrlParser { this.capture('/'); } - const paths = [this.parsePathWithParams()]; + let paths: any[] = []; + if (!this.peekStartsWith('(')) { + paths.push(this.parsePathWithParams()); + } while (this.peekStartsWith('/') && !this.peekStartsWith('//') && !this.peekStartsWith('/(')) { this.capture('/'); @@ -325,7 +330,10 @@ class UrlParser { res = this.parseParens(false); } - res[PRIMARY_OUTLET] = new UrlSegment(paths, children); + if (paths.length > 0 || Object.keys(children).length > 0) { + res[PRIMARY_OUTLET] = new UrlSegment(paths, children); + } + return res; } @@ -413,7 +421,6 @@ class UrlParser { parseParens(allowPrimary: boolean): {[key: string]: UrlSegment} { const segments: {[key: string]: UrlSegment} = {}; this.capture('('); - while (!this.peekStartsWith(')') && this.remaining.length > 0) { let path = matchPathWithParams(this.remaining); let outletName: string; @@ -434,7 +441,6 @@ class UrlParser { } } this.capture(')'); - return segments; } } diff --git a/modules/@angular/router/test/recognize.spec.ts b/modules/@angular/router/test/recognize.spec.ts index 19f54a86da..9463214dd1 100644 --- a/modules/@angular/router/test/recognize.spec.ts +++ b/modules/@angular/router/test/recognize.spec.ts @@ -494,6 +494,22 @@ describe('recognize', () => { checkActivatedRoute(c[1], 'c', {}, ComponentC, 'aux'); }); }); + + it('should work when split is at the root level', () => { + checkRecognize( + [ + {path: '', component: ComponentA}, {path: 'b', component: ComponentB}, + {path: 'c', component: ComponentC, outlet: 'aux'} + ], + '(aux:c)', (s: RouterStateSnapshot) => { + checkActivatedRoute(s.root, '', {}, RootComponent); + + const children = s.children(s.root); + expect(children.length).toEqual(2); + checkActivatedRoute(children[0], '', {}, ComponentA); + checkActivatedRoute(children[1], 'c', {}, ComponentC, 'aux'); + }); + }); }); describe('split at the end (right child)', () => { diff --git a/modules/@angular/router/test/url_serializer.spec.ts b/modules/@angular/router/test/url_serializer.spec.ts index 25e5b073ee..59ed286804 100644 --- a/modules/@angular/router/test/url_serializer.spec.ts +++ b/modules/@angular/router/test/url_serializer.spec.ts @@ -6,7 +6,6 @@ describe('url serializer', () => { it('should parse the root url', () => { const tree = url.parse('/'); - expectSegment(tree.root, ''); expect(url.serialize(tree)).toEqual('/'); }); @@ -27,6 +26,26 @@ describe('url serializer', () => { expect(url.serialize(tree)).toEqual('/one/two(left:three//right:four)'); }); + it('should parse top-level nodes with only secondary segment', () => { + const tree = url.parse('/(left:one)'); + + expect(tree.root.numberOfChildren).toEqual(1); + expectSegment(tree.root.children['left'], 'one'); + + expect(url.serialize(tree)).toEqual('/(left:one)'); + }); + + it('should parse nodes with only secondary segment', () => { + const tree = url.parse('/one/(left:two)'); + + const one = tree.root.children[PRIMARY_OUTLET]; + expectSegment(one, 'one', true); + expect(one.numberOfChildren).toEqual(1); + expectSegment(one.children['left'], 'two'); + + expect(url.serialize(tree)).toEqual('/one/(left:two)'); + }); + it('should not parse empty path segments with params', () => { expect(() => url.parse('/one/two/(;a=1//right:;b=2)')) .toThrowError(/Empty path url segment cannot have parameters/); @@ -167,6 +186,9 @@ describe('url serializer', () => { }); function expectSegment(segment: UrlSegment, expected: string, hasChildren: boolean = false): void { + if (segment.pathsWithParams.filter(s => s.path === '').length > 0) { + throw new Error(`UrlPathWithParams cannot be empty ${segment.pathsWithParams}`); + } const p = segment.pathsWithParams.map(p => serializePath(p)).join('/'); expect(p).toEqual(expected); expect(Object.keys(segment.children).length > 0).toEqual(hasChildren);