Skip to content

Commit fddd4dc

Browse files
martrappnatemoo-re
andauthored
Fixes in the client-side router (#8166)
* Fixes in the client-side router * reverted function declaration after review (#8166) --------- Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
1 parent cfc465d commit fddd4dc

5 files changed

Lines changed: 105 additions & 9 deletions

File tree

.changeset/chilled-shoes-fail.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+
ViewTransitions: Fixes in the client-side router

packages/astro/components/ViewTransitions.astro

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,21 @@ const { fallback = 'animate' } = Astro.props as Props;
2020
type Events = 'astro:load' | 'astro:beforeload';
2121

2222
const persistState = (state: State) => history.replaceState(state, '');
23+
const supportsViewTransitions = !!document.startViewTransition;
24+
const transitionEnabledOnThisPage = () =>
25+
!!document.querySelector('[name="astro-view-transitions-enabled"]');
26+
const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
27+
const onload = () => triggerEvent('astro:load');
28+
const PERSIST_ATTR = 'data-astro-transition-persist';
2329

2430
// The History API does not tell you if navigation is forward or back, so
2531
// you can figure it using an index. On pushState the index is incremented so you
2632
// can use that to determine popstate if going forward or back.
2733
let currentHistoryIndex = history.state?.index || 0;
28-
if (!history.state) {
34+
if (!history.state && transitionEnabledOnThisPage()) {
2935
persistState({ index: currentHistoryIndex, scrollY: 0 });
3036
}
3137

32-
const supportsViewTransitions = !!document.startViewTransition;
33-
const transitionEnabledOnThisPage = () =>
34-
!!document.querySelector('[name="astro-view-transitions-enabled"]');
35-
const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
36-
const onload = () => triggerEvent('astro:load');
37-
const PERSIST_ATTR = 'data-astro-transition-persist';
38-
3938
const throttle = (cb: (...args: any[]) => any, delay: number) => {
4039
let wait = false;
4140
// During the waiting time additional events are lost.
@@ -323,9 +322,10 @@ const { fallback = 'animate' } = Astro.props as Props;
323322
});
324323

325324
addEventListener('popstate', (ev) => {
326-
if (!transitionEnabledOnThisPage()) {
325+
if (!transitionEnabledOnThisPage() && ev.state) {
327326
// The current page doesn't haven't View Transitions,
328327
// respect that with a full page reload
328+
// -- but only for transition managed by us (ev.state is set)
329329
location.reload();
330330
return;
331331
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
import { ViewTransitions } from 'astro:transitions';
3+
4+
// For the test fixture, we import the script but we don't use the <ViewTransitions /> component
5+
// While this seems to be some strange mistake,
6+
// it might be realistic, e.g. in a configurable CommenHead component
7+
8+
interface Props {
9+
transitions?: string;
10+
}
11+
const { transitions } = Astro.props;
12+
---
13+
<html>
14+
<head>
15+
<title>Half-Baked</title>
16+
{transitions && <ViewTransitions />}
17+
</head>
18+
<body>
19+
<main>
20+
<p id="half-baked">Half Baked</p>
21+
<a id="click-hash" href="#click-hash">hash target</a>
22+
</main>
23+
</body>
24+
</html>

packages/astro/e2e/fixtures/view-transitions/src/pages/three.astro

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
<main>
77
<p id="three">Page 3</p>
88
<a id="click-two" href="/two">go to 2</a>
9+
<br/>
10+
<a id="click-hash" href="#click-hash">hash target</a>
11+
<p style="height: 150vh">Long paragraph</p>
912
</main>
1013
</body>
1114
</html>

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,40 @@ test.describe('View Transitions', () => {
112112
).toEqual(2);
113113
});
114114

115+
test('Moving within a page without ViewTransitions does not trigger a full page navigation', async ({
116+
page,
117+
astro,
118+
}) => {
119+
const loads = [];
120+
page.addListener('load', async (p) => {
121+
loads.push(p.title());
122+
});
123+
// Go to page 1
124+
await page.goto(astro.resolveUrl('/one'));
125+
let p = page.locator('#one');
126+
await expect(p, 'should have content').toHaveText('Page 1');
127+
128+
// Go to page 3 which does *not* have ViewTransitions enabled
129+
await page.click('#click-three');
130+
p = page.locator('#three');
131+
await expect(p, 'should have content').toHaveText('Page 3');
132+
133+
// click a hash link to navigate further down the page
134+
await page.click('#click-hash');
135+
// still on page 3
136+
p = page.locator('#three');
137+
await expect(p, 'should have content').toHaveText('Page 3');
138+
139+
// check that we are further down the page
140+
const Y = await page.evaluate(() => window.scrollY);
141+
expect(Y, 'The target is further down the page').toBeGreaterThan(0);
142+
143+
expect(
144+
loads.length,
145+
'There should be only 1 page load. The original, but no additional loads for the hash change'
146+
).toEqual(1);
147+
});
148+
115149
test('Moving from a page without ViewTransitions w/ back button', async ({ page, astro }) => {
116150
const loads = [];
117151
page.addListener('load', (p) => {
@@ -332,4 +366,34 @@ test.describe('View Transitions', () => {
332366

333367
await expect(loads.length, 'There should only be 1 page load').toEqual(1);
334368
});
369+
370+
test('Importing ViewTransitions w/o using the component must not mess with history', async ({
371+
page,
372+
astro,
373+
}) => {
374+
const loads = [];
375+
page.addListener('load', async (p) => {
376+
loads.push(p);
377+
});
378+
// Go to the half bakeed page
379+
await page.goto(astro.resolveUrl('/half-baked'));
380+
let p = page.locator('#half-baked');
381+
await expect(p, 'should have content').toHaveText('Half Baked');
382+
383+
// click a hash link to navigate further down the page
384+
await page.click('#click-hash');
385+
// still on page
386+
p = page.locator('#half-baked');
387+
await expect(p, 'should have content').toHaveText('Half Baked');
388+
389+
// go back within same page without reloading
390+
await page.goBack();
391+
p = page.locator('#half-baked');
392+
await expect(p, 'should have content').toHaveText('Half Baked');
393+
394+
expect(
395+
loads.length,
396+
'There should be only 1 page load. No additional loads for going back on same page'
397+
).toEqual(1);
398+
});
335399
});

0 commit comments

Comments
 (0)