next.js
dee811ff - test: enable verified caching deploy tests (#98523)

Commit
3 days ago
test: enable verified caching deploy tests (#98523) ## Summary Enable the same 24 previously selected deployment-test scopes across 21 caching test files, now in a stack rooted on canary. Remove 24 `@force-gate !deploy` directives and their associated TODO comments, which are already present on canary. Other mode, bundler, middleware, and Cache Components exclusions remain in place. This preserves the selection with passing evidence from the previous deployment runs. No additional candidate scopes are enabled; excluded variants are not counted as deployment coverage. ## Verification - All selected test registration names and assertion bodies match the previous enabled revision, checked by AST comparison. - Verified that the canary diff contains only the inventoried exclusions and their obsolete skip plumbing; other exclusions are preserved. - Formatting and lint passed; 77 gate infrastructure unit tests passed. - Full local bootstrap was blocked by missing package-level dependencies in the temporary worktree. Fresh deployment execution on these rewritten commits remains to be verified in CI. <details> <summary>Preserved scope inventory (24)</summary> - ID 1: `test/e2e/app-dir/app-client-cache/client-cache.original.test.ts` — `describe('app dir client cache semantics (30s/5min)', () => { const { next, isNextDev } = nextTestSetup({ files: path.join(__dirname, 'fixtures', 'regular'), nextConfig: { experimental: { staleTimes: { dynamic: 30, static: 180 } }, }, }) if (isNextDev) { // dev doesn't support prefetch={true}, so this just performs a basic test to make sure data is reused for 30s it('should renew the 30s cache once the data is revalidated', async () => { let browser = await next.browser('/', browserConfigWithFixedTime) // navigate to prefetch-auto page await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') let initialNumber = await browser.elementById('random-number').text() // Navigate back to the index, and then back to the prefetch-auto page await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/1"]') await browser.eval(fastForwardTo, 5 * 1000) await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') let newNumber = await browser.elementById('random-number').text() // the number should be the same, as we navigated within 30s. expect(newNumber).toBe(initialNumber) // Fast forward to expire the cache await browser.eval(fastForwardTo, 30 * 1000) // Navigate back to the index, and then back to the prefetch-auto page await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/1"]') await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') newNumber = await browser.elementById('random-number').text() // ~35s have passed, so the cache should be expired and the number should be different expect(newNumber).not.toBe(initialNumber) // once the number is updated, we should have a renewed 30s cache for this entry // store this new number so we can check that it stays the same initialNumber = newNumber await browser.eval(fastForwardTo, 5 * 1000) // Navigate back to the index, and then back to the prefetch-auto page await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/1"]') await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') newNumber = await browser.elementById('random-number').text() // the number should be the same, as we navigated within 30s (part 2). expect(newNumber).toBe(initialNumber) }) } else { describe('prefetch={true}', () => { let browser: Playwright beforeEach(async () => { browser = await next.browser('/', browserConfigWithFixedTime) }) it('should prefetch the full page', async () => { const { getRequests, clearRequests } = await createRequestsListener(browser) await retry(() => { expect( getRequests().some( ([url, didPartialPrefetch]) => getPathname(url) === '/0' && !didPartialPrefetch ) ).toBe(true) }) clearRequests() await browser.elementByCss('[href="/0?timeout=0"]').click() await browser.waitForElementByCss('#random-number') await retry(() => { const requests = getRequests() expect(requests.every(([url]) => getPathname(url) !== '/0')).toBe( true ) }) }) it('should re-use the cache for the full page, only for 5 mins', async () => { await browser.elementByCss('[href="/0?timeout=0"]').click() await browser.waitForElementByCss('#random-number') const randomNumber = await browser.elementById('random-number').text() await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/0?timeout=0"]') await browser.elementByCss('[href="/0?timeout=0"]').click() await browser.waitForElementByCss('#random-number') const number = await browser.elementById('random-number').text() expect(number).toBe(randomNumber) await browser.eval(fastForwardTo, 5 * 60 * 1000) await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/0?timeout=0"]') await browser.elementByCss('[href="/0?timeout=0"]').click() await browser.waitForElementByCss('#random-number') const newNumber = await browser.elementById('random-number').text() expect(newNumber).not.toBe(randomNumber) }) it('should prefetch again after 5 mins if the link is visible again', async () => { const { getRequests, clearRequests } = await createRequestsListener(browser) await retry(() => { expect( getRequests().some( ([url, didPartialPrefetch]) => getPathname(url) === '/0' && !didPartialPrefetch ) ).toBe(true) }) await browser.elementByCss('[href="/0?timeout=0"]').click() await browser.waitForElementByCss('#random-number') const randomNumber = await browser.elementById('random-number').text() await browser.eval(fastForwardTo, 5 * 60 * 1000) clearRequests() await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/0?timeout=0"]') await retry(() => { expect( getRequests().some( ([url, didPartialPrefetch]) => getPathname(url) === '/0' && !didPartialPrefetch ) ).toBe(true) }) await browser.elementByCss('[href="/0?timeout=0"]').click() await browser.waitForElementByCss('#random-number') const number = await browser.elementById('random-number').text() expect(number).not.toBe(randomNumber) }) }) describe('prefetch={false}', () => { let browser: Playwright beforeEach(async () => { browser = await next.browser('/', browserConfigWithFixedTime) }) it('should not prefetch the page at all', async () => { const { getRequests } = await createRequestsListener(browser) await browser.elementByCss('[href="/2"]').click() await browser.waitForElementByCss('#random-number') await retry(() => { const requests = getRequests().filter( ([url]) => getPathname(url) === '/2' ) expect(requests.length).toBe(1) }) expect( getRequests().some( ([url, didPartialPrefetch]) => getPathname(url) === '/2' && didPartialPrefetch ) ).toBe(false) }) it('should re-use the cache only for 30 seconds', async () => { await browser.elementByCss('[href="/2"]').click() await browser.waitForElementByCss('#random-number') const randomNumber = await browser.elementById('random-number').text() await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/2"]') await browser.elementByCss('[href="/2"]').click() await browser.waitForElementByCss('#random-number') const number = await browser.elementById('random-number').text() expect(number).toBe(randomNumber) await browser.eval(fastForwardTo, 30 * 1000) await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/2"]') await browser.elementByCss('[href="/2"]').click() await browser.waitForElementByCss('#random-number') const newNumber = await browser.elementById('random-number').text() expect(newNumber).not.toBe(randomNumber) }) }) describe('prefetch={undefined} - default', () => { let browser: Playwright beforeEach(async () => { browser = await next.browser('/', browserConfigWithFixedTime) }) it('should prefetch partially a dynamic page', async () => { const { getRequests, clearRequests } = await createRequestsListener(browser) await retry(() => { expect( getRequests().some( ([url, didPartialPrefetch]) => getPathname(url) === '/1' && didPartialPrefetch ) ).toBe(true) }) clearRequests() await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') await retry(() => { expect( getRequests().some( ([url, didPartialPrefetch]) => getPathname(url) === '/1' && !didPartialPrefetch ) ).toBe(true) }) }) it('should re-use the full cache for only 30 seconds', async () => { await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') const randomNumber = await browser.elementById('random-number').text() await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/1"]') await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') const number = await browser.elementById('random-number').text() expect(number).toBe(randomNumber) await browser.eval(fastForwardTo, 5 * 1000) await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/1"]') await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') const newNumber = await browser.elementById('random-number').text() expect(newNumber).toBe(randomNumber) await browser.eval(fastForwardTo, 30 * 1000) await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/1"]') await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') const newNumber2 = await browser.elementById('random-number').text() expect(newNumber2).not.toBe(newNumber) }) it('should renew the 30s cache once the data is revalidated', async () => { // navigate to prefetch-auto page await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') let initialNumber = await browser.elementById('random-number').text() // Navigate back to the index, and then back to the prefetch-auto page await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/1"]') await browser.eval(fastForwardTo, 5 * 1000) await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') let newNumber = await browser.elementById('random-number').text() // the number should be the same, as we navigated within 30s. expect(newNumber).toBe(initialNumber) // Fast forward to expire the cache await browser.eval(fastForwardTo, 30 * 1000) // Navigate back to the index, and then back to the prefetch-auto page await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/1"]') await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') newNumber = await browser.elementById('random-number').text() // ~35s have passed, so the cache should be expired and the number should be different expect(newNumber).not.toBe(initialNumber) // once the number is updated, we should have a renewed 30s cache for this entry // store this new number so we can check that it stays the same initialNumber = newNumber await browser.eval(fastForwardTo, 5 * 1000) // Navigate back to the index, and then back to the prefetch-auto page await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/1"]') await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') newNumber = await browser.elementById('random-number').text() // the number should be the same, as we navigated within 30s (part 2). expect(newNumber).toBe(initialNumber) }) it('should refetch below the fold after 30 seconds', async () => { await browser.elementByCss('[href="/1?timeout=1000"]').click() await browser.waitForElementByCss('#random-number') const randomNumber = await browser.elementById('random-number').text() await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/1?timeout=1000"]') await browser.eval(fastForwardTo, 30 * 1000) await browser.elementByCss('[href="/1?timeout=1000"]').click() await browser.waitForElementByCss('#random-number') const newNumber = await browser.elementById('random-number').text() expect(newNumber).not.toBe(randomNumber) }) it('should refetch the full page after 5 mins', async () => { // Wait for initial prefetch to complete before clicking await browser.waitForIdleNetwork() const randomLoadingNumber = await browser .elementByCss('[href="/1?timeout=1000"]') .click() .waitForElementByCss('#loading') .text() const randomNumber = await browser .waitForElementByCss('#random-number') .text() await browser.eval(fastForwardTo, 5 * 60 * 1000) await browser .elementByCss('[href="/"]') .click() .waitForElementByCss('[href="/1?timeout=1000"]') // Wait for prefetch requests to complete before clicking, otherwise // clicking during an in-flight prefetch aborts it and skips loading state await browser.waitForIdleNetwork() const newLoadingNumber = await browser .elementByCss('[href="/1?timeout=1000"]') .click() .waitForElementByCss('#loading') .text() const newNumber = await browser .waitForElementByCss('#random-number') .text() expect(newLoadingNumber).not.toBe(randomLoadingNumber) expect(newNumber).not.toBe(randomNumber) }) it('should respect a loading boundary that returns `null`', async () => { await browser.elementByCss('[href="/null-loading"]').click() // the page content should disappear immediately await retry(async () => { expect( await browser.hasElementByCssSelector('[href="/null-loading"]') ).toBe(false) }) // the root layout should still be visible expect(await browser.hasElementByCssSelector('#root-layout')).toBe(true) // the dynamic content should eventually appear await browser.waitForElementByCss('#random-number') expect(await browser.hasElementByCssSelector('#random-number')).toBe( true ) }) }) it('should seed the prefetch cache with the fetched page data', async () => { const browser = await next.browser('/1', browserConfigWithFixedTime) await browser.waitForElementByCss('#random-number') const initialNumber = await browser.elementById('random-number').text() // Move forward a few seconds, navigate off the page and then back to it await browser.eval(fastForwardTo, 5 * 1000) await browser.elementByCss('[href="/"]').click() await browser.waitForElementByCss('[href="/1"]') await browser.waitForIdleNetwork() await browser.elementByCss('[href="/1"]').click() await browser.waitForElementByCss('#random-number') const newNumber = await browser.elementById('random-number').text() // The number should be the same as we've seeded it in the prefetch cache when we loaded the full page expect(newNumber).toBe(initialNumber) }) it('should renew the initial seeded data after expiration time', async () => { const browser = await next.browser( '/without-loading/1', browserConfigWithFixedTime ) await browser.waitForElementByCss('#random-number') const initialNumber = await browser.elementById('random-number').text() // Expire the cache await browser.eval(fastForwardTo, 30 * 1000) await browser.elementByCss('[href="/without-loading"]').click() await browser.waitForElementByCss('[href="/without-loading/1"]') await browser.elementByCss('[href="/without-loading/1"]').click() await browser.waitForElementByCss('#random-number') const newNumber = await browser.elementById('random-number').text() // The number should be different, as the seeded data has expired after 30s expect(newNumber).not.toBe(initialNumber) }) } })` - ID 4: `test/e2e/app-dir/app-custom-cache-handler/index.test.ts` — `describe('app-dir - custom-cache-handler - cjs', () => { const { next, isNextDev } = nextTestSetup({ files: __dirname, env: { CUSTOM_CACHE_HANDLER: 'cache-handler.js', }, }) runTests('cjs module exports', { next, isNextDev }) })` - ID 5: `test/e2e/app-dir/app-custom-cache-handler/index.test.ts` — `describe('app-dir - custom-cache-handler - cjs-default-export', () => { const { next, isNextDev } = nextTestSetup({ files: __dirname, env: { CUSTOM_CACHE_HANDLER: 'cache-handler-cjs-default-export.js', }, }) runTests('cjs default export', { next, isNextDev }) })` - ID 6: `test/e2e/app-dir/app-custom-cache-handler/index.test.ts` — `describe('app-dir - custom-cache-handler - esm', () => { const { next, isNextDev } = nextTestSetup({ files: { app: new FileRef(__dirname + '/app'), 'cache-handler-esm.js': new FileRef(__dirname + '/cache-handler-esm.js'), 'next.config.js': originalNextConfig.replace( 'module.exports = ', 'export default ' ), }, packageJson: { type: 'module', }, env: { CUSTOM_CACHE_HANDLER: 'cache-handler-esm.js', }, }) runTests('esm default export', { next, isNextDev }) })` - ID 7: `test/e2e/app-dir/app-custom-cache-handler/index.test.ts` — `describe('app-dir - custom-cache-handler - esm import.meta.resolve', () => { const { next, isNextDev } = nextTestSetup({ files: { app: new FileRef(__dirname + '/app'), 'cache-handler-esm.js': new FileRef(__dirname + '/cache-handler-esm.js'), 'next.config.js': importMetaResolveNextConfig, }, packageJson: { type: 'module', }, }) runTests('esm default export', { next, isNextDev }) })` - ID 9: `test/e2e/app-dir/app-prefetch/prefetching.stale-times.test.ts` — `describe('app dir - prefetching (custom staleTime)', () => { const { next, isNextDev } = nextTestSetup({ files: { app: new FileRef(join(__dirname, 'app')), }, nextConfig: { experimental: { staleTimes: { static: 30, // Minimum enforced by clientSegmentCache is 30 seconds dynamic: 5, }, }, }, }) if (isNextDev) { it('should skip next dev for now', () => {}) return } it('should not fetch again when a static page was prefetched when navigating to it twice', async () => { let act: ReturnType<typeof createRouterAct> const browser = await next.browser('/', { beforePageLoad(page) { act = createRouterAct(page) }, }) // Reveal the link to trigger prefetch and wait for it to complete const link = await act( async () => { const reveal = await browser.elementByCss('#accordion-to-static-page') await reveal.click() return browser.elementByCss('#to-static-page') }, { includes: 'Static Page [prefetch-sentinel]' } ) // Navigate to static page - should use prefetched data with no additional requests await act(async () => { await link.click() const staticPageText = await browser.elementByCss('#static-page').text() expect(staticPageText).toBe('Static Page [prefetch-sentinel]') }, 'no-requests') // Reveal the "to-home" link and navigate back // Note: Not using act() here because behavior differs between cache models. // With clientSegmentCache, revealing may trigger a prefetch. Without it, home is already // cached so no prefetch occurs. Either way, navigation works with cached data. const reveal = await browser.elementByCss('#accordion-to-home') await reveal.click() const homeLink = await browser.waitForElementByCss('#to-home') await homeLink.click() await browser.waitForElementByCss('#accordion-to-static-page') // Reveal the static page link again since accordion is hidden after navigation await browser.elementByCss('#accordion-to-static-page').click() await browser.waitForElementByCss('#to-static-page') // Navigate to static page again using the accordion - should still use cached data with no additional requests const staticPageText = await act(async () => { await browser.elementByCss('#to-static-page').click() return browser.elementByCss('#static-page').text() }, 'no-requests') expect(staticPageText).toBe('Static Page [prefetch-sentinel]') }) it('should fetch again when a static page was prefetched when navigating to it after the stale time has passed', async () => { let act: ReturnType<typeof createRouterAct> const timeController = createTimeController() const browser = await next.browser('/', { beforePageLoad(page) { act = createRouterAct(page) }, }) // Install time controller await timeController.install(browser) // Reveal the static-page link to trigger prefetch and wait for it to complete let link = await act( async () => { const reveal = await browser.elementByCss('#accordion-to-static-page') await reveal.click() return browser.elementByCss('#to-static-page') }, { includes: 'Static Page [prefetch-sentinel]' } ) // Navigate to static page - should use prefetched data with no additional requests await act(async () => { await link.click() await browser.waitForElementByCss('#static-page') }, 'no-requests') // Reveal the "to-home" link and navigate back const reveal = await browser.elementByCss('#accordion-to-home') await reveal.click() const homeLink = await browser.waitForElementByCss('#to-home') await homeLink.click() await browser.waitForElementByCss('#accordion-to-static-page') // Advance time past the stale time await timeController.advance(browser, 31000) // Reveal the static-page link to trigger prefetch and wait for it to complete link = await act( async () => { const reveal = await browser.elementByCss('#accordion-to-static-page') await reveal.click() return browser.elementByCss('#to-static-page') }, { includes: 'Static Page [prefetch-sentinel]' } ) // Navigate to static page - should use prefetched data with no additional requests await act(async () => { await link.click() await browser.waitForElementByCss('#static-page') }, 'no-requests') }) // FIXME: Flaky test - investigate and re-enable it.skip('should not re-fetch cached data when navigating back to a route group', async () => { let act: ReturnType<typeof createRouterAct> // Just installing so that the page doesn't automatically move past dynamic stale time createTimeController() const browser = await next.browser('/prefetch-auto-route-groups', { beforePageLoad(page) { act = createRouterAct(page) }, }) // Once the page has loaded, we expect a data fetch (initial page load) expect(await browser.elementById('count').text()).toBe('1') // Navigate to a sub-page - this will trigger a data fetch await act(async () => { await browser .elementByCss("[href='/prefetch-auto-route-groups/sub/foo']") .click() }) // Navigate back to the route group page - should use cached data with no additional fetch await act(async () => { await browser.elementByCss("[href='/prefetch-auto-route-groups']").click() // Confirm that the dashboard page is still rendering the stale fetch count, as it should be cached }, 'no-requests') expect(await browser.elementById('count').text()).toBe('1') // Navigate to a new sub-page - this will trigger another data fetch await act(async () => { await browser .elementByCss("[href='/prefetch-auto-route-groups/sub/bar']") .click() }) // Finally, go back to the route group page - should use cached data with no additional fetch await act(async () => { await browser.elementByCss("[href='/prefetch-auto-route-groups']").click() }, 'no-requests') // Confirm that the dashboard page is still rendering the stale fetch count, as it should be cached expect(await browser.elementById('count').text()).toBe('1') // Reload the page to get the accurate total number of fetches await browser.refresh() // The initial fetch, 2 sub-page fetches, and a final fetch when reloading the page expect(await browser.elementById('count').text()).toBe('4') }) it('should fetch again when the initially visited static page is visited after the stale time has passed', async () => { let act: ReturnType<typeof createRouterAct> const timeController = createTimeController() const browser = await next.browser('/static-page-no-prefetch', { beforePageLoad(page) { act = createRouterAct(page) }, }) // Install time controller await timeController.install(browser) // Wait for the page to load (initial navigation request happened during browser load) await browser.waitForElementByCss('#static-page-no-prefetch') // Reveal the home link and wait for prefetch to complete, then navigate const homeLink = await act( async () => { const reveal = await browser.elementByCss('#accordion-to-home') await reveal.click() return browser.elementByCss('#to-home') }, { includes: 'Home Page [prefetch-sentinel]' } ) // Navigate to home - no additional requests since we just prefetched await homeLink.click() await browser.waitForElementByCss('#accordion-to-static-page') // Advance time past the stale time await timeController.advance(browser, 31000) // Reveal the link to static-page-no-prefetch and wait for prefetch const link = await act( async () => { const reveal = await browser.elementByCss( '#accordion-to-static-page-no-prefetch' ) await reveal.click() return browser.elementByCss('#to-static-page-no-prefetch') }, { includes: 'Static Page No Prefetch [prefetch-sentinel]' } ) // Navigate back to static-page-no-prefetch - should use the fresh prefetch data const staticPageText = await act(async () => { await link.click() return browser.elementByCss('#static-page-no-prefetch').text() }, 'no-requests') expect(staticPageText).toBe('Static Page No Prefetch [prefetch-sentinel]') }) it('should renew the stale time after refetching expired RSC data', async () => { let act: ReturnType<typeof createRouterAct> const timeController = createTimeController() const browser = await next.browser('/', { beforePageLoad(page) { act = createRouterAct(page) }, }) // Install time controller await timeController.install(browser) // Reveal the static-page link to trigger prefetch and wait for it to complete let link = await act( async () => { const reveal = await browser.elementByCss('#accordion-to-static-page') await reveal.click() return browser.elementByCss('#to-static-page') }, { includes: 'Static Page [prefetch-sentinel]' } ) // Navigate to static page (should use prefetched data with no additional requests) await act(async () => { await link.click() await browser.waitForElementByCss('#static-page') }, 'no-requests') // Reveal the "to-home" link and navigate back // Note: Not using act() here because behavior differs between cache models. // With clientSegmentCache, revealing may trigger a prefetch. Without it, home is already // cached so no prefetch occurs. Either way, navigation works with cached data. const reveal = await browser.elementByCss('#accordion-to-home') await reveal.click() const homeLink = await browser.waitForElementByCss('#to-home') await homeLink.click() await browser.waitForElementByCss('#accordion-to-static-page') // Advance time past the stale time await timeController.advance(browser, 31000) // Reveal the static-page link to trigger prefetch and wait for it to complete link = await act( async () => { const reveal = await browser.elementByCss('#accordion-to-static-page') await reveal.click() return browser.elementByCss('#to-static-page') }, { includes: 'Static Page [prefetch-sentinel]' } ) // Navigate to static page again (should use freshly prefetched data with no additional requests) await act(async () => { await link.click() await browser.waitForElementByCss('#static-page') }, 'no-requests') // Go back to home (reveal the link and navigate) // Note: Not using act() here because behavior differs between cache models. const reveal2 = await browser.elementByCss('#accordion-to-home') await reveal2.click() const homeLink2 = await browser.waitForElementByCss('#to-home') await homeLink2.click() await browser.waitForElementByCss('#accordion-to-static-page') // Advance time but not past the stale time (20 seconds < 30 second stale time - should still be fresh) await timeController.advance(browser, 20000) // Reveal the static-page link to trigger prefetch (should use cached data, not refetch) link = await act(async () => { const reveal = await browser.elementByCss('#accordion-to-static-page') await reveal.click() return browser.elementByCss('#to-static-page') }, 'no-requests') // Navigate to static page again (should NOT refetch - stale time should be renewed) // If this assertion passes, it means the stale time was properly renewed after the refetch const staticPageText = await act(async () => { await link.click() return browser.elementByCss('#static-page').text() }, 'no-requests') expect(staticPageText).toBe('Static Page [prefetch-sentinel]') }) })` - ID 10: `test/e2e/app-dir/app-root-params-getters/use-cache.test.ts` — `describe('app-root-param-getters - cache dedup with root params', () => { const { next, isNextDev } = nextTestSetup({ files: join(__dirname, 'fixtures', 'use-cache-dedup'), }) it('should dedupe same root params and isolate different root params', async () => { // Three concurrent requests: ca/en, ca/fr, ca/fr. const [$en, $fr1, $fr2] = await Promise.all([ next.render$('/ca/en'), next.render$('/ca/fr'), next.render$('/ca/fr'), ]) const randomEn = $en('#random').text() const randomFr1 = $fr1('#random').text() const randomFr2 = $fr2('#random').text() expect(randomEn).toBeTruthy() expect(randomFr1).toBeTruthy() // ca/en and ca/fr should have different results (isolation). expect(randomEn).not.toBe(randomFr1) // Both ca/fr requests should have the same result (deduped). expect(randomFr1).toBe(randomFr2) }) it('should dedupe same root params and isolate different root params for private caches', async () => { // Three concurrent requests: ca/en, ca/fr, ca/fr. const [$en, $fr1, $fr2] = await Promise.all([ next.render$('/ca/en/use-cache-private'), next.render$('/ca/fr/use-cache-private'), next.render$('/ca/fr/use-cache-private'), ]) const randomEn = $en('#random').text() const randomFr1 = $fr1('#random').text() const randomFr2 = $fr2('#random').text() expect(randomEn).toBeTruthy() expect(randomFr1).toBeTruthy() // Different root params produce different entries, in dev and production. expect(randomEn).not.toBe(randomFr1) if (isNextDev) { // In dev, private caches are persisted and participate in cross-request // deduplication keyed by root params, so the two ca/fr requests join one // in-flight invocation and share a single fill. expect(randomFr1).toBe(randomFr2) } else { // In production, private caches are not persisted and are never deduped // across requests, so each ca/fr request generates its own value. expect(randomFr1).not.toBe(randomFr2) } }) })` - ID 21: `test/e2e/app-dir/cache-components-errors/module-scope.test.ts` — `describe('Lazy Module Init', () => { const { next, isNextDev } = nextTestSetup({ files: __dirname + '/fixtures/lazy-module-init', skipStart: true, }) if (isNextDev) { it('does not run in dev', () => {}) return } it('should build statically even if module scope uses sync APIs like current time and random', async () => { try { await next.start() } catch { throw new Error('expected build not to fail for fully static project') } expect(next.cliOutput).toContain('○ /server') expect(next.cliOutput).toContain('○ /client') expect(next.cliOutput).toContain('○ /client-page') expect(next.cliOutput).toContain('◐ /[dyn]') let $ $ = await next.render$('/server') expect($('#id').text().length).toBeGreaterThan(0) $ = await next.render$('/client') expect($('#id').text().length).toBeGreaterThan(0) $ = await next.render$('/client-page') expect($('#id').text().length).toBeGreaterThan(0) $ = await next.render$('/foo') expect($('#id').text().length).toBeGreaterThan(0) $ = await next.render$('/serial-client-sync-io') expect($('#id').text().length).toBeGreaterThan(0) }) })` - ID 27: `test/e2e/app-dir/cache-components/cache-components.connection.test.ts` — `describe('cache-components', () => { const { next, isNextDev } = nextTestSetup({ files: __dirname, }) it('should partially prerender pages that use connection', async () => { let $ = await next.render$('/connection/static-behavior/boundary', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#foo').text()).toBe('foo') } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#foo').text()).toBe('foo') } }) it('should be able to pass connection as a promise to another component and trigger an intermediate Suspense boundary', async () => { const $ = await next.render$('/connection/static-behavior/pass-deeply') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') // In dev, whether or not the fallback appears in the HTML is unreliable // and depends on timing, so we don't assert on its presence // (if we want to assert on it, we should use a browser test) expect($('#page').text()).toBe('at runtime') } else { expect($('#layout').text()).toBe('at buildtime') expect($('#fallback').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') } }) })` - ID 28: `test/e2e/app-dir/cache-components/cache-components.cookies.test.ts` — `describe('cache-components', () => { const { next, isNextDev } = nextTestSetup({ files: __dirname, }) it('should partially prerender pages that use cookies', async () => { let $ = await next.render$('/cookies/static-behavior', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#x-sentinel').text()).toBe('hello') } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#x-sentinel').text()).toBe('hello') } }) it('should be able to pass cookies as a promise to another component and trigger an intermediate Suspense boundary', async () => { const $ = await next.render$('/cookies/static-behavior/pass-deeply') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#fallback').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') } else { expect($('#layout').text()).toBe('at buildtime') expect($('#fallback').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') } }) it('should be able to access cookie properties', async () => { let $ = await next.render$('/cookies/exercise', {}) let cookieWarnings = next.cliOutput .split('\n') .filter((l) => l.includes('Route "/cookies/exercise')) expect(cookieWarnings).toHaveLength(0) // For...of iteration expect($('#for-of-x-sentinel').text()).toContain('hello') expect($('#for-of-x-sentinel-path').text()).toContain('/cookies/exercise') expect($('#for-of-x-sentinel-rand').text()).toContain('x-sentinel-rand') // ...spread iteration expect($('#spread-x-sentinel').text()).toContain('hello') expect($('#spread-x-sentinel-path').text()).toContain('/cookies/exercise') expect($('#spread-x-sentinel-rand').text()).toContain('x-sentinel-rand') // cookies().size expect(parseInt($('#size-cookies').text())).toBeGreaterThanOrEqual(3) // cookies().get('...') && cookies().getAll('...') expect($('#get-x-sentinel').text()).toContain('hello') expect($('#get-x-sentinel-path').text()).toContain('/cookies/exercise') expect($('#get-x-sentinel-rand').text()).toContain('x-sentinel-rand') // cookies().has('...') expect($('#has-x-sentinel').text()).toContain('true') expect($('#has-x-sentinel-foobar').text()).toContain('false') // cookies().set('...', '...') expect($('#set-result-x-sentinel').text()).toContain( 'Cookies can only be modified in a Server Action' ) expect($('#set-value-x-sentinel').text()).toContain('hello') // cookies().delete('...', '...') expect($('#delete-result-x-sentinel').text()).toContain( 'Cookies can only be modified in a Server Action' ) expect($('#delete-value-x-sentinel').text()).toContain('hello') // cookies().clear() expect($('#clear-result').text()).toContain( 'Cookies can only be modified in a Server Action' ) expect($('#clear-value-x-sentinel').text()).toContain('hello') // cookies().toString() expect($('#toString').text()).toContain('x-sentinel=hello') expect($('#toString').text()).toContain('x-sentinel-path') expect($('#toString').text()).toContain('x-sentinel-rand=') }) })` - ID 29: `test/e2e/app-dir/cache-components/cache-components.date.test.ts` — `describe('cache-components', () => { const { next, isNextDev } = nextTestSetup({ files: __dirname, }) it('should not have route specific errors', async () => { expect(next.cliOutput).not.toMatch('Error: Route "/') expect(next.cliOutput).not.toMatch('Error occurred prerendering page') }) it('should prerender pages with cached `Date.now()` calls', async () => { let $ = await next.render$('/date/now/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#value').text()).toMatch(/^\d+$/) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#value').text()).toMatch(/^\d+$/) } }) it('should prerender pages with cached `Date()` calls', async () => { let $ = await next.render$('/date/date/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#value').text()).toContain('GMT') } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#value').text()).toContain('GMT') } }) it('should prerender pages with cached `new Date()` calls', async () => { let $ = await next.render$('/date/new-date/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#value').text()).toContain('GMT') } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#value').text()).toContain('GMT') } }) it('should prerender pages with cached static Date instances like `new Date(0)`', async () => { let $ = await next.render$('/date/static-date/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#value').text()).toContain('GMT') } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#value').text()).toContain('GMT') } }) it('should not prerender pages with uncached static Date instances like `new Date(0)`', async () => { let $ = await next.render$('/date/static-date/uncached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#value').text()).toContain('GMT') } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#value').text()).toContain('GMT') } }) })` - ID 30: `test/e2e/app-dir/cache-components/cache-components.draft-mode.test.ts` — `describe('cache-components', () => { const { next, isNextDev } = nextTestSetup({ files: __dirname, }) let cliIndex = 0 beforeEach(() => { cliIndex = next.cliOutput.length }) function getLines(containing: string): Array<string> { const warnings = next.cliOutput .slice(cliIndex) .split('\n') .filter((l) => l.includes(containing)) cliIndex = next.cliOutput.length return warnings } it('should fully prerender pages that use draftMode', async () => { expect(getLines('Route "/draftmode')).toEqual([]) let $ = await next.render$('/draftmode', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#draft-mode').text()).toBe('false') expect(getLines('Route "/draftmode')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#draft-mode').text()).toBe('false') expect(getLines('Route "/draftmode')).toEqual([]) } }) if (!isNextDev) { it('should stream Suspense fallbacks when draft mode is enabled', async () => { const draftRes = await next.fetch('/draftmode/toggle') const setCookie = draftRes.headers.get('set-cookie') const cookieHeader = { Cookie: setCookie?.split(';', 1)[0] } expect(cookieHeader.Cookie).toBeTruthy() const $ = await next.render$('/draftmode/streaming', undefined, { headers: cookieHeader, }) expect($('#draft-mode').text()).toBe('true') expect($('#delayed-runtime-fallback').text()).toBe( 'Loading draft content...' ) }) } })` - ID 32: `test/e2e/app-dir/cache-components/cache-components.node-crypto.test.ts` — `describe('cache-components', () => { const { next, isNextDev } = nextTestSetup({ files: __dirname, }) it('should not have route specific errors', async () => { expect(next.cliOutput).not.toMatch('Error: Route "/') expect(next.cliOutput).not.toMatch('Error occurred prerendering page') }) it("should prerender pages with cached `require('node:crypto').getRandomValues(...)` calls", async () => { let $ = await next.render$('/node-crypto/get-random-values/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#first').text()).not.toEqual($('#second').text()) } }) it("should prerender pages with cached `require('node:crypto').randomUUID()` calls", async () => { let $ = await next.render$('/node-crypto/random-uuid/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#first').text()).not.toEqual($('#second').text()) } }) it("should prerender pages with cached `require('node:crypto').randomBytes(size)` calls", async () => { let $ = await next.render$('/node-crypto/random-bytes/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#first').text()).not.toEqual($('#second').text()) } }) it("should prerender pages with cached `require('node:crypto').randomFillSync(buffer)` calls", async () => { let $ = await next.render$('/node-crypto/random-fill-sync/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#first').text()).not.toEqual($('#second').text()) } }) it("should prerender pages with cached `require('node:crypto').randomInt(max)` calls", async () => { let $ = await next.render$('/node-crypto/random-int/up-to/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#first').text()).not.toEqual($('#second').text()) } }) it("should prerender pages with cached `require('node:crypto').randomInt(min, max)` calls", async () => { let $ = await next.render$('/node-crypto/random-int/between/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#first').text()).not.toEqual($('#second').text()) } }) it("should prerender pages with cached `require('node:crypto').generatePrimeSync(size, options)` calls", async () => { let $ = await next.render$('/node-crypto/generate-prime-sync/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#first').text()).not.toEqual($('#second').text()) } }) it("should prerender pages with cached `require('node:crypto').generateKeyPairSync(type, options)` calls", async () => { let $ = await next.render$('/node-crypto/generate-key-pair-sync/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#first').text()).not.toEqual($('#second').text()) } }) it("should prerender pages with cached `require('node:crypto').generateKeySync(type, options)` calls", async () => { let $ = await next.render$('/node-crypto/generate-key-sync/cached', {}) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#first').text()).not.toEqual($('#second').text()) } }) })` - ID 33: `test/e2e/app-dir/cache-components/cache-components.params.test.ts` — `describe('cache-components', () => { const { next, isNextDev } = nextTestSetup({ files: __dirname, }) let cliIndex = 0 beforeEach(() => { cliIndex = next.cliOutput.length }) function getLines(containing: string): Array<string> { const warnings = next.cliOutput .slice(cliIndex) .split('\n') .filter((l) => l.includes(containing)) cliIndex = next.cliOutput.length return warnings } describe('Params', () => { it('should partially prerender pages that await params in a server components', async () => { expect(getLines('Route "/params')).toEqual([]) let $ = await next.render$( '/params/semantics/one/build/layout-access/server' ) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('build') } $ = await next.render$('/params/semantics/one/run/layout-access/server') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#highcard-fallback').text()).toBe('loading highcard children') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/build/page-access/server') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/run/page-access/server') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#highcard-fallback').text()).toBe('loading highcard children') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) } }) // Since #85155, we intentionally omit search params from client segments // if the page is otherwise static, and resume using a client fetch // instead. So it's expected that the value is missing pre-hydration. // There are separate tests that verify that it is eventually hydrated. // TODO: Rewrite or update this test. it.skip('should partially prerender pages that use params in a client components', async () => { expect(getLines('Route "/params')).toEqual([]) let $ = await next.render$( '/params/semantics/one/build/layout-access/client' ) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/run/layout-access/client') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#highcard-fallback').text()).toBe('loading highcard children') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/build/page-access/client') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/run/page-access/client') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#highcard-fallback').text()).toBe('loading highcard children') expect($('#page').text()).toBe('at runtime') expect($('#param-lowcard').text()).toBe('one') expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) } }) it('should fully prerender pages that check individual param keys after awaiting params in a server component', async () => { expect(getLines('Route "/params')).toEqual([]) let $ = await next.render$( '/params/semantics/one/build/layout-has/server' ) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-has-lowcard').text()).toBe('true') expect($('#param-has-highcard').text()).toBe('true') expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#param-has-lowcard').text()).toBe('true') expect($('#param-has-highcard').text()).toBe('true') expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/build/page-has/server') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-has-lowcard').text()).toBe('true') expect($('#param-has-highcard').text()).toBe('true') expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#param-has-lowcard').text()).toBe('true') expect($('#param-has-highcard').text()).toBe('true') expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/run/layout-has/server') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-has-lowcard').text()).toBe('true') expect($('#param-has-highcard').text()).toBe('true') expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { // With PPR fallbacks the first visit is still partially prerendered expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#highcard-fallback').text()).toBe('loading highcard children') expect($('#page').text()).toBe('at runtime') expect($('#param-has-lowcard').text()).toBe('true') expect($('#param-has-highcard').text()).toBe('true') expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/run/page-has/server') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-has-lowcard').text()).toBe('true') expect($('#param-has-highcard').text()).toBe('true') expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { // With PPR fallbacks the first visit is still partially prerendered expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#highcard-fallback').text()).toBe('loading highcard children') expect($('#page').text()).toBe('at runtime') expect($('#param-has-lowcard').text()).toBe('true') expect($('#param-has-highcard').text()).toBe('true') expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } }) // Since #85155, we intentionally omit search params from client segments // if the page is otherwise static, and resume using a client fetch // instead. So it's expected that the value is missing pre-hydration. // There are separate tests that verify that it is eventually hydrated. // TODO: Rewrite or update this test. it.skip('should fully prerender pages that check individual param keys after `use`ing params in a client component', async () => { expect(getLines('Route "/params')).toEqual([]) let $ = await next.render$( '/params/semantics/one/build/layout-has/client' ) if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#param-has-lowcard').text()).toBe('true') expect($('#param-has-highcard').text()).toBe('true') expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { expect($('#layout').text()).toBe('at buildtime') expect($('#lowcard').text()).toBe('at buildtime') expect($('#highcard').text()).toBe('at buildtime') expect($('#page').text()).toBe('at buildtime') expect($('#param-has-lowcard').text()).toBe('true') expect($('#param-has-highcard').text()).toBe('true') expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/build/page-has/client') if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#lowcard').text()).toBe('at runtime') expect($('#highcard').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect(…
Author
Parents
Loading