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:
Brian Ford
2015-11-02 16:14:10 -08:00
parent 422a7b18f6
commit cf7292fcb1
41 changed files with 2969 additions and 1116 deletions
@@ -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 {
}
@@ -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 {
+5 -1
View File
@@ -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);
}