rsnext/test/e2e/app-dir/parallel-routes-catchall-default/parallel-routes-catchall-default.test.ts
Zack Tanner 90f95399dd
fix catch-all route normalization for default parallel routes (#60240)
### What?
When relying on a `default` route as a fallback, greedier catch-all
segments in the application hierarchy would take precedence, causing
unexpected errors/matching behavior.

### Why?
When performing parallel route catch-all normalization, we push
potential catch-all matches to paths without considering that a path
might instead be matched by a `default` page. Because of this, the
catch-all take precedence and the app will not try and load the default.

For example, given this structure:

```
{
  "/": ["/page"],
  "/[[...catchAll]]": ["/[[...catchAll]]/page"],
  "/nested/[foo]/[bar]": ["/nested/[foo]/[bar]/@slot/page"],
  "/nested/[foo]/[bar]/[baz]": ["/nested/[foo]/[bar]/@slot/[baz]/page"],
}
```

(Where there's a `/nested/[foo]/[bar]/default.tsx`)

The route normalization logic would produce:

```
{
  "/": ["/page"],
  "/[[...catchAll]]": ["/[[...catchAll]]/page"],
  "/nested/[foo]/[bar]": [
    "/nested/[foo]/[bar]/@slot/page",
    "/[[...catchAll]]/page",
  ],
  "/nested/[foo]/[bar]/[baz]": [
    "/nested/[foo]/[bar]/@slot/[baz]/page",
    "/[[...catchAll]]/page",
  ],
}
```
This means that when building the `LoaderTree`, it won't ever try to
find the default for that segment. **This solution operates under the
assumption that if you defined a `default` at a particular layout
segment, you intend for that to render in place of a greedier
catch-all.** (Let me know if this is an incorrect assumption)

### How?
We can't safely normalize catch-all parallel routes without having
context about where the `default` segments are, so this updates
`appPaths` to be inclusive of default segments and then filters them
when doing anything relating to build/export to maintain existing
behavior. We use this information to check if an existing default exists
at the same segment level that we'd push the catch-all to. If one
exists, we don't push the catch-all. Otherwise we proceed as normal.

Closes NEXT-1987
2024-01-05 14:20:45 -08:00

44 lines
1.7 KiB
TypeScript

import { createNextDescribe } from 'e2e-utils'
createNextDescribe(
'parallel-routes-catchall-default',
{
files: __dirname,
},
({ next }) => {
it('should match default paths before catch-all', async () => {
let browser = await next.browser('/en/nested')
// we have a top-level catch-all but the /nested dir doesn't have a default/page until the /[foo]/[bar] segment
// so we expect the top-level catch-all to render
expect(await browser.elementById('children').text()).toBe(
'/[locale]/[[...catchAll]]/page.tsx'
)
browser = await next.browser('/en/nested/foo/bar')
// we're now at the /[foo]/[bar] segment, so we expect the matched page to be the default (since there's no page defined)
expect(await browser.elementById('nested-children').text()).toBe(
'/[locale]/nested/[foo]/[bar]/default.tsx'
)
// we expect the slot to match since there's a page defined at this segment
expect(await browser.elementById('slot').text()).toBe(
'/[locale]/nested/[foo]/[bar]/@slot/page.tsx'
)
browser = await next.browser('/en/nested/foo/bar/baz')
// the page slot should still be the one matched at the /[foo]/[bar] segment because it's the default and we
// didn't define a page at the /[foo]/[bar]/[baz] segment
expect(await browser.elementById('nested-children').text()).toBe(
'/[locale]/nested/[foo]/[bar]/default.tsx'
)
// however we do have a slot for the `[baz]` segment and so we expect that to no match
expect(await browser.elementById('slot').text()).toBe(
'/[locale]/nested/[foo]/[bar]/@slot/[baz]/page.tsx'
)
})
}
)