feat(router): add interfaces for route definitions in RouteConfig
Closes #2261
This commit is contained in:
parent
61c73576c8
commit
4d28167bc0
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
library angular2.src.router.route_config_normalizer;
|
||||
|
||||
import "route_config_decorator.dart";
|
||||
|
||||
RouteDefinition normalizeRouteConfig(RouteDefinition config) {
|
||||
return config;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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});
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -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 () {}
|
|
@ -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) {}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue