Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function Nav({ type }: { type: 'header' | 'footer' }) {
className: 'font-bold',
}}
activeOptions={{ exact: true }}
data-testid={`${prefix}-home-link`}
>
{prefix}-/
</Link>{' '}
Expand All @@ -53,6 +54,7 @@ function Nav({ type }: { type: 'header' | 'footer' }) {
activeProps={{
className: 'font-bold',
}}
data-testid={`${prefix}-${options.to}-link`}
>
{prefix}-{options.to}
</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import * as React from 'react'
import { Link, linkOptions } from '@tanstack/react-router'
import { Link, linkOptions, useNavigate } from '@tanstack/react-router'

export const Route = createFileRoute('/')({
component: HomeComponent,
})

function HomeComponent() {
const navigate = useNavigate()

return (
<div className="p-2 grid gap-2">
<h3>Welcome Home!</h3>
Expand All @@ -27,12 +29,59 @@ function HomeComponent() {
<div key={`index-page-tests-${options.to}-${i}`} className="border p-2">
<h4>{options.to} tests</h4>
<p>
<Link {...options} hash="at-the-bottom">
<Link
{...options}
hash="at-the-bottom"
data-testid={`index-${options.to}-hash-link`}
>
{options.to}#at-the-bottom
</Link>
</p>
</div>
))}
<div className="border p-2">
<h4>scrollRestorationBehavior tests (Link)</h4>
<p>
<Link
to="/normal-page"
scrollRestorationBehavior="smooth"
data-testid="smooth-scroll-link"
>
/normal-page (smooth scroll)
</Link>
</p>
<p>
<Link to="/lazy-page" data-testid="default-scroll-link">
/lazy-page (default scroll)
</Link>
</p>
</div>
<div className="border p-2">
<h4>scrollRestorationBehavior tests (navigate)</h4>
<p>
<button
type="button"
data-testid="smooth-scroll-navigate-btn"
onClick={() =>
navigate({
to: '/normal-page',
scrollRestorationBehavior: 'smooth',
})
}
>
navigate to /normal-page (smooth scroll)
</button>
</p>
<p>
<button
type="button"
data-testid="default-scroll-navigate-btn"
onClick={() => navigate({ to: '/lazy-page' })}
>
navigate to /lazy-page (default scroll)
</button>
</p>
</div>
</div>
)
}
228 changes: 223 additions & 5 deletions e2e/react-router/scroll-restoration-sandbox-vite/tests/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pages.forEach((options, index) => {
page,
}) => {
await page.goto(toRuntimePath(from))
const link = page.getByRole('link', { name: `Foot-${options.to}` })
const link = page.getByTestId(`Foot-${options.to}-link`)
await link.scrollIntoViewIfNeeded()
await page.waitForTimeout(500)
await link.click()
Expand All @@ -37,7 +37,7 @@ pages.forEach((options) => {
page,
}) => {
await page.goto(toRuntimePath('/'))
await page.getByRole('link', { name: `Head-${options.to}` }).click()
await page.getByTestId(`Head-${options.to}-link`).click()
await expect(page.getByTestId('at-the-top')).toBeInViewport()
})

Expand All @@ -46,9 +46,7 @@ pages.forEach((options) => {
page,
}) => {
await page.goto(toRuntimePath('/'))
await page
.getByRole('link', { name: `${options.to}#at-the-bottom` })
.click()
await page.getByTestId(`index-${options.to}-hash-link`).click()
await expect(page.getByTestId('at-the-bottom')).toBeInViewport()
})

Expand All @@ -64,3 +62,223 @@ pages.forEach((options) => {
await expect(page.getByTestId('at-the-bottom')).toBeInViewport()
})
})

// Test for scrollRestorationBehavior option
test('scrollRestorationBehavior: smooth should pass behavior to window.scrollTo', async ({
page,
}) => {
// Intercept window.scrollTo to capture calls with their options
await page.addInitScript(() => {
;(window as any).__scrollToCalls = []
const originalScrollTo = window.scrollTo.bind(window)
window.scrollTo = ((...args: any[]) => {
;(window as any).__scrollToCalls.push(args)
return originalScrollTo(...args)
}) as typeof window.scrollTo
})

// First go to /normal-page
await page.goto(toRuntimePath('/normal-page'))
await expect(page.getByTestId('at-the-top')).toBeInViewport()

// Scroll down a bit
await page.evaluate(() => window.scrollBy(0, 300))
await page.waitForTimeout(200)

// Go back to home
await page.getByTestId('Head-home-link').click()
await expect(
page.getByRole('heading', { name: 'Welcome Home!' }),
).toBeVisible()

// Clear any scroll calls from previous navigations
await page.evaluate(() => {
;(window as any).__scrollToCalls = []
})

// Click link with scrollRestorationBehavior: 'smooth'
await page.getByTestId('smooth-scroll-link').click()
await expect(page.getByTestId('at-the-top')).toBeInViewport()

// Get the scroll calls and find the one with behavior option
const smoothCalls = await page.evaluate(() => (window as any).__scrollToCalls)

// There should be at least one scrollTo call with behavior: 'smooth'
const hasSmoothBehavior = smoothCalls.some(
(call: any) => call[0]?.behavior === 'smooth',
)
expect(hasSmoothBehavior).toBe(true)
})

test('scrollRestorationBehavior should reset to default after navigation', async ({
page,
}) => {
// Intercept window.scrollTo to capture calls with their options
await page.addInitScript(() => {
;(window as any).__scrollToCalls = []
const originalScrollTo = window.scrollTo.bind(window)
window.scrollTo = ((...args: any[]) => {
;(window as any).__scrollToCalls.push(args)
return originalScrollTo(...args)
}) as typeof window.scrollTo
})

// First go to /normal-page
await page.goto(toRuntimePath('/normal-page'))
await expect(page.getByTestId('at-the-top')).toBeInViewport()

// Scroll down a bit
await page.evaluate(() => window.scrollBy(0, 300))
await page.waitForTimeout(200)

// Go back to home
await page.getByTestId('Head-home-link').click()
await expect(
page.getByRole('heading', { name: 'Welcome Home!' }),
).toBeVisible()

// Navigate with smooth scroll
await page.getByTestId('smooth-scroll-link').click()
await expect(page.getByTestId('at-the-top')).toBeInViewport()

// Scroll down a bit on normal-page
await page.evaluate(() => window.scrollBy(0, 300))
await page.waitForTimeout(200)

// Navigate back to home
await page.getByTestId('Head-home-link').click()
await expect(
page.getByRole('heading', { name: 'Welcome Home!' }),
).toBeVisible()

// Clear scroll calls
await page.evaluate(() => {
;(window as any).__scrollToCalls = []
})

// Now navigate with default scroll (no scrollRestorationBehavior)
await page.getByTestId('default-scroll-link').click()
await expect(page.getByTestId('at-the-top')).toBeInViewport()

// Get the scroll calls - should NOT have behavior: 'smooth'
const defaultCalls = await page.evaluate(
() => (window as any).__scrollToCalls,
)

// The scrollTo calls should not have behavior: 'smooth'
// (they should have undefined behavior or no behavior property)
const hasSmoothBehavior = defaultCalls.some(
(call: any) => call[0]?.behavior === 'smooth',
)
expect(hasSmoothBehavior).toBe(false)
})

// Test for imperative navigate() with scrollRestorationBehavior option
test('navigate() with scrollRestorationBehavior: smooth should pass behavior to window.scrollTo', async ({
page,
}) => {
// Intercept window.scrollTo to capture calls with their options
await page.addInitScript(() => {
;(window as any).__scrollToCalls = []
const originalScrollTo = window.scrollTo.bind(window)
window.scrollTo = ((...args: any[]) => {
;(window as any).__scrollToCalls.push(args)
return originalScrollTo(...args)
}) as typeof window.scrollTo
})

// First go to /normal-page
await page.goto(toRuntimePath('/normal-page'))
await expect(page.getByTestId('at-the-top')).toBeInViewport()

// Scroll down a bit
await page.evaluate(() => window.scrollBy(0, 300))
await page.waitForTimeout(200)

// Go back to home
await page.getByTestId('Head-home-link').click()
await expect(
page.getByRole('heading', { name: 'Welcome Home!' }),
).toBeVisible()

// Clear any scroll calls from previous navigations
await page.evaluate(() => {
;(window as any).__scrollToCalls = []
})

// Click button that uses navigate() with scrollRestorationBehavior: 'smooth'
await page.getByTestId('smooth-scroll-navigate-btn').click()
await expect(page.getByTestId('at-the-top')).toBeInViewport()

// Get the scroll calls and find the one with behavior option
const smoothCalls = await page.evaluate(() => (window as any).__scrollToCalls)

// There should be at least one scrollTo call with behavior: 'smooth'
const hasSmoothBehavior = smoothCalls.some(
(call: any) => call[0]?.behavior === 'smooth',
)
expect(hasSmoothBehavior).toBe(true)
})

test('navigate() scrollRestorationBehavior should reset to default after navigation', async ({
page,
}) => {
// Intercept window.scrollTo to capture calls with their options
await page.addInitScript(() => {
;(window as any).__scrollToCalls = []
const originalScrollTo = window.scrollTo.bind(window)
window.scrollTo = ((...args: any[]) => {
;(window as any).__scrollToCalls.push(args)
return originalScrollTo(...args)
}) as typeof window.scrollTo
})

// First go to /normal-page
await page.goto(toRuntimePath('/normal-page'))
await expect(page.getByTestId('at-the-top')).toBeInViewport()

// Scroll down a bit
await page.evaluate(() => window.scrollBy(0, 300))
await page.waitForTimeout(200)

// Go back to home
await page.getByTestId('Head-home-link').click()
await expect(
page.getByRole('heading', { name: 'Welcome Home!' }),
).toBeVisible()

// Navigate with smooth scroll using navigate()
await page.getByTestId('smooth-scroll-navigate-btn').click()
await expect(page.getByTestId('at-the-top')).toBeInViewport()

// Scroll down a bit on normal-page
await page.evaluate(() => window.scrollBy(0, 300))
await page.waitForTimeout(200)

// Navigate back to home
await page.getByTestId('Head-home-link').click()
await expect(
page.getByRole('heading', { name: 'Welcome Home!' }),
).toBeVisible()

// Clear scroll calls
await page.evaluate(() => {
;(window as any).__scrollToCalls = []
})

// Now navigate with default scroll using navigate() (no scrollRestorationBehavior)
await page.getByTestId('default-scroll-navigate-btn').click()
await expect(page.getByTestId('at-the-top')).toBeInViewport()

// Get the scroll calls - should NOT have behavior: 'smooth'
const defaultCalls = await page.evaluate(
() => (window as any).__scrollToCalls,
)

// The scrollTo calls should not have behavior: 'smooth'
// (they should have undefined behavior or no behavior property)
const hasSmoothBehavior = defaultCalls.some(
(call: any) => call[0]?.behavior === 'smooth',
)
expect(hasSmoothBehavior).toBe(false)
})
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function Nav({ type }: { type: 'header' | 'footer' }) {
class: 'font-bold',
}}
activeOptions={{ exact: true }}
data-testid={`${prefix}-home-link`}
>
{prefix}-/
</Link>{' '}
Expand All @@ -54,6 +55,7 @@ function Nav({ type }: { type: 'header' | 'footer' }) {
activeProps={{
class: 'font-bold',
}}
data-testid={`${prefix}-${options.to}-link`}
>
{prefix}-{options.to}
</Link>
Expand Down
Loading
Loading