refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5352
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
describe,
|
||||
it,
|
||||
iit,
|
||||
ddescribe,
|
||||
expect,
|
||||
inject,
|
||||
beforeEach,
|
||||
SpyObject
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {Map, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {RouteMatch, PathMatch, RedirectMatch} from 'angular2/src/router/route_recognizer';
|
||||
import {ComponentRecognizer} from 'angular2/src/router/component_recognizer';
|
||||
|
||||
import {Route, Redirect} from 'angular2/src/router/route_config_decorator';
|
||||
import {parser} from 'angular2/src/router/url_parser';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/promise';
|
||||
|
||||
|
||||
export function main() {
|
||||
describe('ComponentRecognizer', () => {
|
||||
var recognizer: ComponentRecognizer;
|
||||
|
||||
beforeEach(() => { recognizer = new ComponentRecognizer(); });
|
||||
|
||||
|
||||
it('should recognize a static segment', inject([AsyncTestCompleter], (async) => {
|
||||
recognizer.config(new Route({path: '/test', component: DummyCmpA}));
|
||||
recognize(recognizer, '/test')
|
||||
.then((solutions: RouteMatch[]) => {
|
||||
expect(solutions.length).toBe(1);
|
||||
expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should recognize a single slash', inject([AsyncTestCompleter], (async) => {
|
||||
recognizer.config(new Route({path: '/', component: DummyCmpA}));
|
||||
recognize(recognizer, '/')
|
||||
.then((solutions: RouteMatch[]) => {
|
||||
expect(solutions.length).toBe(1);
|
||||
expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should recognize a dynamic segment', inject([AsyncTestCompleter], (async) => {
|
||||
recognizer.config(new Route({path: '/user/:name', component: DummyCmpA}));
|
||||
recognize(recognizer, '/user/brian')
|
||||
.then((solutions: RouteMatch[]) => {
|
||||
expect(solutions.length).toBe(1);
|
||||
expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
|
||||
expect(getParams(solutions[0])).toEqual({'name': 'brian'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should recognize a star segment', inject([AsyncTestCompleter], (async) => {
|
||||
recognizer.config(new Route({path: '/first/*rest', component: DummyCmpA}));
|
||||
recognize(recognizer, '/first/second/third')
|
||||
.then((solutions: RouteMatch[]) => {
|
||||
expect(solutions.length).toBe(1);
|
||||
expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
|
||||
expect(getParams(solutions[0])).toEqual({'rest': 'second/third'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should throw when given two routes that start with the same static segment', () => {
|
||||
recognizer.config(new Route({path: '/hello', component: DummyCmpA}));
|
||||
expect(() => recognizer.config(new Route({path: '/hello', component: DummyCmpB})))
|
||||
.toThrowError('Configuration \'/hello\' conflicts with existing route \'/hello\'');
|
||||
});
|
||||
|
||||
|
||||
it('should throw when given two routes that have dynamic segments in the same order', () => {
|
||||
recognizer.config(new Route({path: '/hello/:person/how/:doyoudou', component: DummyCmpA}));
|
||||
expect(() => recognizer.config(
|
||||
new Route({path: '/hello/:friend/how/:areyou', component: DummyCmpA})))
|
||||
.toThrowError(
|
||||
'Configuration \'/hello/:friend/how/:areyou\' conflicts with existing route \'/hello/:person/how/:doyoudou\'');
|
||||
|
||||
expect(() => recognizer.config(
|
||||
new Redirect({path: '/hello/:pal/how/:goesit', redirectTo: ['/Foo']})))
|
||||
.toThrowError(
|
||||
'Configuration \'/hello/:pal/how/:goesit\' conflicts with existing route \'/hello/:person/how/:doyoudou\'');
|
||||
});
|
||||
|
||||
|
||||
it('should recognize redirects', inject([AsyncTestCompleter], (async) => {
|
||||
recognizer.config(new Route({path: '/b', component: DummyCmpA}));
|
||||
recognizer.config(new Redirect({path: '/a', redirectTo: ['B']}));
|
||||
recognize(recognizer, '/a')
|
||||
.then((solutions: RouteMatch[]) => {
|
||||
expect(solutions.length).toBe(1);
|
||||
var solution = solutions[0];
|
||||
expect(solution).toBeAnInstanceOf(RedirectMatch);
|
||||
if (solution instanceof RedirectMatch) {
|
||||
expect(solution.redirectTo).toEqual(['B']);
|
||||
}
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should generate URLs with params', () => {
|
||||
recognizer.config(new Route({path: '/app/user/:name', component: DummyCmpA, name: 'User'}));
|
||||
var instruction = recognizer.generate('User', {'name': 'misko'});
|
||||
expect(instruction.urlPath).toEqual('app/user/misko');
|
||||
});
|
||||
|
||||
|
||||
it('should generate URLs with numeric params', () => {
|
||||
recognizer.config(new Route({path: '/app/page/:number', component: DummyCmpA, name: 'Page'}));
|
||||
expect(recognizer.generate('Page', {'number': 42}).urlPath).toEqual('app/page/42');
|
||||
});
|
||||
|
||||
|
||||
it('should throw in the absence of required params URLs', () => {
|
||||
recognizer.config(new Route({path: 'app/user/:name', component: DummyCmpA, name: 'User'}));
|
||||
expect(() => recognizer.generate('User', {}))
|
||||
.toThrowError('Route generator for \'name\' was not included in parameters passed.');
|
||||
});
|
||||
|
||||
|
||||
it('should throw if the route alias is not TitleCase', () => {
|
||||
expect(() => recognizer.config(
|
||||
new Route({path: 'app/user/:name', component: DummyCmpA, name: 'user'})))
|
||||
.toThrowError(
|
||||
`Route "app/user/:name" with name "user" does not begin with an uppercase letter. Route names should be CamelCase like "User".`);
|
||||
});
|
||||
|
||||
|
||||
describe('params', () => {
|
||||
it('should recognize parameters within the URL path',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
recognizer.config(
|
||||
new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
|
||||
recognize(recognizer, '/profile/matsko?comments=all')
|
||||
.then((solutions: RouteMatch[]) => {
|
||||
expect(solutions.length).toBe(1);
|
||||
expect(getParams(solutions[0])).toEqual({'name': 'matsko', 'comments': 'all'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should generate and populate the given static-based route with querystring params',
|
||||
() => {
|
||||
recognizer.config(
|
||||
new Route({path: 'forum/featured', component: DummyCmpA, name: 'ForumPage'}));
|
||||
|
||||
var params = {'start': 10, 'end': 100};
|
||||
|
||||
var result = recognizer.generate('ForumPage', params);
|
||||
expect(result.urlPath).toEqual('forum/featured');
|
||||
expect(result.urlParams).toEqual(['start=10', 'end=100']);
|
||||
});
|
||||
|
||||
|
||||
it('should prefer positional params over query params',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
recognizer.config(
|
||||
new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
|
||||
recognize(recognizer, '/profile/yegor?name=igor')
|
||||
.then((solutions: RouteMatch[]) => {
|
||||
expect(solutions.length).toBe(1);
|
||||
expect(getParams(solutions[0])).toEqual({'name': 'yegor'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should ignore matrix params for the top-level component',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
recognizer.config(
|
||||
new Route({path: '/home/:subject', component: DummyCmpA, name: 'User'}));
|
||||
recognize(recognizer, '/home;sort=asc/zero;one=1?two=2')
|
||||
.then((solutions: RouteMatch[]) => {
|
||||
expect(solutions.length).toBe(1);
|
||||
expect(getParams(solutions[0])).toEqual({'subject': 'zero', 'two': '2'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function recognize(recognizer: ComponentRecognizer, url: string): Promise<RouteMatch[]> {
|
||||
var parsedUrl = parser.parse(url);
|
||||
return PromiseWrapper.all(recognizer.recognize(parsedUrl));
|
||||
}
|
||||
|
||||
function getComponentType(routeMatch: RouteMatch): any {
|
||||
if (routeMatch instanceof PathMatch) {
|
||||
return routeMatch.instruction.componentType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getParams(routeMatch: RouteMatch): any {
|
||||
if (routeMatch instanceof PathMatch) {
|
||||
return routeMatch.instruction.params;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class DummyCmpA {}
|
||||
class DummyCmpB {}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Router integration tests
|
||||
|
||||
These tests only mock out `Location`, and otherwise use all the real parts of routing to ensure that
|
||||
various routing scenarios work as expected.
|
||||
|
||||
The Component Router in Angular 2 exposes only a handful of different options, but because they can
|
||||
be combined and nested in so many ways, it's difficult to rigorously test all the cases.
|
||||
|
||||
The address this problem, we introduce `describeRouter`, `describeWith`, and `describeWithout`.
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
describeRouter,
|
||||
ddescribeRouter,
|
||||
describeWith,
|
||||
describeWithout,
|
||||
describeWithAndWithout,
|
||||
itShouldRoute
|
||||
} from './util';
|
||||
|
||||
import {registerSpecs} from './impl/async_route_spec_impl';
|
||||
|
||||
export function main() {
|
||||
registerSpecs();
|
||||
|
||||
ddescribeRouter('async routes', () => {
|
||||
describeWithout('children', () => {
|
||||
describeWith('route data', itShouldRoute);
|
||||
describeWithAndWithout('params', itShouldRoute);
|
||||
});
|
||||
|
||||
describeWith('sync children',
|
||||
() => { describeWithAndWithout('default routes', itShouldRoute); });
|
||||
|
||||
describeWith('async children', () => {
|
||||
describeWithAndWithout('params', () => { describeWithout('default routes', itShouldRoute); });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
RootTestComponent,
|
||||
AsyncTestCompleter,
|
||||
TestComponentBuilder,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
el,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
beforeEachProviders,
|
||||
it,
|
||||
xit
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {provide, Component, Injector, Inject} from 'angular2/core';
|
||||
|
||||
import {Router, ROUTER_DIRECTIVES, RouteParams, RouteData, Location} from 'angular2/router';
|
||||
import {RouteConfig, Route, AuxRoute, Redirect} from 'angular2/src/router/route_config_decorator';
|
||||
|
||||
import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
|
||||
|
||||
var cmpInstanceCount;
|
||||
var childCmpInstanceCount;
|
||||
|
||||
export function main() {
|
||||
describe('auxiliary routes', () => {
|
||||
|
||||
var tcb: TestComponentBuilder;
|
||||
var rootTC: RootTestComponent;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
childCmpInstanceCount = 0;
|
||||
cmpInstanceCount = 0;
|
||||
}));
|
||||
|
||||
it('should recognize and navigate from the URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
||||
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Aux'})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/hello(modal)'))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate via the link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
||||
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
|
||||
]))
|
||||
.then((_) => rtr.navigate(['/Hello', ['Modal']]))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'hello-cmp', template: `{{greeting}}`})
|
||||
class HelloCmp {
|
||||
greeting: string;
|
||||
constructor() { this.greeting = 'hello'; }
|
||||
}
|
||||
|
||||
@Component({selector: 'modal-cmp', template: `modal`})
|
||||
class ModalCmp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'aux-cmp',
|
||||
template: 'main {<router-outlet></router-outlet>} | ' +
|
||||
'aux {<router-outlet name="modal"></router-outlet>}',
|
||||
directives: [ROUTER_DIRECTIVES],
|
||||
})
|
||||
@RouteConfig([
|
||||
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
||||
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Aux'})
|
||||
])
|
||||
class AuxCmp {
|
||||
}
|
||||
+62
-54
@@ -38,44 +38,39 @@ import {ApplicationRef} from 'angular2/src/core/application_ref';
|
||||
import {MockApplicationRef} from 'angular2/src/mock/mock_application_ref';
|
||||
|
||||
export function main() {
|
||||
describe('router injectables', () => {
|
||||
beforeEachProviders(() => {
|
||||
return [
|
||||
ROUTER_PROVIDERS,
|
||||
provide(LocationStrategy, {useClass: MockLocationStrategy}),
|
||||
provide(ApplicationRef, {useClass: MockApplicationRef})
|
||||
];
|
||||
});
|
||||
describe('router bootstrap', () => {
|
||||
beforeEachProviders(() => [
|
||||
ROUTER_PROVIDERS,
|
||||
provide(LocationStrategy, {useClass: MockLocationStrategy}),
|
||||
provide(ApplicationRef, {useClass: MockApplicationRef})
|
||||
]);
|
||||
|
||||
// do not refactor out the `bootstrap` functionality. We still want to
|
||||
// keep this test around so we can ensure that bootstrapping a router works
|
||||
describe('bootstrap functionality', () => {
|
||||
it('should bootstrap a simple app', inject([AsyncTestCompleter], (async) => {
|
||||
var fakeDoc = DOM.createHtmlDocument();
|
||||
var el = DOM.createElement('app-cmp', fakeDoc);
|
||||
DOM.appendChild(fakeDoc.body, el);
|
||||
it('should bootstrap a simple app', inject([AsyncTestCompleter], (async) => {
|
||||
var fakeDoc = DOM.createHtmlDocument();
|
||||
var el = DOM.createElement('app-cmp', fakeDoc);
|
||||
DOM.appendChild(fakeDoc.body, el);
|
||||
|
||||
bootstrap(AppCmp,
|
||||
[
|
||||
ROUTER_PROVIDERS,
|
||||
provide(ROUTER_PRIMARY_COMPONENT, {useValue: AppCmp}),
|
||||
provide(LocationStrategy, {useClass: MockLocationStrategy}),
|
||||
provide(DOCUMENT, {useValue: fakeDoc})
|
||||
])
|
||||
.then((applicationRef) => {
|
||||
var router = applicationRef.hostComponent.router;
|
||||
router.subscribe((_) => {
|
||||
expect(el).toHaveText('outer { hello }');
|
||||
expect(applicationRef.hostComponent.location.path()).toEqual('');
|
||||
async.done();
|
||||
});
|
||||
bootstrap(AppCmp,
|
||||
[
|
||||
ROUTER_PROVIDERS,
|
||||
provide(ROUTER_PRIMARY_COMPONENT, {useValue: AppCmp}),
|
||||
provide(LocationStrategy, {useClass: MockLocationStrategy}),
|
||||
provide(DOCUMENT, {useValue: fakeDoc})
|
||||
])
|
||||
.then((applicationRef) => {
|
||||
var router = applicationRef.hostComponent.router;
|
||||
router.subscribe((_) => {
|
||||
expect(el).toHaveText('outer { hello }');
|
||||
expect(applicationRef.hostComponent.location.path()).toEqual('');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
describe('broken app', () => {
|
||||
beforeEachProviders(
|
||||
() => { return [provide(ROUTER_PRIMARY_COMPONENT, {useValue: BrokenAppCmp})]; });
|
||||
beforeEachProviders(() => [provide(ROUTER_PRIMARY_COMPONENT, {useValue: BrokenAppCmp})]);
|
||||
|
||||
it('should rethrow exceptions from component constructors',
|
||||
inject([AsyncTestCompleter, TestComponentBuilder], (async, tcb: TestComponentBuilder) => {
|
||||
@@ -91,8 +86,7 @@ export function main() {
|
||||
});
|
||||
|
||||
describe('back button app', () => {
|
||||
beforeEachProviders(
|
||||
() => { return [provide(ROUTER_PRIMARY_COMPONENT, {useValue: HierarchyAppCmp})]; });
|
||||
beforeEachProviders(() => [provide(ROUTER_PRIMARY_COMPONENT, {useValue: HierarchyAppCmp})]);
|
||||
|
||||
it('should change the url without pushing a new history state for back navigations',
|
||||
inject([AsyncTestCompleter, TestComponentBuilder], (async, tcb: TestComponentBuilder) => {
|
||||
@@ -184,7 +178,7 @@ export function main() {
|
||||
}));
|
||||
});
|
||||
});
|
||||
// TODO: add a test in which the child component has bindings
|
||||
|
||||
|
||||
describe('querystring params app', () => {
|
||||
beforeEachProviders(
|
||||
@@ -243,20 +237,21 @@ export function main() {
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'hello-cmp'})
|
||||
@View({template: 'hello'})
|
||||
@Component({selector: 'hello-cmp', template: 'hello'})
|
||||
class HelloCmp {
|
||||
public message: string;
|
||||
}
|
||||
|
||||
@Component({selector: 'hello2-cmp'})
|
||||
@View({template: 'hello2'})
|
||||
@Component({selector: 'hello2-cmp', template: 'hello2'})
|
||||
class Hello2Cmp {
|
||||
public greeting: string;
|
||||
}
|
||||
|
||||
@Component({selector: 'app-cmp'})
|
||||
@View({template: "outer { <router-outlet></router-outlet> }", directives: ROUTER_DIRECTIVES})
|
||||
@Component({
|
||||
selector: 'app-cmp',
|
||||
template: `outer { <router-outlet></router-outlet> }`,
|
||||
directives: ROUTER_DIRECTIVES
|
||||
})
|
||||
@RouteConfig([new Route({path: '/', component: HelloCmp})])
|
||||
class AppCmp {
|
||||
constructor(public router: Router, public location: LocationStrategy) {}
|
||||
@@ -283,20 +278,29 @@ class AppWithViewChildren implements AfterViewInit {
|
||||
afterViewInit() { this.helloCmp.message = 'Ahoy'; }
|
||||
}
|
||||
|
||||
@Component({selector: 'parent-cmp'})
|
||||
@View({template: `parent { <router-outlet></router-outlet> }`, directives: ROUTER_DIRECTIVES})
|
||||
@Component({
|
||||
selector: 'parent-cmp',
|
||||
template: `parent { <router-outlet></router-outlet> }`,
|
||||
directives: ROUTER_DIRECTIVES
|
||||
})
|
||||
@RouteConfig([new Route({path: '/child', component: HelloCmp})])
|
||||
class ParentCmp {
|
||||
}
|
||||
|
||||
@Component({selector: 'super-parent-cmp'})
|
||||
@View({template: `super-parent { <router-outlet></router-outlet> }`, directives: ROUTER_DIRECTIVES})
|
||||
@Component({
|
||||
selector: 'super-parent-cmp',
|
||||
template: `super-parent { <router-outlet></router-outlet> }`,
|
||||
directives: ROUTER_DIRECTIVES
|
||||
})
|
||||
@RouteConfig([new Route({path: '/child', component: Hello2Cmp})])
|
||||
class SuperParentCmp {
|
||||
}
|
||||
|
||||
@Component({selector: 'app-cmp'})
|
||||
@View({template: `root { <router-outlet></router-outlet> }`, directives: ROUTER_DIRECTIVES})
|
||||
@Component({
|
||||
selector: 'app-cmp',
|
||||
template: `root { <router-outlet></router-outlet> }`,
|
||||
directives: ROUTER_DIRECTIVES
|
||||
})
|
||||
@RouteConfig([
|
||||
new Route({path: '/parent/...', component: ParentCmp}),
|
||||
new Route({path: '/super-parent/...', component: SuperParentCmp})
|
||||
@@ -305,28 +309,32 @@ class HierarchyAppCmp {
|
||||
constructor(public router: Router, public location: LocationStrategy) {}
|
||||
}
|
||||
|
||||
@Component({selector: 'qs-cmp'})
|
||||
@View({template: "qParam = {{q}}"})
|
||||
@Component({selector: 'qs-cmp', template: `qParam = {{q}}`})
|
||||
class QSCmp {
|
||||
q: string;
|
||||
constructor(params: RouteParams) { this.q = params.get('q'); }
|
||||
}
|
||||
|
||||
@Component({selector: 'app-cmp'})
|
||||
@View({template: `<router-outlet></router-outlet>`, directives: ROUTER_DIRECTIVES})
|
||||
@Component({
|
||||
selector: 'app-cmp',
|
||||
template: `<router-outlet></router-outlet>`,
|
||||
directives: ROUTER_DIRECTIVES
|
||||
})
|
||||
@RouteConfig([new Route({path: '/qs', component: QSCmp})])
|
||||
class QueryStringAppCmp {
|
||||
constructor(public router: Router, public location: LocationStrategy) {}
|
||||
}
|
||||
|
||||
@Component({selector: 'oops-cmp'})
|
||||
@View({template: "oh no"})
|
||||
@Component({selector: 'oops-cmp', template: "oh no"})
|
||||
class BrokenCmp {
|
||||
constructor() { throw new BaseException('oops!'); }
|
||||
}
|
||||
|
||||
@Component({selector: 'app-cmp'})
|
||||
@View({template: `outer { <router-outlet></router-outlet> }`, directives: ROUTER_DIRECTIVES})
|
||||
@Component({
|
||||
selector: 'app-cmp',
|
||||
template: `outer { <router-outlet></router-outlet> }`,
|
||||
directives: ROUTER_DIRECTIVES
|
||||
})
|
||||
@RouteConfig([new Route({path: '/cause-error', component: BrokenCmp})])
|
||||
class BrokenAppCmp {
|
||||
constructor(public router: Router, public location: LocationStrategy) {}
|
||||
@@ -0,0 +1,655 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
expect,
|
||||
iit,
|
||||
flushMicrotasks,
|
||||
inject,
|
||||
it,
|
||||
TestComponentBuilder,
|
||||
RootTestComponent,
|
||||
xit,
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {specs, compile, TEST_ROUTER_PROVIDERS, clickOnElement, getHref} from '../util';
|
||||
|
||||
import {Router, AsyncRoute, Route, Location} from 'angular2/router';
|
||||
|
||||
import {
|
||||
HelloCmp,
|
||||
helloCmpLoader,
|
||||
UserCmp,
|
||||
userCmpLoader,
|
||||
TeamCmp,
|
||||
asyncTeamLoader,
|
||||
ParentCmp,
|
||||
parentCmpLoader,
|
||||
asyncParentCmpLoader,
|
||||
asyncDefaultParentCmpLoader,
|
||||
ParentWithDefaultCmp,
|
||||
parentWithDefaultCmpLoader,
|
||||
asyncRouteDataCmp
|
||||
} from './fixture_components';
|
||||
|
||||
function getLinkElement(rtc: RootTestComponent) {
|
||||
return rtc.debugElement.componentViewChildren[0].nativeElement;
|
||||
}
|
||||
|
||||
function asyncRoutesWithoutChildrenWithRouteData() {
|
||||
var fixture;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should inject route data into the component', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute(
|
||||
{path: '/route-data', loader: asyncRouteDataCmp, data: {isAdmin: true}})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/route-data'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('true');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should inject empty object if the route has no data property',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/route-data-default', loader: asyncRouteDataCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/route-data-default'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
function asyncRoutesWithoutChildrenWithoutParams() {
|
||||
var fixture;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
|
||||
.then((_) => rtr.navigateByUrl('/test'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
|
||||
.then((_) => rtr.navigate(['/Hello']))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `<a [router-link]="['Hello']">go to hello</a> | <router-outlet></router-outlet>`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(getHref(getLinkElement(fixture))).toEqual('/test');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb, `<a [router-link]="['Hello']">go to hello</a> | <router-outlet></router-outlet>`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('go to hello | ');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('go to hello | hello');
|
||||
expect(location.urlChanges).toEqual(['/test']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(fixture));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function asyncRoutesWithoutChildrenWithParams() {
|
||||
var fixture;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
|
||||
.then((_) => rtr.navigateByUrl('/user/igor'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
||||
.then((_) => rtr.navigate(['/User', {name: 'brian'}]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `<a [router-link]="['User', {name: 'naomi'}]">greet naomi</a> | <router-outlet></router-outlet>`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(getHref(getLinkElement(fixture))).toEqual('/user/naomi');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb, `<a [router-link]="['User', {name: 'naomi'}]">greet naomi</a> | <router-outlet></router-outlet>`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | ');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | hello naomi');
|
||||
expect(location.urlChanges).toEqual(['/user/naomi']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(fixture));
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate between components with different parameters',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
|
||||
.then((_) => rtr.navigateByUrl('/user/brian'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
|
||||
})
|
||||
.then((_) => rtr.navigateByUrl('/user/igor'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function asyncRoutesWithSyncChildrenWithoutDefaultRoutes() {
|
||||
var fixture;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
|
||||
.then((_) => rtr.navigateByUrl('/a/b'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
|
||||
.then((_) => rtr.navigate(['/Parent', 'Child']))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `<a [router-link]="['Parent']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(getHref(getLinkElement(fixture))).toEqual('/a');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb, `<a [router-link]="['Parent', 'Child']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('nav to child | outer { }');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('nav to child | outer { inner { hello } }');
|
||||
expect(location.urlChanges).toEqual(['/a/b']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(fixture));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function asyncRoutesWithSyncChildrenWithDefaultRoutes() {
|
||||
var fixture;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/a'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => rtr.navigate(['/Parent']))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `<a [router-link]="['/Parent']">link to inner</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(getHref(getLinkElement(fixture))).toEqual('/a');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb, `<a [router-link]="['/Parent']">link to inner</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('link to inner | outer { }');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('link to inner | outer { inner { hello } }');
|
||||
expect(location.urlChanges).toEqual(['/a/b']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(fixture));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function asyncRoutesWithAsyncChildrenWithoutParamsWithoutDefaultRoutes() {
|
||||
var rootTC;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/a/b'))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => rtr.navigate(['/Parent', 'Child']))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `<a [router-link]="['Parent', 'Child']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(getHref(getLinkElement(rootTC))).toEqual('/a');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb, `<a [router-link]="['Parent', 'Child']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('nav to child | outer { }');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement)
|
||||
.toHaveText('nav to child | outer { inner { hello } }');
|
||||
expect(location.urlChanges).toEqual(['/a/b']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(rootTC));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function asyncRoutesWithAsyncChildrenWithoutParamsWithDefaultRoutes() {
|
||||
var rootTC;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute(
|
||||
{path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/a'))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute(
|
||||
{path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => rtr.navigate(['/Parent']))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `<a [router-link]="['Parent']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute(
|
||||
{path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(getHref(getLinkElement(rootTC))).toEqual('/a');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb, `<a [router-link]="['Parent']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute(
|
||||
{path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
|
||||
]))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('nav to child | outer { }');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement)
|
||||
.toHaveText('nav to child | outer { inner { hello } }');
|
||||
expect(location.urlChanges).toEqual(['/a/b']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(rootTC));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function asyncRoutesWithAsyncChildrenWithParamsWithoutDefaultRoutes() {
|
||||
var fixture;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `{ <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/team/angular/user/matias'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('{ team angular | user { hello matias } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `{ <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
|
||||
]))
|
||||
.then((_) => rtr.navigate(['/Team', {id: 'angular'}, 'User', {name: 'matias'}]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('{ team angular | user { hello matias } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(
|
||||
tcb,
|
||||
`<a [router-link]="['/Team', {id: 'angular'}, 'User', {name: 'matias'}]">nav to matias</a> { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
|
||||
]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(getHref(getLinkElement(fixture))).toEqual('/team/angular');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(
|
||||
tcb,
|
||||
`<a [router-link]="['/Team', {id: 'angular'}, 'User', {name: 'matias'}]">nav to matias</a> { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
|
||||
]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('nav to matias { }');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('nav to matias { team angular | user { hello matias } }');
|
||||
expect(location.urlChanges).toEqual(['/team/angular/user/matias']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(fixture));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
export function registerSpecs() {
|
||||
specs['asyncRoutesWithoutChildrenWithRouteData'] = asyncRoutesWithoutChildrenWithRouteData;
|
||||
specs['asyncRoutesWithoutChildrenWithoutParams'] = asyncRoutesWithoutChildrenWithoutParams;
|
||||
specs['asyncRoutesWithoutChildrenWithParams'] = asyncRoutesWithoutChildrenWithParams;
|
||||
specs['asyncRoutesWithSyncChildrenWithoutDefaultRoutes'] =
|
||||
asyncRoutesWithSyncChildrenWithoutDefaultRoutes;
|
||||
specs['asyncRoutesWithSyncChildrenWithDefaultRoutes'] =
|
||||
asyncRoutesWithSyncChildrenWithDefaultRoutes;
|
||||
specs['asyncRoutesWithAsyncChildrenWithoutParamsWithoutDefaultRoutes'] =
|
||||
asyncRoutesWithAsyncChildrenWithoutParamsWithoutDefaultRoutes;
|
||||
specs['asyncRoutesWithAsyncChildrenWithoutParamsWithDefaultRoutes'] =
|
||||
asyncRoutesWithAsyncChildrenWithoutParamsWithDefaultRoutes;
|
||||
specs['asyncRoutesWithAsyncChildrenWithParamsWithoutDefaultRoutes'] =
|
||||
asyncRoutesWithAsyncChildrenWithParamsWithoutDefaultRoutes;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import {Component} from 'angular2/angular2';
|
||||
import {
|
||||
AsyncRoute,
|
||||
Route,
|
||||
Redirect,
|
||||
RouteConfig,
|
||||
RouteParams,
|
||||
RouteData,
|
||||
ROUTER_DIRECTIVES
|
||||
} from 'angular2/router';
|
||||
import {PromiseWrapper} from 'angular2/src/facade/async';
|
||||
|
||||
@Component({selector: 'hello-cmp', template: `{{greeting}}`})
|
||||
export class HelloCmp {
|
||||
greeting: string;
|
||||
constructor() { this.greeting = 'hello'; }
|
||||
}
|
||||
|
||||
export function helloCmpLoader() {
|
||||
return PromiseWrapper.resolve(HelloCmp);
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'user-cmp', template: `hello {{user}}`})
|
||||
export class UserCmp {
|
||||
user: string;
|
||||
constructor(params: RouteParams) { this.user = params.get('name'); }
|
||||
}
|
||||
|
||||
export function userCmpLoader() {
|
||||
return PromiseWrapper.resolve(UserCmp);
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'parent-cmp',
|
||||
template: `inner { <router-outlet></router-outlet> }`,
|
||||
directives: [ROUTER_DIRECTIVES],
|
||||
})
|
||||
@RouteConfig([new Route({path: '/b', component: HelloCmp, name: 'Child'})])
|
||||
export class ParentCmp {
|
||||
}
|
||||
|
||||
export function parentCmpLoader() {
|
||||
return PromiseWrapper.resolve(ParentCmp);
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'parent-cmp',
|
||||
template: `inner { <router-outlet></router-outlet> }`,
|
||||
directives: [ROUTER_DIRECTIVES],
|
||||
})
|
||||
@RouteConfig([new AsyncRoute({path: '/b', loader: helloCmpLoader, name: 'Child'})])
|
||||
export class AsyncParentCmp {
|
||||
}
|
||||
|
||||
export function asyncParentCmpLoader() {
|
||||
return PromiseWrapper.resolve(AsyncParentCmp);
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'parent-cmp',
|
||||
template: `inner { <router-outlet></router-outlet> }`,
|
||||
directives: [ROUTER_DIRECTIVES],
|
||||
})
|
||||
@RouteConfig(
|
||||
[new AsyncRoute({path: '/b', loader: helloCmpLoader, name: 'Child', useAsDefault: true})])
|
||||
export class AsyncDefaultParentCmp {
|
||||
}
|
||||
|
||||
export function asyncDefaultParentCmpLoader() {
|
||||
return PromiseWrapper.resolve(AsyncDefaultParentCmp);
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'parent-cmp',
|
||||
template: `inner { <router-outlet></router-outlet> }`,
|
||||
directives: [ROUTER_DIRECTIVES],
|
||||
})
|
||||
@RouteConfig([new Route({path: '/b', component: HelloCmp, name: 'Child', useAsDefault: true})])
|
||||
export class ParentWithDefaultCmp {
|
||||
}
|
||||
|
||||
export function parentWithDefaultCmpLoader() {
|
||||
return PromiseWrapper.resolve(ParentWithDefaultCmp);
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'team-cmp',
|
||||
template: `team {{id}} | user { <router-outlet></router-outlet> }`,
|
||||
directives: [ROUTER_DIRECTIVES],
|
||||
})
|
||||
@RouteConfig([new Route({path: '/user/:name', component: UserCmp, name: 'User'})])
|
||||
export class TeamCmp {
|
||||
id: string;
|
||||
constructor(params: RouteParams) { this.id = params.get('id'); }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'team-cmp',
|
||||
template: `team {{id}} | user { <router-outlet></router-outlet> }`,
|
||||
directives: [ROUTER_DIRECTIVES],
|
||||
})
|
||||
@RouteConfig([new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})])
|
||||
export class AsyncTeamCmp {
|
||||
id: string;
|
||||
constructor(params: RouteParams) { this.id = params.get('id'); }
|
||||
}
|
||||
|
||||
export function asyncTeamLoader() {
|
||||
return PromiseWrapper.resolve(AsyncTeamCmp);
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'data-cmp', template: `{{myData}}`})
|
||||
export class RouteDataCmp {
|
||||
myData: boolean;
|
||||
constructor(data: RouteData) { this.myData = data.get('isAdmin'); }
|
||||
}
|
||||
|
||||
export function asyncRouteDataCmp() {
|
||||
return PromiseWrapper.resolve(RouteDataCmp);
|
||||
}
|
||||
|
||||
@Component({selector: 'redirect-to-parent-cmp', template: 'redirect-to-parent'})
|
||||
@RouteConfig([new Redirect({path: '/child-redirect', redirectTo: ['../HelloSib']})])
|
||||
export class RedirectToParentCmp {
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
expect,
|
||||
iit,
|
||||
flushMicrotasks,
|
||||
inject,
|
||||
it,
|
||||
TestComponentBuilder,
|
||||
RootTestComponent,
|
||||
xit,
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {specs, compile, TEST_ROUTER_PROVIDERS, clickOnElement, getHref} from '../util';
|
||||
|
||||
import {Router, Route, Location} from 'angular2/router';
|
||||
|
||||
import {HelloCmp, UserCmp, TeamCmp, ParentCmp, ParentWithDefaultCmp} from './fixture_components';
|
||||
|
||||
|
||||
function getLinkElement(rtc: RootTestComponent) {
|
||||
return rtc.debugElement.componentViewChildren[0].nativeElement;
|
||||
}
|
||||
|
||||
function syncRoutesWithoutChildrenWithoutParams() {
|
||||
var fixture;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) =>
|
||||
rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
|
||||
.then((_) => rtr.navigateByUrl('/test'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) =>
|
||||
rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
|
||||
.then((_) => rtr.navigate(['/Hello']))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `<a [router-link]="['Hello']">go to hello</a> | <router-outlet></router-outlet>`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) =>
|
||||
rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(getHref(getLinkElement(fixture))).toEqual('/test');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb, `<a [router-link]="['Hello']">go to hello</a> | <router-outlet></router-outlet>`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) =>
|
||||
rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('go to hello | ');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('go to hello | hello');
|
||||
expect(location.urlChanges).toEqual(['/test']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(fixture));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function syncRoutesWithoutChildrenWithParams() {
|
||||
var fixture;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
||||
.then((_) => rtr.navigateByUrl('/user/igor'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
||||
.then((_) => rtr.navigate(['/User', {name: 'brian'}]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `<a [router-link]="['User', {name: 'naomi'}]">greet naomi</a> | <router-outlet></router-outlet>`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(getHref(getLinkElement(fixture))).toEqual('/user/naomi');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb, `<a [router-link]="['User', {name: 'naomi'}]">greet naomi</a> | <router-outlet></router-outlet>`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | ');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | hello naomi');
|
||||
expect(location.urlChanges).toEqual(['/user/naomi']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(fixture));
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate between components with different parameters',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
||||
.then((_) => rtr.navigateByUrl('/user/brian'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
|
||||
})
|
||||
.then((_) => rtr.navigateByUrl('/user/igor'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function syncRoutesWithSyncChildrenWithoutDefaultRoutesWithoutParams() {
|
||||
var fixture;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
|
||||
.then((_) => rtr.navigateByUrl('/a/b'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
|
||||
.then((_) => rtr.navigate(['/Parent', 'Child']))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `<a [router-link]="['Parent', 'Child']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(getHref(getLinkElement(fixture))).toEqual('/a/b');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb, `<a [router-link]="['Parent', 'Child']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('nav to child | outer { }');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('nav to child | outer { inner { hello } }');
|
||||
expect(location.urlChanges).toEqual(['/a/b']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(fixture));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function syncRoutesWithSyncChildrenWithoutDefaultRoutesWithParams() {
|
||||
var fixture;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `{ <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
|
||||
.then((_) => rtr.navigateByUrl('/team/angular/user/matias'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('{ team angular | user { hello matias } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `{ <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
|
||||
.then((_) => rtr.navigate(['/Team', {id: 'angular'}, 'User', {name: 'matias'}]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('{ team angular | user { hello matias } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(
|
||||
tcb,
|
||||
`<a [router-link]="['/Team', {id: 'angular'}, 'User', {name: 'matias'}]">nav to matias</a> { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(getHref(getLinkElement(fixture))).toEqual('/team/angular/user/matias');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(
|
||||
tcb,
|
||||
`<a [router-link]="['/Team', {id: 'angular'}, 'User', {name: 'matias'}]">nav to matias</a> { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('nav to matias { }');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('nav to matias { team angular | user { hello matias } }');
|
||||
expect(location.urlChanges).toEqual(['/team/angular/user/matias']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(fixture));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams() {
|
||||
var fixture;
|
||||
var tcb;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
}));
|
||||
|
||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then(
|
||||
(_) => rtr.config(
|
||||
[new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
|
||||
.then((_) => rtr.navigateByUrl('/a'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then(
|
||||
(_) => rtr.config(
|
||||
[new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
|
||||
.then((_) => rtr.navigate(['/Parent']))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||
compile(tcb, `<a [router-link]="['/Parent']">link to inner</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then(
|
||||
(_) => rtr.config(
|
||||
[new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(getHref(getLinkElement(fixture))).toEqual('/a');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should navigate from a link click',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb, `<a [router-link]="['/Parent']">link to inner</a> | outer { <router-outlet></router-outlet> }`)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then(
|
||||
(_) => rtr.config(
|
||||
[new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('link to inner | outer { }');
|
||||
|
||||
rtr.subscribe((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('link to inner | outer { inner { hello } }');
|
||||
expect(location.urlChanges).toEqual(['/a/b']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
clickOnElement(getLinkElement(fixture));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
export function registerSpecs() {
|
||||
specs['syncRoutesWithoutChildrenWithoutParams'] = syncRoutesWithoutChildrenWithoutParams;
|
||||
specs['syncRoutesWithoutChildrenWithParams'] = syncRoutesWithoutChildrenWithParams;
|
||||
specs['syncRoutesWithSyncChildrenWithoutDefaultRoutesWithoutParams'] =
|
||||
syncRoutesWithSyncChildrenWithoutDefaultRoutesWithoutParams;
|
||||
specs['syncRoutesWithSyncChildrenWithoutDefaultRoutesWithParams'] =
|
||||
syncRoutesWithSyncChildrenWithoutDefaultRoutesWithParams;
|
||||
specs['syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams'] =
|
||||
syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
beforeEachBindings,
|
||||
beforeEachProviders,
|
||||
it,
|
||||
xit
|
||||
} from 'angular2/testing_internal';
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
ObservableWrapper
|
||||
} from 'angular2/src/facade/async';
|
||||
|
||||
import {RootRouter} from 'angular2/src/router/router';
|
||||
import {Router, RouterOutlet, RouterLink, RouteParams} from 'angular2/router';
|
||||
import {
|
||||
RouteConfig,
|
||||
@@ -35,9 +34,6 @@ import {
|
||||
Redirect
|
||||
} from 'angular2/src/router/route_config_decorator';
|
||||
|
||||
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||
import {Location} from 'angular2/src/router/location';
|
||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||
import {
|
||||
OnActivate,
|
||||
OnDeactivate,
|
||||
@@ -47,7 +43,9 @@ import {
|
||||
} from 'angular2/src/router/interfaces';
|
||||
import {CanActivate} from 'angular2/src/router/lifecycle_annotations';
|
||||
import {ComponentInstruction} from 'angular2/src/router/instruction';
|
||||
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
|
||||
|
||||
|
||||
import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
|
||||
|
||||
var cmpInstanceCount;
|
||||
var log: string[];
|
||||
@@ -61,17 +59,7 @@ export function main() {
|
||||
var fixture: ComponentFixture;
|
||||
var rtr;
|
||||
|
||||
beforeEachBindings(() => [
|
||||
RouteRegistry,
|
||||
DirectiveResolver,
|
||||
provide(Location, {useClass: SpyLocation}),
|
||||
provide(Router,
|
||||
{
|
||||
useFactory:
|
||||
(registry, location) => { return new RootRouter(registry, location, MyComp); },
|
||||
deps: [RouteRegistry, Location]
|
||||
})
|
||||
]);
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
@@ -81,17 +69,9 @@ export function main() {
|
||||
eventBus = new EventEmitter();
|
||||
}));
|
||||
|
||||
function compile(template: string = "<router-outlet></router-outlet>") {
|
||||
return tcb.overrideView(MyComp, new View({
|
||||
template: ('<div>' + template + '</div>'),
|
||||
directives: [RouterOutlet, RouterLink]
|
||||
}))
|
||||
.createAsync(MyComp)
|
||||
.then((tc) => { fixture = tc; });
|
||||
}
|
||||
|
||||
it('should call the onActivate hook', inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/on-activate'))
|
||||
.then((_) => {
|
||||
@@ -104,7 +84,8 @@ export function main() {
|
||||
|
||||
it('should wait for a parent component\'s onActivate hook to resolve before calling its child\'s',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => {
|
||||
ObservableWrapper.subscribe<string>(eventBus, (ev) => {
|
||||
@@ -126,7 +107,8 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should call the onDeactivate hook', inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/on-deactivate'))
|
||||
.then((_) => rtr.navigateByUrl('/a'))
|
||||
@@ -140,7 +122,8 @@ export function main() {
|
||||
|
||||
it('should wait for a child component\'s onDeactivate hook to resolve before calling its parent\'s',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/parent-deactivate/child-deactivate'))
|
||||
.then((_) => {
|
||||
@@ -165,7 +148,8 @@ export function main() {
|
||||
|
||||
it('should reuse a component when the canReuse hook returns true',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/on-reuse/1/a'))
|
||||
.then((_) => {
|
||||
@@ -187,7 +171,8 @@ export function main() {
|
||||
|
||||
it('should not reuse a component when the canReuse hook returns false',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/never-reuse/1/a'))
|
||||
.then((_) => {
|
||||
@@ -208,7 +193,8 @@ export function main() {
|
||||
|
||||
|
||||
it('should navigate when canActivate returns true', inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => {
|
||||
ObservableWrapper.subscribe<string>(eventBus, (ev) => {
|
||||
@@ -228,7 +214,8 @@ export function main() {
|
||||
|
||||
it('should not navigate when canActivate returns false',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => {
|
||||
ObservableWrapper.subscribe<string>(eventBus, (ev) => {
|
||||
@@ -248,7 +235,8 @@ export function main() {
|
||||
|
||||
it('should navigate away when canDeactivate returns true',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/can-deactivate/a'))
|
||||
.then((_) => {
|
||||
@@ -273,7 +261,8 @@ export function main() {
|
||||
|
||||
it('should not navigate away when canDeactivate returns false',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/can-deactivate/a'))
|
||||
.then((_) => {
|
||||
@@ -299,7 +288,8 @@ export function main() {
|
||||
|
||||
it('should run activation and deactivation hooks in the correct order',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/activation-hooks/child'))
|
||||
.then((_) => {
|
||||
@@ -325,7 +315,8 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should only run reuse hooks when reusing', inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/reuse-hooks/1'))
|
||||
.then((_) => {
|
||||
@@ -352,7 +343,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should not run reuse hooks when not reusing', inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/reuse-hooks/1'))
|
||||
.then((_) => {
|
||||
@@ -383,23 +374,16 @@ export function main() {
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'a-cmp'})
|
||||
@View({template: "A"})
|
||||
@Component({selector: 'a-cmp', template: "A"})
|
||||
class A {
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'b-cmp'})
|
||||
@View({template: "B"})
|
||||
@Component({selector: 'b-cmp', template: "B"})
|
||||
class B {
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'my-comp'})
|
||||
class MyComp {
|
||||
name;
|
||||
}
|
||||
|
||||
function logHook(name: string, next: ComponentInstruction, prev: ComponentInstruction) {
|
||||
var message = name + ': ' + (isPresent(prev) ? ('/' + prev.urlPath) : 'null') + ' -> ' +
|
||||
(isPresent(next) ? ('/' + next.urlPath) : 'null');
|
||||
@@ -407,16 +391,18 @@ function logHook(name: string, next: ComponentInstruction, prev: ComponentInstru
|
||||
ObservableWrapper.callEmit(eventBus, message);
|
||||
}
|
||||
|
||||
@Component({selector: 'activate-cmp'})
|
||||
@View({template: 'activate cmp'})
|
||||
@Component({selector: 'activate-cmp', template: 'activate cmp'})
|
||||
class ActivateCmp implements OnActivate {
|
||||
onActivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
||||
logHook('activate', next, prev);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'parent-activate-cmp'})
|
||||
@View({template: `parent {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||
@Component({
|
||||
selector: 'parent-activate-cmp',
|
||||
template: `parent {<router-outlet></router-outlet>}`,
|
||||
directives: [RouterOutlet]
|
||||
})
|
||||
@RouteConfig([new Route({path: '/child-activate', component: ActivateCmp})])
|
||||
class ParentActivateCmp implements OnActivate {
|
||||
onActivate(next: ComponentInstruction, prev: ComponentInstruction): Promise<any> {
|
||||
@@ -426,16 +412,14 @@ class ParentActivateCmp implements OnActivate {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'deactivate-cmp'})
|
||||
@View({template: 'deactivate cmp'})
|
||||
@Component({selector: 'deactivate-cmp', template: 'deactivate cmp'})
|
||||
class DeactivateCmp implements OnDeactivate {
|
||||
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
||||
logHook('deactivate', next, prev);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'deactivate-cmp'})
|
||||
@View({template: 'deactivate cmp'})
|
||||
@Component({selector: 'deactivate-cmp', template: 'deactivate cmp'})
|
||||
class WaitDeactivateCmp implements OnDeactivate {
|
||||
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction): Promise<any> {
|
||||
completer = PromiseWrapper.completer();
|
||||
@@ -444,8 +428,11 @@ class WaitDeactivateCmp implements OnDeactivate {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'parent-deactivate-cmp'})
|
||||
@View({template: `parent {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||
@Component({
|
||||
selector: 'parent-deactivate-cmp',
|
||||
template: `parent {<router-outlet></router-outlet>}`,
|
||||
directives: [RouterOutlet]
|
||||
})
|
||||
@RouteConfig([new Route({path: '/child-deactivate', component: WaitDeactivateCmp})])
|
||||
class ParentDeactivateCmp implements OnDeactivate {
|
||||
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
||||
@@ -453,26 +440,37 @@ class ParentDeactivateCmp implements OnDeactivate {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'reuse-cmp'})
|
||||
@View({template: `reuse {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||
@Component({
|
||||
selector: 'reuse-cmp',
|
||||
template: `reuse {<router-outlet></router-outlet>}`,
|
||||
directives: [RouterOutlet]
|
||||
})
|
||||
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
||||
class ReuseCmp implements OnReuse, CanReuse {
|
||||
class ReuseCmp implements OnReuse,
|
||||
CanReuse {
|
||||
constructor() { cmpInstanceCount += 1; }
|
||||
canReuse(next: ComponentInstruction, prev: ComponentInstruction) { return true; }
|
||||
onReuse(next: ComponentInstruction, prev: ComponentInstruction) { logHook('reuse', next, prev); }
|
||||
}
|
||||
|
||||
@Component({selector: 'never-reuse-cmp'})
|
||||
@View({template: `reuse {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||
@Component({
|
||||
selector: 'never-reuse-cmp',
|
||||
template: `reuse {<router-outlet></router-outlet>}`,
|
||||
directives: [RouterOutlet]
|
||||
})
|
||||
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
||||
class NeverReuseCmp implements OnReuse, CanReuse {
|
||||
class NeverReuseCmp implements OnReuse,
|
||||
CanReuse {
|
||||
constructor() { cmpInstanceCount += 1; }
|
||||
canReuse(next: ComponentInstruction, prev: ComponentInstruction) { return false; }
|
||||
onReuse(next: ComponentInstruction, prev: ComponentInstruction) { logHook('reuse', next, prev); }
|
||||
}
|
||||
|
||||
@Component({selector: 'can-activate-cmp'})
|
||||
@View({template: `canActivate {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||
@Component({
|
||||
selector: 'can-activate-cmp',
|
||||
template: `canActivate {<router-outlet></router-outlet>}`,
|
||||
directives: [RouterOutlet]
|
||||
})
|
||||
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
||||
@CanActivate(CanActivateCmp.canActivate)
|
||||
class CanActivateCmp {
|
||||
@@ -483,8 +481,11 @@ class CanActivateCmp {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'can-deactivate-cmp'})
|
||||
@View({template: `canDeactivate {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||
@Component({
|
||||
selector: 'can-deactivate-cmp',
|
||||
template: `canDeactivate {<router-outlet></router-outlet>}`,
|
||||
directives: [RouterOutlet]
|
||||
})
|
||||
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
||||
class CanDeactivateCmp implements CanDeactivate {
|
||||
canDeactivate(next: ComponentInstruction, prev: ComponentInstruction): Promise<boolean> {
|
||||
@@ -494,8 +495,7 @@ class CanDeactivateCmp implements CanDeactivate {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'all-hooks-child-cmp'})
|
||||
@View({template: `child`})
|
||||
@Component({selector: 'all-hooks-child-cmp', template: `child`})
|
||||
@CanActivate(AllHooksChildCmp.canActivate)
|
||||
class AllHooksChildCmp implements CanDeactivate, OnDeactivate, OnActivate {
|
||||
canDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
||||
@@ -517,11 +517,15 @@ class AllHooksChildCmp implements CanDeactivate, OnDeactivate, OnActivate {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'all-hooks-parent-cmp'})
|
||||
@View({template: `<router-outlet></router-outlet>`, directives: [RouterOutlet]})
|
||||
@Component({
|
||||
selector: 'all-hooks-parent-cmp',
|
||||
template: `<router-outlet></router-outlet>`,
|
||||
directives: [RouterOutlet]
|
||||
})
|
||||
@RouteConfig([new Route({path: '/child', component: AllHooksChildCmp})])
|
||||
@CanActivate(AllHooksParentCmp.canActivate)
|
||||
class AllHooksParentCmp implements CanDeactivate, OnDeactivate, OnActivate {
|
||||
class AllHooksParentCmp implements CanDeactivate,
|
||||
OnDeactivate, OnActivate {
|
||||
canDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
||||
logHook('canDeactivate parent', next, prev);
|
||||
return true;
|
||||
@@ -541,8 +545,7 @@ class AllHooksParentCmp implements CanDeactivate, OnDeactivate, OnActivate {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'reuse-hooks-cmp'})
|
||||
@View({template: 'reuse hooks cmp'})
|
||||
@Component({selector: 'reuse-hooks-cmp', template: 'reuse hooks cmp'})
|
||||
@CanActivate(ReuseHooksCmp.canActivate)
|
||||
class ReuseHooksCmp implements OnActivate, OnReuse, OnDeactivate, CanReuse, CanDeactivate {
|
||||
canReuse(next: ComponentInstruction, prev: ComponentInstruction): Promise<any> {
|
||||
@@ -574,8 +577,11 @@ class ReuseHooksCmp implements OnActivate, OnReuse, OnDeactivate, CanReuse, CanD
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'lifecycle-cmp'})
|
||||
@View({template: `<router-outlet></router-outlet>`, directives: [RouterOutlet]})
|
||||
@Component({
|
||||
selector: 'lifecycle-cmp',
|
||||
template: `<router-outlet></router-outlet>`,
|
||||
directives: [RouterOutlet]
|
||||
})
|
||||
@RouteConfig([
|
||||
new Route({path: '/a', component: A}),
|
||||
new Route({path: '/on-activate', component: ActivateCmp}),
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
beforeEachBindings,
|
||||
beforeEachProviders,
|
||||
it,
|
||||
xit
|
||||
} from 'angular2/testing_internal';
|
||||
@@ -18,8 +18,7 @@ import {
|
||||
import {provide, Component, View, Injector, Inject} from 'angular2/core';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
|
||||
import {RootRouter} from 'angular2/src/router/router';
|
||||
import {Router, RouterOutlet, RouterLink, RouteParams, RouteData} from 'angular2/router';
|
||||
import {Router, RouterOutlet, RouterLink, RouteParams, RouteData, Location} from 'angular2/router';
|
||||
import {
|
||||
RouteConfig,
|
||||
Route,
|
||||
@@ -28,14 +27,10 @@ import {
|
||||
Redirect
|
||||
} from 'angular2/src/router/route_config_decorator';
|
||||
|
||||
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||
import {Location} from 'angular2/src/router/location';
|
||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
|
||||
import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
|
||||
|
||||
var cmpInstanceCount;
|
||||
var childCmpInstanceCount;
|
||||
var log: string[];
|
||||
|
||||
export function main() {
|
||||
describe('navigation', () => {
|
||||
@@ -44,37 +39,18 @@ export function main() {
|
||||
var fixture: ComponentFixture;
|
||||
var rtr;
|
||||
|
||||
beforeEachBindings(() => [
|
||||
RouteRegistry,
|
||||
DirectiveResolver,
|
||||
provide(Location, {useClass: SpyLocation}),
|
||||
provide(Router,
|
||||
{
|
||||
useFactory:
|
||||
(registry, location) => { return new RootRouter(registry, location, MyComp); },
|
||||
deps: [RouteRegistry, Location]
|
||||
})
|
||||
]);
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
childCmpInstanceCount = 0;
|
||||
cmpInstanceCount = 0;
|
||||
log = [];
|
||||
}));
|
||||
|
||||
function compile(template: string = "<router-outlet></router-outlet>") {
|
||||
return tcb.overrideView(MyComp, new View({
|
||||
template: ('<div>' + template + '</div>'),
|
||||
directives: [RouterOutlet, RouterLink]
|
||||
}))
|
||||
.createAsync(MyComp)
|
||||
.then((tc) => { fixture = tc; });
|
||||
}
|
||||
|
||||
it('should work in a simple case', inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/test', component: HelloCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/test'))
|
||||
.then((_) => {
|
||||
@@ -87,7 +63,8 @@ export function main() {
|
||||
|
||||
it('should navigate between components with different parameters',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/user/:name', component: UserCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/user/brian'))
|
||||
.then((_) => {
|
||||
@@ -102,9 +79,9 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should navigate to child routes', inject([AsyncTestCompleter], (async) => {
|
||||
compile('outer { <router-outlet></router-outlet> }')
|
||||
compile(tcb, 'outer { <router-outlet></router-outlet> }')
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/a/b'))
|
||||
.then((_) => {
|
||||
@@ -116,7 +93,9 @@ export function main() {
|
||||
|
||||
it('should navigate to child routes that capture an empty path',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile('outer { <router-outlet></router-outlet> }')
|
||||
|
||||
compile(tcb, 'outer { <router-outlet></router-outlet> }')
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/a'))
|
||||
.then((_) => {
|
||||
@@ -126,9 +105,9 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should navigate to child routes of async routes', inject([AsyncTestCompleter], (async) => {
|
||||
compile('outer { <router-outlet></router-outlet> }')
|
||||
compile(tcb, 'outer { <router-outlet></router-outlet> }')
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new AsyncRoute({path: '/a/...', loader: parentLoader})]))
|
||||
.then((_) => rtr.navigateByUrl('/a/b'))
|
||||
.then((_) => {
|
||||
@@ -138,26 +117,9 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should recognize and apply redirects',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile()
|
||||
.then((_) => rtr.config([
|
||||
new Redirect({path: '/original', redirectTo: '/redirected'}),
|
||||
new Route({path: '/redirected', component: HelloCmp})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/original'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
expect(location.urlChanges).toEqual(['/redirected']);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should reuse common parent components', inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/team/:id/...', component: TeamCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/team/angular/user/rado'))
|
||||
.then((_) => {
|
||||
@@ -177,7 +139,8 @@ export function main() {
|
||||
|
||||
it('should not reuse children when parent components change',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([new Route({path: '/team/:id/...', component: TeamCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/team/angular/user/rado'))
|
||||
.then((_) => {
|
||||
@@ -197,7 +160,8 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should inject route data into component', inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new Route({path: '/route-data', component: RouteDataCmp, data: {isAdmin: true}})
|
||||
]))
|
||||
@@ -211,10 +175,11 @@ export function main() {
|
||||
|
||||
it('should inject route data into component with AsyncRoute',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new AsyncRoute(
|
||||
{path: '/route-data', loader: AsyncRouteDataCmp, data: {isAdmin: true}})
|
||||
{path: '/route-data', loader: asyncRouteDataCmp, data: {isAdmin: true}})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/route-data'))
|
||||
.then((_) => {
|
||||
@@ -226,7 +191,8 @@ export function main() {
|
||||
|
||||
it('should inject empty object if the route has no data property',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
compile(tcb)
|
||||
.then((rtc) => {fixture = rtc})
|
||||
.then((_) => rtr.config(
|
||||
[new Route({path: '/route-data-default', component: RouteDataCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/route-data-default'))
|
||||
@@ -236,45 +202,28 @@ export function main() {
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('auxiliary routes', () => {
|
||||
it('should recognize a simple case', inject([AsyncTestCompleter], (async) => {
|
||||
compile()
|
||||
.then((_) => rtr.config([new Route({path: '/...', component: AuxCmp})]))
|
||||
.then((_) => rtr.navigateByUrl('/hello(modal)'))
|
||||
.then((_) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('main {hello} | aux {modal}');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'hello-cmp'})
|
||||
@View({template: "{{greeting}}"})
|
||||
@Component({selector: 'hello-cmp', template: `{{greeting}}`})
|
||||
class HelloCmp {
|
||||
greeting: string;
|
||||
constructor() { this.greeting = "hello"; }
|
||||
constructor() { this.greeting = 'hello'; }
|
||||
}
|
||||
|
||||
|
||||
function AsyncRouteDataCmp() {
|
||||
function asyncRouteDataCmp() {
|
||||
return PromiseWrapper.resolve(RouteDataCmp);
|
||||
}
|
||||
|
||||
@Component({selector: 'data-cmp'})
|
||||
@View({template: "{{myData}}"})
|
||||
@Component({selector: 'data-cmp', template: `{{myData}}`})
|
||||
class RouteDataCmp {
|
||||
myData: boolean;
|
||||
constructor(data: RouteData) { this.myData = data.get('isAdmin'); }
|
||||
}
|
||||
|
||||
@Component({selector: 'user-cmp'})
|
||||
@View({template: "hello {{user}}"})
|
||||
@Component({selector: 'user-cmp', template: `hello {{user}}`})
|
||||
class UserCmp {
|
||||
user: string;
|
||||
constructor(params: RouteParams) {
|
||||
@@ -288,9 +237,9 @@ function parentLoader() {
|
||||
return PromiseWrapper.resolve(ParentCmp);
|
||||
}
|
||||
|
||||
@Component({selector: 'parent-cmp'})
|
||||
@View({
|
||||
template: "inner { <router-outlet></router-outlet> }",
|
||||
@Component({
|
||||
selector: 'parent-cmp',
|
||||
template: `inner { <router-outlet></router-outlet> }`,
|
||||
directives: [RouterOutlet],
|
||||
})
|
||||
@RouteConfig([
|
||||
@@ -298,13 +247,12 @@ function parentLoader() {
|
||||
new Route({path: '/', component: HelloCmp}),
|
||||
])
|
||||
class ParentCmp {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'team-cmp'})
|
||||
@View({
|
||||
template: "team {{id}} { <router-outlet></router-outlet> }",
|
||||
@Component({
|
||||
selector: 'team-cmp',
|
||||
template: `team {{id}} { <router-outlet></router-outlet> }`,
|
||||
directives: [RouterOutlet],
|
||||
})
|
||||
@RouteConfig([new Route({path: '/user/:name', component: UserCmp})])
|
||||
@@ -315,27 +263,3 @@ class TeamCmp {
|
||||
cmpInstanceCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'my-comp'})
|
||||
class MyComp {
|
||||
name;
|
||||
}
|
||||
|
||||
@Component({selector: 'modal-cmp'})
|
||||
@View({template: "modal"})
|
||||
class ModalCmp {
|
||||
}
|
||||
|
||||
@Component({selector: 'aux-cmp'})
|
||||
@View({
|
||||
template: 'main {<router-outlet></router-outlet>} | ' +
|
||||
'aux {<router-outlet name="modal"></router-outlet>}',
|
||||
directives: [RouterOutlet],
|
||||
})
|
||||
@RouteConfig([
|
||||
new Route({path: '/hello', component: HelloCmp}),
|
||||
new AuxRoute({path: '/modal', component: ModalCmp}),
|
||||
])
|
||||
class AuxCmp {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import {
|
||||
RootTestComponent,
|
||||
AsyncTestCompleter,
|
||||
TestComponentBuilder,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
el,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
beforeEachProviders,
|
||||
it,
|
||||
xit
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {Router, RouterOutlet, RouterLink, RouteParams, RouteData, Location} from 'angular2/router';
|
||||
import {
|
||||
RouteConfig,
|
||||
Route,
|
||||
AuxRoute,
|
||||
AsyncRoute,
|
||||
Redirect
|
||||
} from 'angular2/src/router/route_config_decorator';
|
||||
|
||||
import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
|
||||
import {HelloCmp, RedirectToParentCmp} from './impl/fixture_components';
|
||||
|
||||
var cmpInstanceCount;
|
||||
var childCmpInstanceCount;
|
||||
|
||||
export function main() {
|
||||
describe('redirects', () => {
|
||||
|
||||
var tcb: TestComponentBuilder;
|
||||
var rootTC: RootTestComponent;
|
||||
var rtr;
|
||||
|
||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||
|
||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||
tcb = tcBuilder;
|
||||
rtr = router;
|
||||
childCmpInstanceCount = 0;
|
||||
cmpInstanceCount = 0;
|
||||
}));
|
||||
|
||||
|
||||
it('should apply when navigating by URL',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new Redirect({path: '/original', redirectTo: ['Hello']}),
|
||||
new Route({path: '/redirected', component: HelloCmp, name: 'Hello'})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/original'))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('hello');
|
||||
expect(location.urlChanges).toEqual(['/redirected']);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should recognize and apply absolute redirects',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new Redirect({path: '/original', redirectTo: ['/Hello']}),
|
||||
new Route({path: '/redirected', component: HelloCmp, name: 'Hello'})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/original'))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('hello');
|
||||
expect(location.urlChanges).toEqual(['/redirected']);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should recognize and apply relative child redirects',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new Redirect({path: '/original', redirectTo: ['./Hello']}),
|
||||
new Route({path: '/redirected', component: HelloCmp, name: 'Hello'})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/original'))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('hello');
|
||||
expect(location.urlChanges).toEqual(['/redirected']);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should recognize and apply relative parent redirects',
|
||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||
compile(tcb)
|
||||
.then((rtc) => {rootTC = rtc})
|
||||
.then((_) => rtr.config([
|
||||
new Route({path: '/original/...', component: RedirectToParentCmp}),
|
||||
new Route({path: '/redirected', component: HelloCmp, name: 'HelloSib'})
|
||||
]))
|
||||
.then((_) => rtr.navigateByUrl('/original/child-redirect'))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(rootTC.debugElement.nativeElement).toHaveText('hello');
|
||||
expect(location.urlChanges).toEqual(['/redirected']);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
beforeEachBindings,
|
||||
beforeEachProviders,
|
||||
it,
|
||||
xit,
|
||||
TestComponentBuilder,
|
||||
@@ -21,7 +21,7 @@ import {NumberWrapper} from 'angular2/src/facade/lang';
|
||||
import {PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {provide, Component, DirectiveResolver} from 'angular2/core';
|
||||
import {provide, Component, View, DirectiveResolver} from 'angular2/core';
|
||||
|
||||
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||
import {
|
||||
@@ -47,7 +47,7 @@ export function main() {
|
||||
var fixture: ComponentFixture;
|
||||
var router, location;
|
||||
|
||||
beforeEachBindings(() => [
|
||||
beforeEachProviders(() => [
|
||||
RouteRegistry,
|
||||
DirectiveResolver,
|
||||
provide(Location, {useClass: SpyLocation}),
|
||||
@@ -240,8 +240,8 @@ export function main() {
|
||||
.then((_) => router.config([new Route({path: '/...', component: AuxLinkCmp})]))
|
||||
.then((_) => router.navigateByUrl('/'))
|
||||
.then((_) => {
|
||||
rootTC.detectChanges();
|
||||
expect(DOM.getAttribute(rootTC.debugElement.componentViewChildren[1]
|
||||
fixture.detectChanges();
|
||||
expect(DOM.getAttribute(fixture.debugElement.componentViewChildren[1]
|
||||
.componentViewChildren[0]
|
||||
.nativeElement,
|
||||
'href'))
|
||||
@@ -386,10 +386,7 @@ class MyComp {
|
||||
name;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'user-cmp',
|
||||
template: "hello {{user}}"
|
||||
})
|
||||
@Component({selector: 'user-cmp', template: "hello {{user}}"})
|
||||
class UserCmp {
|
||||
user: string;
|
||||
constructor(params: RouteParams) { this.user = params.get('name'); }
|
||||
@@ -425,17 +422,11 @@ class NoPrefixSiblingPageCmp {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'hello-cmp',
|
||||
template: 'hello'
|
||||
})
|
||||
@Component({selector: 'hello-cmp', template: 'hello'})
|
||||
class HelloCmp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'hello2-cmp',
|
||||
template: 'hello2'
|
||||
})
|
||||
@Component({selector: 'hello2-cmp', template: 'hello2'})
|
||||
class Hello2Cmp {
|
||||
}
|
||||
|
||||
@@ -455,7 +446,6 @@ function parentCmpLoader() {
|
||||
new Route({path: '/better-grandchild', component: Hello2Cmp, name: 'BetterGrandchild'})
|
||||
])
|
||||
class ParentCmp {
|
||||
constructor(public router: Router) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
describeRouter,
|
||||
ddescribeRouter,
|
||||
describeWith,
|
||||
describeWithout,
|
||||
describeWithAndWithout,
|
||||
itShouldRoute
|
||||
} from './util';
|
||||
|
||||
import {registerSpecs} from './impl/sync_route_spec_impl';
|
||||
|
||||
export function main() {
|
||||
registerSpecs();
|
||||
|
||||
describeRouter('sync routes', () => {
|
||||
describeWithout('children', () => { describeWithAndWithout('params', itShouldRoute); });
|
||||
|
||||
describeWith('sync children', () => {
|
||||
describeWithout('default routes', () => { describeWithAndWithout('params', itShouldRoute); });
|
||||
describeWith('default routes', () => { describeWithout('params', itShouldRoute); });
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import {provide, Provider, Component, View} from 'angular2/core';
|
||||
export {Provider} from 'angular2/core';
|
||||
import {Type, isBlank} from 'angular2/src/facade/lang';
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
|
||||
import {
|
||||
RootTestComponent,
|
||||
AsyncTestCompleter,
|
||||
TestComponentBuilder,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
el,
|
||||
inject,
|
||||
beforeEachProviders,
|
||||
it,
|
||||
xit
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {RootRouter} from 'angular2/src/router/router';
|
||||
import {Router, ROUTER_DIRECTIVES} from 'angular2/router';
|
||||
|
||||
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||
import {Location} from 'angular2/src/router/location';
|
||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
|
||||
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
||||
export {ComponentFixture} from 'angular2/testing_internal';
|
||||
|
||||
|
||||
/**
|
||||
* Router test helpers and fixtures
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'root-comp',
|
||||
template: `<router-outlet></router-outlet>`,
|
||||
directives: [ROUTER_DIRECTIVES]
|
||||
})
|
||||
export class RootCmp {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function compile(tcb: TestComponentBuilder,
|
||||
template: string = "<router-outlet></router-outlet>") {
|
||||
return tcb.overrideTemplate(RootCmp, ('<div>' + template + '</div>')).createAsync(RootCmp);
|
||||
}
|
||||
|
||||
export var TEST_ROUTER_PROVIDERS = [
|
||||
RouteRegistry,
|
||||
DirectiveResolver,
|
||||
provide(Location, {useClass: SpyLocation}),
|
||||
provide(
|
||||
Router,
|
||||
{
|
||||
useFactory: (registry, location) => { return new RootRouter(registry, location, RootCmp);},
|
||||
deps: [RouteRegistry, Location]
|
||||
})
|
||||
];
|
||||
|
||||
export function clickOnElement(anchorEl) {
|
||||
var dispatchedEvent = DOM.createMouseEvent('click');
|
||||
DOM.dispatchEvent(anchorEl, dispatchedEvent);
|
||||
return dispatchedEvent;
|
||||
}
|
||||
|
||||
export function getHref(elt) {
|
||||
return DOM.getAttribute(elt, 'href');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Router integration suite DSL
|
||||
*/
|
||||
|
||||
var specNameBuilder = [];
|
||||
|
||||
// we add the specs themselves onto this map
|
||||
export var specs = {};
|
||||
|
||||
export function describeRouter(description: string, fn: Function, exclusive = false): void {
|
||||
var specName = descriptionToSpecName(description);
|
||||
specNameBuilder.push(specName);
|
||||
describe(description, fn);
|
||||
specNameBuilder.pop();
|
||||
}
|
||||
|
||||
export function ddescribeRouter(description: string, fn: Function, exclusive = false): void {
|
||||
describeRouter(description, fn, true);
|
||||
}
|
||||
|
||||
export function describeWithAndWithout(description: string, fn: Function): void {
|
||||
// the "without" case is usually simpler, so we opt to run this spec first
|
||||
describeWithout(description, fn);
|
||||
describeWith(description, fn);
|
||||
}
|
||||
|
||||
export function describeWith(description: string, fn: Function): void {
|
||||
var specName = 'with ' + description;
|
||||
specNameBuilder.push(specName);
|
||||
describe(specName, fn);
|
||||
specNameBuilder.pop();
|
||||
}
|
||||
|
||||
export function describeWithout(description: string, fn: Function): void {
|
||||
var specName = 'without ' + description;
|
||||
specNameBuilder.push(specName);
|
||||
describe(specName, fn);
|
||||
specNameBuilder.pop();
|
||||
}
|
||||
|
||||
function descriptionToSpecName(description: string): string {
|
||||
return spaceCaseToCamelCase(description);
|
||||
}
|
||||
|
||||
// this helper looks up the suite registered from the "impl" folder in this directory
|
||||
export function itShouldRoute() {
|
||||
var specSuiteName = spaceCaseToCamelCase(specNameBuilder.join(' '));
|
||||
|
||||
var spec = specs[specSuiteName];
|
||||
if (isBlank(spec)) {
|
||||
throw new BaseException(`Router integration spec suite "${specSuiteName}" was not found.`);
|
||||
} else {
|
||||
// todo: remove spec from map, throw if there are extra left over??
|
||||
spec();
|
||||
}
|
||||
}
|
||||
|
||||
function spaceCaseToCamelCase(str: string): string {
|
||||
var words = str.split(' ');
|
||||
var first = words.shift();
|
||||
return first + words.map(title).join('');
|
||||
}
|
||||
|
||||
function title(str: string): string {
|
||||
return str[0].toUpperCase() + str.substring(1);
|
||||
}
|
||||
@@ -12,100 +12,82 @@ import {
|
||||
|
||||
import {PathRecognizer} from 'angular2/src/router/path_recognizer';
|
||||
import {parser, Url, RootUrl} from 'angular2/src/router/url_parser';
|
||||
import {SyncRouteHandler} from 'angular2/src/router/sync_route_handler';
|
||||
|
||||
class DummyClass {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
var mockRouteHandler = new SyncRouteHandler(DummyClass);
|
||||
|
||||
export function main() {
|
||||
describe('PathRecognizer', () => {
|
||||
|
||||
it('should throw when given an invalid path', () => {
|
||||
expect(() => new PathRecognizer('/hi#', mockRouteHandler))
|
||||
expect(() => new PathRecognizer('/hi#'))
|
||||
.toThrowError(`Path "/hi#" should not include "#". Use "HashLocationStrategy" instead.`);
|
||||
expect(() => new PathRecognizer('hi?', mockRouteHandler))
|
||||
expect(() => new PathRecognizer('hi?'))
|
||||
.toThrowError(`Path "hi?" contains "?" which is not allowed in a route config.`);
|
||||
expect(() => new PathRecognizer('hi;', mockRouteHandler))
|
||||
expect(() => new PathRecognizer('hi;'))
|
||||
.toThrowError(`Path "hi;" contains ";" which is not allowed in a route config.`);
|
||||
expect(() => new PathRecognizer('hi=', mockRouteHandler))
|
||||
expect(() => new PathRecognizer('hi='))
|
||||
.toThrowError(`Path "hi=" contains "=" which is not allowed in a route config.`);
|
||||
expect(() => new PathRecognizer('hi(', mockRouteHandler))
|
||||
expect(() => new PathRecognizer('hi('))
|
||||
.toThrowError(`Path "hi(" contains "(" which is not allowed in a route config.`);
|
||||
expect(() => new PathRecognizer('hi)', mockRouteHandler))
|
||||
expect(() => new PathRecognizer('hi)'))
|
||||
.toThrowError(`Path "hi)" contains ")" which is not allowed in a route config.`);
|
||||
expect(() => new PathRecognizer('hi//there', mockRouteHandler))
|
||||
expect(() => new PathRecognizer('hi//there'))
|
||||
.toThrowError(`Path "hi//there" contains "//" which is not allowed in a route config.`);
|
||||
});
|
||||
|
||||
it('should return the same instruction instance when recognizing the same path', () => {
|
||||
var rec = new PathRecognizer('/one', mockRouteHandler);
|
||||
|
||||
var one = new Url('one', null, null, {});
|
||||
|
||||
var firstMatch = rec.recognize(one);
|
||||
var secondMatch = rec.recognize(one);
|
||||
|
||||
expect(firstMatch.instruction).toBe(secondMatch.instruction);
|
||||
});
|
||||
|
||||
describe('querystring params', () => {
|
||||
it('should parse querystring params so long as the recognizer is a root', () => {
|
||||
var rec = new PathRecognizer('/hello/there', mockRouteHandler);
|
||||
var rec = new PathRecognizer('/hello/there');
|
||||
var url = parser.parse('/hello/there?name=igor');
|
||||
var match = rec.recognize(url);
|
||||
expect(match.instruction.params).toEqual({'name': 'igor'});
|
||||
expect(match['allParams']).toEqual({'name': 'igor'});
|
||||
});
|
||||
|
||||
it('should return a combined map of parameters with the param expected in the URL path',
|
||||
() => {
|
||||
var rec = new PathRecognizer('/hello/:name', mockRouteHandler);
|
||||
var rec = new PathRecognizer('/hello/:name');
|
||||
var url = parser.parse('/hello/paul?topic=success');
|
||||
var match = rec.recognize(url);
|
||||
expect(match.instruction.params).toEqual({'name': 'paul', 'topic': 'success'});
|
||||
expect(match['allParams']).toEqual({'name': 'paul', 'topic': 'success'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('matrix params', () => {
|
||||
it('should be parsed along with dynamic paths', () => {
|
||||
var rec = new PathRecognizer('/hello/:id', mockRouteHandler);
|
||||
var rec = new PathRecognizer('/hello/:id');
|
||||
var url = new Url('hello', new Url('matias', null, null, {'key': 'value'}));
|
||||
var match = rec.recognize(url);
|
||||
expect(match.instruction.params).toEqual({'id': 'matias', 'key': 'value'});
|
||||
expect(match['allParams']).toEqual({'id': 'matias', 'key': 'value'});
|
||||
});
|
||||
|
||||
it('should be parsed on a static path', () => {
|
||||
var rec = new PathRecognizer('/person', mockRouteHandler);
|
||||
var rec = new PathRecognizer('/person');
|
||||
var url = new Url('person', null, null, {'name': 'dave'});
|
||||
var match = rec.recognize(url);
|
||||
expect(match.instruction.params).toEqual({'name': 'dave'});
|
||||
expect(match['allParams']).toEqual({'name': 'dave'});
|
||||
});
|
||||
|
||||
it('should be ignored on a wildcard segment', () => {
|
||||
var rec = new PathRecognizer('/wild/*everything', mockRouteHandler);
|
||||
var rec = new PathRecognizer('/wild/*everything');
|
||||
var url = parser.parse('/wild/super;variable=value');
|
||||
var match = rec.recognize(url);
|
||||
expect(match.instruction.params).toEqual({'everything': 'super;variable=value'});
|
||||
expect(match['allParams']).toEqual({'everything': 'super;variable=value'});
|
||||
});
|
||||
|
||||
it('should set matrix param values to true when no value is present', () => {
|
||||
var rec = new PathRecognizer('/path', mockRouteHandler);
|
||||
var rec = new PathRecognizer('/path');
|
||||
var url = new Url('path', null, null, {'one': true, 'two': true, 'three': '3'});
|
||||
var match = rec.recognize(url);
|
||||
expect(match.instruction.params).toEqual({'one': true, 'two': true, 'three': '3'});
|
||||
expect(match['allParams']).toEqual({'one': true, 'two': true, 'three': '3'});
|
||||
});
|
||||
|
||||
it('should be parsed on the final segment of the path', () => {
|
||||
var rec = new PathRecognizer('/one/two/three', mockRouteHandler);
|
||||
var rec = new PathRecognizer('/one/two/three');
|
||||
|
||||
var three = new Url('three', null, null, {'c': '3'});
|
||||
var two = new Url('two', three, null, {'b': '2'});
|
||||
var one = new Url('one', two, null, {'a': '1'});
|
||||
|
||||
var match = rec.recognize(one);
|
||||
expect(match.instruction.params).toEqual({'c': '3'});
|
||||
expect(match['allParams']).toEqual({'c': '3'});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -214,7 +214,10 @@ class HelloCmp {
|
||||
|
||||
@Component({selector: 'app-cmp'})
|
||||
@View({template: `root { <router-outlet></router-outlet> }`, directives: ROUTER_DIRECTIVES})
|
||||
@RouteConfig([{path: '/before', redirectTo: '/after'}, {path: '/after', component: HelloCmp}])
|
||||
@RouteConfig([
|
||||
{path: '/before', redirectTo: ['Hello']},
|
||||
{path: '/after', component: HelloCmp, name: 'Hello'}
|
||||
])
|
||||
class RedirectAppCmp {
|
||||
constructor(public router: Router, public location: LocationStrategy) {}
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
describe,
|
||||
it,
|
||||
iit,
|
||||
ddescribe,
|
||||
expect,
|
||||
inject,
|
||||
beforeEach,
|
||||
SpyObject
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {Map, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {RouteRecognizer} from 'angular2/src/router/route_recognizer';
|
||||
import {ComponentInstruction} from 'angular2/src/router/instruction';
|
||||
|
||||
import {Route, Redirect} from 'angular2/src/router/route_config_decorator';
|
||||
import {parser} from 'angular2/src/router/url_parser';
|
||||
|
||||
export function main() {
|
||||
describe('RouteRecognizer', () => {
|
||||
var recognizer;
|
||||
|
||||
beforeEach(() => { recognizer = new RouteRecognizer(); });
|
||||
|
||||
|
||||
it('should recognize a static segment', () => {
|
||||
recognizer.config(new Route({path: '/test', component: DummyCmpA}));
|
||||
var solution = recognize(recognizer, '/test');
|
||||
expect(getComponentType(solution)).toEqual(DummyCmpA);
|
||||
});
|
||||
|
||||
|
||||
it('should recognize a single slash', () => {
|
||||
recognizer.config(new Route({path: '/', component: DummyCmpA}));
|
||||
var solution = recognize(recognizer, '/');
|
||||
expect(getComponentType(solution)).toEqual(DummyCmpA);
|
||||
});
|
||||
|
||||
|
||||
it('should recognize a dynamic segment', () => {
|
||||
recognizer.config(new Route({path: '/user/:name', component: DummyCmpA}));
|
||||
var solution = recognize(recognizer, '/user/brian');
|
||||
expect(getComponentType(solution)).toEqual(DummyCmpA);
|
||||
expect(solution.params).toEqual({'name': 'brian'});
|
||||
});
|
||||
|
||||
|
||||
it('should recognize a star segment', () => {
|
||||
recognizer.config(new Route({path: '/first/*rest', component: DummyCmpA}));
|
||||
var solution = recognize(recognizer, '/first/second/third');
|
||||
expect(getComponentType(solution)).toEqual(DummyCmpA);
|
||||
expect(solution.params).toEqual({'rest': 'second/third'});
|
||||
});
|
||||
|
||||
|
||||
it('should throw when given two routes that start with the same static segment', () => {
|
||||
recognizer.config(new Route({path: '/hello', component: DummyCmpA}));
|
||||
expect(() => recognizer.config(new Route({path: '/hello', component: DummyCmpB})))
|
||||
.toThrowError('Configuration \'/hello\' conflicts with existing route \'/hello\'');
|
||||
});
|
||||
|
||||
|
||||
it('should throw when given two routes that have dynamic segments in the same order', () => {
|
||||
recognizer.config(new Route({path: '/hello/:person/how/:doyoudou', component: DummyCmpA}));
|
||||
expect(() => recognizer.config(
|
||||
new Route({path: '/hello/:friend/how/:areyou', component: DummyCmpA})))
|
||||
.toThrowError(
|
||||
'Configuration \'/hello/:friend/how/:areyou\' conflicts with existing route \'/hello/:person/how/:doyoudou\'');
|
||||
});
|
||||
|
||||
|
||||
it('should recognize redirects', () => {
|
||||
recognizer.config(new Route({path: '/b', component: DummyCmpA}));
|
||||
recognizer.config(new Redirect({path: '/a', redirectTo: 'b'}));
|
||||
var solution = recognize(recognizer, '/a');
|
||||
expect(getComponentType(solution)).toEqual(DummyCmpA);
|
||||
expect(solution.urlPath).toEqual('b');
|
||||
});
|
||||
|
||||
|
||||
it('should not perform root URL redirect on a non-root route', () => {
|
||||
recognizer.config(new Redirect({path: '/', redirectTo: '/foo'}));
|
||||
recognizer.config(new Route({path: '/bar', component: DummyCmpA}));
|
||||
var solution = recognize(recognizer, '/bar');
|
||||
expect(solution.componentType).toEqual(DummyCmpA);
|
||||
expect(solution.urlPath).toEqual('bar');
|
||||
});
|
||||
|
||||
|
||||
it('should perform a root URL redirect only for root routes', () => {
|
||||
recognizer.config(new Redirect({path: '/', redirectTo: '/matias'}));
|
||||
recognizer.config(new Route({path: '/matias', component: DummyCmpA}));
|
||||
recognizer.config(new Route({path: '/fatias', component: DummyCmpA}));
|
||||
|
||||
var solution;
|
||||
|
||||
solution = recognize(recognizer, '/');
|
||||
expect(solution.urlPath).toEqual('matias');
|
||||
|
||||
solution = recognize(recognizer, '/fatias');
|
||||
expect(solution.urlPath).toEqual('fatias');
|
||||
|
||||
solution = recognize(recognizer, '');
|
||||
expect(solution.urlPath).toEqual('matias');
|
||||
});
|
||||
|
||||
|
||||
it('should generate URLs with params', () => {
|
||||
recognizer.config(new Route({path: '/app/user/:name', component: DummyCmpA, name: 'User'}));
|
||||
var instruction = recognizer.generate('User', {'name': 'misko'});
|
||||
expect(instruction.urlPath).toEqual('app/user/misko');
|
||||
});
|
||||
|
||||
|
||||
it('should generate URLs with numeric params', () => {
|
||||
recognizer.config(new Route({path: '/app/page/:number', component: DummyCmpA, name: 'Page'}));
|
||||
expect(recognizer.generate('Page', {'number': 42}).urlPath).toEqual('app/page/42');
|
||||
});
|
||||
|
||||
|
||||
it('should throw in the absence of required params URLs', () => {
|
||||
recognizer.config(new Route({path: 'app/user/:name', component: DummyCmpA, name: 'User'}));
|
||||
expect(() => recognizer.generate('User', {}))
|
||||
.toThrowError('Route generator for \'name\' was not included in parameters passed.');
|
||||
});
|
||||
|
||||
|
||||
it('should throw if the route alias is not CamelCase', () => {
|
||||
expect(() => recognizer.config(
|
||||
new Route({path: 'app/user/:name', component: DummyCmpA, name: 'user'})))
|
||||
.toThrowError(
|
||||
`Route "app/user/:name" with name "user" does not begin with an uppercase letter. Route names should be CamelCase like "User".`);
|
||||
});
|
||||
|
||||
|
||||
describe('params', () => {
|
||||
it('should recognize parameters within the URL path', () => {
|
||||
recognizer.config(new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
|
||||
var solution = recognize(recognizer, '/profile/matsko?comments=all');
|
||||
expect(solution.params).toEqual({'name': 'matsko', 'comments': 'all'});
|
||||
});
|
||||
|
||||
|
||||
it('should generate and populate the given static-based route with querystring params',
|
||||
() => {
|
||||
recognizer.config(
|
||||
new Route({path: 'forum/featured', component: DummyCmpA, name: 'ForumPage'}));
|
||||
|
||||
var params = {'start': 10, 'end': 100};
|
||||
|
||||
var result = recognizer.generate('ForumPage', params);
|
||||
expect(result.urlPath).toEqual('forum/featured');
|
||||
expect(result.urlParams).toEqual(['start=10', 'end=100']);
|
||||
});
|
||||
|
||||
|
||||
it('should prefer positional params over query params', () => {
|
||||
recognizer.config(new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
|
||||
|
||||
var solution = recognize(recognizer, '/profile/yegor?name=igor');
|
||||
expect(solution.params).toEqual({'name': 'yegor'});
|
||||
});
|
||||
|
||||
|
||||
it('should ignore matrix params for the top-level component', () => {
|
||||
recognizer.config(new Route({path: '/home/:subject', component: DummyCmpA, name: 'User'}));
|
||||
var solution = recognize(recognizer, '/home;sort=asc/zero;one=1?two=2');
|
||||
expect(solution.params).toEqual({'subject': 'zero', 'two': '2'});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function recognize(recognizer: RouteRecognizer, url: string): ComponentInstruction {
|
||||
return recognizer.recognize(parser.parse(url))[0].instruction;
|
||||
}
|
||||
|
||||
function getComponentType(routeMatch: ComponentInstruction): any {
|
||||
return routeMatch.componentType;
|
||||
}
|
||||
|
||||
class DummyCmpA {}
|
||||
class DummyCmpB {}
|
||||
@@ -21,9 +21,9 @@ import {
|
||||
AuxRoute,
|
||||
AsyncRoute
|
||||
} from 'angular2/src/router/route_config_decorator';
|
||||
import {stringifyInstruction} from 'angular2/src/router/instruction';
|
||||
import {IS_DART} from 'angular2/src/facade/lang';
|
||||
|
||||
|
||||
export function main() {
|
||||
describe('RouteRegistry', () => {
|
||||
var registry;
|
||||
@@ -34,7 +34,7 @@ export function main() {
|
||||
registry.config(RootHostCmp, new Route({path: '/', component: DummyCmpA}));
|
||||
registry.config(RootHostCmp, new Route({path: '/test', component: DummyCmpB}));
|
||||
|
||||
registry.recognize('/test', RootHostCmp)
|
||||
registry.recognize('/test', [RootHostCmp])
|
||||
.then((instruction) => {
|
||||
expect(instruction.component.componentType).toBe(DummyCmpB);
|
||||
async.done();
|
||||
@@ -45,28 +45,32 @@ export function main() {
|
||||
registry.config(RootHostCmp,
|
||||
new Route({path: '/first/...', component: DummyParentCmp, name: 'FirstCmp'}));
|
||||
|
||||
expect(stringifyInstruction(registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp)))
|
||||
expect(stringifyInstruction(registry.generate(['FirstCmp', 'SecondCmp'], [RootHostCmp])))
|
||||
.toEqual('first/second');
|
||||
expect(stringifyInstruction(registry.generate(['SecondCmp'], DummyParentCmp)))
|
||||
expect(stringifyInstruction(registry.generate(['SecondCmp'], [DummyParentCmp])))
|
||||
.toEqual('second');
|
||||
});
|
||||
|
||||
xit('should generate URLs that account for redirects', () => {
|
||||
it('should generate URLs that account for default routes', () => {
|
||||
registry.config(
|
||||
RootHostCmp,
|
||||
new Route({path: '/first/...', component: DummyParentRedirectCmp, name: 'FirstCmp'}));
|
||||
new Route({path: '/first/...', component: ParentWithDefaultRouteCmp, name: 'FirstCmp'}));
|
||||
|
||||
expect(stringifyInstruction(registry.generate(['FirstCmp'], RootHostCmp)))
|
||||
.toEqual('first/second');
|
||||
var instruction = registry.generate(['FirstCmp'], [RootHostCmp]);
|
||||
|
||||
expect(instruction.toLinkUrl()).toEqual('first');
|
||||
expect(instruction.toRootUrl()).toEqual('first/second');
|
||||
});
|
||||
|
||||
xit('should generate URLs in a hierarchy of redirects', () => {
|
||||
it('should generate URLs in a hierarchy of default routes', () => {
|
||||
registry.config(
|
||||
RootHostCmp,
|
||||
new Route({path: '/first/...', component: DummyMultipleRedirectCmp, name: 'FirstCmp'}));
|
||||
new Route({path: '/first/...', component: MultipleDefaultCmp, name: 'FirstCmp'}));
|
||||
|
||||
expect(stringifyInstruction(registry.generate(['FirstCmp'], RootHostCmp)))
|
||||
.toEqual('first/second/third');
|
||||
var instruction = registry.generate(['FirstCmp'], [RootHostCmp]);
|
||||
|
||||
expect(instruction.toLinkUrl()).toEqual('first');
|
||||
expect(instruction.toRootUrl()).toEqual('first/second/third');
|
||||
});
|
||||
|
||||
it('should generate URLs with params', () => {
|
||||
@@ -75,13 +79,13 @@ export function main() {
|
||||
new Route({path: '/first/:param/...', component: DummyParentParamCmp, name: 'FirstCmp'}));
|
||||
|
||||
var url = stringifyInstruction(registry.generate(
|
||||
['FirstCmp', {param: 'one'}, 'SecondCmp', {param: 'two'}], RootHostCmp));
|
||||
['FirstCmp', {param: 'one'}, 'SecondCmp', {param: 'two'}], [RootHostCmp]));
|
||||
expect(url).toEqual('first/one/second/two');
|
||||
});
|
||||
|
||||
it('should generate params as an empty StringMap when no params are given', () => {
|
||||
registry.config(RootHostCmp, new Route({path: '/test', component: DummyCmpA, name: 'Test'}));
|
||||
var instruction = registry.generate(['Test'], RootHostCmp);
|
||||
var instruction = registry.generate(['Test'], [RootHostCmp]);
|
||||
expect(instruction.component.params).toEqual({});
|
||||
});
|
||||
|
||||
@@ -91,20 +95,20 @@ export function main() {
|
||||
RootHostCmp,
|
||||
new AsyncRoute({path: '/first/...', loader: asyncParentLoader, name: 'FirstCmp'}));
|
||||
|
||||
expect(() => registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp))
|
||||
.toThrowError('Could not find route named "SecondCmp".');
|
||||
var instruction = registry.generate(['FirstCmp', 'SecondCmp'], [RootHostCmp]);
|
||||
|
||||
registry.recognize('/first/second', RootHostCmp)
|
||||
expect(stringifyInstruction(instruction)).toEqual('first');
|
||||
|
||||
registry.recognize('/first/second', [RootHostCmp])
|
||||
.then((_) => {
|
||||
expect(
|
||||
stringifyInstruction(registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp)))
|
||||
.toEqual('first/second');
|
||||
var instruction = registry.generate(['FirstCmp', 'SecondCmp'], [RootHostCmp]);
|
||||
expect(stringifyInstruction(instruction)).toEqual('first/second');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw when generating a url and a parent has no config', () => {
|
||||
expect(() => registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp))
|
||||
expect(() => registry.generate(['FirstCmp', 'SecondCmp'], [RootHostCmp]))
|
||||
.toThrowError('Component "RootHostCmp" has no route config.');
|
||||
});
|
||||
|
||||
@@ -113,7 +117,7 @@ export function main() {
|
||||
new Route({path: '/primary', component: DummyCmpA, name: 'Primary'}));
|
||||
registry.config(RootHostCmp, new AuxRoute({path: '/aux', component: DummyCmpB, name: 'Aux'}));
|
||||
|
||||
expect(stringifyInstruction(registry.generate(['Primary', ['Aux']], RootHostCmp)))
|
||||
expect(stringifyInstruction(registry.generate(['Primary', ['Aux']], [RootHostCmp])))
|
||||
.toEqual('primary(aux)');
|
||||
});
|
||||
|
||||
@@ -121,7 +125,7 @@ export function main() {
|
||||
registry.config(RootHostCmp, new Route({path: '/:site', component: DummyCmpB}));
|
||||
registry.config(RootHostCmp, new Route({path: '/home', component: DummyCmpA}));
|
||||
|
||||
registry.recognize('/home', RootHostCmp)
|
||||
registry.recognize('/home', [RootHostCmp])
|
||||
.then((instruction) => {
|
||||
expect(instruction.component.componentType).toBe(DummyCmpA);
|
||||
async.done();
|
||||
@@ -132,7 +136,7 @@ export function main() {
|
||||
registry.config(RootHostCmp, new Route({path: '/:site', component: DummyCmpA}));
|
||||
registry.config(RootHostCmp, new Route({path: '/*site', component: DummyCmpB}));
|
||||
|
||||
registry.recognize('/home', RootHostCmp)
|
||||
registry.recognize('/home', [RootHostCmp])
|
||||
.then((instruction) => {
|
||||
expect(instruction.component.componentType).toBe(DummyCmpA);
|
||||
async.done();
|
||||
@@ -143,7 +147,7 @@ export function main() {
|
||||
registry.config(RootHostCmp, new Route({path: '/:first/*rest', component: DummyCmpA}));
|
||||
registry.config(RootHostCmp, new Route({path: '/*all', component: DummyCmpB}));
|
||||
|
||||
registry.recognize('/some/path', RootHostCmp)
|
||||
registry.recognize('/some/path', [RootHostCmp])
|
||||
.then((instruction) => {
|
||||
expect(instruction.component.componentType).toBe(DummyCmpA);
|
||||
async.done();
|
||||
@@ -154,7 +158,7 @@ export function main() {
|
||||
registry.config(RootHostCmp, new Route({path: '/first/:second', component: DummyCmpA}));
|
||||
registry.config(RootHostCmp, new Route({path: '/:first/:second', component: DummyCmpB}));
|
||||
|
||||
registry.recognize('/first/second', RootHostCmp)
|
||||
registry.recognize('/first/second', [RootHostCmp])
|
||||
.then((instruction) => {
|
||||
expect(instruction.component.componentType).toBe(DummyCmpA);
|
||||
async.done();
|
||||
@@ -168,7 +172,7 @@ export function main() {
|
||||
registry.config(RootHostCmp,
|
||||
new Route({path: '/first/:second/third', component: DummyCmpA}));
|
||||
|
||||
registry.recognize('/first/second/third', RootHostCmp)
|
||||
registry.recognize('/first/second/third', [RootHostCmp])
|
||||
.then((instruction) => {
|
||||
expect(instruction.component.componentType).toBe(DummyCmpB);
|
||||
async.done();
|
||||
@@ -178,7 +182,7 @@ export function main() {
|
||||
it('should match the full URL using child components', inject([AsyncTestCompleter], (async) => {
|
||||
registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyParentCmp}));
|
||||
|
||||
registry.recognize('/first/second', RootHostCmp)
|
||||
registry.recognize('/first/second', [RootHostCmp])
|
||||
.then((instruction) => {
|
||||
expect(instruction.component.componentType).toBe(DummyParentCmp);
|
||||
expect(instruction.child.component.componentType).toBe(DummyCmpB);
|
||||
@@ -190,11 +194,14 @@ export function main() {
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyAsyncCmp}));
|
||||
|
||||
registry.recognize('/first/second', RootHostCmp)
|
||||
registry.recognize('/first/second', [RootHostCmp])
|
||||
.then((instruction) => {
|
||||
expect(instruction.component.componentType).toBe(DummyAsyncCmp);
|
||||
expect(instruction.child.component.componentType).toBe(DummyCmpB);
|
||||
async.done();
|
||||
|
||||
instruction.child.resolveComponent().then((childComponentInstruction) => {
|
||||
expect(childComponentInstruction.componentType).toBe(DummyCmpB);
|
||||
async.done();
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -203,11 +210,14 @@ export function main() {
|
||||
registry.config(RootHostCmp,
|
||||
new AsyncRoute({path: '/first/...', loader: asyncParentLoader}));
|
||||
|
||||
registry.recognize('/first/second', RootHostCmp)
|
||||
registry.recognize('/first/second', [RootHostCmp])
|
||||
.then((instruction) => {
|
||||
expect(instruction.component.componentType).toBe(DummyParentCmp);
|
||||
expect(instruction.child.component.componentType).toBe(DummyCmpB);
|
||||
async.done();
|
||||
|
||||
instruction.child.resolveComponent().then((childType) => {
|
||||
expect(childType.componentType).toBe(DummyCmpB);
|
||||
async.done();
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -242,15 +252,15 @@ export function main() {
|
||||
it('should throw when linkParams are not terminal', () => {
|
||||
registry.config(RootHostCmp,
|
||||
new Route({path: '/first/...', component: DummyParentCmp, name: 'First'}));
|
||||
expect(() => { registry.generate(['First'], RootHostCmp); })
|
||||
.toThrowError('Link "["First"]" does not resolve to a terminal or async instruction.');
|
||||
expect(() => { registry.generate(['First'], [RootHostCmp]); })
|
||||
.toThrowError('Link "["First"]" does not resolve to a terminal instruction.');
|
||||
});
|
||||
|
||||
it('should match matrix params on child components and query params on the root component',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyParentCmp}));
|
||||
|
||||
registry.recognize('/first/second;filter=odd?comments=all', RootHostCmp)
|
||||
registry.recognize('/first/second;filter=odd?comments=all', [RootHostCmp])
|
||||
.then((instruction) => {
|
||||
expect(instruction.component.componentType).toBe(DummyParentCmp);
|
||||
expect(instruction.component.params).toEqual({'comments': 'all'});
|
||||
@@ -276,13 +286,18 @@ export function main() {
|
||||
sort: 'asc',
|
||||
}
|
||||
],
|
||||
RootHostCmp));
|
||||
[RootHostCmp]));
|
||||
expect(url).toEqual('first/one/second/two;sort=asc?query=cats');
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function stringifyInstruction(instruction): string {
|
||||
return instruction.toRootUrl();
|
||||
}
|
||||
|
||||
|
||||
function asyncParentLoader() {
|
||||
return PromiseWrapper.resolve(DummyParentCmp);
|
||||
}
|
||||
@@ -300,26 +315,22 @@ class DummyAsyncCmp {
|
||||
class DummyCmpA {}
|
||||
class DummyCmpB {}
|
||||
|
||||
@RouteConfig([
|
||||
new Redirect({path: '/', redirectTo: '/third'}),
|
||||
new Route({path: '/third', component: DummyCmpB, name: 'ThirdCmp'})
|
||||
])
|
||||
class DummyRedirectCmp {
|
||||
@RouteConfig(
|
||||
[new Route({path: '/third', component: DummyCmpB, name: 'ThirdCmp', useAsDefault: true})])
|
||||
class DefaultRouteCmp {
|
||||
}
|
||||
|
||||
|
||||
@RouteConfig([
|
||||
new Redirect({path: '/', redirectTo: '/second'}),
|
||||
new Route({path: '/second/...', component: DummyRedirectCmp, name: 'SecondCmp'})
|
||||
new Route(
|
||||
{path: '/second/...', component: DefaultRouteCmp, name: 'SecondCmp', useAsDefault: true})
|
||||
])
|
||||
class DummyMultipleRedirectCmp {
|
||||
class MultipleDefaultCmp {
|
||||
}
|
||||
|
||||
@RouteConfig([
|
||||
new Redirect({path: '/', redirectTo: '/second'}),
|
||||
new Route({path: '/second', component: DummyCmpB, name: 'SecondCmp'})
|
||||
])
|
||||
class DummyParentRedirectCmp {
|
||||
@RouteConfig(
|
||||
[new Route({path: '/second', component: DummyCmpB, name: 'SecondCmp', useAsDefault: true})])
|
||||
class ParentWithDefaultRouteCmp {
|
||||
}
|
||||
|
||||
@RouteConfig([new Route({path: '/second', component: DummyCmpB, name: 'SecondCmp'})])
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
beforeEachBindings,
|
||||
beforeEachProviders,
|
||||
it,
|
||||
xit,
|
||||
TestComponentBuilder
|
||||
@@ -27,24 +27,20 @@ import {
|
||||
RouterOutlet,
|
||||
Route,
|
||||
RouteParams,
|
||||
Instruction,
|
||||
ComponentInstruction
|
||||
} from 'angular2/router';
|
||||
|
||||
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
||||
import {ComponentInstruction_} from 'angular2/src/router/instruction';
|
||||
import {PathRecognizer} from 'angular2/src/router/path_recognizer';
|
||||
import {SyncRouteHandler} from 'angular2/src/router/sync_route_handler';
|
||||
import {ResolvedInstruction} from 'angular2/src/router/instruction';
|
||||
|
||||
let dummyPathRecognizer = new PathRecognizer('', new SyncRouteHandler(null));
|
||||
let dummyInstruction =
|
||||
new Instruction(new ComponentInstruction_('detail', [], dummyPathRecognizer), null, {});
|
||||
new ResolvedInstruction(new ComponentInstruction('detail', [], null, null, true, 0), null, {});
|
||||
|
||||
export function main() {
|
||||
describe('router-link directive', function() {
|
||||
var tcb: TestComponentBuilder;
|
||||
|
||||
beforeEachBindings(() => [
|
||||
beforeEachProviders(() => [
|
||||
provide(Location, {useValue: makeDummyLocation()}),
|
||||
provide(Router, {useValue: makeDummyRouter()})
|
||||
]);
|
||||
@@ -106,11 +102,6 @@ export function main() {
|
||||
});
|
||||
}
|
||||
|
||||
@Component({selector: 'my-comp'})
|
||||
class MyComp {
|
||||
name;
|
||||
}
|
||||
|
||||
@Component({selector: 'user-cmp'})
|
||||
@View({template: "hello {{user}}"})
|
||||
class UserCmp {
|
||||
|
||||
@@ -18,7 +18,6 @@ import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Router, RootRouter} from 'angular2/src/router/router';
|
||||
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||
import {Location} from 'angular2/src/router/location';
|
||||
import {stringifyInstruction} from 'angular2/src/router/instruction';
|
||||
|
||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||
import {RouteConfig, AsyncRoute, Route} from 'angular2/src/router/route_config_decorator';
|
||||
@@ -225,6 +224,11 @@ export function main() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function stringifyInstruction(instruction): string {
|
||||
return instruction.toRootUrl();
|
||||
}
|
||||
|
||||
function loader(): Promise<Type> {
|
||||
return PromiseWrapper.resolve(DummyComponent);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user