diff --git a/modules/angular2/src/alt_router/router_url_parser.ts b/modules/angular2/src/alt_router/router_url_serializer.ts similarity index 67% rename from modules/angular2/src/alt_router/router_url_parser.ts rename to modules/angular2/src/alt_router/router_url_serializer.ts index fece8a9d2c..914d5ad143 100644 --- a/modules/angular2/src/alt_router/router_url_parser.ts +++ b/modules/angular2/src/alt_router/router_url_serializer.ts @@ -1,11 +1,14 @@ -import {UrlSegment, Tree, TreeNode} from './segments'; +import {UrlSegment, Tree, TreeNode, rootNode} from './segments'; import {BaseException} from 'angular2/src/facade/exceptions'; import {isBlank, isPresent, RegExpWrapper} from 'angular2/src/facade/lang'; -import {DEFAULT_OUTLET_NAME} from './constants'; +import {ListWrapper} from 'angular2/src/facade/collection'; -export abstract class RouterUrlParser { abstract parse(url: string): Tree; } +export abstract class RouterUrlSerializer { + abstract parse(url: string): Tree; + abstract serialize(tree: Tree): string; +} -export class DefaultRouterUrlParser extends RouterUrlParser { +export class DefaultRouterUrlSerializer extends RouterUrlSerializer { parse(url: string): Tree { if (url.length === 0) { throw new BaseException(`Invalid url '${url}'`); @@ -13,6 +16,29 @@ export class DefaultRouterUrlParser extends RouterUrlParser { let root = new _UrlParser().parse(url); return new Tree(root); } + + serialize(tree: Tree): string { return _serializeUrlTreeNode(rootNode(tree)); } +} + +function _serializeUrlTreeNode(node: TreeNode): string { + return `${node.value}${_serializeChildren(node)}`; +} + +function _serializeUrlTreeNodes(nodes: TreeNode[]): string { + let main = nodes[0].value.toString(); + let auxNodes = nodes.slice(1); + let aux = auxNodes.length > 0 ? `(${auxNodes.map(_serializeUrlTreeNode).join("//")})` : ""; + let children = _serializeChildren(nodes[0]); + return `${main}${aux}${children}`; +} + +function _serializeChildren(node: TreeNode): string { + if (node.children.length > 0) { + let slash = isBlank(node.children[0].value.segment) ? "" : "/"; + return `${slash}${_serializeUrlTreeNodes(node.children)}`; + } else { + return ""; + } } var SEGMENT_RE = RegExpWrapper.create('^[^\\/\\(\\)\\?;=&#]+'); @@ -41,19 +67,19 @@ class _UrlParser { parse(url: string): TreeNode { this._remaining = url; if (url == '' || url == '/') { - return new TreeNode(new UrlSegment('', {}, DEFAULT_OUTLET_NAME), []); + return new TreeNode(new UrlSegment('', {}, null), []); } else { return this.parseRoot(); } } parseRoot(): TreeNode { - let segments = this.parseSegments(DEFAULT_OUTLET_NAME); + let segments = this.parseSegments(); let queryParams = this.peekStartsWith('?') ? this.parseQueryParams() : {}; - return new TreeNode(new UrlSegment('', queryParams, DEFAULT_OUTLET_NAME), segments); + return new TreeNode(new UrlSegment('', queryParams, null), segments); } - parseSegments(outletName: string): TreeNode[] { + parseSegments(outletName: string = null): TreeNode[] { if (this._remaining.length == 0) { return []; } @@ -70,7 +96,7 @@ class _UrlParser { path = parts[1]; } - var matrixParams: {[key: string]: any} = {}; + var matrixParams: {[key: string]: any} = null; if (this.peekStartsWith(';')) { matrixParams = this.parseMatrixParams(); } @@ -83,12 +109,19 @@ class _UrlParser { var children: TreeNode[] = []; if (this.peekStartsWith('/') && !this.peekStartsWith('//')) { this.capture('/'); - children = this.parseSegments(DEFAULT_OUTLET_NAME); + children = this.parseSegments(); } - let segment = new UrlSegment(path, matrixParams, outletName); - let node = new TreeNode(segment, children); - return [node].concat(aux); + if (isPresent(matrixParams)) { + let matrixParamsSegment = new UrlSegment(null, matrixParams, null); + let matrixParamsNode = new TreeNode(matrixParamsSegment, children); + let segment = new UrlSegment(path, null, outletName); + return [new TreeNode(segment, [matrixParamsNode].concat(aux))]; + } else { + let segment = new UrlSegment(path, null, outletName); + let node = new TreeNode(segment, children); + return [node].concat(aux); + } } parseQueryParams(): {[key: string]: any} { diff --git a/modules/angular2/test/alt_router/router_url_parser_spec.ts b/modules/angular2/test/alt_router/router_url_parser_spec.ts deleted file mode 100644 index 9d09f15a9c..0000000000 --- a/modules/angular2/test/alt_router/router_url_parser_spec.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { - ComponentFixture, - AsyncTestCompleter, - TestComponentBuilder, - beforeEach, - ddescribe, - xdescribe, - describe, - el, - expect, - iit, - inject, - beforeEachProviders, - it, - xit -} from 'angular2/testing_internal'; - -import {DefaultRouterUrlParser} from 'angular2/src/alt_router/router_url_parser'; -import {UrlSegment} from 'angular2/src/alt_router/segments'; -import {DEFAULT_OUTLET_NAME} from 'angular2/src/alt_router/constants'; - -export function main() { - describe('url parsing', () => { - let parser = new DefaultRouterUrlParser(); - - it('should throw on an empty urls', () => { expect(() => parser.parse("")).toThrow(); }); - - it('should parse the root url', () => { - let tree = parser.parse("/"); - expectSegment(tree.root, ""); - }); - - it('should parse non-empty urls', () => { - let tree = parser.parse("one/two"); - expectSegment(tree.firstChild(tree.root), "one"); - expectSegment(tree.firstChild(tree.firstChild(tree.root)), "two"); - }); - - it("should parse multiple aux routes", () => { - let tree = parser.parse("/one/two(/three//right:four)/five"); - let c = tree.children(tree.firstChild(tree.root)); - - expectSegment(c[0], "two"); - expectSegment(c[1], "aux:three"); - expectSegment(c[2], "right:four"); - - expectSegment(tree.firstChild(c[0]), "five"); - }); - - it("should parse aux routes that have aux routes", () => { - let tree = parser.parse("/one(/two(/three))"); - let c = tree.children(tree.root); - - expectSegment(c[0], "one"); - expectSegment(c[1], "aux:two"); - expectSegment(c[2], "aux:three"); - }); - - it("should parse aux routes that have children", () => { - let tree = parser.parse("/one(/two/three)"); - let c = tree.children(tree.root); - - expectSegment(c[0], "one"); - expectSegment(c[1], "aux:two"); - expectSegment(tree.firstChild(c[1]), "three"); - }); - - it("should parse an empty aux route definition", () => { - let tree = parser.parse("/one()"); - let c = tree.children(tree.root); - - expectSegment(c[0], "one"); - expect(tree.children(c[0]).length).toEqual(0); - }); - - it("should parse key-value matrix params", () => { - let tree = parser.parse("/one;a=11a;b=11b(/two;c=22//right:three;d=33)"); - - let c = tree.children(tree.root); - expectSegment(c[0], "one;a=11a;b=11b"); - expectSegment(c[1], "aux:two;c=22"); - expectSegment(c[2], "right:three;d=33"); - }); - - it("should parse key only matrix params", () => { - let tree = parser.parse("/one;a"); - - let c = tree.children(tree.root); - expectSegment(c[0], "one;a=true"); - }); - - it("should parse key-value query params", () => { - let tree = parser.parse("/one?a=1&b=2"); - expect(tree.root).toEqual(new UrlSegment("", {'a': '1', 'b': '2'}, DEFAULT_OUTLET_NAME)); - }); - - it("should parse key only query params", () => { - let tree = parser.parse("/one?a"); - expect(tree.root).toEqual(new UrlSegment("", {'a': "true"}, DEFAULT_OUTLET_NAME)); - }); - - it("should parse a url with only query params", () => { - let tree = parser.parse("?a"); - expect(tree.root).toEqual(new UrlSegment("", {'a': "true"}, DEFAULT_OUTLET_NAME)); - }); - - it("should allow slashes within query params", () => { - let tree = parser.parse("?a=http://boo"); - expect(tree.root).toEqual(new UrlSegment("", {'a': "http://boo"}, DEFAULT_OUTLET_NAME)); - }); - }); -} - -function expectSegment(segment: UrlSegment, expected: string): void { - expect(segment.toString()).toEqual(expected); -} \ No newline at end of file diff --git a/modules/angular2/test/alt_router/router_url_serializer_spec.ts b/modules/angular2/test/alt_router/router_url_serializer_spec.ts new file mode 100644 index 0000000000..60db8bef5e --- /dev/null +++ b/modules/angular2/test/alt_router/router_url_serializer_spec.ts @@ -0,0 +1,136 @@ +import { + ComponentFixture, + AsyncTestCompleter, + TestComponentBuilder, + beforeEach, + ddescribe, + xdescribe, + describe, + el, + expect, + iit, + inject, + beforeEachProviders, + it, + xit +} from 'angular2/testing_internal'; + +import {DefaultRouterUrlSerializer} from 'angular2/src/alt_router/router_url_serializer'; +import {UrlSegment} from 'angular2/src/alt_router/segments'; + +export function main() { + describe('url parsing', () => { + let url = new DefaultRouterUrlSerializer(); + + it('should throw on an empty urls', () => { expect(() => url.parse("")).toThrow(); }); + + it('should parse the root url', () => { + let tree = url.parse("/"); + expectSegment(tree.root, ""); + expect(url.serialize(tree)).toEqual(""); + }); + + it('should parse non-empty urls', () => { + let tree = url.parse("one/two"); + expectSegment(tree.firstChild(tree.root), "one"); + expectSegment(tree.firstChild(tree.firstChild(tree.root)), "two"); + expect(url.serialize(tree)).toEqual("/one/two"); + }); + + it("should parse multiple aux routes", () => { + let tree = url.parse("/one/two(/three//right:four)/five"); + let c = tree.children(tree.firstChild(tree.root)); + + expectSegment(c[0], "two"); + expectSegment(c[1], "aux:three"); + expectSegment(c[2], "right:four"); + + expectSegment(tree.firstChild(c[0]), "five"); + + expect(url.serialize(tree)).toEqual("/one/two(aux:three//right:four)/five"); + }); + + it("should parse aux routes that have aux routes", () => { + let tree = url.parse("/one(/two(/three))"); + let c = tree.children(tree.root); + + expectSegment(c[0], "one"); + expectSegment(c[1], "aux:two"); + expectSegment(c[2], "aux:three"); + + expect(url.serialize(tree)).toEqual("/one(aux:two//aux:three)"); + }); + + it("should parse aux routes that have children", () => { + let tree = url.parse("/one(/two/three)"); + let c = tree.children(tree.root); + + expectSegment(c[0], "one"); + expectSegment(c[1], "aux:two"); + expectSegment(tree.firstChild(c[1]), "three"); + + expect(url.serialize(tree)).toEqual("/one(aux:two/three)"); + }); + + it("should parse an empty aux route definition", () => { + let tree = url.parse("/one()"); + let c = tree.children(tree.root); + + expectSegment(c[0], "one"); + expect(tree.children(c[0]).length).toEqual(0); + + expect(url.serialize(tree)).toEqual("/one"); + }); + + it("should parse key-value matrix params", () => { + let tree = url.parse("/one;a=11a;b=11b(/two;c=22//right:three;d=33)"); + + let c = tree.firstChild(tree.root); + expectSegment(c, "one"); + + let c2 = tree.children(c); + expectSegment(c2[0], ";a=11a;b=11b"); + expectSegment(c2[1], "aux:two"); + expectSegment(c2[2], "right:three"); + + expectSegment(tree.firstChild(c2[1]), ";c=22"); + expectSegment(tree.firstChild(c2[2]), ";d=33"); + + expect(url.serialize(tree)).toEqual("/one;a=11a;b=11b(aux:two;c=22//right:three;d=33)"); + }); + + it("should parse key only matrix params", () => { + let tree = url.parse("/one;a"); + + let c = tree.firstChild(tree.root); + expectSegment(c, "one"); + expectSegment(tree.firstChild(c), ";a=true"); + + expect(url.serialize(tree)).toEqual("/one;a=true"); + }); + + // it("should parse key-value query params", () => { + // let tree = url.parse("/one?a=1&b=2"); + // expect(tree.root).toEqual(new UrlSegment("", {'a': '1', 'b': '2'}, DEFAULT_OUTLET_NAME)); + // }); + // + // it("should parse key only query params", () => { + // let tree = url.parse("/one?a"); + // expect(tree.root).toEqual(new UrlSegment("", {'a': "true"}, DEFAULT_OUTLET_NAME)); + // }); + // + // it("should parse a url with only query params", () => { + // let tree = url.parse("?a"); + // expect(tree.root).toEqual(new UrlSegment("", {'a': "true"}, DEFAULT_OUTLET_NAME)); + // }); + // + // it("should allow slashes within query params", () => { + // let tree = url.parse("?a=http://boo"); + // expect(tree.root).toEqual(new UrlSegment("", {'a': "http://boo"}, DEFAULT_OUTLET_NAME)); + // }); + }); +} + +function expectSegment(segment: UrlSegment, expected: string): void { + expect(segment.toString()).toEqual(expected); +} \ No newline at end of file