diff --git a/.changeset/flat-planes-kneel.md b/.changeset/flat-planes-kneel.md new file mode 100644 index 000000000000..fad7d235a6c1 --- /dev/null +++ b/.changeset/flat-planes-kneel.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: correctly replace state when `data-sveltekit-replacestate` is used with a hash link diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 0e05ded0a3a0..0ec95f1fee96 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1532,7 +1532,6 @@ export function create_client(app, target) { if (hash !== undefined && nonhash === location.href.split('#')[0]) { // set this flag to distinguish between navigations triggered by // clicking a hash link and those triggered by popstate - // TODO why not update history here directly? hash_navigating = true; update_scroll_positions(current_history_index); @@ -1541,7 +1540,11 @@ export function create_client(app, target) { stores.page.set({ ...page, url }); stores.page.notify(); - return; + if (!options.replace_state) return; + + // hashchange event shouldn't occur if the router is replacing state. + hash_navigating = false; + event.preventDefault(); } navigate({ diff --git a/packages/kit/test/apps/basics/src/routes/routing/hashes/a/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/hashes/a/+page.svelte index 0f5acc8bc10d..19192d1e957b 100644 --- a/packages/kit/test/apps/basics/src/routes/routing/hashes/a/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/routing/hashes/a/+page.svelte @@ -1,3 +1,4 @@

a

hash link b +replace state \ No newline at end of file diff --git a/packages/kit/test/apps/basics/test/cross-platform/client.test.js b/packages/kit/test/apps/basics/test/cross-platform/client.test.js index 42693f8f30d4..77e8d2a4877c 100644 --- a/packages/kit/test/apps/basics/test/cross-platform/client.test.js +++ b/packages/kit/test/apps/basics/test/cross-platform/client.test.js @@ -623,6 +623,33 @@ test.describe('Routing', () => { await expect(page.locator('#page-url-hash')).toHaveText(''); }); + test('back button returns to previous route when previous route has been navigated to via hash anchor', async ({ + page, + clicknav + }) => { + await page.goto('/routing/hashes/a'); + + await page.locator('[href="#hash-target"]').click(); + await clicknav('[href="/routing/hashes/b"]'); + + await page.goBack(); + expect(await page.textContent('h1')).toBe('a'); + }); + + test('replaces state if the data-sveltekit-replacestate router option is specified for the hash link', async ({ + page, + clicknav, + baseURL + }) => { + await page.goto('/routing/hashes/a'); + + await clicknav('[href="#hash-target"]'); + await clicknav('[href="#replace-state"]'); + + await page.goBack(); + expect(await page.url()).toBe(`${baseURL}/routing/hashes/a`); + }); + test('does not normalize external path', async ({ page, start_server }) => { const html_ok = 'ok'; const { port } = await start_server((_req, res) => { diff --git a/packages/kit/test/apps/basics/test/cross-platform/test.js b/packages/kit/test/apps/basics/test/cross-platform/test.js index 38bfdb02c7aa..3d69b9f221e5 100644 --- a/packages/kit/test/apps/basics/test/cross-platform/test.js +++ b/packages/kit/test/apps/basics/test/cross-platform/test.js @@ -743,19 +743,6 @@ test.describe('Routing', () => { expect(await page.textContent('h1')).toBe('Great success!'); }); - test('back button returns to previous route when previous route has been navigated to via hash anchor', async ({ - page, - clicknav - }) => { - await page.goto('/routing/hashes/a'); - - await page.locator('[href="#hash-target"]').click(); - await clicknav('[href="/routing/hashes/b"]'); - - await page.goBack(); - expect(await page.textContent('h1')).toBe('a'); - }); - test('focus works if page load has hash', async ({ page, browserName }) => { await page.goto('/routing/hashes/target#p2');