fix(richtext-lexical): inline blocks did not store nested fields correctly (#10578)
Fixes https://github.com/payloadcms/payload/issues/10555 Form state with nested fields was not unflattened before saving field data to the node
This commit is contained in:
@@ -491,14 +491,13 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
|||||||
fields={clientBlock?.fields}
|
fields={clientBlock?.fields}
|
||||||
initialState={initialState}
|
initialState={initialState}
|
||||||
onChange={[onChange]}
|
onChange={[onChange]}
|
||||||
onSubmit={(formState) => {
|
onSubmit={(formState, newData) => {
|
||||||
// This is only called when form is submitted from drawer - usually only the case if the block has a custom Block component
|
// This is only called when form is submitted from drawer - usually only the case if the block has a custom Block component
|
||||||
const newData: any = reduceFieldsToValues(formState)
|
|
||||||
newData.blockType = formData.blockType
|
newData.blockType = formData.blockType
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
const node = $getNodeByKey(nodeKey)
|
const node = $getNodeByKey(nodeKey)
|
||||||
if (node && $isBlockNode(node)) {
|
if (node && $isBlockNode(node)) {
|
||||||
node.setFields(newData, true)
|
node.setFields(newData as BlockFields, true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
toggleDrawer()
|
toggleDrawer()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import React, { createContext, useCallback, useEffect, useMemo, useRef } from 'react'
|
import React, { createContext, useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
const baseClass = 'inline-block'
|
const baseClass = 'inline-block'
|
||||||
|
|
||||||
import type { BlocksFieldClient, FormState } from 'payload'
|
import type { BlocksFieldClient, Data, FormState } from 'payload'
|
||||||
|
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||||
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection'
|
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection'
|
||||||
@@ -33,7 +33,6 @@ import {
|
|||||||
KEY_BACKSPACE_COMMAND,
|
KEY_BACKSPACE_COMMAND,
|
||||||
KEY_DELETE_COMMAND,
|
KEY_DELETE_COMMAND,
|
||||||
} from 'lexical'
|
} from 'lexical'
|
||||||
import { reduceFieldsToValues } from 'payload/shared'
|
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
@@ -308,13 +307,13 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
|||||||
* HANDLE FORM SUBMIT
|
* HANDLE FORM SUBMIT
|
||||||
*/
|
*/
|
||||||
const onFormSubmit = useCallback(
|
const onFormSubmit = useCallback(
|
||||||
(formState: FormState) => {
|
(formState: FormState, newData: Data) => {
|
||||||
const newData: any = reduceFieldsToValues(formState)
|
|
||||||
newData.blockType = formData.blockType
|
newData.blockType = formData.blockType
|
||||||
|
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
const node = $getNodeByKey(nodeKey)
|
const node = $getNodeByKey(nodeKey)
|
||||||
if (node && $isInlineBlockNode(node)) {
|
if (node && $isInlineBlockNode(node)) {
|
||||||
node.setFields(newData, true)
|
node.setFields(newData as InlineBlockFields, true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -414,8 +413,8 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
|||||||
fields={clientBlock?.fields}
|
fields={clientBlock?.fields}
|
||||||
initialState={initialState || {}}
|
initialState={initialState || {}}
|
||||||
onChange={[onChange]}
|
onChange={[onChange]}
|
||||||
onSubmit={(formState) => {
|
onSubmit={(formState, data) => {
|
||||||
onFormSubmit(formState)
|
onFormSubmit(formState, data)
|
||||||
toggleDrawer()
|
toggleDrawer()
|
||||||
}}
|
}}
|
||||||
uuid={uuid()}
|
uuid={uuid()}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ describe('Array', () => {
|
|||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
|
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ describe('Collapsibles', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ describe('Conditional Logic', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ describe('Custom IDs', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ describe('Date', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ describe('Email', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ describe('Radio', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ describe('JSON', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type {
|
|||||||
SerializedParagraphNode,
|
SerializedParagraphNode,
|
||||||
SerializedTextNode,
|
SerializedTextNode,
|
||||||
} from '@payloadcms/richtext-lexical/lexical'
|
} from '@payloadcms/richtext-lexical/lexical'
|
||||||
import type { BrowserContext, Page } from '@playwright/test'
|
import type { BrowserContext, Locator, Page } from '@playwright/test'
|
||||||
|
|
||||||
import { expect, test } from '@playwright/test'
|
import { expect, test } from '@playwright/test'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
@@ -49,7 +49,9 @@ let serverURL: string
|
|||||||
async function navigateToLexicalFields(
|
async function navigateToLexicalFields(
|
||||||
navigateToListView: boolean = true,
|
navigateToListView: boolean = true,
|
||||||
localized: boolean = false,
|
localized: boolean = false,
|
||||||
) {
|
): Promise<{
|
||||||
|
richTextField: Locator
|
||||||
|
}> {
|
||||||
if (navigateToListView) {
|
if (navigateToListView) {
|
||||||
const url: AdminUrlUtil = new AdminUrlUtil(
|
const url: AdminUrlUtil = new AdminUrlUtil(
|
||||||
serverURL,
|
serverURL,
|
||||||
@@ -65,13 +67,135 @@ async function navigateToLexicalFields(
|
|||||||
await linkToDoc.click()
|
await linkToDoc.click()
|
||||||
|
|
||||||
await page.waitForURL(`**${linkDocHref}`)
|
await page.waitForURL(`**${linkDocHref}`)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return {
|
||||||
|
richTextField,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createInlineBlock({
|
||||||
|
name,
|
||||||
|
richTextField,
|
||||||
|
}: {
|
||||||
|
name: string
|
||||||
|
richTextField: Locator
|
||||||
|
}): Promise<{
|
||||||
|
inlineBlockDrawer: Locator
|
||||||
|
saveDrawer: () => Promise<{
|
||||||
|
inlineBlock: Locator
|
||||||
|
openEditDrawer: () => Promise<{ editDrawer: Locator; saveEditDrawer: () => Promise<void> }>
|
||||||
|
}>
|
||||||
|
}> {
|
||||||
|
const lastParagraph = richTextField.locator('p').last()
|
||||||
|
await lastParagraph.scrollIntoViewIfNeeded()
|
||||||
|
await expect(lastParagraph).toBeVisible()
|
||||||
|
|
||||||
|
const spanInEditor = richTextField.locator('span').getByText('Upload Node:').first()
|
||||||
|
await expect(spanInEditor).toBeVisible()
|
||||||
|
await spanInEditor.click()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new sub-block
|
||||||
|
*/
|
||||||
|
await page.keyboard.press(' ')
|
||||||
|
await page.keyboard.press('/')
|
||||||
|
await page.keyboard.type(name.includes(' ') ? name.split(' ')[0] : name)
|
||||||
|
|
||||||
|
// Create Rich Text Block
|
||||||
|
const slashMenuPopover = page.locator('#slash-menu .slash-menu-popup')
|
||||||
|
await expect(slashMenuPopover).toBeVisible()
|
||||||
|
|
||||||
|
const richTextBlockSelectButton = slashMenuPopover.locator('button').getByText(name).first()
|
||||||
|
await expect(richTextBlockSelectButton).toBeVisible()
|
||||||
|
await expect(richTextBlockSelectButton).toHaveText(name)
|
||||||
|
await richTextBlockSelectButton.click()
|
||||||
|
await expect(slashMenuPopover).toBeHidden()
|
||||||
|
|
||||||
|
// Wait for inline block drawer to pop up. Drawer id starts with drawer_1_lexical-inlineBlocks-create-
|
||||||
|
const inlineBlockDrawer = page
|
||||||
|
.locator('dialog[id^=drawer_1_lexical-inlineBlocks-create-]')
|
||||||
|
.first()
|
||||||
|
await expect(inlineBlockDrawer).toBeVisible()
|
||||||
|
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||||
|
await wait(500)
|
||||||
|
|
||||||
|
return {
|
||||||
|
inlineBlockDrawer,
|
||||||
|
saveDrawer: async () => {
|
||||||
|
await wait(500)
|
||||||
|
await inlineBlockDrawer.locator('button').getByText('Save changes').click()
|
||||||
|
await expect(inlineBlockDrawer).toBeHidden()
|
||||||
|
|
||||||
|
const inlineBlock = richTextField.locator('.inline-block').nth(0)
|
||||||
|
const editButton = inlineBlock.locator('.inline-block__editButton').first()
|
||||||
|
|
||||||
|
return {
|
||||||
|
inlineBlock,
|
||||||
|
openEditDrawer: async () => {
|
||||||
|
await editButton.click()
|
||||||
|
const editDrawer = page
|
||||||
|
.locator('dialog[id^=drawer_1_lexical-inlineBlocks-create-]')
|
||||||
|
.first()
|
||||||
|
await expect(editDrawer).toBeVisible()
|
||||||
|
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||||
|
await wait(500)
|
||||||
|
|
||||||
|
return {
|
||||||
|
editDrawer,
|
||||||
|
saveEditDrawer: async () => {
|
||||||
|
await wait(500)
|
||||||
|
const saveButton = editDrawer.locator('button').getByText('Save changes').first()
|
||||||
|
await saveButton.click()
|
||||||
|
await expect(editDrawer).toBeHidden()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assertLexicalDoc({
|
||||||
|
fn,
|
||||||
|
depth = 0,
|
||||||
|
}: {
|
||||||
|
depth?: number
|
||||||
|
fn: (args: {
|
||||||
|
lexicalDoc: LexicalField
|
||||||
|
lexicalWithBlocks: SerializedEditorState
|
||||||
|
}) => Promise<void> | void
|
||||||
|
}) {
|
||||||
|
await expect(async () => {
|
||||||
|
const lexicalDoc: LexicalField = (
|
||||||
|
await payload.find({
|
||||||
|
collection: lexicalFieldsSlug,
|
||||||
|
depth,
|
||||||
|
overrideAccess: true,
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
equals: lexicalDocData.title,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).docs[0] as never
|
||||||
|
|
||||||
|
await fn({ lexicalDoc, lexicalWithBlocks: lexicalDoc.lexicalWithBlocks })
|
||||||
|
}).toPass({
|
||||||
|
timeout: POLL_TOPASS_TIMEOUT,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('lexicalBlocks', () => {
|
describe('lexicalBlocks', () => {
|
||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname }))
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
|
||||||
|
|
||||||
context = await browser.newContext()
|
context = await browser.newContext()
|
||||||
page = await context.newPage()
|
page = await context.newPage()
|
||||||
@@ -103,12 +227,7 @@ describe('lexicalBlocks', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('ensure block with custom Block RSC can be created, updates data when saving edit fields drawer, and maintains cursor position', async () => {
|
test('ensure block with custom Block RSC can be created, updates data when saving edit fields drawer, and maintains cursor position', async () => {
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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)
|
|
||||||
|
|
||||||
const lastParagraph = richTextField.locator('p').last()
|
const lastParagraph = richTextField.locator('p').last()
|
||||||
await lastParagraph.scrollIntoViewIfNeeded()
|
await lastParagraph.scrollIntoViewIfNeeded()
|
||||||
@@ -178,188 +297,24 @@ describe('lexicalBlocks', () => {
|
|||||||
await expect(newRSCBlock.locator('.collapsible__content')).toHaveText('Data: value2')
|
await expect(newRSCBlock.locator('.collapsible__content')).toHaveText('Data: value2')
|
||||||
|
|
||||||
// Check if the API result is correct
|
// Check if the API result is correct
|
||||||
|
await assertLexicalDoc({
|
||||||
// TODO:
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
await expect(async () => {
|
const rscBlock: SerializedBlockNode = lexicalWithBlocks.root
|
||||||
const lexicalDoc: LexicalField = (
|
.children[14] as SerializedBlockNode
|
||||||
await payload.find({
|
const paragraphBlock: SerializedBlockNode = lexicalWithBlocks.root
|
||||||
collection: lexicalFieldsSlug,
|
|
||||||
depth: 0,
|
|
||||||
overrideAccess: true,
|
|
||||||
where: {
|
|
||||||
title: {
|
|
||||||
equals: lexicalDocData.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).docs[0] as never
|
|
||||||
|
|
||||||
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
|
|
||||||
const rscBlock: SerializedBlockNode = lexicalField.root.children[14] as SerializedBlockNode
|
|
||||||
const paragraphBlock: SerializedBlockNode = lexicalField.root
|
|
||||||
.children[12] as SerializedBlockNode
|
.children[12] as SerializedBlockNode
|
||||||
|
|
||||||
expect(rscBlock.fields.blockType).toBe('BlockRSC')
|
expect(rscBlock.fields.blockType).toBe('BlockRSC')
|
||||||
expect(rscBlock.fields.key).toBe('value2')
|
expect(rscBlock.fields.key).toBe('value2')
|
||||||
expect((paragraphBlock.children[0] as SerializedTextNode).text).toBe('123')
|
expect((paragraphBlock.children[0] as SerializedTextNode).text).toBe('123')
|
||||||
expect((paragraphBlock.children[0] as SerializedTextNode).format).toBe(1)
|
expect((paragraphBlock.children[0] as SerializedTextNode).format).toBe(1)
|
||||||
}).toPass({
|
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('ensure inline blocks can be created and its values can be mutated from outside their form', 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)
|
|
||||||
|
|
||||||
const lastParagraph = richTextField.locator('p').last()
|
|
||||||
await lastParagraph.scrollIntoViewIfNeeded()
|
|
||||||
await expect(lastParagraph).toBeVisible()
|
|
||||||
|
|
||||||
const spanInEditor = richTextField.locator('span').getByText('Upload Node:').first()
|
|
||||||
await expect(spanInEditor).toBeVisible()
|
|
||||||
await spanInEditor.click()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create new sub-block
|
|
||||||
*/
|
|
||||||
await page.keyboard.press(' ')
|
|
||||||
await page.keyboard.press('/')
|
|
||||||
await page.keyboard.type('inline')
|
|
||||||
|
|
||||||
// Create Rich Text Block
|
|
||||||
const slashMenuPopover = page.locator('#slash-menu .slash-menu-popup')
|
|
||||||
await expect(slashMenuPopover).toBeVisible()
|
|
||||||
|
|
||||||
const richTextBlockSelectButton = slashMenuPopover.locator('button').first()
|
|
||||||
await expect(richTextBlockSelectButton).toBeVisible()
|
|
||||||
await expect(richTextBlockSelectButton).toHaveText('My Inline Block')
|
|
||||||
await richTextBlockSelectButton.click()
|
|
||||||
await expect(slashMenuPopover).toBeHidden()
|
|
||||||
|
|
||||||
// Wait for inline block drawer to pop up. Drawer id starts with drawer_1_lexical-inlineBlocks-create-
|
|
||||||
const inlineBlockDrawer = page
|
|
||||||
.locator('dialog[id^=drawer_1_lexical-inlineBlocks-create-]')
|
|
||||||
.first()
|
|
||||||
await expect(inlineBlockDrawer).toBeVisible()
|
|
||||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
|
||||||
await wait(500)
|
|
||||||
|
|
||||||
// Click on react select in drawer, select 'value1'
|
|
||||||
|
|
||||||
await inlineBlockDrawer.locator('.rs__control .value-container').first().click()
|
|
||||||
await wait(500)
|
|
||||||
await expect(inlineBlockDrawer.locator('.rs__option').first()).toBeVisible()
|
|
||||||
await expect(inlineBlockDrawer.locator('.rs__option').first()).toContainText('value1')
|
|
||||||
await inlineBlockDrawer.locator('.rs__option').first().click()
|
|
||||||
// Wait 500
|
|
||||||
await wait(500)
|
|
||||||
// Click on save changes button and close drawer
|
|
||||||
await inlineBlockDrawer.locator('button').getByText('Save changes').click()
|
|
||||||
await expect(inlineBlockDrawer).toBeHidden()
|
|
||||||
// Save document
|
|
||||||
await saveDocAndAssert(page)
|
|
||||||
// Check if the API result is correct
|
|
||||||
await expect(async () => {
|
|
||||||
const lexicalDoc: LexicalField = (
|
|
||||||
await payload.find({
|
|
||||||
collection: lexicalFieldsSlug,
|
|
||||||
depth: 0,
|
|
||||||
overrideAccess: true,
|
|
||||||
where: {
|
|
||||||
title: {
|
|
||||||
equals: lexicalDocData.title,
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
})
|
|
||||||
).docs[0] as never
|
|
||||||
|
|
||||||
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
|
|
||||||
const firstParagraph: SerializedParagraphNode = lexicalField.root
|
|
||||||
.children[0] as SerializedParagraphNode
|
|
||||||
const inlineBlock: SerializedInlineBlockNode = firstParagraph
|
|
||||||
.children[1] as SerializedInlineBlockNode
|
|
||||||
|
|
||||||
await expect(inlineBlock.fields.key).toBe('value1')
|
|
||||||
}).toPass({
|
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Open drawer by clicking on edit button inline-block__editButton
|
|
||||||
const inlineBlock = richTextField.locator('.inline-block').nth(0)
|
|
||||||
const editButton = inlineBlock.locator('.inline-block__editButton').first()
|
|
||||||
await editButton.click()
|
|
||||||
const editDrawer = page.locator('dialog[id^=drawer_1_lexical-inlineBlocks-create-]').first()
|
|
||||||
await expect(editDrawer).toBeVisible()
|
|
||||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
|
||||||
await wait(500)
|
|
||||||
|
|
||||||
// Expect react select to have value 'value1'
|
|
||||||
await expect(editDrawer.locator('.rs__control .value-container')).toHaveText('value1')
|
|
||||||
// Close drawer by pressing escape
|
|
||||||
await page.keyboard.press('Escape')
|
|
||||||
await expect(editDrawer).toBeHidden()
|
|
||||||
|
|
||||||
// Select inline block again
|
|
||||||
await inlineBlock.click()
|
|
||||||
await wait(500)
|
|
||||||
|
|
||||||
// Press toolbar-popup__button-setKeyToDebug button of richtext editor
|
|
||||||
const toolbarPopup = richTextField.locator('.toolbar-popup__button-setKeyToDebug').first()
|
|
||||||
// Click it
|
|
||||||
await toolbarPopup.click()
|
|
||||||
await wait(3000)
|
|
||||||
|
|
||||||
// Open edit drawer, check if value is now value2, then exit
|
|
||||||
await inlineBlock.click()
|
|
||||||
await editButton.click()
|
|
||||||
await expect(editDrawer).toBeVisible()
|
|
||||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
|
||||||
await wait(500)
|
|
||||||
await expect(editDrawer.locator('.rs__control .value-container')).toHaveText('value2')
|
|
||||||
await page.keyboard.press('Escape')
|
|
||||||
await expect(editDrawer).toBeHidden()
|
|
||||||
|
|
||||||
// Save and check api result
|
|
||||||
await saveDocAndAssert(page)
|
|
||||||
await expect(async () => {
|
|
||||||
const lexicalDoc: LexicalField = (
|
|
||||||
await payload.find({
|
|
||||||
collection: lexicalFieldsSlug,
|
|
||||||
depth: 0,
|
|
||||||
overrideAccess: true,
|
|
||||||
where: {
|
|
||||||
title: {
|
|
||||||
equals: lexicalDocData.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).docs[0] as never
|
|
||||||
|
|
||||||
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
|
|
||||||
const firstParagraph: SerializedParagraphNode = lexicalField.root
|
|
||||||
.children[0] as SerializedParagraphNode
|
|
||||||
const inlineBlock: SerializedInlineBlockNode = firstParagraph
|
|
||||||
.children[1] as SerializedInlineBlockNode
|
|
||||||
|
|
||||||
await expect(inlineBlock.fields.key).toBe('value2')
|
|
||||||
}).toPass({
|
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('nested lexical editor in block', () => {
|
describe('nested lexical editor in block', () => {
|
||||||
test('should type and save typed text', async () => {
|
test('should type and save typed text', async () => {
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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)
|
|
||||||
|
|
||||||
const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node"
|
const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node"
|
||||||
await lexicalBlock.scrollIntoViewIfNeeded()
|
await lexicalBlock.scrollIntoViewIfNeeded()
|
||||||
@@ -382,23 +337,10 @@ describe('lexicalBlocks', () => {
|
|||||||
await expect(spanInSubEditor).toHaveText('Some text below relationship node 1 inserted text')
|
await expect(spanInSubEditor).toHaveText('Some text below relationship node 1 inserted text')
|
||||||
await saveDocAndAssert(page)
|
await saveDocAndAssert(page)
|
||||||
|
|
||||||
await expect(async () => {
|
await assertLexicalDoc({
|
||||||
const lexicalDoc: LexicalField = (
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
await payload.find({
|
const blockNode: SerializedBlockNode = lexicalWithBlocks.root
|
||||||
collection: lexicalFieldsSlug,
|
.children[4] as SerializedBlockNode
|
||||||
depth: 0,
|
|
||||||
overrideAccess: true,
|
|
||||||
where: {
|
|
||||||
title: {
|
|
||||||
equals: lexicalDocData.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).docs[0] as never
|
|
||||||
|
|
||||||
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
|
|
||||||
|
|
||||||
const blockNode: SerializedBlockNode = lexicalField.root.children[4] as SerializedBlockNode
|
|
||||||
|
|
||||||
const textNodeInBlockNodeRichText =
|
const textNodeInBlockNodeRichText =
|
||||||
blockNode.fields.richTextField.root.children[1].children[0]
|
blockNode.fields.richTextField.root.children[1].children[0]
|
||||||
@@ -406,18 +348,12 @@ describe('lexicalBlocks', () => {
|
|||||||
expect(textNodeInBlockNodeRichText.text).toBe(
|
expect(textNodeInBlockNodeRichText.text).toBe(
|
||||||
'Some text below relationship node 1 inserted text',
|
'Some text below relationship node 1 inserted text',
|
||||||
)
|
)
|
||||||
}).toPass({
|
},
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
test('should be able to bold text using floating select toolbar', async () => {
|
test('should be able to bold text using floating select toolbar', async () => {
|
||||||
// Reproduces https://github.com/payloadcms/payload/issues/4025
|
// Reproduces https://github.com/payloadcms/payload/issues/4025
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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)
|
|
||||||
|
|
||||||
const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node"
|
const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node"
|
||||||
await lexicalBlock.scrollIntoViewIfNeeded()
|
await lexicalBlock.scrollIntoViewIfNeeded()
|
||||||
@@ -461,22 +397,10 @@ describe('lexicalBlocks', () => {
|
|||||||
|
|
||||||
await saveDocAndAssert(page)
|
await saveDocAndAssert(page)
|
||||||
|
|
||||||
await expect(async () => {
|
await assertLexicalDoc({
|
||||||
const lexicalDoc: LexicalField = (
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
await payload.find({
|
const blockNode: SerializedBlockNode = lexicalWithBlocks.root
|
||||||
collection: lexicalFieldsSlug,
|
.children[4] as SerializedBlockNode
|
||||||
depth: 0,
|
|
||||||
overrideAccess: true,
|
|
||||||
where: {
|
|
||||||
title: {
|
|
||||||
equals: lexicalDocData.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).docs[0] as never
|
|
||||||
|
|
||||||
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
|
|
||||||
const blockNode: SerializedBlockNode = lexicalField.root.children[4] as SerializedBlockNode
|
|
||||||
const paragraphNodeInBlockNodeRichText = blockNode.fields.richTextField.root.children[1]
|
const paragraphNodeInBlockNodeRichText = blockNode.fields.richTextField.root.children[1]
|
||||||
|
|
||||||
expect(paragraphNodeInBlockNodeRichText.children).toHaveLength(2)
|
expect(paragraphNodeInBlockNodeRichText.children).toHaveLength(2)
|
||||||
@@ -489,19 +413,13 @@ describe('lexicalBlocks', () => {
|
|||||||
|
|
||||||
expect(boldNode.text).toBe('elationship node 1')
|
expect(boldNode.text).toBe('elationship node 1')
|
||||||
expect(boldNode.format).toBe(1)
|
expect(boldNode.format).toBe(1)
|
||||||
}).toPass({
|
},
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should be able to select text, make it an external link and receive the updated link value', async () => {
|
test('should be able to select text, make it an external link and receive the updated link value', async () => {
|
||||||
// Reproduces https://github.com/payloadcms/payload/issues/4025
|
// Reproduces https://github.com/payloadcms/payload/issues/4025
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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)
|
|
||||||
|
|
||||||
// Find span in contentEditable with text "Some text below relationship node"
|
// Find span in contentEditable with text "Some text below relationship node"
|
||||||
const spanInEditor = richTextField.locator('span').getByText('Upload Node:').first()
|
const spanInEditor = richTextField.locator('span').getByText('Upload Node:').first()
|
||||||
@@ -554,41 +472,21 @@ describe('lexicalBlocks', () => {
|
|||||||
await expect(linkInEditor).toHaveAttribute('href', 'https://www.payloadcms.com')
|
await expect(linkInEditor).toHaveAttribute('href', 'https://www.payloadcms.com')
|
||||||
|
|
||||||
// Make sure it's being returned from the API as well
|
// Make sure it's being returned from the API as well
|
||||||
await expect(async () => {
|
await assertLexicalDoc({
|
||||||
const lexicalDoc: LexicalField = (
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
await payload.find({
|
|
||||||
collection: lexicalFieldsSlug,
|
|
||||||
depth: 0,
|
|
||||||
overrideAccess: true,
|
|
||||||
where: {
|
|
||||||
title: {
|
|
||||||
equals: lexicalDocData.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).docs[0] as never
|
|
||||||
|
|
||||||
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
(
|
(
|
||||||
(lexicalField.root.children[0] as SerializedParagraphNode)
|
(lexicalWithBlocks.root.children[0] as SerializedParagraphNode)
|
||||||
.children[1] as SerializedLinkNode
|
.children[1] as SerializedLinkNode
|
||||||
).fields.url,
|
).fields.url,
|
||||||
).toBe('https://www.payloadcms.com')
|
).toBe('https://www.payloadcms.com')
|
||||||
}).toPass({
|
},
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('ensure slash menu is not hidden behind other blocks', async () => {
|
test('ensure slash menu is not hidden behind other blocks', async () => {
|
||||||
// This test makes sure there are no z-index issues here
|
// This test makes sure there are no z-index issues here
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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)
|
|
||||||
|
|
||||||
const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node"
|
const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node"
|
||||||
await lexicalBlock.scrollIntoViewIfNeeded()
|
await lexicalBlock.scrollIntoViewIfNeeded()
|
||||||
@@ -658,12 +556,7 @@ describe('lexicalBlocks', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
test('should allow adding new blocks to a sub-blocks field, part of a parent lexical blocks field', async () => {
|
test('should allow adding new blocks to a sub-blocks field, part of a parent lexical blocks field', async () => {
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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)
|
|
||||||
|
|
||||||
const lexicalBlock = richTextField.locator('.lexical-block').nth(3) // third: "Block Node, with Blocks Field, With RichText Field, With Relationship Node"
|
const lexicalBlock = richTextField.locator('.lexical-block').nth(3) // third: "Block Node, with Blocks Field, With RichText Field, With Relationship Node"
|
||||||
await lexicalBlock.scrollIntoViewIfNeeded()
|
await lexicalBlock.scrollIntoViewIfNeeded()
|
||||||
@@ -700,27 +593,10 @@ describe('lexicalBlocks', () => {
|
|||||||
|
|
||||||
await saveDocAndAssert(page)
|
await saveDocAndAssert(page)
|
||||||
|
|
||||||
await expect(async () => {
|
await assertLexicalDoc({
|
||||||
/**
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
* Using the local API, check if the data was saved correctly and
|
const blockNode: SerializedBlockNode = lexicalWithBlocks.root
|
||||||
* can be retrieved correctly
|
.children[5] as SerializedBlockNode
|
||||||
*/
|
|
||||||
|
|
||||||
const lexicalDoc: LexicalField = (
|
|
||||||
await payload.find({
|
|
||||||
collection: lexicalFieldsSlug,
|
|
||||||
depth: 0,
|
|
||||||
overrideAccess: true,
|
|
||||||
where: {
|
|
||||||
title: {
|
|
||||||
equals: lexicalDocData.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).docs[0] as never
|
|
||||||
|
|
||||||
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
|
|
||||||
const blockNode: SerializedBlockNode = lexicalField.root.children[5] as SerializedBlockNode
|
|
||||||
const subBlocks = blockNode.fields.subBlocks
|
const subBlocks = blockNode.fields.subBlocks
|
||||||
|
|
||||||
expect(subBlocks).toHaveLength(2)
|
expect(subBlocks).toHaveLength(2)
|
||||||
@@ -728,19 +604,13 @@ describe('lexicalBlocks', () => {
|
|||||||
const createdTextAreaBlock = subBlocks[1]
|
const createdTextAreaBlock = subBlocks[1]
|
||||||
|
|
||||||
expect(createdTextAreaBlock.content).toBe('text123')
|
expect(createdTextAreaBlock.content).toBe('text123')
|
||||||
}).toPass({
|
},
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Big test which tests a bunch of things: Creation of blocks via slash commands, creation of deeply nested sub-lexical-block fields via slash commands, properly populated deeply nested fields within those
|
// Big test which tests a bunch of things: Creation of blocks via slash commands, creation of deeply nested sub-lexical-block fields via slash commands, properly populated deeply nested fields within those
|
||||||
test('ensure creation of a lexical, lexical-field-block, which contains another lexical, lexical-and-upload-field-block, works and that the sub-upload field is properly populated', async () => {
|
test('ensure creation of a lexical, lexical-field-block, which contains another lexical, lexical-and-upload-field-block, works and that the sub-upload field is properly populated', async () => {
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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)
|
|
||||||
|
|
||||||
const lastParagraph = richTextField.locator('p').last()
|
const lastParagraph = richTextField.locator('p').last()
|
||||||
await lastParagraph.scrollIntoViewIfNeeded()
|
await lastParagraph.scrollIntoViewIfNeeded()
|
||||||
@@ -865,20 +735,8 @@ describe('lexicalBlocks', () => {
|
|||||||
await expect(paragraphInSubEditor).toHaveText('Some subText')
|
await expect(paragraphInSubEditor).toHaveText('Some subText')
|
||||||
|
|
||||||
// Check if the API result is populated correctly - Depth 0
|
// Check if the API result is populated correctly - Depth 0
|
||||||
await expect(async () => {
|
await assertLexicalDoc({
|
||||||
const lexicalDoc: LexicalField = (
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
await payload.find({
|
|
||||||
collection: lexicalFieldsSlug,
|
|
||||||
depth: 0,
|
|
||||||
overrideAccess: true,
|
|
||||||
where: {
|
|
||||||
title: {
|
|
||||||
equals: lexicalDocData.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).docs[0] as never
|
|
||||||
|
|
||||||
const uploadDoc: Upload = (
|
const uploadDoc: Upload = (
|
||||||
await payload.find({
|
await payload.find({
|
||||||
collection: 'uploads',
|
collection: 'uploads',
|
||||||
@@ -892,8 +750,7 @@ describe('lexicalBlocks', () => {
|
|||||||
})
|
})
|
||||||
).docs[0] as never
|
).docs[0] as never
|
||||||
|
|
||||||
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
|
const richTextBlock: SerializedBlockNode = lexicalWithBlocks.root
|
||||||
const richTextBlock: SerializedBlockNode = lexicalField.root
|
|
||||||
.children[13] as SerializedBlockNode
|
.children[13] as SerializedBlockNode
|
||||||
const subRichTextBlock: SerializedBlockNode = richTextBlock.fields.richTextField.root
|
const subRichTextBlock: SerializedBlockNode = richTextBlock.fields.richTextField.root
|
||||||
.children[1] as SerializedBlockNode // index 0 and 2 are paragraphs created by default around the block node when a new block is added via slash command
|
.children[1] as SerializedBlockNode // index 0 and 2 are paragraphs created by default around the block node when a new block is added via slash command
|
||||||
@@ -903,26 +760,13 @@ describe('lexicalBlocks', () => {
|
|||||||
|
|
||||||
expect(subSubRichTextField.root.children[0].children[0].text).toBe('Some subText')
|
expect(subSubRichTextField.root.children[0].children[0].text).toBe('Some subText')
|
||||||
expect(subSubUploadField).toBe(uploadDoc.id)
|
expect(subSubUploadField).toBe(uploadDoc.id)
|
||||||
}).toPass({
|
},
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check if the API result is populated correctly - Depth 1
|
// Check if the API result is populated correctly - Depth 1
|
||||||
await expect(async () => {
|
await assertLexicalDoc({
|
||||||
// Now with depth 1
|
|
||||||
const lexicalDocDepth1: LexicalField = (
|
|
||||||
await payload.find({
|
|
||||||
collection: lexicalFieldsSlug,
|
|
||||||
depth: 1,
|
depth: 1,
|
||||||
overrideAccess: true,
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
where: {
|
|
||||||
title: {
|
|
||||||
equals: lexicalDocData.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).docs[0] as never
|
|
||||||
|
|
||||||
const uploadDoc: Upload = (
|
const uploadDoc: Upload = (
|
||||||
await payload.find({
|
await payload.find({
|
||||||
collection: 'uploads',
|
collection: 'uploads',
|
||||||
@@ -936,8 +780,7 @@ describe('lexicalBlocks', () => {
|
|||||||
})
|
})
|
||||||
).docs[0] as never
|
).docs[0] as never
|
||||||
|
|
||||||
const lexicalField2: SerializedEditorState = lexicalDocDepth1.lexicalWithBlocks
|
const richTextBlock2: SerializedBlockNode = lexicalWithBlocks.root
|
||||||
const richTextBlock2: SerializedBlockNode = lexicalField2.root
|
|
||||||
.children[13] as SerializedBlockNode
|
.children[13] as SerializedBlockNode
|
||||||
const subRichTextBlock2: SerializedBlockNode = richTextBlock2.fields.richTextField.root
|
const subRichTextBlock2: SerializedBlockNode = richTextBlock2.fields.richTextField.root
|
||||||
.children[1] as SerializedBlockNode // index 0 and 2 are paragraphs created by default around the block node when a new block is added via slash command
|
.children[1] as SerializedBlockNode // index 0 and 2 are paragraphs created by default around the block node when a new block is added via slash command
|
||||||
@@ -948,20 +791,14 @@ describe('lexicalBlocks', () => {
|
|||||||
expect(subSubRichTextField2.root.children[0].children[0].text).toBe('Some subText')
|
expect(subSubRichTextField2.root.children[0].children[0].text).toBe('Some subText')
|
||||||
expect(subSubUploadField2.id).toBe(uploadDoc.id)
|
expect(subSubUploadField2.id).toBe(uploadDoc.id)
|
||||||
expect(subSubUploadField2.filename).toBe(uploadDoc.filename)
|
expect(subSubUploadField2.filename).toBe(uploadDoc.filename)
|
||||||
}).toPass({
|
},
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should allow changing values of two different radio button blocks independently', async () => {
|
test('should allow changing values of two different radio button blocks independently', async () => {
|
||||||
// This test ensures that https://github.com/payloadcms/payload/issues/3911 does not happen again
|
// This test ensures that https://github.com/payloadcms/payload/issues/3911 does not happen again
|
||||||
|
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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(1000) // Wait for form state requests to be done, to reduce flakes
|
await wait(1000) // Wait for form state requests to be done, to reduce flakes
|
||||||
|
|
||||||
const radioButtonBlock1 = richTextField.locator('.lexical-block').nth(5)
|
const radioButtonBlock1 = richTextField.locator('.lexical-block').nth(5)
|
||||||
@@ -999,28 +836,16 @@ describe('lexicalBlocks', () => {
|
|||||||
|
|
||||||
await saveDocAndAssert(page)
|
await saveDocAndAssert(page)
|
||||||
|
|
||||||
await expect(async () => {
|
await assertLexicalDoc({
|
||||||
const lexicalDoc: LexicalField = (
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
await payload.find({
|
const radio1: SerializedBlockNode = lexicalWithBlocks.root
|
||||||
collection: lexicalFieldsSlug,
|
.children[8] as SerializedBlockNode
|
||||||
depth: 0,
|
const radio2: SerializedBlockNode = lexicalWithBlocks.root
|
||||||
overrideAccess: true,
|
.children[9] as SerializedBlockNode
|
||||||
where: {
|
|
||||||
title: {
|
|
||||||
equals: lexicalDocData.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).docs[0] as never
|
|
||||||
|
|
||||||
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
|
|
||||||
const radio1: SerializedBlockNode = lexicalField.root.children[8] as SerializedBlockNode
|
|
||||||
const radio2: SerializedBlockNode = lexicalField.root.children[9] as SerializedBlockNode
|
|
||||||
|
|
||||||
expect(radio1.fields.radioButtons).toBe('option2')
|
expect(radio1.fields.radioButtons).toBe('option2')
|
||||||
expect(radio2.fields.radioButtons).toBe('option3')
|
expect(radio2.fields.radioButtons).toBe('option3')
|
||||||
}).toPass({
|
},
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1031,12 +856,7 @@ describe('lexicalBlocks', () => {
|
|||||||
// 2. Focus nested editor and write something
|
// 2. Focus nested editor and write something
|
||||||
// 3. In the issue, after writing one character, the cursor focuses back into the parent editor
|
// 3. In the issue, after writing one character, the cursor focuses back into the parent editor
|
||||||
|
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. Focus parent editor
|
* 1. Focus parent editor
|
||||||
@@ -1130,14 +950,7 @@ describe('lexicalBlocks', () => {
|
|||||||
|
|
||||||
test('should respect row removal in nested array field after navigating away from lexical document, then navigating back', async () => {
|
test('should respect row removal in nested array field after navigating away from lexical document, then navigating back', async () => {
|
||||||
// This test verifies an issue where a lexical editor with blocks disappears when navigating away from the lexical document, then navigating back, without a hard refresh
|
// This test verifies an issue where a lexical editor with blocks disappears when navigating away from the lexical document, then navigating back, without a hard refresh
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = await navigateToLexicalFields()
|
||||||
|
|
||||||
// Wait for lexical to be loaded up fully
|
|
||||||
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
|
await wait(1000) // Wait for form state requests to be done, to reduce flakes
|
||||||
|
|
||||||
const conditionalArrayBlock = richTextField.locator('.lexical-block').nth(7)
|
const conditionalArrayBlock = richTextField.locator('.lexical-block').nth(7)
|
||||||
@@ -1158,12 +971,7 @@ describe('lexicalBlocks', () => {
|
|||||||
|
|
||||||
test('ensure pre-seeded uploads node is visible', async () => {
|
test('ensure pre-seeded uploads node is visible', async () => {
|
||||||
// Due to issues with the relationships condition, we had issues with that not being visible. Checking for visibility ensures there is no breakage there again
|
// Due to issues with the relationships condition, we had issues with that not being visible. Checking for visibility ensures there is no breakage there again
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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)
|
|
||||||
|
|
||||||
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)
|
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()
|
await uploadBlock.scrollIntoViewIfNeeded()
|
||||||
@@ -1175,12 +983,7 @@ describe('lexicalBlocks', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should respect required error state in deeply nested text field', async () => {
|
test('should respect required error state in deeply nested text field', async () => {
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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)
|
await wait(300)
|
||||||
|
|
||||||
@@ -1226,13 +1029,7 @@ describe('lexicalBlocks', () => {
|
|||||||
|
|
||||||
// Reproduces https://github.com/payloadcms/payload/issues/6631
|
// Reproduces https://github.com/payloadcms/payload/issues/6631
|
||||||
test('ensure tabs field within lexical block correctly loads and saves data', async () => {
|
test('ensure tabs field within lexical block correctly loads and saves data', async () => {
|
||||||
await navigateToLexicalFields()
|
const { richTextField } = 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)
|
|
||||||
|
|
||||||
const tabsBlock = richTextField.locator('.lexical-block').nth(8)
|
const tabsBlock = richTextField.locator('.lexical-block').nth(8)
|
||||||
await wait(300)
|
await wait(300)
|
||||||
@@ -1276,28 +1073,14 @@ describe('lexicalBlocks', () => {
|
|||||||
await tab2Button.click()
|
await tab2Button.click()
|
||||||
await expect(tab2Text1Field).toHaveValue('Some text2 changed')
|
await expect(tab2Text1Field).toHaveValue('Some text2 changed')
|
||||||
|
|
||||||
await expect(async () => {
|
await assertLexicalDoc({
|
||||||
const lexicalDoc: LexicalField = (
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
await payload.find({
|
const tabBlockData: SerializedBlockNode = lexicalWithBlocks.root
|
||||||
collection: lexicalFieldsSlug,
|
|
||||||
depth: 0,
|
|
||||||
overrideAccess: true,
|
|
||||||
where: {
|
|
||||||
title: {
|
|
||||||
equals: lexicalDocData.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).docs[0] as never
|
|
||||||
|
|
||||||
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
|
|
||||||
const tabBlockData: SerializedBlockNode = lexicalField.root
|
|
||||||
.children[13] as SerializedBlockNode
|
.children[13] as SerializedBlockNode
|
||||||
|
|
||||||
expect(tabBlockData.fields.tab1.text1).toBe('Some text1 changed')
|
expect(tabBlockData.fields.tab1.text1).toBe('Some text1 changed')
|
||||||
expect(tabBlockData.fields.tab2.text2).toBe('Some text2 changed')
|
expect(tabBlockData.fields.tab2.text2).toBe('Some text2 changed')
|
||||||
}).toPass({
|
},
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1343,4 +1126,145 @@ describe('lexicalBlocks', () => {
|
|||||||
).toHaveText('Some Description')
|
).toHaveText('Some Description')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('inline blocks', () => {
|
||||||
|
test('ensure inline blocks can be created and its values can be mutated from outside their form', async () => {
|
||||||
|
const { richTextField } = await navigateToLexicalFields()
|
||||||
|
const { inlineBlockDrawer, saveDrawer } = await createInlineBlock({
|
||||||
|
richTextField,
|
||||||
|
name: 'My Inline Block',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Click on react select in drawer, select 'value1'
|
||||||
|
await inlineBlockDrawer.locator('.rs__control .value-container').first().click()
|
||||||
|
await wait(500)
|
||||||
|
await expect(inlineBlockDrawer.locator('.rs__option').first()).toBeVisible()
|
||||||
|
await expect(inlineBlockDrawer.locator('.rs__option').first()).toContainText('value1')
|
||||||
|
await inlineBlockDrawer.locator('.rs__option').first().click()
|
||||||
|
|
||||||
|
const { inlineBlock, openEditDrawer } = await saveDrawer()
|
||||||
|
// Save document
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
// Check if the API result is correct
|
||||||
|
await assertLexicalDoc({
|
||||||
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
|
const firstParagraph: SerializedParagraphNode = lexicalWithBlocks.root
|
||||||
|
.children[0] as SerializedParagraphNode
|
||||||
|
const inlineBlock: SerializedInlineBlockNode = firstParagraph
|
||||||
|
.children[1] as SerializedInlineBlockNode
|
||||||
|
|
||||||
|
expect(inlineBlock.fields.key).toBe('value1')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { editDrawer } = await openEditDrawer()
|
||||||
|
|
||||||
|
// Expect react select to have value 'value1'
|
||||||
|
await expect(editDrawer.locator('.rs__control .value-container')).toHaveText('value1')
|
||||||
|
// Close drawer by pressing escape
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await expect(editDrawer).toBeHidden()
|
||||||
|
|
||||||
|
// Select inline block again
|
||||||
|
await inlineBlock.click()
|
||||||
|
await wait(500)
|
||||||
|
|
||||||
|
// Press toolbar-popup__button-setKeyToDebug button of richtext editor
|
||||||
|
const toolbarPopup = richTextField.locator('.toolbar-popup__button-setKeyToDebug').first()
|
||||||
|
// Click it
|
||||||
|
await toolbarPopup.click()
|
||||||
|
await wait(1000)
|
||||||
|
|
||||||
|
// Open edit drawer, check if value is now value2, then exit
|
||||||
|
await inlineBlock.click()
|
||||||
|
await openEditDrawer()
|
||||||
|
await expect(editDrawer.locator('.rs__control .value-container')).toHaveText('value2')
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await expect(editDrawer).toBeHidden()
|
||||||
|
|
||||||
|
// Save and check api result
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
await assertLexicalDoc({
|
||||||
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
|
const firstParagraph: SerializedParagraphNode = lexicalWithBlocks.root
|
||||||
|
.children[0] as SerializedParagraphNode
|
||||||
|
const inlineBlock: SerializedInlineBlockNode = firstParagraph
|
||||||
|
.children[1] as SerializedInlineBlockNode
|
||||||
|
|
||||||
|
await expect(inlineBlock.fields.key).toBe('value2')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ensure upload fields within inline blocks store and populate correctly', async () => {
|
||||||
|
const { richTextField } = await navigateToLexicalFields()
|
||||||
|
const { inlineBlockDrawer, saveDrawer } = await createInlineBlock({
|
||||||
|
richTextField,
|
||||||
|
name: 'Avatar Group',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Click button that says Add Avatar
|
||||||
|
await inlineBlockDrawer.getByRole('button', { name: 'Add Avatar' }).click()
|
||||||
|
await inlineBlockDrawer.getByRole('button', { name: 'Choose from existing' }).click()
|
||||||
|
const uploadDrawer = page.locator('dialog[id^=list-drawer_2_]').first()
|
||||||
|
await uploadDrawer.getByText('payload.jpg').click()
|
||||||
|
await expect(inlineBlockDrawer.locator('.upload-relationship-details__filename')).toHaveText(
|
||||||
|
'payload.jpg',
|
||||||
|
)
|
||||||
|
await saveDrawer()
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
|
||||||
|
await assertLexicalDoc({
|
||||||
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
|
const firstParagraph: SerializedParagraphNode = lexicalWithBlocks.root
|
||||||
|
.children[0] as SerializedParagraphNode
|
||||||
|
const inlineBlock: SerializedInlineBlockNode = firstParagraph
|
||||||
|
.children[1] as SerializedInlineBlockNode
|
||||||
|
|
||||||
|
const uploadDoc: Upload = (
|
||||||
|
await payload.find({
|
||||||
|
collection: 'uploads',
|
||||||
|
depth: 0,
|
||||||
|
overrideAccess: true,
|
||||||
|
where: {
|
||||||
|
filename: {
|
||||||
|
equals: 'payload.jpg',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).docs[0] as never
|
||||||
|
expect(inlineBlock.fields.blockType).toBe('AvatarGroup')
|
||||||
|
expect(inlineBlock.fields.avatars?.length).toBe(1)
|
||||||
|
expect(inlineBlock.fields.avatars[0].image).toBe(uploadDoc.id)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await assertLexicalDoc({
|
||||||
|
depth: 1,
|
||||||
|
fn: async ({ lexicalWithBlocks }) => {
|
||||||
|
const firstParagraph: SerializedParagraphNode = lexicalWithBlocks.root
|
||||||
|
.children[0] as SerializedParagraphNode
|
||||||
|
const inlineBlock: SerializedInlineBlockNode = firstParagraph
|
||||||
|
.children[1] as SerializedInlineBlockNode
|
||||||
|
|
||||||
|
const uploadDoc: Upload = (
|
||||||
|
await payload.find({
|
||||||
|
collection: 'uploads',
|
||||||
|
depth: 0,
|
||||||
|
overrideAccess: true,
|
||||||
|
where: {
|
||||||
|
filename: {
|
||||||
|
equals: 'payload.jpg',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).docs[0] as never
|
||||||
|
expect(inlineBlock.fields.blockType).toBe('AvatarGroup')
|
||||||
|
expect(inlineBlock.fields.avatars?.length).toBe(1)
|
||||||
|
expect(inlineBlock.fields.avatars[0].image.id).toBe(uploadDoc.id)
|
||||||
|
expect(inlineBlock.fields.avatars[0].image.text).toBe(uploadDoc.text)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ describe('lexicalMain', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname }))
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
|
||||||
|
|
||||||
context = await browser.newContext()
|
context = await browser.newContext()
|
||||||
page = await context.newPage()
|
page = await context.newPage()
|
||||||
|
|||||||
@@ -176,6 +176,25 @@ const editorConfig: ServerEditorConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
inlineBlocks: [
|
inlineBlocks: [
|
||||||
|
{
|
||||||
|
slug: 'AvatarGroup',
|
||||||
|
interfaceName: 'AvatarGroupBlock',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'avatars',
|
||||||
|
type: 'array',
|
||||||
|
minRows: 1,
|
||||||
|
maxRows: 6,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'image',
|
||||||
|
type: 'upload',
|
||||||
|
relationTo: 'uploads',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slug: 'myInlineBlock',
|
slug: 'myInlineBlock',
|
||||||
admin: {
|
admin: {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ describe('Number', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ describe('Point', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe('Radio', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ describe('relationship', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe('Rich Text', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ serverURL } = await initPayloadE2ENoConfig({
|
;({ serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ describe('Row', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ describe('Radio', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ describe('Tabs', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ describe('Tabs', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ describe('Text', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe('Radio', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ describe('Upload', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ describe('Upload with restrictions', () => {
|
|||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||||
dirname,
|
dirname,
|
||||||
// prebuild,
|
// prebuild,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -3374,6 +3374,21 @@ export interface LexicalBlocksRadioButtonsBlock {
|
|||||||
blockName?: string | null;
|
blockName?: string | null;
|
||||||
blockType: 'radioButtons';
|
blockType: 'radioButtons';
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "AvatarGroupBlock".
|
||||||
|
*/
|
||||||
|
export interface AvatarGroupBlock {
|
||||||
|
avatars?:
|
||||||
|
| {
|
||||||
|
image?: (string | null) | Upload;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'AvatarGroup';
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "auth".
|
* via the `definition` "auth".
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ let serverURL: string
|
|||||||
describe('Locked Documents', () => {
|
describe('Locked Documents', () => {
|
||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname }))
|
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
|
||||||
|
|
||||||
globalUrl = new AdminUrlUtil(serverURL, 'menu')
|
globalUrl = new AdminUrlUtil(serverURL, 'menu')
|
||||||
postsUrl = new AdminUrlUtil(serverURL, 'posts')
|
postsUrl = new AdminUrlUtil(serverURL, 'posts')
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const dirname = path.dirname(filename)
|
|||||||
|
|
||||||
dotenv.config({ path: path.resolve(dirname, 'test.env') })
|
dotenv.config({ path: path.resolve(dirname, 'test.env') })
|
||||||
|
|
||||||
let multiplier = process.env.CI ? 3 : 0.5
|
let multiplier = process.env.CI ? 3 : 0.75
|
||||||
let smallMultiplier = process.env.CI ? 2 : 0.75
|
let smallMultiplier = process.env.CI ? 2 : 0.75
|
||||||
|
|
||||||
export const TEST_TIMEOUT_LONG = 640000 * multiplier // 8*3 minutes - used as timeOut for the beforeAll
|
export const TEST_TIMEOUT_LONG = 640000 * multiplier // 8*3 minutes - used as timeOut for the beforeAll
|
||||||
|
|||||||
Reference in New Issue
Block a user