From e00e47d560d0473397f42d937b1b4485cc5d71d9 Mon Sep 17 00:00:00 2001 From: Zack Tanner <1939140+ztanner@users.noreply.github.com> Date: Sun, 9 Jun 2024 09:34:14 -0700 Subject: [PATCH] ensure router cache updates reference the latest cache values --- .../reducers/navigate-reducer.ts | 5 +- .../client-cache.defaults.test.ts | 3 +- .../client-cache.experimental.test.ts | 3 +- .../client-cache.original.test.ts | 3 +- .../client-cache.parallel-routes.test.ts | 81 +++++++++++++++++++ .../app/@breadcrumbs/[id]/page.js | 7 ++ .../parallel-routes/app/@breadcrumbs/page.js | 3 + .../fixtures/parallel-routes/app/[id]/page.js | 18 +++++ .../fixtures/parallel-routes/app/layout.js | 12 +++ .../fixtures/parallel-routes/app/page.js | 11 +++ .../fixtures/regular/app/[id]/client.js | 5 ++ .../regular}/app/[id]/loading.js | 0 .../{ => fixtures/regular}/app/[id]/page.js | 2 + .../{ => fixtures/regular}/app/layout.js | 0 .../regular}/app/null-loading/loading.js | 0 .../regular}/app/null-loading/page.js | 0 .../{ => fixtures/regular}/app/page.js | 0 .../without-loading/@breadcrumbs/[id]/page.js | 7 ++ .../app/without-loading/@breadcrumbs/page.js | 3 + .../regular}/app/without-loading/[id]/page.js | 0 .../regular/app/without-loading/layout.js | 8 ++ .../regular}/app/without-loading/page.js | 0 .../app-dir/app-client-cache/next.config.js | 1 + 23 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 test/e2e/app-dir/app-client-cache/client-cache.parallel-routes.test.ts create mode 100644 test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/@breadcrumbs/[id]/page.js create mode 100644 test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/@breadcrumbs/page.js create mode 100644 test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/[id]/page.js create mode 100644 test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/layout.js create mode 100644 test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/page.js create mode 100644 test/e2e/app-dir/app-client-cache/fixtures/regular/app/[id]/client.js rename test/e2e/app-dir/app-client-cache/{ => fixtures/regular}/app/[id]/loading.js (100%) rename test/e2e/app-dir/app-client-cache/{ => fixtures/regular}/app/[id]/page.js (86%) rename test/e2e/app-dir/app-client-cache/{ => fixtures/regular}/app/layout.js (100%) rename test/e2e/app-dir/app-client-cache/{ => fixtures/regular}/app/null-loading/loading.js (100%) rename test/e2e/app-dir/app-client-cache/{ => fixtures/regular}/app/null-loading/page.js (100%) rename test/e2e/app-dir/app-client-cache/{ => fixtures/regular}/app/page.js (100%) create mode 100644 test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/@breadcrumbs/[id]/page.js create mode 100644 test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/@breadcrumbs/page.js rename test/e2e/app-dir/app-client-cache/{ => fixtures/regular}/app/without-loading/[id]/page.js (100%) create mode 100644 test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/layout.js rename test/e2e/app-dir/app-client-cache/{ => fixtures/regular}/app/without-loading/page.js (100%) create mode 100644 test/e2e/app-dir/app-client-cache/next.config.js diff --git a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts index fe2853eade6430..7798c3ab7c57be 100644 --- a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts @@ -167,7 +167,7 @@ function navigateReducer_noPPR( updatedCanonicalUrl.split('#', 1)[0] let currentTree = state.tree - const currentCache = state.cache + let currentCache = state.cache let scrollableSegments: FlightSegmentPath[] = [] for (const flightDataPath of flightData) { const flightSegmentPath = flightDataPath.slice( @@ -258,6 +258,9 @@ function navigateReducer_noPPR( mutable.cache = cache } else if (applied) { mutable.cache = cache + // If we applied the cache, we update the "current cache" value so any other + // segments in the FlightDataPath will be able to reference the updated cache. + currentCache = cache } currentTree = newTree diff --git a/test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts b/test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts index f8a168aca14c74..dad47c457c4c05 100644 --- a/test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts +++ b/test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts @@ -7,10 +7,11 @@ import { fastForwardTo, getPathname, } from './test-utils' +import path from 'path' describe('app dir client cache semantics (default semantics)', () => { const { next, isNextDev } = nextTestSetup({ - files: __dirname, + files: path.join(__dirname, 'fixture', 'regular'), }) if (isNextDev) { diff --git a/test/e2e/app-dir/app-client-cache/client-cache.experimental.test.ts b/test/e2e/app-dir/app-client-cache/client-cache.experimental.test.ts index 604ead35788625..690b652789d955 100644 --- a/test/e2e/app-dir/app-client-cache/client-cache.experimental.test.ts +++ b/test/e2e/app-dir/app-client-cache/client-cache.experimental.test.ts @@ -1,11 +1,12 @@ import { nextTestSetup } from 'e2e-utils' import { browserConfigWithFixedTime, fastForwardTo } from './test-utils' import { findAllTelemetryEvents } from 'next-test-utils' +import path from 'path' describe('app dir client cache semantics (experimental staleTimes)', () => { describe('dynamic: 0', () => { const { next, isNextDev } = nextTestSetup({ - files: __dirname, + files: path.join(__dirname, 'fixture', 'regular'), nextConfig: { experimental: { staleTimes: { dynamic: 0 } }, }, diff --git a/test/e2e/app-dir/app-client-cache/client-cache.original.test.ts b/test/e2e/app-dir/app-client-cache/client-cache.original.test.ts index f5065702d6466f..1c3baa4341204d 100644 --- a/test/e2e/app-dir/app-client-cache/client-cache.original.test.ts +++ b/test/e2e/app-dir/app-client-cache/client-cache.original.test.ts @@ -7,11 +7,12 @@ import { fastForwardTo, getPathname, } from './test-utils' +import path from 'path' // This preserves existing tests for the 30s/5min heuristic (previous router defaults) describe('app dir client cache semantics (30s/5min)', () => { const { next, isNextDev } = nextTestSetup({ - files: __dirname, + files: path.join(__dirname, 'fixture', 'regular'), nextConfig: { experimental: { staleTimes: { dynamic: 30, static: 180 } }, }, diff --git a/test/e2e/app-dir/app-client-cache/client-cache.parallel-routes.test.ts b/test/e2e/app-dir/app-client-cache/client-cache.parallel-routes.test.ts new file mode 100644 index 00000000000000..7a418405623086 --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/client-cache.parallel-routes.test.ts @@ -0,0 +1,81 @@ +import { nextTestSetup } from 'e2e-utils' +import { check } from 'next-test-utils' +import { BrowserInterface } from 'next-webdriver' +import { + browserConfigWithFixedTime, + createRequestsListener, + fastForwardTo, + getPathname, +} from './test-utils' +import path from 'path' + +describe('app dir client cache with parallel routes', () => { + const { next } = nextTestSetup({ + files: path.join(__dirname, 'fixtures', 'parallel-routes'), + }) + + describe('prefetch={true}', () => { + let browser: BrowserInterface + + beforeEach(async () => { + browser = (await next.browser( + '/', + browserConfigWithFixedTime + )) as BrowserInterface + }) + + it('should prefetch the full page', async () => { + const { getRequests, clearRequests } = + await createRequestsListener(browser) + await check(() => { + return getRequests().some( + ([url, didPartialPrefetch]) => + getPathname(url) === '/0' && !didPartialPrefetch + ) + ? 'success' + : 'fail' + }, 'success') + + clearRequests() + + await browser + .elementByCss('[href="/0"]') + .click() + .waitForElementByCss('#random-number') + + expect(getRequests().every(([url]) => getPathname(url) !== '/0')).toEqual( + true + ) + }) + + it('should re-use the cache for the full page, only for 5 mins', async () => { + const randomNumber = await browser + .elementByCss('[href="/0"]') + .click() + .waitForElementByCss('#random-number') + .text() + + await browser.elementByCss('[href="/"]').click() + + const number = await browser + .elementByCss('[href="/0"]') + .click() + .waitForElementByCss('#random-number') + .text() + + expect(number).toBe(randomNumber) + + await browser.eval(fastForwardTo, 5 * 60 * 1000) + + await browser.elementByCss('[href="/"]').click() + + const newNumber = await browser + .elementByCss('[href="/0"]') + .click() + .waitForElementByCss('#random-number') + .text() + + expect(newNumber).not.toBe(randomNumber) + }) + }) +}) diff --git a/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/@breadcrumbs/[id]/page.js b/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/@breadcrumbs/[id]/page.js new file mode 100644 index 00000000000000..43ade54c21804c --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/@breadcrumbs/[id]/page.js @@ -0,0 +1,7 @@ +export default function Page({ params }) { + return ( +
+ Catchall
{JSON.stringify(params)}
{' '} +
+ ) +} diff --git a/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/@breadcrumbs/page.js b/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/@breadcrumbs/page.js new file mode 100644 index 00000000000000..9e02fe9730f1d0 --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/@breadcrumbs/page.js @@ -0,0 +1,3 @@ +export default function Page() { + return
Root Breadcrumb
+} diff --git a/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/[id]/page.js b/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/[id]/page.js new file mode 100644 index 00000000000000..cd2e35b1f72a6d --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/[id]/page.js @@ -0,0 +1,18 @@ +import Link from 'next/link' + +export default async function Page() { + const randomNumber = await new Promise((resolve) => { + setTimeout(() => { + resolve(Math.random()) + }, 1000) + }) + + return ( + <> +
+ Back to Home +
+
{randomNumber}
+ + ) +} diff --git a/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/layout.js b/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/layout.js new file mode 100644 index 00000000000000..2fc53540d0283a --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/layout.js @@ -0,0 +1,12 @@ +export default function Root({ children, breadcrumbs }) { + return ( + + + +
{breadcrumbs}
+
Root Layout
+
{children}
+ + + ) +} diff --git a/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/page.js b/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/page.js new file mode 100644 index 00000000000000..9adcd6a6d0a698 --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/fixtures/parallel-routes/app/page.js @@ -0,0 +1,11 @@ +import Link from 'next/link' + +export default function Page() { + return ( +
+ + To Dynamic Page + +
+ ) +} diff --git a/test/e2e/app-dir/app-client-cache/fixtures/regular/app/[id]/client.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/[id]/client.js new file mode 100644 index 00000000000000..6e53f207737d4a --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/[id]/client.js @@ -0,0 +1,5 @@ +'use client' + +export function ClientComponent() { + return
Client Component
+} diff --git a/test/e2e/app-dir/app-client-cache/app/[id]/loading.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/[id]/loading.js similarity index 100% rename from test/e2e/app-dir/app-client-cache/app/[id]/loading.js rename to test/e2e/app-dir/app-client-cache/fixtures/regular/app/[id]/loading.js diff --git a/test/e2e/app-dir/app-client-cache/app/[id]/page.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/[id]/page.js similarity index 86% rename from test/e2e/app-dir/app-client-cache/app/[id]/page.js rename to test/e2e/app-dir/app-client-cache/fixtures/regular/app/[id]/page.js index 34ebdb761501db..fcb988af39754e 100644 --- a/test/e2e/app-dir/app-client-cache/app/[id]/page.js +++ b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/[id]/page.js @@ -1,4 +1,5 @@ import Link from 'next/link' +import { ClientComponent } from './client' export default async function Page({ searchParams: { timeout } }) { const randomNumber = await new Promise((resolve) => { @@ -16,6 +17,7 @@ export default async function Page({ searchParams: { timeout } }) { Back to Home
{randomNumber}
+ ) } diff --git a/test/e2e/app-dir/app-client-cache/app/layout.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/layout.js similarity index 100% rename from test/e2e/app-dir/app-client-cache/app/layout.js rename to test/e2e/app-dir/app-client-cache/fixtures/regular/app/layout.js diff --git a/test/e2e/app-dir/app-client-cache/app/null-loading/loading.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/null-loading/loading.js similarity index 100% rename from test/e2e/app-dir/app-client-cache/app/null-loading/loading.js rename to test/e2e/app-dir/app-client-cache/fixtures/regular/app/null-loading/loading.js diff --git a/test/e2e/app-dir/app-client-cache/app/null-loading/page.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/null-loading/page.js similarity index 100% rename from test/e2e/app-dir/app-client-cache/app/null-loading/page.js rename to test/e2e/app-dir/app-client-cache/fixtures/regular/app/null-loading/page.js diff --git a/test/e2e/app-dir/app-client-cache/app/page.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/page.js similarity index 100% rename from test/e2e/app-dir/app-client-cache/app/page.js rename to test/e2e/app-dir/app-client-cache/fixtures/regular/app/page.js diff --git a/test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/@breadcrumbs/[id]/page.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/@breadcrumbs/[id]/page.js new file mode 100644 index 00000000000000..43ade54c21804c --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/@breadcrumbs/[id]/page.js @@ -0,0 +1,7 @@ +export default function Page({ params }) { + return ( +
+ Catchall
{JSON.stringify(params)}
{' '} +
+ ) +} diff --git a/test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/@breadcrumbs/page.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/@breadcrumbs/page.js new file mode 100644 index 00000000000000..da240ae57a5e6f --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/@breadcrumbs/page.js @@ -0,0 +1,3 @@ +export default function Page({ params }) { + return
Root Breadcrumb
+} diff --git a/test/e2e/app-dir/app-client-cache/app/without-loading/[id]/page.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/[id]/page.js similarity index 100% rename from test/e2e/app-dir/app-client-cache/app/without-loading/[id]/page.js rename to test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/[id]/page.js diff --git a/test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/layout.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/layout.js new file mode 100644 index 00000000000000..0e872eed6b2914 --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/layout.js @@ -0,0 +1,8 @@ +export default function Page({ children, breadcrumbs }) { + return ( + <> +
{breadcrumbs}
+
{children}
+ + ) +} diff --git a/test/e2e/app-dir/app-client-cache/app/without-loading/page.js b/test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/page.js similarity index 100% rename from test/e2e/app-dir/app-client-cache/app/without-loading/page.js rename to test/e2e/app-dir/app-client-cache/fixtures/regular/app/without-loading/page.js diff --git a/test/e2e/app-dir/app-client-cache/next.config.js b/test/e2e/app-dir/app-client-cache/next.config.js new file mode 100644 index 00000000000000..040e946546dbee --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/next.config.js @@ -0,0 +1 @@ +module.exports = { productionBrowserSourceMaps: true }