fix(router): add support for ../

This commit is contained in:
vsavkin 2016-05-02 14:47:59 -07:00
parent 908a102a87
commit 89704e0f93
6 changed files with 261 additions and 143 deletions

View File

@ -1,116 +1,184 @@
import {Tree, TreeNode, UrlSegment, RouteSegment, rootNode, UrlTree, RouteTree} from './segments'; import {Tree, TreeNode, UrlSegment, RouteSegment, rootNode, UrlTree, RouteTree} from './segments';
import {isBlank, isPresent, isString, isStringMap} from './facade/lang'; import {isBlank, isPresent, isString, isStringMap} from './facade/lang';
import {BaseException} from './facade/exceptions';
import {ListWrapper} from './facade/collection'; import {ListWrapper} from './facade/collection';
export function link(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree, export function link(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree, commands: any[]): UrlTree {
change: any[]): UrlTree { if (commands.length === 0) return urlTree;
if (change.length === 0) return urlTree;
let startingNode; let normalizedCommands = _normalizeCommands(commands);
let normalizedChange; if (_navigateToRoot(normalizedCommands)) {
return new UrlTree(new TreeNode<UrlSegment>(urlTree.root, []));
if (isString(change[0]) && change[0].startsWith("./")) {
normalizedChange = ["/", change[0].substring(2)].concat(change.slice(1));
startingNode = _findStartingNode(_findUrlSegment(segment, routeTree), rootNode(urlTree));
} else if (isString(change[0]) && change.length === 1 && change[0] == "/") {
normalizedChange = change;
startingNode = rootNode(urlTree);
} else if (isString(change[0]) && !change[0].startsWith("/")) {
normalizedChange = ["/"].concat(change);
startingNode = _findStartingNode(_findUrlSegment(segment, routeTree), rootNode(urlTree));
} else {
normalizedChange = ["/"].concat(change);
startingNode = rootNode(urlTree);
} }
let updated = _update(startingNode, normalizedChange); let startingNode = _findStartingNode(normalizedCommands, urlTree, segment, routeTree);
let updated = normalizedCommands.commands.length > 0 ?
_updateMany(ListWrapper.clone(startingNode.children), normalizedCommands.commands) : [];
let newRoot = _constructNewTree(rootNode(urlTree), startingNode, updated); let newRoot = _constructNewTree(rootNode(urlTree), startingNode, updated);
return new UrlTree(newRoot); return new UrlTree(newRoot);
} }
function _findUrlSegment(segment: RouteSegment, routeTree: RouteTree): UrlSegment { function _navigateToRoot(normalizedChange:_NormalizedNavigationCommands):boolean {
let s = segment; return normalizedChange.isAbsolute && normalizedChange.commands.length === 1 && normalizedChange.commands[0] == "/";
let res = null;
while (isBlank(res)) {
res = ListWrapper.last(s.urlSegments);
s = routeTree.parent(s);
}
return res;
} }
function _findStartingNode(segment: UrlSegment, node: TreeNode<UrlSegment>): TreeNode<UrlSegment> { class _NormalizedNavigationCommands {
constructor(public isAbsolute: boolean,
public numberOfDoubleDots: number,
public commands: any[]) {}
}
function _normalizeCommands(commands: any[]): _NormalizedNavigationCommands {;''
if (isString(commands[0]) && commands.length === 1 && commands[0] == "/") {
return new _NormalizedNavigationCommands(true, 0, commands);
}
let numberOfDoubleDots = 0;
let isAbsolute = false;
let res = [];
for (let i = 0; i < commands.length; ++i) {
let c = commands[i];
if (!isString(c)) {
res.push(c);
continue;
}
let parts = c.split('/');
for (let j = 0; j < parts.length; ++j) {
let cc = parts[j];
// first exp is treated in a special way
if (i == 0) {
if (j == 0 && cc == ".") { // './a'
// skip it
} else if (j == 0 && cc == "") { // '/a'
isAbsolute = true;
} else if (cc == "..") { // '../a'
numberOfDoubleDots++;
} else if (cc != '') {
res.push(cc);
}
} else {
if (cc != ''){
res.push(cc);
}
}
}
}
return new _NormalizedNavigationCommands(isAbsolute, numberOfDoubleDots, res);
}
function _findUrlSegment(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree, numberOfDoubleDots: number): UrlSegment {
let s = segment;
while (s.urlSegments.length === 0) {
s = routeTree.parent(s);
}
let urlSegment = ListWrapper.last(s.urlSegments);
let path = urlTree.pathFromRoot(urlSegment);
if (path.length <= numberOfDoubleDots) {
throw new BaseException("Invalid number of '../'");
}
return path[path.length - 1 - numberOfDoubleDots];
}
function _findStartingNode(normalizedChange:_NormalizedNavigationCommands, urlTree:UrlTree, segment:RouteSegment, routeTree:RouteTree):TreeNode<UrlSegment> {
if (normalizedChange.isAbsolute) {
return rootNode(urlTree);
} else {
let urlSegment = _findUrlSegment(segment, routeTree, urlTree, normalizedChange.numberOfDoubleDots);
return _findMatchingNode(urlSegment, rootNode(urlTree));
}
}
function _findMatchingNode(segment: UrlSegment, node: TreeNode<UrlSegment>): TreeNode<UrlSegment> {
if (node.value === segment) return node; if (node.value === segment) return node;
for (var c of node.children) { for (var c of node.children) {
let r = _findStartingNode(segment, c); let r = _findMatchingNode(segment, c);
if (isPresent(r)) return r; if (isPresent(r)) return r;
} }
return null; return null;
} }
function _constructNewTree(node: TreeNode<UrlSegment>, original: TreeNode<UrlSegment>, function _constructNewTree(node: TreeNode<UrlSegment>, original: TreeNode<UrlSegment>,
updated: TreeNode<UrlSegment>): TreeNode<UrlSegment> { updated: TreeNode<UrlSegment>[]): TreeNode<UrlSegment> {
if (node === original) { if (node === original) {
return new TreeNode<UrlSegment>(node.value, updated.children); return new TreeNode<UrlSegment>(node.value, updated);
} else { } else {
return new TreeNode<UrlSegment>( return new TreeNode<UrlSegment>(
node.value, node.children.map(c => _constructNewTree(c, original, updated))); node.value, node.children.map(c => _constructNewTree(c, original, updated)));
} }
} }
function _update(node: TreeNode<UrlSegment>, changes: any[]): TreeNode<UrlSegment> { function _update(node: TreeNode<UrlSegment>, commands: any[]): TreeNode<UrlSegment> {
let rest = changes.slice(1); let rest = commands.slice(1);
let outlet = _outlet(changes); let next = rest.length === 0 ? null : rest[0];
let segment = _segment(changes); let outlet = _outlet(commands);
if (isString(segment) && segment[0] == "/") segment = segment.substring(1); let segment = _segment(commands);
// reach the end of the tree => create new tree nodes. // reach the end of the tree => create new tree nodes.
if (isBlank(node)) { if (isBlank(node) && !isStringMap(next)) {
let urlSegment = new UrlSegment(segment, null, outlet); let urlSegment = new UrlSegment(segment, {}, outlet);
let children = rest.length === 0 ? [] : [_update(null, rest)]; let children = rest.length === 0 ? [] : [_update(null, rest)];
return new TreeNode<UrlSegment>(urlSegment, children); return new TreeNode<UrlSegment>(urlSegment, children);
// different outlet => preserve the subtree } else if (isBlank(node) && isStringMap(next)) {
let urlSegment = new UrlSegment(segment, next, outlet);
return _recurse(urlSegment, node, rest.slice(1));
// different outlet => preserve the subtree
} else if (outlet != node.value.outlet) { } else if (outlet != node.value.outlet) {
return node; return node;
// same outlet => modify the subtree // params command
} else { } else if (isStringMap(segment)) {
let urlSegment = isStringMap(segment) ? new UrlSegment(null, segment, null) : let newSegment = new UrlSegment(node.value.segment, segment, node.value.outlet);
new UrlSegment(segment, null, outlet); return _recurse(newSegment, node, rest);
if (rest.length === 0) {
return new TreeNode<UrlSegment>(urlSegment, []);
}
return new TreeNode<UrlSegment>(urlSegment, // next one is a params command
_updateMany(ListWrapper.clone(node.children), rest)); } else if (isStringMap(next)) {
let urlSegment = new UrlSegment(segment, next, outlet);
return _recurse(urlSegment, node, rest.slice(1));
// next one is not a params command
} else {
let urlSegment = new UrlSegment(segment, {}, outlet);
return _recurse(urlSegment, node, rest);
} }
} }
function _updateMany(nodes: TreeNode<UrlSegment>[], changes: any[]): TreeNode<UrlSegment>[] { function _recurse(urlSegment: UrlSegment, node: TreeNode<UrlSegment>, rest: any[]): TreeNode<UrlSegment> {
let outlet = _outlet(changes); if (rest.length === 0) {
return new TreeNode<UrlSegment>(urlSegment, []);
}
return new TreeNode<UrlSegment>(urlSegment, _updateMany(ListWrapper.clone(node.children), rest));
}
function _updateMany(nodes: TreeNode<UrlSegment>[], commands: any[]): TreeNode<UrlSegment>[] {
let outlet = _outlet(commands);
let nodesInRightOutlet = nodes.filter(c => c.value.outlet == outlet); let nodesInRightOutlet = nodes.filter(c => c.value.outlet == outlet);
if (nodesInRightOutlet.length > 0) { if (nodesInRightOutlet.length > 0) {
let nodeRightOutlet = nodesInRightOutlet[0]; // there can be only one let nodeRightOutlet = nodesInRightOutlet[0]; // there can be only one
nodes[nodes.indexOf(nodeRightOutlet)] = _update(nodeRightOutlet, changes); nodes[nodes.indexOf(nodeRightOutlet)] = _update(nodeRightOutlet, commands);
} else { } else {
nodes.push(_update(null, changes)); nodes.push(_update(null, commands));
} }
return nodes; return nodes;
} }
function _segment(changes: any[]): any { function _segment(commands: any[]): any {
if (!isString(changes[0])) return changes[0]; if (!isString(commands[0])) return commands[0];
let parts = changes[0].toString().split(":"); let parts = commands[0].toString().split(":");
return parts.length > 1 ? parts[1] : changes[0]; return parts.length > 1 ? parts[1] : commands[0];
} }
function _outlet(changes: any[]): string { function _outlet(commands: any[]): string {
if (!isString(changes[0])) return null; if (!isString(commands[0])) return null;
let parts = changes[0].toString().split(":"); let parts = commands[0].toString().split(":");
return parts.length > 1 ? parts[0] : null; return parts.length > 1 ? parts[0] : null;
} }

View File

@ -11,7 +11,7 @@ import {reflector} from '@angular/core';
// TODO: vsavkin: recognize should take the old tree and merge it // TODO: vsavkin: recognize should take the old tree and merge it
export function recognize(componentResolver: ComponentResolver, type: Type, export function recognize(componentResolver: ComponentResolver, type: Type,
url: UrlTree): Promise<RouteTree> { url: UrlTree): Promise<RouteTree> {
let matched = new _MatchResult(type, [url.root], null, rootNode(url).children, []); let matched = new _MatchResult(type, [url.root], {}, rootNode(url).children, []);
return _constructSegment(componentResolver, matched).then(roots => new RouteTree(roots[0])); return _constructSegment(componentResolver, matched).then(roots => new RouteTree(roots[0]));
} }
@ -82,7 +82,7 @@ function _recognizeLeftOvers(componentResolver: ComponentResolver,
return componentResolver.resolveComponent(r[0].component) return componentResolver.resolveComponent(r[0].component)
.then(factory => { .then(factory => {
let segment = let segment =
new RouteSegment([], null, DEFAULT_OUTLET_NAME, r[0].component, factory); new RouteSegment([], {}, DEFAULT_OUTLET_NAME, r[0].component, factory);
return [new TreeNode<RouteSegment>(segment, children)]; return [new TreeNode<RouteSegment>(segment, children)];
}); });
}); });
@ -142,14 +142,9 @@ function _matchWithParts(route: RouteMetadata, url: TreeNode<UrlSegment>): _Matc
current = ListWrapper.first(current.children); current = ListWrapper.first(current.children);
} }
if (isPresent(current) && isBlank(current.value.segment)) {
lastParent = lastSegment;
lastSegment = current;
}
let p = lastSegment.value.parameters; let p = lastSegment.value.parameters;
let parameters = let parameters =
<{[key: string]: string}>StringMapWrapper.merge(isBlank(p) ? {} : p, positionalParams); <{[key: string]: string}>StringMapWrapper.merge(p, positionalParams);
let axuUrlSubtrees = isPresent(lastParent) ? lastParent.children.slice(1) : []; let axuUrlSubtrees = isPresent(lastParent) ? lastParent.children.slice(1) : [];
return new _MatchResult(route.component, consumedUrlSegments, parameters, lastSegment.children, return new _MatchResult(route.component, consumedUrlSegments, parameters, lastSegment.children,

View File

@ -30,8 +30,7 @@ function _serializeUrlTreeNodes(nodes: TreeNode<UrlSegment>[]): string {
function _serializeChildren(node: TreeNode<UrlSegment>): string { function _serializeChildren(node: TreeNode<UrlSegment>): string {
if (node.children.length > 0) { if (node.children.length > 0) {
let slash = isBlank(node.children[0].value.segment) ? "" : "/"; return `/${_serializeUrlTreeNodes(node.children)}`;
return `${slash}${_serializeUrlTreeNodes(node.children)}`;
} else { } else {
return ""; return "";
} }
@ -63,7 +62,7 @@ class _UrlParser {
parse(url: string): TreeNode<UrlSegment> { parse(url: string): TreeNode<UrlSegment> {
this._remaining = url; this._remaining = url;
if (url == '' || url == '/') { if (url == '' || url == '/') {
return new TreeNode<UrlSegment>(new UrlSegment('', null, null), []); return new TreeNode<UrlSegment>(new UrlSegment('', {}, null), []);
} else { } else {
return this.parseRoot(); return this.parseRoot();
} }
@ -71,8 +70,7 @@ class _UrlParser {
parseRoot(): TreeNode<UrlSegment> { parseRoot(): TreeNode<UrlSegment> {
let segments = this.parseSegments(); let segments = this.parseSegments();
let queryParams = this.peekStartsWith('?') ? this.parseQueryParams() : null; return new TreeNode<UrlSegment>(new UrlSegment('', {}, null), segments);
return new TreeNode<UrlSegment>(new UrlSegment('', queryParams, null), segments);
} }
parseSegments(outletName: string = null): TreeNode<UrlSegment>[] { parseSegments(outletName: string = null): TreeNode<UrlSegment>[] {
@ -92,7 +90,7 @@ class _UrlParser {
path = parts[1]; path = parts[1];
} }
var matrixParams: {[key: string]: any} = null; var matrixParams: {[key: string]: any} = {};
if (this.peekStartsWith(';')) { if (this.peekStartsWith(';')) {
matrixParams = this.parseMatrixParams(); matrixParams = this.parseMatrixParams();
} }
@ -108,16 +106,9 @@ class _UrlParser {
children = this.parseSegments(); children = this.parseSegments();
} }
if (isPresent(matrixParams)) { let segment = new UrlSegment(path, matrixParams, outletName);
let matrixParamsSegment = new UrlSegment(null, matrixParams, null); let node = new TreeNode<UrlSegment>(segment, children);
let matrixParamsNode = new TreeNode<UrlSegment>(matrixParamsSegment, children); return [node].concat(aux);
let segment = new UrlSegment(path, null, outletName);
return [new TreeNode<UrlSegment>(segment, [matrixParamsNode].concat(aux))];
} else {
let segment = new UrlSegment(path, null, outletName);
let node = new TreeNode<UrlSegment>(segment, children);
return [node].concat(aux);
}
} }
parseQueryParams(): {[key: string]: any} { parseQueryParams(): {[key: string]: any} {

View File

@ -90,21 +90,18 @@ export class TreeNode<T> {
} }
export class UrlSegment { export class UrlSegment {
constructor(public segment: any, public parameters: {[key: string]: string}, constructor(public segment: any, public parameters: {[key: string]: any},
public outlet: string) {} public outlet: string) {}
toString(): string { toString(): string {
let outletPrefix = isBlank(this.outlet) ? "" : `${this.outlet}:`; let outletPrefix = isBlank(this.outlet) ? "" : `${this.outlet}:`;
let segmentPrefix = isBlank(this.segment) ? "" : this.segment; return `${outletPrefix}${this.segment}${_serializeParams(this.parameters)}`;
return `${outletPrefix}${segmentPrefix}${_serializeParams(this.parameters)}`;
} }
} }
function _serializeParams(params: {[key: string]: string}): string { function _serializeParams(params: {[key: string]: string}): string {
let res = ""; let res = "";
if (isPresent(params)) { StringMapWrapper.forEach(params, (v, k) => res += `;${k}=${v}`);
StringMapWrapper.forEach(params, (v, k) => res += `;${k}=${v}`);
}
return res; return res;
} }
@ -115,7 +112,7 @@ export class RouteSegment {
/** @internal */ /** @internal */
_componentFactory: ComponentFactory<any>; _componentFactory: ComponentFactory<any>;
constructor(public urlSegments: UrlSegment[], public parameters: {[key: string]: string}, constructor(public urlSegments: UrlSegment[], public parameters: {[key: string]: any},
public outlet: string, type: Type, componentFactory: ComponentFactory<any>) { public outlet: string, type: Type, componentFactory: ComponentFactory<any>) {
this._type = type; this._type = type;
this._componentFactory = componentFactory; this._componentFactory = componentFactory;
@ -145,9 +142,6 @@ export function equalSegments(a: RouteSegment, b: RouteSegment): boolean {
if (!isBlank(a) && isBlank(b)) return false; if (!isBlank(a) && isBlank(b)) return false;
if (a._type !== b._type) return false; if (a._type !== b._type) return false;
if (a.outlet != b.outlet) return false; if (a.outlet != b.outlet) return false;
if (isBlank(a.parameters) && !isBlank(b.parameters)) return false;
if (!isBlank(a.parameters) && isBlank(b.parameters)) return false;
if (isBlank(a.parameters) && isBlank(b.parameters)) return true;
return StringMapWrapper.equals(a.parameters, b.parameters); return StringMapWrapper.equals(a.parameters, b.parameters);
} }
@ -156,9 +150,12 @@ export function equalUrlSegments(a: UrlSegment, b: UrlSegment): boolean {
if (!isBlank(a) && isBlank(b)) return false; if (!isBlank(a) && isBlank(b)) return false;
if (a.segment != b.segment) return false; if (a.segment != b.segment) return false;
if (a.outlet != b.outlet) return false; if (a.outlet != b.outlet) return false;
if (isBlank(a.parameters) && !isBlank(b.parameters)) return false; if (isBlank(a.parameters)) {
if (!isBlank(a.parameters) && isBlank(b.parameters)) return false; console.log("a", a);
if (isBlank(a.parameters) && isBlank(b.parameters)) return true; }
if (isBlank(b.parameters)) {
console.log("b", b);
}
return StringMapWrapper.equals(a.parameters, b.parameters); return StringMapWrapper.equals(a.parameters, b.parameters);
} }

View File

@ -27,68 +27,142 @@ export function main() {
expect(t).toBe(p); expect(t).toBe(p);
}); });
it("should support going to root", () => { it("should navigate to the root", () => {
let p = parser.parse("/"); let p = parser.parse("/");
let tree = s(p.root); let tree = s(p.root);
let t = link(tree.root, tree, p, ["/"]); let t = link(tree.root, tree, p, ["/"]);
expect(parser.serialize(t)).toEqual(""); expect(parser.serialize(t)).toEqual("");
}); });
it("should support positional params", () => { it("should support nested segments", () => {
let p = parser.parse("/a/b"); let p = parser.parse("/a/b");
let tree = s(p.firstChild(p.root)); let tree = s(p.firstChild(p.root));
let t = link(tree.root, tree, p, ["/one", 11, "two", 22]); let t = link(tree.root, tree, p, ["/one", 11, "two", 22]);
expect(parser.serialize(t)).toEqual("/one/11/two/22"); expect(parser.serialize(t)).toEqual("/one/11/two/22");
}); });
it("should preserve route siblings when changing the main route", () => { it("should preserve siblings", () => {
let p = parser.parse("/a/11/b(c)"); let p = parser.parse("/a/11/b(c)");
let tree = s(p.root); let tree = s(p.root);
let t = link(tree.root, tree, p, ["/a", 11, 'd']); let t = link(tree.root, tree, p, ["/a", 11, 'd']);
expect(parser.serialize(t)).toEqual("/a/11/d(aux:c)"); expect(parser.serialize(t)).toEqual("/a/11/d(aux:c)");
}); });
it("should preserve route siblings when changing a aux route", () => { it('should update matrix parameters', () => {
let p = parser.parse("/a/11/b(c)");
let tree = s(p.root);
let t = link(tree.root, tree, p, ["/a", 11, 'aux:d']);
expect(parser.serialize(t)).toEqual("/a/11/b(aux:d)");
});
it('should update parameters', () => {
let p = parser.parse("/a;aa=11"); let p = parser.parse("/a;aa=11");
let tree = s(p.root); let tree = s(p.root);
let t = link(tree.root, tree, p, ["/a", {aa: 22, bb: 33}]); let t = link(tree.root, tree, p, ["/a", {aa: 22, bb: 33}]);
expect(parser.serialize(t)).toEqual("/a;aa=22;bb=33"); expect(parser.serialize(t)).toEqual("/a;aa=22;bb=33");
}); });
it("should update relative subtree (when starts with ./)", () => { it('should create matrix parameters', () => {
let p = parser.parse("/a(ap)/c(cp)"); let p = parser.parse("/a");
let c = p.firstChild(p.root); let tree = s(p.root);
let tree = s(c); let t = link(tree.root, tree, p, ["/a", {aa: 22, bb: 33}]);
let t = link(tree.root, tree, p, ["./c2"]); expect(parser.serialize(t)).toEqual("/a;aa=22;bb=33");
expect(parser.serialize(t)).toEqual("/a(aux:ap)/c2(aux:cp)");
}); });
it("should update relative subtree (when does not start with ./)", () => { it('should create matrix parameters together with other segments', () => {
let p = parser.parse("/a(ap)/c(cp)"); let p = parser.parse("/a");
let c = p.firstChild(p.root); let tree = s(p.root);
let tree = s(c); let t = link(tree.root, tree, p, ["/a", "/b", {aa: 22, bb: 33}]);
let t = link(tree.root, tree, p, ["c2"]); expect(parser.serialize(t)).toEqual("/a/b;aa=22;bb=33");
expect(parser.serialize(t)).toEqual("/a(aux:ap)/c2(aux:cp)");
}); });
it("should update relative subtree when the provided segment doesn't have url segments", () => { describe("relative navigation", () => {
let p = parser.parse("/a(ap)/c(cp)"); it("should work", () => {
let c = p.firstChild(p.root); let p = parser.parse("/a(ap)/c(cp)");
let c = p.firstChild(p.root);
let tree = s(c);
let t = link(tree.root, tree, p, ["c2"]);
expect(parser.serialize(t)).toEqual("/a(aux:ap)/c2(aux:cp)");
});
let child = new RouteSegment([], null, null, null, null); it("should work when the first command starts with a ./", () => {
let root = new TreeNode<RouteSegment>(new RouteSegment([c], {}, null, null, null), let p = parser.parse("/a(ap)/c(cp)");
[new TreeNode<RouteSegment>(child, [])]); let c = p.firstChild(p.root);
let tree = new RouteTree(root); let tree = s(c);
let t = link(tree.root, tree, p, ["./c2"]);
expect(parser.serialize(t)).toEqual("/a(aux:ap)/c2(aux:cp)");
});
let t = link(child, tree, p, ["./c2"]); it("should work when the first command is ./)", () => {
expect(parser.serialize(t)).toEqual("/a(aux:ap)/c2(aux:cp)"); let p = parser.parse("/a(ap)/c(cp)");
let c = p.firstChild(p.root);
let tree = s(c);
let t = link(tree.root, tree, p, ["./", "c2"]);
expect(parser.serialize(t)).toEqual("/a(aux:ap)/c2(aux:cp)");
});
it("should work when given params", () => {
let p = parser.parse("/a(ap)/c(cp)");
let c = p.firstChild(p.root);
let tree = s(c);
let t = link(tree.root, tree, p, [{'x': 99}]);
expect(parser.serialize(t)).toEqual("/a(aux:ap)/c;x=99(aux:cp)");
});
it("should support going to a parent", () => {
let p = parser.parse("/a(ap)/c(cp)");
let a = p.firstChild(p.root);
let tree = s(a);
let t = link(tree.root, tree, p, ["../a2"]);
expect(parser.serialize(t)).toEqual("/a2(aux:ap)");
});
it("should support going to a parent (nested case)", () => {
let p = parser.parse("/a/c");
let c = p.firstChild(p.firstChild(p.root));
let tree = s(c);
let t = link(tree.root, tree, p, ["../c2"]);
expect(parser.serialize(t)).toEqual("/a/c2");
});
it("should work when given ../", () => {
let p = parser.parse("/a/c");
let c = p.firstChild(p.firstChild(p.root));
let tree = s(c);
let t = link(tree.root, tree, p, ["../"]);
expect(parser.serialize(t)).toEqual("/a");
});
it("should navigate to the root", () => {
let p = parser.parse("/a/c");
let c = p.firstChild(p.root);
let tree = s(c);
let t = link(tree.root, tree, p, ["../"]);
expect(parser.serialize(t)).toEqual("");
});
it("should support setting matrix params", () => {
let p = parser.parse("/a(ap)/c(cp)");
let c = p.firstChild(p.root);
let tree = s(c);
let t = link(tree.root, tree, p, ["../", {'x': 5}]);
expect(parser.serialize(t)).toEqual("/a;x=5(aux:ap)");
});
it("should throw when too many ..", () => {
let p = parser.parse("/a(ap)/c(cp)");
let c = p.firstChild(p.root);
let tree = s(c);
expect(() => link(tree.root, tree, p, ["../../"])).toThrowError("Invalid number of '../'");
});
it("should work when the provided segment doesn't have url segments", () => {
let p = parser.parse("/a(ap)/c(cp)");
let c = p.firstChild(p.root);
let child = new RouteSegment([], {'one':1}, null, null, null);
let root = new TreeNode<RouteSegment>(new RouteSegment([c], {}, null, null, null),
[new TreeNode<RouteSegment>(child, [])]);
let tree = new RouteTree(root);
let t = link(child, tree, p, ["./c2"]);
expect(parser.serialize(t)).toEqual("/a(aux:ap)/c2(aux:cp)");
});
}); });
}); });
} }

View File

@ -79,17 +79,11 @@ export function main() {
it("should parse key-value matrix params", () => { it("should parse key-value matrix params", () => {
let tree = url.parse("/one;a=11a;b=11b(/two;c=22//right:three;d=33)"); let tree = url.parse("/one;a=11a;b=11b(/two;c=22//right:three;d=33)");
let c = tree.children(tree.root);
let c = tree.firstChild(tree.root); expectSegment(c[0], "one;a=11a;b=11b");
expectSegment(c, "one"); expectSegment(c[1], "aux:two;c=22");
expectSegment(c[2], "right:three;d=33");
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)"); expect(url.serialize(tree)).toEqual("/one;a=11a;b=11b(aux:two;c=22//right:three;d=33)");
}); });
@ -98,8 +92,7 @@ export function main() {
let tree = url.parse("/one;a"); let tree = url.parse("/one;a");
let c = tree.firstChild(tree.root); let c = tree.firstChild(tree.root);
expectSegment(c, "one"); expectSegment(c, "one;a=true");
expectSegment(tree.firstChild(c), ";a=true");
expect(url.serialize(tree)).toEqual("/one;a=true"); expect(url.serialize(tree)).toEqual("/one;a=true");
}); });