Skip to content

Commit 7c07323

Browse files
fix history normalizePath relative path normalization (#3477)
Co-authored-by: MichaelWest22 <michael.west@docuvera.com>
1 parent bd442b5 commit 7c07323

3 files changed

Lines changed: 60 additions & 14 deletions

File tree

src/htmx.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -844,10 +844,11 @@ var htmx = (function() {
844844
* @returns {string}
845845
*/
846846
function normalizePath(path) {
847-
// use dummy base URL to allow normalize on path only
848-
const url = new URL(path, 'http://x')
849-
if (url) {
847+
try {
848+
const url = new URL(path, window.location.href)
850849
path = url.pathname + url.search
850+
} catch (e) {
851+
// fallback for malformed URLs
851852
}
852853
// remove trailing slash, unless index page
853854
if (path != '/') {

test/attributes/hx-push-url.js

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ describe('hx-push-url attribute', function() {
22
const chai = window.chai
33
var HTMX_HISTORY_CACHE_NAME = 'htmx-history-cache'
44

5+
// Helper to normalize paths like htmx does
6+
function normalizePath(path) {
7+
const url = new URL(path, window.location.href)
8+
return url.pathname
9+
}
10+
511
beforeEach(function() {
612
this.server = makeServer()
713
clearWorkArea()
@@ -23,7 +29,7 @@ describe('hx-push-url attribute', function() {
2329
this.server.respond()
2430
getWorkArea().textContent.should.equal('second')
2531
var cache = JSON.parse(sessionStorage.getItem(HTMX_HISTORY_CACHE_NAME))
26-
cache[cache.length - 1].url.should.equal('/test')
32+
cache[cache.length - 1].url.should.equal(normalizePath('/test'))
2733
})
2834

2935
it('navigation should not push an element into the cache when false', function() {
@@ -50,7 +56,7 @@ describe('hx-push-url attribute', function() {
5056
getWorkArea().textContent.should.equal('second')
5157
var cache = JSON.parse(sessionStorage.getItem(HTMX_HISTORY_CACHE_NAME))
5258
cache.length.should.equal(2)
53-
cache[1].url.should.equal('/abc123')
59+
cache[1].url.should.equal(normalizePath('abc123'))
5460
})
5561

5662
it('restore should return old value', function() {
@@ -197,9 +203,9 @@ describe('hx-push-url attribute', function() {
197203
htmx._('saveToHistoryCache')('url1', make('<div>'))
198204
var cache = JSON.parse(sessionStorage.getItem(HTMX_HISTORY_CACHE_NAME))
199205
cache.length.should.equal(3)
200-
cache[0].url.should.equal('/url3')
201-
cache[1].url.should.equal('/url2')
202-
cache[2].url.should.equal('/url1')
206+
cache[0].url.should.equal(normalizePath('url3'))
207+
cache[1].url.should.equal(normalizePath('url2'))
208+
cache[2].url.should.equal(normalizePath('url1'))
203209
})
204210

205211
it('htmx:afterSettle is called when replacing outerHTML', function() {
@@ -273,21 +279,21 @@ describe('hx-push-url attribute', function() {
273279
})
274280
}
275281

276-
it.skip('normalizePath falls back to no normalization if path not valid URL', function() {
277-
// path normalization has a bug breaking it right now preventing this test
282+
it('normalizePath falls back to no normalization if path not valid URL', function() {
283+
// path normalization bug is now fixed
278284
htmx._('saveToHistoryCache')('http://', make('<div>'))
279285
htmx._('saveToHistoryCache')('http//', make('<div>'))
280286
var cache = JSON.parse(sessionStorage.getItem(HTMX_HISTORY_CACHE_NAME))
281287
cache.length.should.equal(2)
282-
cache[0].url.should.equal('http://') // no normalization as invalid
283-
cache[1].url.should.equal('/http') // can normalize this one
288+
cache[0].url.should.equal('http:') // trailing slash removed after normalization
289+
cache[1].url.should.equal(normalizePath('http')) // can normalize this one
284290
})
285291

286292
it('history cache clears out disabled attribute', function() {
287293
htmx._('saveToHistoryCache')('/url1', make('<div><div data-disabled-by-htmx disabled></div></div>'))
288294
var cache = JSON.parse(sessionStorage.getItem(HTMX_HISTORY_CACHE_NAME))
289295
cache.length.should.equal(1)
290-
cache[0].url.should.equal('/url1')
296+
cache[0].url.should.equal(normalizePath('/url1'))
291297
cache[0].content.should.equal('<div data-disabled-by-htmx=""></div>')
292298
})
293299

@@ -375,6 +381,39 @@ describe('hx-push-url attribute', function() {
375381
htmx.off('htmx:pushedIntoHistory', handler)
376382
})
377383

384+
it('normalizes relative paths correctly', function() {
385+
// Get current path to build expected normalized paths
386+
var currentPath = window.location.pathname
387+
var basePath = currentPath.substring(0, currentPath.lastIndexOf('/') + 1)
388+
389+
htmx._('saveToHistoryCache')('relative/path', make('<div>'))
390+
htmx._('saveToHistoryCache')('./another/path', make('<div>'))
391+
var cache = JSON.parse(sessionStorage.getItem(HTMX_HISTORY_CACHE_NAME))
392+
cache.length.should.equal(2)
393+
// Both should be normalized to absolute paths based on current location
394+
cache[0].url.should.equal(basePath + 'relative/path')
395+
cache[1].url.should.equal(basePath + 'another/path')
396+
})
397+
398+
it('normalizes paths with query strings correctly', function() {
399+
var currentPath = window.location.pathname
400+
var basePath = currentPath.substring(0, currentPath.lastIndexOf('/') + 1)
401+
402+
htmx._('saveToHistoryCache')('path?foo=bar', make('<div>'))
403+
var cache = JSON.parse(sessionStorage.getItem(HTMX_HISTORY_CACHE_NAME))
404+
cache.length.should.equal(1)
405+
cache[0].url.should.equal(basePath + 'path?foo=bar')
406+
})
407+
408+
it('normalizes paths with trailing slashes', function() {
409+
htmx._('saveToHistoryCache')('/path/', make('<div>'))
410+
htmx._('saveToHistoryCache')('/path/to/page/', make('<div>'))
411+
var cache = JSON.parse(sessionStorage.getItem(HTMX_HISTORY_CACHE_NAME))
412+
cache.length.should.equal(2)
413+
cache[0].url.should.equal(normalizePath('/path'))
414+
cache[1].url.should.equal(normalizePath('/path/to/page'))
415+
})
416+
378417
it('pushing url without anchor will retain the page anchor tag', function() {
379418
var handler = htmx.on('htmx:configRequest', function(evt) {
380419
evt.detail.path = evt.detail.path + '#test'

test/attributes/hx-replace-url.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
describe('hx-replace-url attribute', function() {
22
var HTMX_HISTORY_CACHE_NAME = 'htmx-history-cache'
33

4+
// Helper to normalize paths like htmx does
5+
function normalizePath(path) {
6+
const url = new URL(path, window.location.href)
7+
return url.pathname
8+
}
9+
410
beforeEach(function() {
511
this.server = makeServer()
612
clearWorkArea()
@@ -22,7 +28,7 @@ describe('hx-replace-url attribute', function() {
2228
this.server.respond()
2329
getWorkArea().textContent.should.equal('second')
2430
var cache = JSON.parse(sessionStorage.getItem(HTMX_HISTORY_CACHE_NAME))
25-
cache[cache.length - 1].url.should.equal('/test')
31+
cache[cache.length - 1].url.should.equal(normalizePath('/test'))
2632
})
2733

2834
it('should handle HX-Replace-Url response header', function() {

0 commit comments

Comments
 (0)