Files
Andrew ClarkandGitHub befa5fb082 Add experimental_gesturePush to App Router (#88776)
## Summary

This adds a new experimental router method `experimental_gesturePush`
that enables gesture-driven navigation. The feature is gated behind a
new `experimental.gestureTransition` config flag, which implicitly opts
the app into using the experimental React build.

It is _not_ suitable for production use yet; it's a proof-of-concept so
we can test the integration between the Next.js client router and
React's Gesture Transitions experiment. You can use it to build demos,
but it shouldn't be used in a real app.

It dispatches updates through React's useOptimistic hook instead of the
main router state queue, essentially forking the router state until the
gesture completes.

This should only be called inside `startGestureTransition`. It's a
low-level method that is not meant to be used in application code, only
in libraries that know how to properly integrate with both Next and
React.

## Test Plan

- E2E test that simulates a gesture by holding an async transition open
between button clicks
- Run: `pnpm testonly test/e2e/app-dir/gesture-transitions`
2026-01-20 00:35:10 -05:00

58 lines
2.1 KiB
TypeScript

/**
* Tests for experimental_gesturePush, which enables gesture-driven navigation
* via React's Gesture Transitions feature.
*
* The test simulates a gesture by using two buttons:
* - "Start Gesture" calls experimental_gesturePush and begins an async
* transition that doesn't complete until "End Gesture" is clicked
* - "End Gesture" resolves the pending promise and triggers the canonical
* router.push
*
* This allows us to observe the intermediate gesture state before the
* canonical navigation completes.
*/
import { nextTestSetup } from 'e2e-utils'
describe('gesture-transitions', () => {
const { next } = nextTestSetup({
files: __dirname,
})
it('shows optimistic state during gesture, then canonical state after', async () => {
const browser = await next.browser('/')
// Verify we're on the home page
expect(
await browser.elementByCss('[data-testid="home-page"]').text()
).toContain('Home')
// Click "Start Gesture" to begin the optimistic navigation
await browser.elementByCss('[data-testid="start-gesture"]').click()
// The target page content should be visible (optimistic state)
const targetPage = await browser.elementByCss('[data-testid="target-page"]')
expect(await targetPage.text()).toContain('Target Page')
// Static content should be visible
expect(
await browser.elementByCss('[data-testid="static-content"]').text()
).toBe('This is static content')
// The URL should have updated to /target-page
expect(await browser.url()).toContain('/target-page')
// Click "End Gesture" to complete the canonical navigation
await browser.elementByCss('[data-testid="end-gesture"]').click()
// After the gesture ends, we should still be on the target page
// with the canonical state (dynamic content fully loaded)
const dynamicContent = await browser.elementByCss(
'[data-testid="dynamic-content"]'
)
expect(await dynamicContent.text()).toBe('Dynamic content')
// URL should still be /target-page
expect(await browser.url()).toContain('/target-page')
})
})