parent
909e70bd61
commit
2a2f9a9a19
@ -195,6 +195,10 @@ var ListWrapper = {
|
|||||||
return array[0];
|
return array[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
last: function(array) {
|
||||||
|
return (array && array.length) > 0 ? array[array.length - 1] : null;
|
||||||
|
},
|
||||||
|
|
||||||
map: function (l, fn) {
|
map: function (l, fn) {
|
||||||
return l.map(fn);
|
return l.map(fn);
|
||||||
},
|
},
|
||||||
|
@ -7,7 +7,8 @@ import {
|
|||||||
AbstractRecognizer,
|
AbstractRecognizer,
|
||||||
RouteRecognizer,
|
RouteRecognizer,
|
||||||
RedirectRecognizer,
|
RedirectRecognizer,
|
||||||
RouteMatch
|
RouteMatch,
|
||||||
|
PathMatch
|
||||||
} from './route_recognizer';
|
} from './route_recognizer';
|
||||||
import {Route, AsyncRoute, AuxRoute, Redirect, RouteDefinition} from './route_config_impl';
|
import {Route, AsyncRoute, AuxRoute, Redirect, RouteDefinition} from './route_config_impl';
|
||||||
import {AsyncRouteHandler} from './async_route_handler';
|
import {AsyncRouteHandler} from './async_route_handler';
|
||||||
@ -117,6 +118,11 @@ export class ComponentRecognizer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// handle cases where we are routing just to an aux route
|
||||||
|
if (solutions.length == 0 && isPresent(urlParse) && urlParse.auxiliary.length > 0) {
|
||||||
|
return [PromiseWrapper.resolve(new PathMatch(null, null, urlParse.auxiliary))];
|
||||||
|
}
|
||||||
|
|
||||||
return solutions;
|
return solutions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,9 +111,9 @@ export abstract class Instruction {
|
|||||||
public child: Instruction;
|
public child: Instruction;
|
||||||
public auxInstruction: {[key: string]: Instruction} = {};
|
public auxInstruction: {[key: string]: Instruction} = {};
|
||||||
|
|
||||||
get urlPath(): string { return this.component.urlPath; }
|
get urlPath(): string { return isPresent(this.component) ? this.component.urlPath : ''; }
|
||||||
|
|
||||||
get urlParams(): string[] { return this.component.urlParams; }
|
get urlParams(): string[] { return isPresent(this.component) ? this.component.urlParams : []; }
|
||||||
|
|
||||||
get specificity(): number {
|
get specificity(): number {
|
||||||
var total = 0;
|
var total = 0;
|
||||||
@ -181,7 +181,7 @@ export abstract class Instruction {
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_stringifyMatrixParams(): string {
|
_stringifyMatrixParams(): string {
|
||||||
return this.urlParams.length > 0 ? (';' + this.component.urlParams.join(';')) : '';
|
return this.urlParams.length > 0 ? (';' + this.urlParams.join(';')) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
@ -144,20 +144,18 @@ export class RouteRegistry {
|
|||||||
*/
|
*/
|
||||||
recognize(url: string, ancestorInstructions: Instruction[]): Promise<Instruction> {
|
recognize(url: string, ancestorInstructions: Instruction[]): Promise<Instruction> {
|
||||||
var parsedUrl = parser.parse(url);
|
var parsedUrl = parser.parse(url);
|
||||||
return this._recognize(parsedUrl, ancestorInstructions);
|
return this._recognize(parsedUrl, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recognizes all parent-child routes, but creates unresolved auxiliary routes
|
* Recognizes all parent-child routes, but creates unresolved auxiliary routes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private _recognize(parsedUrl: Url, ancestorInstructions: Instruction[],
|
private _recognize(parsedUrl: Url, ancestorInstructions: Instruction[],
|
||||||
_aux = false): Promise<Instruction> {
|
_aux = false): Promise<Instruction> {
|
||||||
var parentComponent =
|
var parentInstruction = ListWrapper.last(ancestorInstructions);
|
||||||
ancestorInstructions.length > 0 ?
|
var parentComponent = isPresent(parentInstruction) ? parentInstruction.component.componentType :
|
||||||
ancestorInstructions[ancestorInstructions.length - 1].component.componentType :
|
this._rootComponent;
|
||||||
this._rootComponent;
|
|
||||||
|
|
||||||
var componentRecognizer = this._rules.get(parentComponent);
|
var componentRecognizer = this._rules.get(parentComponent);
|
||||||
if (isBlank(componentRecognizer)) {
|
if (isBlank(componentRecognizer)) {
|
||||||
@ -174,14 +172,13 @@ export class RouteRegistry {
|
|||||||
|
|
||||||
if (candidate instanceof PathMatch) {
|
if (candidate instanceof PathMatch) {
|
||||||
var auxParentInstructions =
|
var auxParentInstructions =
|
||||||
ancestorInstructions.length > 0 ?
|
ancestorInstructions.length > 0 ? [ListWrapper.last(ancestorInstructions)] : [];
|
||||||
[ancestorInstructions[ancestorInstructions.length - 1]] :
|
|
||||||
[];
|
|
||||||
var auxInstructions =
|
var auxInstructions =
|
||||||
this._auxRoutesToUnresolved(candidate.remainingAux, auxParentInstructions);
|
this._auxRoutesToUnresolved(candidate.remainingAux, auxParentInstructions);
|
||||||
|
|
||||||
var instruction = new ResolvedInstruction(candidate.instruction, null, auxInstructions);
|
var instruction = new ResolvedInstruction(candidate.instruction, null, auxInstructions);
|
||||||
|
|
||||||
if (candidate.instruction.terminal) {
|
if (isBlank(candidate.instruction) || candidate.instruction.terminal) {
|
||||||
return instruction;
|
return instruction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +200,8 @@ export class RouteRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (candidate instanceof RedirectMatch) {
|
if (candidate instanceof RedirectMatch) {
|
||||||
var instruction = this.generate(candidate.redirectTo, ancestorInstructions);
|
var instruction =
|
||||||
|
this.generate(candidate.redirectTo, ancestorInstructions.concat([null]));
|
||||||
return new RedirectInstruction(instruction.component, instruction.child,
|
return new RedirectInstruction(instruction.component, instruction.child,
|
||||||
instruction.auxInstruction);
|
instruction.auxInstruction);
|
||||||
}
|
}
|
||||||
@ -237,69 +235,88 @@ export class RouteRegistry {
|
|||||||
* route boundary.
|
* route boundary.
|
||||||
*/
|
*/
|
||||||
generate(linkParams: any[], ancestorInstructions: Instruction[], _aux = false): Instruction {
|
generate(linkParams: any[], ancestorInstructions: Instruction[], _aux = false): Instruction {
|
||||||
let normalizedLinkParams = splitAndFlattenLinkParams(linkParams);
|
var params = splitAndFlattenLinkParams(linkParams);
|
||||||
|
var prevInstruction;
|
||||||
var first = ListWrapper.first(normalizedLinkParams);
|
|
||||||
var rest = ListWrapper.slice(normalizedLinkParams, 1);
|
|
||||||
|
|
||||||
// The first segment should be either '.' (generate from parent) or '' (generate from root).
|
// 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 ''.
|
// When we normalize above, we strip all the slashes, './' becomes '.' and '/' becomes ''.
|
||||||
if (first == '') {
|
if (ListWrapper.first(params) == '') {
|
||||||
|
params.shift();
|
||||||
|
prevInstruction = ListWrapper.first(ancestorInstructions);
|
||||||
ancestorInstructions = [];
|
ancestorInstructions = [];
|
||||||
} else if (first == '..') {
|
} else {
|
||||||
// we already captured the first instance of "..", so we need to pop off an ancestor
|
prevInstruction = ancestorInstructions.length > 0 ? ancestorInstructions.pop() : null;
|
||||||
ancestorInstructions.pop();
|
|
||||||
while (ListWrapper.first(rest) == '..') {
|
if (ListWrapper.first(params) == '.') {
|
||||||
rest = ListWrapper.slice(rest, 1);
|
params.shift();
|
||||||
ancestorInstructions.pop();
|
} else if (ListWrapper.first(params) == '..') {
|
||||||
if (ancestorInstructions.length <= 0) {
|
while (ListWrapper.first(params) == '..') {
|
||||||
throw new BaseException(
|
if (ancestorInstructions.length <= 0) {
|
||||||
`Link "${ListWrapper.toJSON(linkParams)}" has too many "../" segments.`);
|
throw new BaseException(
|
||||||
|
`Link "${ListWrapper.toJSON(linkParams)}" has too many "../" segments.`);
|
||||||
|
}
|
||||||
|
prevInstruction = ancestorInstructions.pop();
|
||||||
|
params = ListWrapper.slice(params, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're on to implicit child/sibling route
|
||||||
|
} else {
|
||||||
|
// we must only peak at the link param, and not consume it
|
||||||
|
let routeName = ListWrapper.first(params);
|
||||||
|
let parentComponentType = this._rootComponent;
|
||||||
|
let grandparentComponentType = null;
|
||||||
|
|
||||||
|
if (ancestorInstructions.length > 1) {
|
||||||
|
let parentComponentInstruction = ancestorInstructions[ancestorInstructions.length - 1];
|
||||||
|
let grandComponentInstruction = ancestorInstructions[ancestorInstructions.length - 2];
|
||||||
|
|
||||||
|
parentComponentType = parentComponentInstruction.component.componentType;
|
||||||
|
grandparentComponentType = grandComponentInstruction.component.componentType;
|
||||||
|
} else if (ancestorInstructions.length == 1) {
|
||||||
|
parentComponentType = ancestorInstructions[0].component.componentType;
|
||||||
|
grandparentComponentType = this._rootComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(routeName, parentComponentType);
|
||||||
|
var parentRouteExists = isPresent(grandparentComponentType) &&
|
||||||
|
this.hasRoute(routeName, grandparentComponentType);
|
||||||
|
|
||||||
|
if (parentRouteExists && childRouteExists) {
|
||||||
|
let msg =
|
||||||
|
`Link "${ListWrapper.toJSON(linkParams)}" is ambiguous, use "./" or "../" to disambiguate.`;
|
||||||
|
throw new BaseException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentRouteExists) {
|
||||||
|
prevInstruction = ancestorInstructions.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (first != '.') {
|
|
||||||
let parentComponent = this._rootComponent;
|
|
||||||
let grandparentComponent = null;
|
|
||||||
if (ancestorInstructions.length > 1) {
|
|
||||||
parentComponent =
|
|
||||||
ancestorInstructions[ancestorInstructions.length - 1].component.componentType;
|
|
||||||
grandparentComponent =
|
|
||||||
ancestorInstructions[ancestorInstructions.length - 2].component.componentType;
|
|
||||||
} else if (ancestorInstructions.length == 1) {
|
|
||||||
parentComponent = ancestorInstructions[0].component.componentType;
|
|
||||||
grandparentComponent = this._rootComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
ancestorInstructions.pop();
|
|
||||||
}
|
|
||||||
rest = linkParams;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rest[rest.length - 1] == '') {
|
if (params[params.length - 1] == '') {
|
||||||
rest.pop();
|
params.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rest.length < 1) {
|
if (params.length > 0 && params[0] == '') {
|
||||||
|
params.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.length < 1) {
|
||||||
let msg = `Link "${ListWrapper.toJSON(linkParams)}" must include a route name.`;
|
let msg = `Link "${ListWrapper.toJSON(linkParams)}" must include a route name.`;
|
||||||
throw new BaseException(msg);
|
throw new BaseException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
var generatedInstruction = this._generate(rest, ancestorInstructions, _aux);
|
var generatedInstruction =
|
||||||
|
this._generate(params, ancestorInstructions, prevInstruction, _aux, linkParams);
|
||||||
|
|
||||||
|
// we don't clone the first (root) element
|
||||||
for (var i = ancestorInstructions.length - 1; i >= 0; i--) {
|
for (var i = ancestorInstructions.length - 1; i >= 0; i--) {
|
||||||
let ancestorInstruction = ancestorInstructions[i];
|
let ancestorInstruction = ancestorInstructions[i];
|
||||||
|
if (isBlank(ancestorInstruction)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
generatedInstruction = ancestorInstruction.replaceChild(generatedInstruction);
|
generatedInstruction = ancestorInstruction.replaceChild(generatedInstruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,95 +325,113 @@ export class RouteRegistry {
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Internal helper that does not make any assertions about the beginning of the link DSL
|
* Internal helper that does not make any assertions about the beginning of the link DSL.
|
||||||
|
* `ancestorInstructions` are parents that will be cloned.
|
||||||
|
* `prevInstruction` is the existing instruction that would be replaced, but which might have
|
||||||
|
* aux routes that need to be cloned.
|
||||||
*/
|
*/
|
||||||
private _generate(linkParams: any[], ancestorInstructions: Instruction[],
|
private _generate(linkParams: any[], ancestorInstructions: Instruction[],
|
||||||
_aux = false): Instruction {
|
prevInstruction: Instruction, _aux = false, _originalLink: any[]): Instruction {
|
||||||
let parentComponent =
|
let parentComponentType = this._rootComponent;
|
||||||
ancestorInstructions.length > 0 ?
|
let componentInstruction = null;
|
||||||
ancestorInstructions[ancestorInstructions.length - 1].component.componentType :
|
let auxInstructions: {[key: string]: Instruction} = {};
|
||||||
this._rootComponent;
|
|
||||||
|
|
||||||
|
let parentInstruction: Instruction = ListWrapper.last(ancestorInstructions);
|
||||||
|
if (isPresent(parentInstruction) && isPresent(parentInstruction.component)) {
|
||||||
|
parentComponentType = parentInstruction.component.componentType;
|
||||||
|
}
|
||||||
|
|
||||||
if (linkParams.length == 0) {
|
if (linkParams.length == 0) {
|
||||||
return this.generateDefault(parentComponent);
|
let defaultInstruction = this.generateDefault(parentComponentType);
|
||||||
}
|
if (isBlank(defaultInstruction)) {
|
||||||
let linkIndex = 0;
|
throw new BaseException(
|
||||||
let routeName = linkParams[linkIndex];
|
`Link "${ListWrapper.toJSON(_originalLink)}" does not resolve to a terminal instruction.`);
|
||||||
|
|
||||||
if (!isString(routeName)) {
|
|
||||||
throw new BaseException(`Unexpected segment "${routeName}" in link DSL. Expected a string.`);
|
|
||||||
} else if (routeName == '' || routeName == '.' || routeName == '..') {
|
|
||||||
throw new BaseException(`"${routeName}/" is only allowed at the beginning of a link DSL.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let params = {};
|
|
||||||
if (linkIndex + 1 < linkParams.length) {
|
|
||||||
let nextSegment = linkParams[linkIndex + 1];
|
|
||||||
if (isStringMap(nextSegment) && !isArray(nextSegment)) {
|
|
||||||
params = nextSegment;
|
|
||||||
linkIndex += 1;
|
|
||||||
}
|
}
|
||||||
|
return defaultInstruction;
|
||||||
}
|
}
|
||||||
|
|
||||||
let auxInstructions: {[key: string]: Instruction} = {};
|
// for non-aux routes, we want to reuse the predecessor's existing primary and aux routes
|
||||||
var nextSegment;
|
// and only override routes for which the given link DSL provides
|
||||||
while (linkIndex + 1 < linkParams.length && isArray(nextSegment = linkParams[linkIndex + 1])) {
|
if (isPresent(prevInstruction) && !_aux) {
|
||||||
let auxParentInstruction = ancestorInstructions.length > 0 ?
|
auxInstructions = StringMapWrapper.merge(prevInstruction.auxInstruction, auxInstructions);
|
||||||
[ancestorInstructions[ancestorInstructions.length - 1]] :
|
componentInstruction = prevInstruction.component;
|
||||||
[];
|
}
|
||||||
let auxInstruction = this._generate(nextSegment, auxParentInstruction, true);
|
|
||||||
|
var componentRecognizer = this._rules.get(parentComponentType);
|
||||||
|
if (isBlank(componentRecognizer)) {
|
||||||
|
throw new BaseException(
|
||||||
|
`Component "${getTypeNameForDebugging(parentComponentType)}" has no route config.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let linkParamIndex = 0;
|
||||||
|
let routeParams = {};
|
||||||
|
|
||||||
|
// first, recognize the primary route if one is provided
|
||||||
|
if (linkParamIndex < linkParams.length && isString(linkParams[linkParamIndex])) {
|
||||||
|
let routeName = linkParams[linkParamIndex];
|
||||||
|
if (routeName == '' || routeName == '.' || routeName == '..') {
|
||||||
|
throw new BaseException(`"${routeName}/" is only allowed at the beginning of a link DSL.`);
|
||||||
|
}
|
||||||
|
linkParamIndex += 1;
|
||||||
|
if (linkParamIndex < linkParams.length) {
|
||||||
|
let linkParam = linkParams[linkParamIndex];
|
||||||
|
if (isStringMap(linkParam) && !isArray(linkParam)) {
|
||||||
|
routeParams = linkParam;
|
||||||
|
linkParamIndex += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var routeRecognizer =
|
||||||
|
(_aux ? componentRecognizer.auxNames : componentRecognizer.names).get(routeName);
|
||||||
|
|
||||||
|
if (isBlank(routeRecognizer)) {
|
||||||
|
throw new BaseException(
|
||||||
|
`Component "${getTypeNameForDebugging(parentComponentType)}" has no route named "${routeName}".`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an "unresolved instruction" for async routes
|
||||||
|
// we'll figure out the rest of the route when we resolve the instruction and
|
||||||
|
// perform a navigation
|
||||||
|
if (isBlank(routeRecognizer.handler.componentType)) {
|
||||||
|
var compInstruction = routeRecognizer.generateComponentPathValues(routeParams);
|
||||||
|
return new UnresolvedInstruction(() => {
|
||||||
|
return routeRecognizer.handler.resolveComponentType().then((_) => {
|
||||||
|
return this._generate(linkParams, ancestorInstructions, prevInstruction, _aux,
|
||||||
|
_originalLink);
|
||||||
|
});
|
||||||
|
}, compInstruction['urlPath'], compInstruction['urlParams']);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentInstruction = _aux ? componentRecognizer.generateAuxiliary(routeName, routeParams) :
|
||||||
|
componentRecognizer.generate(routeName, routeParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, recognize auxiliary instructions.
|
||||||
|
// If we have an ancestor instruction, we preserve whatever aux routes are active from it.
|
||||||
|
while (linkParamIndex < linkParams.length && isArray(linkParams[linkParamIndex])) {
|
||||||
|
let auxParentInstruction = [parentInstruction];
|
||||||
|
let auxInstruction = this._generate(linkParams[linkParamIndex], auxParentInstruction, null,
|
||||||
|
true, _originalLink);
|
||||||
|
|
||||||
// TODO: this will not work for aux routes with parameters or multiple segments
|
// TODO: this will not work for aux routes with parameters or multiple segments
|
||||||
auxInstructions[auxInstruction.component.urlPath] = auxInstruction;
|
auxInstructions[auxInstruction.component.urlPath] = auxInstruction;
|
||||||
linkIndex += 1;
|
linkParamIndex += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var componentRecognizer = this._rules.get(parentComponent);
|
|
||||||
if (isBlank(componentRecognizer)) {
|
|
||||||
throw new BaseException(
|
|
||||||
`Component "${getTypeNameForDebugging(parentComponent)}" has no route config.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
var routeRecognizer =
|
|
||||||
(_aux ? componentRecognizer.auxNames : componentRecognizer.names).get(routeName);
|
|
||||||
|
|
||||||
if (!isPresent(routeRecognizer)) {
|
|
||||||
throw new BaseException(
|
|
||||||
`Component "${getTypeNameForDebugging(parentComponent)}" has no route named "${routeName}".`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isPresent(routeRecognizer.handler.componentType)) {
|
|
||||||
var compInstruction = routeRecognizer.generateComponentPathValues(params);
|
|
||||||
return new UnresolvedInstruction(() => {
|
|
||||||
return routeRecognizer.handler.resolveComponentType().then(
|
|
||||||
(_) => { return this._generate(linkParams, ancestorInstructions, _aux); });
|
|
||||||
}, compInstruction['urlPath'], compInstruction['urlParams']);
|
|
||||||
}
|
|
||||||
|
|
||||||
var componentInstruction = _aux ? componentRecognizer.generateAuxiliary(routeName, params) :
|
|
||||||
componentRecognizer.generate(routeName, params);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var remaining = linkParams.slice(linkIndex + 1);
|
|
||||||
|
|
||||||
var instruction = new ResolvedInstruction(componentInstruction, null, auxInstructions);
|
var instruction = new ResolvedInstruction(componentInstruction, null, auxInstructions);
|
||||||
|
|
||||||
// the component is sync
|
// If the component is sync, we can generate resolved child route instructions
|
||||||
if (isPresent(componentInstruction.componentType)) {
|
// If not, we'll resolve the instructions at navigation time
|
||||||
|
if (isPresent(componentInstruction) && isPresent(componentInstruction.componentType)) {
|
||||||
let childInstruction: Instruction = null;
|
let childInstruction: Instruction = null;
|
||||||
if (linkIndex + 1 < linkParams.length) {
|
if (componentInstruction.terminal) {
|
||||||
let childAncestorComponents = ancestorInstructions.concat([instruction]);
|
if (linkParamIndex >= linkParams.length) {
|
||||||
childInstruction = this._generate(remaining, childAncestorComponents);
|
// TODO: throw that there are extra link params beyond the terminal component
|
||||||
} 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.`);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let childAncestorComponents = ancestorInstructions.concat([instruction]);
|
||||||
|
let remainingLinkParams = linkParams.slice(linkParamIndex);
|
||||||
|
childInstruction = this._generate(remainingLinkParams, childAncestorComponents, null, false,
|
||||||
|
_originalLink);
|
||||||
}
|
}
|
||||||
instruction.child = childInstruction;
|
instruction.child = childInstruction;
|
||||||
}
|
}
|
||||||
@ -422,7 +457,6 @@ export class RouteRegistry {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var defaultChild = null;
|
var defaultChild = null;
|
||||||
if (isPresent(componentRecognizer.defaultRoute.handler.componentType)) {
|
if (isPresent(componentRecognizer.defaultRoute.handler.componentType)) {
|
||||||
var componentInstruction = componentRecognizer.defaultRoute.generate({});
|
var componentInstruction = componentRecognizer.defaultRoute.generate({});
|
||||||
|
@ -95,8 +95,6 @@ export class Router {
|
|||||||
throw new BaseException(`registerAuxOutlet expects to be called with an outlet with a name.`);
|
throw new BaseException(`registerAuxOutlet expects to be called with an outlet with a name.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO...
|
|
||||||
// what is the host of an aux route???
|
|
||||||
var router = this.auxRouter(this.hostComponent);
|
var router = this.auxRouter(this.hostComponent);
|
||||||
|
|
||||||
this._auxRouters.set(outletName, router);
|
this._auxRouters.set(outletName, router);
|
||||||
@ -224,10 +222,12 @@ export class Router {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
_settleInstruction(instruction: Instruction): Promise<any> {
|
_settleInstruction(instruction: Instruction): Promise<any> {
|
||||||
return instruction.resolveComponent().then((_) => {
|
return instruction.resolveComponent().then((_) => {
|
||||||
instruction.component.reuse = false;
|
|
||||||
|
|
||||||
var unsettledInstructions: Array<Promise<any>> = [];
|
var unsettledInstructions: Array<Promise<any>> = [];
|
||||||
|
|
||||||
|
if (isPresent(instruction.component)) {
|
||||||
|
instruction.component.reuse = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isPresent(instruction.child)) {
|
if (isPresent(instruction.child)) {
|
||||||
unsettledInstructions.push(this._settleInstruction(instruction.child));
|
unsettledInstructions.push(this._settleInstruction(instruction.child));
|
||||||
}
|
}
|
||||||
@ -256,6 +256,9 @@ export class Router {
|
|||||||
if (isBlank(this._outlet)) {
|
if (isBlank(this._outlet)) {
|
||||||
return _resolveToFalse;
|
return _resolveToFalse;
|
||||||
}
|
}
|
||||||
|
if (isBlank(instruction.component)) {
|
||||||
|
return _resolveToTrue;
|
||||||
|
}
|
||||||
return this._outlet.routerCanReuse(instruction.component)
|
return this._outlet.routerCanReuse(instruction.component)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
instruction.component.reuse = result;
|
instruction.component.reuse = result;
|
||||||
@ -280,7 +283,7 @@ export class Router {
|
|||||||
if (isPresent(instruction)) {
|
if (isPresent(instruction)) {
|
||||||
childInstruction = instruction.child;
|
childInstruction = instruction.child;
|
||||||
componentInstruction = instruction.component;
|
componentInstruction = instruction.component;
|
||||||
reuse = instruction.component.reuse;
|
reuse = isBlank(instruction.component) || instruction.component.reuse;
|
||||||
}
|
}
|
||||||
if (reuse) {
|
if (reuse) {
|
||||||
next = _resolveToTrue;
|
next = _resolveToTrue;
|
||||||
@ -304,8 +307,9 @@ export class Router {
|
|||||||
*/
|
*/
|
||||||
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
|
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
|
||||||
this._currentInstruction = instruction;
|
this._currentInstruction = instruction;
|
||||||
|
|
||||||
var next: Promise<any> = _resolveToTrue;
|
var next: Promise<any> = _resolveToTrue;
|
||||||
if (isPresent(this._outlet)) {
|
if (isPresent(this._outlet) && isPresent(instruction.component)) {
|
||||||
var componentInstruction = instruction.component;
|
var componentInstruction = instruction.component;
|
||||||
if (componentInstruction.reuse) {
|
if (componentInstruction.reuse) {
|
||||||
next = this._outlet.reuse(componentInstruction);
|
next = this._outlet.reuse(componentInstruction);
|
||||||
@ -381,15 +385,12 @@ export class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _getAncestorInstructions(): Instruction[] {
|
private _getAncestorInstructions(): Instruction[] {
|
||||||
var ancestorComponents = [];
|
var ancestorInstructions = [this._currentInstruction];
|
||||||
var ancestorRouter: Router = this;
|
var ancestorRouter: Router = this;
|
||||||
while (isPresent(ancestorRouter.parent) &&
|
while (isPresent(ancestorRouter = ancestorRouter.parent)) {
|
||||||
isPresent(ancestorRouter.parent._currentInstruction)) {
|
ancestorInstructions.unshift(ancestorRouter._currentInstruction);
|
||||||
ancestorRouter = ancestorRouter.parent;
|
|
||||||
ancestorComponents.unshift(ancestorRouter._currentInstruction);
|
|
||||||
}
|
}
|
||||||
|
return ancestorInstructions;
|
||||||
return ancestorComponents;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -505,6 +506,9 @@ class ChildRouter extends Router {
|
|||||||
function canActivateOne(nextInstruction: Instruction,
|
function canActivateOne(nextInstruction: Instruction,
|
||||||
prevInstruction: Instruction): Promise<boolean> {
|
prevInstruction: Instruction): Promise<boolean> {
|
||||||
var next = _resolveToTrue;
|
var next = _resolveToTrue;
|
||||||
|
if (isBlank(nextInstruction.component)) {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
if (isPresent(nextInstruction.child)) {
|
if (isPresent(nextInstruction.child)) {
|
||||||
next = canActivateOne(nextInstruction.child,
|
next = canActivateOne(nextInstruction.child,
|
||||||
isPresent(prevInstruction) ? prevInstruction.child : null);
|
isPresent(prevInstruction) ? prevInstruction.child : null);
|
||||||
|
@ -53,16 +53,24 @@ export class RouterLink {
|
|||||||
// the instruction passed to the router to navigate
|
// the instruction passed to the router to navigate
|
||||||
private _navigationInstruction: Instruction;
|
private _navigationInstruction: Instruction;
|
||||||
|
|
||||||
constructor(private _router: Router, private _location: Location) {}
|
constructor(private _router: Router, private _location: Location) {
|
||||||
|
// we need to update the link whenever a route changes to account for aux routes
|
||||||
|
this._router.subscribe((_) => this._updateLink());
|
||||||
|
}
|
||||||
|
|
||||||
|
// because auxiliary links take existing primary and auxiliary routes into account,
|
||||||
|
// we need to update the link whenever params or other routes change.
|
||||||
|
private _updateLink(): void {
|
||||||
|
this._navigationInstruction = this._router.generate(this._routeParams);
|
||||||
|
var navigationHref = this._navigationInstruction.toLinkUrl();
|
||||||
|
this.visibleHref = this._location.prepareExternalUrl(navigationHref);
|
||||||
|
}
|
||||||
|
|
||||||
get isRouteActive(): boolean { return this._router.isRouteActive(this._navigationInstruction); }
|
get isRouteActive(): boolean { return this._router.isRouteActive(this._navigationInstruction); }
|
||||||
|
|
||||||
set routeParams(changes: any[]) {
|
set routeParams(changes: any[]) {
|
||||||
this._routeParams = changes;
|
this._routeParams = changes;
|
||||||
this._navigationInstruction = this._router.generate(this._routeParams);
|
this._updateLink();
|
||||||
|
|
||||||
var navigationHref = this._navigationInstruction.toLinkUrl();
|
|
||||||
this.visibleHref = this._location.prepareExternalUrl(navigationHref);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick(): boolean {
|
onClick(): boolean {
|
||||||
|
@ -12,7 +12,7 @@ import {registerSpecs} from './impl/async_route_spec_impl';
|
|||||||
export function main() {
|
export function main() {
|
||||||
registerSpecs();
|
registerSpecs();
|
||||||
|
|
||||||
ddescribeRouter('async routes', () => {
|
describeRouter('async routes', () => {
|
||||||
describeWithout('children', () => {
|
describeWithout('children', () => {
|
||||||
describeWith('route data', itShouldRoute);
|
describeWith('route data', itShouldRoute);
|
||||||
describeWithAndWithout('params', itShouldRoute);
|
describeWithAndWithout('params', itShouldRoute);
|
||||||
|
@ -1,145 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
ComponentFixture,
|
describeRouter,
|
||||||
AsyncTestCompleter,
|
ddescribeRouter,
|
||||||
TestComponentBuilder,
|
describeWith,
|
||||||
beforeEach,
|
describeWithout,
|
||||||
ddescribe,
|
describeWithAndWithout,
|
||||||
xdescribe,
|
itShouldRoute
|
||||||
describe,
|
} from './util';
|
||||||
el,
|
|
||||||
expect,
|
|
||||||
iit,
|
|
||||||
inject,
|
|
||||||
beforeEachProviders,
|
|
||||||
it,
|
|
||||||
xit
|
|
||||||
} from 'angular2/testing_internal';
|
|
||||||
|
|
||||||
import {provide, Component, Injector, Inject} from 'angular2/core';
|
import {registerSpecs} from './impl/aux_route_spec_impl';
|
||||||
|
|
||||||
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, clickOnElement, getHref} from './util';
|
|
||||||
|
|
||||||
function getLinkElement(rtc: ComponentFixture) {
|
|
||||||
return rtc.debugElement.componentViewChildren[0].nativeElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmpInstanceCount;
|
|
||||||
var childCmpInstanceCount;
|
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('auxiliary routes', () => {
|
registerSpecs();
|
||||||
|
|
||||||
var tcb: TestComponentBuilder;
|
describeRouter('aux routes', () => {
|
||||||
var fixture: ComponentFixture;
|
itShouldRoute();
|
||||||
var rtr;
|
describeWith('a primary route', itShouldRoute);
|
||||||
|
|
||||||
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) => {fixture = 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((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.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) => {fixture = 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((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile(
|
|
||||||
tcb,
|
|
||||||
`<a [routerLink]="['/Hello', ['Modal']]">open modal</a> | main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
|
||||||
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
|
|
||||||
]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(getHref(getLinkElement(fixture))).toEqual('/hello(modal)');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate from a link click',
|
|
||||||
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile(
|
|
||||||
tcb,
|
|
||||||
`<a [routerLink]="['/Hello', ['Modal']]">open modal</a> | main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
|
|
||||||
.then((rtc) => {fixture = rtc})
|
|
||||||
.then((_) => rtr.config([
|
|
||||||
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
|
||||||
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
|
|
||||||
]))
|
|
||||||
.then((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('open modal | main {} | aux {}');
|
|
||||||
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(fixture.debugElement.nativeElement)
|
|
||||||
.toHaveText('open modal | main {hello} | aux {modal}');
|
|
||||||
expect(location.urlChanges).toEqual(['/hello(modal)']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
clickOnElement(getLinkElement(fixture));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@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 {
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,247 @@
|
|||||||
|
import {
|
||||||
|
ComponentFixture,
|
||||||
|
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 {specs, compile, TEST_ROUTER_PROVIDERS, clickOnElement, getHref} from '../util';
|
||||||
|
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||||
|
|
||||||
|
function getLinkElement(rtc: ComponentFixture, linkIndex: number = 0) {
|
||||||
|
return rtc.debugElement.componentViewChildren[linkIndex].nativeElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function auxRoutes() {
|
||||||
|
var tcb: TestComponentBuilder;
|
||||||
|
var fixture: ComponentFixture;
|
||||||
|
var rtr;
|
||||||
|
|
||||||
|
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||||
|
|
||||||
|
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||||
|
tcb = tcBuilder;
|
||||||
|
rtr = router;
|
||||||
|
}));
|
||||||
|
|
||||||
|
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) => {fixture = rtc})
|
||||||
|
.then((_) => rtr.config([
|
||||||
|
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
||||||
|
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Aux'})
|
||||||
|
]))
|
||||||
|
.then((_) => rtr.navigateByUrl('/(modal)'))
|
||||||
|
.then((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.debugElement.nativeElement).toHaveText('main {} | 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) => {fixture = rtc})
|
||||||
|
.then((_) => rtr.config([
|
||||||
|
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
||||||
|
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
|
||||||
|
]))
|
||||||
|
.then((_) => rtr.navigate(['/', ['Modal']]))
|
||||||
|
.then((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.debugElement.nativeElement).toHaveText('main {} | aux {modal}');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile(
|
||||||
|
tcb,
|
||||||
|
`<a [routerLink]="['/', ['Modal']]">open modal</a> | main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
|
||||||
|
.then((rtc) => {fixture = rtc})
|
||||||
|
.then((_) => rtr.config([
|
||||||
|
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
||||||
|
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
|
||||||
|
]))
|
||||||
|
.then((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getHref(getLinkElement(fixture))).toEqual('/(modal)');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should navigate from a link click',
|
||||||
|
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||||
|
compile(
|
||||||
|
tcb,
|
||||||
|
`<a [routerLink]="['/', ['Modal']]">open modal</a> | <a [routerLink]="['/Hello']">hello</a> | main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
|
||||||
|
.then((rtc) => {fixture = rtc})
|
||||||
|
.then((_) => rtr.config([
|
||||||
|
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
||||||
|
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
|
||||||
|
]))
|
||||||
|
.then((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.debugElement.nativeElement)
|
||||||
|
.toHaveText('open modal | hello | main {} | aux {}');
|
||||||
|
|
||||||
|
var navCount = 0;
|
||||||
|
|
||||||
|
rtr.subscribe((_) => {
|
||||||
|
navCount += 1;
|
||||||
|
fixture.detectChanges();
|
||||||
|
if (navCount == 1) {
|
||||||
|
expect(fixture.debugElement.nativeElement)
|
||||||
|
.toHaveText('open modal | hello | main {} | aux {modal}');
|
||||||
|
expect(location.urlChanges).toEqual(['/(modal)']);
|
||||||
|
expect(getHref(getLinkElement(fixture, 0))).toEqual('/(modal)');
|
||||||
|
expect(getHref(getLinkElement(fixture, 1))).toEqual('/hello(modal)');
|
||||||
|
|
||||||
|
// click on primary route link
|
||||||
|
clickOnElement(getLinkElement(fixture, 1));
|
||||||
|
} else if (navCount == 2) {
|
||||||
|
expect(fixture.debugElement.nativeElement)
|
||||||
|
.toHaveText('open modal | hello | main {hello} | aux {modal}');
|
||||||
|
expect(location.urlChanges).toEqual(['/(modal)', '/hello(modal)']);
|
||||||
|
expect(getHref(getLinkElement(fixture, 0))).toEqual('/hello(modal)');
|
||||||
|
expect(getHref(getLinkElement(fixture, 1))).toEqual('/hello(modal)');
|
||||||
|
async.done();
|
||||||
|
} else {
|
||||||
|
throw new BaseException(`Unexpected route change #${navCount}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
clickOnElement(getLinkElement(fixture));
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function auxRoutesWithAPrimaryRoute() {
|
||||||
|
var tcb: TestComponentBuilder;
|
||||||
|
var fixture: ComponentFixture;
|
||||||
|
var rtr;
|
||||||
|
|
||||||
|
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
||||||
|
|
||||||
|
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
||||||
|
tcb = tcBuilder;
|
||||||
|
rtr = router;
|
||||||
|
}));
|
||||||
|
|
||||||
|
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) => {fixture = 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((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.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) => {fixture = 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((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile(
|
||||||
|
tcb,
|
||||||
|
`<a [routerLink]="['/Hello', ['Modal']]">open modal</a> | main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
|
||||||
|
.then((rtc) => {fixture = rtc})
|
||||||
|
.then((_) => rtr.config([
|
||||||
|
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
||||||
|
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
|
||||||
|
]))
|
||||||
|
.then((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getHref(getLinkElement(fixture))).toEqual('/hello(modal)');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should navigate from a link click',
|
||||||
|
inject([AsyncTestCompleter, Location], (async, location) => {
|
||||||
|
compile(
|
||||||
|
tcb,
|
||||||
|
`<a [routerLink]="['/Hello', ['Modal']]">open modal</a> | main {<router-outlet></router-outlet>} | aux {<router-outlet name="modal"></router-outlet>}`)
|
||||||
|
.then((rtc) => {fixture = rtc})
|
||||||
|
.then((_) => rtr.config([
|
||||||
|
new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
|
||||||
|
new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
|
||||||
|
]))
|
||||||
|
.then((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.debugElement.nativeElement).toHaveText('open modal | main {} | aux {}');
|
||||||
|
|
||||||
|
rtr.subscribe((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.debugElement.nativeElement)
|
||||||
|
.toHaveText('open modal | main {hello} | aux {modal}');
|
||||||
|
expect(location.urlChanges).toEqual(['/hello(modal)']);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
clickOnElement(getLinkElement(fixture));
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerSpecs() {
|
||||||
|
specs['auxRoutes'] = auxRoutes;
|
||||||
|
specs['auxRoutesWithAPrimaryRoute'] = auxRoutesWithAPrimaryRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@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 {
|
||||||
|
}
|
@ -325,7 +325,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
describe("router link dsl", () => {
|
describe('router link dsl', () => {
|
||||||
it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => {
|
it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => {
|
||||||
compile('<a href="hello" [routerLink]="route:./User(name: name)">{{name}}</a>')
|
compile('<a href="hello" [routerLink]="route:./User(name: name)">{{name}}</a>')
|
||||||
.then((_) => router.config(
|
.then((_) => router.config(
|
||||||
|
@ -77,7 +77,11 @@ export var specs = {};
|
|||||||
export function describeRouter(description: string, fn: Function, exclusive = false): void {
|
export function describeRouter(description: string, fn: Function, exclusive = false): void {
|
||||||
var specName = descriptionToSpecName(description);
|
var specName = descriptionToSpecName(description);
|
||||||
specNameBuilder.push(specName);
|
specNameBuilder.push(specName);
|
||||||
describe(description, fn);
|
if (exclusive) {
|
||||||
|
ddescribe(description, fn);
|
||||||
|
} else {
|
||||||
|
describe(description, fn);
|
||||||
|
}
|
||||||
specNameBuilder.pop();
|
specNameBuilder.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,9 +47,9 @@ export function main() {
|
|||||||
var instr = registry.generate(['FirstCmp', 'SecondCmp'], []);
|
var instr = registry.generate(['FirstCmp', 'SecondCmp'], []);
|
||||||
expect(stringifyInstruction(instr)).toEqual('first/second');
|
expect(stringifyInstruction(instr)).toEqual('first/second');
|
||||||
|
|
||||||
expect(stringifyInstruction(registry.generate(['SecondCmp'], [instr])))
|
expect(stringifyInstruction(registry.generate(['SecondCmp'], [instr, instr.child])))
|
||||||
.toEqual('first/second');
|
.toEqual('first/second');
|
||||||
expect(stringifyInstruction(registry.generate(['./SecondCmp'], [instr])))
|
expect(stringifyInstruction(registry.generate(['./SecondCmp'], [instr, instr.child])))
|
||||||
.toEqual('first/second');
|
.toEqual('first/second');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user