diff --git a/modules/angular2/src/router/route_config_decorator.ts b/modules/angular2/src/router/route_config_decorator.ts index c2b9db5d69..c1b9261d62 100644 --- a/modules/angular2/src/router/route_config_decorator.ts +++ b/modules/angular2/src/router/route_config_decorator.ts @@ -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); diff --git a/modules/angular2/src/router/route_config_impl.ts b/modules/angular2/src/router/route_config_impl.ts index 1598576a98..175a288ef0 100644 --- a/modules/angular2/src/router/route_config_impl.ts +++ b/modules/angular2/src/router/route_config_impl.ts @@ -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>) {} + constructor(public configs: List) {} +} + + +@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; + } } diff --git a/modules/angular2/src/router/route_config_nomalizer.dart b/modules/angular2/src/router/route_config_nomalizer.dart new file mode 100644 index 0000000000..dc0e3597a1 --- /dev/null +++ b/modules/angular2/src/router/route_config_nomalizer.dart @@ -0,0 +1,7 @@ +library angular2.src.router.route_config_normalizer; + +import "route_config_decorator.dart"; + +RouteDefinition normalizeRouteConfig(RouteDefinition config) { + return config; +} diff --git a/modules/angular2/src/router/route_config_nomalizer.ts b/modules/angular2/src/router/route_config_nomalizer.ts new file mode 100644 index 0000000000..1b401e8a92 --- /dev/null +++ b/modules/angular2/src/router/route_config_nomalizer.ts @@ -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 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 = config.component; + if (componentDefinitionObject.type == 'constructor') { + return new Route({ + path: config.path, + component: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; +} diff --git a/modules/angular2/src/router/route_definition.dart b/modules/angular2/src/router/route_definition.dart new file mode 100644 index 0000000000..401e563f8e --- /dev/null +++ b/modules/angular2/src/router/route_definition.dart @@ -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}); +} diff --git a/modules/angular2/src/router/route_definition.ts b/modules/angular2/src/router/route_definition.ts new file mode 100644 index 0000000000..1fa46ffd25 --- /dev/null +++ b/modules/angular2/src/router/route_definition.ts @@ -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; +} diff --git a/modules/angular2/src/router/route_recognizer.ts b/modules/angular2/src/router/route_recognizer.ts index 0dd55dbc41..bb92271c9a 100644 --- a/modules/angular2/src/router/route_recognizer.ts +++ b/modules/angular2/src/router/route_recognizer.ts @@ -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 = new Map(); matchers: Map = 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; } diff --git a/modules/angular2/src/router/route_registry.ts b/modules/angular2/src/router/route_registry.ts index 7e35705f5f..bba7ec8726 100644 --- a/modules/angular2/src/router/route_registry.ts +++ b/modules/angular2/src/router/route_registry.ts @@ -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): 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): 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 { - 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 */ diff --git a/modules/angular2/src/router/router.ts b/modules/angular2/src/router/router.ts index dd81df9ac4..08c30268b0 100644 --- a/modules/angular2/src/router/router.ts +++ b/modules/angular2/src/router/router.ts @@ -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| List>): Promise { - if (isArray(config)) { - (>config) - .forEach((configObject) => { this.registry.config(this.hostComponent, configObject); }); - } else { - this.registry.config(this.hostComponent, config); - } + config(definitions: List): Promise { + definitions.forEach( + (routeDefinition) => { this.registry.config(this.hostComponent, routeDefinition); }); return this.renavigate(); } diff --git a/modules/angular2/test/router/outlet_spec.ts b/modules/angular2/test/router/outlet_spec.ts index 45726627b2..b5f66e6966 100644 --- a/modules/angular2/test/router/outlet_spec.ts +++ b/modules/angular2/test/router/outlet_spec.ts @@ -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 { }') - .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('') - .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('') - .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('{{name}}') - .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('') - .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('') - .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 { `, 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 { }", 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}} { }", 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 {}`, 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 { completer = PromiseWrapper.completer(); @@ -689,14 +694,14 @@ class WaitDeactivateCmp implements OnDeactivate { @Component({selector: 'parent-deactivate-cmp'}) @View({template: `parent {}`, 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 {}`, 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 {}`, 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 {}`, 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 {}`, 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: ``, 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: ``, 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 { } diff --git a/modules/angular2/test/router/route_config_spec.dart b/modules/angular2/test/router/route_config_spec.dart new file mode 100644 index 0000000000..2a5b778508 --- /dev/null +++ b/modules/angular2/test/router/route_config_spec.dart @@ -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 () {} diff --git a/modules/angular2/test/router/route_config_spec.ts b/modules/angular2/test/router/route_config_spec.ts new file mode 100644 index 0000000000..091f9614a9 --- /dev/null +++ b/modules/angular2/test/router/route_config_spec.ts @@ -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 { }`, directives: routerDirectives}) +@RouteConfig([{path: '/before', redirectTo: '/after'}, {path: '/after', component: HelloCmp}]) +class RedirectAppCmp { + constructor(public router: Router, public location: LocationStrategy) {} +} + +function HelloLoader(): Promise { + return Promise.resolve(HelloCmp); +} + +@Component({selector: 'app-cmp'}) +@View({template: `root { }`, 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 { }`, 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 { }`, directives: routerDirectives}) +@RouteConfig([{path: '/child', component: HelloCmp}]) +class ParentCmp { +} + +@Component({selector: 'app-cmp'}) +@View({template: `root { }`, directives: routerDirectives}) +@RouteConfig([{path: '/parent/...', component: ParentCmp}]) +class HierarchyAppCmp { + constructor(public router: Router, public location: LocationStrategy) {} +} diff --git a/modules/angular2/test/router/route_recognizer_spec.ts b/modules/angular2/test/router/route_recognizer_spec.ts index 44fb7631ad..9462f59ea8 100644 --- a/modules/angular2/test/router/route_recognizer_spec.ts +++ b/modules/angular2/test/router/route_recognizer_spec.ts @@ -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'; diff --git a/modules/angular2/test/router/route_registry_spec.ts b/modules/angular2/test/router/route_registry_spec.ts index 5156062a94..832f114408 100644 --- a/modules/angular2/test/router/route_registry_spec.ts +++ b/modules/angular2/test/router/route_registry_spec.ts @@ -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 { } diff --git a/modules/angular2/test/router/router_integration_spec.ts b/modules/angular2/test/router/router_integration_spec.ts index 8fa5d101be..d73ca619a5 100644 --- a/modules/angular2/test/router/router_integration_spec.ts +++ b/modules/angular2/test/router/router_integration_spec.ts @@ -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 { }", 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 { }`, directives: routerDirectives}) -@RouteConfig([{path: '/child', component: HelloCmp}]) +@RouteConfig([new Route({path: '/child', component: HelloCmp})]) class ParentCmp { } @Component({selector: 'app-cmp'}) @View({template: `root { }`, 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 { }", directives: routerDirectives}) -@RouteConfig([{path: '/cause-error', component: BrokenCmp}]) +@View({template: `outer { }`, directives: routerDirectives}) +@RouteConfig([new Route({path: '/cause-error', component: BrokenCmp})]) class BrokenAppCmp { constructor(public router: Router, public location: LocationStrategy) {} } diff --git a/modules/angular2/test/router/router_spec.ts b/modules/angular2/test/router/router_spec.ts index 54bd50e5b8..6c49a44653 100644 --- a/modules/angular2/test/router/router_spec.ts +++ b/modules/angular2/test/router/router_spec.ts @@ -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 { }