feat!: on demand rsc (#8364)
Currently, Payload renders all custom components on initial compile of the admin panel. This is problematic for two key reasons: 1. Custom components do not receive contextual data, i.e. fields do not receive their field data, edit views do not receive their document data, etc. 2. Components are unnecessarily rendered before they are used This was initially required to support React Server Components within the Payload Admin Panel for two key reasons: 1. Fields can be dynamically rendered within arrays, blocks, etc. 2. Documents can be recursively rendered within a "drawer" UI, i.e. relationship fields 3. Payload supports server/client component composition In order to achieve this, components need to be rendered on the server and passed as "slots" to the client. Currently, the pattern for this is to render custom server components in the "client config". Then when a view or field is needed to be rendered, we first check the client config for a "pre-rendered" component, otherwise render our client-side fallback component. But for the reasons listed above, this pattern doesn't exactly make custom server components very useful within the Payload Admin Panel, which is where this PR comes in. Now, instead of pre-rendering all components on initial compile, we're able to render custom components _on demand_, only as they are needed. To achieve this, we've established [this pattern](https://github.com/payloadcms/payload/pull/8481) of React Server Functions in the Payload Admin Panel. With Server Functions, we can iterate the Payload Config and return JSX through React's `text/x-component` content-type. This means we're able to pass contextual props to custom components, such as data for fields and views. ## Breaking Changes 1. Add the following to your root layout file, typically located at `(app)/(payload)/layout.tsx`: ```diff /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ + import type { ServerFunctionClient } from 'payload' import config from '@payload-config' import { RootLayout } from '@payloadcms/next/layouts' import { handleServerFunctions } from '@payloadcms/next/utilities' import React from 'react' import { importMap } from './admin/importMap.js' import './custom.scss' type Args = { children: React.ReactNode } + const serverFunctions: ServerFunctionClient = async function (args) { + 'use server' + return handleServerFunctions({ + ...args, + config, + importMap, + }) + } const Layout = ({ children }: Args) => ( <RootLayout config={config} importMap={importMap} + serverFunctions={serverFunctions} > {children} </RootLayout> ) export default Layout ``` 2. If you were previously posting to the `/api/form-state` endpoint, it no longer exists. Instead, you'll need to invoke the `form-state` Server Function, which can be done through the _new_ `getFormState` utility: ```diff - import { getFormState } from '@payloadcms/ui' - const { state } = await getFormState({ - apiRoute: '', - body: { - // ... - }, - serverURL: '' - }) + const { getFormState } = useServerFunctions() + + const { state } = await getFormState({ + // ... + }) ``` ## Breaking Changes ```diff - useFieldProps() - useCellProps() ``` More details coming soon. --------- Co-authored-by: Alessio Gravili <alessio@gravili.de> Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com> Co-authored-by: James <james@trbl.design>
This commit is contained in:
@@ -43,17 +43,13 @@ describe('Array', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsArrayTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsArrayTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
|
||||
@@ -27,6 +27,15 @@ const ArrayFields: CollectionConfig = {
|
||||
name: 'anotherText',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'uiField',
|
||||
type: 'ui',
|
||||
admin: {
|
||||
components: {
|
||||
Field: './collections/Array/LabelComponent.js#ArrayRowLabel',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'localizedText',
|
||||
type: 'text',
|
||||
|
||||
@@ -3,29 +3,38 @@
|
||||
import { useField, useForm } from '@payloadcms/ui'
|
||||
import * as React from 'react'
|
||||
|
||||
import { blockFieldsSlug } from '../../../../slugs.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'custom-blocks-field-management'
|
||||
|
||||
const blocksPath = 'customBlocks'
|
||||
|
||||
export const AddCustomBlocks: React.FC = () => {
|
||||
export const AddCustomBlocks: React.FC<any> = (props) => {
|
||||
const { addFieldRow, replaceFieldRow } = useForm()
|
||||
const { value } = useField<number>({ path: blocksPath })
|
||||
const field = useField<number>({ path: blocksPath })
|
||||
const { value } = field
|
||||
|
||||
const schemaPath = props.schemaPath.replace(`.${props.field.name}`, `.${blocksPath}`)
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__blocks-grid`}>
|
||||
<button
|
||||
className={`${baseClass}__block-button`}
|
||||
onClick={() =>
|
||||
onClick={() => {
|
||||
addFieldRow({
|
||||
data: { block1Title: 'Block 1: Prefilled Title', blockType: 'block-1' },
|
||||
blockType: 'block-1',
|
||||
path: blocksPath,
|
||||
schemaPath: `${blockFieldsSlug}.${blocksPath}.block-1`,
|
||||
schemaPath,
|
||||
subFieldState: {
|
||||
block1Title: {
|
||||
initialValue: 'Block 1: Prefilled Title',
|
||||
valid: true,
|
||||
value: 'Block 1: Prefilled Title',
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Add Block 1
|
||||
@@ -33,13 +42,20 @@ export const AddCustomBlocks: React.FC = () => {
|
||||
|
||||
<button
|
||||
className={`${baseClass}__block-button`}
|
||||
onClick={() =>
|
||||
onClick={() => {
|
||||
addFieldRow({
|
||||
data: { block2Title: 'Block 2: Prefilled Title', blockType: 'block-2' },
|
||||
blockType: 'block-2',
|
||||
path: blocksPath,
|
||||
schemaPath: `${blockFieldsSlug}.${blocksPath}.block-2`,
|
||||
schemaPath,
|
||||
subFieldState: {
|
||||
block2Title: {
|
||||
initialValue: 'Block 2: Prefilled Title',
|
||||
valid: true,
|
||||
value: 'Block 2: Prefilled Title',
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Add Block 2
|
||||
@@ -51,10 +67,17 @@ export const AddCustomBlocks: React.FC = () => {
|
||||
className={`${baseClass}__block-button ${baseClass}__replace-block-button`}
|
||||
onClick={() =>
|
||||
replaceFieldRow({
|
||||
data: { block1Title: 'REPLACED BLOCK', blockType: 'block-1' },
|
||||
blockType: 'block-1',
|
||||
path: blocksPath,
|
||||
rowIndex: value - 1,
|
||||
schemaPath: `${blockFieldsSlug}.${blocksPath}.block-1`,
|
||||
rowIndex: value,
|
||||
schemaPath,
|
||||
subFieldState: {
|
||||
block1Title: {
|
||||
initialValue: 'REPLACED BLOCK',
|
||||
valid: true,
|
||||
value: 'REPLACED BLOCK',
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
type="button"
|
||||
|
||||
@@ -38,17 +38,13 @@ describe('Block fields', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'blockFieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'blockFieldsTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { BlockField, CollectionConfig } from 'payload'
|
||||
import type { BlocksField, CollectionConfig } from 'payload'
|
||||
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
|
||||
import { blockFieldsSlug, textFieldsSlug } from '../../slugs.js'
|
||||
import { getBlocksFieldSeedData } from './shared.js'
|
||||
|
||||
export const getBlocksField = (prefix?: string): BlockField => ({
|
||||
export const getBlocksField = (prefix?: string): BlocksField => ({
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
|
||||
@@ -32,11 +32,6 @@ export const getBlocksFieldSeedData = (prefix?: string): any => [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blockName: 'I18n Block',
|
||||
blockType: 'i18n-text',
|
||||
text: 'first block',
|
||||
},
|
||||
]
|
||||
|
||||
export const blocksDoc: Partial<BlockField> = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RowLabelComponent } from 'payload'
|
||||
import type { CollapsibleField } from 'payload'
|
||||
import type React from 'react'
|
||||
|
||||
export const getCustomLabel = ({
|
||||
@@ -9,7 +9,7 @@ export const getCustomLabel = ({
|
||||
fallback?: string
|
||||
path: string
|
||||
style: React.CSSProperties
|
||||
}): RowLabelComponent => {
|
||||
}): CollapsibleField['admin']['components']['Label'] => {
|
||||
return {
|
||||
clientProps: {
|
||||
fallback,
|
||||
|
||||
@@ -82,7 +82,7 @@ const CollapsibleFields: CollectionConfig = {
|
||||
description: 'Collapsible label rendered from a function.',
|
||||
initCollapsed: true,
|
||||
components: {
|
||||
RowLabel: getCustomLabel({
|
||||
Label: getCustomLabel({
|
||||
path: 'functionTitleField',
|
||||
fallback: 'Custom Collapsible Label',
|
||||
style: {},
|
||||
@@ -101,7 +101,7 @@ const CollapsibleFields: CollectionConfig = {
|
||||
admin: {
|
||||
description: 'Collapsible label rendered as a react component.',
|
||||
components: {
|
||||
RowLabel: getCustomLabel({ path: 'componentTitleField', style: {} }),
|
||||
Label: getCustomLabel({ path: 'componentTitleField', style: {} }),
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
@@ -113,7 +113,7 @@ const CollapsibleFields: CollectionConfig = {
|
||||
type: 'collapsible',
|
||||
admin: {
|
||||
components: {
|
||||
RowLabel: getCustomLabel({
|
||||
Label: getCustomLabel({
|
||||
path: 'nestedTitle',
|
||||
fallback: 'Nested Collapsible',
|
||||
style: {},
|
||||
@@ -134,12 +134,12 @@ const CollapsibleFields: CollectionConfig = {
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
type: 'collapsible',
|
||||
admin: {
|
||||
components: {
|
||||
RowLabel: '/collections/Collapsible/NestedCustomLabel/index.js#NestedCustomLabel',
|
||||
Label: '/collections/Collapsible/NestedCustomLabel/index.js#NestedCustomLabel',
|
||||
},
|
||||
},
|
||||
type: 'collapsible',
|
||||
fields: [
|
||||
{
|
||||
name: 'innerCollapsible',
|
||||
|
||||
@@ -45,17 +45,13 @@ describe('Date', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsDateTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsDateTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
|
||||
@@ -2,16 +2,11 @@
|
||||
|
||||
import type { EmailFieldClientComponent } from 'payload'
|
||||
|
||||
import { useFieldProps } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
export const CustomLabel: EmailFieldClientComponent = ({ field }) => {
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
|
||||
const path = pathFromContext ?? field?._schemaPath // pathFromContext will be undefined in list view
|
||||
|
||||
export const CustomLabel: EmailFieldClientComponent = ({ path }) => {
|
||||
return (
|
||||
<label className="custom-label" htmlFor={`field-${path.replace(/\./g, '__')}`}>
|
||||
<label className="custom-label" htmlFor={`field-${path?.replace(/\./g, '__')}`}>
|
||||
#label
|
||||
</label>
|
||||
)
|
||||
|
||||
@@ -48,17 +48,13 @@ describe('Email', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsEmailTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsEmailTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
|
||||
@@ -69,11 +69,7 @@ describe('lexicalBlocks', () => {
|
||||
page = await context.newPage()
|
||||
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsLexicalBlocksTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
@@ -84,7 +80,7 @@ describe('lexicalBlocks', () => {
|
||||
})*/
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsLexicalBlocksTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: [
|
||||
path.resolve(dirname, './collections/Upload/uploads'),
|
||||
path.resolve(dirname, './collections/Upload2/uploads2'),
|
||||
@@ -104,6 +100,8 @@ describe('lexicalBlocks', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
|
||||
const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node"
|
||||
await lexicalBlock.scrollIntoViewIfNeeded()
|
||||
@@ -160,6 +158,8 @@ describe('lexicalBlocks', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
|
||||
const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node"
|
||||
await lexicalBlock.scrollIntoViewIfNeeded()
|
||||
@@ -242,6 +242,8 @@ describe('lexicalBlocks', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
|
||||
// Find span in contentEditable with text "Some text below relationship node"
|
||||
const spanInEditor = richTextField.locator('span').getByText('Upload Node:').first()
|
||||
@@ -272,7 +274,8 @@ describe('lexicalBlocks', () => {
|
||||
|
||||
const urlField = drawerContent.locator('input#field-url').first()
|
||||
await expect(urlField).toBeVisible()
|
||||
// Fill with https://www.payloadcms.com
|
||||
await expect(urlField).toHaveValue('https://')
|
||||
await wait(1000)
|
||||
await urlField.fill('https://www.payloadcms.com')
|
||||
await expect(urlField).toHaveValue('https://www.payloadcms.com')
|
||||
await drawerContent.locator('.form-submit button').click({ delay: 100 })
|
||||
@@ -326,6 +329,8 @@ describe('lexicalBlocks', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
|
||||
const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node"
|
||||
await lexicalBlock.scrollIntoViewIfNeeded()
|
||||
@@ -399,6 +404,8 @@ describe('lexicalBlocks', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
|
||||
const lexicalBlock = richTextField.locator('.lexical-block').nth(3) // third: "Block Node, with Blocks Field, With RichText Field, With Relationship Node"
|
||||
await lexicalBlock.scrollIntoViewIfNeeded()
|
||||
@@ -474,6 +481,8 @@ describe('lexicalBlocks', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
|
||||
const lastParagraph = richTextField.locator('p').last()
|
||||
await lastParagraph.scrollIntoViewIfNeeded()
|
||||
@@ -693,6 +702,9 @@ describe('lexicalBlocks', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
await wait(1000) // Wait for form state requests to be done, to reduce flakes
|
||||
|
||||
const radioButtonBlock1 = richTextField.locator('.lexical-block').nth(5)
|
||||
|
||||
@@ -765,6 +777,8 @@ describe('lexicalBlocks', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
|
||||
/**
|
||||
* 1. Focus parent editor
|
||||
@@ -805,6 +819,8 @@ describe('lexicalBlocks', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
await wait(1000) // Wait for form state requests to be done, to reduce flakes
|
||||
|
||||
const conditionalArrayBlock = richTextField.locator('.lexical-block').nth(7)
|
||||
|
||||
@@ -862,6 +878,9 @@ describe('lexicalBlocks', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
await wait(1000) // Wait for form state requests to be done, to reduce flakes
|
||||
|
||||
const conditionalArrayBlock = richTextField.locator('.lexical-block').nth(7)
|
||||
|
||||
@@ -885,6 +904,8 @@ describe('lexicalBlocks', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
|
||||
const uploadBlock = richTextField.locator('.ContentEditable__root > div').first() // Check for the first div, as we wanna make sure it's the first div in the editor (1. node is a paragraph, second node is a div which is the upload node)
|
||||
await uploadBlock.scrollIntoViewIfNeeded()
|
||||
@@ -898,9 +919,11 @@ describe('lexicalBlocks', () => {
|
||||
test('should respect required error state in deeply nested text field', async () => {
|
||||
await navigateToLexicalFields()
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
|
||||
await wait(300)
|
||||
|
||||
const conditionalArrayBlock = richTextField.locator('.lexical-block').nth(7)
|
||||
@@ -950,6 +973,8 @@ describe('lexicalBlocks', () => {
|
||||
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
|
||||
const tabsBlock = richTextField.locator('.lexical-block').nth(8)
|
||||
await wait(300)
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ensureCompilationIsDone,
|
||||
initPageConsoleErrorCatch,
|
||||
saveDocAndAssert,
|
||||
throttleTest,
|
||||
} from '../../../../../helpers.js'
|
||||
import { AdminUrlUtil } from '../../../../../helpers/adminUrlUtil.js'
|
||||
import { initPayloadE2ENoConfig } from '../../../../../helpers/initPayloadE2ENoConfig.js'
|
||||
@@ -69,22 +70,18 @@ describe('lexicalMain', () => {
|
||||
page = await context.newPage()
|
||||
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsLexicalMainTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
/*await throttleTest({
|
||||
page,
|
||||
context,
|
||||
delay: 'Slow 4G',
|
||||
delay: 'Fast 4G',
|
||||
})*/
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsLexicalMainTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: [
|
||||
path.resolve(dirname, './collections/Upload/uploads'),
|
||||
path.resolve(dirname, './collections/Upload2/uploads2'),
|
||||
@@ -117,6 +114,10 @@ describe('lexicalMain', () => {
|
||||
test('should not warn about unsaved changes when navigating to lexical editor with blocks node and then leaving the page after making a change and saving', async () => {
|
||||
// Relevant issue: https://github.com/payloadcms/payload/issues/4115
|
||||
await navigateToLexicalFields()
|
||||
await expect(page.locator('.rich-text-lexical').nth(2).locator('.lexical-block')).toHaveCount(
|
||||
10,
|
||||
)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
const thirdBlock = page.locator('.rich-text-lexical').nth(2).locator('.lexical-block').nth(2)
|
||||
await thirdBlock.scrollIntoViewIfNeeded()
|
||||
await expect(thirdBlock).toBeVisible()
|
||||
@@ -140,6 +141,10 @@ describe('lexicalMain', () => {
|
||||
|
||||
// Save
|
||||
await saveDocAndAssert(page)
|
||||
await expect(page.locator('.rich-text-lexical').nth(2).locator('.lexical-block')).toHaveCount(
|
||||
10,
|
||||
)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
await expect(newSpanInBlock).toHaveText('Some text below rmoretextelationship node 1')
|
||||
|
||||
// Navigate to some different page, away from the current document
|
||||
@@ -154,6 +159,9 @@ describe('lexicalMain', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
|
||||
const spanInEditor = richTextField.locator('span').getByText('Upload Node:').first()
|
||||
await expect(spanInEditor).toBeVisible()
|
||||
@@ -198,6 +206,9 @@ describe('lexicalMain', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
|
||||
const spanInEditor = richTextField.locator('span').getByText('Upload Node:').first()
|
||||
await expect(spanInEditor).toBeVisible()
|
||||
@@ -303,6 +314,11 @@ describe('lexicalMain', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(1)
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(page.locator('.rich-text-lexical').nth(2).locator('.lexical-block')).toHaveCount(
|
||||
10,
|
||||
)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
|
||||
// Find span in contentEditable with text "Some text below relationship node"
|
||||
const contentEditable = richTextField.locator('.ContentEditable__root').first()
|
||||
@@ -364,6 +380,9 @@ describe('lexicalMain', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
|
||||
const lastParagraph = richTextField.locator('p').last()
|
||||
await lastParagraph.scrollIntoViewIfNeeded()
|
||||
@@ -391,6 +410,7 @@ describe('lexicalMain', () => {
|
||||
const uploadListDrawer = page.locator('dialog[id^=list-drawer_1_]').first() // IDs starting with list-drawer_1_ (there's some other symbol after the underscore)
|
||||
await expect(uploadListDrawer).toBeVisible()
|
||||
await wait(500)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
|
||||
await uploadListDrawer.locator('.rs__control .value-container').first().click()
|
||||
await wait(500)
|
||||
@@ -423,7 +443,10 @@ describe('lexicalMain', () => {
|
||||
await expect(uploadListDrawer).toBeHidden()
|
||||
await wait(500)
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
await expect(page.locator('.rich-text-lexical').nth(2).locator('.lexical-block')).toHaveCount(
|
||||
10,
|
||||
)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
// second one should be the newly created one
|
||||
const secondUploadNode = richTextField.locator('.lexical-upload').nth(1)
|
||||
await secondUploadNode.scrollIntoViewIfNeeded()
|
||||
@@ -446,6 +469,7 @@ describe('lexicalMain', () => {
|
||||
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
|
||||
const lastParagraph = richTextField.locator('p').last()
|
||||
await lastParagraph.scrollIntoViewIfNeeded()
|
||||
@@ -502,9 +526,17 @@ describe('lexicalMain', () => {
|
||||
await expect(uploadExtraFieldsDrawer).toBeHidden()
|
||||
await wait(500)
|
||||
await saveDocAndAssert(page)
|
||||
await wait(500)
|
||||
await expect(page.locator('.rich-text-lexical').nth(2).locator('.lexical-block')).toHaveCount(
|
||||
10,
|
||||
)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
// Reload page, open the extra fields drawer again and check if the text is still there
|
||||
await page.reload()
|
||||
await wait(300)
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
|
||||
const reloadedUploadNode = page
|
||||
.locator('.rich-text-lexical')
|
||||
.nth(2)
|
||||
@@ -570,9 +602,15 @@ describe('lexicalMain', () => {
|
||||
*/
|
||||
test('ensure lexical editor within drawer within relationship within lexical field has fully-functioning inline toolbar', async () => {
|
||||
await navigateToLexicalFields()
|
||||
await wait(500)
|
||||
const richTextField = page.locator('.rich-text-lexical').first()
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(page.locator('.rich-text-lexical').nth(2).locator('.lexical-block')).toHaveCount(
|
||||
10,
|
||||
)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
|
||||
const paragraph = richTextField.locator('.LexicalEditorTheme__paragraph').first()
|
||||
await paragraph.scrollIntoViewIfNeeded()
|
||||
@@ -622,7 +660,6 @@ describe('lexicalMain', () => {
|
||||
await wait(500)
|
||||
|
||||
const docRichTextField = docDrawer.locator('.rich-text-lexical').first()
|
||||
await docRichTextField.scrollIntoViewIfNeeded()
|
||||
await expect(docRichTextField).toBeVisible()
|
||||
|
||||
const docParagraph = docRichTextField.locator('.LexicalEditorTheme__paragraph').first()
|
||||
@@ -690,6 +727,11 @@ describe('lexicalMain', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').first()
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(page.locator('.rich-text-lexical').nth(2).locator('.lexical-block')).toHaveCount(
|
||||
10,
|
||||
)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
|
||||
const paragraph = richTextField.locator('.LexicalEditorTheme__paragraph').first()
|
||||
await paragraph.scrollIntoViewIfNeeded()
|
||||
@@ -788,6 +830,11 @@ describe('lexicalMain', () => {
|
||||
const richTextField = page.locator('.rich-text-lexical').first()
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(page.locator('.rich-text-lexical').nth(2).locator('.lexical-block')).toHaveCount(
|
||||
10,
|
||||
)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
|
||||
const paragraph = richTextField.locator('.LexicalEditorTheme__paragraph').first()
|
||||
await paragraph.scrollIntoViewIfNeeded()
|
||||
@@ -811,13 +858,14 @@ describe('lexicalMain', () => {
|
||||
const uploadSelectButton = slashMenuPopover.locator('button').first()
|
||||
await expect(uploadSelectButton).toBeVisible()
|
||||
await expect(uploadSelectButton).toContainText('Upload')
|
||||
await wait(1000)
|
||||
await uploadSelectButton.click()
|
||||
await expect(slashMenuPopover).toBeHidden()
|
||||
|
||||
await wait(500) // wait for drawer form state to initialize (it's a flake)
|
||||
const uploadListDrawer = page.locator('dialog[id^=list-drawer_1_]').first() // IDs starting with list-drawer_1_ (there's some other symbol after the underscore)
|
||||
await expect(uploadListDrawer).toBeVisible()
|
||||
await wait(500)
|
||||
await wait(1000)
|
||||
|
||||
await uploadListDrawer.locator('button').getByText('payload.png').first().click()
|
||||
await expect(uploadListDrawer).toBeHidden()
|
||||
@@ -826,15 +874,23 @@ describe('lexicalMain', () => {
|
||||
await newUploadNode.scrollIntoViewIfNeeded()
|
||||
await expect(newUploadNode).toBeVisible()
|
||||
|
||||
await expect(slashMenuPopover).toBeHidden()
|
||||
|
||||
await expect(newUploadNode.locator('.lexical-upload__bottomRow')).toContainText('payload.png')
|
||||
|
||||
await page.keyboard.press('Enter') // floating toolbar needs to appear with enough distance to the upload node, otherwise clicking may fail
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
// Select "there" by pressing shift + arrow left
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await page.keyboard.press('Shift+ArrowLeft')
|
||||
}
|
||||
|
||||
await newUploadNode.locator('.lexical-upload__swap-drawer-toggler').first().click()
|
||||
const swapDrawerButton = newUploadNode.locator('.lexical-upload__swap-drawer-toggler').first()
|
||||
|
||||
await expect(swapDrawerButton).toBeVisible()
|
||||
|
||||
await swapDrawerButton.click()
|
||||
|
||||
const uploadSwapDrawer = page.locator('dialog[id^=list-drawer_1_]').first()
|
||||
await expect(uploadSwapDrawer).toBeVisible()
|
||||
@@ -879,7 +935,9 @@ describe('lexicalMain', () => {
|
||||
.children[0] as SerializedParagraphNode
|
||||
const secondParagraph: SerializedParagraphNode = lexicalField.root
|
||||
.children[1] as SerializedParagraphNode
|
||||
const uploadNode: SerializedUploadNode = lexicalField.root.children[2] as SerializedUploadNode
|
||||
const thirdParagraph: SerializedParagraphNode = lexicalField.root
|
||||
.children[2] as SerializedParagraphNode
|
||||
const uploadNode: SerializedUploadNode = lexicalField.root.children[3] as SerializedUploadNode
|
||||
|
||||
expect(firstParagraph.children).toHaveLength(2)
|
||||
expect((firstParagraph.children[0] as SerializedTextNode).text).toBe('Some ')
|
||||
@@ -888,6 +946,7 @@ describe('lexicalMain', () => {
|
||||
expect((firstParagraph.children[1] as SerializedTextNode).format).toBe(1)
|
||||
|
||||
expect(secondParagraph.children).toHaveLength(0)
|
||||
expect(thirdParagraph.children).toHaveLength(0)
|
||||
|
||||
expect(uploadNode.relationTo).toBe('uploads')
|
||||
}).toPass({
|
||||
|
||||
@@ -181,7 +181,12 @@ export const LexicalFields: CollectionConfig = {
|
||||
|
||||
const yourEditorState: SerializedEditorState = siblingData.lexicalWithBlocks
|
||||
try {
|
||||
headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState))
|
||||
headlessEditor.update(
|
||||
() => {
|
||||
headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState))
|
||||
},
|
||||
{ discrete: true },
|
||||
)
|
||||
} catch (e) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
@@ -46,18 +46,14 @@ describe('Number', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsNumberTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsNumberTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
if (client) {
|
||||
|
||||
@@ -46,17 +46,13 @@ describe('Point', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsPointTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsPointTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
|
||||
@@ -48,17 +48,13 @@ describe('relationship', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsRelationshipTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsRelationshipTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
|
||||
@@ -38,17 +38,13 @@ describe('Rich Text', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsRichTextTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsRichTextTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
@@ -189,6 +185,7 @@ describe('Rich Text', () => {
|
||||
|
||||
test('should only list RTE enabled upload collections in drawer', async () => {
|
||||
await navigateToRichTextFields()
|
||||
await wait(1000)
|
||||
|
||||
// Open link drawer
|
||||
await page
|
||||
@@ -196,6 +193,9 @@ describe('Rich Text', () => {
|
||||
.first()
|
||||
.click()
|
||||
|
||||
const drawer = page.locator('[id^=list-drawer_1_]')
|
||||
await expect(drawer).toBeVisible()
|
||||
|
||||
// open the list select menu
|
||||
await page.locator('.list-drawer__select-collection-wrap .rs__control').click()
|
||||
|
||||
@@ -225,7 +225,7 @@ describe('Rich Text', () => {
|
||||
)
|
||||
|
||||
// change the selected collection to `array-fields`
|
||||
await page.locator('.list-drawer__select-collection-wrap .rs__control').click()
|
||||
await page.locator('.list-drawer_select-collection-wrap .rs__control').click()
|
||||
const menu = page.locator('.list-drawer__select-collection-wrap .rs__menu')
|
||||
await menu.locator('.rs__option').getByText('Array Field').click()
|
||||
|
||||
@@ -318,6 +318,7 @@ describe('Rich Text', () => {
|
||||
describe('editor', () => {
|
||||
test('should populate url link', async () => {
|
||||
await navigateToRichTextFields()
|
||||
await wait(500)
|
||||
|
||||
// Open link popup
|
||||
await page.locator('#field-richText span >> text="render links"').click()
|
||||
@@ -334,6 +335,7 @@ describe('Rich Text', () => {
|
||||
const textField = editLinkModal.locator('#field-text')
|
||||
await expect(textField).toHaveValue('render links')
|
||||
|
||||
await wait(1000)
|
||||
// Close the drawer
|
||||
await editLinkModal.locator('button[type="submit"]').click()
|
||||
await expect(editLinkModal).toBeHidden()
|
||||
@@ -402,6 +404,7 @@ describe('Rich Text', () => {
|
||||
|
||||
test('should populate new links', async () => {
|
||||
await navigateToRichTextFields()
|
||||
await wait(1000)
|
||||
|
||||
// Highlight existing text
|
||||
const headingElement = page.locator(
|
||||
@@ -409,6 +412,8 @@ describe('Rich Text', () => {
|
||||
)
|
||||
await headingElement.selectText()
|
||||
|
||||
await wait(500)
|
||||
|
||||
// click the toolbar link button
|
||||
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click()
|
||||
|
||||
@@ -423,12 +428,15 @@ describe('Rich Text', () => {
|
||||
await page.locator('#field-blocks').scrollIntoViewIfNeeded()
|
||||
await expect(page.locator('#field-blocks__0__text')).toBeVisible()
|
||||
await expect(page.locator('#field-blocks__0__text')).toHaveValue('Regular text')
|
||||
await wait(500)
|
||||
const editBlock = page.locator('#blocks-row-0 .popup-button')
|
||||
await editBlock.click()
|
||||
const removeButton = page.locator('#blocks-row-0').getByRole('button', { name: 'Remove' })
|
||||
await expect(removeButton).toBeVisible()
|
||||
await wait(500)
|
||||
await removeButton.click()
|
||||
const richTextField = page.locator('#field-blocks__0__text')
|
||||
await expect(richTextField).toBeVisible()
|
||||
const richTextValue = await richTextField.innerText()
|
||||
expect(richTextValue).toContain('Rich text')
|
||||
})
|
||||
|
||||
@@ -48,17 +48,13 @@ describe('Tabs', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsTabsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsTabsTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
|
||||
5
test/fields/collections/Text/CustomDescription.tsx
Normal file
5
test/fields/collections/Text/CustomDescription.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function CustomDescription() {
|
||||
return <div>Custom Description</div>
|
||||
}
|
||||
@@ -48,17 +48,13 @@ describe('Text', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsTextTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsTextTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
'use client'
|
||||
import { useFieldProps } from '@payloadcms/ui'
|
||||
import type { TextFieldClientComponent } from 'payload'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
export const UICustomClient: React.FC = () => {
|
||||
const { custom, path } = useFieldProps()
|
||||
|
||||
return <div id={path}>{custom?.customValue}</div>
|
||||
export const UICustomClient: TextFieldClientComponent = ({
|
||||
field: {
|
||||
name,
|
||||
admin: { custom },
|
||||
},
|
||||
}) => {
|
||||
return <div id={name}>{custom?.customValue}</div>
|
||||
}
|
||||
|
||||
@@ -47,17 +47,13 @@ describe('Upload', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsUploadTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsUploadTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@ const Uploads: CollectionConfig = {
|
||||
},
|
||||
relationTo: uploadsSlug,
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
},
|
||||
// {
|
||||
// name: 'richText',
|
||||
// type: 'richText',
|
||||
// },
|
||||
],
|
||||
upload: {
|
||||
staticDir: path.resolve(dirname, './uploads'),
|
||||
|
||||
@@ -42,17 +42,13 @@ describe('Upload with restrictions', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsUploadRestrictedTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsUploadRestrictedTest',
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
|
||||
@@ -25,10 +25,10 @@ const Uploads3: CollectionConfig = {
|
||||
name: 'media',
|
||||
relationTo: uploads3Slug,
|
||||
},
|
||||
{
|
||||
type: 'richText',
|
||||
name: 'richText',
|
||||
},
|
||||
// {
|
||||
// type: 'richText',
|
||||
// name: 'richText',
|
||||
// },
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Locator, Page } from '@playwright/test'
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import path from 'path'
|
||||
@@ -45,11 +45,7 @@ describe('fields', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
@@ -243,7 +239,7 @@ describe('fields', () => {
|
||||
test('should render collapsible as collapsed if initCollapsed is true', async () => {
|
||||
await page.goto(url.create)
|
||||
const collapsedCollapsible = page.locator(
|
||||
'#field-collapsible-1 .collapsible__toggle--collapsed',
|
||||
'#field-collapsible-_index-1 .collapsible__toggle--collapsed',
|
||||
)
|
||||
await expect(collapsedCollapsible).toBeVisible()
|
||||
})
|
||||
@@ -251,10 +247,10 @@ describe('fields', () => {
|
||||
test('should render CollapsibleLabel using a function', async () => {
|
||||
const label = 'custom row label'
|
||||
await page.goto(url.create)
|
||||
await page.locator('#field-collapsible-3__1 #field-nestedTitle').fill(label)
|
||||
await page.locator('#field-collapsible-_index-3-1 #field-nestedTitle').fill(label)
|
||||
await wait(100)
|
||||
const customCollapsibleLabel = page.locator(
|
||||
`#field-collapsible-3__1 .collapsible-field__row-label-wrap :text("${label}")`,
|
||||
`#field-collapsible-_index-3-1 .collapsible-field__row-label-wrap :text("${label}")`,
|
||||
)
|
||||
await expect(customCollapsibleLabel).toContainText(label)
|
||||
})
|
||||
@@ -271,7 +267,7 @@ describe('fields', () => {
|
||||
|
||||
await page
|
||||
.locator(
|
||||
'#arrayWithCollapsibles-row-0 #field-collapsible-4__0-arrayWithCollapsibles__0 #field-arrayWithCollapsibles__0__innerCollapsible',
|
||||
'#arrayWithCollapsibles-row-0 #field-collapsible-arrayWithCollapsibles__0___index-0 #field-arrayWithCollapsibles__0__innerCollapsible',
|
||||
)
|
||||
.fill(label)
|
||||
await wait(100)
|
||||
|
||||
@@ -999,7 +999,9 @@ describe('Fields', () => {
|
||||
})
|
||||
|
||||
it('should read', async () => {
|
||||
if (payload.db.name === 'sqlite') {return}
|
||||
if (payload.db.name === 'sqlite') {
|
||||
return
|
||||
}
|
||||
const find = await payload.find({
|
||||
collection: 'point-fields',
|
||||
pagination: false,
|
||||
@@ -1013,7 +1015,9 @@ describe('Fields', () => {
|
||||
})
|
||||
|
||||
it('should create', async () => {
|
||||
if (payload.db.name === 'sqlite') {return}
|
||||
if (payload.db.name === 'sqlite') {
|
||||
return
|
||||
}
|
||||
doc = await payload.create({
|
||||
collection: 'point-fields',
|
||||
data: {
|
||||
@@ -1029,7 +1033,9 @@ describe('Fields', () => {
|
||||
})
|
||||
|
||||
it('should not create duplicate point when unique', async () => {
|
||||
if (payload.db.name === 'sqlite') {return}
|
||||
if (payload.db.name === 'sqlite') {
|
||||
return
|
||||
}
|
||||
// first create the point field
|
||||
doc = await payload.create({
|
||||
collection: 'point-fields',
|
||||
@@ -1099,7 +1105,7 @@ describe('Fields', () => {
|
||||
uniqueRelationship: textDoc.id,
|
||||
},
|
||||
})
|
||||
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
|
||||
// Skip mongodb unique error because it threats localizedUniqueRequriedText.es as undefined
|
||||
.then((doc) =>
|
||||
payload.update({
|
||||
locale: 'es',
|
||||
@@ -1135,7 +1141,7 @@ describe('Fields', () => {
|
||||
uniqueHasManyRelationship: [textDoc.id],
|
||||
},
|
||||
})
|
||||
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
|
||||
// Skip mongodb unique error because it threats localizedUniqueRequriedText.es as undefined
|
||||
.then((doc) =>
|
||||
payload.update({
|
||||
locale: 'es',
|
||||
@@ -1156,7 +1162,7 @@ describe('Fields', () => {
|
||||
uniqueHasManyRelationship_2: [textDoc.id],
|
||||
},
|
||||
})
|
||||
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
|
||||
// Skip mongodb unique error because it threats localizedUniqueRequriedText.es as undefined
|
||||
.then((doc) =>
|
||||
payload.update({
|
||||
locale: 'es',
|
||||
@@ -1179,7 +1185,7 @@ describe('Fields', () => {
|
||||
).rejects.toBeTruthy()
|
||||
})
|
||||
|
||||
it('should throw validation error saving on unique relationship fields polymorphic', async () => {
|
||||
it('should throw validation error saving on unique relationship fields polymorphic not hasMany', async () => {
|
||||
const textDoc = await payload.create({ collection: 'text-fields', data: { text: 'asd' } })
|
||||
|
||||
await payload
|
||||
@@ -1192,7 +1198,7 @@ describe('Fields', () => {
|
||||
uniquePolymorphicRelationship: { relationTo: 'text-fields', value: textDoc.id },
|
||||
},
|
||||
})
|
||||
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
|
||||
// Skip mongodb unique error because it threats localizedUniqueRequriedText.es as undefined
|
||||
.then((doc) =>
|
||||
payload.update({
|
||||
locale: 'es',
|
||||
@@ -1213,7 +1219,7 @@ describe('Fields', () => {
|
||||
uniquePolymorphicRelationship_2: { relationTo: 'text-fields', value: textDoc.id },
|
||||
},
|
||||
})
|
||||
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
|
||||
// Skip mongodb unique error because it threats localizedUniqueRequriedText.es as undefined
|
||||
.then((doc) =>
|
||||
payload.update({
|
||||
locale: 'es',
|
||||
@@ -1273,7 +1279,7 @@ describe('Fields', () => {
|
||||
],
|
||||
},
|
||||
})
|
||||
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
|
||||
// Skip mongodb unique error because it threats localizedUniqueRequriedText.es as undefined
|
||||
.then((doc) =>
|
||||
payload.update({
|
||||
locale: 'es',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,21 +63,6 @@ const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export const seed = async (_payload: Payload) => {
|
||||
if (_payload.db.name === 'mongoose') {
|
||||
await Promise.all(
|
||||
_payload.config.collections.map(async (coll) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
_payload.db?.collections[coll.slug]?.ensureIndexes(function (err) {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const jpgPath = path.resolve(dirname, './collections/Upload/payload.jpg')
|
||||
const pngPath = path.resolve(dirname, './uploads/payload.png')
|
||||
|
||||
|
||||
@@ -35,31 +35,31 @@ export const uiSlug = 'ui-fields'
|
||||
|
||||
export const collectionSlugs = [
|
||||
usersSlug,
|
||||
arrayFieldsSlug,
|
||||
blockFieldsSlug,
|
||||
checkboxFieldsSlug,
|
||||
codeFieldsSlug,
|
||||
collapsibleFieldsSlug,
|
||||
conditionalLogicSlug,
|
||||
dateFieldsSlug,
|
||||
groupFieldsSlug,
|
||||
indexedFieldsSlug,
|
||||
jsonFieldsSlug,
|
||||
lexicalFieldsSlug,
|
||||
lexicalMigrateFieldsSlug,
|
||||
lexicalRelationshipFieldsSlug,
|
||||
numberFieldsSlug,
|
||||
pointFieldsSlug,
|
||||
radioFieldsSlug,
|
||||
relationshipFieldsSlug,
|
||||
richTextFieldsSlug,
|
||||
rowFieldsSlug,
|
||||
selectFieldsSlug,
|
||||
tabsFieldsSlug,
|
||||
tabsFields2Slug,
|
||||
textFieldsSlug,
|
||||
uploadsSlug,
|
||||
uploads2Slug,
|
||||
uploads3Slug,
|
||||
uiSlug,
|
||||
// arrayFieldsSlug,
|
||||
// blockFieldsSlug,
|
||||
// checkboxFieldsSlug,
|
||||
// codeFieldsSlug,
|
||||
// collapsibleFieldsSlug,
|
||||
// conditionalLogicSlug,
|
||||
// dateFieldsSlug,
|
||||
// groupFieldsSlug,
|
||||
// indexedFieldsSlug,
|
||||
// jsonFieldsSlug,
|
||||
// lexicalFieldsSlug,
|
||||
// lexicalMigrateFieldsSlug,
|
||||
// lexicalRelationshipFieldsSlug,
|
||||
// numberFieldsSlug,
|
||||
// pointFieldsSlug,
|
||||
// radioFieldsSlug,
|
||||
// relationshipFieldsSlug,
|
||||
// richTextFieldsSlug,
|
||||
// rowFieldsSlug,
|
||||
// selectFieldsSlug,
|
||||
// tabsFieldsSlug,
|
||||
// tabsFields2Slug,
|
||||
// textFieldsSlug,
|
||||
// uploadsSlug,
|
||||
// uploads2Slug,
|
||||
// uploads3Slug,
|
||||
// uiSlug,
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user