rsnext/test/e2e/app-dir/parallel-routes-use-selected-layout-segment/parallel-routes-use-selected-layout-segment.test.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

76 lines
4.3 KiB
TypeScript
Raw Normal View History

fix useSelectedLayoutSegment's support for parallel routes (#60912) fixes NEXT-2173 Fixes #59968 ### TODOs - [x] recreate [repro](https://github.com/williamli/nextjs-NEXT-2173) - [x] patch `useSelectedLayoutSegment` to support parallel routes (see "What") - [x] check `useSelectedLayoutSegments` to see if it is affected - [x] add test cases - [x] finalise PR description ### What? `useSelectedLayoutSegment` does not return the name of the active state of parallel route slots. #### Expected Behaviour According to https://nextjs.org/docs/app/building-your-application/routing/parallel-routes#useselectedlayoutsegments > When a user navigates to app/@auth/login (or /login in the URL bar), loginSegments will be equal to the string "login". 👉🏽 We should update the docs to explain `null` and __DEFAULT__ result as well. According to the [API reference for useSelectedLayoutSegment](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segment#returns): > useSelectedLayoutSegment returns a string of the active segment or null if one doesn't exist. > For example, given the Layouts and URLs below, the returned segment would be: > <img width="881" alt="CleanShot 2024-01-20 at 14 50 52@2x" src="https://github.com/vercel/next.js/assets/179761/bfaa34c8-3139-4ec3-bd70-4346c682e36b"> #### Current Behaviour Currently a string "children" is returned for everything inside a parallel route with active state and `__DEFAULT__` is returned if there is no active state for the parallel route (since the `default.tsx` is loaded). ~`null` is returned when the `default.tsx` is not loaded (possibly caused by another bug, see test case 5).~ #### Reproduction [GitHub Repo](https://github.com/williamli/nextjs-NEXT-2173) is created based on the example provided in [Next.js docs for using `useSelectedLayoutSegment` with Parallel Routes](https://nextjs.org/docs/app/building-your-application/routing/parallel-routes#useselectedlayoutsegments). #### Test Cases 1. If you visit https://next-2173.vercel.app/, you get loginSegments: __DEFAULT__ (hard navigation) or children (soft navigation after returning from a visit to /login) 2. If you soft nav to (/app/@auth/login and /app/@nav/login) https://next-2173.vercel.app/login, you get 1. loginSegment: `children` (expected value should be `login`) 2. navSegment: `children` (expected value should be `login`) 3. If you soft nav to (/app/@auth/reset) https://next-2173.vercel.app/reset, you get 1. loginSegments: `children` (expected value should be `reset`) 2. navSegment: `children` (expected value should be `login`) 4. If you soft nav to (/app/@auth/reset/withEmail) https://next-2173.vercel.app/reset/withEmail, you get 1. loginSegments: `children` (expected value should be `withEmail`) 2. navSegment: `children` (expected value should be `login`) 5. ~If you hard nav to (/app/@auth/reset/withEmail) https://next-2173.vercel.app/reset/withEmail, you get an unexpected result due to possibly another bug:~ * ~navSegment is `null` on the deployed (Vercel) version, the navSlot is *not* loaded~ * ~navSegment is `__DEFAULT__` on local dev, the navSlot loads `/app/@nav/default.tsx`.~ ### Why? In `packages/next/src/client/components/navigation.ts`, `getSelectedLayoutSegmentPath` is called and returns the correct segmentPath for parallel routes (even though there is a TODO comment indicating this function needs to be updated to handle parallel routes) but `useSelectedLayoutSegment` failed to return the correct segment when a parallelRouteKey is provided. ### How? `useSelectedLayoutSegment` is updated to return selectedLayoutSegments[0] for non parallel routes (original logic), but it will return the last segments for parallel routes (or null if nothing is active). ``` return parallelRouteKey === 'children' ? selectedLayoutSegments[0] : selectedLayoutSegments[selectedLayoutSegments.length-1] ?? null ``` --------- Co-authored-by: Zack Tanner <zacktanner@gmail.com>
2024-01-24 00:53:45 +01:00
import { createNextDescribe } from 'e2e-utils'
import { check } from 'next-test-utils'
createNextDescribe(
'parallel-routes-use-selected-layout-segment',
{
files: __dirname,
},
({ next }) => {
it('hard nav to router page and soft nav around other router pages', async () => {
const browser = await next.browser('/')
await check(() => browser.elementById('navSegment').text(), /^$/)
await check(() => browser.elementById('authSegment').text(), /^$/)
await check(() => browser.elementById('routeSegment').text(), /^$/)
await browser.elementByCss('[href="/foo"]').click()
await check(() => browser.elementById('navSegment').text(), /^$/)
await check(() => browser.elementById('authSegment').text(), /^$/)
await check(() => browser.elementById('routeSegment').text(), /foo/)
})
it('hard nav to router page and soft nav to parallel routes', async () => {
const browser = await next.browser('/')
await check(() => browser.elementById('navSegment').text(), /^$/)
await check(() => browser.elementById('authSegment').text(), /^$/)
await check(() => browser.elementById('routeSegment').text(), /^$/)
// soft nav to /login, since both @nav and @auth has /login defined, we expect both navSegment and authSegment to be 'login'
await browser.elementByCss('[href="/login"]').click()
await check(() => browser.elementById('navSegment').text(), /login/)
await check(() => browser.elementById('authSegment').text(), /login/)
await check(() => browser.elementById('routeSegment').text(), /^$/)
// when navigating to /reset, the @auth slot will render the /reset page ('reset') while maintaining the currently active page for the @nav slot ('login') since /reset is only defined in @auth
await browser.elementByCss('[href="/reset"]').click()
await check(() => browser.elementById('navSegment').text(), /login/)
await check(() => browser.elementById('authSegment').text(), /reset/)
await check(() => browser.elementById('routeSegment').text(), /^$/)
// when navigating to nested path /reset/withEmail, the @auth slot will render the nested /reset/withEmail page ('reset') while maintaining the currently active page for the @nav slot ('login') since /reset/withEmail is only defined in @auth
await browser.elementByCss('[href="/reset/withEmail"]').click()
await check(() => browser.elementById('navSegment').text(), /login/)
await check(() => browser.elementById('authSegment').text(), /withEmail/)
await check(() => browser.elementById('routeSegment').text(), /^$/)
})
it('hard nav to router page and soft nav to parallel route and soft nav back to another router page', async () => {
const browser = await next.browser('/')
await check(() => browser.elementById('navSegment').text(), /^$/)
await check(() => browser.elementById('authSegment').text(), /^$/)
await check(() => browser.elementById('routeSegment').text(), /^$/)
// when navigating to /reset, the @auth slot will render the /reset page ('reset') while maintaining the currently active page for the @nav slot ('null') since /reset is only defined in @auth
await browser.elementByCss('[href="/reset"]').click()
await check(() => browser.elementById('navSegment').text(), /^$/)
await check(() => browser.elementById('authSegment').text(), /reset/)
await check(() => browser.elementById('routeSegment').text(), /^$/)
// when soft navigate to /foo, the @auth and @nav slot will maintain their the currently active states since they do not have /foo defined
await browser.elementByCss('[href="/foo"]').click()
await check(() => browser.elementById('navSegment').text(), /^$/)
await check(() => browser.elementById('authSegment').text(), /reset/)
await check(() => browser.elementById('routeSegment').text(), /foo/)
})
it('hard nav to parallel route', async () => {
const browser = await next.browser('/reset/withMobile')
await check(() => browser.elementById('navSegment').text(), /^$/)
await check(() => browser.elementById('authSegment').text(), /withMobile/)
// the /app/default.tsx is rendered since /reset/withMobile is only defined in @auth
await check(() => browser.elementById('routeSegment').text(), /^$/)
})
}
)