Rene Weber 2bf21e1747 fix(Router): replace state when normalized path is equal to current normalized path
Make sure the same path is not added multiple times to the history.
It is replacing the state, instead of skipping it completely,
because the current path in the browser might not be normalized,
while the given one is normalized.

Closes #7829

Closes #7897
2016-05-20 15:30:15 -07:00

117 lines
3.5 KiB
TypeScript

import {Injectable, EventEmitter} from '@angular/core';
import {ObservableWrapper} from '../src/facade/async';
import {Location} from '../index';
/**
* A spy for {@link Location} that allows tests to fire simulated location events.
*/
@Injectable()
export class SpyLocation implements Location {
urlChanges: string[] = [];
/** @internal */
private _history: LocationState[] = [new LocationState('', '')];
/** @internal */
private _historyIndex: number = 0;
/** @internal */
_subject: EventEmitter<any> = new EventEmitter();
/** @internal */
_baseHref: string = '';
setInitialPath(url: string) { this._history[this._historyIndex].path = url; }
setBaseHref(url: string) { this._baseHref = url; }
path(): string { return this._history[this._historyIndex].path; }
isCurrentPathEqualTo(path: string, query: string = ''): boolean {
var givenPath = path.endsWith('/') ? path.substring(0, path.length - 1) : path;
var currPath =
this.path().endsWith('/') ? this.path().substring(0, this.path().length - 1) : this.path();
return currPath == givenPath + (query.length > 0 ? ('?' + query) : '');
}
simulateUrlPop(pathname: string) {
ObservableWrapper.callEmit(this._subject, {'url': pathname, 'pop': true});
}
simulateHashChange(pathname: string) {
// Because we don't prevent the native event, the browser will independently update the path
this.setInitialPath(pathname);
this.urlChanges.push('hash: ' + pathname);
ObservableWrapper.callEmit(this._subject, {'url': pathname, 'pop': true, 'type': 'hashchange'});
}
prepareExternalUrl(url: string): string {
if (url.length > 0 && !url.startsWith('/')) {
url = '/' + url;
}
return this._baseHref + url;
}
go(path: string, query: string = '') {
path = this.prepareExternalUrl(path);
if (this._historyIndex > 0) {
this._history.splice(this._historyIndex + 1);
}
this._history.push(new LocationState(path, query));
this._historyIndex = this._history.length - 1;
var locationState = this._history[this._historyIndex - 1];
if(locationState.path == path && locationState.query == query) {
return;
}
var url = path + (query.length > 0 ? ('?' + query) : '');
this.urlChanges.push(url);
}
replaceState(path: string, query: string = '') {
path = this.prepareExternalUrl(path);
var history = this._history[this._historyIndex];
if (history.path == path && history.query == query) {
return;
}
history.path = path;
history.query = query;
var url = path + (query.length > 0 ? ('?' + query) : '');
this.urlChanges.push('replace: ' + url);
}
forward() {
if (this._historyIndex < (this._history.length - 1)) {
this._historyIndex++;
ObservableWrapper.callEmit(this._subject, {'url': this.path(), 'pop': true})
}
}
back() {
if (this._historyIndex > 0) {
this._historyIndex--;
ObservableWrapper.callEmit(this._subject, {'url': this.path(), 'pop': true})
}
}
subscribe(onNext: (value: any) => void, onThrow: (error: any) => void = null,
onReturn: () => void = null): Object {
return ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
}
// TODO: remove these once Location is an interface, and can be implemented cleanly
platformStrategy: any = null;
normalize(url: string): string { return null; }
}
class LocationState {
path: string;
query: string;
constructor(path: string, query: string) {
this.path = path;
this.query = query;
}
}