Revert "refactor(router): improve recognition and generation pipeline"
This reverts commit cf7292fcb1
.
This commit triggered an existing race condition in Google code. More work is needed on the Router to fix this condition before this refactor can land.
This commit is contained in:
5
modules/angular1_router/build.js
vendored
5
modules/angular1_router/build.js
vendored
@ -6,13 +6,12 @@ var ts = require('typescript');
|
|||||||
var files = [
|
var files = [
|
||||||
'lifecycle_annotations_impl.ts',
|
'lifecycle_annotations_impl.ts',
|
||||||
'url_parser.ts',
|
'url_parser.ts',
|
||||||
'route_recognizer.ts',
|
'path_recognizer.ts',
|
||||||
'route_config_impl.ts',
|
'route_config_impl.ts',
|
||||||
'async_route_handler.ts',
|
'async_route_handler.ts',
|
||||||
'sync_route_handler.ts',
|
'sync_route_handler.ts',
|
||||||
'component_recognizer.ts',
|
'route_recognizer.ts',
|
||||||
'instruction.ts',
|
'instruction.ts',
|
||||||
'path_recognizer.ts',
|
|
||||||
'route_config_nomalizer.ts',
|
'route_config_nomalizer.ts',
|
||||||
'route_lifecycle_reflector.ts',
|
'route_lifecycle_reflector.ts',
|
||||||
'route_registry.ts',
|
'route_registry.ts',
|
||||||
|
@ -173,10 +173,6 @@ var StringMapWrapper = {
|
|||||||
|
|
||||||
var List = Array;
|
var List = Array;
|
||||||
var ListWrapper = {
|
var ListWrapper = {
|
||||||
clear: function (l) {
|
|
||||||
l.length = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
create: function () {
|
create: function () {
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
|
@ -31,9 +31,7 @@ function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootSc
|
|||||||
// property in a route config
|
// property in a route config
|
||||||
exports.assertComponentExists = function () {};
|
exports.assertComponentExists = function () {};
|
||||||
|
|
||||||
angular.stringifyInstruction = function (instruction) {
|
angular.stringifyInstruction = exports.stringifyInstruction;
|
||||||
return instruction.toRootUrl();
|
|
||||||
};
|
|
||||||
|
|
||||||
var RouteRegistry = exports.RouteRegistry;
|
var RouteRegistry = exports.RouteRegistry;
|
||||||
var RootRouter = exports.RootRouter;
|
var RootRouter = exports.RootRouter;
|
||||||
|
4
modules/angular1_router/src/ng_route_shim.js
vendored
4
modules/angular1_router/src/ng_route_shim.js
vendored
@ -110,7 +110,7 @@
|
|||||||
routeMap[path] = routeCopy;
|
routeMap[path] = routeCopy;
|
||||||
|
|
||||||
if (route.redirectTo) {
|
if (route.redirectTo) {
|
||||||
routeDefinition.redirectTo = [routeMap[route.redirectTo].name];
|
routeDefinition.redirectTo = route.redirectTo;
|
||||||
} else {
|
} else {
|
||||||
if (routeCopy.controller && !routeCopy.controllerAs) {
|
if (routeCopy.controller && !routeCopy.controllerAs) {
|
||||||
console.warn('Route for "' + path + '" should use "controllerAs".');
|
console.warn('Route for "' + path + '" should use "controllerAs".');
|
||||||
@ -123,7 +123,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
routeDefinition.component = directiveName;
|
routeDefinition.component = directiveName;
|
||||||
routeDefinition.name = route.name || upperCase(directiveName);
|
routeDefinition.as = upperCase(directiveName);
|
||||||
|
|
||||||
var directiveController = routeCopy.controller;
|
var directiveController = routeCopy.controller;
|
||||||
|
|
||||||
|
@ -113,7 +113,8 @@ describe('navigation', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should work with recursive nested outlets', function () {
|
// TODO: fix this
|
||||||
|
xit('should work with recursive nested outlets', function () {
|
||||||
registerComponent('recurCmp', {
|
registerComponent('recurCmp', {
|
||||||
template: '<div>recur { <div ng-outlet></div> }</div>',
|
template: '<div>recur { <div ng-outlet></div> }</div>',
|
||||||
$routeConfig: [
|
$routeConfig: [
|
||||||
@ -151,8 +152,8 @@ describe('navigation', function () {
|
|||||||
compile('<div ng-outlet></div>');
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
$router.config([
|
$router.config([
|
||||||
{ path: '/', redirectTo: ['/User'] },
|
{ path: '/', redirectTo: '/user' },
|
||||||
{ path: '/user', component: 'userCmp', name: 'User' }
|
{ path: '/user', component: 'userCmp' }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$router.navigateByUrl('/');
|
$router.navigateByUrl('/');
|
||||||
@ -166,15 +167,16 @@ describe('navigation', function () {
|
|||||||
registerComponent('childRouter', {
|
registerComponent('childRouter', {
|
||||||
template: '<div>inner { <div ng-outlet></div> }</div>',
|
template: '<div>inner { <div ng-outlet></div> }</div>',
|
||||||
$routeConfig: [
|
$routeConfig: [
|
||||||
{ path: '/new-child', component: 'oneCmp', name: 'NewChild'},
|
{ path: '/old-child', redirectTo: '/new-child' },
|
||||||
{ path: '/new-child-two', component: 'twoCmp', name: 'NewChildTwo'}
|
{ path: '/new-child', component: 'oneCmp'},
|
||||||
|
{ path: '/old-child-two', redirectTo: '/new-child-two' },
|
||||||
|
{ path: '/new-child-two', component: 'twoCmp'}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
$router.config([
|
$router.config([
|
||||||
{ path: '/old-parent/old-child', redirectTo: ['/NewParent', 'NewChild'] },
|
{ path: '/old-parent', redirectTo: '/new-parent' },
|
||||||
{ path: '/old-parent/old-child-two', redirectTo: ['/NewParent', 'NewChildTwo'] },
|
{ path: '/new-parent/...', component: 'childRouter' }
|
||||||
{ path: '/new-parent/...', component: 'childRouter', name: 'NewParent' }
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
compile('<div ng-outlet></div>');
|
compile('<div ng-outlet></div>');
|
||||||
|
@ -139,12 +139,11 @@ describe('ngRoute shim', function () {
|
|||||||
|
|
||||||
it('should adapt routes with redirects', inject(function ($location) {
|
it('should adapt routes with redirects', inject(function ($location) {
|
||||||
$routeProvider
|
$routeProvider
|
||||||
.when('/home', {
|
|
||||||
template: 'welcome home!',
|
|
||||||
name: 'Home'
|
|
||||||
})
|
|
||||||
.when('/', {
|
.when('/', {
|
||||||
redirectTo: '/home'
|
redirectTo: '/home'
|
||||||
|
})
|
||||||
|
.when('/home', {
|
||||||
|
template: 'welcome home!'
|
||||||
});
|
});
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
|
import {RouteHandler} from './route_handler';
|
||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
import {isPresent, Type} from 'angular2/src/facade/lang';
|
import {isPresent, Type} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
import {RouteHandler} from './route_handler';
|
|
||||||
import {RouteData, BLANK_ROUTE_DATA} from './instruction';
|
|
||||||
|
|
||||||
|
|
||||||
export class AsyncRouteHandler implements RouteHandler {
|
export class AsyncRouteHandler implements RouteHandler {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_resolvedComponent: Promise<any> = null;
|
_resolvedComponent: Promise<any> = null;
|
||||||
componentType: Type;
|
componentType: Type;
|
||||||
public data: RouteData;
|
|
||||||
|
|
||||||
constructor(private _loader: Function, data: {[key: string]: any} = null) {
|
constructor(private _loader: Function, public data?: {[key: string]: any}) {}
|
||||||
this.data = isPresent(data) ? new RouteData(data) : BLANK_ROUTE_DATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveComponentType(): Promise<any> {
|
resolveComponentType(): Promise<any> {
|
||||||
if (isPresent(this._resolvedComponent)) {
|
if (isPresent(this._resolvedComponent)) {
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
|
||||||
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
|
||||||
import {Map, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
|
||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
|
||||||
|
|
||||||
import {
|
|
||||||
AbstractRecognizer,
|
|
||||||
RouteRecognizer,
|
|
||||||
RedirectRecognizer,
|
|
||||||
RouteMatch
|
|
||||||
} from './route_recognizer';
|
|
||||||
import {Route, AsyncRoute, AuxRoute, Redirect, RouteDefinition} from './route_config_impl';
|
|
||||||
import {AsyncRouteHandler} from './async_route_handler';
|
|
||||||
import {SyncRouteHandler} from './sync_route_handler';
|
|
||||||
import {Url} from './url_parser';
|
|
||||||
import {ComponentInstruction} from './instruction';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `ComponentRecognizer` is responsible for recognizing routes for a single component.
|
|
||||||
* It is consumed by `RouteRegistry`, which knows how to recognize an entire hierarchy of
|
|
||||||
* components.
|
|
||||||
*/
|
|
||||||
export class ComponentRecognizer {
|
|
||||||
names = new Map<string, RouteRecognizer>();
|
|
||||||
|
|
||||||
// map from name to recognizer
|
|
||||||
auxNames = new Map<string, RouteRecognizer>();
|
|
||||||
|
|
||||||
// map from starting path to recognizer
|
|
||||||
auxRoutes = new Map<string, RouteRecognizer>();
|
|
||||||
|
|
||||||
// TODO: optimize this into a trie
|
|
||||||
matchers: AbstractRecognizer[] = [];
|
|
||||||
|
|
||||||
defaultRoute: RouteRecognizer = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns whether or not the config is terminal
|
|
||||||
*/
|
|
||||||
config(config: RouteDefinition): boolean {
|
|
||||||
var handler;
|
|
||||||
|
|
||||||
if (isPresent(config.name) && config.name[0].toUpperCase() != config.name[0]) {
|
|
||||||
var suggestedName = config.name[0].toUpperCase() + config.name.substring(1);
|
|
||||||
throw new BaseException(
|
|
||||||
`Route "${config.path}" with name "${config.name}" does not begin with an uppercase letter. Route names should be CamelCase like "${suggestedName}".`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config instanceof AuxRoute) {
|
|
||||||
handler = new SyncRouteHandler(config.component, config.data);
|
|
||||||
let path = config.path.startsWith('/') ? config.path.substring(1) : config.path;
|
|
||||||
var recognizer = new RouteRecognizer(config.path, handler);
|
|
||||||
this.auxRoutes.set(path, recognizer);
|
|
||||||
if (isPresent(config.name)) {
|
|
||||||
this.auxNames.set(config.name, recognizer);
|
|
||||||
}
|
|
||||||
return recognizer.terminal;
|
|
||||||
}
|
|
||||||
|
|
||||||
var useAsDefault = false;
|
|
||||||
|
|
||||||
if (config instanceof Redirect) {
|
|
||||||
let redirector = new RedirectRecognizer(config.path, config.redirectTo);
|
|
||||||
this._assertNoHashCollision(redirector.hash, config.path);
|
|
||||||
this.matchers.push(redirector);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config instanceof Route) {
|
|
||||||
handler = new SyncRouteHandler(config.component, config.data);
|
|
||||||
useAsDefault = isPresent(config.useAsDefault) && config.useAsDefault;
|
|
||||||
} else if (config instanceof AsyncRoute) {
|
|
||||||
handler = new AsyncRouteHandler(config.loader, config.data);
|
|
||||||
useAsDefault = isPresent(config.useAsDefault) && config.useAsDefault;
|
|
||||||
}
|
|
||||||
var recognizer = new RouteRecognizer(config.path, handler);
|
|
||||||
|
|
||||||
this._assertNoHashCollision(recognizer.hash, config.path);
|
|
||||||
|
|
||||||
if (useAsDefault) {
|
|
||||||
if (isPresent(this.defaultRoute)) {
|
|
||||||
throw new BaseException(`Only one route can be default`);
|
|
||||||
}
|
|
||||||
this.defaultRoute = recognizer;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.matchers.push(recognizer);
|
|
||||||
if (isPresent(config.name)) {
|
|
||||||
this.names.set(config.name, recognizer);
|
|
||||||
}
|
|
||||||
return recognizer.terminal;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private _assertNoHashCollision(hash: string, path) {
|
|
||||||
this.matchers.forEach((matcher) => {
|
|
||||||
if (hash == matcher.hash) {
|
|
||||||
throw new BaseException(
|
|
||||||
`Configuration '${path}' conflicts with existing route '${matcher.path}'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a URL, returns a list of `RouteMatch`es, which are partial recognitions for some route.
|
|
||||||
*/
|
|
||||||
recognize(urlParse: Url): Promise<RouteMatch>[] {
|
|
||||||
var solutions = [];
|
|
||||||
|
|
||||||
this.matchers.forEach((routeRecognizer: AbstractRecognizer) => {
|
|
||||||
var pathMatch = routeRecognizer.recognize(urlParse);
|
|
||||||
|
|
||||||
if (isPresent(pathMatch)) {
|
|
||||||
solutions.push(pathMatch);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return solutions;
|
|
||||||
}
|
|
||||||
|
|
||||||
recognizeAuxiliary(urlParse: Url): Promise<RouteMatch>[] {
|
|
||||||
var routeRecognizer: RouteRecognizer = this.auxRoutes.get(urlParse.path);
|
|
||||||
if (isPresent(routeRecognizer)) {
|
|
||||||
return [routeRecognizer.recognize(urlParse)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [PromiseWrapper.resolve(null)];
|
|
||||||
}
|
|
||||||
|
|
||||||
hasRoute(name: string): boolean { return this.names.has(name); }
|
|
||||||
|
|
||||||
componentLoaded(name: string): boolean {
|
|
||||||
return this.hasRoute(name) && isPresent(this.names.get(name).handler.componentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadComponent(name: string): Promise<any> {
|
|
||||||
return this.names.get(name).handler.resolveComponentType();
|
|
||||||
}
|
|
||||||
|
|
||||||
generate(name: string, params: any): ComponentInstruction {
|
|
||||||
var pathRecognizer: RouteRecognizer = this.names.get(name);
|
|
||||||
if (isBlank(pathRecognizer)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return pathRecognizer.generate(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
generateAuxiliary(name: string, params: any): ComponentInstruction {
|
|
||||||
var pathRecognizer: RouteRecognizer = this.auxNames.get(name);
|
|
||||||
if (isBlank(pathRecognizer)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return pathRecognizer.generate(params);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,10 @@
|
|||||||
import {Map, MapWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
import {Map, MapWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
import {unimplemented} from 'angular2/src/facade/exceptions';
|
||||||
import {isPresent, isBlank, normalizeBlank, Type, CONST_EXPR} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, normalizeBlank, Type, CONST_EXPR} from 'angular2/src/facade/lang';
|
||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
import {Promise} from 'angular2/src/facade/async';
|
||||||
|
|
||||||
|
import {PathRecognizer} from './path_recognizer';
|
||||||
|
import {Url} from './url_parser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `RouteParams` is an immutable map of parameters for the given route
|
* `RouteParams` is an immutable map of parameters for the given route
|
||||||
@ -74,7 +77,7 @@ export class RouteData {
|
|||||||
get(key: string): any { return normalizeBlank(StringMapWrapper.get(this.data, key)); }
|
get(key: string): any { return normalizeBlank(StringMapWrapper.get(this.data, key)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
export var BLANK_ROUTE_DATA = new RouteData();
|
var BLANK_ROUTE_DATA = new RouteData();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `Instruction` is a tree of {@link ComponentInstruction}s with all the information needed
|
* `Instruction` is a tree of {@link ComponentInstruction}s with all the information needed
|
||||||
@ -103,184 +106,74 @@ export var BLANK_ROUTE_DATA = new RouteData();
|
|||||||
* bootstrap(AppCmp, ROUTER_PROVIDERS);
|
* bootstrap(AppCmp, ROUTER_PROVIDERS);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export abstract class Instruction {
|
export class Instruction {
|
||||||
public component: ComponentInstruction;
|
constructor(public component: ComponentInstruction, public child: Instruction,
|
||||||
public child: Instruction;
|
public auxInstruction: {[key: string]: Instruction}) {}
|
||||||
public auxInstruction: {[key: string]: Instruction} = {};
|
|
||||||
|
|
||||||
get urlPath(): string { return this.component.urlPath; }
|
|
||||||
|
|
||||||
get urlParams(): string[] { return this.component.urlParams; }
|
|
||||||
|
|
||||||
get specificity(): number {
|
|
||||||
var total = 0;
|
|
||||||
if (isPresent(this.component)) {
|
|
||||||
total += this.component.specificity;
|
|
||||||
}
|
|
||||||
if (isPresent(this.child)) {
|
|
||||||
total += this.child.specificity;
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract resolveComponent(): Promise<ComponentInstruction>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* converts the instruction into a URL string
|
|
||||||
*/
|
|
||||||
toRootUrl(): string { return this.toUrlPath() + this.toUrlQuery(); }
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_toNonRootUrl(): string {
|
|
||||||
return this._stringifyPathMatrixAuxPrefixed() +
|
|
||||||
(isPresent(this.child) ? this.child._toNonRootUrl() : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
toUrlQuery(): string { return this.urlParams.length > 0 ? ('?' + this.urlParams.join('&')) : ''; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new instruction that shares the state of the existing instruction, but with
|
* Returns a new instruction that shares the state of the existing instruction, but with
|
||||||
* the given child {@link Instruction} replacing the existing child.
|
* the given child {@link Instruction} replacing the existing child.
|
||||||
*/
|
*/
|
||||||
replaceChild(child: Instruction): Instruction {
|
replaceChild(child: Instruction): Instruction {
|
||||||
return new ResolvedInstruction(this.component, child, this.auxInstruction);
|
return new Instruction(this.component, child, this.auxInstruction);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the final URL for the instruction is ``
|
* Represents a partially completed instruction during recognition that only has the
|
||||||
*/
|
* primary (non-aux) route instructions matched.
|
||||||
toUrlPath(): string {
|
*
|
||||||
return this.urlPath + this._stringifyAux() +
|
* `PrimaryInstruction` is an internal class used by `RouteRecognizer` while it's
|
||||||
(isPresent(this.child) ? this.child._toNonRootUrl() : '');
|
* figuring out where to navigate.
|
||||||
|
*/
|
||||||
|
export class PrimaryInstruction {
|
||||||
|
constructor(public component: ComponentInstruction, public child: PrimaryInstruction,
|
||||||
|
public auxUrls: Url[]) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringifyInstruction(instruction: Instruction): string {
|
||||||
|
return stringifyInstructionPath(instruction) + stringifyInstructionQuery(instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringifyInstructionPath(instruction: Instruction): string {
|
||||||
|
return instruction.component.urlPath + stringifyAux(instruction) +
|
||||||
|
stringifyPrimaryPrefixed(instruction.child);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringifyInstructionQuery(instruction: Instruction): string {
|
||||||
|
return instruction.component.urlParams.length > 0 ?
|
||||||
|
('?' + instruction.component.urlParams.join('&')) :
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyPrimaryPrefixed(instruction: Instruction): string {
|
||||||
|
var primary = stringifyPrimary(instruction);
|
||||||
|
if (primary.length > 0) {
|
||||||
|
primary = '/' + primary;
|
||||||
}
|
}
|
||||||
|
return primary;
|
||||||
|
}
|
||||||
|
|
||||||
// default instructions override these
|
function stringifyPrimary(instruction: Instruction): string {
|
||||||
toLinkUrl(): string {
|
if (isBlank(instruction)) {
|
||||||
return this.urlPath + this._stringifyAux() +
|
|
||||||
(isPresent(this.child) ? this.child._toLinkUrl() : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is the non-root version (called recursively)
|
|
||||||
/** @internal */
|
|
||||||
_toLinkUrl(): string {
|
|
||||||
return this._stringifyPathMatrixAuxPrefixed() +
|
|
||||||
(isPresent(this.child) ? this.child._toLinkUrl() : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_stringifyPathMatrixAuxPrefixed(): string {
|
|
||||||
var primary = this._stringifyPathMatrixAux();
|
|
||||||
if (primary.length > 0) {
|
|
||||||
primary = '/' + primary;
|
|
||||||
}
|
|
||||||
return primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_stringifyMatrixParams(): string {
|
|
||||||
return this.urlParams.length > 0 ? (';' + this.component.urlParams.join(';')) : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_stringifyPathMatrixAux(): string {
|
|
||||||
if (isBlank(this.component)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return this.urlPath + this._stringifyMatrixParams() + this._stringifyAux();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_stringifyAux(): string {
|
|
||||||
var routes = [];
|
|
||||||
StringMapWrapper.forEach(this.auxInstruction, (auxInstruction, _) => {
|
|
||||||
routes.push(auxInstruction._stringifyPathMatrixAux());
|
|
||||||
});
|
|
||||||
if (routes.length > 0) {
|
|
||||||
return '(' + routes.join('//') + ')';
|
|
||||||
}
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
var params = instruction.component.urlParams.length > 0 ?
|
||||||
|
(';' + instruction.component.urlParams.join(';')) :
|
||||||
|
'';
|
||||||
|
return instruction.component.urlPath + params + stringifyAux(instruction) +
|
||||||
|
stringifyPrimaryPrefixed(instruction.child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stringifyAux(instruction: Instruction): string {
|
||||||
/**
|
var routes = [];
|
||||||
* a resolved instruction has an outlet instruction for itself, but maybe not for...
|
StringMapWrapper.forEach(instruction.auxInstruction, (auxInstruction, _) => {
|
||||||
*/
|
routes.push(stringifyPrimary(auxInstruction));
|
||||||
export class ResolvedInstruction extends Instruction {
|
});
|
||||||
constructor(public component: ComponentInstruction, public child: Instruction,
|
if (routes.length > 0) {
|
||||||
public auxInstruction: {[key: string]: Instruction}) {
|
return '(' + routes.join('//') + ')';
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveComponent(): Promise<ComponentInstruction> {
|
|
||||||
return PromiseWrapper.resolve(this.component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a resolved default route
|
|
||||||
*/
|
|
||||||
export class DefaultInstruction extends Instruction {
|
|
||||||
constructor(public component: ComponentInstruction, public child: DefaultInstruction) { super(); }
|
|
||||||
|
|
||||||
resolveComponent(): Promise<ComponentInstruction> {
|
|
||||||
return PromiseWrapper.resolve(this.component);
|
|
||||||
}
|
|
||||||
|
|
||||||
toLinkUrl(): string { return ''; }
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_toLinkUrl(): string { return ''; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a component that may need to do some redirection or lazy loading at a later time.
|
|
||||||
*/
|
|
||||||
export class UnresolvedInstruction extends Instruction {
|
|
||||||
constructor(private _resolver: () => Promise<Instruction>, private _urlPath: string = '',
|
|
||||||
private _urlParams: string[] = CONST_EXPR([])) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
get urlPath(): string {
|
|
||||||
if (isPresent(this.component)) {
|
|
||||||
return this.component.urlPath;
|
|
||||||
}
|
|
||||||
if (isPresent(this._urlPath)) {
|
|
||||||
return this._urlPath;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
get urlParams(): string[] {
|
|
||||||
if (isPresent(this.component)) {
|
|
||||||
return this.component.urlParams;
|
|
||||||
}
|
|
||||||
if (isPresent(this._urlParams)) {
|
|
||||||
return this._urlParams;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveComponent(): Promise<ComponentInstruction> {
|
|
||||||
if (isPresent(this.component)) {
|
|
||||||
return PromiseWrapper.resolve(this.component);
|
|
||||||
}
|
|
||||||
return this._resolver().then((resolution: Instruction) => {
|
|
||||||
this.child = resolution.child;
|
|
||||||
return this.component = resolution.component;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class RedirectInstruction extends ResolvedInstruction {
|
|
||||||
constructor(component: ComponentInstruction, child: Instruction,
|
|
||||||
auxInstruction: {[key: string]: Instruction}) {
|
|
||||||
super(component, child, auxInstruction);
|
|
||||||
}
|
}
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -292,18 +185,67 @@ export class RedirectInstruction extends ResolvedInstruction {
|
|||||||
* to route lifecycle hooks, like {@link CanActivate}.
|
* to route lifecycle hooks, like {@link CanActivate}.
|
||||||
*
|
*
|
||||||
* `ComponentInstruction`s are [https://en.wikipedia.org/wiki/Hash_consing](hash consed). You should
|
* `ComponentInstruction`s are [https://en.wikipedia.org/wiki/Hash_consing](hash consed). You should
|
||||||
* never construct one yourself with "new." Instead, rely on {@link Router/RouteRecognizer} to
|
* never construct one yourself with "new." Instead, rely on {@link Router/PathRecognizer} to
|
||||||
* construct `ComponentInstruction`s.
|
* construct `ComponentInstruction`s.
|
||||||
*
|
*
|
||||||
* You should not modify this object. It should be treated as immutable.
|
* You should not modify this object. It should be treated as immutable.
|
||||||
*/
|
*/
|
||||||
export class ComponentInstruction {
|
export abstract class ComponentInstruction {
|
||||||
reuse: boolean = false;
|
reuse: boolean = false;
|
||||||
public routeData: RouteData;
|
public urlPath: string;
|
||||||
|
public urlParams: string[];
|
||||||
|
public params: {[key: string]: any};
|
||||||
|
|
||||||
constructor(public urlPath: string, public urlParams: string[], data: RouteData,
|
/**
|
||||||
public componentType, public terminal: boolean, public specificity: number,
|
* Returns the component type of the represented route, or `null` if this instruction
|
||||||
public params: {[key: string]: any} = null) {
|
* hasn't been resolved.
|
||||||
this.routeData = isPresent(data) ? data : BLANK_ROUTE_DATA;
|
*/
|
||||||
}
|
get componentType() { return unimplemented(); };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a promise that will resolve to component type of the represented route.
|
||||||
|
* If this instruction references an {@link AsyncRoute}, the `loader` function of that route
|
||||||
|
* will run.
|
||||||
|
*/
|
||||||
|
abstract resolveComponentType(): Promise<Type>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the specificity of the route associated with this `Instruction`.
|
||||||
|
*/
|
||||||
|
get specificity() { return unimplemented(); };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if the component type of this instruction has no child {@link RouteConfig},
|
||||||
|
* or `false` if it does.
|
||||||
|
*/
|
||||||
|
get terminal() { return unimplemented(); };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the route data of the given route that was specified in the {@link RouteDefinition},
|
||||||
|
* or an empty object if no route data was specified.
|
||||||
|
*/
|
||||||
|
get routeData(): RouteData { return unimplemented(); };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ComponentInstruction_ extends ComponentInstruction {
|
||||||
|
private _routeData: RouteData;
|
||||||
|
|
||||||
|
constructor(urlPath: string, urlParams: string[], private _recognizer: PathRecognizer,
|
||||||
|
params: {[key: string]: any} = null) {
|
||||||
|
super();
|
||||||
|
this.urlPath = urlPath;
|
||||||
|
this.urlParams = urlParams;
|
||||||
|
this.params = params;
|
||||||
|
if (isPresent(this._recognizer.handler.data)) {
|
||||||
|
this._routeData = new RouteData(this._recognizer.handler.data);
|
||||||
|
} else {
|
||||||
|
this._routeData = BLANK_ROUTE_DATA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get componentType() { return this._recognizer.handler.componentType; }
|
||||||
|
resolveComponentType(): Promise<Type> { return this._recognizer.handler.resolveComponentType(); }
|
||||||
|
get specificity() { return this._recognizer.specificity; }
|
||||||
|
get terminal() { return this._recognizer.terminal; }
|
||||||
|
get routeData(): RouteData { return this._routeData; }
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,12 @@ import {
|
|||||||
isBlank
|
isBlank
|
||||||
} from 'angular2/src/facade/lang';
|
} from 'angular2/src/facade/lang';
|
||||||
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
||||||
|
|
||||||
import {Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
|
import {RouteHandler} from './route_handler';
|
||||||
import {Url, RootUrl, serializeParams} from './url_parser';
|
import {Url, RootUrl, serializeParams} from './url_parser';
|
||||||
|
import {ComponentInstruction, ComponentInstruction_} from './instruction';
|
||||||
|
|
||||||
class TouchMap {
|
class TouchMap {
|
||||||
map: {[key: string]: string} = {};
|
map: {[key: string]: string} = {};
|
||||||
@ -30,7 +33,7 @@ class TouchMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUnused(): {[key: string]: any} {
|
getUnused(): {[key: string]: any} {
|
||||||
var unused: {[key: string]: any} = {};
|
var unused: {[key: string]: any} = StringMapWrapper.create();
|
||||||
var keys = StringMapWrapper.keys(this.keys);
|
var keys = StringMapWrapper.keys(this.keys);
|
||||||
keys.forEach(key => unused[key] = StringMapWrapper.get(this.map, key));
|
keys.forEach(key => unused[key] = StringMapWrapper.get(this.map, key));
|
||||||
return unused;
|
return unused;
|
||||||
@ -123,6 +126,7 @@ function parsePathString(route: string): {[key: string]: any} {
|
|||||||
results.push(new StarSegment(match[1]));
|
results.push(new StarSegment(match[1]));
|
||||||
} else if (segment == '...') {
|
} else if (segment == '...') {
|
||||||
if (i < limit) {
|
if (i < limit) {
|
||||||
|
// TODO (matsko): setup a proper error here `
|
||||||
throw new BaseException(`Unexpected "..." before the end of the path for "${route}".`);
|
throw new BaseException(`Unexpected "..." before the end of the path for "${route}".`);
|
||||||
}
|
}
|
||||||
results.push(new ContinuationSegment());
|
results.push(new ContinuationSegment());
|
||||||
@ -171,17 +175,23 @@ function assertPath(path: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class PathMatch {
|
||||||
|
constructor(public instruction: ComponentInstruction, public remaining: Url,
|
||||||
|
public remainingAux: Url[]) {}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// represents something like '/foo/:bar'
|
||||||
* Parses a URL string using a given matcher DSL, and generates URLs from param maps
|
|
||||||
*/
|
|
||||||
export class PathRecognizer {
|
export class PathRecognizer {
|
||||||
private _segments: Segment[];
|
private _segments: Segment[];
|
||||||
specificity: number;
|
specificity: number;
|
||||||
terminal: boolean = true;
|
terminal: boolean = true;
|
||||||
hash: string;
|
hash: string;
|
||||||
|
private _cache: Map<string, ComponentInstruction> = new Map<string, ComponentInstruction>();
|
||||||
|
|
||||||
constructor(public path: string) {
|
|
||||||
|
// TODO: cache component instruction instances by params and by ParsedUrl instance
|
||||||
|
|
||||||
|
constructor(public path: string, public handler: RouteHandler) {
|
||||||
assertPath(path);
|
assertPath(path);
|
||||||
var parsed = parsePathString(path);
|
var parsed = parsePathString(path);
|
||||||
|
|
||||||
@ -193,7 +203,8 @@ export class PathRecognizer {
|
|||||||
this.terminal = !(lastSegment instanceof ContinuationSegment);
|
this.terminal = !(lastSegment instanceof ContinuationSegment);
|
||||||
}
|
}
|
||||||
|
|
||||||
recognize(beginningSegment: Url): {[key: string]: any} {
|
|
||||||
|
recognize(beginningSegment: Url): PathMatch {
|
||||||
var nextSegment = beginningSegment;
|
var nextSegment = beginningSegment;
|
||||||
var currentSegment: Url;
|
var currentSegment: Url;
|
||||||
var positionalParams = {};
|
var positionalParams = {};
|
||||||
@ -236,6 +247,7 @@ export class PathRecognizer {
|
|||||||
var urlPath = captured.join('/');
|
var urlPath = captured.join('/');
|
||||||
|
|
||||||
var auxiliary;
|
var auxiliary;
|
||||||
|
var instruction: ComponentInstruction;
|
||||||
var urlParams;
|
var urlParams;
|
||||||
var allParams;
|
var allParams;
|
||||||
if (isPresent(currentSegment)) {
|
if (isPresent(currentSegment)) {
|
||||||
@ -255,11 +267,12 @@ export class PathRecognizer {
|
|||||||
auxiliary = [];
|
auxiliary = [];
|
||||||
urlParams = [];
|
urlParams = [];
|
||||||
}
|
}
|
||||||
return {urlPath, urlParams, allParams, auxiliary, nextSegment};
|
instruction = this._getInstruction(urlPath, urlParams, this, allParams);
|
||||||
|
return new PathMatch(instruction, nextSegment, auxiliary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
generate(params: {[key: string]: any}): {[key: string]: any} {
|
generate(params: {[key: string]: any}): ComponentInstruction {
|
||||||
var paramTokens = new TouchMap(params);
|
var paramTokens = new TouchMap(params);
|
||||||
|
|
||||||
var path = [];
|
var path = [];
|
||||||
@ -275,6 +288,18 @@ export class PathRecognizer {
|
|||||||
var nonPositionalParams = paramTokens.getUnused();
|
var nonPositionalParams = paramTokens.getUnused();
|
||||||
var urlParams = serializeParams(nonPositionalParams);
|
var urlParams = serializeParams(nonPositionalParams);
|
||||||
|
|
||||||
return {urlPath, urlParams};
|
return this._getInstruction(urlPath, urlParams, this, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getInstruction(urlPath: string, urlParams: string[], _recognizer: PathRecognizer,
|
||||||
|
params: {[key: string]: any}): ComponentInstruction {
|
||||||
|
var hashKey = urlPath + '?' + urlParams.join('?');
|
||||||
|
if (this._cache.has(hashKey)) {
|
||||||
|
return this._cache.get(hashKey);
|
||||||
|
}
|
||||||
|
var instruction = new ComponentInstruction_(urlPath, urlParams, _recognizer, params);
|
||||||
|
this._cache.set(hashKey, instruction);
|
||||||
|
|
||||||
|
return instruction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,6 @@ export class RouteConfig {
|
|||||||
* - `name` is an optional `CamelCase` string representing the name of the route.
|
* - `name` is an optional `CamelCase` string representing the name of the route.
|
||||||
* - `data` is an optional property of any type representing arbitrary route metadata for the given
|
* - `data` is an optional property of any type representing arbitrary route metadata for the given
|
||||||
* route. It is injectable via {@link RouteData}.
|
* route. It is injectable via {@link RouteData}.
|
||||||
* - `useAsDefault` is a boolean value. If `true`, the child route will be navigated to if no child
|
|
||||||
* route is specified during the navigation.
|
|
||||||
*
|
*
|
||||||
* ### Example
|
* ### Example
|
||||||
* ```
|
* ```
|
||||||
@ -40,20 +38,16 @@ export class Route implements RouteDefinition {
|
|||||||
path: string;
|
path: string;
|
||||||
component: Type;
|
component: Type;
|
||||||
name: string;
|
name: string;
|
||||||
useAsDefault: boolean;
|
|
||||||
// added next three properties to work around https://github.com/Microsoft/TypeScript/issues/4107
|
// added next three properties to work around https://github.com/Microsoft/TypeScript/issues/4107
|
||||||
aux: string = null;
|
aux: string = null;
|
||||||
loader: Function = null;
|
loader: Function = null;
|
||||||
redirectTo: any[] = null;
|
redirectTo: string = null;
|
||||||
constructor({path, component, name, data, useAsDefault}: {
|
constructor({path, component, name,
|
||||||
path: string,
|
data}: {path: string, component: Type, name?: string, data?: {[key: string]: any}}) {
|
||||||
component: Type, name?: string, data?: {[key: string]: any}, useAsDefault?: boolean
|
|
||||||
}) {
|
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.component = component;
|
this.component = component;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.useAsDefault = useAsDefault;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,8 +80,7 @@ export class AuxRoute implements RouteDefinition {
|
|||||||
// added next three properties to work around https://github.com/Microsoft/TypeScript/issues/4107
|
// added next three properties to work around https://github.com/Microsoft/TypeScript/issues/4107
|
||||||
aux: string = null;
|
aux: string = null;
|
||||||
loader: Function = null;
|
loader: Function = null;
|
||||||
redirectTo: any[] = null;
|
redirectTo: string = null;
|
||||||
useAsDefault: boolean = false;
|
|
||||||
constructor({path, component, name}: {path: string, component: Type, name?: string}) {
|
constructor({path, component, name}: {path: string, component: Type, name?: string}) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.component = component;
|
this.component = component;
|
||||||
@ -105,8 +98,6 @@ export class AuxRoute implements RouteDefinition {
|
|||||||
* - `name` is an optional `CamelCase` string representing the name of the route.
|
* - `name` is an optional `CamelCase` string representing the name of the route.
|
||||||
* - `data` is an optional property of any type representing arbitrary route metadata for the given
|
* - `data` is an optional property of any type representing arbitrary route metadata for the given
|
||||||
* route. It is injectable via {@link RouteData}.
|
* route. It is injectable via {@link RouteData}.
|
||||||
* - `useAsDefault` is a boolean value. If `true`, the child route will be navigated to if no child
|
|
||||||
* route is specified during the navigation.
|
|
||||||
*
|
*
|
||||||
* ### Example
|
* ### Example
|
||||||
* ```
|
* ```
|
||||||
@ -124,37 +115,31 @@ export class AsyncRoute implements RouteDefinition {
|
|||||||
path: string;
|
path: string;
|
||||||
loader: Function;
|
loader: Function;
|
||||||
name: string;
|
name: string;
|
||||||
useAsDefault: boolean;
|
|
||||||
aux: string = null;
|
aux: string = null;
|
||||||
constructor({path, loader, name, data, useAsDefault}: {
|
constructor({path, loader, name, data}:
|
||||||
path: string,
|
{path: string, loader: Function, name?: string, data?: {[key: string]: any}}) {
|
||||||
loader: Function, name?: string, data?: {[key: string]: any}, useAsDefault?: boolean
|
|
||||||
}) {
|
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.loader = loader;
|
this.loader = loader;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.useAsDefault = useAsDefault;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `Redirect` is a type of {@link RouteDefinition} used to route a path to a canonical route.
|
* `Redirect` is a type of {@link RouteDefinition} used to route a path to an asynchronously loaded
|
||||||
|
* component.
|
||||||
*
|
*
|
||||||
* It has the following properties:
|
* It has the following properties:
|
||||||
* - `path` is a string that uses the route matcher DSL.
|
* - `path` is a string that uses the route matcher DSL.
|
||||||
* - `redirectTo` is an array representing the link DSL.
|
* - `redirectTo` is a string representing the new URL to be matched against.
|
||||||
*
|
|
||||||
* Note that redirects **do not** affect how links are generated. For that, see the `useAsDefault`
|
|
||||||
* option.
|
|
||||||
*
|
*
|
||||||
* ### Example
|
* ### Example
|
||||||
* ```
|
* ```
|
||||||
* import {RouteConfig} from 'angular2/router';
|
* import {RouteConfig} from 'angular2/router';
|
||||||
*
|
*
|
||||||
* @RouteConfig([
|
* @RouteConfig([
|
||||||
* {path: '/', redirectTo: ['/Home'] },
|
* {path: '/', redirectTo: '/home'},
|
||||||
* {path: '/home', component: HomeCmp, name: 'Home'}
|
* {path: '/home', component: HomeCmp}
|
||||||
* ])
|
* ])
|
||||||
* class MyApp {}
|
* class MyApp {}
|
||||||
* ```
|
* ```
|
||||||
@ -162,14 +147,13 @@ export class AsyncRoute implements RouteDefinition {
|
|||||||
@CONST()
|
@CONST()
|
||||||
export class Redirect implements RouteDefinition {
|
export class Redirect implements RouteDefinition {
|
||||||
path: string;
|
path: string;
|
||||||
redirectTo: any[];
|
redirectTo: string;
|
||||||
name: string = null;
|
name: string = null;
|
||||||
// added next three properties to work around https://github.com/Microsoft/TypeScript/issues/4107
|
// added next three properties to work around https://github.com/Microsoft/TypeScript/issues/4107
|
||||||
loader: Function = null;
|
loader: Function = null;
|
||||||
data: any = null;
|
data: any = null;
|
||||||
aux: string = null;
|
aux: string = null;
|
||||||
useAsDefault: boolean = false;
|
constructor({path, redirectTo}: {path: string, redirectTo: string}) {
|
||||||
constructor({path, redirectTo}: {path: string, redirectTo: any[]}) {
|
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.redirectTo = redirectTo;
|
this.redirectTo = redirectTo;
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,9 @@
|
|||||||
library angular2.src.router.route_config_normalizer;
|
library angular2.src.router.route_config_normalizer;
|
||||||
|
|
||||||
import "route_config_decorator.dart";
|
import "route_config_decorator.dart";
|
||||||
import "route_registry.dart";
|
|
||||||
import "package:angular2/src/facade/exceptions.dart" show BaseException;
|
import "package:angular2/src/facade/exceptions.dart" show BaseException;
|
||||||
|
|
||||||
RouteDefinition normalizeRouteConfig(RouteDefinition config, RouteRegistry registry) {
|
RouteDefinition normalizeRouteConfig(RouteDefinition config) {
|
||||||
if (config is AsyncRoute) {
|
|
||||||
|
|
||||||
configRegistryAndReturnType(componentType) {
|
|
||||||
registry.configFromComponent(componentType);
|
|
||||||
return componentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
loader() {
|
|
||||||
return config.loader().then(configRegistryAndReturnType);
|
|
||||||
}
|
|
||||||
return new AsyncRoute(path: config.path, loader: loader, name: config.name, data: config.data, useAsDefault: config.useAsDefault);
|
|
||||||
}
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,29 +2,14 @@ import {AsyncRoute, AuxRoute, Route, Redirect, RouteDefinition} from './route_co
|
|||||||
import {ComponentDefinition} from './route_definition';
|
import {ComponentDefinition} from './route_definition';
|
||||||
import {isType, Type} from 'angular2/src/facade/lang';
|
import {isType, Type} from 'angular2/src/facade/lang';
|
||||||
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
||||||
import {RouteRegistry} from './route_registry';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a JS Object that represents a route config, returns a corresponding Route, AsyncRoute,
|
* Given a JS Object that represents... returns a corresponding Route, AsyncRoute, or Redirect
|
||||||
* AuxRoute or Redirect object.
|
|
||||||
*
|
|
||||||
* Also wraps an AsyncRoute's loader function to add the loaded component's route config to the
|
|
||||||
* `RouteRegistry`.
|
|
||||||
*/
|
*/
|
||||||
export function normalizeRouteConfig(config: RouteDefinition,
|
export function normalizeRouteConfig(config: RouteDefinition): RouteDefinition {
|
||||||
registry: RouteRegistry): RouteDefinition {
|
if (config instanceof Route || config instanceof Redirect || config instanceof AsyncRoute ||
|
||||||
if (config instanceof AsyncRoute) {
|
config instanceof AuxRoute) {
|
||||||
var wrappedLoader = wrapLoaderToReconfigureRegistry(config.loader, registry);
|
|
||||||
return new AsyncRoute({
|
|
||||||
path: config.path,
|
|
||||||
loader: wrappedLoader,
|
|
||||||
name: config.name,
|
|
||||||
data: config.data,
|
|
||||||
useAsDefault: config.useAsDefault
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (config instanceof Route || config instanceof Redirect || config instanceof AuxRoute) {
|
|
||||||
return <RouteDefinition>config;
|
return <RouteDefinition>config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,13 +24,7 @@ export function normalizeRouteConfig(config: RouteDefinition,
|
|||||||
config.name = config.as;
|
config.name = config.as;
|
||||||
}
|
}
|
||||||
if (config.loader) {
|
if (config.loader) {
|
||||||
var wrappedLoader = wrapLoaderToReconfigureRegistry(config.loader, registry);
|
return new AsyncRoute({path: config.path, loader: config.loader, name: config.name});
|
||||||
return new AsyncRoute({
|
|
||||||
path: config.path,
|
|
||||||
loader: wrappedLoader,
|
|
||||||
name: config.name,
|
|
||||||
useAsDefault: config.useAsDefault
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (config.aux) {
|
if (config.aux) {
|
||||||
return new AuxRoute({path: config.aux, component:<Type>config.component, name: config.name});
|
return new AuxRoute({path: config.aux, component:<Type>config.component, name: config.name});
|
||||||
@ -57,17 +36,11 @@ export function normalizeRouteConfig(config: RouteDefinition,
|
|||||||
return new Route({
|
return new Route({
|
||||||
path: config.path,
|
path: config.path,
|
||||||
component:<Type>componentDefinitionObject.constructor,
|
component:<Type>componentDefinitionObject.constructor,
|
||||||
name: config.name,
|
name: config.name
|
||||||
data: config.data,
|
|
||||||
useAsDefault: config.useAsDefault
|
|
||||||
});
|
});
|
||||||
} else if (componentDefinitionObject.type == 'loader') {
|
} else if (componentDefinitionObject.type == 'loader') {
|
||||||
return new AsyncRoute({
|
return new AsyncRoute(
|
||||||
path: config.path,
|
{path: config.path, loader: componentDefinitionObject.loader, name: config.name});
|
||||||
loader: componentDefinitionObject.loader,
|
|
||||||
name: config.name,
|
|
||||||
useAsDefault: config.useAsDefault
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
throw new BaseException(
|
throw new BaseException(
|
||||||
`Invalid component type "${componentDefinitionObject.type}". Valid types are "constructor" and "loader".`);
|
`Invalid component type "${componentDefinitionObject.type}". Valid types are "constructor" and "loader".`);
|
||||||
@ -77,8 +50,6 @@ export function normalizeRouteConfig(config: RouteDefinition,
|
|||||||
path: string;
|
path: string;
|
||||||
component: Type;
|
component: Type;
|
||||||
name?: string;
|
name?: string;
|
||||||
data?: {[key: string]: any};
|
|
||||||
useAsDefault?: boolean;
|
|
||||||
}>config);
|
}>config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,16 +60,6 @@ export function normalizeRouteConfig(config: RouteDefinition,
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function wrapLoaderToReconfigureRegistry(loader: Function, registry: RouteRegistry): Function {
|
|
||||||
return () => {
|
|
||||||
return loader().then((componentType) => {
|
|
||||||
registry.configFromComponent(componentType);
|
|
||||||
return componentType;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function assertComponentExists(component: Type, path: string): void {
|
export function assertComponentExists(component: Type, path: string): void {
|
||||||
if (!isType(component)) {
|
if (!isType(component)) {
|
||||||
throw new BaseException(`Component for route "${path}" is not defined, or is not a class.`);
|
throw new BaseException(`Component for route "${path}" is not defined, or is not a class.`);
|
||||||
|
@ -3,6 +3,5 @@ library angular2.src.router.route_definition;
|
|||||||
abstract class RouteDefinition {
|
abstract class RouteDefinition {
|
||||||
final String path;
|
final String path;
|
||||||
final String name;
|
final String name;
|
||||||
final bool useAsDefault;
|
const RouteDefinition({this.path, this.name});
|
||||||
const RouteDefinition({this.path, this.name, this.useAsDefault : false});
|
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,10 @@ export interface RouteDefinition {
|
|||||||
aux?: string;
|
aux?: string;
|
||||||
component?: Type | ComponentDefinition;
|
component?: Type | ComponentDefinition;
|
||||||
loader?: Function;
|
loader?: Function;
|
||||||
redirectTo?: any[];
|
redirectTo?: string;
|
||||||
as?: string;
|
as?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
data?: any;
|
data?: any;
|
||||||
useAsDefault?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ComponentDefinition {
|
export interface ComponentDefinition {
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
import {Type} from 'angular2/src/facade/lang';
|
import {Type} from 'angular2/src/facade/lang';
|
||||||
import {RouteData} from './instruction';
|
|
||||||
|
|
||||||
export interface RouteHandler {
|
export interface RouteHandler {
|
||||||
componentType: Type;
|
componentType: Type;
|
||||||
resolveComponentType(): Promise<any>;
|
resolveComponentType(): Promise<any>;
|
||||||
data: RouteData;
|
data?: {[key: string]: any};
|
||||||
}
|
}
|
||||||
|
@ -1,119 +1,184 @@
|
|||||||
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
import {
|
||||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
RegExp,
|
||||||
import {PromiseWrapper, Promise} from 'angular2/src/facade/promise';
|
RegExpWrapper,
|
||||||
import {Map} from 'angular2/src/facade/collection';
|
isBlank,
|
||||||
|
isPresent,
|
||||||
|
isType,
|
||||||
|
isStringMap,
|
||||||
|
Type
|
||||||
|
} from 'angular2/src/facade/lang';
|
||||||
|
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
||||||
|
import {Map, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {RouteHandler} from './route_handler';
|
import {PathRecognizer, PathMatch} from './path_recognizer';
|
||||||
|
import {Route, AsyncRoute, AuxRoute, Redirect, RouteDefinition} from './route_config_impl';
|
||||||
|
import {AsyncRouteHandler} from './async_route_handler';
|
||||||
|
import {SyncRouteHandler} from './sync_route_handler';
|
||||||
import {Url} from './url_parser';
|
import {Url} from './url_parser';
|
||||||
import {ComponentInstruction} from './instruction';
|
import {ComponentInstruction} from './instruction';
|
||||||
import {PathRecognizer} from './path_recognizer';
|
|
||||||
|
|
||||||
|
|
||||||
export abstract class RouteMatch {}
|
/**
|
||||||
|
* `RouteRecognizer` is responsible for recognizing routes for a single component.
|
||||||
|
* It is consumed by `RouteRegistry`, which knows how to recognize an entire hierarchy of
|
||||||
|
* components.
|
||||||
|
*/
|
||||||
|
export class RouteRecognizer {
|
||||||
|
names = new Map<string, PathRecognizer>();
|
||||||
|
|
||||||
export interface AbstractRecognizer {
|
// map from name to recognizer
|
||||||
hash: string;
|
auxNames = new Map<string, PathRecognizer>();
|
||||||
path: string;
|
|
||||||
recognize(beginningSegment: Url): Promise<RouteMatch>;
|
// map from starting path to recognizer
|
||||||
generate(params: {[key: string]: any}): ComponentInstruction;
|
auxRoutes = new Map<string, PathRecognizer>();
|
||||||
}
|
|
||||||
|
// TODO: optimize this into a trie
|
||||||
|
matchers: PathRecognizer[] = [];
|
||||||
|
|
||||||
|
// TODO: optimize this into a trie
|
||||||
|
redirects: Redirector[] = [];
|
||||||
|
|
||||||
|
config(config: RouteDefinition): boolean {
|
||||||
|
var handler;
|
||||||
|
|
||||||
|
if (isPresent(config.name) && config.name[0].toUpperCase() != config.name[0]) {
|
||||||
|
var suggestedName = config.name[0].toUpperCase() + config.name.substring(1);
|
||||||
|
throw new BaseException(
|
||||||
|
`Route "${config.path}" with name "${config.name}" does not begin with an uppercase letter. Route names should be CamelCase like "${suggestedName}".`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config instanceof AuxRoute) {
|
||||||
|
handler = new SyncRouteHandler(config.component, config.data);
|
||||||
|
let path = config.path.startsWith('/') ? config.path.substring(1) : config.path;
|
||||||
|
var recognizer = new PathRecognizer(config.path, handler);
|
||||||
|
this.auxRoutes.set(path, recognizer);
|
||||||
|
if (isPresent(config.name)) {
|
||||||
|
this.auxNames.set(config.name, recognizer);
|
||||||
|
}
|
||||||
|
return recognizer.terminal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config instanceof Redirect) {
|
||||||
|
this.redirects.push(new Redirector(config.path, config.redirectTo));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config instanceof Route) {
|
||||||
|
handler = new SyncRouteHandler(config.component, config.data);
|
||||||
|
} else if (config instanceof AsyncRoute) {
|
||||||
|
handler = new AsyncRouteHandler(config.loader, config.data);
|
||||||
|
}
|
||||||
|
var recognizer = new PathRecognizer(config.path, handler);
|
||||||
|
|
||||||
|
this.matchers.forEach((matcher) => {
|
||||||
|
if (recognizer.hash == matcher.hash) {
|
||||||
|
throw new BaseException(
|
||||||
|
`Configuration '${config.path}' conflicts with existing route '${matcher.path}'`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.matchers.push(recognizer);
|
||||||
|
if (isPresent(config.name)) {
|
||||||
|
this.names.set(config.name, recognizer);
|
||||||
|
}
|
||||||
|
return recognizer.terminal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export class PathMatch extends RouteMatch {
|
/**
|
||||||
constructor(public instruction: ComponentInstruction, public remaining: Url,
|
* Given a URL, returns a list of `RouteMatch`es, which are partial recognitions for some route.
|
||||||
public remainingAux: Url[]) {
|
*
|
||||||
super();
|
*/
|
||||||
|
recognize(urlParse: Url): PathMatch[] {
|
||||||
|
var solutions = [];
|
||||||
|
|
||||||
|
urlParse = this._redirect(urlParse);
|
||||||
|
|
||||||
|
this.matchers.forEach((pathRecognizer: PathRecognizer) => {
|
||||||
|
var pathMatch = pathRecognizer.recognize(urlParse);
|
||||||
|
|
||||||
|
if (isPresent(pathMatch)) {
|
||||||
|
solutions.push(pathMatch);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return solutions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
_redirect(urlParse: Url): Url {
|
||||||
|
for (var i = 0; i < this.redirects.length; i += 1) {
|
||||||
|
let redirector = this.redirects[i];
|
||||||
|
var redirectedUrl = redirector.redirect(urlParse);
|
||||||
|
if (isPresent(redirectedUrl)) {
|
||||||
|
return redirectedUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlParse;
|
||||||
|
}
|
||||||
|
|
||||||
|
recognizeAuxiliary(urlParse: Url): PathMatch {
|
||||||
|
var pathRecognizer = this.auxRoutes.get(urlParse.path);
|
||||||
|
if (isBlank(pathRecognizer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return pathRecognizer.recognize(urlParse);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRoute(name: string): boolean { return this.names.has(name); }
|
||||||
|
|
||||||
|
generate(name: string, params: any): ComponentInstruction {
|
||||||
|
var pathRecognizer: PathRecognizer = this.names.get(name);
|
||||||
|
if (isBlank(pathRecognizer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return pathRecognizer.generate(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateAuxiliary(name: string, params: any): ComponentInstruction {
|
||||||
|
var pathRecognizer: PathRecognizer = this.auxNames.get(name);
|
||||||
|
if (isBlank(pathRecognizer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return pathRecognizer.generate(params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Redirector {
|
||||||
|
segments: string[] = [];
|
||||||
|
toSegments: string[] = [];
|
||||||
|
|
||||||
export class RedirectMatch extends RouteMatch {
|
constructor(path: string, redirectTo: string) {
|
||||||
constructor(public redirectTo: any[], public specificity) { super(); }
|
if (path.startsWith('/')) {
|
||||||
}
|
path = path.substring(1);
|
||||||
|
}
|
||||||
export class RedirectRecognizer implements AbstractRecognizer {
|
this.segments = path.split('/');
|
||||||
private _pathRecognizer: PathRecognizer;
|
if (redirectTo.startsWith('/')) {
|
||||||
public hash: string;
|
redirectTo = redirectTo.substring(1);
|
||||||
|
}
|
||||||
constructor(public path: string, public redirectTo: any[]) {
|
this.toSegments = redirectTo.split('/');
|
||||||
this._pathRecognizer = new PathRecognizer(path);
|
|
||||||
this.hash = this._pathRecognizer.hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns `null` or a `ParsedUrl` representing the new path to match
|
* Returns `null` or a `ParsedUrl` representing the new path to match
|
||||||
*/
|
*/
|
||||||
recognize(beginningSegment: Url): Promise<RouteMatch> {
|
redirect(urlParse: Url): Url {
|
||||||
var match = null;
|
for (var i = 0; i < this.segments.length; i += 1) {
|
||||||
if (isPresent(this._pathRecognizer.recognize(beginningSegment))) {
|
if (isBlank(urlParse)) {
|
||||||
match = new RedirectMatch(this.redirectTo, this._pathRecognizer.specificity);
|
return null;
|
||||||
|
}
|
||||||
|
let segment = this.segments[i];
|
||||||
|
if (segment != urlParse.path) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
urlParse = urlParse.child;
|
||||||
}
|
}
|
||||||
return PromiseWrapper.resolve(match);
|
|
||||||
}
|
|
||||||
|
|
||||||
generate(params: {[key: string]: any}): ComponentInstruction {
|
for (var i = this.toSegments.length - 1; i >= 0; i -= 1) {
|
||||||
throw new BaseException(`Tried to generate a redirect.`);
|
let segment = this.toSegments[i];
|
||||||
}
|
urlParse = new Url(segment, urlParse);
|
||||||
}
|
}
|
||||||
|
return urlParse;
|
||||||
|
|
||||||
// represents something like '/foo/:bar'
|
|
||||||
export class RouteRecognizer implements AbstractRecognizer {
|
|
||||||
specificity: number;
|
|
||||||
terminal: boolean = true;
|
|
||||||
hash: string;
|
|
||||||
|
|
||||||
private _cache: Map<string, ComponentInstruction> = new Map<string, ComponentInstruction>();
|
|
||||||
private _pathRecognizer: PathRecognizer;
|
|
||||||
|
|
||||||
// TODO: cache component instruction instances by params and by ParsedUrl instance
|
|
||||||
|
|
||||||
constructor(public path: string, public handler: RouteHandler) {
|
|
||||||
this._pathRecognizer = new PathRecognizer(path);
|
|
||||||
this.specificity = this._pathRecognizer.specificity;
|
|
||||||
this.hash = this._pathRecognizer.hash;
|
|
||||||
this.terminal = this._pathRecognizer.terminal;
|
|
||||||
}
|
|
||||||
|
|
||||||
recognize(beginningSegment: Url): Promise<RouteMatch> {
|
|
||||||
var res = this._pathRecognizer.recognize(beginningSegment);
|
|
||||||
if (isBlank(res)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handler.resolveComponentType().then((_) => {
|
|
||||||
var componentInstruction =
|
|
||||||
this._getInstruction(res['urlPath'], res['urlParams'], res['allParams']);
|
|
||||||
return new PathMatch(componentInstruction, res['nextSegment'], res['auxiliary']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
generate(params: {[key: string]: any}): ComponentInstruction {
|
|
||||||
var generated = this._pathRecognizer.generate(params);
|
|
||||||
var urlPath = generated['urlPath'];
|
|
||||||
var urlParams = generated['urlParams'];
|
|
||||||
return this._getInstruction(urlPath, urlParams, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
generateComponentPathValues(params: {[key: string]: any}): {[key: string]: any} {
|
|
||||||
return this._pathRecognizer.generate(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getInstruction(urlPath: string, urlParams: string[],
|
|
||||||
params: {[key: string]: any}): ComponentInstruction {
|
|
||||||
if (isBlank(this.handler.componentType)) {
|
|
||||||
throw new BaseException(`Tried to get instruction before the type was loaded.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
var hashKey = urlPath + '?' + urlParams.join('?');
|
|
||||||
if (this._cache.has(hashKey)) {
|
|
||||||
return this._cache.get(hashKey);
|
|
||||||
}
|
|
||||||
var instruction =
|
|
||||||
new ComponentInstruction(urlPath, urlParams, this.handler.data, this.handler.componentType,
|
|
||||||
this.terminal, this.specificity, params);
|
|
||||||
this._cache.set(hashKey, instruction);
|
|
||||||
|
|
||||||
return instruction;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import {PathMatch} from './path_recognizer';
|
||||||
|
import {RouteRecognizer} from './route_recognizer';
|
||||||
|
import {Instruction, ComponentInstruction, PrimaryInstruction} from './instruction';
|
||||||
import {ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
import {
|
import {
|
||||||
@ -13,7 +16,6 @@ import {
|
|||||||
getTypeNameForDebugging
|
getTypeNameForDebugging
|
||||||
} from 'angular2/src/facade/lang';
|
} from 'angular2/src/facade/lang';
|
||||||
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
||||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
|
||||||
import {
|
import {
|
||||||
RouteConfig,
|
RouteConfig,
|
||||||
AsyncRoute,
|
AsyncRoute,
|
||||||
@ -22,16 +24,7 @@ import {
|
|||||||
Redirect,
|
Redirect,
|
||||||
RouteDefinition
|
RouteDefinition
|
||||||
} from './route_config_impl';
|
} from './route_config_impl';
|
||||||
import {PathMatch, RedirectMatch, RouteMatch} from './route_recognizer';
|
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||||
import {ComponentRecognizer} from './component_recognizer';
|
|
||||||
import {
|
|
||||||
Instruction,
|
|
||||||
ResolvedInstruction,
|
|
||||||
RedirectInstruction,
|
|
||||||
UnresolvedInstruction,
|
|
||||||
DefaultInstruction
|
|
||||||
} from './instruction';
|
|
||||||
|
|
||||||
import {Injectable} from 'angular2/angular2';
|
import {Injectable} from 'angular2/angular2';
|
||||||
import {normalizeRouteConfig, assertComponentExists} from './route_config_nomalizer';
|
import {normalizeRouteConfig, assertComponentExists} from './route_config_nomalizer';
|
||||||
import {parser, Url, pathSegmentsToUrl} from './url_parser';
|
import {parser, Url, pathSegmentsToUrl} from './url_parser';
|
||||||
@ -45,13 +38,13 @@ var _resolveToNull = PromiseWrapper.resolve(null);
|
|||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RouteRegistry {
|
export class RouteRegistry {
|
||||||
private _rules = new Map<any, ComponentRecognizer>();
|
private _rules = new Map<any, RouteRecognizer>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a component and a configuration object, add the route to this registry
|
* Given a component and a configuration object, add the route to this registry
|
||||||
*/
|
*/
|
||||||
config(parentComponent: any, config: RouteDefinition): void {
|
config(parentComponent: any, config: RouteDefinition): void {
|
||||||
config = normalizeRouteConfig(config, this);
|
config = normalizeRouteConfig(config);
|
||||||
|
|
||||||
// this is here because Dart type guard reasons
|
// this is here because Dart type guard reasons
|
||||||
if (config instanceof Route) {
|
if (config instanceof Route) {
|
||||||
@ -60,10 +53,10 @@ export class RouteRegistry {
|
|||||||
assertComponentExists(config.component, config.path);
|
assertComponentExists(config.component, config.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
var recognizer: ComponentRecognizer = this._rules.get(parentComponent);
|
var recognizer: RouteRecognizer = this._rules.get(parentComponent);
|
||||||
|
|
||||||
if (isBlank(recognizer)) {
|
if (isBlank(recognizer)) {
|
||||||
recognizer = new ComponentRecognizer();
|
recognizer = new RouteRecognizer();
|
||||||
this._rules.set(parentComponent, recognizer);
|
this._rules.set(parentComponent, recognizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,162 +102,102 @@ export class RouteRegistry {
|
|||||||
* Given a URL and a parent component, return the most specific instruction for navigating
|
* Given a URL and a parent component, return the most specific instruction for navigating
|
||||||
* the application into the state specified by the url
|
* the application into the state specified by the url
|
||||||
*/
|
*/
|
||||||
recognize(url: string, ancestorComponents: any[]): Promise<Instruction> {
|
recognize(url: string, parentComponent: any): Promise<Instruction> {
|
||||||
var parsedUrl = parser.parse(url);
|
var parsedUrl = parser.parse(url);
|
||||||
return this._recognize(parsedUrl, ancestorComponents);
|
return this._recognize(parsedUrl, parentComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _recognize(parsedUrl: Url, parentComponent): Promise<Instruction> {
|
||||||
|
return this._recognizePrimaryRoute(parsedUrl, parentComponent)
|
||||||
|
.then((instruction: PrimaryInstruction) =>
|
||||||
|
this._completeAuxiliaryRouteMatches(instruction, parentComponent));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
private _recognizePrimaryRoute(parsedUrl: Url, parentComponent): Promise<PrimaryInstruction> {
|
||||||
* Recognizes all parent-child routes, but creates unresolved auxiliary routes
|
|
||||||
*/
|
|
||||||
|
|
||||||
private _recognize(parsedUrl: Url, ancestorComponents: any[],
|
|
||||||
_aux = false): Promise<Instruction> {
|
|
||||||
var parentComponent = ancestorComponents[ancestorComponents.length - 1];
|
|
||||||
var componentRecognizer = this._rules.get(parentComponent);
|
var componentRecognizer = this._rules.get(parentComponent);
|
||||||
if (isBlank(componentRecognizer)) {
|
if (isBlank(componentRecognizer)) {
|
||||||
return _resolveToNull;
|
return _resolveToNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matches some beginning part of the given URL
|
// Matches some beginning part of the given URL
|
||||||
var possibleMatches: Promise<RouteMatch>[] =
|
var possibleMatches = componentRecognizer.recognize(parsedUrl);
|
||||||
_aux ? componentRecognizer.recognizeAuxiliary(parsedUrl) :
|
|
||||||
componentRecognizer.recognize(parsedUrl);
|
|
||||||
|
|
||||||
var matchPromises: Promise<Instruction>[] = possibleMatches.map(
|
var matchPromises =
|
||||||
(candidate: Promise<RouteMatch>) => candidate.then((candidate: RouteMatch) => {
|
possibleMatches.map(candidate => this._completePrimaryRouteMatch(candidate));
|
||||||
|
|
||||||
if (candidate instanceof PathMatch) {
|
|
||||||
if (candidate.instruction.terminal) {
|
|
||||||
var unresolvedAux =
|
|
||||||
this._auxRoutesToUnresolved(candidate.remainingAux, parentComponent);
|
|
||||||
return new ResolvedInstruction(candidate.instruction, null, unresolvedAux);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newAncestorComponents =
|
|
||||||
ancestorComponents.concat([candidate.instruction.componentType]);
|
|
||||||
|
|
||||||
return this._recognize(candidate.remaining, newAncestorComponents)
|
|
||||||
.then((childInstruction) => {
|
|
||||||
if (isBlank(childInstruction)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// redirect instructions are already absolute
|
|
||||||
if (childInstruction instanceof RedirectInstruction) {
|
|
||||||
return childInstruction;
|
|
||||||
}
|
|
||||||
var unresolvedAux =
|
|
||||||
this._auxRoutesToUnresolved(candidate.remainingAux, parentComponent);
|
|
||||||
return new ResolvedInstruction(candidate.instruction, childInstruction,
|
|
||||||
unresolvedAux);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidate instanceof RedirectMatch) {
|
|
||||||
var instruction = this.generate(candidate.redirectTo, ancestorComponents);
|
|
||||||
return new RedirectInstruction(instruction.component, instruction.child,
|
|
||||||
instruction.auxInstruction);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
if ((isBlank(parsedUrl) || parsedUrl.path == '') && possibleMatches.length == 0) {
|
|
||||||
return PromiseWrapper.resolve(this.generateDefault(parentComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
return PromiseWrapper.all(matchPromises).then(mostSpecific);
|
return PromiseWrapper.all(matchPromises).then(mostSpecific);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _auxRoutesToUnresolved(auxRoutes: Url[], parentComponent): {[key: string]: Instruction} {
|
private _completePrimaryRouteMatch(partialMatch: PathMatch): Promise<PrimaryInstruction> {
|
||||||
var unresolvedAuxInstructions: {[key: string]: Instruction} = {};
|
var instruction = partialMatch.instruction;
|
||||||
|
return instruction.resolveComponentType().then((componentType) => {
|
||||||
|
this.configFromComponent(componentType);
|
||||||
|
|
||||||
auxRoutes.forEach((auxUrl: Url) => {
|
if (instruction.terminal) {
|
||||||
unresolvedAuxInstructions[auxUrl.path] = new UnresolvedInstruction(
|
return new PrimaryInstruction(instruction, null, partialMatch.remainingAux);
|
||||||
() => { return this._recognize(auxUrl, [parentComponent], true); });
|
}
|
||||||
|
|
||||||
|
return this._recognizePrimaryRoute(partialMatch.remaining, componentType)
|
||||||
|
.then((childInstruction) => {
|
||||||
|
if (isBlank(childInstruction)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return new PrimaryInstruction(instruction, childInstruction,
|
||||||
|
partialMatch.remainingAux);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return unresolvedAuxInstructions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private _completeAuxiliaryRouteMatches(instruction: PrimaryInstruction,
|
||||||
|
parentComponent: any): Promise<Instruction> {
|
||||||
|
if (isBlank(instruction)) {
|
||||||
|
return _resolveToNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
var componentRecognizer = this._rules.get(parentComponent);
|
||||||
|
var auxInstructions: {[key: string]: Instruction} = {};
|
||||||
|
|
||||||
|
var promises = instruction.auxUrls.map((auxSegment: Url) => {
|
||||||
|
var match = componentRecognizer.recognizeAuxiliary(auxSegment);
|
||||||
|
if (isBlank(match)) {
|
||||||
|
return _resolveToNull;
|
||||||
|
}
|
||||||
|
return this._completePrimaryRouteMatch(match).then((auxInstruction: PrimaryInstruction) => {
|
||||||
|
if (isPresent(auxInstruction)) {
|
||||||
|
return this._completeAuxiliaryRouteMatches(auxInstruction, parentComponent)
|
||||||
|
.then((finishedAuxRoute: Instruction) => {
|
||||||
|
auxInstructions[auxSegment.path] = finishedAuxRoute;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return PromiseWrapper.all(promises).then((_) => {
|
||||||
|
if (isBlank(instruction.child)) {
|
||||||
|
return new Instruction(instruction.component, null, auxInstructions);
|
||||||
|
}
|
||||||
|
return this._completeAuxiliaryRouteMatches(instruction.child,
|
||||||
|
instruction.component.componentType)
|
||||||
|
.then((completeChild) => {
|
||||||
|
return new Instruction(instruction.component, completeChild, auxInstructions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a normalized list with component names and params like: `['user', {id: 3 }]`
|
* Given a normalized list with component names and params like: `['user', {id: 3 }]`
|
||||||
* generates a url with a leading slash relative to the provided `parentComponent`.
|
* generates a url with a leading slash relative to the provided `parentComponent`.
|
||||||
*
|
|
||||||
* If the optional param `_aux` is `true`, then we generate starting at an auxiliary
|
|
||||||
* route boundary.
|
|
||||||
*/
|
*/
|
||||||
generate(linkParams: any[], ancestorComponents: any[], _aux = false): Instruction {
|
generate(linkParams: any[], parentComponent: any, _aux = false): Instruction {
|
||||||
let parentComponent = ancestorComponents[ancestorComponents.length - 1];
|
|
||||||
let grandparentComponent =
|
|
||||||
ancestorComponents.length > 1 ? ancestorComponents[ancestorComponents.length - 2] : null;
|
|
||||||
|
|
||||||
let normalizedLinkParams = splitAndFlattenLinkParams(linkParams);
|
|
||||||
|
|
||||||
var first = ListWrapper.first(normalizedLinkParams);
|
|
||||||
var rest = ListWrapper.slice(normalizedLinkParams, 1);
|
|
||||||
|
|
||||||
// The first segment should be either '.' (generate from parent) or '' (generate from root).
|
|
||||||
// When we normalize above, we strip all the slashes, './' becomes '.' and '/' becomes ''.
|
|
||||||
if (first == '') {
|
|
||||||
var firstComponent = ancestorComponents[0];
|
|
||||||
ListWrapper.clear(ancestorComponents);
|
|
||||||
ancestorComponents.push(firstComponent);
|
|
||||||
} else if (first == '..') {
|
|
||||||
// we already captured the first instance of "..", so we need to pop off an ancestor
|
|
||||||
ancestorComponents.pop();
|
|
||||||
while (ListWrapper.first(rest) == '..') {
|
|
||||||
rest = ListWrapper.slice(rest, 1);
|
|
||||||
ancestorComponents.pop();
|
|
||||||
if (ancestorComponents.length <= 0) {
|
|
||||||
throw new BaseException(
|
|
||||||
`Link "${ListWrapper.toJSON(linkParams)}" has too many "../" segments.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (first != '.') {
|
|
||||||
// For a link with no leading `./`, `/`, or `../`, we look for a sibling and child.
|
|
||||||
// If both exist, we throw. Otherwise, we prefer whichever exists.
|
|
||||||
var childRouteExists = this.hasRoute(first, parentComponent);
|
|
||||||
var parentRouteExists =
|
|
||||||
isPresent(grandparentComponent) && this.hasRoute(first, grandparentComponent);
|
|
||||||
|
|
||||||
if (parentRouteExists && childRouteExists) {
|
|
||||||
let msg =
|
|
||||||
`Link "${ListWrapper.toJSON(linkParams)}" is ambiguous, use "./" or "../" to disambiguate.`;
|
|
||||||
throw new BaseException(msg);
|
|
||||||
}
|
|
||||||
if (parentRouteExists) {
|
|
||||||
ancestorComponents.pop();
|
|
||||||
}
|
|
||||||
rest = linkParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rest[rest.length - 1] == '') {
|
|
||||||
rest.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rest.length < 1) {
|
|
||||||
let msg = `Link "${ListWrapper.toJSON(linkParams)}" must include a route name.`;
|
|
||||||
throw new BaseException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._generate(rest, ancestorComponents, _aux);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Internal helper that does not make any assertions about the beginning of the link DSL
|
|
||||||
*/
|
|
||||||
private _generate(linkParams: any[], ancestorComponents: any[], _aux = false): Instruction {
|
|
||||||
let parentComponent = ancestorComponents[ancestorComponents.length - 1];
|
|
||||||
|
|
||||||
if (linkParams.length == 0) {
|
|
||||||
return this.generateDefault(parentComponent);
|
|
||||||
}
|
|
||||||
let linkIndex = 0;
|
let linkIndex = 0;
|
||||||
let routeName = linkParams[linkIndex];
|
let routeName = linkParams[linkIndex];
|
||||||
|
|
||||||
|
// TODO: this is kind of odd but it makes existing assertions pass
|
||||||
|
if (isBlank(parentComponent)) {
|
||||||
|
throw new BaseException(`Could not find route named "${routeName}".`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isString(routeName)) {
|
if (!isString(routeName)) {
|
||||||
throw new BaseException(`Unexpected segment "${routeName}" in link DSL. Expected a string.`);
|
throw new BaseException(`Unexpected segment "${routeName}" in link DSL. Expected a string.`);
|
||||||
} else if (routeName == '' || routeName == '.' || routeName == '..') {
|
} else if (routeName == '' || routeName == '.' || routeName == '..') {
|
||||||
@ -283,10 +216,7 @@ export class RouteRegistry {
|
|||||||
let auxInstructions: {[key: string]: Instruction} = {};
|
let auxInstructions: {[key: string]: Instruction} = {};
|
||||||
var nextSegment;
|
var nextSegment;
|
||||||
while (linkIndex + 1 < linkParams.length && isArray(nextSegment = linkParams[linkIndex + 1])) {
|
while (linkIndex + 1 < linkParams.length && isArray(nextSegment = linkParams[linkIndex + 1])) {
|
||||||
let auxInstruction = this._generate(nextSegment, [parentComponent], true);
|
auxInstructions[nextSegment[0]] = this.generate(nextSegment, parentComponent, true);
|
||||||
|
|
||||||
// TODO: this will not work for aux routes with parameters or multiple segments
|
|
||||||
auxInstructions[auxInstruction.component.urlPath] = auxInstruction;
|
|
||||||
linkIndex += 1;
|
linkIndex += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,105 +226,74 @@ export class RouteRegistry {
|
|||||||
`Component "${getTypeNameForDebugging(parentComponent)}" has no route config.`);
|
`Component "${getTypeNameForDebugging(parentComponent)}" has no route config.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
var routeRecognizer =
|
var componentInstruction = _aux ? componentRecognizer.generateAuxiliary(routeName, params) :
|
||||||
(_aux ? componentRecognizer.auxNames : componentRecognizer.names).get(routeName);
|
componentRecognizer.generate(routeName, params);
|
||||||
|
|
||||||
if (!isPresent(routeRecognizer)) {
|
if (isBlank(componentInstruction)) {
|
||||||
throw new BaseException(
|
throw new BaseException(
|
||||||
`Component "${getTypeNameForDebugging(parentComponent)}" has no route named "${routeName}".`);
|
`Component "${getTypeNameForDebugging(parentComponent)}" has no route named "${routeName}".`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isPresent(routeRecognizer.handler.componentType)) {
|
var childInstruction = null;
|
||||||
var compInstruction = routeRecognizer.generateComponentPathValues(params);
|
if (linkIndex + 1 < linkParams.length) {
|
||||||
return new UnresolvedInstruction(() => {
|
var remaining = linkParams.slice(linkIndex + 1);
|
||||||
return routeRecognizer.handler.resolveComponentType().then(
|
childInstruction = this.generate(remaining, componentInstruction.componentType);
|
||||||
(_) => { return this._generate(linkParams, ancestorComponents, _aux); });
|
} else if (!componentInstruction.terminal) {
|
||||||
}, compInstruction['urlPath'], compInstruction['urlParams']);
|
throw new BaseException(
|
||||||
|
`Link "${ListWrapper.toJSON(linkParams)}" does not resolve to a terminal or async instruction.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
var componentInstruction = _aux ? componentRecognizer.generateAuxiliary(routeName, params) :
|
return new Instruction(componentInstruction, childInstruction, auxInstructions);
|
||||||
componentRecognizer.generate(routeName, params);
|
|
||||||
|
|
||||||
|
|
||||||
var childInstruction: Instruction = null;
|
|
||||||
|
|
||||||
var remaining = linkParams.slice(linkIndex + 1);
|
|
||||||
|
|
||||||
// the component is sync
|
|
||||||
if (isPresent(componentInstruction.componentType)) {
|
|
||||||
if (linkIndex + 1 < linkParams.length) {
|
|
||||||
let childAncestorComponents =
|
|
||||||
ancestorComponents.concat([componentInstruction.componentType]);
|
|
||||||
childInstruction = this._generate(remaining, childAncestorComponents);
|
|
||||||
} else if (!componentInstruction.terminal) {
|
|
||||||
// ... look for defaults
|
|
||||||
childInstruction = this.generateDefault(componentInstruction.componentType);
|
|
||||||
|
|
||||||
if (isBlank(childInstruction)) {
|
|
||||||
throw new BaseException(
|
|
||||||
`Link "${ListWrapper.toJSON(linkParams)}" does not resolve to a terminal instruction.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ResolvedInstruction(componentInstruction, childInstruction, auxInstructions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasRoute(name: string, parentComponent: any): boolean {
|
public hasRoute(name: string, parentComponent: any): boolean {
|
||||||
var componentRecognizer: ComponentRecognizer = this._rules.get(parentComponent);
|
var componentRecognizer: RouteRecognizer = this._rules.get(parentComponent);
|
||||||
if (isBlank(componentRecognizer)) {
|
if (isBlank(componentRecognizer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return componentRecognizer.hasRoute(name);
|
return componentRecognizer.hasRoute(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public generateDefault(componentCursor: Type): Instruction {
|
// if the child includes a redirect like : "/" -> "/something",
|
||||||
|
// we want to honor that redirection when creating the link
|
||||||
|
private _generateRedirects(componentCursor: Type): Instruction {
|
||||||
if (isBlank(componentCursor)) {
|
if (isBlank(componentCursor)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var componentRecognizer = this._rules.get(componentCursor);
|
var componentRecognizer = this._rules.get(componentCursor);
|
||||||
if (isBlank(componentRecognizer) || isBlank(componentRecognizer.defaultRoute)) {
|
if (isBlank(componentRecognizer)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < componentRecognizer.redirects.length; i += 1) {
|
||||||
|
let redirect = componentRecognizer.redirects[i];
|
||||||
|
|
||||||
var defaultChild = null;
|
// we only handle redirecting from an empty segment
|
||||||
if (isPresent(componentRecognizer.defaultRoute.handler.componentType)) {
|
if (redirect.segments.length == 1 && redirect.segments[0] == '') {
|
||||||
var componentInstruction = componentRecognizer.defaultRoute.generate({});
|
var toSegments = pathSegmentsToUrl(redirect.toSegments);
|
||||||
if (!componentRecognizer.defaultRoute.terminal) {
|
var matches = componentRecognizer.recognize(toSegments);
|
||||||
defaultChild = this.generateDefault(componentRecognizer.defaultRoute.handler.componentType);
|
var primaryInstruction =
|
||||||
|
ListWrapper.maximum(matches, (match: PathMatch) => match.instruction.specificity);
|
||||||
|
|
||||||
|
if (isPresent(primaryInstruction)) {
|
||||||
|
var child = this._generateRedirects(primaryInstruction.instruction.componentType);
|
||||||
|
return new Instruction(primaryInstruction.instruction, child, {});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return new DefaultInstruction(componentInstruction, defaultChild);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new UnresolvedInstruction(() => {
|
return null;
|
||||||
return componentRecognizer.defaultRoute.handler.resolveComponentType().then(
|
|
||||||
() => this.generateDefault(componentCursor));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Given: ['/a/b', {c: 2}]
|
|
||||||
* Returns: ['', 'a', 'b', {c: 2}]
|
|
||||||
*/
|
|
||||||
function splitAndFlattenLinkParams(linkParams: any[]): any[] {
|
|
||||||
return linkParams.reduce((accumulation: any[], item) => {
|
|
||||||
if (isString(item)) {
|
|
||||||
let strItem: string = item;
|
|
||||||
return accumulation.concat(strItem.split('/'));
|
|
||||||
}
|
|
||||||
accumulation.push(item);
|
|
||||||
return accumulation;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Given a list of instructions, returns the most specific instruction
|
* Given a list of instructions, returns the most specific instruction
|
||||||
*/
|
*/
|
||||||
function mostSpecific(instructions: Instruction[]): Instruction {
|
function mostSpecific(instructions: PrimaryInstruction[]): PrimaryInstruction {
|
||||||
return ListWrapper.maximum(instructions, (instruction: Instruction) => instruction.specificity);
|
return ListWrapper.maximum(
|
||||||
|
instructions, (instruction: PrimaryInstruction) => instruction.component.specificity);
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertTerminalComponent(component, path) {
|
function assertTerminalComponent(component, path) {
|
||||||
|
@ -6,6 +6,9 @@ import {RouteRegistry} from './route_registry';
|
|||||||
import {
|
import {
|
||||||
ComponentInstruction,
|
ComponentInstruction,
|
||||||
Instruction,
|
Instruction,
|
||||||
|
stringifyInstruction,
|
||||||
|
stringifyInstructionPath,
|
||||||
|
stringifyInstructionQuery
|
||||||
} from './instruction';
|
} from './instruction';
|
||||||
import {RouterOutlet} from './router_outlet';
|
import {RouterOutlet} from './router_outlet';
|
||||||
import {Location} from './location';
|
import {Location} from './location';
|
||||||
@ -209,7 +212,7 @@ export class Router {
|
|||||||
if (result) {
|
if (result) {
|
||||||
return this.commit(instruction, _skipLocationChange)
|
return this.commit(instruction, _skipLocationChange)
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
this._emitNavigationFinish(instruction.toRootUrl());
|
this._emitNavigationFinish(stringifyInstruction(instruction));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -217,20 +220,25 @@ export class Router {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(btford): it'd be nice to remove this method as part of cleaning up the traversal logic
|
||||||
|
// Since refactoring `Router.generate` to return an instruction rather than a string, it's not
|
||||||
|
// guaranteed that the `componentType`s for the terminal async routes have been loaded by the time
|
||||||
|
// we begin navigation. The method below simply traverses instructions and resolves any components
|
||||||
|
// for which `componentType` is not present
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_settleInstruction(instruction: Instruction): Promise<any> {
|
_settleInstruction(instruction: Instruction): Promise<any> {
|
||||||
return instruction.resolveComponent().then((_) => {
|
var unsettledInstructions: Array<Promise<any>> = [];
|
||||||
var unsettledInstructions: Array<Promise<any>> = [];
|
if (isBlank(instruction.component.componentType)) {
|
||||||
|
unsettledInstructions.push(instruction.component.resolveComponentType().then(
|
||||||
if (isPresent(instruction.child)) {
|
(type: Type) => { this.registry.configFromComponent(type); }));
|
||||||
unsettledInstructions.push(this._settleInstruction(instruction.child));
|
}
|
||||||
}
|
if (isPresent(instruction.child)) {
|
||||||
|
unsettledInstructions.push(this._settleInstruction(instruction.child));
|
||||||
StringMapWrapper.forEach(instruction.auxInstruction, (instruction, _) => {
|
}
|
||||||
unsettledInstructions.push(this._settleInstruction(instruction));
|
StringMapWrapper.forEach(instruction.auxInstruction, (instruction, _) => {
|
||||||
});
|
unsettledInstructions.push(this._settleInstruction(instruction));
|
||||||
return PromiseWrapper.all(unsettledInstructions);
|
|
||||||
});
|
});
|
||||||
|
return PromiseWrapper.all(unsettledInstructions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _emitNavigationFinish(url): void { ObservableWrapper.callEmit(this._subject, url); }
|
private _emitNavigationFinish(url): void { ObservableWrapper.callEmit(this._subject, url); }
|
||||||
@ -370,22 +378,7 @@ export class Router {
|
|||||||
* Given a URL, returns an instruction representing the component graph
|
* Given a URL, returns an instruction representing the component graph
|
||||||
*/
|
*/
|
||||||
recognize(url: string): Promise<Instruction> {
|
recognize(url: string): Promise<Instruction> {
|
||||||
var ancestorComponents = this._getAncestorComponents();
|
return this.registry.recognize(url, this.hostComponent);
|
||||||
return this.registry.recognize(url, ancestorComponents);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get all the host components for this and
|
|
||||||
*/
|
|
||||||
private _getAncestorComponents(): any[] {
|
|
||||||
var ancestorComponents = [];
|
|
||||||
var ancestorRouter = this;
|
|
||||||
do {
|
|
||||||
ancestorComponents.unshift(ancestorRouter.hostComponent);
|
|
||||||
ancestorRouter = ancestorRouter.parent;
|
|
||||||
} while (isPresent(ancestorRouter));
|
|
||||||
|
|
||||||
return ancestorComponents;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -406,27 +399,67 @@ export class Router {
|
|||||||
* app's base href.
|
* app's base href.
|
||||||
*/
|
*/
|
||||||
generate(linkParams: any[]): Instruction {
|
generate(linkParams: any[]): Instruction {
|
||||||
var ancestorComponents = this._getAncestorComponents();
|
let normalizedLinkParams = splitAndFlattenLinkParams(linkParams);
|
||||||
var startingNumberOfAncestors = ancestorComponents.length;
|
|
||||||
|
|
||||||
var nextInstruction = this.registry.generate(linkParams, ancestorComponents);
|
var first = ListWrapper.first(normalizedLinkParams);
|
||||||
if (isBlank(nextInstruction)) {
|
var rest = ListWrapper.slice(normalizedLinkParams, 1);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parentInstructionsToClone = startingNumberOfAncestors - ancestorComponents.length;
|
var router = this;
|
||||||
|
|
||||||
var router = this.parent;
|
// The first segment should be either '.' (generate from parent) or '' (generate from root).
|
||||||
for (var i = 0; i < parentInstructionsToClone; i++) {
|
// When we normalize above, we strip all the slashes, './' becomes '.' and '/' becomes ''.
|
||||||
if (isBlank(router)) {
|
if (first == '') {
|
||||||
break;
|
while (isPresent(router.parent)) {
|
||||||
|
router = router.parent;
|
||||||
}
|
}
|
||||||
|
} else if (first == '..') {
|
||||||
router = router.parent;
|
router = router.parent;
|
||||||
|
while (ListWrapper.first(rest) == '..') {
|
||||||
|
rest = ListWrapper.slice(rest, 1);
|
||||||
|
router = router.parent;
|
||||||
|
if (isBlank(router)) {
|
||||||
|
throw new BaseException(
|
||||||
|
`Link "${ListWrapper.toJSON(linkParams)}" has too many "../" segments.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (first != '.') {
|
||||||
|
// For a link with no leading `./`, `/`, or `../`, we look for a sibling and child.
|
||||||
|
// If both exist, we throw. Otherwise, we prefer whichever exists.
|
||||||
|
var childRouteExists = this.registry.hasRoute(first, this.hostComponent);
|
||||||
|
var parentRouteExists =
|
||||||
|
isPresent(this.parent) && this.registry.hasRoute(first, this.parent.hostComponent);
|
||||||
|
|
||||||
|
if (parentRouteExists && childRouteExists) {
|
||||||
|
let msg =
|
||||||
|
`Link "${ListWrapper.toJSON(linkParams)}" is ambiguous, use "./" or "../" to disambiguate.`;
|
||||||
|
throw new BaseException(msg);
|
||||||
|
}
|
||||||
|
if (parentRouteExists) {
|
||||||
|
router = this.parent;
|
||||||
|
}
|
||||||
|
rest = linkParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (isPresent(router) && isPresent(router._currentInstruction)) {
|
if (rest[rest.length - 1] == '') {
|
||||||
nextInstruction = router._currentInstruction.replaceChild(nextInstruction);
|
rest.pop();
|
||||||
router = router.parent;
|
}
|
||||||
|
|
||||||
|
if (rest.length < 1) {
|
||||||
|
let msg = `Link "${ListWrapper.toJSON(linkParams)}" must include a route name.`;
|
||||||
|
throw new BaseException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextInstruction = this.registry.generate(rest, router.hostComponent);
|
||||||
|
|
||||||
|
var url = [];
|
||||||
|
var parent = router.parent;
|
||||||
|
while (isPresent(parent)) {
|
||||||
|
url.unshift(parent._currentInstruction);
|
||||||
|
parent = parent.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (url.length > 0) {
|
||||||
|
nextInstruction = url.pop().replaceChild(nextInstruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextInstruction;
|
return nextInstruction;
|
||||||
@ -449,8 +482,8 @@ export class RootRouter extends Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
|
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
|
||||||
var emitPath = instruction.toUrlPath();
|
var emitPath = stringifyInstructionPath(instruction);
|
||||||
var emitQuery = instruction.toUrlQuery();
|
var emitQuery = stringifyInstructionQuery(instruction);
|
||||||
if (emitPath.length > 0) {
|
if (emitPath.length > 0) {
|
||||||
emitPath = '/' + emitPath;
|
emitPath = '/' + emitPath;
|
||||||
}
|
}
|
||||||
@ -488,6 +521,20 @@ class ChildRouter extends Router {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given: ['/a/b', {c: 2}]
|
||||||
|
* Returns: ['', 'a', 'b', {c: 2}]
|
||||||
|
*/
|
||||||
|
function splitAndFlattenLinkParams(linkParams: any[]): any[] {
|
||||||
|
return linkParams.reduce((accumulation: any[], item) => {
|
||||||
|
if (isString(item)) {
|
||||||
|
let strItem: string = item;
|
||||||
|
return accumulation.concat(strItem.split('/'));
|
||||||
|
}
|
||||||
|
accumulation.push(item);
|
||||||
|
return accumulation;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
function canActivateOne(nextInstruction: Instruction,
|
function canActivateOne(nextInstruction: Instruction,
|
||||||
prevInstruction: Instruction): Promise<boolean> {
|
prevInstruction: Instruction): Promise<boolean> {
|
||||||
|
@ -3,7 +3,7 @@ import {isString} from 'angular2/src/facade/lang';
|
|||||||
|
|
||||||
import {Router} from './router';
|
import {Router} from './router';
|
||||||
import {Location} from './location';
|
import {Location} from './location';
|
||||||
import {Instruction} from './instruction';
|
import {Instruction, stringifyInstruction} from './instruction';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The RouterLink directive lets you link to specific parts of your app.
|
* The RouterLink directive lets you link to specific parts of your app.
|
||||||
@ -61,7 +61,7 @@ export class RouterLink {
|
|||||||
this._routeParams = changes;
|
this._routeParams = changes;
|
||||||
this._navigationInstruction = this._router.generate(this._routeParams);
|
this._navigationInstruction = this._router.generate(this._routeParams);
|
||||||
|
|
||||||
var navigationHref = this._navigationInstruction.toLinkUrl();
|
var navigationHref = stringifyInstruction(this._navigationInstruction);
|
||||||
this.visibleHref = this._location.prepareExternalUrl(navigationHref);
|
this.visibleHref = this._location.prepareExternalUrl(navigationHref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
|
||||||
import {isPresent, Type} from 'angular2/src/facade/lang';
|
|
||||||
|
|
||||||
import {RouteHandler} from './route_handler';
|
import {RouteHandler} from './route_handler';
|
||||||
import {RouteData, BLANK_ROUTE_DATA} from './instruction';
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
|
import {Type} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
export class SyncRouteHandler implements RouteHandler {
|
export class SyncRouteHandler implements RouteHandler {
|
||||||
public data: RouteData;
|
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_resolvedComponent: Promise<any> = null;
|
_resolvedComponent: Promise<any> = null;
|
||||||
|
|
||||||
constructor(public componentType: Type, data?: {[key: string]: any}) {
|
constructor(public componentType: Type, public data?: {[key: string]: any}) {
|
||||||
this._resolvedComponent = PromiseWrapper.resolve(componentType);
|
this._resolvedComponent = PromiseWrapper.resolve(componentType);
|
||||||
this.data = isPresent(data) ? new RouteData(data) : BLANK_ROUTE_DATA;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveComponentType(): Promise<any> { return this._resolvedComponent; }
|
resolveComponentType(): Promise<any> { return this._resolvedComponent; }
|
||||||
|
@ -1,216 +0,0 @@
|
|||||||
import {
|
|
||||||
AsyncTestCompleter,
|
|
||||||
describe,
|
|
||||||
it,
|
|
||||||
iit,
|
|
||||||
ddescribe,
|
|
||||||
expect,
|
|
||||||
inject,
|
|
||||||
beforeEach,
|
|
||||||
SpyObject
|
|
||||||
} from 'angular2/testing_internal';
|
|
||||||
|
|
||||||
import {Map, StringMapWrapper} from 'angular2/src/facade/collection';
|
|
||||||
|
|
||||||
import {RouteMatch, PathMatch, RedirectMatch} from 'angular2/src/router/route_recognizer';
|
|
||||||
import {ComponentRecognizer} from 'angular2/src/router/component_recognizer';
|
|
||||||
|
|
||||||
import {Route, Redirect} from 'angular2/src/router/route_config_decorator';
|
|
||||||
import {parser} from 'angular2/src/router/url_parser';
|
|
||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/promise';
|
|
||||||
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('ComponentRecognizer', () => {
|
|
||||||
var recognizer: ComponentRecognizer;
|
|
||||||
|
|
||||||
beforeEach(() => { recognizer = new ComponentRecognizer(); });
|
|
||||||
|
|
||||||
|
|
||||||
it('should recognize a static segment', inject([AsyncTestCompleter], (async) => {
|
|
||||||
recognizer.config(new Route({path: '/test', component: DummyCmpA}));
|
|
||||||
recognize(recognizer, '/test')
|
|
||||||
.then((solutions: RouteMatch[]) => {
|
|
||||||
expect(solutions.length).toBe(1);
|
|
||||||
expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should recognize a single slash', inject([AsyncTestCompleter], (async) => {
|
|
||||||
recognizer.config(new Route({path: '/', component: DummyCmpA}));
|
|
||||||
recognize(recognizer, '/')
|
|
||||||
.then((solutions: RouteMatch[]) => {
|
|
||||||
expect(solutions.length).toBe(1);
|
|
||||||
expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should recognize a dynamic segment', inject([AsyncTestCompleter], (async) => {
|
|
||||||
recognizer.config(new Route({path: '/user/:name', component: DummyCmpA}));
|
|
||||||
recognize(recognizer, '/user/brian')
|
|
||||||
.then((solutions: RouteMatch[]) => {
|
|
||||||
expect(solutions.length).toBe(1);
|
|
||||||
expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
|
|
||||||
expect(getParams(solutions[0])).toEqual({'name': 'brian'});
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should recognize a star segment', inject([AsyncTestCompleter], (async) => {
|
|
||||||
recognizer.config(new Route({path: '/first/*rest', component: DummyCmpA}));
|
|
||||||
recognize(recognizer, '/first/second/third')
|
|
||||||
.then((solutions: RouteMatch[]) => {
|
|
||||||
expect(solutions.length).toBe(1);
|
|
||||||
expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
|
|
||||||
expect(getParams(solutions[0])).toEqual({'rest': 'second/third'});
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should throw when given two routes that start with the same static segment', () => {
|
|
||||||
recognizer.config(new Route({path: '/hello', component: DummyCmpA}));
|
|
||||||
expect(() => recognizer.config(new Route({path: '/hello', component: DummyCmpB})))
|
|
||||||
.toThrowError('Configuration \'/hello\' conflicts with existing route \'/hello\'');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should throw when given two routes that have dynamic segments in the same order', () => {
|
|
||||||
recognizer.config(new Route({path: '/hello/:person/how/:doyoudou', component: DummyCmpA}));
|
|
||||||
expect(() => recognizer.config(
|
|
||||||
new Route({path: '/hello/:friend/how/:areyou', component: DummyCmpA})))
|
|
||||||
.toThrowError(
|
|
||||||
'Configuration \'/hello/:friend/how/:areyou\' conflicts with existing route \'/hello/:person/how/:doyoudou\'');
|
|
||||||
|
|
||||||
expect(() => recognizer.config(
|
|
||||||
new Redirect({path: '/hello/:pal/how/:goesit', redirectTo: ['/Foo']})))
|
|
||||||
.toThrowError(
|
|
||||||
'Configuration \'/hello/:pal/how/:goesit\' conflicts with existing route \'/hello/:person/how/:doyoudou\'');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should recognize redirects', inject([AsyncTestCompleter], (async) => {
|
|
||||||
recognizer.config(new Route({path: '/b', component: DummyCmpA}));
|
|
||||||
recognizer.config(new Redirect({path: '/a', redirectTo: ['B']}));
|
|
||||||
recognize(recognizer, '/a')
|
|
||||||
.then((solutions: RouteMatch[]) => {
|
|
||||||
expect(solutions.length).toBe(1);
|
|
||||||
var solution = solutions[0];
|
|
||||||
expect(solution).toBeAnInstanceOf(RedirectMatch);
|
|
||||||
if (solution instanceof RedirectMatch) {
|
|
||||||
expect(solution.redirectTo).toEqual(['B']);
|
|
||||||
}
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should generate URLs with params', () => {
|
|
||||||
recognizer.config(new Route({path: '/app/user/:name', component: DummyCmpA, name: 'User'}));
|
|
||||||
var instruction = recognizer.generate('User', {'name': 'misko'});
|
|
||||||
expect(instruction.urlPath).toEqual('app/user/misko');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should generate URLs with numeric params', () => {
|
|
||||||
recognizer.config(new Route({path: '/app/page/:number', component: DummyCmpA, name: 'Page'}));
|
|
||||||
expect(recognizer.generate('Page', {'number': 42}).urlPath).toEqual('app/page/42');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should throw in the absence of required params URLs', () => {
|
|
||||||
recognizer.config(new Route({path: 'app/user/:name', component: DummyCmpA, name: 'User'}));
|
|
||||||
expect(() => recognizer.generate('User', {}))
|
|
||||||
.toThrowError('Route generator for \'name\' was not included in parameters passed.');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should throw if the route alias is not TitleCase', () => {
|
|
||||||
expect(() => recognizer.config(
|
|
||||||
new Route({path: 'app/user/:name', component: DummyCmpA, name: 'user'})))
|
|
||||||
.toThrowError(
|
|
||||||
`Route "app/user/:name" with name "user" does not begin with an uppercase letter. Route names should be CamelCase like "User".`);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe('params', () => {
|
|
||||||
it('should recognize parameters within the URL path',
|
|
||||||
inject([AsyncTestCompleter], (async) => {
|
|
||||||
recognizer.config(
|
|
||||||
new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
|
|
||||||
recognize(recognizer, '/profile/matsko?comments=all')
|
|
||||||
.then((solutions: RouteMatch[]) => {
|
|
||||||
expect(solutions.length).toBe(1);
|
|
||||||
expect(getParams(solutions[0])).toEqual({'name': 'matsko', 'comments': 'all'});
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should generate and populate the given static-based route with querystring params',
|
|
||||||
() => {
|
|
||||||
recognizer.config(
|
|
||||||
new Route({path: 'forum/featured', component: DummyCmpA, name: 'ForumPage'}));
|
|
||||||
|
|
||||||
var params = {'start': 10, 'end': 100};
|
|
||||||
|
|
||||||
var result = recognizer.generate('ForumPage', params);
|
|
||||||
expect(result.urlPath).toEqual('forum/featured');
|
|
||||||
expect(result.urlParams).toEqual(['start=10', 'end=100']);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should prefer positional params over query params',
|
|
||||||
inject([AsyncTestCompleter], (async) => {
|
|
||||||
recognizer.config(
|
|
||||||
new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
|
|
||||||
recognize(recognizer, '/profile/yegor?name=igor')
|
|
||||||
.then((solutions: RouteMatch[]) => {
|
|
||||||
expect(solutions.length).toBe(1);
|
|
||||||
expect(getParams(solutions[0])).toEqual({'name': 'yegor'});
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should ignore matrix params for the top-level component',
|
|
||||||
inject([AsyncTestCompleter], (async) => {
|
|
||||||
recognizer.config(
|
|
||||||
new Route({path: '/home/:subject', component: DummyCmpA, name: 'User'}));
|
|
||||||
recognize(recognizer, '/home;sort=asc/zero;one=1?two=2')
|
|
||||||
.then((solutions: RouteMatch[]) => {
|
|
||||||
expect(solutions.length).toBe(1);
|
|
||||||
expect(getParams(solutions[0])).toEqual({'subject': 'zero', 'two': '2'});
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function recognize(recognizer: ComponentRecognizer, url: string): Promise<RouteMatch[]> {
|
|
||||||
var parsedUrl = parser.parse(url);
|
|
||||||
return PromiseWrapper.all(recognizer.recognize(parsedUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getComponentType(routeMatch: RouteMatch): any {
|
|
||||||
if (routeMatch instanceof PathMatch) {
|
|
||||||
return routeMatch.instruction.componentType;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getParams(routeMatch: RouteMatch): any {
|
|
||||||
if (routeMatch instanceof PathMatch) {
|
|
||||||
return routeMatch.instruction.params;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class DummyCmpA {}
|
|
||||||
class DummyCmpB {}
|
|
@ -1,9 +0,0 @@
|
|||||||
# Router integration tests
|
|
||||||
|
|
||||||
These tests only mock out `Location`, and otherwise use all the real parts of routing to ensure that
|
|
||||||
various routing scenarios work as expected.
|
|
||||||
|
|
||||||
The Component Router in Angular 2 exposes only a handful of different options, but because they can
|
|
||||||
be combined and nested in so many ways, it's difficult to rigorously test all the cases.
|
|
||||||
|
|
||||||
The address this problem, we introduce `describeRouter`, `describeWith`, and `describeWithout`.
|
|
@ -1,28 +0,0 @@
|
|||||||
import {
|
|
||||||
describeRouter,
|
|
||||||
ddescribeRouter,
|
|
||||||
describeWith,
|
|
||||||
describeWithout,
|
|
||||||
describeWithAndWithout,
|
|
||||||
itShouldRoute
|
|
||||||
} from './util';
|
|
||||||
|
|
||||||
import {registerSpecs} from './impl/async_route_spec_impl';
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
registerSpecs();
|
|
||||||
|
|
||||||
ddescribeRouter('async routes', () => {
|
|
||||||
describeWithout('children', () => {
|
|
||||||
describeWith('route data', itShouldRoute);
|
|
||||||
describeWithAndWithout('params', itShouldRoute);
|
|
||||||
});
|
|
||||||
|
|
||||||
describeWith('sync children',
|
|
||||||
() => { describeWithAndWithout('default routes', itShouldRoute); });
|
|
||||||
|
|
||||||
describeWith('async children', () => {
|
|
||||||
describeWithAndWithout('params', () => { describeWithout('default routes', itShouldRoute); });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
import {
|
|
||||||
RootTestComponent,
|
|
||||||
AsyncTestCompleter,
|
|
||||||
TestComponentBuilder,
|
|
||||||
beforeEach,
|
|
||||||
ddescribe,
|
|
||||||
xdescribe,
|
|
||||||
describe,
|
|
||||||
el,
|
|
||||||
expect,
|
|
||||||
iit,
|
|
||||||
inject,
|
|
||||||
beforeEachProviders,
|
|
||||||
it,
|
|
||||||
xit
|
|
||||||
} from 'angular2/testing_internal';
|
|
||||||
|
|
||||||
import {provide, Component, Injector, Inject} from 'angular2/core';
|
|
||||||
|
|
||||||
import {Router, ROUTER_DIRECTIVES, RouteParams, RouteData, Location} from 'angular2/router';
|
|
||||||
import {RouteConfig, Route, AuxRoute, Redirect} from 'angular2/src/router/route_config_decorator';
|
|
||||||
|
|
||||||
import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
|
|
||||||
|
|
||||||
var cmpInstanceCount;
|
|
||||||
var childCmpInstanceCount;
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('auxiliary routes', () => {
|
|
||||||
|
|
||||||
var tcb: TestComponentBuilder;
|
|
||||||
var rootTC: RootTestComponent;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
childCmpInstanceCount = 0;
|
|
||||||
cmpInstanceCount = 0;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should recognize and navigate from the URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
|
||||||
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Aux'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/hello(modal)'))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate via the link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
|
||||||
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigate(['/Hello', ['Modal']]))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({selector: 'hello-cmp', template: `{{greeting}}`})
|
|
||||||
class HelloCmp {
|
|
||||||
greeting: string;
|
|
||||||
constructor() { this.greeting = 'hello'; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({selector: 'modal-cmp', template: `modal`})
|
|
||||||
class ModalCmp {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'aux-cmp',
|
|
||||||
template: 'main {<router-outlet></router-outlet>} | ' +
|
|
||||||
'aux {<router-outlet name="modal"></router-outlet>}',
|
|
||||||
directives: [ROUTER_DIRECTIVES],
|
|
||||||
})
|
|
||||||
@RouteConfig([
|
|
||||||
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
|
||||||
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Aux'})
|
|
||||||
])
|
|
||||||
class AuxCmp {
|
|
||||||
}
|
|
@ -1,655 +0,0 @@
|
|||||||
import {
|
|
||||||
AsyncTestCompleter,
|
|
||||||
beforeEach,
|
|
||||||
beforeEachProviders,
|
|
||||||
expect,
|
|
||||||
iit,
|
|
||||||
flushMicrotasks,
|
|
||||||
inject,
|
|
||||||
it,
|
|
||||||
TestComponentBuilder,
|
|
||||||
RootTestComponent,
|
|
||||||
xit,
|
|
||||||
} from 'angular2/testing_internal';
|
|
||||||
|
|
||||||
import {specs, compile, TEST_ROUTER_PROVIDERS, clickOnElement, getHref} from '../util';
|
|
||||||
|
|
||||||
import {Router, AsyncRoute, Route, Location} from 'angular2/router';
|
|
||||||
|
|
||||||
import {
|
|
||||||
HelloCmp,
|
|
||||||
helloCmpLoader,
|
|
||||||
UserCmp,
|
|
||||||
userCmpLoader,
|
|
||||||
TeamCmp,
|
|
||||||
asyncTeamLoader,
|
|
||||||
ParentCmp,
|
|
||||||
parentCmpLoader,
|
|
||||||
asyncParentCmpLoader,
|
|
||||||
asyncDefaultParentCmpLoader,
|
|
||||||
ParentWithDefaultCmp,
|
|
||||||
parentWithDefaultCmpLoader,
|
|
||||||
asyncRouteDataCmp
|
|
||||||
} from './fixture_components';
|
|
||||||
|
|
||||||
function getLinkElement(rtc: RootTestComponent) {
|
|
||||||
return rtc.debugElement.componentViewChildren[0].nativeElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
function asyncRoutesWithoutChildrenWithRouteData() {
|
|
||||||
var fixture;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should inject route data into the component', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute(
|
|
||||||
{path: '/route-data', loader: asyncRouteDataCmp, data: {isAdmin: true}})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/route-data'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('true');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should inject empty object if the route has no data property',
|
|
||||||
inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/route-data-default', loader: asyncRouteDataCmp})]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/route-data-default'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function asyncRoutesWithoutChildrenWithoutParams() {
|
|
||||||
var fixture;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/test'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
|
|
||||||
.then((_) => rtr.navigate(['/Hello']))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Hello']">go to hello</a> | <router-outlet></router-outlet>`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(fixture))).toEqual('/test');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Hello']">go to hello</a> | <router-outlet></router-outlet>`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('go to hello | ');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('go to hello | hello');
|
|
||||||
expect(location.urlChanges).toEqual(['/test']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(fixture));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function asyncRoutesWithoutChildrenWithParams() {
|
|
||||||
var fixture;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/user/igor'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
|
||||||
.then((_) => rtr.navigate(['/User', {name: 'brian'}]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `<a [router-link]="['User', {name: 'naomi'}]">greet naomi</a> | <router-outlet></router-outlet>`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(fixture))).toEqual('/user/naomi');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb, `<a [router-link]="['User', {name: 'naomi'}]">greet naomi</a> | <router-outlet></router-outlet>`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | ');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | hello naomi');
|
|
||||||
expect(location.urlChanges).toEqual(['/user/naomi']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(fixture));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate between components with different parameters',
|
|
||||||
inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/user/brian'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
|
|
||||||
})
|
|
||||||
.then((_) => rtr.navigateByUrl('/user/igor'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function asyncRoutesWithSyncChildrenWithoutDefaultRoutes() {
|
|
||||||
var fixture;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/a/b'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
|
|
||||||
.then((_) => rtr.navigate(['/Parent', 'Child']))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Parent']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(fixture))).toEqual('/a');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Parent', 'Child']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('nav to child | outer { }');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('nav to child | outer { inner { hello } }');
|
|
||||||
expect(location.urlChanges).toEqual(['/a/b']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(fixture));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function asyncRoutesWithSyncChildrenWithDefaultRoutes() {
|
|
||||||
var fixture;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/a'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigate(['/Parent']))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `<a [router-link]="['/Parent']">link to inner</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(fixture))).toEqual('/a');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb, `<a [router-link]="['/Parent']">link to inner</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('link to inner | outer { }');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('link to inner | outer { inner { hello } }');
|
|
||||||
expect(location.urlChanges).toEqual(['/a/b']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(fixture));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function asyncRoutesWithAsyncChildrenWithoutParamsWithoutDefaultRoutes() {
|
|
||||||
var rootTC;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/a/b'))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigate(['/Parent', 'Child']))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Parent', 'Child']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(rootTC))).toEqual('/a');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Parent', 'Child']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('nav to child | outer { }');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement)
|
|
||||||
.toHaveText('nav to child | outer { inner { hello } }');
|
|
||||||
expect(location.urlChanges).toEqual(['/a/b']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(rootTC));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function asyncRoutesWithAsyncChildrenWithoutParamsWithDefaultRoutes() {
|
|
||||||
var rootTC;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute(
|
|
||||||
{path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/a'))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute(
|
|
||||||
{path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigate(['/Parent']))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Parent']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute(
|
|
||||||
{path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(rootTC))).toEqual('/a');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Parent']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute(
|
|
||||||
{path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
|
|
||||||
]))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('nav to child | outer { }');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement)
|
|
||||||
.toHaveText('nav to child | outer { inner { hello } }');
|
|
||||||
expect(location.urlChanges).toEqual(['/a/b']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(rootTC));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function asyncRoutesWithAsyncChildrenWithParamsWithoutDefaultRoutes() {
|
|
||||||
var fixture;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `{ <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/team/angular/user/matias'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('{ team angular | user { hello matias } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `{ <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigate(['/Team', {id: 'angular'}, 'User', {name: 'matias'}]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('{ team angular | user { hello matias } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(
|
|
||||||
tcb,
|
|
||||||
`<a [router-link]="['/Team', {id: 'angular'}, 'User', {name: 'matias'}]">nav to matias</a> { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
|
|
||||||
]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(fixture))).toEqual('/team/angular');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(
|
|
||||||
tcb,
|
|
||||||
`<a [router-link]="['/Team', {id: 'angular'}, 'User', {name: 'matias'}]">nav to matias</a> { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
|
|
||||||
]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('nav to matias { }');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('nav to matias { team angular | user { hello matias } }');
|
|
||||||
expect(location.urlChanges).toEqual(['/team/angular/user/matias']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(fixture));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registerSpecs() {
|
|
||||||
specs['asyncRoutesWithoutChildrenWithRouteData'] = asyncRoutesWithoutChildrenWithRouteData;
|
|
||||||
specs['asyncRoutesWithoutChildrenWithoutParams'] = asyncRoutesWithoutChildrenWithoutParams;
|
|
||||||
specs['asyncRoutesWithoutChildrenWithParams'] = asyncRoutesWithoutChildrenWithParams;
|
|
||||||
specs['asyncRoutesWithSyncChildrenWithoutDefaultRoutes'] =
|
|
||||||
asyncRoutesWithSyncChildrenWithoutDefaultRoutes;
|
|
||||||
specs['asyncRoutesWithSyncChildrenWithDefaultRoutes'] =
|
|
||||||
asyncRoutesWithSyncChildrenWithDefaultRoutes;
|
|
||||||
specs['asyncRoutesWithAsyncChildrenWithoutParamsWithoutDefaultRoutes'] =
|
|
||||||
asyncRoutesWithAsyncChildrenWithoutParamsWithoutDefaultRoutes;
|
|
||||||
specs['asyncRoutesWithAsyncChildrenWithoutParamsWithDefaultRoutes'] =
|
|
||||||
asyncRoutesWithAsyncChildrenWithoutParamsWithDefaultRoutes;
|
|
||||||
specs['asyncRoutesWithAsyncChildrenWithParamsWithoutDefaultRoutes'] =
|
|
||||||
asyncRoutesWithAsyncChildrenWithParamsWithoutDefaultRoutes;
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
import {Component} from 'angular2/angular2';
|
|
||||||
import {
|
|
||||||
AsyncRoute,
|
|
||||||
Route,
|
|
||||||
Redirect,
|
|
||||||
RouteConfig,
|
|
||||||
RouteParams,
|
|
||||||
RouteData,
|
|
||||||
ROUTER_DIRECTIVES
|
|
||||||
} from 'angular2/router';
|
|
||||||
import {PromiseWrapper} from 'angular2/src/facade/async';
|
|
||||||
|
|
||||||
@Component({selector: 'hello-cmp', template: `{{greeting}}`})
|
|
||||||
export class HelloCmp {
|
|
||||||
greeting: string;
|
|
||||||
constructor() { this.greeting = 'hello'; }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function helloCmpLoader() {
|
|
||||||
return PromiseWrapper.resolve(HelloCmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({selector: 'user-cmp', template: `hello {{user}}`})
|
|
||||||
export class UserCmp {
|
|
||||||
user: string;
|
|
||||||
constructor(params: RouteParams) { this.user = params.get('name'); }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function userCmpLoader() {
|
|
||||||
return PromiseWrapper.resolve(UserCmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'parent-cmp',
|
|
||||||
template: `inner { <router-outlet></router-outlet> }`,
|
|
||||||
directives: [ROUTER_DIRECTIVES],
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/b', component: HelloCmp, name: 'Child'})])
|
|
||||||
export class ParentCmp {
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parentCmpLoader() {
|
|
||||||
return PromiseWrapper.resolve(ParentCmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'parent-cmp',
|
|
||||||
template: `inner { <router-outlet></router-outlet> }`,
|
|
||||||
directives: [ROUTER_DIRECTIVES],
|
|
||||||
})
|
|
||||||
@RouteConfig([new AsyncRoute({path: '/b', loader: helloCmpLoader, name: 'Child'})])
|
|
||||||
export class AsyncParentCmp {
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asyncParentCmpLoader() {
|
|
||||||
return PromiseWrapper.resolve(AsyncParentCmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'parent-cmp',
|
|
||||||
template: `inner { <router-outlet></router-outlet> }`,
|
|
||||||
directives: [ROUTER_DIRECTIVES],
|
|
||||||
})
|
|
||||||
@RouteConfig(
|
|
||||||
[new AsyncRoute({path: '/b', loader: helloCmpLoader, name: 'Child', useAsDefault: true})])
|
|
||||||
export class AsyncDefaultParentCmp {
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asyncDefaultParentCmpLoader() {
|
|
||||||
return PromiseWrapper.resolve(AsyncDefaultParentCmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'parent-cmp',
|
|
||||||
template: `inner { <router-outlet></router-outlet> }`,
|
|
||||||
directives: [ROUTER_DIRECTIVES],
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/b', component: HelloCmp, name: 'Child', useAsDefault: true})])
|
|
||||||
export class ParentWithDefaultCmp {
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parentWithDefaultCmpLoader() {
|
|
||||||
return PromiseWrapper.resolve(ParentWithDefaultCmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'team-cmp',
|
|
||||||
template: `team {{id}} | user { <router-outlet></router-outlet> }`,
|
|
||||||
directives: [ROUTER_DIRECTIVES],
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/user/:name', component: UserCmp, name: 'User'})])
|
|
||||||
export class TeamCmp {
|
|
||||||
id: string;
|
|
||||||
constructor(params: RouteParams) { this.id = params.get('id'); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'team-cmp',
|
|
||||||
template: `team {{id}} | user { <router-outlet></router-outlet> }`,
|
|
||||||
directives: [ROUTER_DIRECTIVES],
|
|
||||||
})
|
|
||||||
@RouteConfig([new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})])
|
|
||||||
export class AsyncTeamCmp {
|
|
||||||
id: string;
|
|
||||||
constructor(params: RouteParams) { this.id = params.get('id'); }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asyncTeamLoader() {
|
|
||||||
return PromiseWrapper.resolve(AsyncTeamCmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({selector: 'data-cmp', template: `{{myData}}`})
|
|
||||||
export class RouteDataCmp {
|
|
||||||
myData: boolean;
|
|
||||||
constructor(data: RouteData) { this.myData = data.get('isAdmin'); }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asyncRouteDataCmp() {
|
|
||||||
return PromiseWrapper.resolve(RouteDataCmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({selector: 'redirect-to-parent-cmp', template: 'redirect-to-parent'})
|
|
||||||
@RouteConfig([new Redirect({path: '/child-redirect', redirectTo: ['../HelloSib']})])
|
|
||||||
export class RedirectToParentCmp {
|
|
||||||
}
|
|
@ -1,431 +0,0 @@
|
|||||||
import {
|
|
||||||
AsyncTestCompleter,
|
|
||||||
beforeEach,
|
|
||||||
beforeEachProviders,
|
|
||||||
expect,
|
|
||||||
iit,
|
|
||||||
flushMicrotasks,
|
|
||||||
inject,
|
|
||||||
it,
|
|
||||||
TestComponentBuilder,
|
|
||||||
RootTestComponent,
|
|
||||||
xit,
|
|
||||||
} from 'angular2/testing_internal';
|
|
||||||
|
|
||||||
import {specs, compile, TEST_ROUTER_PROVIDERS, clickOnElement, getHref} from '../util';
|
|
||||||
|
|
||||||
import {Router, Route, Location} from 'angular2/router';
|
|
||||||
|
|
||||||
import {HelloCmp, UserCmp, TeamCmp, ParentCmp, ParentWithDefaultCmp} from './fixture_components';
|
|
||||||
|
|
||||||
|
|
||||||
function getLinkElement(rtc: RootTestComponent) {
|
|
||||||
return rtc.debugElement.componentViewChildren[0].nativeElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncRoutesWithoutChildrenWithoutParams() {
|
|
||||||
var fixture;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) =>
|
|
||||||
rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/test'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) =>
|
|
||||||
rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
|
|
||||||
.then((_) => rtr.navigate(['/Hello']))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Hello']">go to hello</a> | <router-outlet></router-outlet>`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) =>
|
|
||||||
rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(fixture))).toEqual('/test');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Hello']">go to hello</a> | <router-outlet></router-outlet>`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) =>
|
|
||||||
rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('go to hello | ');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('go to hello | hello');
|
|
||||||
expect(location.urlChanges).toEqual(['/test']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(fixture));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function syncRoutesWithoutChildrenWithParams() {
|
|
||||||
var fixture;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/user/igor'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
|
||||||
.then((_) => rtr.navigate(['/User', {name: 'brian'}]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `<a [router-link]="['User', {name: 'naomi'}]">greet naomi</a> | <router-outlet></router-outlet>`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(fixture))).toEqual('/user/naomi');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb, `<a [router-link]="['User', {name: 'naomi'}]">greet naomi</a> | <router-outlet></router-outlet>`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | ');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | hello naomi');
|
|
||||||
expect(location.urlChanges).toEqual(['/user/naomi']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(fixture));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate between components with different parameters',
|
|
||||||
inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/user/brian'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
|
|
||||||
})
|
|
||||||
.then((_) => rtr.navigateByUrl('/user/igor'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function syncRoutesWithSyncChildrenWithoutDefaultRoutesWithoutParams() {
|
|
||||||
var fixture;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/a/b'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
|
|
||||||
.then((_) => rtr.navigate(['/Parent', 'Child']))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Parent', 'Child']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(fixture))).toEqual('/a/b');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb, `<a [router-link]="['Parent', 'Child']">nav to child</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('nav to child | outer { }');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('nav to child | outer { inner { hello } }');
|
|
||||||
expect(location.urlChanges).toEqual(['/a/b']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(fixture));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function syncRoutesWithSyncChildrenWithoutDefaultRoutesWithParams() {
|
|
||||||
var fixture;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `{ <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/team/angular/user/matias'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('{ team angular | user { hello matias } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `{ <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
|
|
||||||
.then((_) => rtr.navigate(['/Team', {id: 'angular'}, 'User', {name: 'matias'}]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('{ team angular | user { hello matias } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(
|
|
||||||
tcb,
|
|
||||||
`<a [router-link]="['/Team', {id: 'angular'}, 'User', {name: 'matias'}]">nav to matias</a> { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(fixture))).toEqual('/team/angular/user/matias');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(
|
|
||||||
tcb,
|
|
||||||
`<a [router-link]="['/Team', {id: 'angular'}, 'User', {name: 'matias'}]">nav to matias</a> { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
|
||||||
[new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('nav to matias { }');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('nav to matias { team angular | user { hello matias } }');
|
|
||||||
expect(location.urlChanges).toEqual(['/team/angular/user/matias']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(fixture));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams() {
|
|
||||||
var fixture;
|
|
||||||
var tcb;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then(
|
|
||||||
(_) => rtr.config(
|
|
||||||
[new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/a'))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then(
|
|
||||||
(_) => rtr.config(
|
|
||||||
[new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
|
|
||||||
.then((_) => rtr.navigate(['/Parent']))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(tcb, `<a [router-link]="['/Parent']">link to inner</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then(
|
|
||||||
(_) => rtr.config(
|
|
||||||
[new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(fixture))).toEqual('/a');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb, `<a [router-link]="['/Parent']">link to inner</a> | outer { <router-outlet></router-outlet> }`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then(
|
|
||||||
(_) => rtr.config(
|
|
||||||
[new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('link to inner | outer { }');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('link to inner | outer { inner { hello } }');
|
|
||||||
expect(location.urlChanges).toEqual(['/a/b']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(fixture));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registerSpecs() {
|
|
||||||
specs['syncRoutesWithoutChildrenWithoutParams'] = syncRoutesWithoutChildrenWithoutParams;
|
|
||||||
specs['syncRoutesWithoutChildrenWithParams'] = syncRoutesWithoutChildrenWithParams;
|
|
||||||
specs['syncRoutesWithSyncChildrenWithoutDefaultRoutesWithoutParams'] =
|
|
||||||
syncRoutesWithSyncChildrenWithoutDefaultRoutesWithoutParams;
|
|
||||||
specs['syncRoutesWithSyncChildrenWithoutDefaultRoutesWithParams'] =
|
|
||||||
syncRoutesWithSyncChildrenWithoutDefaultRoutesWithParams;
|
|
||||||
specs['syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams'] =
|
|
||||||
syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams;
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ import {
|
|||||||
expect,
|
expect,
|
||||||
iit,
|
iit,
|
||||||
inject,
|
inject,
|
||||||
beforeEachProviders,
|
beforeEachBindings,
|
||||||
it,
|
it,
|
||||||
xit
|
xit
|
||||||
} from 'angular2/testing_internal';
|
} from 'angular2/testing_internal';
|
||||||
@ -25,6 +25,7 @@ import {
|
|||||||
ObservableWrapper
|
ObservableWrapper
|
||||||
} from 'angular2/src/facade/async';
|
} from 'angular2/src/facade/async';
|
||||||
|
|
||||||
|
import {RootRouter} from 'angular2/src/router/router';
|
||||||
import {Router, RouterOutlet, RouterLink, RouteParams} from 'angular2/router';
|
import {Router, RouterOutlet, RouterLink, RouteParams} from 'angular2/router';
|
||||||
import {
|
import {
|
||||||
RouteConfig,
|
RouteConfig,
|
||||||
@ -34,6 +35,9 @@ import {
|
|||||||
Redirect
|
Redirect
|
||||||
} from 'angular2/src/router/route_config_decorator';
|
} from 'angular2/src/router/route_config_decorator';
|
||||||
|
|
||||||
|
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||||
|
import {Location} from 'angular2/src/router/location';
|
||||||
|
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||||
import {
|
import {
|
||||||
OnActivate,
|
OnActivate,
|
||||||
OnDeactivate,
|
OnDeactivate,
|
||||||
@ -43,9 +47,7 @@ import {
|
|||||||
} from 'angular2/src/router/interfaces';
|
} from 'angular2/src/router/interfaces';
|
||||||
import {CanActivate} from 'angular2/src/router/lifecycle_annotations';
|
import {CanActivate} from 'angular2/src/router/lifecycle_annotations';
|
||||||
import {ComponentInstruction} from 'angular2/src/router/instruction';
|
import {ComponentInstruction} from 'angular2/src/router/instruction';
|
||||||
|
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
|
||||||
|
|
||||||
import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
|
|
||||||
|
|
||||||
var cmpInstanceCount;
|
var cmpInstanceCount;
|
||||||
var log: string[];
|
var log: string[];
|
||||||
@ -59,7 +61,17 @@ export function main() {
|
|||||||
var fixture: ComponentFixture;
|
var fixture: ComponentFixture;
|
||||||
var rtr;
|
var rtr;
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
beforeEachBindings(() => [
|
||||||
|
RouteRegistry,
|
||||||
|
DirectiveResolver,
|
||||||
|
provide(Location, {useClass: SpyLocation}),
|
||||||
|
provide(Router,
|
||||||
|
{
|
||||||
|
useFactory:
|
||||||
|
(registry, location) => { return new RootRouter(registry, location, MyComp); },
|
||||||
|
deps: [RouteRegistry, Location]
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||||
tcb = tcBuilder;
|
tcb = tcBuilder;
|
||||||
@ -69,9 +81,17 @@ export function main() {
|
|||||||
eventBus = new EventEmitter();
|
eventBus = new EventEmitter();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
function compile(template: string = "<router-outlet></router-outlet>") {
|
||||||
|
return tcb.overrideView(MyComp, new View({
|
||||||
|
template: ('<div>' + template + '</div>'),
|
||||||
|
directives: [RouterOutlet, RouterLink]
|
||||||
|
}))
|
||||||
|
.createAsync(MyComp)
|
||||||
|
.then((tc) => { fixture = tc; });
|
||||||
|
}
|
||||||
|
|
||||||
it('should call the onActivate hook', inject([AsyncTestCompleter], (async) => {
|
it('should call the onActivate hook', inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/on-activate'))
|
.then((_) => rtr.navigateByUrl('/on-activate'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -84,8 +104,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should wait for a parent component\'s onActivate hook to resolve before calling its child\'s',
|
it('should wait for a parent component\'s onActivate hook to resolve before calling its child\'s',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
ObservableWrapper.subscribe<string>(eventBus, (ev) => {
|
ObservableWrapper.subscribe<string>(eventBus, (ev) => {
|
||||||
@ -107,8 +126,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should call the onDeactivate hook', inject([AsyncTestCompleter], (async) => {
|
it('should call the onDeactivate hook', inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/on-deactivate'))
|
.then((_) => rtr.navigateByUrl('/on-deactivate'))
|
||||||
.then((_) => rtr.navigateByUrl('/a'))
|
.then((_) => rtr.navigateByUrl('/a'))
|
||||||
@ -122,8 +140,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should wait for a child component\'s onDeactivate hook to resolve before calling its parent\'s',
|
it('should wait for a child component\'s onDeactivate hook to resolve before calling its parent\'s',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/parent-deactivate/child-deactivate'))
|
.then((_) => rtr.navigateByUrl('/parent-deactivate/child-deactivate'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -148,8 +165,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should reuse a component when the canReuse hook returns true',
|
it('should reuse a component when the canReuse hook returns true',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/on-reuse/1/a'))
|
.then((_) => rtr.navigateByUrl('/on-reuse/1/a'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -171,8 +187,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should not reuse a component when the canReuse hook returns false',
|
it('should not reuse a component when the canReuse hook returns false',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/never-reuse/1/a'))
|
.then((_) => rtr.navigateByUrl('/never-reuse/1/a'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -193,8 +208,7 @@ export function main() {
|
|||||||
|
|
||||||
|
|
||||||
it('should navigate when canActivate returns true', inject([AsyncTestCompleter], (async) => {
|
it('should navigate when canActivate returns true', inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
ObservableWrapper.subscribe<string>(eventBus, (ev) => {
|
ObservableWrapper.subscribe<string>(eventBus, (ev) => {
|
||||||
@ -214,8 +228,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should not navigate when canActivate returns false',
|
it('should not navigate when canActivate returns false',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
ObservableWrapper.subscribe<string>(eventBus, (ev) => {
|
ObservableWrapper.subscribe<string>(eventBus, (ev) => {
|
||||||
@ -235,8 +248,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should navigate away when canDeactivate returns true',
|
it('should navigate away when canDeactivate returns true',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/can-deactivate/a'))
|
.then((_) => rtr.navigateByUrl('/can-deactivate/a'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -261,8 +273,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should not navigate away when canDeactivate returns false',
|
it('should not navigate away when canDeactivate returns false',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/can-deactivate/a'))
|
.then((_) => rtr.navigateByUrl('/can-deactivate/a'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -288,8 +299,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should run activation and deactivation hooks in the correct order',
|
it('should run activation and deactivation hooks in the correct order',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/activation-hooks/child'))
|
.then((_) => rtr.navigateByUrl('/activation-hooks/child'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -315,8 +325,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should only run reuse hooks when reusing', inject([AsyncTestCompleter], (async) => {
|
it('should only run reuse hooks when reusing', inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/reuse-hooks/1'))
|
.then((_) => rtr.navigateByUrl('/reuse-hooks/1'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -343,7 +352,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should not run reuse hooks when not reusing', inject([AsyncTestCompleter], (async) => {
|
it('should not run reuse hooks when not reusing', inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
.then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/reuse-hooks/1'))
|
.then((_) => rtr.navigateByUrl('/reuse-hooks/1'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -374,16 +383,23 @@ export function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Component({selector: 'a-cmp', template: "A"})
|
@Component({selector: 'a-cmp'})
|
||||||
|
@View({template: "A"})
|
||||||
class A {
|
class A {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Component({selector: 'b-cmp', template: "B"})
|
@Component({selector: 'b-cmp'})
|
||||||
|
@View({template: "B"})
|
||||||
class B {
|
class B {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'my-comp'})
|
||||||
|
class MyComp {
|
||||||
|
name;
|
||||||
|
}
|
||||||
|
|
||||||
function logHook(name: string, next: ComponentInstruction, prev: ComponentInstruction) {
|
function logHook(name: string, next: ComponentInstruction, prev: ComponentInstruction) {
|
||||||
var message = name + ': ' + (isPresent(prev) ? ('/' + prev.urlPath) : 'null') + ' -> ' +
|
var message = name + ': ' + (isPresent(prev) ? ('/' + prev.urlPath) : 'null') + ' -> ' +
|
||||||
(isPresent(next) ? ('/' + next.urlPath) : 'null');
|
(isPresent(next) ? ('/' + next.urlPath) : 'null');
|
||||||
@ -391,18 +407,16 @@ function logHook(name: string, next: ComponentInstruction, prev: ComponentInstru
|
|||||||
ObservableWrapper.callEmit(eventBus, message);
|
ObservableWrapper.callEmit(eventBus, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'activate-cmp', template: 'activate cmp'})
|
@Component({selector: 'activate-cmp'})
|
||||||
|
@View({template: 'activate cmp'})
|
||||||
class ActivateCmp implements OnActivate {
|
class ActivateCmp implements OnActivate {
|
||||||
onActivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
onActivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
||||||
logHook('activate', next, prev);
|
logHook('activate', next, prev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'parent-activate-cmp'})
|
||||||
selector: 'parent-activate-cmp',
|
@View({template: `parent {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
template: `parent {<router-outlet></router-outlet>}`,
|
|
||||||
directives: [RouterOutlet]
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/child-activate', component: ActivateCmp})])
|
@RouteConfig([new Route({path: '/child-activate', component: ActivateCmp})])
|
||||||
class ParentActivateCmp implements OnActivate {
|
class ParentActivateCmp implements OnActivate {
|
||||||
onActivate(next: ComponentInstruction, prev: ComponentInstruction): Promise<any> {
|
onActivate(next: ComponentInstruction, prev: ComponentInstruction): Promise<any> {
|
||||||
@ -412,14 +426,16 @@ class ParentActivateCmp implements OnActivate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'deactivate-cmp', template: 'deactivate cmp'})
|
@Component({selector: 'deactivate-cmp'})
|
||||||
|
@View({template: 'deactivate cmp'})
|
||||||
class DeactivateCmp implements OnDeactivate {
|
class DeactivateCmp implements OnDeactivate {
|
||||||
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
||||||
logHook('deactivate', next, prev);
|
logHook('deactivate', next, prev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'deactivate-cmp', template: 'deactivate cmp'})
|
@Component({selector: 'deactivate-cmp'})
|
||||||
|
@View({template: 'deactivate cmp'})
|
||||||
class WaitDeactivateCmp implements OnDeactivate {
|
class WaitDeactivateCmp implements OnDeactivate {
|
||||||
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction): Promise<any> {
|
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction): Promise<any> {
|
||||||
completer = PromiseWrapper.completer();
|
completer = PromiseWrapper.completer();
|
||||||
@ -428,11 +444,8 @@ class WaitDeactivateCmp implements OnDeactivate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'parent-deactivate-cmp'})
|
||||||
selector: 'parent-deactivate-cmp',
|
@View({template: `parent {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
template: `parent {<router-outlet></router-outlet>}`,
|
|
||||||
directives: [RouterOutlet]
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/child-deactivate', component: WaitDeactivateCmp})])
|
@RouteConfig([new Route({path: '/child-deactivate', component: WaitDeactivateCmp})])
|
||||||
class ParentDeactivateCmp implements OnDeactivate {
|
class ParentDeactivateCmp implements OnDeactivate {
|
||||||
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
||||||
@ -440,37 +453,26 @@ class ParentDeactivateCmp implements OnDeactivate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'reuse-cmp'})
|
||||||
selector: 'reuse-cmp',
|
@View({template: `reuse {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
template: `reuse {<router-outlet></router-outlet>}`,
|
|
||||||
directives: [RouterOutlet]
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
||||||
class ReuseCmp implements OnReuse,
|
class ReuseCmp implements OnReuse, CanReuse {
|
||||||
CanReuse {
|
|
||||||
constructor() { cmpInstanceCount += 1; }
|
constructor() { cmpInstanceCount += 1; }
|
||||||
canReuse(next: ComponentInstruction, prev: ComponentInstruction) { return true; }
|
canReuse(next: ComponentInstruction, prev: ComponentInstruction) { return true; }
|
||||||
onReuse(next: ComponentInstruction, prev: ComponentInstruction) { logHook('reuse', next, prev); }
|
onReuse(next: ComponentInstruction, prev: ComponentInstruction) { logHook('reuse', next, prev); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'never-reuse-cmp'})
|
||||||
selector: 'never-reuse-cmp',
|
@View({template: `reuse {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
template: `reuse {<router-outlet></router-outlet>}`,
|
|
||||||
directives: [RouterOutlet]
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
||||||
class NeverReuseCmp implements OnReuse,
|
class NeverReuseCmp implements OnReuse, CanReuse {
|
||||||
CanReuse {
|
|
||||||
constructor() { cmpInstanceCount += 1; }
|
constructor() { cmpInstanceCount += 1; }
|
||||||
canReuse(next: ComponentInstruction, prev: ComponentInstruction) { return false; }
|
canReuse(next: ComponentInstruction, prev: ComponentInstruction) { return false; }
|
||||||
onReuse(next: ComponentInstruction, prev: ComponentInstruction) { logHook('reuse', next, prev); }
|
onReuse(next: ComponentInstruction, prev: ComponentInstruction) { logHook('reuse', next, prev); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'can-activate-cmp'})
|
||||||
selector: 'can-activate-cmp',
|
@View({template: `canActivate {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
template: `canActivate {<router-outlet></router-outlet>}`,
|
|
||||||
directives: [RouterOutlet]
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
||||||
@CanActivate(CanActivateCmp.canActivate)
|
@CanActivate(CanActivateCmp.canActivate)
|
||||||
class CanActivateCmp {
|
class CanActivateCmp {
|
||||||
@ -481,11 +483,8 @@ class CanActivateCmp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'can-deactivate-cmp'})
|
||||||
selector: 'can-deactivate-cmp',
|
@View({template: `canDeactivate {<router-outlet></router-outlet>}`, directives: [RouterOutlet]})
|
||||||
template: `canDeactivate {<router-outlet></router-outlet>}`,
|
|
||||||
directives: [RouterOutlet]
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
@RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
|
||||||
class CanDeactivateCmp implements CanDeactivate {
|
class CanDeactivateCmp implements CanDeactivate {
|
||||||
canDeactivate(next: ComponentInstruction, prev: ComponentInstruction): Promise<boolean> {
|
canDeactivate(next: ComponentInstruction, prev: ComponentInstruction): Promise<boolean> {
|
||||||
@ -495,7 +494,8 @@ class CanDeactivateCmp implements CanDeactivate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'all-hooks-child-cmp', template: `child`})
|
@Component({selector: 'all-hooks-child-cmp'})
|
||||||
|
@View({template: `child`})
|
||||||
@CanActivate(AllHooksChildCmp.canActivate)
|
@CanActivate(AllHooksChildCmp.canActivate)
|
||||||
class AllHooksChildCmp implements CanDeactivate, OnDeactivate, OnActivate {
|
class AllHooksChildCmp implements CanDeactivate, OnDeactivate, OnActivate {
|
||||||
canDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
canDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
||||||
@ -517,15 +517,11 @@ class AllHooksChildCmp implements CanDeactivate, OnDeactivate, OnActivate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'all-hooks-parent-cmp'})
|
||||||
selector: 'all-hooks-parent-cmp',
|
@View({template: `<router-outlet></router-outlet>`, directives: [RouterOutlet]})
|
||||||
template: `<router-outlet></router-outlet>`,
|
|
||||||
directives: [RouterOutlet]
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/child', component: AllHooksChildCmp})])
|
@RouteConfig([new Route({path: '/child', component: AllHooksChildCmp})])
|
||||||
@CanActivate(AllHooksParentCmp.canActivate)
|
@CanActivate(AllHooksParentCmp.canActivate)
|
||||||
class AllHooksParentCmp implements CanDeactivate,
|
class AllHooksParentCmp implements CanDeactivate, OnDeactivate, OnActivate {
|
||||||
OnDeactivate, OnActivate {
|
|
||||||
canDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
canDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
||||||
logHook('canDeactivate parent', next, prev);
|
logHook('canDeactivate parent', next, prev);
|
||||||
return true;
|
return true;
|
||||||
@ -545,7 +541,8 @@ class AllHooksParentCmp implements CanDeactivate,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'reuse-hooks-cmp', template: 'reuse hooks cmp'})
|
@Component({selector: 'reuse-hooks-cmp'})
|
||||||
|
@View({template: 'reuse hooks cmp'})
|
||||||
@CanActivate(ReuseHooksCmp.canActivate)
|
@CanActivate(ReuseHooksCmp.canActivate)
|
||||||
class ReuseHooksCmp implements OnActivate, OnReuse, OnDeactivate, CanReuse, CanDeactivate {
|
class ReuseHooksCmp implements OnActivate, OnReuse, OnDeactivate, CanReuse, CanDeactivate {
|
||||||
canReuse(next: ComponentInstruction, prev: ComponentInstruction): Promise<any> {
|
canReuse(next: ComponentInstruction, prev: ComponentInstruction): Promise<any> {
|
||||||
@ -577,11 +574,8 @@ class ReuseHooksCmp implements OnActivate, OnReuse, OnDeactivate, CanReuse, CanD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'lifecycle-cmp'})
|
||||||
selector: 'lifecycle-cmp',
|
@View({template: `<router-outlet></router-outlet>`, directives: [RouterOutlet]})
|
||||||
template: `<router-outlet></router-outlet>`,
|
|
||||||
directives: [RouterOutlet]
|
|
||||||
})
|
|
||||||
@RouteConfig([
|
@RouteConfig([
|
||||||
new Route({path: '/a', component: A}),
|
new Route({path: '/a', component: A}),
|
||||||
new Route({path: '/on-activate', component: ActivateCmp}),
|
new Route({path: '/on-activate', component: ActivateCmp}),
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
expect,
|
expect,
|
||||||
iit,
|
iit,
|
||||||
inject,
|
inject,
|
||||||
beforeEachProviders,
|
beforeEachBindings,
|
||||||
it,
|
it,
|
||||||
xit
|
xit
|
||||||
} from 'angular2/testing_internal';
|
} from 'angular2/testing_internal';
|
||||||
@ -18,7 +18,8 @@ import {
|
|||||||
import {provide, Component, View, Injector, Inject} from 'angular2/core';
|
import {provide, Component, View, Injector, Inject} from 'angular2/core';
|
||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
|
|
||||||
import {Router, RouterOutlet, RouterLink, RouteParams, RouteData, Location} from 'angular2/router';
|
import {RootRouter} from 'angular2/src/router/router';
|
||||||
|
import {Router, RouterOutlet, RouterLink, RouteParams, RouteData} from 'angular2/router';
|
||||||
import {
|
import {
|
||||||
RouteConfig,
|
RouteConfig,
|
||||||
Route,
|
Route,
|
||||||
@ -27,10 +28,14 @@ import {
|
|||||||
Redirect
|
Redirect
|
||||||
} from 'angular2/src/router/route_config_decorator';
|
} from 'angular2/src/router/route_config_decorator';
|
||||||
|
|
||||||
import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
|
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||||
|
import {Location} from 'angular2/src/router/location';
|
||||||
|
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||||
|
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
|
||||||
|
|
||||||
var cmpInstanceCount;
|
var cmpInstanceCount;
|
||||||
var childCmpInstanceCount;
|
var childCmpInstanceCount;
|
||||||
|
var log: string[];
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('navigation', () => {
|
describe('navigation', () => {
|
||||||
@ -39,18 +44,37 @@ export function main() {
|
|||||||
var fixture: ComponentFixture;
|
var fixture: ComponentFixture;
|
||||||
var rtr;
|
var rtr;
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
beforeEachBindings(() => [
|
||||||
|
RouteRegistry,
|
||||||
|
DirectiveResolver,
|
||||||
|
provide(Location, {useClass: SpyLocation}),
|
||||||
|
provide(Router,
|
||||||
|
{
|
||||||
|
useFactory:
|
||||||
|
(registry, location) => { return new RootRouter(registry, location, MyComp); },
|
||||||
|
deps: [RouteRegistry, Location]
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||||
tcb = tcBuilder;
|
tcb = tcBuilder;
|
||||||
rtr = router;
|
rtr = router;
|
||||||
childCmpInstanceCount = 0;
|
childCmpInstanceCount = 0;
|
||||||
cmpInstanceCount = 0;
|
cmpInstanceCount = 0;
|
||||||
|
log = [];
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
function compile(template: string = "<router-outlet></router-outlet>") {
|
||||||
|
return tcb.overrideView(MyComp, new View({
|
||||||
|
template: ('<div>' + template + '</div>'),
|
||||||
|
directives: [RouterOutlet, RouterLink]
|
||||||
|
}))
|
||||||
|
.createAsync(MyComp)
|
||||||
|
.then((tc) => { fixture = tc; });
|
||||||
|
}
|
||||||
|
|
||||||
it('should work in a simple case', inject([AsyncTestCompleter], (async) => {
|
it('should work in a simple case', inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/test', component: HelloCmp})]))
|
.then((_) => rtr.config([new Route({path: '/test', component: HelloCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/test'))
|
.then((_) => rtr.navigateByUrl('/test'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -63,8 +87,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should navigate between components with different parameters',
|
it('should navigate between components with different parameters',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/user/:name', component: UserCmp})]))
|
.then((_) => rtr.config([new Route({path: '/user/:name', component: UserCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/user/brian'))
|
.then((_) => rtr.navigateByUrl('/user/brian'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -79,9 +102,9 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
it('should navigate to child routes', inject([AsyncTestCompleter], (async) => {
|
it('should navigate to child routes', inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb, 'outer { <router-outlet></router-outlet> }')
|
compile('outer { <router-outlet></router-outlet> }')
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
|
.then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/a/b'))
|
.then((_) => rtr.navigateByUrl('/a/b'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -93,9 +116,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should navigate to child routes that capture an empty path',
|
it('should navigate to child routes that capture an empty path',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile('outer { <router-outlet></router-outlet> }')
|
||||||
compile(tcb, 'outer { <router-outlet></router-outlet> }')
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
|
.then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/a'))
|
.then((_) => rtr.navigateByUrl('/a'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -105,9 +126,9 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
it('should navigate to child routes of async routes', inject([AsyncTestCompleter], (async) => {
|
it('should navigate to child routes of async routes', inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb, 'outer { <router-outlet></router-outlet> }')
|
compile('outer { <router-outlet></router-outlet> }')
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new AsyncRoute({path: '/a/...', loader: parentLoader})]))
|
.then((_) => rtr.config([new AsyncRoute({path: '/a/...', loader: parentLoader})]))
|
||||||
.then((_) => rtr.navigateByUrl('/a/b'))
|
.then((_) => rtr.navigateByUrl('/a/b'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -117,9 +138,26 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should recognize and apply redirects',
|
||||||
|
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config([
|
||||||
|
new Redirect({path: '/original', redirectTo: '/redirected'}),
|
||||||
|
new Route({path: '/redirected', component: HelloCmp})
|
||||||
|
]))
|
||||||
|
.then((_) => rtr.navigateByUrl('/original'))
|
||||||
|
.then((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||||
|
expect(location.urlChanges).toEqual(['/redirected']);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
it('should reuse common parent components', inject([AsyncTestCompleter], (async) => {
|
it('should reuse common parent components', inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/team/:id/...', component: TeamCmp})]))
|
.then((_) => rtr.config([new Route({path: '/team/:id/...', component: TeamCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/team/angular/user/rado'))
|
.then((_) => rtr.navigateByUrl('/team/angular/user/rado'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -139,8 +177,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should not reuse children when parent components change',
|
it('should not reuse children when parent components change',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([new Route({path: '/team/:id/...', component: TeamCmp})]))
|
.then((_) => rtr.config([new Route({path: '/team/:id/...', component: TeamCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/team/angular/user/rado'))
|
.then((_) => rtr.navigateByUrl('/team/angular/user/rado'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -160,8 +197,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should inject route data into component', inject([AsyncTestCompleter], (async) => {
|
it('should inject route data into component', inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
.then((_) => rtr.config([
|
||||||
new Route({path: '/route-data', component: RouteDataCmp, data: {isAdmin: true}})
|
new Route({path: '/route-data', component: RouteDataCmp, data: {isAdmin: true}})
|
||||||
]))
|
]))
|
||||||
@ -175,11 +211,10 @@ export function main() {
|
|||||||
|
|
||||||
it('should inject route data into component with AsyncRoute',
|
it('should inject route data into component with AsyncRoute',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
.then((_) => rtr.config([
|
||||||
new AsyncRoute(
|
new AsyncRoute(
|
||||||
{path: '/route-data', loader: asyncRouteDataCmp, data: {isAdmin: true}})
|
{path: '/route-data', loader: AsyncRouteDataCmp, data: {isAdmin: true}})
|
||||||
]))
|
]))
|
||||||
.then((_) => rtr.navigateByUrl('/route-data'))
|
.then((_) => rtr.navigateByUrl('/route-data'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -191,8 +226,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should inject empty object if the route has no data property',
|
it('should inject empty object if the route has no data property',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
compile(tcb)
|
compile()
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config(
|
.then((_) => rtr.config(
|
||||||
[new Route({path: '/route-data-default', component: RouteDataCmp})]))
|
[new Route({path: '/route-data-default', component: RouteDataCmp})]))
|
||||||
.then((_) => rtr.navigateByUrl('/route-data-default'))
|
.then((_) => rtr.navigateByUrl('/route-data-default'))
|
||||||
@ -202,28 +236,45 @@ export function main() {
|
|||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
describe('auxiliary routes', () => {
|
||||||
|
it('should recognize a simple case', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config([new Route({path: '/...', component: AuxCmp})]))
|
||||||
|
.then((_) => rtr.navigateByUrl('/hello(modal)'))
|
||||||
|
.then((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.debugElement.nativeElement)
|
||||||
|
.toHaveText('main {hello} | aux {modal}');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Component({selector: 'hello-cmp', template: `{{greeting}}`})
|
@Component({selector: 'hello-cmp'})
|
||||||
|
@View({template: "{{greeting}}"})
|
||||||
class HelloCmp {
|
class HelloCmp {
|
||||||
greeting: string;
|
greeting: string;
|
||||||
constructor() { this.greeting = 'hello'; }
|
constructor() { this.greeting = "hello"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function asyncRouteDataCmp() {
|
function AsyncRouteDataCmp() {
|
||||||
return PromiseWrapper.resolve(RouteDataCmp);
|
return PromiseWrapper.resolve(RouteDataCmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'data-cmp', template: `{{myData}}`})
|
@Component({selector: 'data-cmp'})
|
||||||
|
@View({template: "{{myData}}"})
|
||||||
class RouteDataCmp {
|
class RouteDataCmp {
|
||||||
myData: boolean;
|
myData: boolean;
|
||||||
constructor(data: RouteData) { this.myData = data.get('isAdmin'); }
|
constructor(data: RouteData) { this.myData = data.get('isAdmin'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'user-cmp', template: `hello {{user}}`})
|
@Component({selector: 'user-cmp'})
|
||||||
|
@View({template: "hello {{user}}"})
|
||||||
class UserCmp {
|
class UserCmp {
|
||||||
user: string;
|
user: string;
|
||||||
constructor(params: RouteParams) {
|
constructor(params: RouteParams) {
|
||||||
@ -237,9 +288,9 @@ function parentLoader() {
|
|||||||
return PromiseWrapper.resolve(ParentCmp);
|
return PromiseWrapper.resolve(ParentCmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'parent-cmp'})
|
||||||
selector: 'parent-cmp',
|
@View({
|
||||||
template: `inner { <router-outlet></router-outlet> }`,
|
template: "inner { <router-outlet></router-outlet> }",
|
||||||
directives: [RouterOutlet],
|
directives: [RouterOutlet],
|
||||||
})
|
})
|
||||||
@RouteConfig([
|
@RouteConfig([
|
||||||
@ -247,12 +298,13 @@ function parentLoader() {
|
|||||||
new Route({path: '/', component: HelloCmp}),
|
new Route({path: '/', component: HelloCmp}),
|
||||||
])
|
])
|
||||||
class ParentCmp {
|
class ParentCmp {
|
||||||
|
constructor() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'team-cmp'})
|
||||||
selector: 'team-cmp',
|
@View({
|
||||||
template: `team {{id}} { <router-outlet></router-outlet> }`,
|
template: "team {{id}} { <router-outlet></router-outlet> }",
|
||||||
directives: [RouterOutlet],
|
directives: [RouterOutlet],
|
||||||
})
|
})
|
||||||
@RouteConfig([new Route({path: '/user/:name', component: UserCmp})])
|
@RouteConfig([new Route({path: '/user/:name', component: UserCmp})])
|
||||||
@ -263,3 +315,27 @@ class TeamCmp {
|
|||||||
cmpInstanceCount += 1;
|
cmpInstanceCount += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'my-comp'})
|
||||||
|
class MyComp {
|
||||||
|
name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'modal-cmp'})
|
||||||
|
@View({template: "modal"})
|
||||||
|
class ModalCmp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'aux-cmp'})
|
||||||
|
@View({
|
||||||
|
template: 'main {<router-outlet></router-outlet>} | ' +
|
||||||
|
'aux {<router-outlet name="modal"></router-outlet>}',
|
||||||
|
directives: [RouterOutlet],
|
||||||
|
})
|
||||||
|
@RouteConfig([
|
||||||
|
new Route({path: '/hello', component: HelloCmp}),
|
||||||
|
new AuxRoute({path: '/modal', component: ModalCmp}),
|
||||||
|
])
|
||||||
|
class AuxCmp {
|
||||||
|
}
|
||||||
|
@ -1,121 +0,0 @@
|
|||||||
import {
|
|
||||||
RootTestComponent,
|
|
||||||
AsyncTestCompleter,
|
|
||||||
TestComponentBuilder,
|
|
||||||
beforeEach,
|
|
||||||
ddescribe,
|
|
||||||
xdescribe,
|
|
||||||
describe,
|
|
||||||
el,
|
|
||||||
expect,
|
|
||||||
iit,
|
|
||||||
inject,
|
|
||||||
beforeEachProviders,
|
|
||||||
it,
|
|
||||||
xit
|
|
||||||
} from 'angular2/testing_internal';
|
|
||||||
|
|
||||||
import {Router, RouterOutlet, RouterLink, RouteParams, RouteData, Location} from 'angular2/router';
|
|
||||||
import {
|
|
||||||
RouteConfig,
|
|
||||||
Route,
|
|
||||||
AuxRoute,
|
|
||||||
AsyncRoute,
|
|
||||||
Redirect
|
|
||||||
} from 'angular2/src/router/route_config_decorator';
|
|
||||||
|
|
||||||
import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
|
|
||||||
import {HelloCmp, RedirectToParentCmp} from './impl/fixture_components';
|
|
||||||
|
|
||||||
var cmpInstanceCount;
|
|
||||||
var childCmpInstanceCount;
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('redirects', () => {
|
|
||||||
|
|
||||||
var tcb: TestComponentBuilder;
|
|
||||||
var rootTC: RootTestComponent;
|
|
||||||
var rtr;
|
|
||||||
|
|
||||||
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
||||||
|
|
||||||
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
||||||
tcb = tcBuilder;
|
|
||||||
rtr = router;
|
|
||||||
childCmpInstanceCount = 0;
|
|
||||||
cmpInstanceCount = 0;
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should apply when navigating by URL',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new Redirect({path: '/original', redirectTo: ['Hello']}),
|
|
||||||
new Route({path: '/redirected', component: HelloCmp, name: 'Hello'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/original'))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('hello');
|
|
||||||
expect(location.urlChanges).toEqual(['/redirected']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should recognize and apply absolute redirects',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new Redirect({path: '/original', redirectTo: ['/Hello']}),
|
|
||||||
new Route({path: '/redirected', component: HelloCmp, name: 'Hello'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/original'))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('hello');
|
|
||||||
expect(location.urlChanges).toEqual(['/redirected']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should recognize and apply relative child redirects',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new Redirect({path: '/original', redirectTo: ['./Hello']}),
|
|
||||||
new Route({path: '/redirected', component: HelloCmp, name: 'Hello'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/original'))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('hello');
|
|
||||||
expect(location.urlChanges).toEqual(['/redirected']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should recognize and apply relative parent redirects',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(tcb)
|
|
||||||
.then((rtc) => {rootTC = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new Route({path: '/original/...', component: RedirectToParentCmp}),
|
|
||||||
new Route({path: '/redirected', component: HelloCmp, name: 'HelloSib'})
|
|
||||||
]))
|
|
||||||
.then((_) => rtr.navigateByUrl('/original/child-redirect'))
|
|
||||||
.then((_) => {
|
|
||||||
rootTC.detectChanges();
|
|
||||||
expect(rootTC.debugElement.nativeElement).toHaveText('hello');
|
|
||||||
expect(location.urlChanges).toEqual(['/redirected']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
@ -38,39 +38,44 @@ import {ApplicationRef} from 'angular2/src/core/application_ref';
|
|||||||
import {MockApplicationRef} from 'angular2/src/mock/mock_application_ref';
|
import {MockApplicationRef} from 'angular2/src/mock/mock_application_ref';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('router bootstrap', () => {
|
describe('router injectables', () => {
|
||||||
beforeEachProviders(() => [
|
beforeEachProviders(() => {
|
||||||
ROUTER_PROVIDERS,
|
return [
|
||||||
provide(LocationStrategy, {useClass: MockLocationStrategy}),
|
ROUTER_PROVIDERS,
|
||||||
provide(ApplicationRef, {useClass: MockApplicationRef})
|
provide(LocationStrategy, {useClass: MockLocationStrategy}),
|
||||||
]);
|
provide(ApplicationRef, {useClass: MockApplicationRef})
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
// do not refactor out the `bootstrap` functionality. We still want to
|
// do not refactor out the `bootstrap` functionality. We still want to
|
||||||
// keep this test around so we can ensure that bootstrapping a router works
|
// keep this test around so we can ensure that bootstrapping a router works
|
||||||
it('should bootstrap a simple app', inject([AsyncTestCompleter], (async) => {
|
describe('bootstrap functionality', () => {
|
||||||
var fakeDoc = DOM.createHtmlDocument();
|
it('should bootstrap a simple app', inject([AsyncTestCompleter], (async) => {
|
||||||
var el = DOM.createElement('app-cmp', fakeDoc);
|
var fakeDoc = DOM.createHtmlDocument();
|
||||||
DOM.appendChild(fakeDoc.body, el);
|
var el = DOM.createElement('app-cmp', fakeDoc);
|
||||||
|
DOM.appendChild(fakeDoc.body, el);
|
||||||
|
|
||||||
bootstrap(AppCmp,
|
bootstrap(AppCmp,
|
||||||
[
|
[
|
||||||
ROUTER_PROVIDERS,
|
ROUTER_PROVIDERS,
|
||||||
provide(ROUTER_PRIMARY_COMPONENT, {useValue: AppCmp}),
|
provide(ROUTER_PRIMARY_COMPONENT, {useValue: AppCmp}),
|
||||||
provide(LocationStrategy, {useClass: MockLocationStrategy}),
|
provide(LocationStrategy, {useClass: MockLocationStrategy}),
|
||||||
provide(DOCUMENT, {useValue: fakeDoc})
|
provide(DOCUMENT, {useValue: fakeDoc})
|
||||||
])
|
])
|
||||||
.then((applicationRef) => {
|
.then((applicationRef) => {
|
||||||
var router = applicationRef.hostComponent.router;
|
var router = applicationRef.hostComponent.router;
|
||||||
router.subscribe((_) => {
|
router.subscribe((_) => {
|
||||||
expect(el).toHaveText('outer { hello }');
|
expect(el).toHaveText('outer { hello }');
|
||||||
expect(applicationRef.hostComponent.location.path()).toEqual('');
|
expect(applicationRef.hostComponent.location.path()).toEqual('');
|
||||||
async.done();
|
async.done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}));
|
||||||
}));
|
});
|
||||||
|
|
||||||
describe('broken app', () => {
|
describe('broken app', () => {
|
||||||
beforeEachProviders(() => [provide(ROUTER_PRIMARY_COMPONENT, {useValue: BrokenAppCmp})]);
|
beforeEachProviders(
|
||||||
|
() => { return [provide(ROUTER_PRIMARY_COMPONENT, {useValue: BrokenAppCmp})]; });
|
||||||
|
|
||||||
it('should rethrow exceptions from component constructors',
|
it('should rethrow exceptions from component constructors',
|
||||||
inject([AsyncTestCompleter, TestComponentBuilder], (async, tcb: TestComponentBuilder) => {
|
inject([AsyncTestCompleter, TestComponentBuilder], (async, tcb: TestComponentBuilder) => {
|
||||||
@ -86,7 +91,8 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('back button app', () => {
|
describe('back button app', () => {
|
||||||
beforeEachProviders(() => [provide(ROUTER_PRIMARY_COMPONENT, {useValue: HierarchyAppCmp})]);
|
beforeEachProviders(
|
||||||
|
() => { return [provide(ROUTER_PRIMARY_COMPONENT, {useValue: HierarchyAppCmp})]; });
|
||||||
|
|
||||||
it('should change the url without pushing a new history state for back navigations',
|
it('should change the url without pushing a new history state for back navigations',
|
||||||
inject([AsyncTestCompleter, TestComponentBuilder], (async, tcb: TestComponentBuilder) => {
|
inject([AsyncTestCompleter, TestComponentBuilder], (async, tcb: TestComponentBuilder) => {
|
||||||
@ -178,7 +184,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// TODO: add a test in which the child component has bindings
|
||||||
|
|
||||||
describe('querystring params app', () => {
|
describe('querystring params app', () => {
|
||||||
beforeEachProviders(
|
beforeEachProviders(
|
||||||
@ -237,21 +243,20 @@ export function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Component({selector: 'hello-cmp', template: 'hello'})
|
@Component({selector: 'hello-cmp'})
|
||||||
|
@View({template: 'hello'})
|
||||||
class HelloCmp {
|
class HelloCmp {
|
||||||
public message: string;
|
public message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'hello2-cmp', template: 'hello2'})
|
@Component({selector: 'hello2-cmp'})
|
||||||
|
@View({template: 'hello2'})
|
||||||
class Hello2Cmp {
|
class Hello2Cmp {
|
||||||
public greeting: string;
|
public greeting: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'app-cmp'})
|
||||||
selector: 'app-cmp',
|
@View({template: "outer { <router-outlet></router-outlet> }", directives: ROUTER_DIRECTIVES})
|
||||||
template: `outer { <router-outlet></router-outlet> }`,
|
|
||||||
directives: ROUTER_DIRECTIVES
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/', component: HelloCmp})])
|
@RouteConfig([new Route({path: '/', component: HelloCmp})])
|
||||||
class AppCmp {
|
class AppCmp {
|
||||||
constructor(public router: Router, public location: LocationStrategy) {}
|
constructor(public router: Router, public location: LocationStrategy) {}
|
||||||
@ -278,29 +283,20 @@ class AppWithViewChildren implements AfterViewInit {
|
|||||||
afterViewInit() { this.helloCmp.message = 'Ahoy'; }
|
afterViewInit() { this.helloCmp.message = 'Ahoy'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'parent-cmp'})
|
||||||
selector: 'parent-cmp',
|
@View({template: `parent { <router-outlet></router-outlet> }`, directives: ROUTER_DIRECTIVES})
|
||||||
template: `parent { <router-outlet></router-outlet> }`,
|
|
||||||
directives: ROUTER_DIRECTIVES
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/child', component: HelloCmp})])
|
@RouteConfig([new Route({path: '/child', component: HelloCmp})])
|
||||||
class ParentCmp {
|
class ParentCmp {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'super-parent-cmp'})
|
||||||
selector: 'super-parent-cmp',
|
@View({template: `super-parent { <router-outlet></router-outlet> }`, directives: ROUTER_DIRECTIVES})
|
||||||
template: `super-parent { <router-outlet></router-outlet> }`,
|
|
||||||
directives: ROUTER_DIRECTIVES
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/child', component: Hello2Cmp})])
|
@RouteConfig([new Route({path: '/child', component: Hello2Cmp})])
|
||||||
class SuperParentCmp {
|
class SuperParentCmp {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'app-cmp'})
|
||||||
selector: 'app-cmp',
|
@View({template: `root { <router-outlet></router-outlet> }`, directives: ROUTER_DIRECTIVES})
|
||||||
template: `root { <router-outlet></router-outlet> }`,
|
|
||||||
directives: ROUTER_DIRECTIVES
|
|
||||||
})
|
|
||||||
@RouteConfig([
|
@RouteConfig([
|
||||||
new Route({path: '/parent/...', component: ParentCmp}),
|
new Route({path: '/parent/...', component: ParentCmp}),
|
||||||
new Route({path: '/super-parent/...', component: SuperParentCmp})
|
new Route({path: '/super-parent/...', component: SuperParentCmp})
|
||||||
@ -309,32 +305,28 @@ class HierarchyAppCmp {
|
|||||||
constructor(public router: Router, public location: LocationStrategy) {}
|
constructor(public router: Router, public location: LocationStrategy) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'qs-cmp', template: `qParam = {{q}}`})
|
@Component({selector: 'qs-cmp'})
|
||||||
|
@View({template: "qParam = {{q}}"})
|
||||||
class QSCmp {
|
class QSCmp {
|
||||||
q: string;
|
q: string;
|
||||||
constructor(params: RouteParams) { this.q = params.get('q'); }
|
constructor(params: RouteParams) { this.q = params.get('q'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'app-cmp'})
|
||||||
selector: 'app-cmp',
|
@View({template: `<router-outlet></router-outlet>`, directives: ROUTER_DIRECTIVES})
|
||||||
template: `<router-outlet></router-outlet>`,
|
|
||||||
directives: ROUTER_DIRECTIVES
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/qs', component: QSCmp})])
|
@RouteConfig([new Route({path: '/qs', component: QSCmp})])
|
||||||
class QueryStringAppCmp {
|
class QueryStringAppCmp {
|
||||||
constructor(public router: Router, public location: LocationStrategy) {}
|
constructor(public router: Router, public location: LocationStrategy) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'oops-cmp', template: "oh no"})
|
@Component({selector: 'oops-cmp'})
|
||||||
|
@View({template: "oh no"})
|
||||||
class BrokenCmp {
|
class BrokenCmp {
|
||||||
constructor() { throw new BaseException('oops!'); }
|
constructor() { throw new BaseException('oops!'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({selector: 'app-cmp'})
|
||||||
selector: 'app-cmp',
|
@View({template: `outer { <router-outlet></router-outlet> }`, directives: ROUTER_DIRECTIVES})
|
||||||
template: `outer { <router-outlet></router-outlet> }`,
|
|
||||||
directives: ROUTER_DIRECTIVES
|
|
||||||
})
|
|
||||||
@RouteConfig([new Route({path: '/cause-error', component: BrokenCmp})])
|
@RouteConfig([new Route({path: '/cause-error', component: BrokenCmp})])
|
||||||
class BrokenAppCmp {
|
class BrokenAppCmp {
|
||||||
constructor(public router: Router, public location: LocationStrategy) {}
|
constructor(public router: Router, public location: LocationStrategy) {}
|
@ -9,7 +9,7 @@ import {
|
|||||||
expect,
|
expect,
|
||||||
iit,
|
iit,
|
||||||
inject,
|
inject,
|
||||||
beforeEachProviders,
|
beforeEachBindings,
|
||||||
it,
|
it,
|
||||||
xit,
|
xit,
|
||||||
TestComponentBuilder,
|
TestComponentBuilder,
|
||||||
@ -21,7 +21,7 @@ import {NumberWrapper} from 'angular2/src/facade/lang';
|
|||||||
import {PromiseWrapper} from 'angular2/src/facade/async';
|
import {PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {provide, Component, View, DirectiveResolver} from 'angular2/core';
|
import {provide, Component, DirectiveResolver} from 'angular2/core';
|
||||||
|
|
||||||
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||||
import {
|
import {
|
||||||
@ -47,7 +47,7 @@ export function main() {
|
|||||||
var fixture: ComponentFixture;
|
var fixture: ComponentFixture;
|
||||||
var router, location;
|
var router, location;
|
||||||
|
|
||||||
beforeEachProviders(() => [
|
beforeEachBindings(() => [
|
||||||
RouteRegistry,
|
RouteRegistry,
|
||||||
DirectiveResolver,
|
DirectiveResolver,
|
||||||
provide(Location, {useClass: SpyLocation}),
|
provide(Location, {useClass: SpyLocation}),
|
||||||
@ -240,8 +240,8 @@ export function main() {
|
|||||||
.then((_) => router.config([new Route({path: '/...', component: AuxLinkCmp})]))
|
.then((_) => router.config([new Route({path: '/...', component: AuxLinkCmp})]))
|
||||||
.then((_) => router.navigateByUrl('/'))
|
.then((_) => router.navigateByUrl('/'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
fixture.detectChanges();
|
rootTC.detectChanges();
|
||||||
expect(DOM.getAttribute(fixture.debugElement.componentViewChildren[1]
|
expect(DOM.getAttribute(rootTC.debugElement.componentViewChildren[1]
|
||||||
.componentViewChildren[0]
|
.componentViewChildren[0]
|
||||||
.nativeElement,
|
.nativeElement,
|
||||||
'href'))
|
'href'))
|
||||||
@ -386,7 +386,10 @@ class MyComp {
|
|||||||
name;
|
name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'user-cmp', template: "hello {{user}}"})
|
@Component({
|
||||||
|
selector: 'user-cmp',
|
||||||
|
template: "hello {{user}}"
|
||||||
|
})
|
||||||
class UserCmp {
|
class UserCmp {
|
||||||
user: string;
|
user: string;
|
||||||
constructor(params: RouteParams) { this.user = params.get('name'); }
|
constructor(params: RouteParams) { this.user = params.get('name'); }
|
||||||
@ -422,11 +425,17 @@ class NoPrefixSiblingPageCmp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'hello-cmp', template: 'hello'})
|
@Component({
|
||||||
|
selector: 'hello-cmp',
|
||||||
|
template: 'hello'
|
||||||
|
})
|
||||||
class HelloCmp {
|
class HelloCmp {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'hello2-cmp', template: 'hello2'})
|
@Component({
|
||||||
|
selector: 'hello2-cmp',
|
||||||
|
template: 'hello2'
|
||||||
|
})
|
||||||
class Hello2Cmp {
|
class Hello2Cmp {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,6 +455,7 @@ function parentCmpLoader() {
|
|||||||
new Route({path: '/better-grandchild', component: Hello2Cmp, name: 'BetterGrandchild'})
|
new Route({path: '/better-grandchild', component: Hello2Cmp, name: 'BetterGrandchild'})
|
||||||
])
|
])
|
||||||
class ParentCmp {
|
class ParentCmp {
|
||||||
|
constructor(public router: Router) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import {
|
|
||||||
describeRouter,
|
|
||||||
ddescribeRouter,
|
|
||||||
describeWith,
|
|
||||||
describeWithout,
|
|
||||||
describeWithAndWithout,
|
|
||||||
itShouldRoute
|
|
||||||
} from './util';
|
|
||||||
|
|
||||||
import {registerSpecs} from './impl/sync_route_spec_impl';
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
registerSpecs();
|
|
||||||
|
|
||||||
describeRouter('sync routes', () => {
|
|
||||||
describeWithout('children', () => { describeWithAndWithout('params', itShouldRoute); });
|
|
||||||
|
|
||||||
describeWith('sync children', () => {
|
|
||||||
describeWithout('default routes', () => { describeWithAndWithout('params', itShouldRoute); });
|
|
||||||
describeWith('default routes', () => { describeWithout('params', itShouldRoute); });
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,138 +0,0 @@
|
|||||||
import {provide, Provider, Component, View} from 'angular2/core';
|
|
||||||
export {Provider} from 'angular2/core';
|
|
||||||
import {Type, isBlank} from 'angular2/src/facade/lang';
|
|
||||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
|
||||||
|
|
||||||
import {
|
|
||||||
RootTestComponent,
|
|
||||||
AsyncTestCompleter,
|
|
||||||
TestComponentBuilder,
|
|
||||||
beforeEach,
|
|
||||||
ddescribe,
|
|
||||||
xdescribe,
|
|
||||||
describe,
|
|
||||||
el,
|
|
||||||
inject,
|
|
||||||
beforeEachProviders,
|
|
||||||
it,
|
|
||||||
xit
|
|
||||||
} from 'angular2/testing_internal';
|
|
||||||
|
|
||||||
import {RootRouter} from 'angular2/src/router/router';
|
|
||||||
import {Router, ROUTER_DIRECTIVES} from 'angular2/router';
|
|
||||||
|
|
||||||
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
|
||||||
import {Location} from 'angular2/src/router/location';
|
|
||||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
|
||||||
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
|
|
||||||
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
|
||||||
export {ComponentFixture} from 'angular2/testing_internal';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Router test helpers and fixtures
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'root-comp',
|
|
||||||
template: `<router-outlet></router-outlet>`,
|
|
||||||
directives: [ROUTER_DIRECTIVES]
|
|
||||||
})
|
|
||||||
export class RootCmp {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function compile(tcb: TestComponentBuilder,
|
|
||||||
template: string = "<router-outlet></router-outlet>") {
|
|
||||||
return tcb.overrideTemplate(RootCmp, ('<div>' + template + '</div>')).createAsync(RootCmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
export var TEST_ROUTER_PROVIDERS = [
|
|
||||||
RouteRegistry,
|
|
||||||
DirectiveResolver,
|
|
||||||
provide(Location, {useClass: SpyLocation}),
|
|
||||||
provide(
|
|
||||||
Router,
|
|
||||||
{
|
|
||||||
useFactory: (registry, location) => { return new RootRouter(registry, location, RootCmp);},
|
|
||||||
deps: [RouteRegistry, Location]
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
export function clickOnElement(anchorEl) {
|
|
||||||
var dispatchedEvent = DOM.createMouseEvent('click');
|
|
||||||
DOM.dispatchEvent(anchorEl, dispatchedEvent);
|
|
||||||
return dispatchedEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHref(elt) {
|
|
||||||
return DOM.getAttribute(elt, 'href');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Router integration suite DSL
|
|
||||||
*/
|
|
||||||
|
|
||||||
var specNameBuilder = [];
|
|
||||||
|
|
||||||
// we add the specs themselves onto this map
|
|
||||||
export var specs = {};
|
|
||||||
|
|
||||||
export function describeRouter(description: string, fn: Function, exclusive = false): void {
|
|
||||||
var specName = descriptionToSpecName(description);
|
|
||||||
specNameBuilder.push(specName);
|
|
||||||
describe(description, fn);
|
|
||||||
specNameBuilder.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ddescribeRouter(description: string, fn: Function, exclusive = false): void {
|
|
||||||
describeRouter(description, fn, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function describeWithAndWithout(description: string, fn: Function): void {
|
|
||||||
// the "without" case is usually simpler, so we opt to run this spec first
|
|
||||||
describeWithout(description, fn);
|
|
||||||
describeWith(description, fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function describeWith(description: string, fn: Function): void {
|
|
||||||
var specName = 'with ' + description;
|
|
||||||
specNameBuilder.push(specName);
|
|
||||||
describe(specName, fn);
|
|
||||||
specNameBuilder.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function describeWithout(description: string, fn: Function): void {
|
|
||||||
var specName = 'without ' + description;
|
|
||||||
specNameBuilder.push(specName);
|
|
||||||
describe(specName, fn);
|
|
||||||
specNameBuilder.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
function descriptionToSpecName(description: string): string {
|
|
||||||
return spaceCaseToCamelCase(description);
|
|
||||||
}
|
|
||||||
|
|
||||||
// this helper looks up the suite registered from the "impl" folder in this directory
|
|
||||||
export function itShouldRoute() {
|
|
||||||
var specSuiteName = spaceCaseToCamelCase(specNameBuilder.join(' '));
|
|
||||||
|
|
||||||
var spec = specs[specSuiteName];
|
|
||||||
if (isBlank(spec)) {
|
|
||||||
throw new BaseException(`Router integration spec suite "${specSuiteName}" was not found.`);
|
|
||||||
} else {
|
|
||||||
// todo: remove spec from map, throw if there are extra left over??
|
|
||||||
spec();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function spaceCaseToCamelCase(str: string): string {
|
|
||||||
var words = str.split(' ');
|
|
||||||
var first = words.shift();
|
|
||||||
return first + words.map(title).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function title(str: string): string {
|
|
||||||
return str[0].toUpperCase() + str.substring(1);
|
|
||||||
}
|
|
@ -12,82 +12,100 @@ import {
|
|||||||
|
|
||||||
import {PathRecognizer} from 'angular2/src/router/path_recognizer';
|
import {PathRecognizer} from 'angular2/src/router/path_recognizer';
|
||||||
import {parser, Url, RootUrl} from 'angular2/src/router/url_parser';
|
import {parser, Url, RootUrl} from 'angular2/src/router/url_parser';
|
||||||
|
import {SyncRouteHandler} from 'angular2/src/router/sync_route_handler';
|
||||||
|
|
||||||
|
class DummyClass {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mockRouteHandler = new SyncRouteHandler(DummyClass);
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('PathRecognizer', () => {
|
describe('PathRecognizer', () => {
|
||||||
|
|
||||||
it('should throw when given an invalid path', () => {
|
it('should throw when given an invalid path', () => {
|
||||||
expect(() => new PathRecognizer('/hi#'))
|
expect(() => new PathRecognizer('/hi#', mockRouteHandler))
|
||||||
.toThrowError(`Path "/hi#" should not include "#". Use "HashLocationStrategy" instead.`);
|
.toThrowError(`Path "/hi#" should not include "#". Use "HashLocationStrategy" instead.`);
|
||||||
expect(() => new PathRecognizer('hi?'))
|
expect(() => new PathRecognizer('hi?', mockRouteHandler))
|
||||||
.toThrowError(`Path "hi?" contains "?" which is not allowed in a route config.`);
|
.toThrowError(`Path "hi?" contains "?" which is not allowed in a route config.`);
|
||||||
expect(() => new PathRecognizer('hi;'))
|
expect(() => new PathRecognizer('hi;', mockRouteHandler))
|
||||||
.toThrowError(`Path "hi;" contains ";" which is not allowed in a route config.`);
|
.toThrowError(`Path "hi;" contains ";" which is not allowed in a route config.`);
|
||||||
expect(() => new PathRecognizer('hi='))
|
expect(() => new PathRecognizer('hi=', mockRouteHandler))
|
||||||
.toThrowError(`Path "hi=" contains "=" which is not allowed in a route config.`);
|
.toThrowError(`Path "hi=" contains "=" which is not allowed in a route config.`);
|
||||||
expect(() => new PathRecognizer('hi('))
|
expect(() => new PathRecognizer('hi(', mockRouteHandler))
|
||||||
.toThrowError(`Path "hi(" contains "(" which is not allowed in a route config.`);
|
.toThrowError(`Path "hi(" contains "(" which is not allowed in a route config.`);
|
||||||
expect(() => new PathRecognizer('hi)'))
|
expect(() => new PathRecognizer('hi)', mockRouteHandler))
|
||||||
.toThrowError(`Path "hi)" contains ")" which is not allowed in a route config.`);
|
.toThrowError(`Path "hi)" contains ")" which is not allowed in a route config.`);
|
||||||
expect(() => new PathRecognizer('hi//there'))
|
expect(() => new PathRecognizer('hi//there', mockRouteHandler))
|
||||||
.toThrowError(`Path "hi//there" contains "//" which is not allowed in a route config.`);
|
.toThrowError(`Path "hi//there" contains "//" which is not allowed in a route config.`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return the same instruction instance when recognizing the same path', () => {
|
||||||
|
var rec = new PathRecognizer('/one', mockRouteHandler);
|
||||||
|
|
||||||
|
var one = new Url('one', null, null, {});
|
||||||
|
|
||||||
|
var firstMatch = rec.recognize(one);
|
||||||
|
var secondMatch = rec.recognize(one);
|
||||||
|
|
||||||
|
expect(firstMatch.instruction).toBe(secondMatch.instruction);
|
||||||
|
});
|
||||||
|
|
||||||
describe('querystring params', () => {
|
describe('querystring params', () => {
|
||||||
it('should parse querystring params so long as the recognizer is a root', () => {
|
it('should parse querystring params so long as the recognizer is a root', () => {
|
||||||
var rec = new PathRecognizer('/hello/there');
|
var rec = new PathRecognizer('/hello/there', mockRouteHandler);
|
||||||
var url = parser.parse('/hello/there?name=igor');
|
var url = parser.parse('/hello/there?name=igor');
|
||||||
var match = rec.recognize(url);
|
var match = rec.recognize(url);
|
||||||
expect(match['allParams']).toEqual({'name': 'igor'});
|
expect(match.instruction.params).toEqual({'name': 'igor'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a combined map of parameters with the param expected in the URL path',
|
it('should return a combined map of parameters with the param expected in the URL path',
|
||||||
() => {
|
() => {
|
||||||
var rec = new PathRecognizer('/hello/:name');
|
var rec = new PathRecognizer('/hello/:name', mockRouteHandler);
|
||||||
var url = parser.parse('/hello/paul?topic=success');
|
var url = parser.parse('/hello/paul?topic=success');
|
||||||
var match = rec.recognize(url);
|
var match = rec.recognize(url);
|
||||||
expect(match['allParams']).toEqual({'name': 'paul', 'topic': 'success'});
|
expect(match.instruction.params).toEqual({'name': 'paul', 'topic': 'success'});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('matrix params', () => {
|
describe('matrix params', () => {
|
||||||
it('should be parsed along with dynamic paths', () => {
|
it('should be parsed along with dynamic paths', () => {
|
||||||
var rec = new PathRecognizer('/hello/:id');
|
var rec = new PathRecognizer('/hello/:id', mockRouteHandler);
|
||||||
var url = new Url('hello', new Url('matias', null, null, {'key': 'value'}));
|
var url = new Url('hello', new Url('matias', null, null, {'key': 'value'}));
|
||||||
var match = rec.recognize(url);
|
var match = rec.recognize(url);
|
||||||
expect(match['allParams']).toEqual({'id': 'matias', 'key': 'value'});
|
expect(match.instruction.params).toEqual({'id': 'matias', 'key': 'value'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be parsed on a static path', () => {
|
it('should be parsed on a static path', () => {
|
||||||
var rec = new PathRecognizer('/person');
|
var rec = new PathRecognizer('/person', mockRouteHandler);
|
||||||
var url = new Url('person', null, null, {'name': 'dave'});
|
var url = new Url('person', null, null, {'name': 'dave'});
|
||||||
var match = rec.recognize(url);
|
var match = rec.recognize(url);
|
||||||
expect(match['allParams']).toEqual({'name': 'dave'});
|
expect(match.instruction.params).toEqual({'name': 'dave'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be ignored on a wildcard segment', () => {
|
it('should be ignored on a wildcard segment', () => {
|
||||||
var rec = new PathRecognizer('/wild/*everything');
|
var rec = new PathRecognizer('/wild/*everything', mockRouteHandler);
|
||||||
var url = parser.parse('/wild/super;variable=value');
|
var url = parser.parse('/wild/super;variable=value');
|
||||||
var match = rec.recognize(url);
|
var match = rec.recognize(url);
|
||||||
expect(match['allParams']).toEqual({'everything': 'super;variable=value'});
|
expect(match.instruction.params).toEqual({'everything': 'super;variable=value'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set matrix param values to true when no value is present', () => {
|
it('should set matrix param values to true when no value is present', () => {
|
||||||
var rec = new PathRecognizer('/path');
|
var rec = new PathRecognizer('/path', mockRouteHandler);
|
||||||
var url = new Url('path', null, null, {'one': true, 'two': true, 'three': '3'});
|
var url = new Url('path', null, null, {'one': true, 'two': true, 'three': '3'});
|
||||||
var match = rec.recognize(url);
|
var match = rec.recognize(url);
|
||||||
expect(match['allParams']).toEqual({'one': true, 'two': true, 'three': '3'});
|
expect(match.instruction.params).toEqual({'one': true, 'two': true, 'three': '3'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be parsed on the final segment of the path', () => {
|
it('should be parsed on the final segment of the path', () => {
|
||||||
var rec = new PathRecognizer('/one/two/three');
|
var rec = new PathRecognizer('/one/two/three', mockRouteHandler);
|
||||||
|
|
||||||
var three = new Url('three', null, null, {'c': '3'});
|
var three = new Url('three', null, null, {'c': '3'});
|
||||||
var two = new Url('two', three, null, {'b': '2'});
|
var two = new Url('two', three, null, {'b': '2'});
|
||||||
var one = new Url('one', two, null, {'a': '1'});
|
var one = new Url('one', two, null, {'a': '1'});
|
||||||
|
|
||||||
var match = rec.recognize(one);
|
var match = rec.recognize(one);
|
||||||
expect(match['allParams']).toEqual({'c': '3'});
|
expect(match.instruction.params).toEqual({'c': '3'});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -214,10 +214,7 @@ class HelloCmp {
|
|||||||
|
|
||||||
@Component({selector: 'app-cmp'})
|
@Component({selector: 'app-cmp'})
|
||||||
@View({template: `root { <router-outlet></router-outlet> }`, directives: ROUTER_DIRECTIVES})
|
@View({template: `root { <router-outlet></router-outlet> }`, directives: ROUTER_DIRECTIVES})
|
||||||
@RouteConfig([
|
@RouteConfig([{path: '/before', redirectTo: '/after'}, {path: '/after', component: HelloCmp}])
|
||||||
{path: '/before', redirectTo: ['Hello']},
|
|
||||||
{path: '/after', component: HelloCmp, name: 'Hello'}
|
|
||||||
])
|
|
||||||
class RedirectAppCmp {
|
class RedirectAppCmp {
|
||||||
constructor(public router: Router, public location: LocationStrategy) {}
|
constructor(public router: Router, public location: LocationStrategy) {}
|
||||||
}
|
}
|
||||||
|
185
modules/angular2/test/router/route_recognizer_spec.ts
Normal file
185
modules/angular2/test/router/route_recognizer_spec.ts
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
describe,
|
||||||
|
it,
|
||||||
|
iit,
|
||||||
|
ddescribe,
|
||||||
|
expect,
|
||||||
|
inject,
|
||||||
|
beforeEach,
|
||||||
|
SpyObject
|
||||||
|
} from 'angular2/testing_internal';
|
||||||
|
|
||||||
|
import {Map, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
|
import {RouteRecognizer} from 'angular2/src/router/route_recognizer';
|
||||||
|
import {ComponentInstruction} from 'angular2/src/router/instruction';
|
||||||
|
|
||||||
|
import {Route, Redirect} from 'angular2/src/router/route_config_decorator';
|
||||||
|
import {parser} from 'angular2/src/router/url_parser';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('RouteRecognizer', () => {
|
||||||
|
var recognizer;
|
||||||
|
|
||||||
|
beforeEach(() => { recognizer = new RouteRecognizer(); });
|
||||||
|
|
||||||
|
|
||||||
|
it('should recognize a static segment', () => {
|
||||||
|
recognizer.config(new Route({path: '/test', component: DummyCmpA}));
|
||||||
|
var solution = recognize(recognizer, '/test');
|
||||||
|
expect(getComponentType(solution)).toEqual(DummyCmpA);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should recognize a single slash', () => {
|
||||||
|
recognizer.config(new Route({path: '/', component: DummyCmpA}));
|
||||||
|
var solution = recognize(recognizer, '/');
|
||||||
|
expect(getComponentType(solution)).toEqual(DummyCmpA);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should recognize a dynamic segment', () => {
|
||||||
|
recognizer.config(new Route({path: '/user/:name', component: DummyCmpA}));
|
||||||
|
var solution = recognize(recognizer, '/user/brian');
|
||||||
|
expect(getComponentType(solution)).toEqual(DummyCmpA);
|
||||||
|
expect(solution.params).toEqual({'name': 'brian'});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should recognize a star segment', () => {
|
||||||
|
recognizer.config(new Route({path: '/first/*rest', component: DummyCmpA}));
|
||||||
|
var solution = recognize(recognizer, '/first/second/third');
|
||||||
|
expect(getComponentType(solution)).toEqual(DummyCmpA);
|
||||||
|
expect(solution.params).toEqual({'rest': 'second/third'});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should throw when given two routes that start with the same static segment', () => {
|
||||||
|
recognizer.config(new Route({path: '/hello', component: DummyCmpA}));
|
||||||
|
expect(() => recognizer.config(new Route({path: '/hello', component: DummyCmpB})))
|
||||||
|
.toThrowError('Configuration \'/hello\' conflicts with existing route \'/hello\'');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should throw when given two routes that have dynamic segments in the same order', () => {
|
||||||
|
recognizer.config(new Route({path: '/hello/:person/how/:doyoudou', component: DummyCmpA}));
|
||||||
|
expect(() => recognizer.config(
|
||||||
|
new Route({path: '/hello/:friend/how/:areyou', component: DummyCmpA})))
|
||||||
|
.toThrowError(
|
||||||
|
'Configuration \'/hello/:friend/how/:areyou\' conflicts with existing route \'/hello/:person/how/:doyoudou\'');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should recognize redirects', () => {
|
||||||
|
recognizer.config(new Route({path: '/b', component: DummyCmpA}));
|
||||||
|
recognizer.config(new Redirect({path: '/a', redirectTo: 'b'}));
|
||||||
|
var solution = recognize(recognizer, '/a');
|
||||||
|
expect(getComponentType(solution)).toEqual(DummyCmpA);
|
||||||
|
expect(solution.urlPath).toEqual('b');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should not perform root URL redirect on a non-root route', () => {
|
||||||
|
recognizer.config(new Redirect({path: '/', redirectTo: '/foo'}));
|
||||||
|
recognizer.config(new Route({path: '/bar', component: DummyCmpA}));
|
||||||
|
var solution = recognize(recognizer, '/bar');
|
||||||
|
expect(solution.componentType).toEqual(DummyCmpA);
|
||||||
|
expect(solution.urlPath).toEqual('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should perform a root URL redirect only for root routes', () => {
|
||||||
|
recognizer.config(new Redirect({path: '/', redirectTo: '/matias'}));
|
||||||
|
recognizer.config(new Route({path: '/matias', component: DummyCmpA}));
|
||||||
|
recognizer.config(new Route({path: '/fatias', component: DummyCmpA}));
|
||||||
|
|
||||||
|
var solution;
|
||||||
|
|
||||||
|
solution = recognize(recognizer, '/');
|
||||||
|
expect(solution.urlPath).toEqual('matias');
|
||||||
|
|
||||||
|
solution = recognize(recognizer, '/fatias');
|
||||||
|
expect(solution.urlPath).toEqual('fatias');
|
||||||
|
|
||||||
|
solution = recognize(recognizer, '');
|
||||||
|
expect(solution.urlPath).toEqual('matias');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should generate URLs with params', () => {
|
||||||
|
recognizer.config(new Route({path: '/app/user/:name', component: DummyCmpA, name: 'User'}));
|
||||||
|
var instruction = recognizer.generate('User', {'name': 'misko'});
|
||||||
|
expect(instruction.urlPath).toEqual('app/user/misko');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should generate URLs with numeric params', () => {
|
||||||
|
recognizer.config(new Route({path: '/app/page/:number', component: DummyCmpA, name: 'Page'}));
|
||||||
|
expect(recognizer.generate('Page', {'number': 42}).urlPath).toEqual('app/page/42');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should throw in the absence of required params URLs', () => {
|
||||||
|
recognizer.config(new Route({path: 'app/user/:name', component: DummyCmpA, name: 'User'}));
|
||||||
|
expect(() => recognizer.generate('User', {}))
|
||||||
|
.toThrowError('Route generator for \'name\' was not included in parameters passed.');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should throw if the route alias is not CamelCase', () => {
|
||||||
|
expect(() => recognizer.config(
|
||||||
|
new Route({path: 'app/user/:name', component: DummyCmpA, name: 'user'})))
|
||||||
|
.toThrowError(
|
||||||
|
`Route "app/user/:name" with name "user" does not begin with an uppercase letter. Route names should be CamelCase like "User".`);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('params', () => {
|
||||||
|
it('should recognize parameters within the URL path', () => {
|
||||||
|
recognizer.config(new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
|
||||||
|
var solution = recognize(recognizer, '/profile/matsko?comments=all');
|
||||||
|
expect(solution.params).toEqual({'name': 'matsko', 'comments': 'all'});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should generate and populate the given static-based route with querystring params',
|
||||||
|
() => {
|
||||||
|
recognizer.config(
|
||||||
|
new Route({path: 'forum/featured', component: DummyCmpA, name: 'ForumPage'}));
|
||||||
|
|
||||||
|
var params = {'start': 10, 'end': 100};
|
||||||
|
|
||||||
|
var result = recognizer.generate('ForumPage', params);
|
||||||
|
expect(result.urlPath).toEqual('forum/featured');
|
||||||
|
expect(result.urlParams).toEqual(['start=10', 'end=100']);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should prefer positional params over query params', () => {
|
||||||
|
recognizer.config(new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
|
||||||
|
|
||||||
|
var solution = recognize(recognizer, '/profile/yegor?name=igor');
|
||||||
|
expect(solution.params).toEqual({'name': 'yegor'});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should ignore matrix params for the top-level component', () => {
|
||||||
|
recognizer.config(new Route({path: '/home/:subject', component: DummyCmpA, name: 'User'}));
|
||||||
|
var solution = recognize(recognizer, '/home;sort=asc/zero;one=1?two=2');
|
||||||
|
expect(solution.params).toEqual({'subject': 'zero', 'two': '2'});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function recognize(recognizer: RouteRecognizer, url: string): ComponentInstruction {
|
||||||
|
return recognizer.recognize(parser.parse(url))[0].instruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getComponentType(routeMatch: ComponentInstruction): any {
|
||||||
|
return routeMatch.componentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyCmpA {}
|
||||||
|
class DummyCmpB {}
|
@ -21,9 +21,9 @@ import {
|
|||||||
AuxRoute,
|
AuxRoute,
|
||||||
AsyncRoute
|
AsyncRoute
|
||||||
} from 'angular2/src/router/route_config_decorator';
|
} from 'angular2/src/router/route_config_decorator';
|
||||||
|
import {stringifyInstruction} from 'angular2/src/router/instruction';
|
||||||
import {IS_DART} from 'angular2/src/facade/lang';
|
import {IS_DART} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('RouteRegistry', () => {
|
describe('RouteRegistry', () => {
|
||||||
var registry;
|
var registry;
|
||||||
@ -34,7 +34,7 @@ export function main() {
|
|||||||
registry.config(RootHostCmp, new Route({path: '/', component: DummyCmpA}));
|
registry.config(RootHostCmp, new Route({path: '/', component: DummyCmpA}));
|
||||||
registry.config(RootHostCmp, new Route({path: '/test', component: DummyCmpB}));
|
registry.config(RootHostCmp, new Route({path: '/test', component: DummyCmpB}));
|
||||||
|
|
||||||
registry.recognize('/test', [RootHostCmp])
|
registry.recognize('/test', RootHostCmp)
|
||||||
.then((instruction) => {
|
.then((instruction) => {
|
||||||
expect(instruction.component.componentType).toBe(DummyCmpB);
|
expect(instruction.component.componentType).toBe(DummyCmpB);
|
||||||
async.done();
|
async.done();
|
||||||
@ -45,32 +45,28 @@ export function main() {
|
|||||||
registry.config(RootHostCmp,
|
registry.config(RootHostCmp,
|
||||||
new Route({path: '/first/...', component: DummyParentCmp, name: 'FirstCmp'}));
|
new Route({path: '/first/...', component: DummyParentCmp, name: 'FirstCmp'}));
|
||||||
|
|
||||||
expect(stringifyInstruction(registry.generate(['FirstCmp', 'SecondCmp'], [RootHostCmp])))
|
expect(stringifyInstruction(registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp)))
|
||||||
.toEqual('first/second');
|
.toEqual('first/second');
|
||||||
expect(stringifyInstruction(registry.generate(['SecondCmp'], [DummyParentCmp])))
|
expect(stringifyInstruction(registry.generate(['SecondCmp'], DummyParentCmp)))
|
||||||
.toEqual('second');
|
.toEqual('second');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate URLs that account for default routes', () => {
|
xit('should generate URLs that account for redirects', () => {
|
||||||
registry.config(
|
registry.config(
|
||||||
RootHostCmp,
|
RootHostCmp,
|
||||||
new Route({path: '/first/...', component: ParentWithDefaultRouteCmp, name: 'FirstCmp'}));
|
new Route({path: '/first/...', component: DummyParentRedirectCmp, name: 'FirstCmp'}));
|
||||||
|
|
||||||
var instruction = registry.generate(['FirstCmp'], [RootHostCmp]);
|
expect(stringifyInstruction(registry.generate(['FirstCmp'], RootHostCmp)))
|
||||||
|
.toEqual('first/second');
|
||||||
expect(instruction.toLinkUrl()).toEqual('first');
|
|
||||||
expect(instruction.toRootUrl()).toEqual('first/second');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate URLs in a hierarchy of default routes', () => {
|
xit('should generate URLs in a hierarchy of redirects', () => {
|
||||||
registry.config(
|
registry.config(
|
||||||
RootHostCmp,
|
RootHostCmp,
|
||||||
new Route({path: '/first/...', component: MultipleDefaultCmp, name: 'FirstCmp'}));
|
new Route({path: '/first/...', component: DummyMultipleRedirectCmp, name: 'FirstCmp'}));
|
||||||
|
|
||||||
var instruction = registry.generate(['FirstCmp'], [RootHostCmp]);
|
expect(stringifyInstruction(registry.generate(['FirstCmp'], RootHostCmp)))
|
||||||
|
.toEqual('first/second/third');
|
||||||
expect(instruction.toLinkUrl()).toEqual('first');
|
|
||||||
expect(instruction.toRootUrl()).toEqual('first/second/third');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate URLs with params', () => {
|
it('should generate URLs with params', () => {
|
||||||
@ -79,13 +75,13 @@ export function main() {
|
|||||||
new Route({path: '/first/:param/...', component: DummyParentParamCmp, name: 'FirstCmp'}));
|
new Route({path: '/first/:param/...', component: DummyParentParamCmp, name: 'FirstCmp'}));
|
||||||
|
|
||||||
var url = stringifyInstruction(registry.generate(
|
var url = stringifyInstruction(registry.generate(
|
||||||
['FirstCmp', {param: 'one'}, 'SecondCmp', {param: 'two'}], [RootHostCmp]));
|
['FirstCmp', {param: 'one'}, 'SecondCmp', {param: 'two'}], RootHostCmp));
|
||||||
expect(url).toEqual('first/one/second/two');
|
expect(url).toEqual('first/one/second/two');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate params as an empty StringMap when no params are given', () => {
|
it('should generate params as an empty StringMap when no params are given', () => {
|
||||||
registry.config(RootHostCmp, new Route({path: '/test', component: DummyCmpA, name: 'Test'}));
|
registry.config(RootHostCmp, new Route({path: '/test', component: DummyCmpA, name: 'Test'}));
|
||||||
var instruction = registry.generate(['Test'], [RootHostCmp]);
|
var instruction = registry.generate(['Test'], RootHostCmp);
|
||||||
expect(instruction.component.params).toEqual({});
|
expect(instruction.component.params).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -95,20 +91,20 @@ export function main() {
|
|||||||
RootHostCmp,
|
RootHostCmp,
|
||||||
new AsyncRoute({path: '/first/...', loader: asyncParentLoader, name: 'FirstCmp'}));
|
new AsyncRoute({path: '/first/...', loader: asyncParentLoader, name: 'FirstCmp'}));
|
||||||
|
|
||||||
var instruction = registry.generate(['FirstCmp', 'SecondCmp'], [RootHostCmp]);
|
expect(() => registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp))
|
||||||
|
.toThrowError('Could not find route named "SecondCmp".');
|
||||||
|
|
||||||
expect(stringifyInstruction(instruction)).toEqual('first');
|
registry.recognize('/first/second', RootHostCmp)
|
||||||
|
|
||||||
registry.recognize('/first/second', [RootHostCmp])
|
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
var instruction = registry.generate(['FirstCmp', 'SecondCmp'], [RootHostCmp]);
|
expect(
|
||||||
expect(stringifyInstruction(instruction)).toEqual('first/second');
|
stringifyInstruction(registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp)))
|
||||||
|
.toEqual('first/second');
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should throw when generating a url and a parent has no config', () => {
|
it('should throw when generating a url and a parent has no config', () => {
|
||||||
expect(() => registry.generate(['FirstCmp', 'SecondCmp'], [RootHostCmp]))
|
expect(() => registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp))
|
||||||
.toThrowError('Component "RootHostCmp" has no route config.');
|
.toThrowError('Component "RootHostCmp" has no route config.');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -117,7 +113,7 @@ export function main() {
|
|||||||
new Route({path: '/primary', component: DummyCmpA, name: 'Primary'}));
|
new Route({path: '/primary', component: DummyCmpA, name: 'Primary'}));
|
||||||
registry.config(RootHostCmp, new AuxRoute({path: '/aux', component: DummyCmpB, name: 'Aux'}));
|
registry.config(RootHostCmp, new AuxRoute({path: '/aux', component: DummyCmpB, name: 'Aux'}));
|
||||||
|
|
||||||
expect(stringifyInstruction(registry.generate(['Primary', ['Aux']], [RootHostCmp])))
|
expect(stringifyInstruction(registry.generate(['Primary', ['Aux']], RootHostCmp)))
|
||||||
.toEqual('primary(aux)');
|
.toEqual('primary(aux)');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -125,7 +121,7 @@ export function main() {
|
|||||||
registry.config(RootHostCmp, new Route({path: '/:site', component: DummyCmpB}));
|
registry.config(RootHostCmp, new Route({path: '/:site', component: DummyCmpB}));
|
||||||
registry.config(RootHostCmp, new Route({path: '/home', component: DummyCmpA}));
|
registry.config(RootHostCmp, new Route({path: '/home', component: DummyCmpA}));
|
||||||
|
|
||||||
registry.recognize('/home', [RootHostCmp])
|
registry.recognize('/home', RootHostCmp)
|
||||||
.then((instruction) => {
|
.then((instruction) => {
|
||||||
expect(instruction.component.componentType).toBe(DummyCmpA);
|
expect(instruction.component.componentType).toBe(DummyCmpA);
|
||||||
async.done();
|
async.done();
|
||||||
@ -136,7 +132,7 @@ export function main() {
|
|||||||
registry.config(RootHostCmp, new Route({path: '/:site', component: DummyCmpA}));
|
registry.config(RootHostCmp, new Route({path: '/:site', component: DummyCmpA}));
|
||||||
registry.config(RootHostCmp, new Route({path: '/*site', component: DummyCmpB}));
|
registry.config(RootHostCmp, new Route({path: '/*site', component: DummyCmpB}));
|
||||||
|
|
||||||
registry.recognize('/home', [RootHostCmp])
|
registry.recognize('/home', RootHostCmp)
|
||||||
.then((instruction) => {
|
.then((instruction) => {
|
||||||
expect(instruction.component.componentType).toBe(DummyCmpA);
|
expect(instruction.component.componentType).toBe(DummyCmpA);
|
||||||
async.done();
|
async.done();
|
||||||
@ -147,7 +143,7 @@ export function main() {
|
|||||||
registry.config(RootHostCmp, new Route({path: '/:first/*rest', component: DummyCmpA}));
|
registry.config(RootHostCmp, new Route({path: '/:first/*rest', component: DummyCmpA}));
|
||||||
registry.config(RootHostCmp, new Route({path: '/*all', component: DummyCmpB}));
|
registry.config(RootHostCmp, new Route({path: '/*all', component: DummyCmpB}));
|
||||||
|
|
||||||
registry.recognize('/some/path', [RootHostCmp])
|
registry.recognize('/some/path', RootHostCmp)
|
||||||
.then((instruction) => {
|
.then((instruction) => {
|
||||||
expect(instruction.component.componentType).toBe(DummyCmpA);
|
expect(instruction.component.componentType).toBe(DummyCmpA);
|
||||||
async.done();
|
async.done();
|
||||||
@ -158,7 +154,7 @@ export function main() {
|
|||||||
registry.config(RootHostCmp, new Route({path: '/first/:second', component: DummyCmpA}));
|
registry.config(RootHostCmp, new Route({path: '/first/:second', component: DummyCmpA}));
|
||||||
registry.config(RootHostCmp, new Route({path: '/:first/:second', component: DummyCmpB}));
|
registry.config(RootHostCmp, new Route({path: '/:first/:second', component: DummyCmpB}));
|
||||||
|
|
||||||
registry.recognize('/first/second', [RootHostCmp])
|
registry.recognize('/first/second', RootHostCmp)
|
||||||
.then((instruction) => {
|
.then((instruction) => {
|
||||||
expect(instruction.component.componentType).toBe(DummyCmpA);
|
expect(instruction.component.componentType).toBe(DummyCmpA);
|
||||||
async.done();
|
async.done();
|
||||||
@ -172,7 +168,7 @@ export function main() {
|
|||||||
registry.config(RootHostCmp,
|
registry.config(RootHostCmp,
|
||||||
new Route({path: '/first/:second/third', component: DummyCmpA}));
|
new Route({path: '/first/:second/third', component: DummyCmpA}));
|
||||||
|
|
||||||
registry.recognize('/first/second/third', [RootHostCmp])
|
registry.recognize('/first/second/third', RootHostCmp)
|
||||||
.then((instruction) => {
|
.then((instruction) => {
|
||||||
expect(instruction.component.componentType).toBe(DummyCmpB);
|
expect(instruction.component.componentType).toBe(DummyCmpB);
|
||||||
async.done();
|
async.done();
|
||||||
@ -182,7 +178,7 @@ export function main() {
|
|||||||
it('should match the full URL using child components', inject([AsyncTestCompleter], (async) => {
|
it('should match the full URL using child components', inject([AsyncTestCompleter], (async) => {
|
||||||
registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyParentCmp}));
|
registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyParentCmp}));
|
||||||
|
|
||||||
registry.recognize('/first/second', [RootHostCmp])
|
registry.recognize('/first/second', RootHostCmp)
|
||||||
.then((instruction) => {
|
.then((instruction) => {
|
||||||
expect(instruction.component.componentType).toBe(DummyParentCmp);
|
expect(instruction.component.componentType).toBe(DummyParentCmp);
|
||||||
expect(instruction.child.component.componentType).toBe(DummyCmpB);
|
expect(instruction.child.component.componentType).toBe(DummyCmpB);
|
||||||
@ -194,14 +190,11 @@ export function main() {
|
|||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyAsyncCmp}));
|
registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyAsyncCmp}));
|
||||||
|
|
||||||
registry.recognize('/first/second', [RootHostCmp])
|
registry.recognize('/first/second', RootHostCmp)
|
||||||
.then((instruction) => {
|
.then((instruction) => {
|
||||||
expect(instruction.component.componentType).toBe(DummyAsyncCmp);
|
expect(instruction.component.componentType).toBe(DummyAsyncCmp);
|
||||||
|
expect(instruction.child.component.componentType).toBe(DummyCmpB);
|
||||||
instruction.child.resolveComponent().then((childComponentInstruction) => {
|
async.done();
|
||||||
expect(childComponentInstruction.componentType).toBe(DummyCmpB);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -210,14 +203,11 @@ export function main() {
|
|||||||
registry.config(RootHostCmp,
|
registry.config(RootHostCmp,
|
||||||
new AsyncRoute({path: '/first/...', loader: asyncParentLoader}));
|
new AsyncRoute({path: '/first/...', loader: asyncParentLoader}));
|
||||||
|
|
||||||
registry.recognize('/first/second', [RootHostCmp])
|
registry.recognize('/first/second', RootHostCmp)
|
||||||
.then((instruction) => {
|
.then((instruction) => {
|
||||||
expect(instruction.component.componentType).toBe(DummyParentCmp);
|
expect(instruction.component.componentType).toBe(DummyParentCmp);
|
||||||
|
expect(instruction.child.component.componentType).toBe(DummyCmpB);
|
||||||
instruction.child.resolveComponent().then((childType) => {
|
async.done();
|
||||||
expect(childType.componentType).toBe(DummyCmpB);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -252,15 +242,15 @@ export function main() {
|
|||||||
it('should throw when linkParams are not terminal', () => {
|
it('should throw when linkParams are not terminal', () => {
|
||||||
registry.config(RootHostCmp,
|
registry.config(RootHostCmp,
|
||||||
new Route({path: '/first/...', component: DummyParentCmp, name: 'First'}));
|
new Route({path: '/first/...', component: DummyParentCmp, name: 'First'}));
|
||||||
expect(() => { registry.generate(['First'], [RootHostCmp]); })
|
expect(() => { registry.generate(['First'], RootHostCmp); })
|
||||||
.toThrowError('Link "["First"]" does not resolve to a terminal instruction.');
|
.toThrowError('Link "["First"]" does not resolve to a terminal or async instruction.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should match matrix params on child components and query params on the root component',
|
it('should match matrix params on child components and query params on the root component',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyParentCmp}));
|
registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyParentCmp}));
|
||||||
|
|
||||||
registry.recognize('/first/second;filter=odd?comments=all', [RootHostCmp])
|
registry.recognize('/first/second;filter=odd?comments=all', RootHostCmp)
|
||||||
.then((instruction) => {
|
.then((instruction) => {
|
||||||
expect(instruction.component.componentType).toBe(DummyParentCmp);
|
expect(instruction.component.componentType).toBe(DummyParentCmp);
|
||||||
expect(instruction.component.params).toEqual({'comments': 'all'});
|
expect(instruction.component.params).toEqual({'comments': 'all'});
|
||||||
@ -286,18 +276,13 @@ export function main() {
|
|||||||
sort: 'asc',
|
sort: 'asc',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[RootHostCmp]));
|
RootHostCmp));
|
||||||
expect(url).toEqual('first/one/second/two;sort=asc?query=cats');
|
expect(url).toEqual('first/one/second/two;sort=asc?query=cats');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringifyInstruction(instruction): string {
|
|
||||||
return instruction.toRootUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function asyncParentLoader() {
|
function asyncParentLoader() {
|
||||||
return PromiseWrapper.resolve(DummyParentCmp);
|
return PromiseWrapper.resolve(DummyParentCmp);
|
||||||
}
|
}
|
||||||
@ -315,22 +300,26 @@ class DummyAsyncCmp {
|
|||||||
class DummyCmpA {}
|
class DummyCmpA {}
|
||||||
class DummyCmpB {}
|
class DummyCmpB {}
|
||||||
|
|
||||||
@RouteConfig(
|
@RouteConfig([
|
||||||
[new Route({path: '/third', component: DummyCmpB, name: 'ThirdCmp', useAsDefault: true})])
|
new Redirect({path: '/', redirectTo: '/third'}),
|
||||||
class DefaultRouteCmp {
|
new Route({path: '/third', component: DummyCmpB, name: 'ThirdCmp'})
|
||||||
|
])
|
||||||
|
class DummyRedirectCmp {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@RouteConfig([
|
@RouteConfig([
|
||||||
new Route(
|
new Redirect({path: '/', redirectTo: '/second'}),
|
||||||
{path: '/second/...', component: DefaultRouteCmp, name: 'SecondCmp', useAsDefault: true})
|
new Route({path: '/second/...', component: DummyRedirectCmp, name: 'SecondCmp'})
|
||||||
])
|
])
|
||||||
class MultipleDefaultCmp {
|
class DummyMultipleRedirectCmp {
|
||||||
}
|
}
|
||||||
|
|
||||||
@RouteConfig(
|
@RouteConfig([
|
||||||
[new Route({path: '/second', component: DummyCmpB, name: 'SecondCmp', useAsDefault: true})])
|
new Redirect({path: '/', redirectTo: '/second'}),
|
||||||
class ParentWithDefaultRouteCmp {
|
new Route({path: '/second', component: DummyCmpB, name: 'SecondCmp'})
|
||||||
|
])
|
||||||
|
class DummyParentRedirectCmp {
|
||||||
}
|
}
|
||||||
|
|
||||||
@RouteConfig([new Route({path: '/second', component: DummyCmpB, name: 'SecondCmp'})])
|
@RouteConfig([new Route({path: '/second', component: DummyCmpB, name: 'SecondCmp'})])
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
expect,
|
expect,
|
||||||
iit,
|
iit,
|
||||||
inject,
|
inject,
|
||||||
beforeEachProviders,
|
beforeEachBindings,
|
||||||
it,
|
it,
|
||||||
xit,
|
xit,
|
||||||
TestComponentBuilder
|
TestComponentBuilder
|
||||||
@ -27,20 +27,24 @@ import {
|
|||||||
RouterOutlet,
|
RouterOutlet,
|
||||||
Route,
|
Route,
|
||||||
RouteParams,
|
RouteParams,
|
||||||
|
Instruction,
|
||||||
ComponentInstruction
|
ComponentInstruction
|
||||||
} from 'angular2/router';
|
} from 'angular2/router';
|
||||||
|
|
||||||
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
||||||
import {ResolvedInstruction} from 'angular2/src/router/instruction';
|
import {ComponentInstruction_} from 'angular2/src/router/instruction';
|
||||||
|
import {PathRecognizer} from 'angular2/src/router/path_recognizer';
|
||||||
|
import {SyncRouteHandler} from 'angular2/src/router/sync_route_handler';
|
||||||
|
|
||||||
|
let dummyPathRecognizer = new PathRecognizer('', new SyncRouteHandler(null));
|
||||||
let dummyInstruction =
|
let dummyInstruction =
|
||||||
new ResolvedInstruction(new ComponentInstruction('detail', [], null, null, true, 0), null, {});
|
new Instruction(new ComponentInstruction_('detail', [], dummyPathRecognizer), null, {});
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('router-link directive', function() {
|
describe('router-link directive', function() {
|
||||||
var tcb: TestComponentBuilder;
|
var tcb: TestComponentBuilder;
|
||||||
|
|
||||||
beforeEachProviders(() => [
|
beforeEachBindings(() => [
|
||||||
provide(Location, {useValue: makeDummyLocation()}),
|
provide(Location, {useValue: makeDummyLocation()}),
|
||||||
provide(Router, {useValue: makeDummyRouter()})
|
provide(Router, {useValue: makeDummyRouter()})
|
||||||
]);
|
]);
|
||||||
@ -102,6 +106,11 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'my-comp'})
|
||||||
|
class MyComp {
|
||||||
|
name;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({selector: 'user-cmp'})
|
@Component({selector: 'user-cmp'})
|
||||||
@View({template: "hello {{user}}"})
|
@View({template: "hello {{user}}"})
|
||||||
class UserCmp {
|
class UserCmp {
|
||||||
|
@ -18,6 +18,7 @@ import {ListWrapper} from 'angular2/src/facade/collection';
|
|||||||
import {Router, RootRouter} from 'angular2/src/router/router';
|
import {Router, RootRouter} from 'angular2/src/router/router';
|
||||||
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||||
import {Location} from 'angular2/src/router/location';
|
import {Location} from 'angular2/src/router/location';
|
||||||
|
import {stringifyInstruction} from 'angular2/src/router/instruction';
|
||||||
|
|
||||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||||
import {RouteConfig, AsyncRoute, Route} from 'angular2/src/router/route_config_decorator';
|
import {RouteConfig, AsyncRoute, Route} from 'angular2/src/router/route_config_decorator';
|
||||||
@ -224,11 +225,6 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function stringifyInstruction(instruction): string {
|
|
||||||
return instruction.toRootUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
function loader(): Promise<Type> {
|
function loader(): Promise<Type> {
|
||||||
return PromiseWrapper.resolve(DummyComponent);
|
return PromiseWrapper.resolve(DummyComponent);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user