Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions e2e/base-path.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,33 @@ test.describe(`base-path`, () => {
text: 'hello',
},
});

// get trailing slash
const resGetTrailing = await request.get(`${baseUrl}hello/`);
expect(resGetTrailing.ok()).toBe(true);
expect(await resGetTrailing.json()).toEqual({
ok: true,
request: {
handler: 'GET',
method: 'GET',
pathname: '/hello/',
},
});

// post trailing slash
const resPostTrailing = await request.post(`${baseUrl}hello/`, {
data: 'hello',
});
expect(resPostTrailing.ok()).toBe(true);
expect(await resPostTrailing.json()).toEqual({
ok: true,
request: {
handler: 'POST',
method: 'POST',
pathname: '/hello/',
text: 'hello',
},
});
});

test('router', async ({ page }) => {
Expand All @@ -52,16 +79,27 @@ test.describe(`base-path`, () => {
await waitForHydration(page);

// push
await page.getByText('dynamic-push').click();
await page
.getByRole('button', { name: 'dynamic-push', exact: true })
.click();
await page.waitForURL(`${baseUrl}dynamic`);
await expect(
page.getByRole('heading', { name: 'Dynamic page' }),
).toBeVisible();

// push trailing slash
await page.goto(baseUrl);
await waitForHydration(page);
await page.getByRole('button', { name: 'dynamic-push-trailing' }).click();
await page.waitForURL(`${baseUrl}dynamic/`);
await expect(
page.getByRole('heading', { name: 'Dynamic page' }),
).toBeVisible();

// replace
await page.goto(baseUrl);
await waitForHydration(page);
await page.getByText('dynamic-replace').click();
await page.getByRole('button', { name: 'dynamic-replace' }).click();
await page.waitForURL(`${baseUrl}dynamic`);
await expect(
page.getByRole('heading', { name: 'Dynamic page' }),
Expand Down Expand Up @@ -115,4 +153,9 @@ async function basicTest(page: Page, baseUrl: string) {
await expect(
page.getByRole('heading', { name: 'Static page' }),
).toBeVisible();

await page.goto(`${baseUrl}static/`);
await expect(
page.getByRole('heading', { name: 'Static page' }),
).toBeVisible();
}
3 changes: 3 additions & 0 deletions e2e/fixtures/base-path/src/pages/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export default async function RootLayout({
<li>
<ClickLink to="/dynamic">dynamic-push</ClickLink>
</li>
<li>
<ClickLink to="/dynamic/">dynamic-push-trailing</ClickLink>
</li>
<li>
<ClickLink to="/dynamic" replace>
dynamic-replace
Expand Down
29 changes: 20 additions & 9 deletions e2e/fixtures/router-client-no-404/src/components/route-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,25 @@ export function PushMissingButton() {
const router = useRouter();
const push = router.push as unknown as (to: string) => Promise<void>;
return (
<button
data-testid="go-missing"
onClick={() => {
void push('/missing');
}}
type="button"
>
Go missing
</button>
<>
<button
data-testid="go-missing"
onClick={() => {
void push('/missing');
}}
type="button"
>
Go missing
</button>
<button
data-testid="go-missing-trailing"
onClick={() => {
void push('/missing/');
}}
type="button"
>
Go missing trailing
</button>
</>
);
}
9 changes: 9 additions & 0 deletions e2e/fixtures/router-client/src/components/route-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ export function RouteState() {
router.push hash missing
</button>
</p>
<p>
<button
data-testid="router-push-trailing-slash"
onClick={() => router.push('/start/')}
type="button"
>
router.push trailing slash
</button>
</p>
<p>
<button
data-testid="router-push-next"
Expand Down
13 changes: 13 additions & 0 deletions e2e/fixtures/use-router/src/TestRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ export default function TestRouter() {
router.push prefetched static target
</button>
</p>
<p>
<button
data-testid="router-push-static-trailing"
onClick={() => router.push('/static/')}
>
router.push static trailing target
</button>
</p>
<p>
<Link to="/static/" data-testid="link-static-trailing">
Go to static trailing
</Link>
</p>
</>
);
}
34 changes: 34 additions & 0 deletions e2e/fs-router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ test.describe('fs-router', () => {
await expect(page.getByRole('heading', { name: 'Foo' })).toBeVisible();
});

test('foo with trailing slash', async ({ page }) => {
await page.goto(`http://localhost:${port}/foo/`);
await expect(page.getByRole('heading', { name: 'Foo' })).toBeVisible();
});

test('nested/foo', async ({ page }) => {
// /nested/foo is defined as a staticPath of /nested/[id] which matches this layout
await page.goto(`http://localhost:${port}/nested/foo`);
Expand All @@ -56,6 +61,20 @@ test.describe('fs-router', () => {
).toBeVisible();
});

test('nested/baz with trailing slash', async ({ page }) => {
await page.goto(`http://localhost:${port}/nested/baz/`);
await expect(
page.getByRole('heading', { name: 'Nested Layout' }),
).toBeVisible();
});

test('static-nested encoded path with trailing slash', async ({ page }) => {
await page.goto(`http://localhost:${port}/static-nested/encoded%20path/`);
await expect(
page.getByRole('heading', { name: 'Nested / encoded%20path' }),
).toBeVisible();
});

test('check hydration error', async ({ page }) => {
const messages: string[] = [];
page.on('console', (msg) => messages.push(msg.text()));
Expand Down Expand Up @@ -85,6 +104,12 @@ test.describe('fs-router', () => {
expect(await res.text()).toBe('Hello from API!');
});

test('api hi with trailing slash', async () => {
const res = await fetch(`http://localhost:${port}/hi/`);
expect(res.status).toBe(200);
expect(await res.text()).toBe('Hello from API!');
});

test('api hi.txt', async () => {
const res = await fetch(`http://localhost:${port}/hi.txt`);
expect(res.status).toBe(200);
Expand All @@ -106,6 +131,15 @@ test.describe('fs-router', () => {
expect(await res.text()).toBe('POST Hello from API! from the test!');
});

test('api hi with POST and trailing slash', async () => {
const res = await fetch(`http://localhost:${port}/hi/`, {
method: 'POST',
body: 'from the test!',
});
expect(res.status).toBe(200);
expect(await res.text()).toBe('POST Hello from API! from the test!');
});

test('api has-default GET', async () => {
const res = await fetch(`http://localhost:${port}/has-default`);
expect(res.status).toBe(200);
Expand Down
17 changes: 17 additions & 0 deletions e2e/router-client-no-404.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,21 @@ test.describe('router-client-no-404', () => {
);
await expect(page).toHaveURL(/\/missing$/);
});

test('client navigation to missing trailing-slash route renders Not Found fallback without /404 page', async ({
page,
}) => {
await page.goto(`http://localhost:${port}/start`);
await waitForHydration(page);

await page.getByTestId('go-missing-trailing').click();

await expect(
page.getByRole('heading', { name: 'Not Found' }),
).toBeVisible();
await expect(page.getByRole('heading', { name: 'Custom 404' })).toHaveCount(
0,
);
await expect(page).toHaveURL(/\/missing\/$/);
});
});
20 changes: 20 additions & 0 deletions e2e/router-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,26 @@ test.describe('router-client', () => {
expect(scrollToCalls).toHaveLength(0);
});

test('same-route trailing-slash navigation preserves scroll and keeps canonical route path', async ({
page,
}) => {
await page.goto(`http://localhost:${port}/start`);
await waitForHydration(page);

await page.evaluate(() => {
window.scrollTo({ left: 0, top: 600 });
});
await installScrollToRecorder(page);

await page.getByTestId('router-push-trailing-slash').click();

await expect(page.getByTestId('route-path')).toHaveText('/start');
await expect(page.getByTestId('route-query')).toHaveText('');
await expect(page.getByTestId('route-hash')).toHaveText('');
await expect(page).toHaveURL(/\/start\/$/);
expect(await getScrollToCalls(page)).toHaveLength(0);
});

test('path-change link navigation resets scroll position to top by default', async ({
page,
}) => {
Expand Down
42 changes: 40 additions & 2 deletions e2e/use-router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ test.describe('useRouter', () => {
await expect(page.getByRole('heading', { name: 'Static' })).toBeVisible();
await expect(page.getByTestId('path')).toHaveText('Path: /static');
});

test(`on dynamic pages with trailing slash`, async ({ page }) => {
await page.goto(`http://localhost:${port}/dynamic/`);
await expect(
page.getByRole('heading', { name: 'Dynamic' }),
).toBeVisible();
await expect(page.getByTestId('path')).toHaveText('Path: /dynamic');
});

test(`on static pages with trailing slash`, async ({ page }) => {
await page.goto(`http://localhost:${port}/static/`);
await expect(page.getByRole('heading', { name: 'Static' })).toBeVisible();
await expect(page.getByTestId('path')).toHaveText('Path: /static');
});
});

test.describe('updates path on link navigation', () => {
Expand Down Expand Up @@ -63,7 +77,9 @@ test.describe('useRouter', () => {
}) => {
await page.goto(`http://localhost:${port}/dynamic`);
await waitForHydration(page);
await page.getByRole('link', { name: 'Go to static' }).click();
await page
.getByRole('link', { name: 'Go to static', exact: true })
.click();
await expect(page.getByRole('heading', { name: 'Static' })).toBeVisible();
const beforeReplaceHistoryLength = await page.evaluate(
() => window.history.length,
Expand Down Expand Up @@ -97,7 +113,9 @@ test.describe('useRouter', () => {
await page.click('text=Increment query');
await expect(page.getByTestId('query')).toHaveText('Query: 1');

await page.getByRole('link', { name: 'Go to static' }).click();
await page
.getByRole('link', { name: 'Go to static', exact: true })
.click();
await expect(page.getByRole('heading', { name: 'Static' })).toBeVisible();
await expect(page.getByTestId('query')).toHaveText('Query: 0');

Expand Down Expand Up @@ -174,6 +192,26 @@ test.describe('useRouter', () => {
await expect(page.getByRole('heading', { name: 'Bar' })).toBeVisible();
expect(messages.some((msg) => msg.includes('Uncaught'))).toBe(false);
});

test('router.push preserves trailing-slash URL while keeping canonical path', async ({
page,
}) => {
await page.goto(`http://localhost:${port}/dynamic`);
await waitForHydration(page);
await page.getByTestId('router-push-static-trailing').click();
await expect(page.getByRole('heading', { name: 'Static' })).toBeVisible();
await expect(page).toHaveURL(`http://localhost:${port}/static/`);
await expect(page.getByTestId('path')).toHaveText('Path: /static');
});

test('Link supports trailing-slash targets', async ({ page }) => {
await page.goto(`http://localhost:${port}/dynamic`);
await waitForHydration(page);
await page.getByTestId('link-static-trailing').click();
await expect(page.getByRole('heading', { name: 'Static' })).toBeVisible();
await expect(page).toHaveURL(`http://localhost:${port}/static/`);
await expect(page.getByTestId('path')).toHaveText('Path: /static');
});
});

test.describe('retrieves query variables', () => {
Expand Down
2 changes: 1 addition & 1 deletion examples/22_define-router/src/waku.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export default adapter(
// using `[slug]` syntax is just an example and it technically conflicts with others. So, it's better to use a different prefix like `dynamic-page:`.
'page:/dynamic/[slug]': {
isStatic: false,
renderer: ({ pathname }) => <h3>{pathname}</h3>,
renderer: ({ routePath }) => <h3>{routePath}</h3>,
},
},
},
Expand Down
Loading
Loading