Merge branch '2.0' of github.com:payloadcms/payload into 2.0

This commit is contained in:
Dan Ribbens
2023-09-15 17:11:22 -04:00
66 changed files with 2102 additions and 1056 deletions

View File

@@ -32,6 +32,7 @@ module.exports = {
{
files: ['**/int.spec.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'jest/prefer-strict-equal': 'off',

View File

@@ -6,6 +6,7 @@ import type { ReadOnlyCollection, RestrictedVersion } from './payload-types'
import payload from '../../packages/payload/src'
import wait from '../../packages/payload/src/utilities/wait'
import { openMainMenu } from '../helpers'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
import {
@@ -99,6 +100,7 @@ describe('access control', () => {
test('should not show in nav', async () => {
await page.goto(url.admin)
await openMainMenu(page)
await expect(page.locator('.nav >> a:has-text("Restricteds")')).toHaveCount(0)
})
@@ -137,7 +139,9 @@ describe('access control', () => {
test('should show in nav', async () => {
await page.goto(url.admin)
await expect(page.locator(`.nav a[href="/admin/collections/${readOnlySlug}"]`)).toHaveCount(1)
await expect(
page.locator(`.main-menu a[href="/admin/collections/${readOnlySlug}"]`),
).toHaveCount(1)
})
test('should have collection url', async () => {

View File

@@ -4,17 +4,18 @@ import type { Post, RelyOnRequestHeader, Restricted } from './payload-types'
import payload from '../../packages/payload/src'
import { Forbidden } from '../../packages/payload/src/errors'
import { initPayloadTest } from '../helpers/configHelpers'
import { requestHeaders } from './config'
import {
firstArrayText,
hiddenAccessSlug,
hiddenFieldsSlug,
relyOnRequestHeadersSlug,
requestHeaders,
restrictedSlug,
restrictedVersionsSlug,
secondArrayText,
siblingDataSlug,
slug,
} from './config'
import { firstArrayText, secondArrayText } from './shared'
} from './shared'
describe('Access Control', () => {
let post1: Post

View File

@@ -2,11 +2,9 @@ import React from 'react'
import { NavLink } from 'react-router-dom'
// As this is the demo project, we import our dependencies from the `src` directory.
import Chevron from '../../../../packages/payload/src/admin/components/icons/Chevron'
import { useConfig } from '../../../../packages/payload/src/admin/components/utilities/Config'
// In your projects, you can import as follows:
// import { Chevron } from 'payload/components';
// import { useConfig } from 'payload/components/utilities';
const baseClass = 'after-nav-links'
@@ -17,26 +15,35 @@ const AfterNavLinks: React.FC = () => {
} = useConfig()
return (
<div className={baseClass}>
<span className="nav__label">Custom Routes</span>
<nav>
<div
className={baseClass}
style={{
display: 'flex',
flexDirection: 'column',
gap: 'calc(var(--base) / 4)',
}}
>
<h4 className="nav__label" style={{ color: 'var(--theme-elevation-400)', margin: 0 }}>
Custom Routes
</h4>
<h4 className="nav__link" style={{ margin: 0 }}>
<NavLink
activeClassName="active"
className="nav__link"
style={{ textDecoration: 'none' }}
to={`${adminRoute}/custom-default-route`}
>
<Chevron />
Default Template
</NavLink>
</h4>
<h4 className="nav__link" style={{ margin: 0 }}>
<NavLink
activeClassName="active"
className="nav__link"
style={{ textDecoration: 'none' }}
to={`${adminRoute}/custom-minimal-route`}
>
<Chevron />
Minimal Template
</NavLink>
</nav>
</h4>
</div>
)
}

View File

@@ -49,14 +49,21 @@ const CustomDefaultRoute: AdminView = ({ canAccessAdmin, user }) => {
title="Custom Route with Default Template"
/>
<Eyebrow />
<h1>Custom Route</h1>
<p>
Here is a custom route that was added in the Payload config. It uses the Default Template,
so the sidebar is rendered.
</p>
<Button buttonStyle="secondary" el="link" to={`${adminRoute}`}>
Go to Dashboard
</Button>
<div
style={{
paddingRight: 'var(--gutter-h)',
paddingLeft: 'var(--gutter-h)',
}}
>
<h1>Custom Route</h1>
<p>
Here is a custom route that was added in the Payload config. It uses the Default Template,
so the sidebar is rendered.
</p>
<Button buttonStyle="secondary" el="link" to={`${adminRoute}`}>
Go to Dashboard
</Button>
</div>
</DefaultTemplate>
)
}

View File

@@ -0,0 +1,98 @@
import React, { Fragment, useEffect } from 'react'
import { Redirect, useParams } from 'react-router-dom'
import Button from '../../../../../packages/payload/src/admin/components/elements/Button'
import Eyebrow from '../../../../../packages/payload/src/admin/components/elements/Eyebrow'
import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav'
import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config'
import { type CustomAdminView } from '../../../../../packages/payload/src/config/types'
const CustomEditView: CustomAdminView = ({ canAccessAdmin, collection, global, user }) => {
const {
routes: { admin: adminRoute },
} = useConfig()
const params = useParams()
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 Edit View',
},
])
}, [setStepNav])
// If an unauthorized user tries to navigate straight to this page,
// Boot 'em out
if (!user || (user && !canAccessAdmin)) {
return <Redirect to={`${adminRoute}/unauthorized`} />
}
let versionsRoute = ''
let customRoute = ''
if (collection) {
versionsRoute = `${adminRoute}/collections/${collection?.slug}/${params.id}/versions`
customRoute = `${adminRoute}/collections/${collection?.slug}/${params.id}/custom`
}
if (global) {
versionsRoute = `${adminRoute}/globals/${global?.slug}/versions`
customRoute = `${adminRoute}/globals/${global?.slug}/custom`
}
return (
<Fragment>
<Eyebrow />
<div
style={{
paddingLeft: 'var(--gutter-h)',
paddingRight: 'var(--gutter-h)',
}}
>
<h1>Custom Edit View</h1>
<p>This custom edit view was added through one of the following Payload configs:</p>
<ul>
<li>
<code>components.views.Edit</code>
<p>
{'This takes precedence over the default edit view, '}
<b>as well as all nested views like versions.</b>
</p>
</li>
<li>
<code>components.views.Edit.Default</code>
<p>
{'This allows you to override only the default edit view, but '}
<b>
<em>not</em>
</b>
{' any nested views like versions, etc.'}
</p>
</li>
<li>
<code>components.views.Edit.Default.Component</code>
<p>
This is the most granular override, allowing you to override only the default edit
view&apos;s Component, and its other properties like path and label.
</p>
</li>
</ul>
<Button buttonStyle="primary" el="link" to={versionsRoute}>
Custom Versions
</Button>
&nbsp; &nbsp; &nbsp;
<Button buttonStyle="secondary" el="link" to={customRoute}>
Custom View
</Button>
</div>
</Fragment>
)
}
export default CustomEditView

View File

@@ -0,0 +1,85 @@
import React, { Fragment, useEffect } from 'react'
import { Redirect, useParams } from 'react-router-dom'
import Button from '../../../../../packages/payload/src/admin/components/elements/Button'
import Eyebrow from '../../../../../packages/payload/src/admin/components/elements/Eyebrow'
import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav'
import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config'
import { type CustomAdminView } from '../../../../../packages/payload/src/config/types'
const CustomVersionsView: CustomAdminView = ({ canAccessAdmin, collection, global, user }) => {
const {
routes: { admin: adminRoute },
} = useConfig()
const params = useParams()
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 Versions View',
},
])
}, [setStepNav])
// If an unauthorized user tries to navigate straight to this page,
// Boot 'em out
if (!user || (user && !canAccessAdmin)) {
return <Redirect to={`${adminRoute}/unauthorized`} />
}
let backURL = adminRoute
if (collection) {
backURL = `${adminRoute}/collections/${collection?.slug}/${params.id}`
}
if (global) {
backURL = `${adminRoute}/globals/${global?.slug}`
}
return (
<Fragment>
<Eyebrow />
<div
style={{
paddingLeft: 'var(--gutter-h)',
paddingRight: 'var(--gutter-h)',
}}
>
<h1>Custom Versions View</h1>
<p>This custom versions view was added through one of the following Payload configs:</p>
<ul>
<li>
<code>components.views.Versions</code>
<p>
{'This takes precedence over the default versions view, '}
<b>as well as all nested views like /versions/:id.</b>
</p>
</li>
<li>
<code>components.views.Edit.versions</code>
<p>Same as above.</p>
</li>
<li>
<code>components.views.Edit.versions.Component</code>
</li>
<p>
This is the most granular override, allowing you to override only the default versions
view&apos;s Component, and its other properties like path and label.
</p>
</ul>
<Button buttonStyle="secondary" el="link" to={backURL}>
Back
</Button>
</div>
</Fragment>
)
}
export default CustomVersionsView

View File

@@ -0,0 +1,67 @@
import React, { Fragment, useEffect } from 'react'
import { useParams } from 'react-router-dom'
import Button from '../../../../../packages/payload/src/admin/components/elements/Button'
import Eyebrow from '../../../../../packages/payload/src/admin/components/elements/Eyebrow'
import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav'
import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config'
import { type CustomAdminView } from '../../../../../packages/payload/src/config/types'
const CustomView: CustomAdminView = ({ collection, global }) => {
const {
routes: { admin: adminRoute },
} = useConfig()
const params = useParams()
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])
let backURL = ''
let versionsRoute = ''
if (collection) {
backURL = `${adminRoute}/collections/${collection?.slug}/${params.id}`
versionsRoute = `${adminRoute}/collections/${collection?.slug}/${params.id}/versions`
}
if (global) {
backURL = `${adminRoute}/globals/${global?.slug}`
versionsRoute = `${adminRoute}/globals/${global?.slug}/versions`
}
return (
<Fragment>
<Eyebrow />
<div
style={{
paddingLeft: 'var(--gutter-h)',
paddingRight: 'var(--gutter-h)',
}}
>
<h1>Custom View</h1>
<p>This custom view was added through the Payload config:</p>
<ul>
<li>
<code>components.views[key].Component</code>
</li>
</ul>
<Button buttonStyle="secondary" el="link" to={backURL}>
Back
</Button>
</div>
</Fragment>
)
}
export default CustomView

View File

@@ -9,8 +9,11 @@ import BeforeLogin from './components/BeforeLogin'
import DemoUIFieldCell from './components/DemoUIField/Cell'
import DemoUIFieldField from './components/DemoUIField/Field'
import Logout from './components/Logout'
import CustomDefaultRoute from './components/views/CustomDefault'
import CustomMinimalRoute from './components/views/CustomMinimal'
import CustomDefaultRoute from './components/routes/CustomDefault'
import CustomMinimalRoute from './components/routes/CustomMinimal'
import CustomEditView from './components/views/CustomEdit'
import CustomVersionsView from './components/views/CustomVersions'
import CustomView from './components/views/CustomView'
import { globalSlug, slug } from './shared'
export interface Post {
@@ -131,6 +134,48 @@ export default buildConfigWithDefaults({
},
],
},
{
slug: 'custom-views-one',
versions: true,
admin: {
components: {
views: {
Edit: CustomEditView,
},
},
},
fields: [
{
name: 'title',
type: 'text',
},
],
},
{
slug: 'custom-views-two',
versions: true,
admin: {
components: {
views: {
Edit: {
Default: CustomEditView,
Versions: CustomVersionsView,
MyCustomView: {
path: '/custom',
Component: CustomView,
label: 'Custom',
},
},
},
},
},
fields: [
{
name: 'title',
type: 'text',
},
],
},
{
slug: 'group-one-collection-ones',
admin: {
@@ -218,6 +263,49 @@ export default buildConfigWithDefaults({
},
],
},
{
slug: 'custom-global-views-one',
versions: true,
admin: {
components: {
views: {
Edit: CustomEditView,
},
},
},
fields: [
{
name: 'title',
type: 'text',
},
],
},
{
slug: 'custom-global-views-two',
versions: true,
admin: {
components: {
views: {
Edit: {
Default: CustomEditView,
Versions: CustomVersionsView,
MyCustomView: {
path: '/custom',
Component: CustomView,
label: 'Custom',
},
},
},
},
},
fields: [
{
name: 'title',
type: 'text',
},
],
},
{
slug: 'group-globals-one',
admin: {
@@ -262,6 +350,20 @@ export default buildConfigWithDefaults({
})
})
await payload.create({
collection: 'custom-views-one',
data: {
title: 'title',
},
})
await payload.create({
collection: 'custom-views-two',
data: {
title: 'title',
},
})
await payload.create({
collection: 'geo',
data: {

View File

@@ -8,7 +8,7 @@ import type { Post } from './config'
import payload from '../../packages/payload/src'
import { mapAsync } from '../../packages/payload/src/utilities/mapAsync'
import wait from '../../packages/payload/src/utilities/wait'
import { saveDocAndAssert, saveDocHotkeyAndAssert } from '../helpers'
import { openMainMenu, saveDocAndAssert, saveDocHotkeyAndAssert } from '../helpers'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
import { globalSlug, slug } from './shared'
@@ -45,27 +45,29 @@ describe('admin', () => {
describe('Nav', () => {
test('should nav to collection - sidebar', async () => {
await page.goto(url.admin)
const collectionLink = page.locator(`#nav-${slug}`)
await collectionLink.click()
await openMainMenu(page)
await page.locator(`#nav-${slug}`).click()
expect(page.url()).toContain(url.list)
})
test('should nav to a global - sidebar', async () => {
await page.goto(url.admin)
await openMainMenu(page)
await page.locator(`#nav-global-${globalSlug}`).click()
expect(page.url()).toContain(url.global(globalSlug))
})
test('should navigate to collection - card', async () => {
await page.goto(url.admin)
await wait(200)
await page.locator(`#card-${slug}`).click()
expect(page.url()).toContain(url.list)
})
test('should collapse and expand collection groups', async () => {
await page.goto(url.admin)
await openMainMenu(page)
const navGroup = page.locator('#nav-group-One .nav-group__toggle')
const link = page.locator('#nav-group-one-collection-ones')
@@ -81,6 +83,8 @@ describe('admin', () => {
test('should collapse and expand globals groups', async () => {
await page.goto(url.admin)
await openMainMenu(page)
const navGroup = page.locator('#nav-group-Group .nav-group__toggle')
const link = page.locator('#nav-global-group-globals-one')
@@ -96,12 +100,9 @@ describe('admin', () => {
test('should save nav group collapse preferences', async () => {
await page.goto(url.admin)
const navGroup = page.locator('#nav-group-One .nav-group__toggle')
await navGroup.click()
await openMainMenu(page)
await page.locator('#nav-group-One .nav-group__toggle').click()
await page.goto(url.admin)
const link = page.locator('#nav-group-one-collection-ones')
await expect(link).toBeHidden()
})
@@ -189,13 +190,11 @@ describe('admin', () => {
})
test('should delete existing', async () => {
const { id, ...post } = await createPost()
const { id, title } = await createPost()
await page.goto(url.edit(id))
await page.locator('#action-delete').click()
await page.locator('#confirm-delete').click()
await expect(page.locator(`text=Post en "${post.title}" successfully deleted.`)).toBeVisible()
await expect(page.locator(`text=Post en "${title}" successfully deleted.`)).toBeVisible()
expect(page.url()).toContain(url.list)
})
@@ -724,7 +723,8 @@ describe('admin', () => {
describe('custom css', () => {
test('should see custom css in admin UI', async () => {
await page.goto(url.admin)
const navControls = page.locator('.nav__controls')
await openMainMenu(page)
const navControls = page.locator('.main-menu__controls')
await expect(navControls).toHaveCSS('font-family', 'monospace')
})
})

View File

@@ -1,7 +1,8 @@
.nav__controls {
.main-menu__controls {
font-family: monospace;
background-image: url('/placeholder.png');
}
.nav__controls:before {
.main-menu__controls:before {
content: 'custom-css';
}

View File

@@ -1,5 +1,5 @@
import jwtDecode from 'jwt-decode';
import { GraphQLClient } from 'graphql-request';
import { GraphQLClient } from 'graphql-request'
import jwtDecode from 'jwt-decode'
import type { User } from '../../packages/payload/src/auth'

View File

@@ -70,7 +70,7 @@ describe('globals', () => {
describe('local', () => {
it('should save empty json objects', async () => {
const createdJSON = await payload.updateGlobal({
const createdJSON: any = await payload.updateGlobal({
slug,
data: {
json: {

View File

@@ -55,3 +55,22 @@ export async function saveDocAndAssert(page: Page, selector = '#action-save'): P
await expect(page.locator('.Toastify')).toContainText('successfully')
expect(page.url()).not.toContain('create')
}
export async function openMainMenu(page: Page): Promise<void> {
await page.locator('.payload__modal-toggler--slug-main-menu').click()
const mainMenuModal = page.locator('#main-menu')
await expect(mainMenuModal).toBeVisible()
}
export async function closeMainMenu(page: Page): Promise<void> {
await page.locator('.payload__modal-toggler--slug-main-menu--is-open').click()
const mainMenuModal = page.locator('#main-menu')
await expect(mainMenuModal).toBeHidden()
}
export async function changeLocale(page: Page, newLocale: string) {
await openMainMenu(page)
await page.locator('.localizer >> button').first().click()
await page.locator(`.localizer >> a:has-text("${newLocale}")`).click()
expect(page.url()).toContain(`locale=${newLocale}`)
}

View File

@@ -34,6 +34,7 @@ export async function initPayloadTest(options: Options): Promise<{ serverURL: st
...(options.init || {}),
}
process.env.PAYLOAD_DROP_DATABASE = 'true'
process.env.NODE_ENV = 'test'
process.env.PAYLOAD_CONFIG_PATH = path.resolve(options.__dirname, './config.ts')

View File

@@ -1,3 +1,5 @@
import type { NestedAfterReadHook } from './payload-types'
import payload from '../../packages/payload/src'
import { AuthenticationError } from '../../packages/payload/src/errors'
import { devUser, regularUser } from '../credentials'
@@ -77,7 +79,7 @@ describe('Hooks', () => {
})
it('should save data generated with afterRead hooks in nested field structures', async () => {
const document = await payload.create({
const document: NestedAfterReadHook = await payload.create({
collection: nestedAfterReadHooksSlug,
data: {
text: 'ok',

View File

@@ -5,7 +5,7 @@ import { expect, test } from '@playwright/test'
import type { LocalizedPost } from './payload-types'
import payload from '../../packages/payload/src'
import { saveDocAndAssert } from '../helpers'
import { changeLocale, saveDocAndAssert } from '../helpers'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadTest } from '../helpers/configHelpers'
import { englishTitle, localizedPostsSlug, spanishLocale } from './shared'
@@ -29,6 +29,7 @@ const arabicTitle = 'arabic title'
const description = 'description'
let page: Page
describe('Localization', () => {
beforeAll(async ({ browser }) => {
const { serverURL } = await initPayloadTest({
@@ -52,7 +53,7 @@ describe('Localization', () => {
await saveDocAndAssert(page)
// Change back to English
await changeLocale('es')
await changeLocale(page, 'es')
// Localized field should not be populated
await expect(page.locator('#field-title')).toBeEmpty()
@@ -60,7 +61,7 @@ describe('Localization', () => {
await fillValues({ title: spanishTitle, description })
await saveDocAndAssert(page)
await changeLocale(defaultLocale)
await changeLocale(page, defaultLocale)
// Expect english title
await expect(page.locator('#field-title')).toHaveValue(title)
@@ -73,13 +74,13 @@ describe('Localization', () => {
const newLocale = 'es'
// Change to Spanish
await changeLocale(newLocale)
await changeLocale(page, newLocale)
await fillValues({ title: spanishTitle, description })
await saveDocAndAssert(page)
// Change back to English
await changeLocale(defaultLocale)
await changeLocale(page, defaultLocale)
// Localized field should not be populated
await expect(page.locator('#field-title')).toBeEmpty()
@@ -101,13 +102,13 @@ describe('Localization', () => {
const newLocale = 'ar'
// Change to Arabic
await changeLocale(newLocale)
await changeLocale(page, newLocale)
await fillValues({ title: arabicTitle, description })
await saveDocAndAssert(page)
// Change back to English
await changeLocale(defaultLocale)
await changeLocale(page, defaultLocale)
// Localized field should not be populated
await expect(page.locator('#field-title')).toBeEmpty()
@@ -125,16 +126,16 @@ describe('Localization', () => {
})
describe('localized duplicate', () => {
let id
beforeAll(async () => {
test('should duplicate data for all locales', async () => {
const localizedPost = await payload.create({
collection: localizedPostsSlug,
data: {
title: englishTitle,
},
})
id = localizedPost.id
const id = localizedPost.id.toString()
await payload.update({
collection: localizedPostsSlug,
id,
@@ -143,17 +144,12 @@ describe('Localization', () => {
title: spanishTitle,
},
})
})
test('should duplicate data for all locales', async () => {
await page.goto(url.edit(id))
await page.locator('.btn.duplicate').first().click()
await expect(page.locator('.Toastify')).toContainText('successfully')
await expect(page.locator('#field-title')).toHaveValue(englishTitle)
await changeLocale(spanishLocale)
await changeLocale(page, spanishLocale)
await expect(page.locator('#field-title')).toHaveValue(spanishTitle)
})
})
@@ -165,9 +161,3 @@ async function fillValues(data: Partial<LocalizedPost>) {
if (titleVal) await page.locator('#field-title').fill(titleVal)
if (descVal) await page.locator('#field-description').fill(descVal)
}
async function changeLocale(newLocale: string) {
await page.locator('.localizer >> button').first().click()
await page.locator(`.localizer >> a:has-text("${newLocale}")`).click()
expect(page.url()).toContain(`locale=${newLocale}`)
}

View File

@@ -17,10 +17,10 @@ import {
relationSpanishTitle,
relationSpanishTitle2,
relationshipLocalizedSlug,
withLocalizedRelSlug,
withRequiredLocalizedFields,
spanishLocale,
spanishTitle,
withLocalizedRelSlug,
withRequiredLocalizedFields,
} from './shared'
const collection = localizedPostsSlug
@@ -36,6 +36,7 @@ describe('Localization', () => {
;({ serverURL } = await initPayloadTest({ __dirname, init: { local: false } }))
config = await configPromise
// @ts-expect-error Force typing
post1 = await payload.create({
collection,
data: {
@@ -43,6 +44,7 @@ describe('Localization', () => {
},
})
// @ts-expect-error Force typing
postWithLocalizedData = await payload.create({
collection,
data: {
@@ -89,7 +91,7 @@ describe('Localization', () => {
expect(updated.title).toEqual(spanishTitle)
const localized = await payload.findByID({
const localized: any = await payload.findByID({
collection,
id: post1.id,
locale: 'all',
@@ -111,7 +113,7 @@ describe('Localization', () => {
expect(updated.title).toEqual(englishTitle)
const localizedFallback = await payload.findByID({
const localizedFallback: any = await payload.findByID({
collection,
id: post1.id,
locale: 'all',
@@ -131,6 +133,7 @@ describe('Localization', () => {
},
})
// @ts-expect-error Force typing
localizedPost = await payload.update({
collection,
id,
@@ -171,7 +174,7 @@ describe('Localization', () => {
})
it('all locales', async () => {
const localized = await payload.findByID({
const localized: any = await payload.findByID({
collection,
locale: 'all',
id: localizedPost.id,
@@ -191,8 +194,8 @@ describe('Localization', () => {
},
})
expect(result.docs.map(({ id }) => id)).toContain(localizedPost.id);
});
expect(result.docs.map(({ id }) => id)).toContain(localizedPost.id)
})
it('by localized field value - alternate locale', async () => {
const result = await payload.find({
@@ -205,8 +208,8 @@ describe('Localization', () => {
},
})
expect(result.docs.map(({ id }) => id)).toContain(localizedPost.id);
});
expect(result.docs.map(({ id }) => id)).toContain(localizedPost.id)
})
it('by localized field value - opposite locale???', async () => {
const result = await payload.find({
@@ -219,10 +222,10 @@ describe('Localization', () => {
},
})
expect(result.docs.map(({ id }) => id)).toContain(localizedPost.id);
});
});
});
expect(result.docs.map(({ id }) => id)).toContain(localizedPost.id)
})
})
})
describe('Localized Relationship', () => {
let localizedRelation: LocalizedPost
@@ -243,6 +246,7 @@ describe('Localization', () => {
},
})
// @ts-expect-error Force typing
withRelationship = await payload.create({
collection: withLocalizedRelSlug,
data: {
@@ -304,15 +308,16 @@ describe('Localization', () => {
it('populates relationships with all locales', async () => {
// the relationship fields themselves are localized on this collection
const result = await payload.find({
const result: any = await payload.find({
collection: relationshipLocalizedSlug,
locale: 'all',
depth: 1,
})
expect((result.docs[0].relationship as any).en.id).toBeDefined()
expect((result.docs[0].relationshipHasMany as any).en[0].id).toBeDefined()
expect((result.docs[0].relationMultiRelationTo as any).en.value.id).toBeDefined()
expect((result.docs[0].relationMultiRelationToHasMany as any).en[0].value.id).toBeDefined()
expect(result.docs[0].relationship.en.id).toBeDefined()
expect(result.docs[0].relationshipHasMany.en[0].id).toBeDefined()
expect(result.docs[0].relationMultiRelationTo.en.value.id).toBeDefined()
expect(result.docs[0].relationMultiRelationToHasMany.en[0].value.id).toBeDefined()
expect(result.docs[0].arrayField.en[0].nestedRelation.id).toBeDefined()
})
})
@@ -328,7 +333,7 @@ describe('Localization', () => {
},
})
expect(result.docs.map(({ id }) => id)).toContain(withRelationship.id);
expect(result.docs.map(({ id }) => id)).toContain(withRelationship.id)
// Second relationship
const result2 = await payload.find({
@@ -340,8 +345,8 @@ describe('Localization', () => {
},
})
expect(result2.docs.map(({ id }) => id)).toContain(withRelationship.id);
});
expect(result2.docs.map(({ id }) => id)).toContain(withRelationship.id)
})
it('specific locale', async () => {
const result = await payload.find({
@@ -395,7 +400,7 @@ describe('Localization', () => {
},
})
expect(result.docs.map(({ id }) => id)).toContain(withRelationship.id);
expect(result.docs.map(({ id }) => id)).toContain(withRelationship.id)
// First relationship - spanish
const result2 = await queryRelation({
@@ -404,7 +409,7 @@ describe('Localization', () => {
},
})
expect(result2.docs.map(({ id }) => id)).toContain(withRelationship.id);
expect(result2.docs.map(({ id }) => id)).toContain(withRelationship.id)
// Second relationship - english
const result3 = await queryRelation({
@@ -413,7 +418,7 @@ describe('Localization', () => {
},
})
expect(result3.docs.map(({ id }) => id)).toContain(withRelationship.id);
expect(result3.docs.map(({ id }) => id)).toContain(withRelationship.id)
// Second relationship - spanish
const result4 = await queryRelation({
@@ -509,7 +514,7 @@ describe('Localization', () => {
describe('Localized - arrays with nested localized fields', () => {
it('should allow moving rows and retain existing row locale data', async () => {
const globalArray = await payload.findGlobal({
const globalArray: any = await payload.findGlobal({
slug: 'global-array',
})
@@ -753,7 +758,7 @@ async function createLocalizedPost(data: {
[spanishLocale]: string
}
}): Promise<LocalizedPost> {
const localizedRelation = await payload.create({
const localizedRelation: any = await payload.create({
collection,
data: {
title: data.title.en,

View File

@@ -2,6 +2,7 @@ import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { closeMainMenu, openMainMenu } from '../helpers'
import { initPayloadE2E } from '../helpers/configHelpers'
const { beforeAll, describe } = test
@@ -19,6 +20,8 @@ describe('refresh-permissions', () => {
test('should show test global immediately after allowing access', async () => {
await page.goto(`${serverURL}/admin/globals/settings`)
await openMainMenu(page)
// Ensure that we have loaded accesses by checking that settings collection
// at least is visible in the menu.
await expect(page.locator('#nav-global-settings')).toBeVisible()
@@ -26,10 +29,14 @@ describe('refresh-permissions', () => {
// Test collection should be hidden at first.
await expect(page.locator('#nav-global-test')).toBeHidden()
await closeMainMenu(page)
// Allow access to test global.
await page.locator('.custom-checkbox:has(#field-test) input').check()
await page.locator('#action-save').click()
await openMainMenu(page)
// Now test collection should appear in the menu.
await expect(page.locator('#nav-global-test')).toBeVisible()
})

View File

@@ -28,6 +28,7 @@ import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import wait from '../../packages/payload/src/utilities/wait'
import { changeLocale } from '../helpers'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
import { autosaveSlug, draftSlug } from './shared'
@@ -130,25 +131,19 @@ describe('versions', () => {
await page.locator('#field-description').fill(description)
await wait(500)
await changeLocale(spanishLocale)
await changeLocale(page, spanishLocale)
await page.locator('#field-title').fill(spanishTitle)
await wait(500)
await changeLocale(locale)
await changeLocale(page, locale)
await page.locator('#field-description').fill(newDescription)
await wait(500)
await changeLocale(spanishLocale)
await changeLocale(page, spanishLocale)
await wait(500)
await page.reload()
await expect(page.locator('#field-title')).toHaveValue(spanishTitle)
await expect(page.locator('#field-description')).toHaveValue(newDescription)
})
})
async function changeLocale(newLocale: string) {
await page.locator('.localizer >> button').first().click()
await page.locator(`.localizer >> a:has-text("${newLocale}")`).click()
expect(page.url()).toContain(`locale=${newLocale}`)
}
})