fix(next): incorrect active state for partial matches of collection names in sidebar (#11511)
Previously, collections with similar names (e.g., `uploads` and `uploads-poly`) both appeared active when viewing either collection. This was due to `pathname.startsWith(href)`, which caused partial matches. This update refines the `isActive` logic to prevent partial matches.
This commit is contained in:
@@ -44,7 +44,8 @@ export const DefaultNavClient: React.FC<{
|
|||||||
id = `nav-global-${slug}`
|
id = `nav-global-${slug}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const isActive = pathname.startsWith(href)
|
const isActive =
|
||||||
|
pathname.startsWith(href) && ['/', undefined].includes(pathname[href.length])
|
||||||
|
|
||||||
const Label = (
|
const Label = (
|
||||||
<>
|
<>
|
||||||
|
|||||||
14
test/admin/collections/UploadTwo.ts
Normal file
14
test/admin/collections/UploadTwo.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
|
import { uploadTwoCollectionSlug } from '../slugs.js'
|
||||||
|
|
||||||
|
export const UploadTwoCollection: CollectionConfig = {
|
||||||
|
slug: uploadTwoCollectionSlug,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
upload: true,
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import { CollectionNoApiView } from './collections/NoApiView.js'
|
|||||||
import { CollectionNotInView } from './collections/NotInView.js'
|
import { CollectionNotInView } from './collections/NotInView.js'
|
||||||
import { Posts } from './collections/Posts.js'
|
import { Posts } from './collections/Posts.js'
|
||||||
import { UploadCollection } from './collections/Upload.js'
|
import { UploadCollection } from './collections/Upload.js'
|
||||||
|
import { UploadTwoCollection } from './collections/UploadTwo.js'
|
||||||
import { Users } from './collections/Users.js'
|
import { Users } from './collections/Users.js'
|
||||||
import { with300Documents } from './collections/With300Documents.js'
|
import { with300Documents } from './collections/With300Documents.js'
|
||||||
import { CustomGlobalViews1 } from './globals/CustomViews1.js'
|
import { CustomGlobalViews1 } from './globals/CustomViews1.js'
|
||||||
@@ -40,6 +41,7 @@ import {
|
|||||||
protectedCustomNestedViewPath,
|
protectedCustomNestedViewPath,
|
||||||
publicCustomViewPath,
|
publicCustomViewPath,
|
||||||
} from './shared.js'
|
} from './shared.js'
|
||||||
|
|
||||||
export default buildConfigWithDefaults({
|
export default buildConfigWithDefaults({
|
||||||
admin: {
|
admin: {
|
||||||
importMap: {
|
importMap: {
|
||||||
@@ -141,6 +143,7 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
collections: [
|
collections: [
|
||||||
UploadCollection,
|
UploadCollection,
|
||||||
|
UploadTwoCollection,
|
||||||
Posts,
|
Posts,
|
||||||
Users,
|
Users,
|
||||||
CollectionHidden,
|
CollectionHidden,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
notInViewCollectionSlug,
|
notInViewCollectionSlug,
|
||||||
postsCollectionSlug,
|
postsCollectionSlug,
|
||||||
settingsGlobalSlug,
|
settingsGlobalSlug,
|
||||||
|
uploadTwoCollectionSlug,
|
||||||
} from '../../slugs.js'
|
} from '../../slugs.js'
|
||||||
|
|
||||||
const { beforeAll, beforeEach, describe } = test
|
const { beforeAll, beforeEach, describe } = test
|
||||||
@@ -73,6 +74,7 @@ describe('General', () => {
|
|||||||
let disableDuplicateURL: AdminUrlUtil
|
let disableDuplicateURL: AdminUrlUtil
|
||||||
let serverURL: string
|
let serverURL: string
|
||||||
let adminRoutes: ReturnType<typeof getRoutes>
|
let adminRoutes: ReturnType<typeof getRoutes>
|
||||||
|
let uploadsTwo: AdminUrlUtil
|
||||||
|
|
||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
const prebuild = false // Boolean(process.env.CI)
|
const prebuild = false // Boolean(process.env.CI)
|
||||||
@@ -90,6 +92,7 @@ describe('General', () => {
|
|||||||
globalURL = new AdminUrlUtil(serverURL, globalSlug)
|
globalURL = new AdminUrlUtil(serverURL, globalSlug)
|
||||||
customViewsURL = new AdminUrlUtil(serverURL, customViews2CollectionSlug)
|
customViewsURL = new AdminUrlUtil(serverURL, customViews2CollectionSlug)
|
||||||
disableDuplicateURL = new AdminUrlUtil(serverURL, disableDuplicateSlug)
|
disableDuplicateURL = new AdminUrlUtil(serverURL, disableDuplicateSlug)
|
||||||
|
uploadsTwo = new AdminUrlUtil(serverURL, uploadTwoCollectionSlug)
|
||||||
|
|
||||||
context = await browser.newContext()
|
context = await browser.newContext()
|
||||||
page = await context.newPage()
|
page = await context.newPage()
|
||||||
@@ -424,6 +427,27 @@ describe('General', () => {
|
|||||||
expect(tagName).toBe('a')
|
expect(tagName).toBe('a')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should only have one nav item active at a time', async () => {
|
||||||
|
await page.goto(uploadsTwo.list)
|
||||||
|
await openNav(page)
|
||||||
|
|
||||||
|
// Locate "uploads" and "uploads-two" nav items
|
||||||
|
const uploadsNavItem = page.locator('.nav-group__content #nav-uploads')
|
||||||
|
const uploadsTwoNavItem = page.locator('.nav-group__content #nav-uploads-two')
|
||||||
|
|
||||||
|
// Ensure both exist before continuing
|
||||||
|
await expect(uploadsNavItem).toBeVisible()
|
||||||
|
await expect(uploadsTwoNavItem).toBeVisible()
|
||||||
|
|
||||||
|
// Locate all nav items containing the nav__link-indicator
|
||||||
|
const activeNavItems = page.locator(
|
||||||
|
'.nav-group__content .nav__link:has(.nav__link-indicator), .nav-group__content div.nav__link:has(.nav__link-indicator)',
|
||||||
|
)
|
||||||
|
|
||||||
|
// Expect exactly one nav item to have the indicator
|
||||||
|
await expect(activeNavItems).toHaveCount(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('breadcrumbs — should navigate from list to dashboard', async () => {
|
test('breadcrumbs — should navigate from list to dashboard', async () => {
|
||||||
await page.goto(postsUrl.list)
|
await page.goto(postsUrl.list)
|
||||||
await page.locator(`.step-nav a[href="${adminRoutes.routes.admin}"]`).click()
|
await page.locator(`.step-nav a[href="${adminRoutes.routes.admin}"]`).click()
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export interface Config {
|
|||||||
};
|
};
|
||||||
collections: {
|
collections: {
|
||||||
uploads: Upload;
|
uploads: Upload;
|
||||||
|
'uploads-two': UploadsTwo;
|
||||||
posts: Post;
|
posts: Post;
|
||||||
users: User;
|
users: User;
|
||||||
'hidden-collection': HiddenCollection;
|
'hidden-collection': HiddenCollection;
|
||||||
@@ -90,6 +91,7 @@ export interface Config {
|
|||||||
collectionsJoins: {};
|
collectionsJoins: {};
|
||||||
collectionsSelect: {
|
collectionsSelect: {
|
||||||
uploads: UploadsSelect<false> | UploadsSelect<true>;
|
uploads: UploadsSelect<false> | UploadsSelect<true>;
|
||||||
|
'uploads-two': UploadsTwoSelect<false> | UploadsTwoSelect<true>;
|
||||||
posts: PostsSelect<false> | PostsSelect<true>;
|
posts: PostsSelect<false> | PostsSelect<true>;
|
||||||
users: UsersSelect<false> | UsersSelect<true>;
|
users: UsersSelect<false> | UsersSelect<true>;
|
||||||
'hidden-collection': HiddenCollectionSelect<false> | HiddenCollectionSelect<true>;
|
'hidden-collection': HiddenCollectionSelect<false> | HiddenCollectionSelect<true>;
|
||||||
@@ -192,6 +194,25 @@ export interface Upload {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "uploads-two".
|
||||||
|
*/
|
||||||
|
export interface UploadsTwo {
|
||||||
|
id: string;
|
||||||
|
title?: string | null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
url?: string | null;
|
||||||
|
thumbnailURL?: string | null;
|
||||||
|
filename?: string | null;
|
||||||
|
mimeType?: string | null;
|
||||||
|
filesize?: number | null;
|
||||||
|
width?: number | null;
|
||||||
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This is a custom collection description.
|
* This is a custom collection description.
|
||||||
*
|
*
|
||||||
@@ -469,6 +490,10 @@ export interface PayloadLockedDocument {
|
|||||||
relationTo: 'uploads';
|
relationTo: 'uploads';
|
||||||
value: string | Upload;
|
value: string | Upload;
|
||||||
} | null)
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'uploads-two';
|
||||||
|
value: string | UploadsTwo;
|
||||||
|
} | null)
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'posts';
|
relationTo: 'posts';
|
||||||
value: string | Post;
|
value: string | Post;
|
||||||
@@ -611,6 +636,24 @@ export interface UploadsSelect<T extends boolean = true> {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "uploads-two_select".
|
||||||
|
*/
|
||||||
|
export interface UploadsTwoSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
url?: T;
|
||||||
|
thumbnailURL?: T;
|
||||||
|
filename?: T;
|
||||||
|
mimeType?: T;
|
||||||
|
filesize?: T;
|
||||||
|
width?: T;
|
||||||
|
height?: T;
|
||||||
|
focalX?: T;
|
||||||
|
focalY?: T;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "posts_select".
|
* via the `definition` "posts_select".
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export const notInViewCollectionSlug = 'not-in-view-collection'
|
|||||||
export const noApiViewCollectionSlug = 'collection-no-api-view'
|
export const noApiViewCollectionSlug = 'collection-no-api-view'
|
||||||
export const disableDuplicateSlug = 'disable-duplicate'
|
export const disableDuplicateSlug = 'disable-duplicate'
|
||||||
export const uploadCollectionSlug = 'uploads'
|
export const uploadCollectionSlug = 'uploads'
|
||||||
|
|
||||||
|
export const uploadTwoCollectionSlug = 'uploads-two'
|
||||||
export const customFieldsSlug = 'custom-fields'
|
export const customFieldsSlug = 'custom-fields'
|
||||||
|
|
||||||
export const listDrawerSlug = 'with-list-drawer'
|
export const listDrawerSlug = 'with-list-drawer'
|
||||||
|
|||||||
Reference in New Issue
Block a user