feat(router): add interfaces for route definitions in RouteConfig

Closes #2261
This commit is contained in:
Brian Ford 2015-07-13 16:12:48 -07:00
parent 61c73576c8
commit 4d28167bc0
16 changed files with 457 additions and 227 deletions

View File

@ -1,4 +1,5 @@
import {RouteConfig as RouteConfigAnnotation} from './route_config_impl';
import {makeDecorator} from 'angular2/src/util/decorators';
export {Route, Redirect, AsyncRoute, RouteDefinition} from './route_config_impl';
export var RouteConfig = makeDecorator(RouteConfigAnnotation);

View File

@ -1,5 +1,7 @@
import {CONST} from 'angular2/src/facade/lang';
import {List, Map} from 'angular2/src/facade/collection';
import {CONST, Type} from 'angular2/src/facade/lang';
import {List} from 'angular2/src/facade/collection';
import {RouteDefinition} from './route_definition';
export {RouteDefinition} from './route_definition';
/**
* You use the RouteConfig annotation to add routes to a component.
@ -11,5 +13,41 @@ import {List, Map} from 'angular2/src/facade/collection';
*/
@CONST()
export class RouteConfig {
constructor(public configs: List<Map<any, any>>) {}
constructor(public configs: List<RouteDefinition>) {}
}
@CONST()
export class Route implements RouteDefinition {
path: string;
component: Type;
as: string;
constructor({path, component, as}: {path: string, component: Type, as?: string}) {
this.path = path;
this.component = component;
this.as = as;
}
}
@CONST()
export class AsyncRoute implements RouteDefinition {
path: string;
loader: Function;
as: string;
constructor({path, loader, as}: {path: string, loader: Function, as?: string}) {
this.path = path;
this.loader = loader;
this.as = as;
}
}
@CONST()
export class Redirect implements RouteDefinition {
path: string;
redirectTo: string;
as: string = null;
constructor({path, redirectTo}: {path: string, redirectTo: string}) {
this.path = path;
this.redirectTo = redirectTo;
}
}

View File

@ -0,0 +1,7 @@
library angular2.src.router.route_config_normalizer;
import "route_config_decorator.dart";
RouteDefinition normalizeRouteConfig(RouteDefinition config) {
return config;
}

View File

@ -0,0 +1,46 @@
import {AsyncRoute, Route, Redirect, RouteDefinition} from './route_config_decorator';
import {ComponentDefinition} from './route_definition';
import {Type, BaseException} from 'angular2/src/facade/lang';
/**
* Given a JS Object that represents... returns a corresponding Route, AsyncRoute, or Redirect
*/
export function normalizeRouteConfig(config: RouteDefinition): RouteDefinition {
if (config instanceof Route || config instanceof Redirect || config instanceof AsyncRoute) {
return <RouteDefinition>config;
}
if ((!config.component) == (!config.redirectTo)) {
throw new BaseException(
`Route config should contain exactly one 'component', or 'redirectTo' property`);
}
if (config.component) {
if (typeof config.component == 'object') {
let componentDefinitionObject = <ComponentDefinition>config.component;
if (componentDefinitionObject.type == 'constructor') {
return new Route({
path: config.path,
component:<Type>componentDefinitionObject.constructor,
as: config.as
});
} else if (componentDefinitionObject.type == 'loader') {
return new AsyncRoute(
{path: config.path, loader: componentDefinitionObject.loader, as: config.as});
} else {
throw new BaseException(
`Invalid component type '${componentDefinitionObject.type}'. Valid types are "constructor" and "loader".`);
}
}
return new Route(<{
path: string;
component: Type;
as?: string
}>config);
}
if (config.redirectTo) {
return new Redirect({path: config.path, redirectTo: config.redirectTo});
}
return config;
}

View File

@ -0,0 +1,7 @@
library angular2.src.router.route_definition;
abstract class RouteDefinition {
final String path;
final String as;
const RouteDefinition({this.path, this.as});
}

View File

@ -0,0 +1,15 @@
import {CONST, Type} from 'angular2/src/facade/lang';
export interface RouteDefinition {
path: string;
component?: Type | ComponentDefinition;
loader?: Function;
redirectTo?: string;
as?: string;
}
export interface ComponentDefinition {
type: string;
loader?: Function;
component?: Type;
}

View File

@ -19,6 +19,7 @@ import {
import {PathRecognizer} from './path_recognizer';
import {RouteHandler} from './route_handler';
import {Route, AsyncRoute, Redirect, RouteDefinition} from './route_config_impl';
import {AsyncRouteHandler} from './async_route_handler';
import {SyncRouteHandler} from './sync_route_handler';
@ -32,25 +33,27 @@ export class RouteRecognizer {
redirects: Map<string, string> = new Map();
matchers: Map<RegExp, PathRecognizer> = new Map();
addRedirect(path: string, target: string): void {
if (path == '/') {
path = '';
config(config: RouteDefinition): boolean {
var handler;
if (config instanceof Redirect) {
let path = config.path == '/' ? '' : config.path;
this.redirects.set(path, config.redirectTo);
return true;
} else if (config instanceof Route) {
handler = new SyncRouteHandler(config.component);
} else if (config instanceof AsyncRoute) {
handler = new AsyncRouteHandler(config.loader);
}
this.redirects.set(path, target);
}
addConfig(path: string, handlerObj: any, alias: string = null): boolean {
var handler = configObjToHandler(handlerObj['component']);
var recognizer = new PathRecognizer(path, handler);
var recognizer = new PathRecognizer(config.path, handler);
MapWrapper.forEach(this.matchers, (matcher, _) => {
if (recognizer.regex.toString() == matcher.regex.toString()) {
throw new BaseException(
`Configuration '${path}' conflicts with existing route '${matcher.path}'`);
`Configuration '${config.path}' conflicts with existing route '${matcher.path}'`);
}
});
this.matchers.set(recognizer.regex, recognizer);
if (isPresent(alias)) {
this.names.set(alias, recognizer);
if (isPresent(config.as)) {
this.names.set(config.as, recognizer);
}
return recognizer.terminal;
}

View File

@ -20,9 +20,10 @@ import {
BaseException,
getTypeNameForDebugging
} from 'angular2/src/facade/lang';
import {RouteConfig} from './route_config_impl';
import {RouteConfig, AsyncRoute, Route, Redirect, RouteDefinition} from './route_config_impl';
import {reflector} from 'angular2/src/reflection/reflection';
import {Injectable} from 'angular2/di';
import {normalizeRouteConfig} from './route_config_nomalizer';
/**
* The RouteRegistry holds route configurations for each component in an Angular app.
@ -36,8 +37,8 @@ export class RouteRegistry {
/**
* Given a component and a configuration object, add the route to this registry
*/
config(parentComponent, config: StringMap<string, any>): void {
assertValidConfig(config);
config(parentComponent, config: RouteDefinition): void {
config = normalizeRouteConfig(config);
var recognizer: RouteRecognizer = this._rules.get(parentComponent);
@ -46,22 +47,13 @@ export class RouteRegistry {
this._rules.set(parentComponent, recognizer);
}
if (StringMapWrapper.contains(config, 'redirectTo')) {
recognizer.addRedirect(config['path'], config['redirectTo']);
return;
}
var terminal = recognizer.config(config);
config = StringMapWrapper.merge(
config, {'component': normalizeComponentDeclaration(config['component'])});
var component = config['component'];
var terminal = recognizer.addConfig(config['path'], config, config['as']);
if (component['type'] == 'constructor') {
if (config instanceof Route) {
if (terminal) {
assertTerminalComponent(component['constructor'], config['path']);
assertTerminalComponent(config.component, config.path);
} else {
this.configFromComponent(component['constructor']);
this.configFromComponent(config.component);
}
}
}
@ -191,50 +183,6 @@ export class RouteRegistry {
}
/*
* A config should have a "path" property, and exactly one of:
* - `component`
* - `redirectTo`
*/
var ALLOWED_TARGETS = ['component', 'redirectTo'];
function assertValidConfig(config: StringMap<string, any>): void {
if (!StringMapWrapper.contains(config, 'path')) {
throw new BaseException(`Route config should contain a "path" property`);
}
var targets = 0;
ListWrapper.forEach(ALLOWED_TARGETS, (target) => {
if (StringMapWrapper.contains(config, target)) {
targets += 1;
}
});
if (targets != 1) {
throw new BaseException(
`Route config should contain exactly one 'component', or 'redirectTo' property`);
}
}
/*
* Returns a StringMap like: `{ 'constructor': SomeType, 'type': 'constructor' }`
*/
var VALID_COMPONENT_TYPES = ['constructor', 'loader'];
function normalizeComponentDeclaration(config: any): StringMap<string, any> {
if (isType(config)) {
return {'constructor': config, 'type': 'constructor'};
} else if (isStringMap(config)) {
if (isBlank(config['type'])) {
throw new BaseException(
`Component declaration when provided as a map should include a 'type' property`);
}
var componentType = config['type'];
if (!ListWrapper.contains(VALID_COMPONENT_TYPES, componentType)) {
throw new BaseException(`Invalid component type '${componentType}'`);
}
return config;
} else {
throw new BaseException(`Component declaration should be either a Map or a Type`);
}
}
/*
* Given a list of instructions, returns the most specific instruction
*/

View File

@ -16,6 +16,7 @@ import {Instruction} from './instruction';
import {RouterOutlet} from './router_outlet';
import {Location} from './location';
import {getCanActivateHook} from './route_lifecycle_reflector';
import {RouteDefinition} from './route_config_impl';
let _resolveToTrue = PromiseWrapper.resolve(true);
let _resolveToFalse = PromiseWrapper.resolve(false);
@ -79,25 +80,15 @@ export class Router {
* # Usage
*
* ```
* router.config({ 'path': '/', 'component': IndexCmp});
* ```
*
* Or:
*
* ```
* router.config([
* { 'path': '/', 'component': IndexComp },
* { 'path': '/user/:id', 'component': UserComp },
* ]);
* ```
*/
config(config: StringMap<string, any>| List<StringMap<string, any>>): Promise<any> {
if (isArray(config)) {
(<List<any>>config)
.forEach((configObject) => { this.registry.config(this.hostComponent, configObject); });
} else {
this.registry.config(this.hostComponent, config);
}
config(definitions: List<RouteDefinition>): Promise<any> {
definitions.forEach(
(routeDefinition) => { this.registry.config(this.hostComponent, routeDefinition); });
return this.renavigate();
}

View File

@ -24,7 +24,7 @@ import {Promise, PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2
import {RootRouter} from 'angular2/src/router/router';
import {Pipeline} from 'angular2/src/router/pipeline';
import {Router, RouterOutlet, RouterLink, RouteParams} from 'angular2/router';
import {RouteConfig} from 'angular2/src/router/route_config_decorator';
import {RouteConfig, Route, AsyncRoute, Redirect} from 'angular2/src/router/route_config_decorator';
import {DOM} from 'angular2/src/dom/dom_adapter';
@ -81,7 +81,7 @@ export function main() {
it('should work in a simple case', inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/test', 'component': HelloCmp}))
.then((_) => rtr.config([new Route({path: '/test', component: HelloCmp})]))
.then((_) => rtr.navigate('/test'))
.then((_) => {
rootTC.detectChanges();
@ -94,7 +94,7 @@ export function main() {
it('should navigate between components with different parameters',
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp}))
.then((_) => rtr.config([new Route({path: '/user/:name', component: UserCmp})]))
.then((_) => rtr.navigate('/user/brian'))
.then((_) => {
rootTC.detectChanges();
@ -111,7 +111,7 @@ export function main() {
it('should work with child routers', inject([AsyncTestCompleter], (async) => {
compile('outer { <router-outlet></router-outlet> }')
.then((_) => rtr.config({'path': '/a/...', 'component': ParentCmp}))
.then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
.then((_) => rtr.navigate('/a/b'))
.then((_) => {
rootTC.detectChanges();
@ -123,8 +123,10 @@ export function main() {
it('should work with redirects', inject([AsyncTestCompleter, Location], (async, location) => {
compile()
.then((_) => rtr.config({'path': '/original', 'redirectTo': '/redirected'}))
.then((_) => rtr.config({'path': '/redirected', 'component': A}))
.then((_) => rtr.config([
new Redirect({path: '/original', redirectTo: '/redirected'}),
new Route({path: '/redirected', component: A})
]))
.then((_) => rtr.navigate('/original'))
.then((_) => {
rootTC.detectChanges();
@ -142,7 +144,7 @@ export function main() {
inject([AsyncTestCompleter], (async) => {
location.setBaseHref('/my/base');
compile('<a href="hello" [router-link]="[\'./user\']"></a>')
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
.then((_) => rtr.config([new Route({path: '/user', component: UserCmp, as: 'user'})]))
.then((_) => rtr.navigate('/a/b'))
.then((_) => {
rootTC.detectChanges();
@ -154,7 +156,7 @@ export function main() {
it('should generate link hrefs without params', inject([AsyncTestCompleter], (async) => {
compile('<a href="hello" [router-link]="[\'./user\']"></a>')
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
.then((_) => rtr.config([new Route({path: '/user', component: UserCmp, as: 'user'})]))
.then((_) => rtr.navigate('/a/b'))
.then((_) => {
rootTC.detectChanges();
@ -166,7 +168,7 @@ export function main() {
it('should reuse common parent components', inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/team/:id/...', 'component': TeamCmp}))
.then((_) => rtr.config([new Route({path: '/team/:id/...', component: TeamCmp})]))
.then((_) => rtr.navigate('/team/angular/user/rado'))
.then((_) => {
rootTC.detectChanges();
@ -185,7 +187,8 @@ export function main() {
it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => {
compile('<a href="hello" [router-link]="[\'./user\', {name: name}]">{{name}}</a>')
.then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp, 'as': 'user'}))
.then((_) => rtr.config(
[new Route({path: '/user/:name', component: UserCmp, as: 'user'})]))
.then((_) => rtr.navigate('/a/b'))
.then((_) => {
rootTC.componentInstance.name = 'brian';
@ -202,7 +205,7 @@ export function main() {
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config(
{'path': '/page/:number', 'component': SiblingPageCmp, 'as': 'page'}))
[new Route({path: '/page/:number', component: SiblingPageCmp, as: 'page'})]))
.then((_) => rtr.navigate('/page/1'))
.then((_) => {
rootTC.detectChanges();
@ -217,8 +220,8 @@ export function main() {
it('should generate relative links preserving the existing parent route',
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) =>
rtr.config({'path': '/book/:title/...', 'component': BookCmp, 'as': 'book'}))
.then((_) => rtr.config(
[new Route({path: '/book/:title/...', component: BookCmp, as: 'book'})]))
.then((_) => rtr.navigate('/book/1984/page/1'))
.then((_) => {
rootTC.detectChanges();
@ -239,7 +242,7 @@ export function main() {
it('should call the onActivate hook', inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => rtr.navigate('/on-activate'))
.then((_) => {
rootTC.detectChanges();
@ -252,7 +255,7 @@ export function main() {
it('should wait for a parent component\'s onActivate hook to resolve before calling its child\'s',
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => {
ObservableWrapper.subscribe(eventBus, (ev) => {
if (ev.startsWith('parent activate')) {
@ -272,7 +275,7 @@ export function main() {
it('should call the onDeactivate hook', inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => rtr.navigate('/on-deactivate'))
.then((_) => rtr.navigate('/a'))
.then((_) => {
@ -286,7 +289,7 @@ export function main() {
it('should wait for a child component\'s onDeactivate hook to resolve before calling its parent\'s',
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => rtr.navigate('/parent-deactivate/child-deactivate'))
.then((_) => {
ObservableWrapper.subscribe(eventBus, (ev) => {
@ -309,7 +312,7 @@ export function main() {
it('should reuse a component when the canReuse hook returns false',
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => rtr.navigate('/on-reuse/1/a'))
.then((_) => {
rootTC.detectChanges();
@ -331,7 +334,7 @@ export function main() {
it('should not reuse a component when the canReuse hook returns false',
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => rtr.navigate('/never-reuse/1/a'))
.then((_) => {
rootTC.detectChanges();
@ -351,7 +354,7 @@ export function main() {
it('should navigate when canActivate returns true', inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => {
ObservableWrapper.subscribe(eventBus, (ev) => {
if (ev.startsWith('canActivate')) {
@ -371,7 +374,7 @@ export function main() {
it('should not navigate when canActivate returns false',
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => {
ObservableWrapper.subscribe(eventBus, (ev) => {
if (ev.startsWith('canActivate')) {
@ -391,7 +394,7 @@ export function main() {
it('should navigate away when canDeactivate returns true',
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => rtr.navigate('/can-deactivate/a'))
.then((_) => {
rootTC.detectChanges();
@ -416,7 +419,7 @@ export function main() {
it('should not navigate away when canDeactivate returns false',
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => rtr.navigate('/can-deactivate/a'))
.then((_) => {
rootTC.detectChanges();
@ -442,7 +445,7 @@ export function main() {
it('should run activation and deactivation hooks in the correct order',
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => rtr.navigate('/activation-hooks/child'))
.then((_) => {
expect(log).toEqual('canActivate child: null -> /child;' +
@ -464,7 +467,7 @@ export function main() {
it('should only run reuse hooks when reusing', inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => rtr.navigate('/reuse-hooks/1'))
.then((_) => {
expect(log).toEqual('canActivate: null -> /reuse-hooks/1;' +
@ -488,7 +491,7 @@ export function main() {
it('should not run reuse hooks when not reusing', inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => rtr.config({'path': '/...', 'component': LifecycleCmp}))
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
.then((_) => rtr.navigate('/reuse-hooks/1'))
.then((_) => {
expect(log).toEqual('canActivate: null -> /reuse-hooks/1;' +
@ -524,7 +527,8 @@ export function main() {
it('should navigate to link hrefs without params', inject([AsyncTestCompleter], (async) => {
compile('<a href="hello" [router-link]="[\'./user\']"></a>')
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
.then((_) =>
rtr.config([new Route({path: '/user', component: UserCmp, as: 'user'})]))
.then((_) => rtr.navigate('/a/b'))
.then((_) => {
rootTC.detectChanges();
@ -545,7 +549,8 @@ export function main() {
inject([AsyncTestCompleter], (async) => {
location.setBaseHref('/base');
compile('<a href="hello" [router-link]="[\'./user\']"></a>')
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
.then((_) =>
rtr.config([new Route({path: '/user', component: UserCmp, as: 'user'})]))
.then((_) => rtr.navigate('/a/b'))
.then((_) => {
rootTC.detectChanges();
@ -615,7 +620,7 @@ class SiblingPageCmp {
<router-outlet></router-outlet>`,
directives: [RouterLink, RouterOutlet]
})
@RouteConfig([{path: '/page/:number', component: SiblingPageCmp, 'as': 'page'}])
@RouteConfig([new Route({path: '/page/:number', component: SiblingPageCmp, as: 'page'})])
class BookCmp {
title: string;
constructor(params: RouteParams) { this.title = params.get('title'); }
@ -624,7 +629,7 @@ class BookCmp {
@Component({selector: 'parent-cmp'})
@View({template: "inner { <router-outlet></router-outlet> }", directives: [RouterOutlet]})
@RouteConfig([{path: '/b', component: HelloCmp}])
@RouteConfig([new Route({path: '/b', component: HelloCmp})])
class ParentCmp {
constructor() {}
}
@ -632,7 +637,7 @@ class ParentCmp {
@Component({selector: 'team-cmp'})
@View({template: "team {{id}} { <router-outlet></router-outlet> }", directives: [RouterOutlet]})
@RouteConfig([{path: '/user/:name', component: UserCmp}])
@RouteConfig([new Route({path: '/user/:name', component: UserCmp})])
class TeamCmp {
id: string;
constructor(params: RouteParams) {
@ -662,7 +667,7 @@ class ActivateCmp implements OnActivate {
@Component({selector: 'parent-activate-cmp'})
@View({template: `parent {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
@RouteConfig([{path: '/child-activate', component: ActivateCmp}])
@RouteConfig([new Route({path: '/child-activate', component: ActivateCmp})])
class ParentActivateCmp implements OnActivate {
onActivate(next: Instruction, prev: Instruction): Promise<any> {
completer = PromiseWrapper.completer();
@ -689,14 +694,14 @@ class WaitDeactivateCmp implements OnDeactivate {
@Component({selector: 'parent-deactivate-cmp'})
@View({template: `parent {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
@RouteConfig([{path: '/child-deactivate', component: WaitDeactivateCmp}])
@RouteConfig([new Route({path: '/child-deactivate', component: WaitDeactivateCmp})])
class ParentDeactivateCmp implements OnDeactivate {
onDeactivate(next: Instruction, prev: Instruction) { logHook('parent deactivate', next, prev); }
}
@Component({selector: 'reuse-cmp'})
@View({template: `reuse {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
@RouteConfig([{path: '/a', component: A}, {path: '/b', component: B}])
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
class ReuseCmp implements OnReuse, CanReuse {
constructor() { cmpInstanceCount += 1; }
canReuse(next: Instruction, prev: Instruction) { return true; }
@ -705,7 +710,7 @@ class ReuseCmp implements OnReuse, CanReuse {
@Component({selector: 'never-reuse-cmp'})
@View({template: `reuse {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
@RouteConfig([{path: '/a', component: A}, {path: '/b', component: B}])
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
class NeverReuseCmp implements OnReuse, CanReuse {
constructor() { cmpInstanceCount += 1; }
canReuse(next: Instruction, prev: Instruction) { return false; }
@ -714,7 +719,7 @@ class NeverReuseCmp implements OnReuse, CanReuse {
@Component({selector: 'can-activate-cmp'})
@View({template: `canActivate {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
@RouteConfig([{path: '/a', component: A}, {path: '/b', component: B}])
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
@CanActivate(CanActivateCmp.canActivate)
class CanActivateCmp {
static canActivate(next: Instruction, prev: Instruction) {
@ -726,7 +731,7 @@ class CanActivateCmp {
@Component({selector: 'can-deactivate-cmp'})
@View({template: `canDeactivate {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
@RouteConfig([{path: '/a', component: A}, {path: '/b', component: B}])
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
class CanDeactivateCmp implements CanDeactivate {
canDeactivate(next: Instruction, prev: Instruction) {
completer = PromiseWrapper.completer();
@ -756,7 +761,7 @@ class AllHooksChildCmp implements CanDeactivate, OnDeactivate, OnActivate {
@Component({selector: 'all-hooks-parent-cmp'})
@View({template: `<router-outlet></router-outlet>`, directives: [RouterOutlet]})
@RouteConfig([{path: '/child', component: AllHooksChildCmp}])
@RouteConfig([new Route({path: '/child', component: AllHooksChildCmp})])
@CanActivate(AllHooksParentCmp.canActivate)
class AllHooksParentCmp implements CanDeactivate, OnDeactivate, OnActivate {
canDeactivate(next: Instruction, prev: Instruction) {
@ -804,17 +809,17 @@ class ReuseHooksCmp implements OnActivate, OnReuse, OnDeactivate, CanReuse, CanD
@Component({selector: 'lifecycle-cmp'})
@View({template: `<router-outlet></router-outlet>`, directives: [RouterOutlet]})
@RouteConfig([
{path: '/a', component: A},
{path: '/on-activate', component: ActivateCmp},
{path: '/parent-activate/...', component: ParentActivateCmp},
{path: '/on-deactivate', component: DeactivateCmp},
{path: '/parent-deactivate/...', component: ParentDeactivateCmp},
{path: '/on-reuse/:number/...', component: ReuseCmp},
{path: '/never-reuse/:number/...', component: NeverReuseCmp},
{path: '/can-activate/...', component: CanActivateCmp},
{path: '/can-deactivate/...', component: CanDeactivateCmp},
{path: '/activation-hooks/...', component: AllHooksParentCmp},
{path: '/reuse-hooks/:number', component: ReuseHooksCmp}
new Route({path: '/a', component: A}),
new Route({path: '/on-activate', component: ActivateCmp}),
new Route({path: '/parent-activate/...', component: ParentActivateCmp}),
new Route({path: '/on-deactivate', component: DeactivateCmp}),
new Route({path: '/parent-deactivate/...', component: ParentDeactivateCmp}),
new Route({path: '/on-reuse/:number/...', component: ReuseCmp}),
new Route({path: '/never-reuse/:number/...', component: NeverReuseCmp}),
new Route({path: '/can-activate/...', component: CanActivateCmp}),
new Route({path: '/can-deactivate/...', component: CanDeactivateCmp}),
new Route({path: '/activation-hooks/...', component: AllHooksParentCmp}),
new Route({path: '/reuse-hooks/:number', component: ReuseHooksCmp})
])
class LifecycleCmp {
}

View File

@ -0,0 +1,9 @@
library angular2.test.router.route_config_spec;
/**
* This is intentionally left blank. `route_config_spec.ts` contains tests specific
* to using untyped objects for users writing idiomatic ES5 or TS.
* The rest of the router tests have typed annotations, so there's no need to add
* additional tests here for Dart.
*/
main () {}

View File

@ -0,0 +1,151 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xdescribe,
xit,
} from 'angular2/test_lib';
import {bootstrap} from 'angular2/core';
import {Component, Directive, View} from 'angular2/annotations';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {bind} from 'angular2/di';
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {
routerInjectables,
Router,
RouteConfig,
appBaseHrefToken,
routerDirectives
} from 'angular2/router';
import {LocationStrategy} from 'angular2/src/router/location_strategy';
import {MockLocationStrategy} from 'angular2/src/mock/mock_location_strategy';
export function main() {
describe('RouteConfig with POJO arguments', () => {
var fakeDoc, el, testBindings;
beforeEach(() => {
fakeDoc = DOM.createHtmlDocument();
el = DOM.createElement('app-cmp', fakeDoc);
DOM.appendChild(fakeDoc.body, el);
testBindings = [
routerInjectables,
bind(LocationStrategy).toClass(MockLocationStrategy),
bind(DOCUMENT_TOKEN).toValue(fakeDoc)
];
});
it('should bootstrap an app with a hierarchy', inject([AsyncTestCompleter], (async) => {
bootstrap(HierarchyAppCmp, testBindings)
.then((applicationRef) => {
var router = applicationRef.hostComponent.router;
router.subscribe((_) => {
expect(el).toHaveText('root { parent { hello } }');
expect(applicationRef.hostComponent.location.path()).toEqual('/parent/child');
async.done();
});
router.navigate('/parent/child');
});
}));
it('should work in an app with redirects', inject([AsyncTestCompleter], (async) => {
bootstrap(RedirectAppCmp, testBindings)
.then((applicationRef) => {
var router = applicationRef.hostComponent.router;
router.subscribe((_) => {
expect(el).toHaveText('root { hello }');
expect(applicationRef.hostComponent.location.path()).toEqual('/after');
async.done();
});
router.navigate('/before');
});
}));
it('should work in an app with async components', inject([AsyncTestCompleter], (async) => {
bootstrap(AsyncAppCmp, testBindings)
.then((applicationRef) => {
var router = applicationRef.hostComponent.router;
router.subscribe((_) => {
expect(el).toHaveText('root { hello }');
expect(applicationRef.hostComponent.location.path()).toEqual('/hello');
async.done();
});
router.navigate('/hello');
});
}));
it('should work in an app with a constructor component',
inject([AsyncTestCompleter], (async) => {
bootstrap(ExplicitConstructorAppCmp, testBindings)
.then((applicationRef) => {
var router = applicationRef.hostComponent.router;
router.subscribe((_) => {
expect(el).toHaveText('root { hello }');
expect(applicationRef.hostComponent.location.path()).toEqual('/hello');
async.done();
});
router.navigate('/hello');
});
}));
// TODO: test apps with wrong configs
});
}
@Component({selector: 'hello-cmp'})
@View({template: 'hello'})
class HelloCmp {
}
@Component({selector: 'app-cmp'})
@View({template: `root { <router-outlet></router-outlet> }`, directives: routerDirectives})
@RouteConfig([{path: '/before', redirectTo: '/after'}, {path: '/after', component: HelloCmp}])
class RedirectAppCmp {
constructor(public router: Router, public location: LocationStrategy) {}
}
function HelloLoader(): Promise<any> {
return Promise.resolve(HelloCmp);
}
@Component({selector: 'app-cmp'})
@View({template: `root { <router-outlet></router-outlet> }`, directives: routerDirectives})
@RouteConfig([
{path: '/hello', component: {type: 'loader', loader: HelloLoader}},
])
class AsyncAppCmp {
constructor(public router: Router, public location: LocationStrategy) {}
}
@Component({selector: 'app-cmp'})
@View({template: `root { <router-outlet></router-outlet> }`, directives: routerDirectives})
@RouteConfig([
{path: '/hello', component: {type: 'constructor', constructor: HelloCmp}},
])
class ExplicitConstructorAppCmp {
constructor(public router: Router, public location: LocationStrategy) {}
}
@Component({selector: 'parent-cmp'})
@View({template: `parent { <router-outlet></router-outlet> }`, directives: routerDirectives})
@RouteConfig([{path: '/child', component: HelloCmp}])
class ParentCmp {
}
@Component({selector: 'app-cmp'})
@View({template: `root { <router-outlet></router-outlet> }`, directives: routerDirectives})
@RouteConfig([{path: '/parent/...', component: ParentCmp}])
class HierarchyAppCmp {
constructor(public router: Router, public location: LocationStrategy) {}
}

View File

@ -14,88 +14,88 @@ import {Map, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
import {RouteRecognizer, RouteMatch} from 'angular2/src/router/route_recognizer';
import {Route, Redirect} from 'angular2/src/router/route_config_decorator';
export function main() {
describe('RouteRecognizer', () => {
var recognizer;
var handler = {'component': DummyCmpA};
var handler2 = {'component': DummyCmpB};
beforeEach(() => { recognizer = new RouteRecognizer(); });
it('should recognize a static segment', () => {
recognizer.addConfig('/test', handler);
recognizer.config(new Route({path: '/test', component: DummyCmpA}));
var solution = recognizer.recognize('/test')[0];
expect(getComponentType(solution)).toEqual(handler['component']);
expect(getComponentType(solution)).toEqual(DummyCmpA);
});
it('should recognize a single slash', () => {
recognizer.addConfig('/', handler);
recognizer.config(new Route({path: '/', component: DummyCmpA}));
var solution = recognizer.recognize('/')[0];
expect(getComponentType(solution)).toEqual(handler['component']);
expect(getComponentType(solution)).toEqual(DummyCmpA);
});
it('should recognize a dynamic segment', () => {
recognizer.addConfig('/user/:name', handler);
recognizer.config(new Route({path: '/user/:name', component: DummyCmpA}));
var solution = recognizer.recognize('/user/brian')[0];
expect(getComponentType(solution)).toEqual(handler['component']);
expect(getComponentType(solution)).toEqual(DummyCmpA);
expect(solution.params()).toEqual({'name': 'brian'});
});
it('should recognize a star segment', () => {
recognizer.addConfig('/first/*rest', handler);
recognizer.config(new Route({path: '/first/*rest', component: DummyCmpA}));
var solution = recognizer.recognize('/first/second/third')[0];
expect(getComponentType(solution)).toEqual(handler['component']);
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.addConfig('/hello', handler);
expect(() => recognizer.addConfig('/hello', handler2))
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.addConfig('/hello/:person/how/:doyoudou', handler);
expect(() => recognizer.addConfig('/hello/:friend/how/:areyou', handler2))
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.addRedirect('/a', '/b');
recognizer.addConfig('/b', handler);
recognizer.config(new Redirect({path: '/a', redirectTo: '/b'}));
recognizer.config(new Route({path: '/b', component: DummyCmpA}));
var solutions = recognizer.recognize('/a');
expect(solutions.length).toBe(1);
var solution = solutions[0];
expect(getComponentType(solution)).toEqual(handler['component']);
expect(getComponentType(solution)).toEqual(DummyCmpA);
expect(solution.matchedUrl).toEqual('/b');
});
it('should not perform root URL redirect on a non-root route', () => {
recognizer.addRedirect('/', '/foo');
recognizer.addConfig('/bar', handler);
recognizer.config(new Redirect({path: '/', redirectTo: '/foo'}));
recognizer.config(new Route({path: '/bar', component: DummyCmpA}));
var solutions = recognizer.recognize('/bar');
expect(solutions.length).toBe(1);
var solution = solutions[0];
expect(getComponentType(solution)).toEqual(handler['component']);
expect(getComponentType(solution)).toEqual(DummyCmpA);
expect(solution.matchedUrl).toEqual('/bar');
});
it('should perform a root URL redirect when only a slash or an empty string is being processed',
() => {
recognizer.addRedirect('/', '/matias');
recognizer.addConfig('/matias', handler);
recognizer.addConfig('/fatias', handler);
recognizer.config(new Redirect({path: '/', redirectTo: '/matias'}));
recognizer.config(new Route({path: '/matias', component: DummyCmpA}));
recognizer.config(new Route({path: '/fatias', component: DummyCmpA}));
var solutions;
@ -110,17 +110,17 @@ export function main() {
});
it('should generate URLs with params', () => {
recognizer.addConfig('/app/user/:name', handler, 'user');
recognizer.config(new Route({path: '/app/user/:name', component: DummyCmpA, as: 'user'}));
expect(recognizer.generate('user', {'name': 'misko'})['url']).toEqual('app/user/misko');
});
it('should generate URLs with numeric params', () => {
recognizer.addConfig('/app/page/:number', handler, 'page');
recognizer.config(new Route({path: '/app/page/:number', component: DummyCmpA, as: 'page'}));
expect(recognizer.generate('page', {'number': 42})['url']).toEqual('app/page/42');
});
it('should throw in the absence of required params URLs', () => {
recognizer.addConfig('app/user/:name', handler, 'user');
recognizer.config(new Route({path: 'app/user/:name', component: DummyCmpA, as: 'user'}));
expect(() => recognizer.generate('user', {})['url'])
.toThrowError('Route generator for \'name\' was not included in parameters passed.');
});
@ -128,7 +128,7 @@ export function main() {
describe('matrix params', () => {
it('should recognize matrix parameters within the URL path', () => {
var recognizer = new RouteRecognizer();
recognizer.addConfig('profile/:name', handler, 'user');
recognizer.config(new Route({path: 'profile/:name', component: DummyCmpA, as: 'user'}));
var solution = recognizer.recognize('/profile/matsko;comments=all')[0];
var params = solution.params();
@ -139,7 +139,7 @@ export function main() {
it('should recognize multiple matrix params and set parameters that contain no value to true',
() => {
var recognizer = new RouteRecognizer();
recognizer.addConfig('/profile/hello', handler, 'user');
recognizer.config(new Route({path: '/profile/hello', component: DummyCmpA, as: 'user'}));
var solution =
recognizer.recognize('/profile/hello;modal;showAll=true;hideAll=false')[0];
@ -152,7 +152,7 @@ export function main() {
it('should only consider the matrix parameters at the end of the path handler', () => {
var recognizer = new RouteRecognizer();
recognizer.addConfig('/profile/hi/:name', handler, 'user');
recognizer.config(new Route({path: '/profile/hi/:name', component: DummyCmpA, as: 'user'}));
var solution = recognizer.recognize('/profile;a=1/hi;b=2;c=3/william;d=4')[0];
var params = solution.params();
@ -162,7 +162,8 @@ export function main() {
it('should generate and populate the given static-based route with matrix params', () => {
var recognizer = new RouteRecognizer();
recognizer.addConfig('forum/featured', handler, 'forum-page');
recognizer.config(
new Route({path: 'forum/featured', component: DummyCmpA, as: 'forum-page'}));
var params = StringMapWrapper.create();
params['start'] = 10;
@ -174,7 +175,8 @@ export function main() {
it('should generate and populate the given dynamic-based route with matrix params', () => {
var recognizer = new RouteRecognizer();
recognizer.addConfig('forum/:topic', handler, 'forum-page');
recognizer.config(
new Route({path: 'forum/:topic', component: DummyCmpA, as: 'forum-page'}));
var params = StringMapWrapper.create();
params['topic'] = 'crazy';
@ -188,7 +190,8 @@ export function main() {
it('should not apply any matrix params if a dynamic route segment takes up the slot when a path is generated',
() => {
var recognizer = new RouteRecognizer();
recognizer.addConfig('hello/:name', handler, 'profile-page');
recognizer.config(
new Route({path: 'hello/:name', component: DummyCmpA, as: 'profile-page'}));
var params = StringMapWrapper.create();
params['name'] = 'matsko';

View File

@ -13,7 +13,7 @@ import {
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {RouteRegistry} from 'angular2/src/router/route_registry';
import {RouteConfig} from 'angular2/src/router/route_config_decorator';
import {RouteConfig, Route, AsyncRoute} from 'angular2/src/router/route_config_decorator';
export function main() {
describe('RouteRegistry', () => {
@ -22,8 +22,8 @@ export function main() {
beforeEach(() => { registry = new RouteRegistry(); });
it('should match the full URL', inject([AsyncTestCompleter], (async) => {
registry.config(RootHostCmp, {'path': '/', 'component': DummyCmpA});
registry.config(RootHostCmp, {'path': '/test', 'component': DummyCmpB});
registry.config(RootHostCmp, new Route({path: '/', component: DummyCmpA}));
registry.config(RootHostCmp, new Route({path: '/test', component: DummyCmpB}));
registry.recognize('/test', RootHostCmp)
.then((instruction) => {
@ -34,7 +34,7 @@ export function main() {
it('should generate URLs starting at the given component', () => {
registry.config(RootHostCmp,
{'path': '/first/...', 'component': DummyParentCmp, 'as': 'firstCmp'});
new Route({path: '/first/...', component: DummyParentCmp, as: 'firstCmp'}));
expect(registry.generate(['firstCmp', 'secondCmp'], RootHostCmp)).toEqual('first/second');
expect(registry.generate(['secondCmp'], DummyParentCmp)).toEqual('second');
@ -43,7 +43,7 @@ export function main() {
it('should generate URLs with params', () => {
registry.config(
RootHostCmp,
{'path': '/first/:param/...', 'component': DummyParentParamCmp, 'as': 'firstCmp'});
new Route({path: '/first/:param/...', component: DummyParentParamCmp, as: 'firstCmp'}));
var url =
registry.generate(['firstCmp', {param: 'one'}, 'secondCmp', {param: 'two'}], RootHostCmp);
@ -52,11 +52,9 @@ export function main() {
it('should generate URLs of loaded components after they are loaded',
inject([AsyncTestCompleter], (async) => {
registry.config(RootHostCmp, {
'path': '/first/...',
'component': {'type': 'loader', 'loader': AsyncParentLoader},
'as': 'firstCmp'
});
registry.config(
RootHostCmp,
new AsyncRoute({path: '/first/...', loader: AsyncParentLoader, as: 'firstCmp'}));
expect(() => registry.generate(['firstCmp', 'secondCmp'], RootHostCmp))
.toThrowError('Could not find route named "secondCmp".');
@ -77,8 +75,8 @@ export function main() {
it('should prefer static segments to dynamic', inject([AsyncTestCompleter], (async) => {
registry.config(RootHostCmp, {'path': '/:site', 'component': DummyCmpB});
registry.config(RootHostCmp, {'path': '/home', 'component': DummyCmpA});
registry.config(RootHostCmp, new Route({path: '/:site', component: DummyCmpB}));
registry.config(RootHostCmp, new Route({path: '/home', component: DummyCmpA}));
registry.recognize('/home', RootHostCmp)
.then((instruction) => {
@ -88,8 +86,8 @@ export function main() {
}));
it('should prefer dynamic segments to star', inject([AsyncTestCompleter], (async) => {
registry.config(RootHostCmp, {'path': '/:site', 'component': DummyCmpA});
registry.config(RootHostCmp, {'path': '/*site', 'component': DummyCmpB});
registry.config(RootHostCmp, new Route({path: '/:site', component: DummyCmpA}));
registry.config(RootHostCmp, new Route({path: '/*site', component: DummyCmpB}));
registry.recognize('/home', RootHostCmp)
.then((instruction) => {
@ -99,8 +97,8 @@ export function main() {
}));
it('should prefer routes with more dynamic segments', inject([AsyncTestCompleter], (async) => {
registry.config(RootHostCmp, {'path': '/:first/*rest', 'component': DummyCmpA});
registry.config(RootHostCmp, {'path': '/*all', 'component': DummyCmpB});
registry.config(RootHostCmp, new Route({path: '/:first/*rest', component: DummyCmpA}));
registry.config(RootHostCmp, new Route({path: '/*all', component: DummyCmpB}));
registry.recognize('/some/path', RootHostCmp)
.then((instruction) => {
@ -110,8 +108,8 @@ export function main() {
}));
it('should prefer routes with more static segments', inject([AsyncTestCompleter], (async) => {
registry.config(RootHostCmp, {'path': '/first/:second', 'component': DummyCmpA});
registry.config(RootHostCmp, {'path': '/:first/:second', 'component': DummyCmpB});
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)
.then((instruction) => {
@ -122,8 +120,10 @@ export function main() {
it('should prefer routes with static segments before dynamic segments',
inject([AsyncTestCompleter], (async) => {
registry.config(RootHostCmp, {'path': '/first/second/:third', 'component': DummyCmpB});
registry.config(RootHostCmp, {'path': '/first/:second/third', 'component': DummyCmpA});
registry.config(RootHostCmp,
new Route({path: '/first/second/:third', component: DummyCmpB}));
registry.config(RootHostCmp,
new Route({path: '/first/:second/third', component: DummyCmpA}));
registry.recognize('/first/second/third', RootHostCmp)
.then((instruction) => {
@ -133,7 +133,7 @@ export function main() {
}));
it('should match the full URL using child components', inject([AsyncTestCompleter], (async) => {
registry.config(RootHostCmp, {'path': '/first/...', 'component': DummyParentCmp});
registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyParentCmp}));
registry.recognize('/first/second', RootHostCmp)
.then((instruction) => {
@ -145,7 +145,7 @@ export function main() {
it('should match the URL using async child components',
inject([AsyncTestCompleter], (async) => {
registry.config(RootHostCmp, {'path': '/first/...', 'component': DummyAsyncCmp});
registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyAsyncCmp}));
registry.recognize('/first/second', RootHostCmp)
.then((instruction) => {
@ -157,9 +157,8 @@ export function main() {
it('should match the URL using an async parent component',
inject([AsyncTestCompleter], (async) => {
registry.config(
RootHostCmp,
{'path': '/first/...', 'component': {'loader': AsyncParentLoader, 'type': 'loader'}});
registry.config(RootHostCmp,
new AsyncRoute({path: '/first/...', loader: AsyncParentLoader}));
registry.recognize('/first/second', RootHostCmp)
.then((instruction) => {
@ -169,31 +168,36 @@ export function main() {
});
}));
it('should throw when a config does not have a component or redirectTo property', () => {
expect(() => registry.config(RootHostCmp, {'path': '/some/path'}))
.toThrowError(
'Route config should contain exactly one \'component\', or \'redirectTo\' property');
});
it('should throw when a config has an invalid component type', () => {
expect(() => registry.config(
RootHostCmp,
{'path': '/some/path', 'component': {'type': 'intentionallyWrongComponentType'}}))
.toThrowError('Invalid component type \'intentionallyWrongComponentType\'');
});
// TODO: not sure what to do with these tests
// it('should throw when a config does not have a component or redirectTo property', () => {
// expect(() => registry.config(rootHostComponent, {'path': '/some/path'}))
// .toThrowError(
// 'Route config should contain exactly one \'component\', or \'redirectTo\'
// property');
//});
//
// it('should throw when a config has an invalid component type', () => {
// expect(() => registry.config(
// rootHostComponent,
// {'path': '/some/path', 'component': {'type':
// 'intentionallyWrongComponentType'}}))
// .toThrowError('Invalid component type \'intentionallyWrongComponentType\'');
//});
it('should throw when a parent config is missing the `...` suffix any of its children add routes',
() => {
expect(() => registry.config(RootHostCmp, {'path': '/', 'component': DummyParentCmp}))
expect(() =>
registry.config(RootHostCmp, new Route({path: '/', component: DummyParentCmp})))
.toThrowError(
'Child routes are not allowed for "/". Use "..." on the parent\'s route path.');
});
it('should throw when a parent config uses `...` suffix before the end of the route', () => {
expect(() => registry.config(RootHostCmp,
{'path': '/home/.../fun/', 'component': DummyParentCmp}))
new Route({path: '/home/.../fun/', component: DummyParentCmp})))
.toThrowError('Unexpected "..." before the end of the path for "home/.../fun/".');
});
});
}
@ -207,18 +211,18 @@ function AsyncChildLoader() {
class RootHostCmp {}
@RouteConfig([{'path': '/second', 'component': {'loader': AsyncChildLoader, 'type': 'loader'}}])
@RouteConfig([new AsyncRoute({path: '/second', loader: AsyncChildLoader})])
class DummyAsyncCmp {
}
class DummyCmpA {}
class DummyCmpB {}
@RouteConfig([{'path': '/second', 'component': DummyCmpB, 'as': 'secondCmp'}])
@RouteConfig([new Route({path: '/second', component: DummyCmpB, as: 'secondCmp'})])
class DummyParentCmp {
}
@RouteConfig([{'path': '/second/:param', 'component': DummyCmpB, 'as': 'secondCmp'}])
@RouteConfig([new Route({path: '/second/:param', component: DummyCmpB, as: 'secondCmp'})])
class DummyParentParamCmp {
}

View File

@ -16,7 +16,7 @@ import {Component, Directive, View} from 'angular2/src/core/annotations/decorato
import {DOM} from 'angular2/src/dom/dom_adapter';
import {bind} from 'angular2/di';
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {RouteConfig} from 'angular2/src/router/route_config_decorator';
import {RouteConfig, Route, Redirect} from 'angular2/src/router/route_config_decorator';
import {PromiseWrapper} from 'angular2/src/facade/async';
import {BaseException} from 'angular2/src/facade/lang';
import {routerInjectables, Router, appBaseHrefToken, routerDirectives} from 'angular2/router';
@ -101,20 +101,20 @@ class HelloCmp {
@Component({selector: 'app-cmp'})
@View({template: "outer { <router-outlet></router-outlet> }", directives: routerDirectives})
@RouteConfig([{path: '/', component: HelloCmp}])
@RouteConfig([new Route({path: '/', component: HelloCmp})])
class AppCmp {
constructor(public router: Router, public location: LocationStrategy) {}
}
@Component({selector: 'parent-cmp'})
@View({template: `parent { <router-outlet></router-outlet> }`, directives: routerDirectives})
@RouteConfig([{path: '/child', component: HelloCmp}])
@RouteConfig([new Route({path: '/child', component: HelloCmp})])
class ParentCmp {
}
@Component({selector: 'app-cmp'})
@View({template: `root { <router-outlet></router-outlet> }`, directives: routerDirectives})
@RouteConfig([{path: '/parent/...', component: ParentCmp}])
@RouteConfig([new Route({path: '/parent/...', component: ParentCmp})])
class HierarchyAppCmp {
constructor(public router: Router, public location: LocationStrategy) {}
}
@ -126,8 +126,8 @@ class BrokenCmp {
}
@Component({selector: 'app-cmp'})
@View({template: "outer { <router-outlet></router-outlet> }", directives: routerDirectives})
@RouteConfig([{path: '/cause-error', component: BrokenCmp}])
@View({template: `outer { <router-outlet></router-outlet> }`, directives: routerDirectives})
@RouteConfig([new Route({path: '/cause-error', component: BrokenCmp})])
class BrokenAppCmp {
constructor(public router: Router, public location: LocationStrategy) {}
}

View File

@ -22,7 +22,7 @@ import {SpyLocation} from 'angular2/src/mock/location_mock';
import {Location} from 'angular2/src/router/location';
import {RouteRegistry} from 'angular2/src/router/route_registry';
import {RouteConfig} from 'angular2/src/router/route_config_decorator';
import {RouteConfig, Route} from 'angular2/src/router/route_config_decorator';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {bind} from 'angular2/di';
@ -52,7 +52,7 @@ export function main() {
it('should navigate based on the initial URL state', inject([AsyncTestCompleter], (async) => {
var outlet = makeDummyOutlet();
router.config({'path': '/', 'component': DummyComponent})
router.config([new Route({path: '/', component: DummyComponent})])
.then((_) => router.registerOutlet(outlet))
.then((_) => {
expect(outlet.spy('commit')).toHaveBeenCalled();
@ -67,7 +67,7 @@ export function main() {
var outlet = makeDummyOutlet();
router.registerOutlet(outlet)
.then((_) => router.config({'path': '/a', 'component': DummyComponent}))
.then((_) => router.config([new Route({path: '/a', component: DummyComponent})]))
.then((_) => router.navigate('/a'))
.then((_) => {
expect(outlet.spy('commit')).toHaveBeenCalled();
@ -84,7 +84,7 @@ export function main() {
.then((_) => router.navigate('/a'))
.then((_) => {
expect(outlet.spy('commit')).not.toHaveBeenCalled();
return router.config({'path': '/a', 'component': DummyComponent});
return router.config([new Route({path: '/a', component: DummyComponent})]);
})
.then((_) => {
expect(outlet.spy('commit')).toHaveBeenCalled();
@ -109,7 +109,7 @@ export function main() {
it('should generate URLs from the root component when the path starts with /', () => {
router.config({'path': '/first/...', 'component': DummyParentComp, 'as': 'firstCmp'});
router.config([new Route({path: '/first/...', component: DummyParentComp, as: 'firstCmp'})]);
expect(router.generate(['/firstCmp', 'secondCmp'])).toEqual('/first/second');
expect(router.generate(['/firstCmp', 'secondCmp'])).toEqual('/first/second');
@ -118,7 +118,8 @@ export function main() {
describe('matrix params', () => {
it('should apply inline matrix params for each router path within the generated URL', () => {
router.config({'path': '/first/...', 'component': DummyParentComp, 'as': 'firstCmp'});
router.config(
[new Route({path: '/first/...', component: DummyParentComp, as: 'firstCmp'})]);
var path =
router.generate(['/firstCmp', {'key': 'value'}, 'secondCmp', {'project': 'angular'}]);
@ -127,8 +128,9 @@ export function main() {
it('should apply inline matrix params for each router path within the generated URL and also include named params',
() => {
router.config(
{'path': '/first/:token/...', 'component': DummyParentComp, 'as': 'firstCmp'});
router.config([
new Route({path: '/first/:token/...', component: DummyParentComp, as: 'firstCmp'})
]);
var path =
router.generate(['/firstCmp', {'token': 'min'}, 'secondCmp', {'author': 'max'}]);
@ -146,7 +148,7 @@ class DummyOutlet extends SpyObject {
class DummyComponent {}
@RouteConfig([{'path': '/second', 'component': DummyComponent, 'as': 'secondCmp'}])
@RouteConfig([new Route({path: '/second', component: DummyComponent, as: 'secondCmp'})])
class DummyParentComp {
}