Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/has/has.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ export default function has(feature: string): FeatureTestResult {
/* Used as a value to provide a debug only code path */
add('debug', true);

add('public-path', undefined);

/* flag for dojo debug, default to false */
add('dojo-debug', false);

Expand Down
19 changes: 13 additions & 6 deletions src/routing/history/StateHistory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import global from '../../shim/global';
import { History as HistoryInterface, HistoryOptions, OnChangeFunction } from './../interfaces';
import has from '../../has/has';

const trailingSlash = new RegExp(/\/$/);
const leadingSlash = new RegExp(/^\//);

function stripBase(base: string, path: string): string {
if (base === '/') {
Expand All @@ -10,9 +12,8 @@ function stripBase(base: string, path: string): string {

if (path.indexOf(base) === 0) {
return path.slice(base.length - 1);
} else {
return '/';
}
return '/';
}

export class StateHistory implements HistoryInterface {
Expand All @@ -21,7 +22,10 @@ export class StateHistory implements HistoryInterface {
private _window: Window;
private _base: string;

constructor({ onChange, window = global.window, base = '/' }: HistoryOptions) {
constructor({ onChange, window = global.window, base }: HistoryOptions) {
if (!base) {
base = has('public-path') ? `${has('public-path')}` : '/';
}
if (/(#|\?)/.test(base)) {
throw new TypeError("base must not contain '#' or '?'");
}
Expand All @@ -31,7 +35,9 @@ export class StateHistory implements HistoryInterface {
if (!trailingSlash.test(this._base)) {
this._base = `${this._base}/`;
}
this._current = this._window.location.pathname + this._window.location.search;
if (!leadingSlash.test(this._base)) {
this._base = `/${this._base}`;
}
this._window.addEventListener('popstate', this._onChange, false);
this._onChange();
}
Expand All @@ -47,7 +53,7 @@ export class StateHistory implements HistoryInterface {
}

public set(path: string) {
this._window.history.pushState({}, '', this.prefix(path));
this._window.history.pushState({}, '', this.prefix(stripBase(this._base, path)));
this._onChange();
}

Expand All @@ -56,7 +62,8 @@ export class StateHistory implements HistoryInterface {
}

private _onChange = () => {
const value = stripBase(this._base, this._window.location.pathname + this._window.location.search);
const pathName = this._window.location.pathname.replace(/\/$/, '');
const value = stripBase(this._base, pathName + this._window.location.search);
if (this._current === value) {
return;
}
Expand Down
26 changes: 20 additions & 6 deletions tests/routing/unit/history/StateHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const { afterEach, beforeEach, describe, it } = intern.getInterface('bdd');
const { assert } = intern.getPlugin('chai');
import { stub } from 'sinon';

import global from '../../../../src/shim/global';
import { add } from '../../../../src/has/has';
import { StateHistory } from '../../../../src/routing/history/StateHistory';

const onChange = stub();
Expand All @@ -23,6 +25,8 @@ describe('StateHistory', () => {
document.body.removeChild(sandbox);
sandbox = null as any;
onChange.reset();
add('public-path', undefined, true);
global.window.__public_path__ = undefined;
});

it('initializes current path to current location', () => {
Expand Down Expand Up @@ -62,15 +66,18 @@ describe('StateHistory', () => {

it('emits change when path is updated', () => {
const history = new StateHistory({ onChange, window: sandbox.contentWindow! });
assert.deepEqual(onChange.firstCall.args, [
sandbox.contentWindow.location.pathname + sandbox.contentWindow.location.search
]);
history.set('/foo');
assert.deepEqual(onChange.firstCall.args, ['/foo']);
assert.deepEqual(onChange.secondCall.args, ['/foo']);
});

it('does not emit change if path is set to the current value', () => {
sandbox.contentWindow!.history.pushState({}, '', '/foo');
const history = new StateHistory({ onChange, window: sandbox.contentWindow! });
history.set('/foo');
assert.isTrue(onChange.notCalled);
assert.isTrue(onChange.calledOnce);
});

describe('with base', () => {
Expand Down Expand Up @@ -129,24 +136,31 @@ describe('StateHistory', () => {

it('#set expands the path with the base when pushing state, with trailing slash', () => {
const history = new StateHistory({ onChange, base: '/foo/', window: sandbox.contentWindow! });
history.set('/bar');
history.set('/foo/bar');
assert.equal(history.current, '/bar');
assert.equal(sandbox.contentWindow!.location.pathname, '/foo/bar');

history.set('baz');
history.set('/foo/baz');
assert.equal(history.current, '/baz');
assert.equal(sandbox.contentWindow!.location.pathname, '/foo/baz');
});

it('#set expands the path with the base when pushing state, without trailing slash', () => {
const history = new StateHistory({ onChange, base: '/foo', window: sandbox.contentWindow! });
history.set('/bar');
history.set('/foo/bar');
assert.equal(history.current, '/bar');
assert.equal(sandbox.contentWindow!.location.pathname, '/foo/bar');

history.set('baz');
history.set('/foo/baz');
assert.equal(history.current, '/baz');
assert.equal(sandbox.contentWindow!.location.pathname, '/foo/baz');
});

it('Assumes base set in window __public_path__ variable if not explicitly set', () => {
add('public-path', 'base/path', true);
global.window.__public_path__ = 'base/path';
const history = new StateHistory({ onChange, window: sandbox.contentWindow! });
assert.strictEqual(history.prefix('foo'), '/base/path/foo');
});
});
});