feat(router): implement terminal

This commit is contained in:
vsavkin
2016-06-14 14:55:59 -07:00
parent 503b07f698
commit 127401598b
17 changed files with 1069 additions and 744 deletions

View File

@ -1,30 +1,32 @@
import {DefaultUrlSerializer} from '../src/url_serializer';
import {TreeNode} from '../src/utils/tree';
import {UrlTree, UrlSegment, equalUrlSegments} from '../src/url_tree';
import {Params, PRIMARY_OUTLET} from '../src/shared';
import {UrlTree, UrlSegment, equalPathsWithParams} from '../src/url_tree';
import {RouterConfig} from '../src/config';
import {applyRedirects} from '../src/apply_redirects';
describe('applyRedirects', () => {
it("should return the same url tree when no redirects", () => {
applyRedirects(tree("/a/b"), [
checkRedirect([
{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}
]).forEach(t => {
], "/a/b", t => {
compareTrees(t, tree('/a/b'));
});
});
it("should add new segments when needed", () => {
applyRedirects(tree("/a/b"), [
{path: 'a/b', redirectTo: 'a/b/c'}
]).forEach(t => {
checkRedirect([
{path: 'a/b', redirectTo: 'a/b/c'},
{path: '**', component: ComponentC}
], "/a/b", t => {
compareTrees(t, tree('/a/b/c'));
});
});
it("should handle positional parameters", () => {
applyRedirects(tree("/a/1/b/2"), [
{path: 'a/:aid/b/:bid', redirectTo: 'newa/:aid/newb/:bid'}
]).forEach(t => {
checkRedirect([
{path: 'a/:aid/b/:bid', redirectTo: 'newa/:aid/newb/:bid'},
{path: '**', component: ComponentC}
], "/a/1/b/2", t => {
compareTrees(t, tree('/newa/1/newb/2'));
});
});
@ -38,50 +40,122 @@ describe('applyRedirects', () => {
});
it("should pass matrix parameters", () => {
applyRedirects(tree("/a;p1=1/1;p2=2"), [
{path: 'a/:id', redirectTo: 'd/a/:id/e'}
]).forEach(t => {
checkRedirect([
{path: 'a/:id', redirectTo: 'd/a/:id/e'},
{path: '**', component: ComponentC}
], "/a;p1=1/1;p2=2", t => {
compareTrees(t, tree('/d/a;p1=1/1;p2=2/e'));
});
});
it("should handle preserve secondary routes", () => {
applyRedirects(tree("/a/1(aux:c/d)"), [
checkRedirect([
{path: 'a/:id', redirectTo: 'd/a/:id/e'},
{path: 'c/d', component: ComponentA, outlet: 'aux'}
]).forEach(t => {
{path: 'c/d', component: ComponentA, outlet: 'aux'},
{path: '**', component: ComponentC}
], "/a/1(aux:c/d)", t => {
compareTrees(t, tree('/d/a/1/e(aux:c/d)'));
});
});
it("should redirect secondary routes", () => {
applyRedirects(tree("/a/1(aux:c/d)"), [
checkRedirect([
{path: 'a/:id', component: ComponentA},
{path: 'c/d', redirectTo: 'f/c/d/e', outlet: 'aux'}
]).forEach(t => {
{path: 'c/d', redirectTo: 'f/c/d/e', outlet: 'aux'},
{path: '**', component: ComponentC, outlet: 'aux'}
], "/a/1(aux:c/d)", t => {
compareTrees(t, tree('/a/1(aux:f/c/d/e)'));
});
});
it("should use the configuration of the route redirected to", () => {
checkRedirect([
{path: 'a', component: ComponentA, children: [
{path: 'b', component: ComponentB},
]},
{path: 'c', redirectTo: 'a'}
], "c/b", t => {
compareTrees(t, tree('a/b'));
});
});
it("should redirect empty path", () => {
checkRedirect([
{path: 'a', component: ComponentA, children: [
{path: 'b', component: ComponentB},
]},
{path: '', redirectTo: 'a'}
], "b", t => {
compareTrees(t, tree('a/b'));
});
});
xit("should support nested redirects", () => {
checkRedirect([
{path: 'a', component: ComponentA, children: [
{path: 'b', component: ComponentB},
{path: '', redirectTo: 'b'}
]},
{path: '', redirectTo: 'a'}
], "", t => {
compareTrees(t, tree('a/b'));
});
});
xit("should support nested redirects (when redirected to an empty path)", () => {
checkRedirect([
{path: '', component: ComponentA, children: [
{path: 'b', component: ComponentB},
{path: '', redirectTo: 'b'}
]},
{path: 'a', redirectTo: ''}
], "a", t => {
compareTrees(t, tree('b'));
});
});
it("should redirect empty path route only when terminal", () => {
const config = [
{path: 'a', component: ComponentA, children: [
{path: 'b', component: ComponentB},
]},
{path: '', redirectTo: 'a', terminal: true}
];
applyRedirects(tree("b"), config).subscribe((_) => {
throw "Should not be reached";
}, e => {
expect(e.message).toEqual("Cannot match any routes: 'b'");
});
});
it("should redirect wild cards", () => {
applyRedirects(tree("/a/1(aux:c/d)"), [
checkRedirect([
{path: '404', component: ComponentA},
{path: '**', redirectTo: '/404'},
]).forEach(t => {
], "/a/1(aux:c/d)", t => {
compareTrees(t, tree('/404'));
});
});
it("should support global redirects", () => {
applyRedirects(tree("/a/b/1"), [
checkRedirect([
{path: 'a', component: ComponentA, children: [
{path: 'b/:id', redirectTo: '/global/:id'}
]},
]).forEach(t => {
{path: '**', component: ComponentC}
], "/a/b/1", t => {
compareTrees(t, tree('/global/1'));
});
});
});
function checkRedirect(config: RouterConfig, url: string, callback: any): void {
applyRedirects(tree(url), config).subscribe(callback, e => {
throw e;
});
}
function tree(url: string): UrlTree {
return new DefaultUrlSerializer().parse(url);
}
@ -89,19 +163,18 @@ function tree(url: string): UrlTree {
function compareTrees(actual: UrlTree, expected: UrlTree): void{
const serializer = new DefaultUrlSerializer();
const error = `"${serializer.serialize(actual)}" is not equal to "${serializer.serialize(expected)}"`;
compareNode(actual._root, expected._root, error);
compareSegments(actual.root, expected.root, error);
}
function compareNode(actual: TreeNode<UrlSegment>, expected: TreeNode<UrlSegment>, error: string): void{
expect(equalUrlSegments([actual.value], [expected.value])).toEqual(true, error);
function compareSegments(actual: UrlSegment, expected: UrlSegment, error: string): void{
expect(actual).toBeDefined(error);
expect(equalPathsWithParams(actual.pathsWithParams, expected.pathsWithParams)).toEqual(true, error);
expect(actual.children.length).toEqual(expected.children.length, error);
expect(Object.keys(actual.children).length).toEqual(Object.keys(expected.children).length, error);
if (actual.children.length === expected.children.length) {
for (let i = 0; i < actual.children.length; ++i) {
compareNode(actual.children[i], expected.children[i], error);
}
}
Object.keys(expected.children).forEach(key => {
compareSegments(actual.children[key], expected.children[key], error);
});
}
class ComponentA {}

View File

@ -1,5 +1,5 @@
import {DefaultUrlSerializer} from '../src/url_serializer';
import {UrlTree} from '../src/url_tree';
import {UrlTree, UrlSegment} from '../src/url_tree';
import {TreeNode} from '../src/utils/tree';
import {Params, PRIMARY_OUTLET} from '../src/shared';
import {ActivatedRoute, RouterState, RouterStateSnapshot, createEmptyState, advanceActivatedRoute} from '../src/router_state';
@ -8,7 +8,7 @@ import {recognize} from '../src/recognize';
import {RouterConfig} from '../src/config';
describe('create router state', () => {
const emptyState = () => createEmptyState(RootComponent);
const emptyState = () => createEmptyState(new UrlTree(new UrlSegment([], {}), {}, null), RootComponent);
it('should work create new state', () => {
const state = createRouterState(createState([
@ -57,7 +57,7 @@ function advanceNode(node: TreeNode<ActivatedRoute>): void {
function createState(config: RouterConfig, url: string): RouterStateSnapshot {
let res;
recognize(RootComponent, config, tree(url)).forEach(s => res = s);
recognize(RootComponent, config, tree(url), url).forEach(s => res = s);
return res;
}

View File

@ -1,5 +1,5 @@
import {DefaultUrlSerializer} from '../src/url_serializer';
import {UrlTree, UrlSegment} from '../src/url_tree';
import {UrlTree, UrlPathWithParams, UrlSegment} from '../src/url_tree';
import {ActivatedRoute, ActivatedRouteSnapshot, advanceActivatedRoute} from '../src/router_state';
import {PRIMARY_OUTLET, Params} from '../src/shared';
import {createUrlTree} from '../src/create_url_tree';
@ -10,185 +10,163 @@ describe('createUrlTree', () => {
it("should navigate to the root", () => {
const p = serializer.parse("/");
const t = create(p.root, p, ["/"]);
expect(serializer.serialize(t)).toEqual("");
const t = createRoot(p, ["/"]);
expect(serializer.serialize(t)).toEqual("/");
});
it("should support nested segments", () => {
const p = serializer.parse("/a/b");
const t = create(p.root, p, ["/one", 11, "two", 22]);
const t = createRoot(p, ["/one", 11, "two", 22]);
expect(serializer.serialize(t)).toEqual("/one/11/two/22");
});
it("should preserve secondary segments", () => {
const p = serializer.parse("/a/11/b(right:c)");
const t = create(p.root, p, ["/a", 11, 'd']);
const t = createRoot(p, ["/a", 11, 'd']);
expect(serializer.serialize(t)).toEqual("/a/11/d(right:c)");
});
it("should support updating secondary segments", () => {
const p = serializer.parse("/a(right:b)");
const t = create(p.root, p, ["right:c", 11, 'd']);
expect(t.children(t.root)[1].outlet).toEqual("right");
const t = createRoot(p, ["right:c", 11, 'd']);
expect(serializer.serialize(t)).toEqual("/a(right:c/11/d)");
});
it("should support updating secondary segments (nested case)", () => {
const p = serializer.parse("/a/b(right:c)");
const t = create(p.root, p, ["a", "right:d", 11, 'e']);
expect(serializer.serialize(t)).toEqual("/a/b(right:d/11/e)");
const p = serializer.parse("/a/(b//right:c)");
const t = createRoot(p, ["a", "right:d", 11, 'e']);
expect(serializer.serialize(t)).toEqual("/a/(b//right:d/11/e)");
});
it('should update matrix parameters', () => {
const p = serializer.parse("/a;aa=11");
const t = create(p.root, p, ["/a", {aa: 22, bb: 33}]);
expect(serializer.serialize(t)).toEqual("/a;aa=22;bb=33");
const p = serializer.parse("/a;pp=11");
const t = createRoot(p, ["/a", {pp: 22, dd: 33}]);
expect(serializer.serialize(t)).toEqual("/a;pp=22;dd=33");
});
it('should create matrix parameters', () => {
const p = serializer.parse("/a");
const t = create(p.root, p, ["/a", {aa: 22, bb: 33}]);
expect(serializer.serialize(t)).toEqual("/a;aa=22;bb=33");
const t = createRoot(p, ["/a", {pp: 22, dd: 33}]);
expect(serializer.serialize(t)).toEqual("/a;pp=22;dd=33");
});
it('should create matrix parameters together with other segments', () => {
const p = serializer.parse("/a");
const t = create(p.root, p, ["/a", "/b", {aa: 22, bb: 33}]);
const t = createRoot(p, ["/a", "/b", {aa: 22, bb: 33}]);
expect(serializer.serialize(t)).toEqual("/a/b;aa=22;bb=33");
});
describe("node reuse", () => {
it('should reuse nodes when path is the same', () => {
const p = serializer.parse("/a/b");
const t = create(p.root, p, ['/a/c']);
expect(t.root).toBe(p.root);
expect(t.firstChild(t.root)).toBe(p.firstChild(p.root));
expect(t.firstChild(<any>t.firstChild(t.root))).not.toBe(p.firstChild(<any>p.firstChild(p.root)));
});
it("should create new node when params are the same", () => {
const p = serializer.parse("/a;x=1");
const t = create(p.root, p, ['/a', {'x': 1}]);
expect(t.firstChild(t.root)).toBe(p.firstChild(p.root));
});
it("should create new node when params are different", () => {
const p = serializer.parse("/a;x=1");
const t = create(p.root, p, ['/a', {'x': 2}]);
expect(t.firstChild(t.root)).not.toBe(p.firstChild(p.root));
});
});
describe("relative navigation", () => {
it("should work", () => {
const p = serializer.parse("/a(left:ap)/c(left:cp)");
const c = p.firstChild(p.root);
const t = create(c, p, ["c2"]);
expect(serializer.serialize(t)).toEqual("/a(left:ap)/c2(left:cp)");
const p = serializer.parse("/a/(c//left:cp)(left:ap)");
const t = create(p.root.children[PRIMARY_OUTLET], 0, p, ["c2"]);
expect(serializer.serialize(t)).toEqual("/a/(c2//left:cp)(left:ap)");
});
it("should work when the first command starts with a ./", () => {
const p = serializer.parse("/a(left:ap)/c(left:cp)");
const c = p.firstChild(p.root);
const t = create(c, p, ["./c2"]);
expect(serializer.serialize(t)).toEqual("/a(left:ap)/c2(left:cp)");
const p = serializer.parse("/a/(c//left:cp)(left:ap)");
const t = create(p.root.children[PRIMARY_OUTLET], 0, p, ["./c2"]);
expect(serializer.serialize(t)).toEqual("/a/(c2//left:cp)(left:ap)");
});
it("should work when the first command is ./)", () => {
const p = serializer.parse("/a(left:ap)/c(left:cp)");
const c = p.firstChild(p.root);
const t = create(c, p, ["./", "c2"]);
expect(serializer.serialize(t)).toEqual("/a(left:ap)/c2(left:cp)");
const p = serializer.parse("/a/(c//left:cp)(left:ap)");
const t = create(p.root.children[PRIMARY_OUTLET], 0, p, ["./", "c2"]);
expect(serializer.serialize(t)).toEqual("/a/(c2//left:cp)(left:ap)");
});
it("should work when given params", () => {
const p = serializer.parse("/a(left:ap)/c(left:cp)");
const c = p.firstChild(p.root);
const t = create(c, p, [{'x': 99}]);
expect(serializer.serialize(t)).toEqual("/a(left:ap)/c;x=99(left:cp)");
const p = serializer.parse("/a/(c//left:cp)(left:ap)");
const t = create(p.root.children[PRIMARY_OUTLET], 0, p, [{'x': 99}]);
expect(serializer.serialize(t)).toEqual("/a/(c;x=99//left:cp)(left:ap)");
});
it("should support going to a parent", () => {
const p = serializer.parse("/a(left:ap)/c(left:cp)");
const c = p.firstChild(p.root);
const t = create(c, p, ["../a2"]);
expect(serializer.serialize(t)).toEqual("/a2(left:ap)");
});
it("should support going to a parent (nested case)", () => {
it("should work when index > 0", () => {
const p = serializer.parse("/a/c");
const c = p.firstChild(<any>p.firstChild(p.root));
const t = create(c, p, ["../c2"]);
const t = create(p.root.children[PRIMARY_OUTLET], 1, p, ["c2"]);
expect(serializer.serialize(t)).toEqual("/a/c/c2");
});
it("should support going to a parent (within a segment)", () => {
const p = serializer.parse("/a/c");
const t = create(p.root.children[PRIMARY_OUTLET], 1, p, ["../c2"]);
expect(serializer.serialize(t)).toEqual("/a/c2");
});
it("should work when given ../", () => {
const p = serializer.parse("/a/c");
const c = p.firstChild(<any>p.firstChild(p.root));
const t = create(c, p, ["../"]);
expect(serializer.serialize(t)).toEqual("/a");
});
it("should navigate to the root", () => {
const p = serializer.parse("/a/c");
const c = p.firstChild(p.root);
const t = create(c, p, ["../"]);
expect(serializer.serialize(t)).toEqual("");
const t = create(p.root.children[PRIMARY_OUTLET], 1, p, ["../", "c2"]);
expect(serializer.serialize(t)).toEqual("/a/c2");
});
it("should support setting matrix params", () => {
const p = serializer.parse("/a(left:ap)/c(left:cp)");
const c = p.firstChild(p.root);
const t = create(c, p, ["../", {'x': 5}]);
const p = serializer.parse("/a/(c//left:cp)(left:ap)");
const t = create(p.root.children[PRIMARY_OUTLET], 0, p, ['../', {x: 5}]);
expect(serializer.serialize(t)).toEqual("/a;x=5(left:ap)");
});
xit("should support going to a parent (across segments)", () => {
const p = serializer.parse("/q/(a/(c//left:cp)//left:qp)(left:ap)");
const t = create(p.root.children[PRIMARY_OUTLET].children[PRIMARY_OUTLET], 0, p, ['../../q2']);
expect(serializer.serialize(t)).toEqual("/q2(left:ap)");
});
xit("should navigate to the root", () => {
const p = serializer.parse("/a/c");
const t = create(p.root.children[PRIMARY_OUTLET], 0, p, ['../']);
expect(serializer.serialize(t)).toEqual("");
});
it("should throw when too many ..", () => {
const p = serializer.parse("/a(left:ap)/c(left:cp)");
const c = p.firstChild(p.root);
expect(() => create(c, p, ["../../"])).toThrowError("Invalid number of '../'");
const p = serializer.parse("/a/(c//left:cp)(left:ap)");
expect(() => create(p.root.children[PRIMARY_OUTLET], 0, p, ['../../'])).toThrowError("Invalid number of '../'");
});
});
it("should set query params", () => {
const p = serializer.parse("/");
const t = create(p.root, p, [], {a: 'hey'});
const t = createRoot(p, [], {a: 'hey'});
expect(t.queryParams).toEqual({a: 'hey'});
});
it("should stringify query params", () => {
const p = serializer.parse("/");
const t = create(p.root, p, [], <any>{a: 1});
const t = createRoot(p, [], <any>{a: 1});
expect(t.queryParams).toEqual({a: '1'});
});
it("should reuse old query params when given undefined", () => {
const p = serializer.parse("/?a=1");
const t = create(p.root, p, [], undefined);
const t = createRoot(p, [], undefined);
expect(t.queryParams).toEqual({a: '1'});
});
it("should set fragment", () => {
const p = serializer.parse("/");
const t = create(p.root, p, [], {}, "fragment");
const t = createRoot(p, [], {}, "fragment");
expect(t.fragment).toEqual("fragment");
});
it("should reused old fragment when given undefined", () => {
const p = serializer.parse("/#fragment");
const t = create(p.root, p, [], undefined, undefined);
const t = createRoot(p, [], undefined, undefined);
expect(t.fragment).toEqual("fragment");
});
});
function create(start: UrlSegment | null, tree: UrlTree, commands: any[], queryParams?: Params, fragment?: string) {
if (!start) {
expect(start).toBeDefined();
function createRoot(tree: UrlTree, commands: any[], queryParams?: Params, fragment?: string) {
const s = new ActivatedRouteSnapshot([], <any>{}, PRIMARY_OUTLET, "someComponent", null, tree.root, -1);
const a = new ActivatedRoute(new BehaviorSubject(null), new BehaviorSubject(null), PRIMARY_OUTLET, "someComponent", s);
advanceActivatedRoute(a);
return createUrlTree(a, tree, commands, queryParams, fragment);
}
function create(segment: UrlSegment, startIndex: number, tree: UrlTree, commands: any[], queryParams?: Params, fragment?: string) {
if (!segment) {
expect(segment).toBeDefined();
}
const s = new ActivatedRouteSnapshot([], <any>{}, PRIMARY_OUTLET, "someComponent", null, <any>start);
const s = new ActivatedRouteSnapshot([], <any>{}, PRIMARY_OUTLET, "someComponent", null, <any>segment, startIndex);
const a = new ActivatedRoute(new BehaviorSubject(null), new BehaviorSubject(null), PRIMARY_OUTLET, "someComponent", s);
advanceActivatedRoute(a);
return createUrlTree(a, tree, commands, queryParams, fragment);

View File

@ -2,28 +2,27 @@ import {DefaultUrlSerializer} from '../src/url_serializer';
import {UrlTree} from '../src/url_tree';
import {Params, PRIMARY_OUTLET} from '../src/shared';
import {ActivatedRouteSnapshot} from '../src/router_state';
import {RouterConfig} from '../src/config';
import {recognize} from '../src/recognize';
describe('recognize', () => {
it('should work', (done) => {
recognize(RootComponent, [
it('should work', () => {
checkRecognize([
{
path: 'a', component: ComponentA
}
], tree("a")).forEach(s => {
], "a", s => {
checkActivatedRoute(s.root, "", {}, RootComponent);
checkActivatedRoute(s.firstChild(s.root), "a", {}, ComponentA);
done();
});
});
it('should support secondary routes', () => {
recognize(RootComponent, [
checkRecognize([
{ path: 'a', component: ComponentA },
{ path: 'b', component: ComponentB, outlet: 'left' },
{ path: 'c', component: ComponentC, outlet: 'right' }
], tree("a(left:b//right:c)")).forEach(s => {
], "a(left:b//right:c)", s => {
const c = s.children(s.root);
checkActivatedRoute(c[0], "a", {}, ComponentA);
checkActivatedRoute(c[1], "b", {}, ComponentB, 'left');
@ -31,43 +30,85 @@ describe('recognize', () => {
});
});
it('should match routes in the depth first order', () => {
it('should set url segment and index properly', () => {
const url = tree("a(left:b//right:c)");
recognize(RootComponent, [
{ path: 'a', component: ComponentA },
{ path: 'b', component: ComponentB, outlet: 'left' },
{ path: 'c', component: ComponentC, outlet: 'right' }
], url, "a(left:b//right:c)").subscribe((s) => {
expect(s.root._urlSegment).toBe(url.root);
expect(s.root._lastPathIndex).toBe(-1);
const c = s.children(s.root);
expect(c[0]._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
expect(c[0]._lastPathIndex).toBe(0);
expect(c[1]._urlSegment).toBe(url.root.children["left"]);
expect(c[1]._lastPathIndex).toBe(0);
expect(c[2]._urlSegment).toBe(url.root.children["right"]);
expect(c[2]._lastPathIndex).toBe(0);
});
});
it('should set url segment and index properly (nested case)', () => {
const url = tree("a/b/c");
recognize(RootComponent, [
{ path: '/a/b', component: ComponentA, children: [
{path: 'c', component: ComponentC}
] },
], url, "a/b/c").subscribe(s => {
expect(s.root._urlSegment).toBe(url.root);
expect(s.root._lastPathIndex).toBe(-1);
const compA = s.firstChild(s.root);
expect(compA._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
expect(compA._lastPathIndex).toBe(1);
const compC = s.firstChild(<any>compA);
expect(compC._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
expect(compC._lastPathIndex).toBe(2);
});
});
it('should match routes in the depth first order', () => {
checkRecognize([
{path: 'a', component: ComponentA, children: [{path: ':id', component: ComponentB}]},
{path: 'a/:id', component: ComponentC}
], tree("a/paramA")).forEach(s => {
], "a/paramA", s => {
checkActivatedRoute(s.root, "", {}, RootComponent);
checkActivatedRoute(s.firstChild(s.root), "a", {}, ComponentA);
checkActivatedRoute(s.firstChild(<any>s.firstChild(s.root)), "paramA", {id: 'paramA'}, ComponentB);
});
recognize(RootComponent, [
checkRecognize([
{path: 'a', component: ComponentA},
{path: 'a/:id', component: ComponentC}
], tree("a/paramA")).forEach(s => {
], "a/paramA", s => {
checkActivatedRoute(s.root, "", {}, RootComponent);
checkActivatedRoute(s.firstChild(s.root), "a/paramA", {id: 'paramA'}, ComponentC);
});
});
it('should use outlet name when matching secondary routes', () => {
recognize(RootComponent, [
checkRecognize([
{ path: 'a', component: ComponentA },
{ path: 'b', component: ComponentB, outlet: 'left' },
{ path: 'b', component: ComponentC, outlet: 'right' }
], tree("a(right:b)")).forEach(s => {
], "a(right:b)", s => {
const c = s.children(s.root);
checkActivatedRoute(c[0], "a", {}, ComponentA);
checkActivatedRoute(c[1], "b", {}, ComponentC, 'right');
});
});
it('should handle nested secondary routes', () => {
recognize(RootComponent, [
xit('should handle nested secondary routes', () => {
checkRecognize([
{ path: 'a', component: ComponentA },
{ path: 'b', component: ComponentB, outlet: 'left' },
{ path: 'c', component: ComponentC, outlet: 'right' }
], tree("a(left:b(right:c))")).forEach(s => {
], "a(left:b(right:c))", s => {
const c = s.children(s.root);
checkActivatedRoute(c[0], "a", {}, ComponentA);
checkActivatedRoute(c[1], "b", {}, ComponentB, 'left');
@ -76,12 +117,12 @@ describe('recognize', () => {
});
it('should handle non top-level secondary routes', () => {
recognize(RootComponent, [
checkRecognize([
{ path: 'a', component: ComponentA, children: [
{ path: 'b', component: ComponentB },
{ path: 'c', component: ComponentC, outlet: 'left' }
] },
], tree("a/b(left:c))")).forEach(s => {
], "a/(b//left:c)", s => {
const c = s.children(<any>s.firstChild(s.root));
checkActivatedRoute(c[0], "b", {}, ComponentB, PRIMARY_OUTLET);
checkActivatedRoute(c[1], "c", {}, ComponentC, 'left');
@ -89,11 +130,11 @@ describe('recognize', () => {
});
it('should sort routes by outlet name', () => {
recognize(RootComponent, [
checkRecognize([
{ path: 'a', component: ComponentA },
{ path: 'c', component: ComponentC, outlet: 'c' },
{ path: 'b', component: ComponentB, outlet: 'b' }
], tree("a(c:c//b:b)")).forEach(s => {
], "a(c:c//b:b)", s => {
const c = s.children(s.root);
checkActivatedRoute(c[0], "a", {}, ComponentA);
checkActivatedRoute(c[1], "b", {}, ComponentB, 'b');
@ -102,52 +143,52 @@ describe('recognize', () => {
});
it('should support matrix parameters', () => {
recognize(RootComponent, [
checkRecognize([
{
path: 'a', component: ComponentA, children: [
{ path: 'b', component: ComponentB },
{ path: 'c', component: ComponentC, outlet: 'left' }
{ path: 'b', component: ComponentB }
]
}
], tree("a;a1=11;a2=22/b;b1=111;b2=222(left:c;c1=1111;c2=2222)")).forEach(s => {
checkActivatedRoute(s.firstChild(s.root), "a", {a1: '11', a2: '22'}, ComponentA);
const c = s.children(<any>s.firstChild(s.root));
checkActivatedRoute(c[0], "b", {b1: '111', b2: '222'}, ComponentB);
},
{ path: 'c', component: ComponentC, outlet: 'left' }
], "a;a1=11;a2=22/b;b1=111;b2=222(left:c;c1=1111;c2=2222)", s => {
const c = s.children(s.root);
checkActivatedRoute(c[0], "a", {a1: '11', a2: '22'}, ComponentA);
checkActivatedRoute(s.firstChild(<any>c[0]), "b", {b1: '111', b2: '222'}, ComponentB);
checkActivatedRoute(c[1], "c", {c1: '1111', c2: '2222'}, ComponentC, 'left');
});
});
describe("index", () => {
it("should support root index routes", () => {
recognize(RootComponent, [
checkRecognize([
{index: true, component: ComponentA}
], tree("")).forEach(s => {
], "", s => {
checkActivatedRoute(s.firstChild(s.root), "", {}, ComponentA);
});
});
it("should support nested root index routes", () => {
recognize(RootComponent, [
checkRecognize([
{index: true, component: ComponentA, children: [{index: true, component: ComponentB}]}
], tree("")).forEach(s => {
], "", s => {
checkActivatedRoute(s.firstChild(s.root), "", {}, ComponentA);
checkActivatedRoute(s.firstChild(<any>s.firstChild(s.root)), "", {}, ComponentB);
});
});
it("should support index routes", () => {
recognize(RootComponent, [
checkRecognize([
{path: 'a', component: ComponentA, children: [
{index: true, component: ComponentB}
]}
], tree("a")).forEach(s => {
], "a", s => {
checkActivatedRoute(s.firstChild(s.root), "a", {}, ComponentA);
checkActivatedRoute(s.firstChild(<any>s.firstChild(s.root)), "", {}, ComponentB);
});
});
it("should support index routes with children", () => {
recognize(RootComponent, [
checkRecognize([
{
index: true, component: ComponentA, children: [
{ index: true, component: ComponentB, children: [
@ -156,7 +197,7 @@ describe('recognize', () => {
}
]
}
], tree("c/10")).forEach(s => {
], "c/10", s => {
checkActivatedRoute(s.firstChild(s.root), "", {}, ComponentA);
checkActivatedRoute(s.firstChild(<any>s.firstChild(s.root)), "", {}, ComponentB);
checkActivatedRoute(
@ -164,21 +205,96 @@ describe('recognize', () => {
});
});
it("should pass parameters to every nested index route (case with non-index route)", () => {
recognize(RootComponent, [
xit("should pass parameters to every nested index route (case with non-index route)", () => {
checkRecognize([
{path: 'a', component: ComponentA, children: [{index: true, component: ComponentB}]}
], tree("/a;a=1")).forEach(s => {
], "/a;a=1", s => {
checkActivatedRoute(s.firstChild(s.root), "a", {a: '1'}, ComponentA);
checkActivatedRoute(s.firstChild(<any>s.firstChild(s.root)), "", {a: '1'}, ComponentB);
});
});
});
describe("matching empty url", () => {
it("should support root index routes", () => {
recognize(RootComponent, [
{path: '', component: ComponentA}
], tree(""), "").forEach(s => {
checkActivatedRoute(s.firstChild(s.root), "", {}, ComponentA);
});
});
it("should support nested root index routes", () => {
recognize(RootComponent, [
{path: '', component: ComponentA, children: [{path: '', component: ComponentB}]}
], tree(""), "").forEach(s => {
checkActivatedRoute(s.firstChild(s.root), "", {}, ComponentA);
checkActivatedRoute(s.firstChild(<any>s.firstChild(s.root)), "", {}, ComponentB);
});
});
it('should set url segment and index properly', () => {
const url = tree("");
recognize(RootComponent, [
{path: '', component: ComponentA, children: [{path: '', component: ComponentB}]}
], url, "").forEach(s => {
expect(s.root._urlSegment).toBe(url.root);
expect(s.root._lastPathIndex).toBe(-1);
const c = s.firstChild(s.root);
expect(c._urlSegment).toBe(url.root);
expect(c._lastPathIndex).toBe(-1);
const c2 = s.firstChild(<any>s.firstChild(s.root));
expect(c2._urlSegment).toBe(url.root);
expect(c2._lastPathIndex).toBe(-1);
});
});
it("should support index routes", () => {
recognize(RootComponent, [
{path: 'a', component: ComponentA, children: [
{path: '', component: ComponentB}
]}
], tree("a"), "a").forEach(s => {
checkActivatedRoute(s.firstChild(s.root), "a", {}, ComponentA);
checkActivatedRoute(s.firstChild(<any>s.firstChild(s.root)), "", {}, ComponentB);
});
});
it("should support index routes with children", () => {
recognize(RootComponent, [
{
path: '', component: ComponentA, children: [
{ path: '', component: ComponentB, children: [
{path: 'c/:id', component: ComponentC}
]
}
]
}
], tree("c/10"), "c/10").forEach(s => {
checkActivatedRoute(s.firstChild(s.root), "", {}, ComponentA);
checkActivatedRoute(s.firstChild(<any>s.firstChild(s.root)), "", {}, ComponentB);
checkActivatedRoute(
s.firstChild(<any>s.firstChild(<any>s.firstChild(s.root))), "c/10", {id: '10'}, ComponentC);
});
});
xit("should pass parameters to every nested index route (case with non-index route)", () => {
recognize(RootComponent, [
{path: 'a', component: ComponentA, children: [{path: '', component: ComponentB}]}
], tree("/a;a=1"), "/a;a=1").forEach(s => {
checkActivatedRoute(s.firstChild(s.root), "a", {a: '1'}, ComponentA);
checkActivatedRoute(s.firstChild(<any>s.firstChild(s.root)), "", {a: '1'}, ComponentB);
});
});
});
describe("wildcards", () => {
it("should support simple wildcards", () => {
recognize(RootComponent, [
checkRecognize([
{path: '**', component: ComponentA}
], tree("a/b/c/d;a1=11")).forEach(s => {
], "a/b/c/d;a1=11", s => {
checkActivatedRoute(s.firstChild(s.root), "a/b/c/d", {a1:'11'}, ComponentA);
});
});
@ -187,7 +303,7 @@ describe('recognize', () => {
describe("query parameters", () => {
it("should support query params", () => {
const config = [{path: 'a', component: ComponentA}];
recognize(RootComponent, config, tree("a?q=11")).forEach(s => {
checkRecognize(config, "a?q=11", s => {
expect(s.queryParams).toEqual({q: '11'});
});
});
@ -196,7 +312,7 @@ describe('recognize', () => {
describe("fragment", () => {
it("should support fragment", () => {
const config = [{path: 'a', component: ComponentA}];
recognize(RootComponent, config, tree("a#f1")).forEach(s => {
checkRecognize(config, "a#f1", s => {
expect(s.fragment).toEqual("f1");
});
});
@ -208,7 +324,7 @@ describe('recognize', () => {
{ path: 'a', component: ComponentA },
{ path: 'b', component: ComponentB, outlet: 'aux' },
{ path: 'c', component: ComponentC, outlet: 'aux' }
], tree("a(aux:b//aux:c)")).subscribe((_) => {}, s => {
], tree("a(aux:b//aux:c)"), "a(aux:b//aux:c)").subscribe((_) => {}, s => {
expect(s.toString()).toContain("Two segments cannot have the same outlet name: 'aux:b' and 'aux:c'.");
});
});
@ -216,7 +332,7 @@ describe('recognize', () => {
it("should error when no matching routes", () => {
recognize(RootComponent, [
{ path: 'a', component: ComponentA }
], tree("invalid")).subscribe((_) => {}, s => {
], tree("invalid"), "invalid").subscribe((_) => {}, s => {
expect(s.toString()).toContain("Cannot match any routes");
});
});
@ -224,18 +340,24 @@ describe('recognize', () => {
it("should error when no matching routes (too short)", () => {
recognize(RootComponent, [
{ path: 'a/:id', component: ComponentA }
], tree("a")).subscribe((_) => {}, s => {
], tree("a"), "a").subscribe((_) => {}, s => {
expect(s.toString()).toContain("Cannot match any routes");
});
});
});
});
function checkRecognize(config: RouterConfig, url: string, callback: any): void {
recognize(RootComponent, config, tree(url), url).subscribe(callback, e => {
throw e;
});
}
function checkActivatedRoute(actual: ActivatedRouteSnapshot | null, url: string, params: Params, cmp: Function, outlet: string = PRIMARY_OUTLET):void {
if (actual === null) {
expect(actual).not.toBeNull();
} else {
expect(actual.urlSegments.map(s => s.path).join("/")).toEqual(url);
expect(actual.url.map(s => s.path).join("/")).toEqual(url);
expect(actual.params).toEqual(params);
expect(actual.component).toBe(cmp);
expect(actual.outlet).toEqual(outlet);

View File

@ -27,6 +27,7 @@ describe("Integration", () => {
beforeEachProviders(() => {
let config: RouterConfig = [
{ path: '', component: BlankCmp },
{ path: 'simple', component: SimpleCmp }
];
@ -54,19 +55,20 @@ describe("Integration", () => {
router.navigateByUrl('/simple');
advance(fixture);
expect(location.path()).toEqual('/simple');
})));
it('should update location when navigating',
fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22');
advance(fixture);
expect(location.path()).toEqual('/team/22');
@ -79,6 +81,9 @@ describe("Integration", () => {
xit('should navigate back and forward',
fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'simple', component: SimpleCmp },
@ -86,7 +91,6 @@ describe("Integration", () => {
] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
router.navigateByUrl('/team/33/simple');
advance(fixture);
@ -106,14 +110,15 @@ describe("Integration", () => {
it('should navigate when locations changes',
fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'user/:name', component: UserCmp }
] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
router.navigateByUrl('/team/22/user/victor');
advance(fixture);
@ -125,6 +130,9 @@ describe("Integration", () => {
it('should support secondary routes',
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'user/:name', component: UserCmp },
@ -132,9 +140,7 @@ describe("Integration", () => {
] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
router.navigateByUrl('/team/22/user/victor(right:simple)');
router.navigateByUrl('/team/22/(user/victor//right:simple)');
advance(fixture);
expect(fixture.debugElement.nativeElement)
@ -143,6 +149,9 @@ describe("Integration", () => {
it('should deactivate outlets',
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'user/:name', component: UserCmp },
@ -150,9 +159,7 @@ describe("Integration", () => {
] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
router.navigateByUrl('/team/22/user/victor(right:simple)');
router.navigateByUrl('/team/22/(user/victor//right:simple)');
advance(fixture);
router.navigateByUrl('/team/22/user/victor');
@ -163,16 +170,18 @@ describe("Integration", () => {
it('should deactivate nested outlets',
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'user/:name', component: UserCmp },
{ path: 'simple', component: SimpleCmp, outlet: 'right' }
] }
] },
{ path: '', component: BlankCmp}
]);
const fixture = tcb.createFakeAsync(RootCmp);
router.navigateByUrl('/team/22/user/victor(right:simple)');
router.navigateByUrl('/team/22/(user/victor//right:simple)');
advance(fixture);
router.navigateByUrl('/');
@ -183,12 +192,13 @@ describe("Integration", () => {
it('should set query params and fragment',
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'query', component: QueryParamsAndFragmentCmp }
]);
const fixture = tcb.createFakeAsync(RootCmp);
router.navigateByUrl('/query?name=1#fragment1');
advance(fixture);
expect(fixture.debugElement.nativeElement).toHaveText('query: 1 fragment: fragment1');
@ -200,14 +210,15 @@ describe("Integration", () => {
it('should push params only when they change',
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb:TestComponentBuilder) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'user/:name', component: UserCmp }
] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
router.navigateByUrl('/team/22/user/victor');
advance(fixture);
const team = fixture.debugElement.children[1].componentInstance;
@ -225,13 +236,14 @@ describe("Integration", () => {
it('should work when navigating to /',
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb:TestComponentBuilder) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ index: true, component: SimpleCmp },
{ path: '/user/:name', component: UserCmp }
]);
const fixture = tcb.createFakeAsync(RootCmp);
router.navigateByUrl('/user/victor');
advance(fixture);
@ -245,6 +257,9 @@ describe("Integration", () => {
it("should cancel in-flight navigations",
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb:TestComponentBuilder) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: '/user/:name', component: UserCmp }
]);
@ -252,7 +267,6 @@ describe("Integration", () => {
const recordedEvents = [];
router.events.forEach(e => recordedEvents.push(e));
const fixture = tcb.createFakeAsync(RootCmp);
router.navigateByUrl('/user/init');
advance(fixture);
@ -269,7 +283,7 @@ describe("Integration", () => {
expect(fixture.debugElement.nativeElement).toHaveText('user fedor');
expect(user.recordedParams).toEqual([{name: 'init'}, {name: 'fedor'}]);
expectEvents(router, recordedEvents.slice(2), [
expectEvents(recordedEvents, [
[NavigationStart, '/user/init'],
[RoutesRecognized, '/user/init'],
[NavigationEnd, '/user/init'],
@ -285,6 +299,9 @@ describe("Integration", () => {
it("should handle failed navigations gracefully",
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb:TestComponentBuilder) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: '/user/:name', component: UserCmp }
]);
@ -292,9 +309,6 @@ describe("Integration", () => {
const recordedEvents = [];
router.events.forEach(e => recordedEvents.push(e));
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
let e;
router.navigateByUrl('/invalid').catch(_ => e = _);
advance(fixture);
@ -305,7 +319,7 @@ describe("Integration", () => {
expect(fixture.debugElement.nativeElement).toHaveText('user fedor');
expectEvents(router, recordedEvents.slice(2), [
expectEvents(recordedEvents, [
[NavigationStart, '/invalid'],
[NavigationError, '/invalid'],
@ -318,6 +332,9 @@ describe("Integration", () => {
describe("router links", () => {
it("should support string router links",
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'link', component: StringLinkCmp },
@ -325,9 +342,6 @@ describe("Integration", () => {
] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22/link');
advance(fixture);
expect(fixture.debugElement.nativeElement).toHaveText('team 22 { link, right: }');
@ -342,6 +356,9 @@ describe("Integration", () => {
it("should support absolute router links",
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'link', component: AbsoluteLinkCmp },
@ -349,9 +366,6 @@ describe("Integration", () => {
] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22/link');
advance(fixture);
expect(fixture.debugElement.nativeElement).toHaveText('team 22 { link, right: }');
@ -366,6 +380,9 @@ describe("Integration", () => {
it("should support relative router links",
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'link', component: RelativeLinkCmp },
@ -373,9 +390,6 @@ describe("Integration", () => {
] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22/link');
advance(fixture);
expect(fixture.debugElement.nativeElement)
@ -394,11 +408,15 @@ describe("Integration", () => {
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb) => {
let fixture = tcb.createFakeAsync(AbsoluteLinkCmp);
advance(fixture);
expect(fixture.debugElement.nativeElement).toHaveText('link');
})));
it("should support query params and fragments",
fakeAsync(inject([Router, Location, TestComponentBuilder], (router, location, tcb) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'link', component: LinkWithQueryParamsAndFragment },
@ -406,9 +424,6 @@ describe("Integration", () => {
] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22/link');
advance(fixture);
@ -426,14 +441,14 @@ describe("Integration", () => {
describe("redirects", () => {
it("should work", fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: '/old/team/:id', redirectTo: 'team/:id' },
{ path: '/team/:id', component: TeamCmp }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('old/team/22');
advance(fixture);
@ -450,17 +465,17 @@ describe("Integration", () => {
it('works',
fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, canActivate: ["alwaysFalse"] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22');
advance(fixture);
expect(location.path()).toEqual('');
expect(location.path()).toEqual('/');
})));
});
@ -471,13 +486,13 @@ describe("Integration", () => {
it('works',
fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, canActivate: ["alwaysTrue"] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22');
advance(fixture);
@ -496,13 +511,13 @@ describe("Integration", () => {
it('works',
fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, canActivate: [AlwaysTrue] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22');
advance(fixture);
@ -519,16 +534,16 @@ describe("Integration", () => {
it('works',
fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, canActivate: ['CanActivate'] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22');
advance(fixture);
expect(location.path()).toEqual('');
expect(location.path()).toEqual('/');
})));
});
});
@ -544,13 +559,13 @@ describe("Integration", () => {
it('works',
fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, canDeactivate: ["CanDeactivate"] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22');
advance(fixture);
@ -579,13 +594,13 @@ describe("Integration", () => {
it('works',
fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, canDeactivate: [AlwaysTrue] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22');
advance(fixture);
expect(location.path()).toEqual('/team/22');
@ -606,13 +621,13 @@ describe("Integration", () => {
it('works',
fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivate'] }
]);
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
router.navigateByUrl('/team/22');
advance(fixture);
expect(location.path()).toEqual('/team/22');
@ -625,10 +640,10 @@ describe("Integration", () => {
});
});
function expectEvents(router: Router, events:Event[], pairs: any[]) {
function expectEvents(events:Event[], pairs: any[]) {
for (let i = 0; i < events.length; ++i) {
expect((<any>events[i].constructor).name).toBe(pairs[i][0].name);
expect(router.serializeUrl((<any>events[i]).url)).toBe(pairs[i][1]);
expect((<any>events[i]).url).toBe(pairs[i][1]);
}
}
@ -641,7 +656,7 @@ class StringLinkCmp {}
@Component({
selector: 'link-cmp',
template: `<a [routerLink]="['/team/33/simple']">link</a>`,
template: `<router-outlet></router-outlet><a [routerLink]="['/team/33/simple']">link</a>`,
directives: ROUTER_DIRECTIVES
})
class AbsoluteLinkCmp {}
@ -668,6 +683,14 @@ class LinkWithQueryParamsAndFragment {}
class SimpleCmp {
}
@Component({
selector: 'blank-cmp',
template: ``,
directives: ROUTER_DIRECTIVES
})
class BlankCmp {
}
@Component({
selector: 'team-cmp',
template: `team {{id | async}} { <router-outlet></router-outlet>, right: <router-outlet name="right"></router-outlet> }`,

View File

@ -1,76 +1,80 @@
import {DefaultUrlSerializer, serializeSegment} from '../src/url_serializer';
import {DefaultUrlSerializer, serializePath} from '../src/url_serializer';
import {UrlSegment} from '../src/url_tree';
import {PRIMARY_OUTLET} from '../src/shared';
describe('url serializer', () => {
const url = new DefaultUrlSerializer();
it('should parse the root url', () => {
const tree = url.parse("/");
expectSegment(tree.root, "");
expect(url.serialize(tree)).toEqual("");
expect(url.serialize(tree)).toEqual("/");
});
it('should parse non-empty urls', () => {
const tree = url.parse("one/two");
const one = tree.firstChild(tree.root);
expectSegment(one, "one");
expectSegment(tree.firstChild(<any>one), "two");
expectSegment(tree.root.children[PRIMARY_OUTLET], "one/two");
expect(url.serialize(tree)).toEqual("/one/two");
});
it("should parse multiple secondary segments", () => {
const tree = url.parse("/one/two(left:three//right:four)/five");
const c = tree.children(<any>tree.firstChild(tree.root));
const tree = url.parse("/one/two(left:three//right:four)");
expectSegment(c[0], "two");
expectSegment(c[1], "left:three");
expectSegment(c[2], "right:four");
expectSegment(tree.root.children[PRIMARY_OUTLET], "one/two");
expectSegment(tree.root.children['left'], "three");
expectSegment(tree.root.children['right'], "four");
expectSegment(tree.firstChild(c[0]), "five");
expect(url.serialize(tree)).toEqual("/one/two(left:three//right:four)/five");
expect(url.serialize(tree)).toEqual("/one/two(left:three//right:four)");
});
it("should parse secondary segments that have secondary segments", () => {
const tree = url.parse("/one(left:two(right:three))");
const c = tree.children(tree.root);
it("should parse scoped secondary segments", () => {
const tree = url.parse("/one/(two//left:three)");
expectSegment(c[0], "one");
expectSegment(c[1], "left:two");
expectSegment(c[2], "right:three");
const primary = tree.root.children[PRIMARY_OUTLET];
expectSegment(primary, "one", true);
expect(url.serialize(tree)).toEqual("/one(left:two//right:three)");
expectSegment(primary.children[PRIMARY_OUTLET], "two");
expectSegment(primary.children["left"], "three");
expect(url.serialize(tree)).toEqual("/one/(two//left:three)");
});
it("should parse scoped secondary segments with unscoped ones", () => {
const tree = url.parse("/one/(two//left:three)(right:four)");
const primary = tree.root.children[PRIMARY_OUTLET];
expectSegment(primary, "one", true);
expectSegment(primary.children[PRIMARY_OUTLET], "two");
expectSegment(primary.children["left"], "three");
expectSegment(tree.root.children["right"], "four");
expect(url.serialize(tree)).toEqual("/one/(two//left:three)(right:four)");
});
it("should parse secondary segments that have children", () => {
const tree = url.parse("/one(left:two/three)");
const c = tree.children(tree.root);
expectSegment(c[0], "one");
expectSegment(c[1], "left:two");
expectSegment(tree.firstChild(c[1]), "three");
expectSegment(tree.root.children[PRIMARY_OUTLET], "one");
expectSegment(tree.root.children['left'], "two/three");
expect(url.serialize(tree)).toEqual("/one(left:two/three)");
});
it("should parse an empty secondary segment group", () => {
const tree = url.parse("/one()");
const c = tree.children(tree.root);
expectSegment(c[0], "one");
expect(tree.children(c[0]).length).toEqual(0);
expectSegment(tree.root.children[PRIMARY_OUTLET], "one");
expect(url.serialize(tree)).toEqual("/one");
});
it("should parse key-value matrix params", () => {
const tree = url.parse("/one;a=11a;b=11b(left:two;c=22//right:three;d=33)");
const c = tree.children(tree.root);
expectSegment(c[0], "one;a=11a;b=11b");
expectSegment(c[1], "left:two;c=22");
expectSegment(c[2], "right:three;d=33");
expectSegment(tree.root.children[PRIMARY_OUTLET], "one;a=11a;b=11b");
expectSegment(tree.root.children["left"], "two;c=22");
expectSegment(tree.root.children["right"], "three;d=33");
expect(url.serialize(tree)).toEqual("/one;a=11a;b=11b(left:two;c=22//right:three;d=33)");
});
@ -78,8 +82,7 @@ describe('url serializer', () => {
it("should parse key only matrix params", () => {
const tree = url.parse("/one;a");
const c = tree.firstChild(tree.root);
expectSegment(c, "one;a=true");
expectSegment(tree.root.children[PRIMARY_OUTLET], "one;a=true");
expect(url.serialize(tree)).toEqual("/one;a=true");
});
@ -112,6 +115,8 @@ describe('url serializer', () => {
});
});
function expectSegment(segment:UrlSegment | null, expected:string):void {
expect(segment ? serializeSegment(segment) : null).toEqual(expected);
function expectSegment(segment:UrlSegment, expected:string, hasChildren: boolean = false):void {
const p = segment.pathsWithParams.map(p => serializePath(p)).join("/");
expect(p).toEqual(expected);
expect(Object.keys(segment.children).length > 0).toEqual(hasChildren);
}