rsnext/test/e2e/app-dir/interception-routes-root-catchall/interception-routes-root-catchall.test.ts

42 lines
1.3 KiB
TypeScript
Raw Normal View History

fix parallel catch-all route normalization (#59791) ### What? Catch-all routes are being matched to parallel routes which causes issues like an interception route being handled by the wrong page component, or a page component being associated with multiple pages resulting in a "You cannot have two parallel pages that resolve to the same path" build error. ### Why? #58215 fixed a bug that caused catchall paths to not properly match when used with parallel routes. In other words, a catchall slot wouldn't render on a page that could match that catch all. Or a catchall page wouldn't match a slot. At build time, a normalization step was introduced to take application paths and attempt to perform this matching behavior. However in it's current form, this causes the errors mentioned above to manifest. To better illustrate the problem, here are a few examples: Given: ``` { '/': [ '/page' ], '/[...slug]': [ '/[...slug]/page' ], '/items/[...ids]': [ '/items/[...ids]/page' ], '/(.)items/[...ids]': [ '/@modal/(.)items/[...ids]/page' ] } ``` The normalization logic would produce: ``` { '/': [ '/page' ], '/[...slug]': [ '/[...slug]/page' ], '/items/[...ids]': [ '/items/[...ids]/page' ], '/(.)items/[...ids]': [ '/@modal/(.)items/[...ids]/page', '/[...slug]/page' ] } ``` The interception route will now be improperly handled by `[...slug]/page` rather than the interception handler. Another example, which rather than incorrectly handling a match, will produce a build error: Given: ``` { '/': [ '/(group-b)/page' ], '/[...catcher]': [ '/(group-a)/@parallel/[...catcher]/page' ] } ``` The normalization logic would produce: ``` { '/': [ '/(group-b)/page', '/(group-a)/@parallel/[...catcher]/page' ], '/[...catcher]': [ '/(group-a)/@parallel/[...catcher]/page' ] } ``` The parallel catch-all slot is now part of `/`. This means when building the loader tree, two `children` parallel segments (aka page components) will be found when hitting `/`, which is an error. The error that was added here was originally intended to help catch scenarios like: `/app/(group-a)/page` and `/app/(group-b)/page`. However it also throws for parallel slots, which isn't necessarily an error (especially since the normalization logic will push potential matches). ### How? There are two small fixes in this PR, the rest are an abundance of e2e tests to help prevent regressions. - When normalizing catch-all routes, we will not attempt to push any page entrypoints for interception routes. These should already have all the information they need in `appPaths`. - Before throwing the error about duplicate page segments in `next-app-loader`, we check to see if it's because we already matched a page component but we also detected a parallel slot that would have matched the page slot. In this case, we don't error, since the app can recover from this. - Loading a client reference manifest shouldn't throw a cryptic require error. `loadClientReferenceManifest` is already potentially returning undefined, so this case should already be handled gracefully Separately, we'll need to follow-up on the Turbopack side to: - Make sure the duplicate matching matches the Webpack implementation (I believe Webpack is sorting, but Turbopack isn't) - Implement #58215 in Turbopack. Once this is done, we should expect the tests added in this PR to start failing. Fixes #58272 Fixes #58660 Fixes #58312 Fixes #59782 Fixes #59784 Closes NEXT-1809
2023-12-22 18:30:23 +01:00
import { createNextDescribe } from 'e2e-utils'
import { check } from 'next-test-utils'
createNextDescribe(
'interception-routes-root-catchall',
{
files: __dirname,
},
({ next }) => {
it('should support having a root catch-all and a catch-all in a parallel route group', async () => {
const browser = await next.browser('/')
await browser.elementByCss('[href="/items/1"]').click()
// this triggers the /items route interception handling
await check(
() => browser.elementById('slot').text(),
/Intercepted Modal Page. Id: 1/
)
await browser.refresh()
// no longer intercepted, using the page
await check(() => browser.elementById('slot').text(), /default @modal/)
await check(
() => browser.elementById('children').text(),
/Regular Item Page. Id: 1/
)
})
it('should handle non-intercepted catch-all pages', async () => {
const browser = await next.browser('/')
// there's no explicit page for /foobar. This will trigger the catchall [...slug] page
await browser.elementByCss('[href="/foobar"]').click()
await check(() => browser.elementById('slot').text(), /default @modal/)
await check(
() => browser.elementById('children').text(),
/Root Catch-All Page/
)
})
}
)