Skip to content

Commit d7e6eb3

Browse files
committed
emit hashchange event with view transitions
1 parent 74c8852 commit d7e6eb3

File tree

3 files changed

+73
-0
lines changed

3 files changed

+73
-0
lines changed

.changeset/sour-ghosts-fall.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes `hashchange` events not firing during same-page hash navigation with view transitions.

packages/astro/e2e/view-transitions.test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ function collectPreloads(page) {
4242
});
4343
}
4444

45+
function collectHashChanges(page) {
46+
return page.evaluate(() => {
47+
window.hashchanges = [];
48+
window.addEventListener('hashchange', (e) => {
49+
window.hashchanges.push({
50+
oldURL: e.oldURL,
51+
newURL: e.newURL,
52+
});
53+
});
54+
});
55+
}
56+
4557
async function nativeViewTransition(page) {
4658
return page.evaluate(() => document.startViewTransition !== undefined);
4759
}
@@ -1714,4 +1726,52 @@ test.describe('View Transitions', () => {
17141726
await new Promise((resolve) => setTimeout(resolve, 1000));
17151727
expect(lines.join('')).toBe('');
17161728
});
1729+
1730+
test('hashchange event fires for same-page hash navigation', async ({ page, astro }) => {
1731+
await page.goto(astro.resolveUrl('/long-page'));
1732+
await collectHashChanges(page);
1733+
1734+
// navigate to hash on same page
1735+
await page.click('#click-scroll-down');
1736+
await page.waitForTimeout(100);
1737+
1738+
// check hashchange event was fired
1739+
const events = await page.evaluate(() => window.hashchanges);
1740+
expect(events.length).toBe(1);
1741+
expect(events[0].oldURL).toContain('/long-page');
1742+
expect(events[0].newURL).toContain('/long-page#click-one-again');
1743+
expect(events[0].oldURL).not.toContain('#');
1744+
});
1745+
1746+
test('hashchange event fires for back/forward with hash', async ({ page, astro }) => {
1747+
await page.goto(astro.resolveUrl('/long-page'));
1748+
await collectHashChanges(page);
1749+
1750+
// navigate to hash
1751+
await page.click('#click-scroll-down');
1752+
await page.waitForTimeout(100);
1753+
1754+
await page.goBack();
1755+
await page.waitForTimeout(100);
1756+
1757+
// should have two hashchange events
1758+
const events = await page.evaluate(() => window.hashchanges);
1759+
expect(events.length).toBe(2);
1760+
expect(events[1].oldURL).toContain('#click-one-again');
1761+
expect(events[1].newURL).toContain('/long-page');
1762+
expect(events[1].newURL).not.toContain('#');
1763+
});
1764+
1765+
test('hashchange event does NOT fire for cross-page navigation', async ({ page, astro }) => {
1766+
await page.goto(astro.resolveUrl('/one'));
1767+
await collectHashChanges(page);
1768+
1769+
// navigate to different page
1770+
await page.click('#click-two');
1771+
await page.waitForTimeout(100);
1772+
1773+
// should have zero hashchange events
1774+
const events = await page.evaluate(() => window.hashchanges);
1775+
expect(events.length).toBe(0);
1776+
});
17171777
});

packages/astro/src/transitions/router.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,14 @@ const moveToLocation = (
242242
scrollTo({ left: 0, top: 0, behavior: 'instant' });
243243
}
244244
}
245+
if (intraPage && from.hash !== to.hash) {
246+
window.dispatchEvent(
247+
new HashChangeEvent('hashchange', {
248+
oldURL: from.href,
249+
newURL: to.href,
250+
}),
251+
);
252+
}
245253
history.scrollRestoration = 'manual';
246254
}
247255
};

0 commit comments

Comments
 (0)