diff --git a/packages/next/src/lib/build-custom-route.ts b/packages/next/src/lib/build-custom-route.ts index 78d5d41212a3a7..0c7e558d5d5504 100644 --- a/packages/next/src/lib/build-custom-route.ts +++ b/packages/next/src/lib/build-custom-route.ts @@ -37,14 +37,6 @@ export function buildCustomRoute( delimiter: '/', // default is `/#?`, but Next does not pass query info }) - let source = compiled.source - if (!route.internal) { - source = modifyRouteRegex( - source, - type === 'redirect' ? restrictedRedirectPaths : undefined - ) - } - // If this is an internal rewrite and it already provides a regex, use it // otherwise, normalize the source to a regex. let regex: string @@ -54,6 +46,14 @@ export function buildCustomRoute( !('regex' in route) || typeof route.regex !== 'string' ) { + let source = compiled.source + if (!route.internal) { + source = modifyRouteRegex( + source, + type === 'redirect' ? restrictedRedirectPaths : undefined + ) + } + regex = normalizeRouteRegex(source) } else { regex = route.regex diff --git a/packages/next/src/lib/generate-interception-routes-rewrites.ts b/packages/next/src/lib/generate-interception-routes-rewrites.ts index c196b64dbf2fec..90de10fab1abc1 100644 --- a/packages/next/src/lib/generate-interception-routes-rewrites.ts +++ b/packages/next/src/lib/generate-interception-routes-rewrites.ts @@ -52,7 +52,6 @@ export function generateInterceptionRoutesRewrites( value: headerRegex, }, ], - internal: true, regex: source.namedRegex, }) } diff --git a/test/e2e/app-dir/parallel-routes-and-interception/next.config.js b/test/e2e/app-dir/parallel-routes-and-interception/next.config.js deleted file mode 100644 index 87591b02febda3..00000000000000 --- a/test/e2e/app-dir/parallel-routes-and-interception/next.config.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @type {import('next').NextConfig} - */ -const nextConfig = { - async rewrites() { - return { - beforeFiles: [ - { - source: '/foo', - destination: '/en/foo', - }, - { - source: '/photos', - destination: '/en/photos', - }, - ], - } - }, -} - -module.exports = nextConfig diff --git a/test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts b/test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts index 6698791a4f273a..4e5c31590c3756 100644 --- a/test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts +++ b/test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts @@ -1,926 +1,1060 @@ import { nextTestSetup, FileRef } from 'e2e-utils' +import { NextConfig } from 'next' import { check, retry } from 'next-test-utils' import path from 'path' -describe('parallel-routes-and-interception', () => { - const { next, isNextDev, isNextStart } = nextTestSetup({ - files: __dirname, - }) +const nextConfig: NextConfig = { + async rewrites() { + return { + beforeFiles: [ + { + source: '/foo', + destination: '/en/foo', + }, + { + source: '/photos', + destination: '/en/photos', + }, + ], + } + }, +} + +describe.each([true, false])( + 'parallel-routes-and-interception (trailingSlash: %s)', + (trailingSlash) => { + const { next, isNextDev, isNextStart } = nextTestSetup({ + files: __dirname, + nextConfig: { + trailingSlash, + ...nextConfig, + }, + }) + + describe('parallel routes', () => { + it('should support parallel route tab bars', async () => { + const browser = await next.browser('/parallel-tab-bar') + + const hasHome = async () => { + await check( + () => browser.waitForElementByCss('#home').text(), + 'Tab bar page (@children)' + ) + } + const hasViewsHome = async () => { + await check( + () => browser.waitForElementByCss('#views-home').text(), + 'Views home' + ) + } + const hasViewDuration = async () => { + await check( + () => browser.waitForElementByCss('#view-duration').text(), + 'View duration' + ) + } + const hasImpressions = async () => { + await check( + () => browser.waitForElementByCss('#impressions').text(), + 'Impressions' + ) + } + const hasAudienceHome = async () => { + await check( + () => browser.waitForElementByCss('#audience-home').text(), + 'Audience home' + ) + } + const hasDemographics = async () => { + await check( + () => browser.waitForElementByCss('#demographics').text(), + 'Demographics' + ) + } + const hasSubscribers = async () => { + await check( + () => browser.waitForElementByCss('#subscribers').text(), + 'Subscribers' + ) + } + const checkUrlPath = async (path: string) => { + await check( + () => browser.url(), + `${next.url}/parallel-tab-bar${path}${trailingSlash ? '/' : ''}` + ) + } + + // Initial page + const step1 = async () => { + await hasHome() + await hasViewsHome() + await hasAudienceHome() + await checkUrlPath('') + } + + await step1() + + console.log('step1') + // Navigate to /views/duration + await browser.elementByCss('#view-duration-link').click() + + const step2 = async () => { + await hasHome() + await hasViewDuration() + await hasAudienceHome() + await checkUrlPath('/view-duration') + } + + await step2() + console.log('step2') - describe('parallel routes', () => { - it('should support parallel route tab bars', async () => { - const browser = await next.browser('/parallel-tab-bar') + // Navigate to /views/impressions + await browser.elementByCss('#impressions-link').click() + + const step3 = async () => { + await hasHome() + await hasImpressions() + await hasAudienceHome() + await checkUrlPath('/impressions') + } + + await step3() + console.log('step3') + + // Navigate to /audience/demographics + await browser.elementByCss('#demographics-link').click() + + const step4 = async () => { + await hasHome() + await hasImpressions() + await hasDemographics() + await checkUrlPath('/demographics') + } + + await step4() + console.log('step4') + + // Navigate to /audience/subscribers + await browser.elementByCss('#subscribers-link').click() + + const step5 = async () => { + await hasHome() + await hasImpressions() + await hasSubscribers() + await checkUrlPath('/subscribers') + } + + await step5() + console.log('step5') + + // Navigate to / + await browser.elementByCss('#home-link-audience').click() + + await checkUrlPath('') + + // TODO: home link behavior + // await step1() + + // TODO: fix back/forward navigation test + // Test that back navigation works as intended + await browser.back() + await step5() + console.log('step5 back') + await browser.back() + await step4() + console.log('step4 back') + await browser.back() + await step3() + console.log('step3 back') + + await browser.back() + await step2() + console.log('step2 back') + await browser.back() + await step1() + console.log('step1 back') + console.log('step6') + + // Test that forward navigation works as intended + await browser.forward() + await step2() + console.log('step2 forward') + await browser.forward() + await step3() + console.log('step3 forward') + await browser.forward() + await step4() + console.log('step4 forward') + await browser.forward() + await step5() + }) + + it('should match parallel routes', async () => { + const $ = await next.render$('/parallel/nested') + const pageText = $('#parallel-layout').text() + expect(pageText).toContain('parallel/layout') + expect(pageText).toContain('parallel/@foo/nested/layout') + expect(pageText).toContain('parallel/@foo/nested/@a/page') + expect(pageText).toContain('parallel/@foo/nested/@b/page') + expect(pageText).toContain('parallel/@bar/nested/layout') + expect(pageText).toContain('parallel/@bar/nested/@a/page') + expect(pageText).toContain('parallel/@bar/nested/@b/page') + expect(pageText).toContain('parallel/nested/page') + }) + + it('should match parallel routes in route groups', async () => { + const $ = await next.render$('/parallel/nested-2') + const pageText = $('#parallel-layout').text() + expect(pageText).toContain('parallel/layout') + expect(pageText).toContain('parallel/(new)/layout') + expect(pageText).toContain('parallel/(new)/@baz/nested/page') + }) - const hasHome = async () => { + it('should throw a 404 when no matching parallel route is found', async () => { + const browser = await next.browser('/parallel-tab-bar') + // we make sure the page is available through navigating await check( () => browser.waitForElementByCss('#home').text(), 'Tab bar page (@children)' ) - } - const hasViewsHome = async () => { - await check( - () => browser.waitForElementByCss('#views-home').text(), - 'Views home' - ) - } - const hasViewDuration = async () => { + await browser.elementByCss('#view-duration-link').click() await check( () => browser.waitForElementByCss('#view-duration').text(), 'View duration' ) - } - const hasImpressions = async () => { - await check( - () => browser.waitForElementByCss('#impressions').text(), - 'Impressions' + + // fetch /parallel-tab-bar/view-duration + const res = await next.fetch( + `${next.url}/parallel-tab-bar/view-duration` ) - } - const hasAudienceHome = async () => { + const html = await res.text() + expect(html).toContain('page could not be found') + }) + + it('should render nested parallel routes', async () => { + const browser = await next.browser('/parallel-side-bar/nested/deeper') await check( - () => browser.waitForElementByCss('#audience-home').text(), - 'Audience home' + () => browser.waitForElementByCss('#nested-deeper-main').text(), + 'Nested deeper page' ) - } - const hasDemographics = async () => { + await check( - () => browser.waitForElementByCss('#demographics').text(), - 'Demographics' + () => browser.waitForElementByCss('#nested-deeper-sidebar').text(), + 'Nested deeper sidebar here' ) - } - const hasSubscribers = async () => { + + await browser + .elementByCss( + `[href="/parallel-side-bar/nested${trailingSlash ? '/' : ''}"]` + ) + .click() + await check( - () => browser.waitForElementByCss('#subscribers').text(), - 'Subscribers' + () => browser.waitForElementByCss('#nested-main').text(), + 'Nested page' ) - } - const checkUrlPath = async (path: string) => { - await check(() => browser.url(), `${next.url}/parallel-tab-bar${path}`) - } - // Initial page - const step1 = async () => { - await hasHome() - await hasViewsHome() - await hasAudienceHome() - await checkUrlPath('') - } + await check( + () => browser.waitForElementByCss('#nested-sidebar').text(), + 'Nested sidebar here' + ) - await step1() + await browser + .elementByCss( + `[href="/parallel-side-bar${trailingSlash ? '/' : ''}"]` + ) + .click() - console.log('step1') - // Navigate to /views/duration - await browser.elementByCss('#view-duration-link').click() + await check( + () => browser.waitForElementByCss('#main').text(), + 'homepage' + ) - const step2 = async () => { - await hasHome() - await hasViewDuration() - await hasAudienceHome() - await checkUrlPath('/view-duration') - } + await check( + () => browser.waitForElementByCss('#sidebar-main').text(), + 'root sidebar here' + ) + }) - await step2() - console.log('step2') + it('should support layout files in parallel routes', async () => { + const browser = await next.browser('/parallel-layout') + await check( + () => browser.waitForElementByCss('#parallel-layout').text(), + 'parallel layout' + ) - // Navigate to /views/impressions - await browser.elementByCss('#impressions-link').click() + // navigate to /parallel-layout/subroute + await browser + .elementByCss( + `[href="/parallel-layout/subroute${trailingSlash ? '/' : ''}"]` + ) + .click() + await check( + () => browser.waitForElementByCss('#parallel-layout').text(), + 'parallel layout' + ) + await check( + () => browser.waitForElementByCss('#parallel-subroute').text(), + 'parallel subroute layout' + ) + }) - const step3 = async () => { - await hasHome() - await hasImpressions() - await hasAudienceHome() - await checkUrlPath('/impressions') - } + it('should only scroll to the parallel route that was navigated to', async () => { + const browser = await next.browser('/parallel-scroll') - await step3() - console.log('step3') + await browser.eval('window.scrollTo(0, 1000)') + const position = await browser.eval('window.scrollY') + console.log('position', position) + await browser + .elementByCss( + `[href="/parallel-scroll/nav${trailingSlash ? '/' : ''}"]` + ) + .click() + await browser.waitForElementByCss('#modal') + // check that we didn't scroll back to the top + await check(() => browser.eval('window.scrollY'), position) + }) - // Navigate to /audience/demographics - await browser.elementByCss('#demographics-link').click() + it('should apply the catch-all route to the parallel route if no matching route is found', async () => { + const browser = await next.browser('/parallel-catchall') - const step4 = async () => { - await hasHome() - await hasImpressions() - await hasDemographics() - await checkUrlPath('/demographics') - } + await browser + .elementByCss( + `[href="/parallel-catchall/bar${trailingSlash ? '/' : ''}"]` + ) + .click() + await check( + () => browser.waitForElementByCss('#main').text(), + 'bar slot' + ) + await check( + () => browser.waitForElementByCss('#slot-content').text(), + 'slot catchall' + ) - await step4() - console.log('step4') + await browser + .elementByCss( + `[href="/parallel-catchall/foo${trailingSlash ? '/' : ''}"]` + ) + .click() + await check(() => browser.waitForElementByCss('#main').text(), 'foo') + await check( + () => browser.waitForElementByCss('#slot-content').text(), + 'foo slot' + ) - // Navigate to /audience/subscribers - await browser.elementByCss('#subscribers-link').click() + await browser + .elementByCss( + `[href="/parallel-catchall/baz${trailingSlash ? '/' : ''}"]` + ) + .click() + await check( + () => browser.waitForElementByCss('#main').text(), + /main catchall/ + ) + await check( + () => browser.waitForElementByCss('#main').text(), + /catchall page client component/ + ) + await check( + () => browser.waitForElementByCss('#slot-content').text(), + 'baz slot' + ) + }) - const step5 = async () => { - await hasHome() - await hasImpressions() - await hasSubscribers() - await checkUrlPath('/subscribers') - } + it('should match the catch-all routes of the more specific path, if there is more than one catch-all route', async () => { + const browser = await next.browser('/parallel-nested-catchall') - await step5() - console.log('step5') - - // Navigate to / - await browser.elementByCss('#home-link-audience').click() - - await checkUrlPath('') - - // TODO: home link behavior - // await step1() - - // TODO: fix back/forward navigation test - // Test that back navigation works as intended - await browser.back() - await step5() - console.log('step5 back') - await browser.back() - await step4() - console.log('step4 back') - await browser.back() - await step3() - console.log('step3 back') - - await browser.back() - await step2() - console.log('step2 back') - await browser.back() - await step1() - console.log('step1 back') - console.log('step6') - - // Test that forward navigation works as intended - await browser.forward() - await step2() - console.log('step2 forward') - await browser.forward() - await step3() - console.log('step3 forward') - await browser.forward() - await step4() - console.log('step4 forward') - await browser.forward() - await step5() - }) + await browser + .elementByCss( + `[href="/parallel-nested-catchall/foo${trailingSlash ? '/' : ''}"]` + ) + .click() + await check(() => browser.waitForElementByCss('#main').text(), 'foo') + await check( + () => browser.waitForElementByCss('#slot-content').text(), + 'foo slot' + ) - it('should match parallel routes', async () => { - const $ = await next.render$('/parallel/nested') - const pageText = $('#parallel-layout').text() - expect(pageText).toContain('parallel/layout') - expect(pageText).toContain('parallel/@foo/nested/layout') - expect(pageText).toContain('parallel/@foo/nested/@a/page') - expect(pageText).toContain('parallel/@foo/nested/@b/page') - expect(pageText).toContain('parallel/@bar/nested/layout') - expect(pageText).toContain('parallel/@bar/nested/@a/page') - expect(pageText).toContain('parallel/@bar/nested/@b/page') - expect(pageText).toContain('parallel/nested/page') - }) + await browser + .elementByCss( + `[href="/parallel-nested-catchall/bar${trailingSlash ? '/' : ''}"]` + ) + .click() + await check(() => browser.waitForElementByCss('#main').text(), 'bar') + await check( + () => browser.waitForElementByCss('#slot-content').text(), + 'slot catchall' + ) - it('should match parallel routes in route groups', async () => { - const $ = await next.render$('/parallel/nested-2') - const pageText = $('#parallel-layout').text() - expect(pageText).toContain('parallel/layout') - expect(pageText).toContain('parallel/(new)/layout') - expect(pageText).toContain('parallel/(new)/@baz/nested/page') - }) + await browser + .elementByCss( + `[href="/parallel-nested-catchall/foo/123${trailingSlash ? '/' : ''}"]` + ) + .click() + await check(() => browser.waitForElementByCss('#main').text(), 'foo id') + await check( + () => browser.waitForElementByCss('#slot-content').text(), + 'foo id catchAll' + ) + }) - it('should throw a 404 when no matching parallel route is found', async () => { - const browser = await next.browser('/parallel-tab-bar') - // we make sure the page is available through navigating - await check( - () => browser.waitForElementByCss('#home').text(), - 'Tab bar page (@children)' - ) - await browser.elementByCss('#view-duration-link').click() - await check( - () => browser.waitForElementByCss('#view-duration').text(), - 'View duration' - ) - - // fetch /parallel-tab-bar/view-duration - const res = await next.fetch(`${next.url}/parallel-tab-bar/view-duration`) - const html = await res.text() - expect(html).toContain('page could not be found') - }) + it('should navigate with a link with prefetch=false', async () => { + const browser = await next.browser('/parallel-prefetch-false') - it('should render nested parallel routes', async () => { - const browser = await next.browser('/parallel-side-bar/nested/deeper') - await check( - () => browser.waitForElementByCss('#nested-deeper-main').text(), - 'Nested deeper page' - ) + // check if the default view loads + await check( + () => browser.waitForElementByCss('#default-parallel').text(), + 'default view for parallel' + ) - await check( - () => browser.waitForElementByCss('#nested-deeper-sidebar').text(), - 'Nested deeper sidebar here' - ) + // check that navigating to /foo re-renders the layout to display @parallel/foo + await check( + () => + browser + .elementByCss( + `[href="/parallel-prefetch-false/foo${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#parallel-foo') + .text(), + 'parallel for foo' + ) + }) - await browser.elementByCss('[href="/parallel-side-bar/nested"]').click() + it('should display all parallel route params with useParams', async () => { + const browser = await next.browser('/parallel-dynamic/foo/bar') - await check( - () => browser.waitForElementByCss('#nested-main').text(), - 'Nested page' - ) + await check( + () => browser.waitForElementByCss('#foo').text(), + `{"slug":"foo","id":"bar"}` + ) - await check( - () => browser.waitForElementByCss('#nested-sidebar').text(), - 'Nested sidebar here' - ) + await check( + () => browser.waitForElementByCss('#bar').text(), + `{"slug":"foo","id":"bar"}` + ) + }) - await browser.elementByCss('[href="/parallel-side-bar"]').click() + it('should load CSS for a default page that exports another page', async () => { + const browser = await next.browser('/default-css') - await check(() => browser.waitForElementByCss('#main').text(), 'homepage') + expect( + await browser.eval( + `window.getComputedStyle(document.getElementById("red-text")).color` + ) + ).toBe('rgb(255, 0, 0)') - await check( - () => browser.waitForElementByCss('#sidebar-main').text(), - 'root sidebar here' - ) - }) + // the more page will now be using the page's `default.tsx` file, which re-exports the root page. + await browser + .elementByCss(`[href="/default-css/more${trailingSlash ? '/' : ''}"]`) + .click() - it('should support layout files in parallel routes', async () => { - const browser = await next.browser('/parallel-layout') - await check( - () => browser.waitForElementByCss('#parallel-layout').text(), - 'parallel layout' - ) - - // navigate to /parallel-layout/subroute - await browser.elementByCss('[href="/parallel-layout/subroute"]').click() - await check( - () => browser.waitForElementByCss('#parallel-layout').text(), - 'parallel layout' - ) - await check( - () => browser.waitForElementByCss('#parallel-subroute').text(), - 'parallel subroute layout' - ) - }) + expect( + await browser.eval( + `window.getComputedStyle(document.getElementById("red-text")).color` + ) + ).toBe('rgb(255, 0, 0)') - it('should only scroll to the parallel route that was navigated to', async () => { - const browser = await next.browser('/parallel-scroll') + // ensure that everything still works on a fresh load + await browser.refresh() - await browser.eval('window.scrollTo(0, 1000)') - const position = await browser.eval('window.scrollY') - console.log('position', position) - await browser.elementByCss('[href="/parallel-scroll/nav"]').click() - await browser.waitForElementByCss('#modal') - // check that we didn't scroll back to the top - await check(() => browser.eval('window.scrollY'), position) - }) + expect( + await browser.eval( + `window.getComputedStyle(document.getElementById("red-text")).color` + ) + ).toBe('rgb(255, 0, 0)') + }) - it('should apply the catch-all route to the parallel route if no matching route is found', async () => { - const browser = await next.browser('/parallel-catchall') - - await browser.elementByCss('[href="/parallel-catchall/bar"]').click() - await check(() => browser.waitForElementByCss('#main').text(), 'bar slot') - await check( - () => browser.waitForElementByCss('#slot-content').text(), - 'slot catchall' - ) - - await browser.elementByCss('[href="/parallel-catchall/foo"]').click() - await check(() => browser.waitForElementByCss('#main').text(), 'foo') - await check( - () => browser.waitForElementByCss('#slot-content').text(), - 'foo slot' - ) - - await browser.elementByCss('[href="/parallel-catchall/baz"]').click() - await check( - () => browser.waitForElementByCss('#main').text(), - /main catchall/ - ) - await check( - () => browser.waitForElementByCss('#main').text(), - /catchall page client component/ - ) - await check( - () => browser.waitForElementByCss('#slot-content').text(), - 'baz slot' - ) - }) + it('should handle a loading state', async () => { + const browser = await next.browser('/with-loading') + expect(await browser.elementById('slot').text()).toBe('Root Slot') + expect(await browser.elementById('children').text()).toBe('Root Page') - it('should match the catch-all routes of the more specific path, if there is more than one catch-all route', async () => { - const browser = await next.browser('/parallel-nested-catchall') - - await browser - .elementByCss('[href="/parallel-nested-catchall/foo"]') - .click() - await check(() => browser.waitForElementByCss('#main').text(), 'foo') - await check( - () => browser.waitForElementByCss('#slot-content').text(), - 'foo slot' - ) - - await browser - .elementByCss('[href="/parallel-nested-catchall/bar"]') - .click() - await check(() => browser.waitForElementByCss('#main').text(), 'bar') - await check( - () => browser.waitForElementByCss('#slot-content').text(), - 'slot catchall' - ) - - await browser - .elementByCss('[href="/parallel-nested-catchall/foo/123"]') - .click() - await check(() => browser.waitForElementByCss('#main').text(), 'foo id') - await check( - () => browser.waitForElementByCss('#slot-content').text(), - 'foo id catchAll' - ) - }) + // should have triggered a loading state + expect( + await browser + .elementByCss( + `[href="/with-loading/foo${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#loading-page') + .text() + ).toBe('Loading...') + + // should eventually load the full page + await retry(async () => { + expect(await browser.elementById('slot').text()).toBe('Nested Slot') + expect(await browser.elementById('children').text()).toBe( + 'Welcome to Foo Page' + ) + }) + }) - it('should navigate with a link with prefetch=false', async () => { - const browser = await next.browser('/parallel-prefetch-false') + if (isNextDev) { + it('should support parallel routes with no page component', async () => { + const browser = await next.browser('/parallel-no-page/foo') + const timestamp = await browser.elementByCss('#timestamp').text() - // check if the default view loads - await check( - () => browser.waitForElementByCss('#default-parallel').text(), - 'default view for parallel' - ) + await new Promise((resolve) => { + setTimeout(resolve, 3000) + }) - // check that navigating to /foo re-renders the layout to display @parallel/foo - await check( - () => - browser - .elementByCss('[href="/parallel-prefetch-false/foo"]') - .click() - .waitForElementByCss('#parallel-foo') - .text(), - 'parallel for foo' - ) - }) + await check(async () => { + // an invalid response triggers a fast refresh, so if the timestamp doesn't update, this behaved correctly + const newTimestamp = await browser.elementByCss('#timestamp').text() + return newTimestamp !== timestamp ? 'failure' : 'success' + }, 'success') + }) - it('should display all parallel route params with useParams', async () => { - const browser = await next.browser('/parallel-dynamic/foo/bar') + it('should support nested parallel routes', async () => { + const browser = await next.browser('parallel-nested/home/nested') + const timestamp = await browser.elementByCss('#timestamp').text() - await check( - () => browser.waitForElementByCss('#foo').text(), - `{"slug":"foo","id":"bar"}` - ) + await new Promise((resolve) => { + setTimeout(resolve, 3000) + }) - await check( - () => browser.waitForElementByCss('#bar').text(), - `{"slug":"foo","id":"bar"}` - ) + await check(async () => { + // an invalid response triggers a fast refresh, so if the timestamp doesn't update, this behaved correctly + const newTimestamp = await browser.elementByCss('#timestamp').text() + return newTimestamp !== timestamp ? 'failure' : 'success' + }, 'success') + }) + } }) - it('should load CSS for a default page that exports another page', async () => { - const browser = await next.browser('/default-css') - - expect( - await browser.eval( - `window.getComputedStyle(document.getElementById("red-text")).color` + describe('route intercepting with dynamic routes', () => { + it('should render intercepted route', async () => { + const browser = await next.browser( + `/intercepting-routes-dynamic/photos${trailingSlash ? '/' : ''}` ) - ).toBe('rgb(255, 0, 0)') - // the more page will now be using the page's `default.tsx` file, which re-exports the root page. - await browser.elementByCss('[href="/default-css/more"]').click() + // Check if navigation to modal route works + await check( + () => + browser + .elementByCss( + `[href="/intercepting-routes-dynamic/photos/next/123${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#user-intercept-page') + .text(), + 'Intercepted Page' + ) - expect( - await browser.eval( - `window.getComputedStyle(document.getElementById("red-text")).color` + // Check if url matches even though it was intercepted. + await check( + () => browser.url(), + next.url + + '/intercepting-routes-dynamic/photos/next/123' + + (trailingSlash ? '/' : '') ) - ).toBe('rgb(255, 0, 0)') - // ensure that everything still works on a fresh load - await browser.refresh() + // Trigger a refresh, this should load the normal page, not the modal. + await check( + () => + browser.refresh().waitForElementByCss('#user-regular-page').text(), + 'Regular Page' + ) - expect( - await browser.eval( - `window.getComputedStyle(document.getElementById("red-text")).color` + // Check if the url matches still. + await check( + () => browser.url(), + next.url + + '/intercepting-routes-dynamic/photos/next/123' + + (trailingSlash ? '/' : '') ) - ).toBe('rgb(255, 0, 0)') + }) }) - it('should handle a loading state', async () => { - const browser = await next.browser('/with-loading') - expect(await browser.elementById('slot').text()).toBe('Root Slot') - expect(await browser.elementById('children').text()).toBe('Root Page') + describe('route intercepting with prerendered dynamic routes ', () => { + it('should render intercepted route', async () => { + const browser = await next.browser( + '/intercepting-routes-dynamic-prerendered/photos' + ) - // should have triggered a loading state - expect( + // Check if navigation to modal route works. await browser - .elementByCss('[href="/with-loading/foo"]') + .elementByCss( + `[href="/intercepting-routes-dynamic-prerendered/photos/1${trailingSlash ? '/' : ''}"]` + ) .click() - .waitForElementByCss('#loading-page') - .text() - ).toBe('Loading...') - // should eventually load the full page - await retry(async () => { - expect(await browser.elementById('slot').text()).toBe('Nested Slot') - expect(await browser.elementById('children').text()).toBe( - 'Welcome to Foo Page' + // This should load the intercepted page. + await retry(async () => { + expect( + await browser.waitForElementByCss('#photo-intercepted-1').text() + ).toBe('Photo INTERCEPTED 1') + }) + + // Check if url matches even though it was intercepted. + expect(await browser.url()).toBe( + next.url + + '/intercepting-routes-dynamic-prerendered/photos/1' + + (trailingSlash ? '/' : '') ) - }) - }) - if (isNextDev) { - it('should support parallel routes with no page component', async () => { - const browser = await next.browser('/parallel-no-page/foo') - const timestamp = await browser.elementByCss('#timestamp').text() + // There must not be any errors from prefetching the intercepted page. + expect( + (await browser.log()).filter(({ source }) => source === 'error') + ).toEqual([]) - await new Promise((resolve) => { - setTimeout(resolve, 3000) - }) + // Trigger a refresh, this should load the normal page, not the modal. + await browser.refresh() + expect(await browser.waitForElementByCss('#photo-page-1').text()).toBe( + 'Photo PAGE 1' + ) - await check(async () => { - // an invalid response triggers a fast refresh, so if the timestamp doesn't update, this behaved correctly - const newTimestamp = await browser.elementByCss('#timestamp').text() - return newTimestamp !== timestamp ? 'failure' : 'success' - }, 'success') + // Check if the url matches still. + expect(await browser.url()).toBe( + next.url + + '/intercepting-routes-dynamic-prerendered/photos/1' + + (trailingSlash ? '/' : '') + ) }) + }) - it('should support nested parallel routes', async () => { - const browser = await next.browser('parallel-nested/home/nested') - const timestamp = await browser.elementByCss('#timestamp').text() + describe('route intercepting with dynamic optional catch-all routes', () => { + it('should render intercepted route', async () => { + const browser = await next.browser( + `/intercepting-routes-dynamic-catchall/photos${trailingSlash ? '/' : ''}` + ) - await new Promise((resolve) => { - setTimeout(resolve, 3000) - }) + // Check if navigation to modal route works + await check( + () => + browser + .elementByCss( + `[href="/intercepting-routes-dynamic-catchall/photos/optional-catchall/123${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#optional-catchall-intercept-page') + .text(), + 'Intercepted Page' + ) - await check(async () => { - // an invalid response triggers a fast refresh, so if the timestamp doesn't update, this behaved correctly - const newTimestamp = await browser.elementByCss('#timestamp').text() - return newTimestamp !== timestamp ? 'failure' : 'success' - }, 'success') - }) - } - }) + // Check if url matches even though it was intercepted. + await check( + () => browser.url(), + next.url + + '/intercepting-routes-dynamic-catchall/photos/optional-catchall/123' + + (trailingSlash ? '/' : '') + ) - describe('route intercepting with dynamic routes', () => { - it('should render intercepted route', async () => { - const browser = await next.browser('/intercepting-routes-dynamic/photos') + // Trigger a refresh, this should load the normal page, not the modal. + await check( + () => + browser + .refresh() + .waitForElementByCss('#optional-catchall-regular-page') + .text(), + 'Regular Page' + ) - // Check if navigation to modal route works - await check( - () => - browser - .elementByCss( - '[href="/intercepting-routes-dynamic/photos/next/123"]' - ) - .click() - .waitForElementByCss('#user-intercept-page') - .text(), - 'Intercepted Page' - ) - - // Check if url matches even though it was intercepted. - await check( - () => browser.url(), - next.url + '/intercepting-routes-dynamic/photos/next/123' - ) - - // Trigger a refresh, this should load the normal page, not the modal. - await check( - () => - browser.refresh().waitForElementByCss('#user-regular-page').text(), - 'Regular Page' - ) - - // Check if the url matches still. - await check( - () => browser.url(), - next.url + '/intercepting-routes-dynamic/photos/next/123' - ) + // Check if the url matches still. + await check( + () => browser.url(), + next.url + + '/intercepting-routes-dynamic-catchall/photos/optional-catchall/123' + + (trailingSlash ? '/' : '') + ) + }) }) - }) - describe('route intercepting with prerendered dynamic routes ', () => { - it('should render intercepted route', async () => { - const browser = await next.browser( - '/intercepting-routes-dynamic-prerendered/photos' - ) + describe('route intercepting with dynamic catch-all routes', () => { + it('should render intercepted route', async () => { + const browser = await next.browser( + `/intercepting-routes-dynamic-catchall/photos${trailingSlash ? '/' : ''}` + ) - // Check if navigation to modal route works. - await browser - .elementByCss( - '[href="/intercepting-routes-dynamic-prerendered/photos/1"]' + // Check if navigation to modal route works + await check( + () => + browser + .elementByCss( + `[href="/intercepting-routes-dynamic-catchall/photos/catchall/123${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#catchall-intercept-page') + .text(), + 'Intercepted Page' ) - .click() - // This should load the intercepted page. - await retry(async () => { - expect( - await browser.waitForElementByCss('#photo-intercepted-1').text() - ).toBe('Photo INTERCEPTED 1') - }) + // Check if url matches even though it was intercepted. + await check( + () => browser.url(), + next.url + + '/intercepting-routes-dynamic-catchall/photos/catchall/123' + + (trailingSlash ? '/' : '') + ) - // Check if url matches even though it was intercepted. - expect(await browser.url()).toBe( - next.url + '/intercepting-routes-dynamic-prerendered/photos/1' - ) - - // There must not be any errors from prefetching the intercepted page. - expect( - (await browser.log()).filter(({ source }) => source === 'error') - ).toEqual([]) - - // Trigger a refresh, this should load the normal page, not the modal. - await browser.refresh() - expect(await browser.waitForElementByCss('#photo-page-1').text()).toBe( - 'Photo PAGE 1' - ) - - // Check if the url matches still. - expect(await browser.url()).toBe( - next.url + '/intercepting-routes-dynamic-prerendered/photos/1' - ) + // Trigger a refresh, this should load the normal page, not the modal. + await check( + () => + browser + .refresh() + .waitForElementByCss('#catchall-regular-page') + .text(), + 'Regular Page' + ) + + // Check if the url matches still. + await check( + () => browser.url(), + next.url + + '/intercepting-routes-dynamic-catchall/photos/catchall/123' + + (trailingSlash ? '/' : '') + ) + }) }) - }) - describe('route intercepting with dynamic optional catch-all routes', () => { - it('should render intercepted route', async () => { - const browser = await next.browser( - '/intercepting-routes-dynamic-catchall/photos' - ) + describe('route intercepting', () => { + it('should render intercepted route', async () => { + const browser = await next.browser( + `/intercepting-routes/feed${trailingSlash ? '/' : ''}` + ) - // Check if navigation to modal route works - await check( - () => - browser - .elementByCss( - '[href="/intercepting-routes-dynamic-catchall/photos/optional-catchall/123"]' - ) - .click() - .waitForElementByCss('#optional-catchall-intercept-page') - .text(), - 'Intercepted Page' - ) - - // Check if url matches even though it was intercepted. - await check( - () => browser.url(), - next.url + - '/intercepting-routes-dynamic-catchall/photos/optional-catchall/123' - ) - - // Trigger a refresh, this should load the normal page, not the modal. - await check( - () => - browser - .refresh() - .waitForElementByCss('#optional-catchall-regular-page') - .text(), - 'Regular Page' - ) - - // Check if the url matches still. - await check( - () => browser.url(), - next.url + - '/intercepting-routes-dynamic-catchall/photos/optional-catchall/123' - ) - }) - }) + // Check if navigation to modal route works. + await check( + () => + browser + .elementByCss( + `[href="/intercepting-routes/feed/photos/1${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#photo-intercepted-1') + .text(), + 'Photo INTERCEPTED 1' + ) - describe('route intercepting with dynamic catch-all routes', () => { - it('should render intercepted route', async () => { - const browser = await next.browser( - '/intercepting-routes-dynamic-catchall/photos' - ) + // Check if intercepted route was rendered while existing page content was removed. + // Content would only be preserved when combined with parallel routes. + // await check(() => browser.elementByCss('#feed-page').text()).not.toBe('Feed') - // Check if navigation to modal route works - await check( - () => - browser - .elementByCss( - '[href="/intercepting-routes-dynamic-catchall/photos/catchall/123"]' - ) - .click() - .waitForElementByCss('#catchall-intercept-page') - .text(), - 'Intercepted Page' - ) - - // Check if url matches even though it was intercepted. - await check( - () => browser.url(), - next.url + '/intercepting-routes-dynamic-catchall/photos/catchall/123' - ) - - // Trigger a refresh, this should load the normal page, not the modal. - await check( - () => - browser - .refresh() - .waitForElementByCss('#catchall-regular-page') - .text(), - 'Regular Page' - ) - - // Check if the url matches still. - await check( - () => browser.url(), - next.url + '/intercepting-routes-dynamic-catchall/photos/catchall/123' - ) - }) - }) + // Check if url matches even though it was intercepted. + await check( + () => browser.url(), + next.url + + '/intercepting-routes/feed/photos/1' + + (trailingSlash ? '/' : '') + ) - describe('route intercepting', () => { - it('should render intercepted route', async () => { - const browser = await next.browser('/intercepting-routes/feed') + // Trigger a refresh, this should load the normal page, not the modal. + await check( + () => browser.refresh().waitForElementByCss('#photo-page-1').text(), + 'Photo PAGE 1' + ) - // Check if navigation to modal route works. - await check( - () => - browser - .elementByCss('[href="/intercepting-routes/feed/photos/1"]') - .click() - .waitForElementByCss('#photo-intercepted-1') - .text(), - 'Photo INTERCEPTED 1' - ) - - // Check if intercepted route was rendered while existing page content was removed. - // Content would only be preserved when combined with parallel routes. - // await check(() => browser.elementByCss('#feed-page').text()).not.toBe('Feed') - - // Check if url matches even though it was intercepted. - await check( - () => browser.url(), - next.url + '/intercepting-routes/feed/photos/1' - ) - - // Trigger a refresh, this should load the normal page, not the modal. - await check( - () => browser.refresh().waitForElementByCss('#photo-page-1').text(), - 'Photo PAGE 1' - ) - - // Check if the url matches still. - await check( - () => browser.url(), - next.url + '/intercepting-routes/feed/photos/1' - ) - }) + // Check if the url matches still. + await check( + () => browser.url(), + next.url + + '/intercepting-routes/feed/photos/1' + + (trailingSlash ? '/' : '') + ) + }) - it('should render an intercepted route from a slot', async () => { - const browser = await next.browser('/') + it('should render an intercepted route from a slot', async () => { + const browser = await next.browser('/') - await check( - () => browser.waitForElementByCss('#default-slot').text(), - 'default from @slot' - ) + await check( + () => browser.waitForElementByCss('#default-slot').text(), + 'default from @slot' + ) - await check( - () => - browser - .elementByCss('[href="/nested"]') - .click() - .waitForElementByCss('#interception-slot') - .text(), - 'interception from @slot/nested' - ) - - // Check if the client component is rendered - await check( - () => browser.waitForElementByCss('#interception-slot-client').text(), - 'client component' - ) - - await check( - () => browser.refresh().waitForElementByCss('#nested').text(), - 'hello world from /nested' - ) - }) + await check( + () => + browser + .elementByCss(`[href="/nested${trailingSlash ? '/' : ''}"]`) + .click() + .waitForElementByCss('#interception-slot') + .text(), + 'interception from @slot/nested' + ) - it('should render an intercepted route at the top level from a nested path', async () => { - const browser = await next.browser('/nested-link') + // Check if the client component is rendered + await check( + () => browser.waitForElementByCss('#interception-slot-client').text(), + 'client component' + ) - await check( - () => browser.waitForElementByCss('#default-slot').text(), - 'default from @slot' - ) + await check( + () => browser.refresh().waitForElementByCss('#nested').text(), + 'hello world from /nested' + ) + }) - await check( - () => - browser - .elementByCss('[href="/nested"]') - .click() - .waitForElementByCss('#interception-slot') - .text(), - 'interception from @slot/nested' - ) - - await check( - () => browser.refresh().waitForElementByCss('#nested').text(), - 'hello world from /nested' - ) - }) + it('should render an intercepted route at the top level from a nested path', async () => { + const browser = await next.browser( + `/nested-link${trailingSlash ? '/' : ''}` + ) - it('should render intercepted route from a nested route', async () => { - const browser = await next.browser('/intercepting-routes/feed/nested') + await check( + () => browser.waitForElementByCss('#default-slot').text(), + 'default from @slot' + ) - // Check if navigation to modal route works. - await check( - () => - browser - .elementByCss('[href="/intercepting-routes/feed/photos/1"]') - .click() - .waitForElementByCss('#photo-intercepted-1') - .text(), - 'Photo INTERCEPTED 1' - ) - - // Check if intercepted route was rendered while existing page content was removed. - // Content would only be preserved when combined with parallel routes. - // await check(() => browser.elementByCss('#feed-page').text()).not.toBe('Feed') - - // Check if url matches even though it was intercepted. - await check( - () => browser.url(), - next.url + '/intercepting-routes/feed/photos/1' - ) - - // Trigger a refresh, this should load the normal page, not the modal. - await check( - () => browser.refresh().waitForElementByCss('#photo-page-1').text(), - 'Photo PAGE 1' - ) - - // Check if the url matches still. - await check( - () => browser.url(), - next.url + '/intercepting-routes/feed/photos/1' - ) - }) + await check( + () => + browser + .elementByCss(`[href="/nested${trailingSlash ? '/' : ''}"]`) + .click() + .waitForElementByCss('#interception-slot') + .text(), + 'interception from @slot/nested' + ) - it('should re-render the layout on the server when it had a default child route', async () => { - const browser = await next.browser('/parallel-non-intercepting') + await check( + () => browser.refresh().waitForElementByCss('#nested').text(), + 'hello world from /nested' + ) + }) - // check if the default view loads - await check( - () => browser.waitForElementByCss('#default-parallel').text(), - 'default view for parallel' - ) + it('should render intercepted route from a nested route', async () => { + const browser = await next.browser( + `/intercepting-routes/feed/nested${trailingSlash ? '/' : ''}` + ) - // check that navigating to /foo re-renders the layout to display @parallel/foo - await check( - () => - browser - .elementByCss('[href="/parallel-non-intercepting/foo"]') - .click() - .waitForElementByCss('#parallel-foo') - .text(), - 'parallel for foo' - ) + // Check if navigation to modal route works. + await check( + () => + browser + .elementByCss( + `[href="/intercepting-routes/feed/photos/1${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#photo-intercepted-1') + .text(), + 'Photo INTERCEPTED 1' + ) - // check that navigating to /foo also re-renders the base children - await check(() => browser.elementByCss('#children-foo').text(), 'foo') - }) + // Check if intercepted route was rendered while existing page content was removed. + // Content would only be preserved when combined with parallel routes. + // await check(() => browser.elementByCss('#feed-page').text()).not.toBe('Feed') - it('should render modal when paired with parallel routes', async () => { - const browser = await next.browser('/intercepting-parallel-modal/vercel') - // Check if navigation to modal route works. - await check( - () => - browser - .elementByCss('[href="/intercepting-parallel-modal/photo/1"]') - .click() - .waitForElementByCss('#photo-modal-1') - .text(), - 'Photo MODAL 1' - ) - - await check( - () => - browser - .elementByCss('[href="/intercepting-parallel-modal/photo/2"]') - .click() - .waitForElementByCss('#photo-modal-2') - .text(), - 'Photo MODAL 2' - ) - - // Check if modal was rendered while existing page content is preserved. - await check( - () => browser.elementByCss('#user-page').text(), - 'Feed for vercel' - ) - - // Check if url matches even though it was intercepted. - await check( - () => browser.url(), - next.url + '/intercepting-parallel-modal/photo/2' - ) - - // Trigger a refresh, this should load the normal page, not the modal. - await check( - () => browser.refresh().waitForElementByCss('#photo-page-2').text(), - 'Photo PAGE 2' - ) - - // Check if the url matches still. - await check( - () => browser.url(), - next.url + '/intercepting-parallel-modal/photo/2' - ) - }) + // Check if url matches even though it was intercepted. + await check( + () => browser.url(), + next.url + + '/intercepting-routes/feed/photos/1' + + (trailingSlash ? '/' : '') + ) - it('should support intercepting with beforeFiles rewrites', async () => { - const browser = await next.browser('/foo') + // Trigger a refresh, this should load the normal page, not the modal. + await check( + () => browser.refresh().waitForElementByCss('#photo-page-1').text(), + 'Photo PAGE 1' + ) - await check( - () => - browser - .elementByCss('[href="/photos"]') - .click() - .waitForElementByCss('#intercepted') - .text(), - 'intercepted' - ) - }) + // Check if the url matches still. + await check( + () => browser.url(), + next.url + + '/intercepting-routes/feed/photos/1' + + (trailingSlash ? '/' : '') + ) + }) - it('should support intercepting local dynamic sibling routes', async () => { - const browser = await next.browser('/intercepting-siblings') + it('should re-render the layout on the server when it had a default child route', async () => { + const browser = await next.browser( + `/parallel-non-intercepting${trailingSlash ? '/' : ''}` + ) - await check( - () => - browser - .elementByCss('[href="/intercepting-siblings/1"]') - .click() - .waitForElementByCss('#intercepted-sibling') - .text(), - '1' - ) - await check( - () => - browser - .elementByCss('[href="/intercepting-siblings/2"]') - .click() - .waitForElementByCss('#intercepted-sibling') - .text(), - '2' - ) - await check( - () => - browser - .elementByCss('[href="/intercepting-siblings/3"]') - .click() - .waitForElementByCss('#intercepted-sibling') - .text(), - '3' - ) + // check if the default view loads + await check( + () => browser.waitForElementByCss('#default-parallel').text(), + 'default view for parallel' + ) - await next.browser('/intercepting-siblings/1') + // check that navigating to /foo re-renders the layout to display @parallel/foo + await check( + () => + browser + .elementByCss( + `[href="/parallel-non-intercepting/foo${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#parallel-foo') + .text(), + 'parallel for foo' + ) - await check(() => browser.waitForElementByCss('#main-slot').text(), '1') - }) + // check that navigating to /foo also re-renders the base children + await check(() => browser.elementByCss('#children-foo').text(), 'foo') + }) - it('should intercept on routes that contain hyphenated/special dynamic params', async () => { - const browser = await next.browser( - '/interception-route-special-params/some-random-param' - ) + it('should render modal when paired with parallel routes', async () => { + const browser = await next.browser( + `/intercepting-parallel-modal/vercel${trailingSlash ? '/' : ''}` + ) + // Check if navigation to modal route works. + await check( + () => + browser + .elementByCss( + `[href="/intercepting-parallel-modal/photo/1${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#photo-modal-1') + .text(), + 'Photo MODAL 1' + ) - await browser - .elementByCss( - "[href='/interception-route-special-params/some-random-param/some-page']" + await check( + () => + browser + .elementByCss( + `[href="/intercepting-parallel-modal/photo/2${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#photo-modal-2') + .text(), + 'Photo MODAL 2' ) - .click() - const interceptionText = - 'Hello from [this-is-my-route]/@intercept/some-page. Param: some-random-param' - const pageText = - 'Hello from [this-is-my-route]/some-page. Param: some-random-param' + // Check if modal was rendered while existing page content is preserved. + await check( + () => browser.elementByCss('#user-page').text(), + 'Feed for vercel' + ) + + // Check if url matches even though it was intercepted. + await check( + () => browser.url(), + next.url + + '/intercepting-parallel-modal/photo/2' + + (trailingSlash ? '/' : '') + ) + + // Trigger a refresh, this should load the normal page, not the modal. + await check( + () => browser.refresh().waitForElementByCss('#photo-page-2').text(), + 'Photo PAGE 2' + ) - await retry(async () => { - expect(await browser.elementByCss('body').text()).toContain( - interceptionText + // Check if the url matches still. + await check( + () => browser.url(), + next.url + + '/intercepting-parallel-modal/photo/2' + + (trailingSlash ? '/' : '') ) + }) + + it('should support intercepting with beforeFiles rewrites', async () => { + const browser = await next.browser(`/foo${trailingSlash ? '/' : ''}`) - expect(await browser.elementByCss('body').text()).not.toContain( - pageText + await check( + () => + browser + .elementByCss(`[href="/photos${trailingSlash ? '/' : ''}"]`) + .click() + .waitForElementByCss('#intercepted') + .text(), + 'intercepted' ) }) - await browser.refresh() + it('should support intercepting local dynamic sibling routes', async () => { + const browser = await next.browser( + `/intercepting-siblings${trailingSlash ? '/' : ''}` + ) - await retry(async () => { - expect(await browser.elementByCss('body').text()).toContain(pageText) + await check( + () => + browser + .elementByCss( + `[href="/intercepting-siblings/1${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#intercepted-sibling') + .text(), + '1' + ) + await check( + () => + browser + .elementByCss( + `[href="/intercepting-siblings/2${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#intercepted-sibling') + .text(), + '2' + ) + await check( + () => + browser + .elementByCss( + `[href="/intercepting-siblings/3${trailingSlash ? '/' : ''}"]` + ) + .click() + .waitForElementByCss('#intercepted-sibling') + .text(), + '3' + ) - expect(await browser.elementByCss('body').text()).not.toContain( - interceptionText + await next.browser( + `/intercepting-siblings/1${trailingSlash ? '/' : ''}` ) + + await check(() => browser.waitForElementByCss('#main-slot').text(), '1') }) - }) - if (isNextStart) { - it('should not have /default paths in the prerender manifest', async () => { - const prerenderManifest = JSON.parse( - await next.readFile('.next/prerender-manifest.json') + it('should intercept on routes that contain hyphenated/special dynamic params', async () => { + const browser = await next.browser( + `/interception-route-special-params/some-random-param${trailingSlash ? '/' : ''}` ) - const routes = Object.keys(prerenderManifest.routes) + await browser + .elementByCss( + `[href="/interception-route-special-params/some-random-param/some-page${trailingSlash ? '/' : ''}"]` + ) + .click() - for (const route of routes) { - expect(route.endsWith('/default')).toBe(false) - } + const interceptionText = + 'Hello from [this-is-my-route]/@intercept/some-page. Param: some-random-param' + const pageText = + 'Hello from [this-is-my-route]/some-page. Param: some-random-param' + + await retry(async () => { + expect(await browser.elementByCss('body').text()).toContain( + interceptionText + ) + + expect(await browser.elementByCss('body').text()).not.toContain( + pageText + ) + }) + + await browser.refresh() + + await retry(async () => { + expect(await browser.elementByCss('body').text()).toContain(pageText) + + expect(await browser.elementByCss('body').text()).not.toContain( + interceptionText + ) + }) }) - } - }) -}) + + if (isNextStart) { + it('should not have /default paths in the prerender manifest', async () => { + const prerenderManifest = JSON.parse( + await next.readFile('.next/prerender-manifest.json') + ) + + const routes = Object.keys(prerenderManifest.routes) + + for (const route of routes) { + expect(route.endsWith('/default')).toBe(false) + } + }) + } + }) + } +) describe('parallel-routes-and-interception-conflicting-pages', () => { const { next, skipped } = nextTestSetup({ @@ -935,6 +1069,7 @@ describe('parallel-routes-and-interception-conflicting-pages', () => { } `, }, + nextConfig, }) if (skipped) return