chore: adds e2e tests for nested views (#3962)
This commit is contained in:
@@ -96,7 +96,7 @@ To swap out any of these views, simply pass in your custom component to the `adm
|
||||
}
|
||||
```
|
||||
|
||||
For more granular control, pass a configuration object instead. Payload exposes all of the properties of `<Route />` component in [React Router v5](https://v5.reactrouter.com):
|
||||
For more granular control, pass a configuration object instead. Each view corresponds to its own `<Route />` component in [React Router v5](https://v5.reactrouter.com). Payload exposes all of the properties of React Router:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -129,6 +129,12 @@ To add a _new_ view to the Admin Panel, simply add another key to the `views` ob
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
Routes are cascading. This means that unless explicitly given the `exact` property, they will match on URLs that simply _start_ with the route's path. This is helpful when creating catch-all routes in your application. Alternatively, you could define your nested route _before_ your parent route.
|
||||
</Banner>
|
||||
|
||||
_For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/main/test/admin/components)._
|
||||
|
||||
For help on how to build your own custom view components, see [building a custom view component](#building-a-custom-view-component).
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import CustomTabComponent from '../components/CustomTabComponent'
|
||||
import CustomTabView from '../components/views/CustomTab'
|
||||
import CustomTabView2 from '../components/views/CustomTab2'
|
||||
import CustomNestedTabView from '../components/views/CustomTabNested'
|
||||
import CustomVersionsView from '../components/views/CustomVersions'
|
||||
import CustomView from '../components/views/CustomView'
|
||||
import { customEditLabel, customTabLabel, customViews2Slug } from '../shared'
|
||||
import {
|
||||
customEditLabel,
|
||||
customNestedTabViewPath,
|
||||
customTabLabel,
|
||||
customTabViewPath,
|
||||
customViews2Slug,
|
||||
} from '../shared'
|
||||
|
||||
export const CustomViews2: CollectionConfig = {
|
||||
slug: customViews2Slug,
|
||||
@@ -21,17 +29,21 @@ export const CustomViews2: CollectionConfig = {
|
||||
Versions: CustomVersionsView,
|
||||
MyCustomView: {
|
||||
path: '/custom-tab-view',
|
||||
Component: CustomView,
|
||||
Component: CustomTabView,
|
||||
Tab: {
|
||||
label: customTabLabel,
|
||||
href: '/custom-tab-view',
|
||||
},
|
||||
},
|
||||
MyCustomViewWithCustomTab: {
|
||||
path: '/custom-tab-component',
|
||||
Component: CustomView,
|
||||
path: customTabViewPath,
|
||||
Component: CustomTabView2,
|
||||
Tab: CustomTabComponent,
|
||||
},
|
||||
MyCustomViewWithNestedPath: {
|
||||
path: customNestedTabViewPath,
|
||||
Component: CustomNestedTabView,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
42
test/admin/components/views/CustomTab/index.tsx
Normal file
42
test/admin/components/views/CustomTab/index.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
|
||||
import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav'
|
||||
import { type AdminViewComponent } from '../../../../../packages/payload/src/config/types'
|
||||
import { customTabViewTitle } from '../../../shared'
|
||||
|
||||
const CustomTabView: AdminViewComponent = () => {
|
||||
const { setStepNav } = useStepNav()
|
||||
|
||||
// This effect will only run one time and will allow us
|
||||
// to set the step nav to display our custom route name
|
||||
|
||||
useEffect(() => {
|
||||
setStepNav([
|
||||
{
|
||||
label: 'Custom Tab View',
|
||||
},
|
||||
])
|
||||
}, [setStepNav])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 'calc(var(--base) * 2)',
|
||||
paddingLeft: 'var(--gutter-h)',
|
||||
paddingRight: 'var(--gutter-h)',
|
||||
}}
|
||||
>
|
||||
<h1 id="custom-view-title">{customTabViewTitle}</h1>
|
||||
<p>This custom view was added through the Payload config:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>components.views[key].Component</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomTabView
|
||||
42
test/admin/components/views/CustomTab2/index.tsx
Normal file
42
test/admin/components/views/CustomTab2/index.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
|
||||
import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav'
|
||||
import { type AdminViewComponent } from '../../../../../packages/payload/src/config/types'
|
||||
import { customTabViewTitle } from '../../../shared'
|
||||
|
||||
const CustomTabView2: AdminViewComponent = () => {
|
||||
const { setStepNav } = useStepNav()
|
||||
|
||||
// This effect will only run one time and will allow us
|
||||
// to set the step nav to display our custom route name
|
||||
|
||||
useEffect(() => {
|
||||
setStepNav([
|
||||
{
|
||||
label: 'Custom Tab View',
|
||||
},
|
||||
])
|
||||
}, [setStepNav])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 'calc(var(--base) * 2)',
|
||||
paddingLeft: 'var(--gutter-h)',
|
||||
paddingRight: 'var(--gutter-h)',
|
||||
}}
|
||||
>
|
||||
<h1 id="custom-view-title">{customTabViewTitle}</h1>
|
||||
<p>This custom view was added through the Payload config:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>components.views[key].Component</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomTabView2
|
||||
42
test/admin/components/views/CustomTabNested/index.tsx
Normal file
42
test/admin/components/views/CustomTabNested/index.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
|
||||
import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav'
|
||||
import { type AdminViewComponent } from '../../../../../packages/payload/src/config/types'
|
||||
import { customNestedTabViewTitle } from '../../../shared'
|
||||
|
||||
const CustomNestedTabView: AdminViewComponent = () => {
|
||||
const { setStepNav } = useStepNav()
|
||||
|
||||
// This effect will only run one time and will allow us
|
||||
// to set the step nav to display our custom route name
|
||||
|
||||
useEffect(() => {
|
||||
setStepNav([
|
||||
{
|
||||
label: 'Custom Nested View',
|
||||
},
|
||||
])
|
||||
}, [setStepNav])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 'calc(var(--base) * 2)',
|
||||
paddingLeft: 'var(--gutter-h)',
|
||||
paddingRight: 'var(--gutter-h)',
|
||||
}}
|
||||
>
|
||||
<h1 id="custom-view-title">{customNestedTabViewTitle}</h1>
|
||||
<p>This custom view was added through the Payload config:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>components.views[key].Component</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomNestedTabView
|
||||
@@ -1,24 +1,10 @@
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav'
|
||||
import { type AdminViewComponent } from '../../../../../packages/payload/src/config/types'
|
||||
import { customViewTitle } from '../../../shared'
|
||||
|
||||
const CustomView: AdminViewComponent = () => {
|
||||
const { setStepNav } = useStepNav()
|
||||
|
||||
// This effect will only run one time and will allow us
|
||||
// to set the step nav to display our custom route name
|
||||
|
||||
useEffect(() => {
|
||||
setStepNav([
|
||||
{
|
||||
label: 'Custom View',
|
||||
},
|
||||
])
|
||||
}, [setStepNav])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 'calc(var(--base) * 2)',
|
||||
@@ -26,7 +12,7 @@ const CustomView: AdminViewComponent = () => {
|
||||
paddingRight: 'var(--gutter-h)',
|
||||
}}
|
||||
>
|
||||
<h1>Custom View</h1>
|
||||
<h1 id="custom-view-title">{customViewTitle}</h1>
|
||||
<p>This custom view was added through the Payload config:</p>
|
||||
<ul>
|
||||
<li>
|
||||
@@ -34,7 +20,6 @@ const CustomView: AdminViewComponent = () => {
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
26
test/admin/components/views/CustomViewNested/index.tsx
Normal file
26
test/admin/components/views/CustomViewNested/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
|
||||
import { type AdminViewComponent } from '../../../../../packages/payload/src/config/types'
|
||||
import { customNestedViewTitle } from '../../../shared'
|
||||
|
||||
const CustomNestedView: AdminViewComponent = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 'calc(var(--base) * 2)',
|
||||
paddingLeft: 'var(--gutter-h)',
|
||||
paddingRight: 'var(--gutter-h)',
|
||||
}}
|
||||
>
|
||||
<h1 id="custom-view-title">{customNestedViewTitle}</h1>
|
||||
<p>This custom view was added through the Payload config:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>components.views[key].Component</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomNestedView
|
||||
@@ -1,9 +1,7 @@
|
||||
import path from 'path'
|
||||
|
||||
import { mapAsync } from '../../packages/payload/src/utilities/mapAsync'
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
|
||||
import { devUser } from '../credentials'
|
||||
import { CustomViews1, customViews1Slug } from './collections/CustomViews1'
|
||||
import { CustomViews1 } from './collections/CustomViews1'
|
||||
import { CustomViews2 } from './collections/CustomViews2'
|
||||
import { Geo } from './collections/Geo'
|
||||
import { CollectionGroup1A } from './collections/Group1A'
|
||||
@@ -20,6 +18,8 @@ import BeforeLogin from './components/BeforeLogin'
|
||||
import Logout from './components/Logout'
|
||||
import CustomDefaultView from './components/views/CustomDefault'
|
||||
import CustomMinimalRoute from './components/views/CustomMinimal'
|
||||
import CustomView from './components/views/CustomView'
|
||||
import CustomNestedView from './components/views/CustomViewNested'
|
||||
import { CustomGlobalViews1 } from './globals/CustomViews1'
|
||||
import { CustomGlobalViews2 } from './globals/CustomViews2'
|
||||
import { Global } from './globals/Global'
|
||||
@@ -27,7 +27,8 @@ import { GlobalGroup1A } from './globals/Group1A'
|
||||
import { GlobalGroup1B } from './globals/Group1B'
|
||||
import { GlobalHidden } from './globals/Hidden'
|
||||
import { GlobalNoApiView } from './globals/NoApiView'
|
||||
import { customViews2Slug, noApiViewCollection, postsSlug } from './shared'
|
||||
import { seed } from './seed'
|
||||
import { customNestedViewPath, customViewPath } from './shared'
|
||||
|
||||
export interface Post {
|
||||
createdAt: Date
|
||||
@@ -51,14 +52,23 @@ export default buildConfigWithDefaults({
|
||||
views: {
|
||||
// Dashboard: CustomDashboardView,
|
||||
// Account: CustomAccountView,
|
||||
CustomMinimalRoute: {
|
||||
CustomMinimalView: {
|
||||
path: '/custom-minimal-view',
|
||||
Component: CustomMinimalRoute,
|
||||
},
|
||||
CustomDefaultRoute: {
|
||||
CustomDefaultView: {
|
||||
path: '/custom-default-view',
|
||||
Component: CustomDefaultView,
|
||||
},
|
||||
CustomView: {
|
||||
path: customViewPath,
|
||||
exact: true,
|
||||
Component: CustomView,
|
||||
},
|
||||
CustomNestedView: {
|
||||
path: customNestedViewPath,
|
||||
Component: CustomNestedView,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -97,56 +107,5 @@ export default buildConfigWithDefaults({
|
||||
GlobalGroup1A,
|
||||
GlobalGroup1B,
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
|
||||
await mapAsync([...Array(11)], async () => {
|
||||
await payload.create({
|
||||
collection: postsSlug,
|
||||
data: {
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: customViews1Slug,
|
||||
data: {
|
||||
title: 'Custom View',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: customViews2Slug,
|
||||
data: {
|
||||
title: 'Custom View',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'geo',
|
||||
data: {
|
||||
point: [7, -7],
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'geo',
|
||||
data: {
|
||||
point: [5, -5],
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: noApiViewCollection,
|
||||
data: {},
|
||||
})
|
||||
},
|
||||
onInit: seed,
|
||||
})
|
||||
|
||||
@@ -22,7 +22,15 @@ import { AdminUrlUtil } from '../helpers/adminUrlUtil'
|
||||
import { initPayloadE2E } from '../helpers/configHelpers'
|
||||
import {
|
||||
customEditLabel,
|
||||
customNestedTabViewPath,
|
||||
customNestedTabViewTitle,
|
||||
customNestedViewPath,
|
||||
customNestedViewTitle,
|
||||
customTabLabel,
|
||||
customTabViewPath,
|
||||
customTabViewTitle,
|
||||
customViewPath,
|
||||
customViewTitle,
|
||||
customViews2Slug,
|
||||
globalSlug,
|
||||
group1Collection1Slug,
|
||||
@@ -164,6 +172,44 @@ describe('admin', () => {
|
||||
await expect(page.locator('.not-found')).toContainText('Nothing found')
|
||||
})
|
||||
|
||||
test('should render custom view', async () => {
|
||||
await page.goto(`${serverURL}/admin${customViewPath}`)
|
||||
const pageURL = page.url()
|
||||
const pathname = new URL(pageURL).pathname
|
||||
expect(pathname).toEqual(`/admin${customViewPath}`)
|
||||
await expect(page.locator('h1#custom-view-title')).toContainText(customViewTitle)
|
||||
})
|
||||
|
||||
test('should render custom nested view', async () => {
|
||||
await page.goto(`${serverURL}/admin${customNestedViewPath}`)
|
||||
const pageURL = page.url()
|
||||
const pathname = new URL(pageURL).pathname
|
||||
expect(pathname).toEqual(`/admin${customNestedViewPath}`)
|
||||
await expect(page.locator('h1#custom-view-title')).toContainText(customNestedViewTitle)
|
||||
})
|
||||
|
||||
test('collection - should render custom tab view', async () => {
|
||||
await page.goto(customViewsURL.create)
|
||||
await page.locator('#field-title').fill('Test')
|
||||
await saveDocAndAssert(page)
|
||||
const pageURL = page.url()
|
||||
const customViewURL = `${pageURL}${customTabViewPath}`
|
||||
await page.goto(customViewURL)
|
||||
expect(page.url()).toEqual(customViewURL)
|
||||
await expect(page.locator('h1#custom-view-title')).toContainText(customTabViewTitle)
|
||||
})
|
||||
|
||||
test('collection - should render custom nested tab view', async () => {
|
||||
await page.goto(customViewsURL.create)
|
||||
await page.locator('#field-title').fill('Test')
|
||||
await saveDocAndAssert(page)
|
||||
const pageURL = page.url()
|
||||
const customNestedTabViewURL = `${pageURL}${customNestedTabViewPath}`
|
||||
await page.goto(customNestedTabViewURL)
|
||||
expect(page.url()).toEqual(customNestedTabViewURL)
|
||||
await expect(page.locator('h1#custom-view-title')).toContainText(customNestedTabViewTitle)
|
||||
})
|
||||
|
||||
test('collection - should render custom tab label', async () => {
|
||||
await page.goto(customViewsURL.create)
|
||||
await page.locator('#field-title').fill('Test')
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
|
||||
|
||||
import CustomTabComponent from '../components/CustomTabComponent'
|
||||
import CustomDefaultEditView from '../components/views/CustomDefaultEdit'
|
||||
import CustomDefaultEditView from '../components/views/CustomEditDefault'
|
||||
import CustomView from '../components/views/CustomTab'
|
||||
import CustomVersionsView from '../components/views/CustomVersions'
|
||||
import CustomView from '../components/views/CustomView'
|
||||
|
||||
export const CustomGlobalViews2: GlobalConfig = {
|
||||
slug: 'custom-global-views-two',
|
||||
|
||||
59
test/admin/seed/index.ts
Normal file
59
test/admin/seed/index.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { Config } from '../../../packages/payload/src/config/types'
|
||||
|
||||
import { mapAsync } from '../../../packages/payload/src/utilities/mapAsync'
|
||||
import { devUser } from '../../credentials'
|
||||
import { customViews1Slug } from '../collections/CustomViews1'
|
||||
import { customViews2Slug, noApiViewCollection, postsSlug } from '../shared'
|
||||
|
||||
export const seed: Config['onInit'] = async (payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
|
||||
await mapAsync([...Array(11)], async () => {
|
||||
await payload.create({
|
||||
collection: postsSlug,
|
||||
data: {
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: customViews1Slug,
|
||||
data: {
|
||||
title: 'Custom View',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: customViews2Slug,
|
||||
data: {
|
||||
title: 'Custom View',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'geo',
|
||||
data: {
|
||||
point: [7, -7],
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'geo',
|
||||
data: {
|
||||
point: [5, -5],
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: noApiViewCollection,
|
||||
data: {},
|
||||
})
|
||||
}
|
||||
@@ -16,8 +16,24 @@ export const noApiViewCollection = 'collection-no-api-view'
|
||||
|
||||
export const noApiViewGlobal = 'global-no-api-view'
|
||||
|
||||
export const customViewPath = '/custom-view'
|
||||
|
||||
export const customViewTitle = 'Custom View'
|
||||
|
||||
export const customNestedViewPath = `${customViewPath}/nested-view`
|
||||
|
||||
export const customNestedViewTitle = 'Custom Nested View'
|
||||
|
||||
export const customViews2Slug = 'custom-views-two'
|
||||
|
||||
export const customEditLabel = 'Custom Edit Label'
|
||||
|
||||
export const customTabLabel = 'Custom Tab Component'
|
||||
export const customTabLabel = 'Custom Tab Label'
|
||||
|
||||
export const customTabViewPath = '/custom-tab-component'
|
||||
|
||||
export const customTabViewTitle = 'Custom View With Tab Component'
|
||||
|
||||
export const customNestedTabViewPath = `${customTabViewPath}/nested-view`
|
||||
|
||||
export const customNestedTabViewTitle = 'Custom Nested Tab View'
|
||||
|
||||
@@ -22,7 +22,7 @@ export default buildConfigWithDefaults({
|
||||
delete: () => true,
|
||||
update: () => true,
|
||||
},
|
||||
endpoints: [...(collectionEndpoints || [])],
|
||||
endpoints: collectionEndpoints,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -45,7 +45,7 @@ export default buildConfigWithDefaults({
|
||||
globals: [
|
||||
{
|
||||
slug: globalSlug,
|
||||
endpoints: [...(globalEndpoints || [])],
|
||||
endpoints: globalEndpoints,
|
||||
fields: [],
|
||||
},
|
||||
{
|
||||
@@ -60,7 +60,7 @@ export default buildConfigWithDefaults({
|
||||
],
|
||||
},
|
||||
],
|
||||
endpoints: [...(endpoints || [])],
|
||||
endpoints,
|
||||
admin: {
|
||||
webpack: (config) => {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user