From 560cc14d97ca618ebf89b10e9b8de7a390425df5 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Thu, 28 Apr 2016 08:01:27 -0700 Subject: [PATCH] feat(router): change location when navigating --- modules/angular2/alt_router.ts | 7 + .../location/browser_platform_location.ts | 57 ++++++ .../location/hash_location_strategy.ts | 101 ++++++++++ .../src/alt_router/location/location.ts | 179 ++++++++++++++++++ .../alt_router/location/location_strategy.ts | 62 ++++++ .../location/path_location_strategy.ts | 105 ++++++++++ .../alt_router/location/platform_location.ts | 48 +++++ modules/angular2/src/alt_router/router.ts | 10 +- .../src/alt_router/router_url_serializer.ts | 3 - .../test/alt_router/integration_spec.ts | 24 ++- .../alt_router/router_url_serializer_spec.ts | 4 +- 11 files changed, 589 insertions(+), 11 deletions(-) create mode 100644 modules/angular2/src/alt_router/location/browser_platform_location.ts create mode 100644 modules/angular2/src/alt_router/location/hash_location_strategy.ts create mode 100644 modules/angular2/src/alt_router/location/location.ts create mode 100644 modules/angular2/src/alt_router/location/location_strategy.ts create mode 100644 modules/angular2/src/alt_router/location/path_location_strategy.ts create mode 100644 modules/angular2/src/alt_router/location/platform_location.ts diff --git a/modules/angular2/alt_router.ts b/modules/angular2/alt_router.ts index b65383f665..f3ccb9c703 100644 --- a/modules/angular2/alt_router.ts +++ b/modules/angular2/alt_router.ts @@ -14,6 +14,13 @@ export { } from './src/alt_router/router_url_serializer'; export {OnActivate} from './src/alt_router/interfaces'; +export {Location} from './src/alt_router/location/location'; +export {LocationStrategy} from './src/alt_router/location/location_strategy'; +export {PathLocationStrategy} from './src/alt_router/location/path_location_strategy'; +export {HashLocationStrategy} from './src/alt_router/location/hash_location_strategy'; +export {PlatformLocation} from './src/alt_router/location/platform_location'; +export {BrowserPlatformLocation} from './src/alt_router/location/browser_platform_location'; + import {RouterOutlet} from './src/alt_router/directives/router_outlet'; import {RouterLink} from './src/alt_router/directives/router_link'; import {CONST_EXPR} from './src/facade/lang'; diff --git a/modules/angular2/src/alt_router/location/browser_platform_location.ts b/modules/angular2/src/alt_router/location/browser_platform_location.ts new file mode 100644 index 0000000000..3572080582 --- /dev/null +++ b/modules/angular2/src/alt_router/location/browser_platform_location.ts @@ -0,0 +1,57 @@ +import {Injectable} from 'angular2/src/core/di/decorators'; +import {UrlChangeListener, PlatformLocation} from './platform_location'; +import {History, Location} from 'angular2/src/facade/browser'; +import {DOM} from 'angular2/src/platform/dom/dom_adapter'; + +/** + * `PlatformLocation` encapsulates all of the direct calls to platform APIs. + * This class should not be used directly by an application developer. Instead, use + * {@link Location}. + */ +@Injectable() +export class BrowserPlatformLocation extends PlatformLocation { + private _location: Location; + private _history: History; + + constructor() { + super(); + this._init(); + } + + // This is moved to its own method so that `MockPlatformLocationStrategy` can overwrite it + /** @internal */ + _init() { + this._location = DOM.getLocation(); + this._history = DOM.getHistory(); + } + + /** @internal */ + get location(): Location { return this._location; } + + getBaseHrefFromDOM(): string { return DOM.getBaseHref(); } + + onPopState(fn: UrlChangeListener): void { + DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false); + } + + onHashChange(fn: UrlChangeListener): void { + DOM.getGlobalEventTarget('window').addEventListener('hashchange', fn, false); + } + + get pathname(): string { return this._location.pathname; } + get search(): string { return this._location.search; } + get hash(): string { return this._location.hash; } + set pathname(newPath: string) { this._location.pathname = newPath; } + + pushState(state: any, title: string, url: string): void { + this._history.pushState(state, title, url); + } + + replaceState(state: any, title: string, url: string): void { + this._history.replaceState(state, title, url); + } + + forward(): void { this._history.forward(); } + + back(): void { this._history.back(); } +} diff --git a/modules/angular2/src/alt_router/location/hash_location_strategy.ts b/modules/angular2/src/alt_router/location/hash_location_strategy.ts new file mode 100644 index 0000000000..0f1fccd994 --- /dev/null +++ b/modules/angular2/src/alt_router/location/hash_location_strategy.ts @@ -0,0 +1,101 @@ +import {Injectable, Inject, Optional} from 'angular2/core'; +import {LocationStrategy, APP_BASE_HREF} from './location_strategy'; +import {Location} from './location'; +import {UrlChangeListener, PlatformLocation} from './platform_location'; +import {isPresent} from 'angular2/src/facade/lang'; + +/** + * `HashLocationStrategy` is a {@link LocationStrategy} used to configure the + * {@link Location} service to represent its state in the + * [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) + * of the browser's URL. + * + * For instance, if you call `location.go('/foo')`, the browser's URL will become + * `example.com#/foo`. + * + * ### Example + * + * ``` + * import {Component, provide} from 'angular2/core'; + * import { + * Location, + * LocationStrategy, + * HashLocationStrategy + * } from 'angular2/platform/common'; + * import { + * ROUTER_DIRECTIVES, + * ROUTER_PROVIDERS, + * RouteConfig + * } from 'angular2/router'; + * + * @Component({directives: [ROUTER_DIRECTIVES]}) + * @RouteConfig([ + * {...}, + * ]) + * class AppCmp { + * constructor(location: Location) { + * location.go('/foo'); + * } + * } + * + * bootstrap(AppCmp, [ + * ROUTER_PROVIDERS, + * provide(LocationStrategy, {useClass: HashLocationStrategy}) + * ]); + * ``` + */ +@Injectable() +export class HashLocationStrategy extends LocationStrategy { + private _baseHref: string = ''; + constructor(private _platformLocation: PlatformLocation, + @Optional() @Inject(APP_BASE_HREF) _baseHref?: string) { + super(); + if (isPresent(_baseHref)) { + this._baseHref = _baseHref; + } + } + + onPopState(fn: UrlChangeListener): void { + this._platformLocation.onPopState(fn); + this._platformLocation.onHashChange(fn); + } + + getBaseHref(): string { return this._baseHref; } + + path(): string { + // the hash value is always prefixed with a `#` + // and if it is empty then it will stay empty + var path = this._platformLocation.hash; + if (!isPresent(path)) path = '#'; + + // Dart will complain if a call to substring is + // executed with a position value that extends the + // length of string. + return (path.length > 0 ? path.substring(1) : path); + } + + prepareExternalUrl(internal: string): string { + var url = Location.joinWithSlash(this._baseHref, internal); + return url.length > 0 ? ('#' + url) : url; + } + + pushState(state: any, title: string, path: string, queryParams: string) { + var url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams)); + if (url.length == 0) { + url = this._platformLocation.pathname; + } + this._platformLocation.pushState(state, title, url); + } + + replaceState(state: any, title: string, path: string, queryParams: string) { + var url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams)); + if (url.length == 0) { + url = this._platformLocation.pathname; + } + this._platformLocation.replaceState(state, title, url); + } + + forward(): void { this._platformLocation.forward(); } + + back(): void { this._platformLocation.back(); } +} diff --git a/modules/angular2/src/alt_router/location/location.ts b/modules/angular2/src/alt_router/location/location.ts new file mode 100644 index 0000000000..ef4254be27 --- /dev/null +++ b/modules/angular2/src/alt_router/location/location.ts @@ -0,0 +1,179 @@ +import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; +import {Injectable, Inject} from 'angular2/core'; +import {LocationStrategy} from './location_strategy'; + +/** + * `Location` is a service that applications can use to interact with a browser's URL. + * Depending on which {@link LocationStrategy} is used, `Location` will either persist + * to the URL's path or the URL's hash segment. + * + * Note: it's better to use {@link Router#navigate} service to trigger route changes. Use + * `Location` only if you need to interact with or create normalized URLs outside of + * routing. + * + * `Location` is responsible for normalizing the URL against the application's base href. + * A normalized URL is absolute from the URL host, includes the application's base href, and has no + * trailing slash: + * - `/my/app/user/123` is normalized + * - `my/app/user/123` **is not** normalized + * - `/my/app/user/123/` **is not** normalized + * + * ### Example + * + * ``` + * import {Component} from 'angular2/core'; + * import {Location} from 'angular2/platform/common'; + * import { + * ROUTER_DIRECTIVES, + * ROUTER_PROVIDERS, + * RouteConfig + * } from 'angular2/router'; + * + * @Component({directives: [ROUTER_DIRECTIVES]}) + * @RouteConfig([ + * {...}, + * ]) + * class AppCmp { + * constructor(location: Location) { + * location.go('/foo'); + * } + * } + * + * bootstrap(AppCmp, [ROUTER_PROVIDERS]); + * ``` + */ +@Injectable() +export class Location { + /** @internal */ + _subject: EventEmitter = new EventEmitter(); + /** @internal */ + _baseHref: string; + + constructor(public platformStrategy: LocationStrategy) { + var browserBaseHref = this.platformStrategy.getBaseHref(); + this._baseHref = Location.stripTrailingSlash(_stripIndexHtml(browserBaseHref)); + this.platformStrategy.onPopState((ev) => { + ObservableWrapper.callEmit(this._subject, {'url': this.path(), 'pop': true, 'type': ev.type}); + }); + } + + /** + * Returns the normalized URL path. + */ + path(): string { return this.normalize(this.platformStrategy.path()); } + + /** + * Given a string representing a URL, returns the normalized URL path without leading or + * trailing slashes + */ + normalize(url: string): string { + return Location.stripTrailingSlash(_stripBaseHref(this._baseHref, _stripIndexHtml(url))); + } + + /** + * Given a string representing a URL, returns the platform-specific external URL path. + * If the given URL doesn't begin with a leading slash (`'/'`), this method adds one + * before normalizing. This method will also add a hash if `HashLocationStrategy` is + * used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use. + */ + prepareExternalUrl(url: string): string { + if (url.length > 0 && !url.startsWith('/')) { + url = '/' + url; + } + return this.platformStrategy.prepareExternalUrl(url); + } + + // TODO: rename this method to pushState + /** + * Changes the browsers URL to the normalized version of the given URL, and pushes a + * new item onto the platform's history. + */ + go(path: string, query: string = ''): void { + this.platformStrategy.pushState(null, '', path, query); + } + + /** + * Changes the browsers URL to the normalized version of the given URL, and replaces + * the top item on the platform's history stack. + */ + replaceState(path: string, query: string = ''): void { + this.platformStrategy.replaceState(null, '', path, query); + } + + /** + * Navigates forward in the platform's history. + */ + forward(): void { this.platformStrategy.forward(); } + + /** + * Navigates back in the platform's history. + */ + back(): void { this.platformStrategy.back(); } + + /** + * Subscribe to the platform's `popState` events. + */ + subscribe(onNext: (value: any) => void, onThrow: (exception: any) => void = null, + onReturn: () => void = null): Object { + return ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn); + } + + /** + * Given a string of url parameters, prepend with '?' if needed, otherwise return parameters as + * is. + */ + public static normalizeQueryParams(params: string): string { + return (params.length > 0 && params.substring(0, 1) != '?') ? ('?' + params) : params; + } + + /** + * Given 2 parts of a url, join them with a slash if needed. + */ + public static joinWithSlash(start: string, end: string): string { + if (start.length == 0) { + return end; + } + if (end.length == 0) { + return start; + } + var slashes = 0; + if (start.endsWith('/')) { + slashes++; + } + if (end.startsWith('/')) { + slashes++; + } + if (slashes == 2) { + return start + end.substring(1); + } + if (slashes == 1) { + return start + end; + } + return start + '/' + end; + } + + /** + * If url has a trailing slash, remove it, otherwise return url as is. + */ + public static stripTrailingSlash(url: string): string { + if (/\/$/g.test(url)) { + url = url.substring(0, url.length - 1); + } + return url; + } +} + +function _stripBaseHref(baseHref: string, url: string): string { + if (baseHref.length > 0 && url.startsWith(baseHref)) { + return url.substring(baseHref.length); + } + return url; +} + +function _stripIndexHtml(url: string): string { + if (/\/index.html$/g.test(url)) { + // '/index.html'.length == 11 + return url.substring(0, url.length - 11); + } + return url; +} diff --git a/modules/angular2/src/alt_router/location/location_strategy.ts b/modules/angular2/src/alt_router/location/location_strategy.ts new file mode 100644 index 0000000000..c7e93db163 --- /dev/null +++ b/modules/angular2/src/alt_router/location/location_strategy.ts @@ -0,0 +1,62 @@ +import {CONST_EXPR} from 'angular2/src/facade/lang'; +import {OpaqueToken} from 'angular2/core'; +import {UrlChangeListener} from './platform_location'; + +/** + * `LocationStrategy` is responsible for representing and reading route state + * from the browser's URL. Angular provides two strategies: + * {@link HashLocationStrategy} and {@link PathLocationStrategy} (default). + * + * This is used under the hood of the {@link Location} service. + * + * Applications should use the {@link Router} or {@link Location} services to + * interact with application route state. + * + * For instance, {@link HashLocationStrategy} produces URLs like + * `http://example.com#/foo`, and {@link PathLocationStrategy} produces + * `http://example.com/foo` as an equivalent URL. + * + * See these two classes for more. + */ +export abstract class LocationStrategy { + abstract path(): string; + abstract prepareExternalUrl(internal: string): string; + abstract pushState(state: any, title: string, url: string, queryParams: string): void; + abstract replaceState(state: any, title: string, url: string, queryParams: string): void; + abstract forward(): void; + abstract back(): void; + abstract onPopState(fn: UrlChangeListener): void; + abstract getBaseHref(): string; +} + + +/** + * The `APP_BASE_HREF` token represents the base href to be used with the + * {@link PathLocationStrategy}. + * + * If you're using {@link PathLocationStrategy}, you must provide a provider to a string + * representing the URL prefix that should be preserved when generating and recognizing + * URLs. + * + * ### Example + * + * ``` + * import {Component} from 'angular2/core'; + * import {ROUTER_DIRECTIVES, ROUTER_PROVIDERS, RouteConfig} from 'angular2/router'; + * import {APP_BASE_HREF} from 'angular2/platform/common'; + * + * @Component({directives: [ROUTER_DIRECTIVES]}) + * @RouteConfig([ + * {...}, + * ]) + * class AppCmp { + * // ... + * } + * + * bootstrap(AppCmp, [ + * ROUTER_PROVIDERS, + * provide(APP_BASE_HREF, {useValue: '/my/app'}) + * ]); + * ``` + */ +export const APP_BASE_HREF: OpaqueToken = CONST_EXPR(new OpaqueToken('appBaseHref')); diff --git a/modules/angular2/src/alt_router/location/path_location_strategy.ts b/modules/angular2/src/alt_router/location/path_location_strategy.ts new file mode 100644 index 0000000000..e5d953a045 --- /dev/null +++ b/modules/angular2/src/alt_router/location/path_location_strategy.ts @@ -0,0 +1,105 @@ +import {Injectable, Inject, Optional} from 'angular2/core'; +import {isBlank} from 'angular2/src/facade/lang'; +import {BaseException} from 'angular2/src/facade/exceptions'; +import {PlatformLocation, UrlChangeListener} from './platform_location'; +import {LocationStrategy, APP_BASE_HREF} from './location_strategy'; +import {Location} from './location'; + +/** + * `PathLocationStrategy` is a {@link LocationStrategy} used to configure the + * {@link Location} service to represent its state in the + * [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the + * browser's URL. + * + * `PathLocationStrategy` is the default binding for {@link LocationStrategy} + * provided in {@link ROUTER_PROVIDERS}. + * + * If you're using `PathLocationStrategy`, you must provide a provider for + * {@link APP_BASE_HREF} to a string representing the URL prefix that should + * be preserved when generating and recognizing URLs. + * + * For instance, if you provide an `APP_BASE_HREF` of `'/my/app'` and call + * `location.go('/foo')`, the browser's URL will become + * `example.com/my/app/foo`. + * + * ### Example + * + * ``` + * import {Component, provide} from 'angular2/core'; + * import {bootstrap} from 'angular2/platform/browser'; + * import { + * Location, + * APP_BASE_HREF + * } from 'angular2/platform/common'; + * import { + * ROUTER_DIRECTIVES, + * ROUTER_PROVIDERS, + * RouteConfig + * } from 'angular2/router'; + * + * @Component({directives: [ROUTER_DIRECTIVES]}) + * @RouteConfig([ + * {...}, + * ]) + * class AppCmp { + * constructor(location: Location) { + * location.go('/foo'); + * } + * } + * + * bootstrap(AppCmp, [ + * ROUTER_PROVIDERS, // includes binding to PathLocationStrategy + * provide(APP_BASE_HREF, {useValue: '/my/app'}) + * ]); + * ``` + */ +@Injectable() +export class PathLocationStrategy extends LocationStrategy { + private _baseHref: string; + + constructor(private _platformLocation: PlatformLocation, + @Optional() @Inject(APP_BASE_HREF) href?: string) { + super(); + + if (isBlank(href)) { + href = this._platformLocation.getBaseHrefFromDOM(); + } + + if (isBlank(href)) { + throw new BaseException( + `No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.`); + } + + this._baseHref = href; + } + + onPopState(fn: UrlChangeListener): void { + this._platformLocation.onPopState(fn); + this._platformLocation.onHashChange(fn); + } + + getBaseHref(): string { return this._baseHref; } + + prepareExternalUrl(internal: string): string { + return Location.joinWithSlash(this._baseHref, internal); + } + + path(): string { + return this._platformLocation.pathname + + Location.normalizeQueryParams(this._platformLocation.search); + } + + pushState(state: any, title: string, url: string, queryParams: string) { + var externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); + this._platformLocation.pushState(state, title, externalUrl); + } + + replaceState(state: any, title: string, url: string, queryParams: string) { + var externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); + this._platformLocation.replaceState(state, title, externalUrl); + } + + forward(): void { this._platformLocation.forward(); } + + back(): void { this._platformLocation.back(); } +} diff --git a/modules/angular2/src/alt_router/location/platform_location.ts b/modules/angular2/src/alt_router/location/platform_location.ts new file mode 100644 index 0000000000..7c41d2e692 --- /dev/null +++ b/modules/angular2/src/alt_router/location/platform_location.ts @@ -0,0 +1,48 @@ +/** + * This class should not be used directly by an application developer. Instead, use + * {@link Location}. + * + * `PlatformLocation` encapsulates all calls to DOM apis, which allows the Router to be platform + * agnostic. + * This means that we can have different implementation of `PlatformLocation` for the different + * platforms + * that angular supports. For example, the default `PlatformLocation` is {@link + * BrowserPlatformLocation}, + * however when you run your app in a WebWorker you use {@link WebWorkerPlatformLocation}. + * + * The `PlatformLocation` class is used directly by all implementations of {@link LocationStrategy} + * when + * they need to interact with the DOM apis like pushState, popState, etc... + * + * {@link LocationStrategy} in turn is used by the {@link Location} service which is used directly + * by + * the {@link Router} in order to navigate between routes. Since all interactions between {@link + * Router} / + * {@link Location} / {@link LocationStrategy} and DOM apis flow through the `PlatformLocation` + * class + * they are all platform independent. + */ +export abstract class PlatformLocation { + abstract getBaseHrefFromDOM(): string; + abstract onPopState(fn: UrlChangeListener): void; + abstract onHashChange(fn: UrlChangeListener): void; + + /* abstract */ get pathname(): string { return null; } + /* abstract */ get search(): string { return null; } + /* abstract */ get hash(): string { return null; } + + abstract replaceState(state: any, title: string, url: string): void; + + abstract pushState(state: any, title: string, url: string): void; + + abstract forward(): void; + + abstract back(): void; +} + +/** + * A serializable version of the event from onPopState or onHashChange + */ +export interface UrlChangeEvent { type: string; } + +export interface UrlChangeListener { (e: UrlChangeEvent): any; } diff --git a/modules/angular2/src/alt_router/router.ts b/modules/angular2/src/alt_router/router.ts index a5e217a99e..95e60feabf 100644 --- a/modules/angular2/src/alt_router/router.ts +++ b/modules/angular2/src/alt_router/router.ts @@ -4,8 +4,10 @@ import {Type, isBlank, isPresent} from 'angular2/src/facade/lang'; import {EventEmitter, Observable} from 'angular2/src/facade/async'; import {StringMapWrapper} from 'angular2/src/facade/collection'; import {BaseException} from 'angular2/src/facade/exceptions'; +import {BaseException} from 'angular2/src/facade/exceptions'; import {RouterUrlSerializer} from './router_url_serializer'; import {recognize} from './recognize'; +import {Location} from './location/location'; import { equalSegments, routeSegmentComponentFactory, @@ -29,11 +31,16 @@ export class Router { private _prevTree: Tree; private _urlTree: Tree; + private _location: Location; + private _changes: EventEmitter = new EventEmitter(); constructor(private _componentType: Type, private _componentResolver: ComponentResolver, private _urlSerializer: RouterUrlSerializer, - private _routerOutletMap: RouterOutletMap) {} + private _routerOutletMap: RouterOutletMap, location: Location) { + this._location = location; + this.navigateByUrl(location.path()); + } get urlTree(): Tree { return this._urlTree; } @@ -45,6 +52,7 @@ export class Router { new _LoadSegments(currTree, this._prevTree) .loadSegments(rootNode(currTree), prevRoot, this._routerOutletMap); this._prevTree = currTree; + this._location.go(this._urlSerializer.serialize(this._urlTree)); this._changes.emit(null); }); } diff --git a/modules/angular2/src/alt_router/router_url_serializer.ts b/modules/angular2/src/alt_router/router_url_serializer.ts index 914d5ad143..ccb3adb798 100644 --- a/modules/angular2/src/alt_router/router_url_serializer.ts +++ b/modules/angular2/src/alt_router/router_url_serializer.ts @@ -10,9 +10,6 @@ export abstract class RouterUrlSerializer { export class DefaultRouterUrlSerializer extends RouterUrlSerializer { parse(url: string): Tree { - if (url.length === 0) { - throw new BaseException(`Invalid url '${url}'`); - } let root = new _UrlParser().parse(url); return new Tree(root); } diff --git a/modules/angular2/test/alt_router/integration_spec.ts b/modules/angular2/test/alt_router/integration_spec.ts index bad66dfd4f..a78c78b5ac 100644 --- a/modules/angular2/test/alt_router/integration_spec.ts +++ b/modules/angular2/test/alt_router/integration_spec.ts @@ -28,8 +28,10 @@ import { Routes, RouterUrlSerializer, DefaultRouterUrlSerializer, - OnActivate + OnActivate, + Location } from 'angular2/alt_router'; +import {SpyLocation} from 'angular2/src/mock/location_mock'; import {DOM} from 'angular2/src/platform/dom/dom_adapter'; export function main() { @@ -37,14 +39,28 @@ export function main() { beforeEachProviders(() => [ provide(RouterUrlSerializer, {useClass: DefaultRouterUrlSerializer}), RouterOutletMap, + provide(Location, {useClass: SpyLocation}), provide(Router, { - useFactory: (resolver, urlParser, outletMap) => - new Router(RootCmp, resolver, urlParser, outletMap), - deps: [ComponentResolver, RouterUrlSerializer, RouterOutletMap] + useFactory: (resolver, urlParser, outletMap, location) => + new Router(RootCmp, resolver, urlParser, outletMap, location), + deps: [ComponentResolver, RouterUrlSerializer, RouterOutletMap, Location] }) ]); + it('should update location when navigating', + fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => { + let fixture = tcb.createFakeAsync(RootCmp); + + router.navigateByUrl('/team/22/user/victor'); + advance(fixture); + expect(location.path()).toEqual('/team/22/user/victor'); + + router.navigateByUrl('/team/33/simple'); + advance(fixture); + expect(location.path()).toEqual('/team/33/simple'); + }))); + it('should support nested routes', fakeAsync(inject([Router, TestComponentBuilder], (router, tcb) => { let fixture = tcb.createFakeAsync(RootCmp); diff --git a/modules/angular2/test/alt_router/router_url_serializer_spec.ts b/modules/angular2/test/alt_router/router_url_serializer_spec.ts index 60db8bef5e..b4ab239a8f 100644 --- a/modules/angular2/test/alt_router/router_url_serializer_spec.ts +++ b/modules/angular2/test/alt_router/router_url_serializer_spec.ts @@ -19,11 +19,9 @@ import {DefaultRouterUrlSerializer} from 'angular2/src/alt_router/router_url_ser import {UrlSegment} from 'angular2/src/alt_router/segments'; export function main() { - describe('url parsing', () => { + describe('url serializer', () => { let url = new DefaultRouterUrlSerializer(); - it('should throw on an empty urls', () => { expect(() => url.parse("")).toThrow(); }); - it('should parse the root url', () => { let tree = url.parse("/"); expectSegment(tree.root, "");