Skip to content

Commit bfa5755

Browse files
fix(pushStateLocation): Fix URLs: add slash between base and path when necessary
test(pushStateLocation): Add tests for various base path scenarios
1 parent 0251424 commit bfa5755

File tree

3 files changed

+162
-38
lines changed

3 files changed

+162
-38
lines changed

src/vanilla/baseLocationService.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export abstract class BaseLocationServices implements LocationServices, Disposab
3232
* - port
3333
* - base href or hash
3434
*/
35-
abstract _get(): string;
35+
protected abstract _get(): string;
3636

3737
/**
3838
* This should set the current URL.
@@ -47,7 +47,7 @@ export abstract class BaseLocationServices implements LocationServices, Disposab
4747
* However, after this function completes, the browser URL should reflect the entire (fully qualified)
4848
* HREF including those data.
4949
*/
50-
abstract _set(state: any, title: string, url: string, replace: boolean);
50+
protected abstract _set(state: any, title: string, url: string, replace: boolean);
5151

5252
hash = () => parseUrl(this._get()).hash;
5353
path = () => parseUrl(this._get()).path;

src/vanilla/pushStateLocationService.ts

+15-8
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,37 @@ export class PushStateLocationService extends BaseLocationServices {
2727
* - trailing filename
2828
* - protocol and hostname
2929
*
30+
* If <base href='/base/'>, this returns '/base'.
31+
* If <base href='/foo/base/'>, this returns '/foo/base'.
3032
* If <base href='/base/index.html'>, this returns '/base'.
3133
* If <base href='http://localhost:8080/base/index.html'>, this returns '/base'.
34+
* If <base href='/base'>, this returns ''.
35+
* If <base href='http://localhost:8080'>, this returns ''.
36+
* If <base href='http://localhost:8080/'>, this returns ''.
3237
*
3338
* See: https://html.spec.whatwg.org/dev/semantics.html#the-base-element
3439
*/
35-
_getBasePrefix() {
40+
private _getBasePrefix() {
3641
return stripLastPathElement(this._config.baseHref());
3742
}
3843

39-
_get() {
44+
protected _get() {
4045
let { pathname, hash, search } = this._location;
4146
search = splitQuery(search)[1]; // strip ? if found
4247
hash = splitHash(hash)[1]; // strip # if found
4348

4449
const basePrefix = this._getBasePrefix();
45-
let exactMatch = pathname === this._config.baseHref();
46-
let startsWith = pathname.startsWith(basePrefix);
47-
pathname = exactMatch ? '/' : startsWith ? pathname.substring(basePrefix.length) : pathname;
50+
const exactBaseHrefMatch = pathname === this._config.baseHref();
51+
const startsWithBase = pathname.substr(0, basePrefix.length) === basePrefix;
52+
pathname = exactBaseHrefMatch ? '/' : startsWithBase ? pathname.substring(basePrefix.length) : pathname;
4853

4954
return pathname + (search ? '?' + search : '') + (hash ? '#' + hash : '');
5055
}
5156

52-
_set(state: any, title: string, url: string, replace: boolean) {
53-
let fullUrl = this._getBasePrefix() + url;
57+
protected _set(state: any, title: string, url: string, replace: boolean) {
58+
const basePrefix = this._getBasePrefix();
59+
const slash = url && url[0] !== '/' ? '/' : '';
60+
const fullUrl = url === '' ? this._config.baseHref() : basePrefix + slash + url;
5461

5562
if (replace) {
5663
this._history.replaceState(state, title, fullUrl);
@@ -59,7 +66,7 @@ export class PushStateLocationService extends BaseLocationServices {
5966
}
6067
}
6168

62-
dispose(router: UIRouter) {
69+
public dispose(router: UIRouter) {
6370
super.dispose(router);
6471
root.removeEventListener('popstate', this._listener);
6572
}

test/vanilla.pushStateLocationServiceSpec.ts

+145-28
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,34 @@ describe('pushStateLocationService', () => {
3333
expect(router.urlService.config.html5Mode()).toBe(true);
3434
});
3535

36-
it('returns the correct path', async(done) => {
37-
await $state.go('path', { urlParam: 'bar' });
38-
36+
it('sets and returns the correct path', () => {
37+
$url.url('/path/bar');
3938
expect(window.location.pathname).toBe('/path/bar');
4039
expect($url.path()).toBe('/path/bar');
4140
expect($url.search()).toEqual({});
41+
});
4242

43-
done();
43+
it('sets and returns an empty path', () => {
44+
$url.url('');
45+
expect(window.location.pathname).toBe('');
46+
expect($url.path()).toBe('');
4447
});
4548

46-
it('returns the correct search', async(done) => {
47-
await $state.go('path', { urlParam: 'bar', queryParam: 'query' });
49+
it('sets and returns a path with a single slash', () => {
50+
$url.url('/');
51+
expect(window.location.pathname).toBe('/');
52+
expect($url.path()).toBe('/');
53+
});
4854

55+
it('returns the correct search', () => {
56+
$url.url('/path/bar?queryParam=query');
4957
expect(window.location.pathname).toBe('/path/bar');
5058
expect(window.location.search).toBe('?queryParam=query');
5159
expect($url.path()).toBe('/path/bar');
5260
expect($url.search()).toEqual({ queryParam: 'query' });
53-
54-
done();
5561
});
5662

57-
58-
describe('with base tag', () => {
63+
fdescribe('with base tag', () => {
5964
let baseTag: HTMLBaseElement;
6065
const applyBaseTag = (href: string) => {
6166
baseTag = document.createElement('base');
@@ -72,71 +77,183 @@ describe('pushStateLocationService', () => {
7277
expect(router.urlService.config.html5Mode()).toBe(true);
7378
});
7479

75-
it('returns the correct url', async(done) => {
76-
await $state.go('path', { urlParam: 'bar' });
80+
it('handles bar correctly', () => {
81+
$url.url('bar');
82+
expect(window.location.pathname).toBe('/base/bar');
83+
expect($url.path()).toBe('/bar');
84+
});
85+
86+
it('handles /bar correctly', () => {
87+
$url.url('/bar');
88+
expect(window.location.pathname).toBe('/base/bar');
89+
expect($url.path()).toBe('/bar');
90+
});
7791

92+
it('handles /path/bar correctly', () => {
93+
$url.url('/path/bar');
7894
expect(window.location.pathname).toBe('/base/path/bar');
7995
expect($url.path()).toBe('/path/bar');
80-
expect($url.search()).toEqual({});
96+
});
8197

82-
done();
98+
it('handles / correctly', () => {
99+
$url.url('/');
100+
expect(window.location.pathname).toBe('/base/');
101+
expect($url.path()).toBe('/');
83102
});
84103

85-
it('returns the correct search', async(done) => {
86-
await $state.go('path', { urlParam: 'bar', queryParam: 'query' });
104+
it('handles "" correctly', () => {
105+
$url.url('foobar');
106+
expect(window.location.pathname).toBe('/base/foobar');
107+
$url.url('');
108+
expect(window.location.pathname).toBe('/base/');
109+
expect($url.path()).toBe('/');
110+
});
87111

112+
it('handles ?queryParam=query correctly', () => {
113+
$url.url('/path/bar?queryParam=query');
88114
expect(window.location.pathname).toBe('/base/path/bar');
89115
expect(window.location.search).toBe('?queryParam=query');
90116
expect($url.path()).toBe('/path/bar');
91117
expect($url.search()).toEqual({ queryParam: 'query' });
92-
93-
done();
94118
});
95119
});
96120

97121
describe('/debug.html', () => {
98122
beforeEach(() => applyBaseTag("/debug.html"));
99123

100-
it('returns the correct url', async(done) => {
101-
await $state.go('path', { urlParam: 'bar' });
124+
it('handles bar correctly', () => {
125+
$url.url('bar');
126+
expect(window.location.pathname).toBe('/bar');
127+
expect($url.path()).toBe('/bar');
128+
});
129+
130+
it('handles /bar correctly', () => {
131+
$url.url('/bar');
132+
expect(window.location.pathname).toBe('/bar');
133+
expect($url.path()).toBe('/bar');
134+
});
102135

136+
it('handles /path/bar correctly', () => {
137+
$url.url('/path/bar');
103138
expect(window.location.pathname).toBe('/path/bar');
104139
expect($url.path()).toBe('/path/bar');
140+
});
141+
142+
it('handles / correctly', () => {
143+
$url.url('/');
144+
expect(window.location.pathname).toBe('/debug.html');
145+
expect($url.path()).toBe('/');
146+
});
147+
148+
it('handles "" correctly', () => {
149+
$url.url('foobar');
150+
expect(window.location.pathname).toBe('/foobar');
151+
$url.url('');
152+
expect(window.location.pathname).toBe('/debug.html');
153+
expect($url.path()).toBe('/');
154+
});
105155

106-
done();
156+
it('handles ?queryParam=query correctly', () => {
157+
$url.url('/path/bar?queryParam=query');
158+
expect(window.location.pathname).toBe('/path/bar');
159+
expect(window.location.search).toBe('?queryParam=query');
160+
expect($url.path()).toBe('/path/bar');
161+
expect($url.search()).toEqual({ queryParam: 'query' });
107162
});
108163
});
109164

110165
describe(origin + '/debug.html', () => {
111166
beforeEach(() => applyBaseTag(origin + '/debug.html'));
112167

113-
it('returns the correct url', async(done) => {
114-
await $state.go('path', { urlParam: 'bar' });
168+
it('handles bar correctly', () => {
169+
$url.url('bar');
170+
expect(window.location.pathname).toBe('/bar');
171+
expect($url.path()).toBe('/bar');
172+
});
115173

174+
it('handles /bar correctly', () => {
175+
$url.url('/bar');
176+
expect(window.location.pathname).toBe('/bar');
177+
expect($url.path()).toBe('/bar');
178+
});
179+
180+
it('handles /path/bar correctly', () => {
181+
$url.url('/path/bar');
116182
expect(window.location.pathname).toBe('/path/bar');
117183
expect($url.path()).toBe('/path/bar');
184+
});
185+
186+
it('handles / correctly', () => {
187+
$url.url('/');
188+
expect(window.location.pathname).toBe('/debug.html');
189+
expect($url.path()).toBe('/');
190+
});
191+
192+
it('handles "" correctly', () => {
193+
$url.url('foobar');
194+
expect(window.location.pathname).toBe('/foobar');
195+
$url.url('');
196+
expect(window.location.pathname).toBe('/debug.html');
197+
expect($url.path()).toBe('/');
198+
});
118199

119-
done();
200+
it('handles ?queryParam=query correctly', () => {
201+
$url.url('/path/bar?queryParam=query');
202+
expect(window.location.pathname).toBe('/path/bar');
203+
expect(window.location.search).toBe('?queryParam=query');
204+
expect($url.path()).toBe('/path/bar');
205+
expect($url.search()).toEqual({ queryParam: 'query' });
120206
});
121207
});
122208

123209
describe(origin + '/base/debug.html', () => {
124210
beforeEach(() => applyBaseTag(origin + '/base/debug.html'));
125211

126-
it('returns the correct url', async(done) => {
127-
await $state.go('path', { urlParam: 'bar' });
212+
it('handles bar correctly', () => {
213+
$url.url('bar');
214+
expect(window.location.pathname).toBe('/base/bar');
215+
expect($url.path()).toBe('/bar');
216+
});
128217

218+
it('handles /bar correctly', () => {
219+
$url.url('/bar');
220+
expect(window.location.pathname).toBe('/base/bar');
221+
expect($url.path()).toBe('/bar');
222+
});
223+
224+
it('handles /path/bar correctly', () => {
225+
$url.url('/path/bar');
129226
expect(window.location.pathname).toBe('/base/path/bar');
130227
expect($url.path()).toBe('/path/bar');
228+
});
131229

132-
done();
230+
it('handles / correctly', () => {
231+
$url.url('/');
232+
expect(window.location.pathname).toBe('/base/');
233+
expect($url.path()).toBe('/');
234+
});
235+
236+
it('handles "" correctly', () => {
237+
$url.url('foobar');
238+
expect(window.location.pathname).toBe('/base/foobar');
239+
$url.url('');
240+
expect(window.location.pathname).toBe('/base/debug.html');
241+
expect($url.path()).toBe('/');
242+
});
243+
244+
it('handles ?queryParam=query correctly', () => {
245+
$url.url('/path/bar?queryParam=query');
246+
expect(window.location.pathname).toBe('/base/path/bar');
247+
expect(window.location.search).toBe('?queryParam=query');
248+
expect($url.path()).toBe('/path/bar');
249+
expect($url.search()).toEqual({ queryParam: 'query' });
133250
});
134251
});
135252

136253
describe('window.location.pathname exactly', () => {
137254
beforeEach(() => applyBaseTag(window.location.pathname));
138255

139-
it('returns the correct url', () => {
256+
it('returns the correct url, /', () => {
140257
expect($url.path()).toBe('/');
141258
});
142259
});

0 commit comments

Comments
 (0)