diff --git a/packages/richtext-lexical/src/features/blocks/client/component/index.tsx b/packages/richtext-lexical/src/features/blocks/client/component/index.tsx index 30ebfe1896..e0141b6568 100644 --- a/packages/richtext-lexical/src/features/blocks/client/component/index.tsx +++ b/packages/richtext-lexical/src/features/blocks/client/component/index.tsx @@ -491,14 +491,13 @@ export const BlockComponent: React.FC = (props) => { fields={clientBlock?.fields} initialState={initialState} 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 - const newData: any = reduceFieldsToValues(formState) newData.blockType = formData.blockType editor.update(() => { const node = $getNodeByKey(nodeKey) if (node && $isBlockNode(node)) { - node.setFields(newData, true) + node.setFields(newData as BlockFields, true) } }) toggleDrawer() diff --git a/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx b/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx index 823f321c24..27b3add8a7 100644 --- a/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx +++ b/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx @@ -3,7 +3,7 @@ import React, { createContext, useCallback, useEffect, useMemo, useRef } from 'react' const baseClass = 'inline-block' -import type { BlocksFieldClient, FormState } from 'payload' +import type { BlocksFieldClient, Data, FormState } from 'payload' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection' @@ -33,7 +33,6 @@ import { KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, } from 'lexical' -import { reduceFieldsToValues } from 'payload/shared' import './index.scss' @@ -308,13 +307,13 @@ export const InlineBlockComponent: React.FC = (props) => { * HANDLE FORM SUBMIT */ const onFormSubmit = useCallback( - (formState: FormState) => { - const newData: any = reduceFieldsToValues(formState) + (formState: FormState, newData: Data) => { newData.blockType = formData.blockType + editor.update(() => { const node = $getNodeByKey(nodeKey) if (node && $isInlineBlockNode(node)) { - node.setFields(newData, true) + node.setFields(newData as InlineBlockFields, true) } }) }, @@ -414,8 +413,8 @@ export const InlineBlockComponent: React.FC = (props) => { fields={clientBlock?.fields} initialState={initialState || {}} onChange={[onChange]} - onSubmit={(formState) => { - onFormSubmit(formState) + onSubmit={(formState, data) => { + onFormSubmit(formState, data) toggleDrawer() }} uuid={uuid()} diff --git a/test/fields/collections/Array/e2e.spec.ts b/test/fields/collections/Array/e2e.spec.ts index 5b3c704dcb..42f1dac330 100644 --- a/test/fields/collections/Array/e2e.spec.ts +++ b/test/fields/collections/Array/e2e.spec.ts @@ -36,7 +36,7 @@ describe('Array', () => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, })) diff --git a/test/fields/collections/Collapsible/e2e.spec.ts b/test/fields/collections/Collapsible/e2e.spec.ts index f0be6ef78d..8e6f13b7e6 100644 --- a/test/fields/collections/Collapsible/e2e.spec.ts +++ b/test/fields/collections/Collapsible/e2e.spec.ts @@ -32,7 +32,7 @@ describe('Collapsibles', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/ConditionalLogic/e2e.spec.ts b/test/fields/collections/ConditionalLogic/e2e.spec.ts index 4cad02b081..fba7237e00 100644 --- a/test/fields/collections/ConditionalLogic/e2e.spec.ts +++ b/test/fields/collections/ConditionalLogic/e2e.spec.ts @@ -45,7 +45,7 @@ describe('Conditional Logic', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/CustomID/e2e.spec.ts b/test/fields/collections/CustomID/e2e.spec.ts index ec7c709361..94c1613136 100644 --- a/test/fields/collections/CustomID/e2e.spec.ts +++ b/test/fields/collections/CustomID/e2e.spec.ts @@ -35,7 +35,7 @@ describe('Custom IDs', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Date/e2e.spec.ts b/test/fields/collections/Date/e2e.spec.ts index 61fb4c1399..2473c96083 100644 --- a/test/fields/collections/Date/e2e.spec.ts +++ b/test/fields/collections/Date/e2e.spec.ts @@ -36,7 +36,7 @@ describe('Date', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Email/e2e.spec.ts b/test/fields/collections/Email/e2e.spec.ts index 22887772f2..855e3c5f50 100644 --- a/test/fields/collections/Email/e2e.spec.ts +++ b/test/fields/collections/Email/e2e.spec.ts @@ -35,7 +35,7 @@ describe('Email', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Indexed/e2e.spec.ts b/test/fields/collections/Indexed/e2e.spec.ts index 71de2e4e2d..7afc703274 100644 --- a/test/fields/collections/Indexed/e2e.spec.ts +++ b/test/fields/collections/Indexed/e2e.spec.ts @@ -32,7 +32,7 @@ describe('Radio', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/JSON/e2e.spec.ts b/test/fields/collections/JSON/e2e.spec.ts index e7f4a8f2ad..139491afd9 100644 --- a/test/fields/collections/JSON/e2e.spec.ts +++ b/test/fields/collections/JSON/e2e.spec.ts @@ -36,7 +36,7 @@ describe('JSON', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Lexical/e2e/blocks/e2e.spec.ts b/test/fields/collections/Lexical/e2e/blocks/e2e.spec.ts index 5f8601703d..583ab034ec 100644 --- a/test/fields/collections/Lexical/e2e/blocks/e2e.spec.ts +++ b/test/fields/collections/Lexical/e2e/blocks/e2e.spec.ts @@ -8,7 +8,7 @@ import type { SerializedParagraphNode, SerializedTextNode, } 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 path from 'path' @@ -49,7 +49,9 @@ let serverURL: string async function navigateToLexicalFields( navigateToListView: boolean = true, localized: boolean = false, -) { +): Promise<{ + richTextField: Locator +}> { if (navigateToListView) { const url: AdminUrlUtil = new AdminUrlUtil( serverURL, @@ -65,13 +67,135 @@ async function navigateToLexicalFields( await linkToDoc.click() 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 }> + }> +}> { + 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 +}) { + 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', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname })) + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname })) context = await browser.newContext() 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 () => { - 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 { richTextField } = await navigateToLexicalFields() const lastParagraph = richTextField.locator('p').last() await lastParagraph.scrollIntoViewIfNeeded() @@ -178,188 +297,24 @@ describe('lexicalBlocks', () => { await expect(newRSCBlock.locator('.collapsible__content')).toHaveText('Data: value2') // Check if the API result is correct + await assertLexicalDoc({ + fn: async ({ lexicalWithBlocks }) => { + const rscBlock: SerializedBlockNode = lexicalWithBlocks.root + .children[14] as SerializedBlockNode + const paragraphBlock: SerializedBlockNode = lexicalWithBlocks.root + .children[12] as SerializedBlockNode - // TODO: - 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 rscBlock: SerializedBlockNode = lexicalField.root.children[14] as SerializedBlockNode - const paragraphBlock: SerializedBlockNode = lexicalField.root - .children[12] as SerializedBlockNode - - expect(rscBlock.fields.blockType).toBe('BlockRSC') - expect(rscBlock.fields.key).toBe('value2') - expect((paragraphBlock.children[0] as SerializedTextNode).text).toBe('123') - 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, + expect(rscBlock.fields.blockType).toBe('BlockRSC') + expect(rscBlock.fields.key).toBe('value2') + expect((paragraphBlock.children[0] as SerializedTextNode).text).toBe('123') + expect((paragraphBlock.children[0] as SerializedTextNode).format).toBe(1) + }, }) }) describe('nested lexical editor in block', () => { test('should type and save typed text', 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 { richTextField } = await navigateToLexicalFields() const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node" await lexicalBlock.scrollIntoViewIfNeeded() @@ -382,42 +337,23 @@ describe('lexicalBlocks', () => { await expect(spanInSubEditor).toHaveText('Some text below relationship node 1 inserted text') 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 + await assertLexicalDoc({ + fn: async ({ lexicalWithBlocks }) => { + const blockNode: SerializedBlockNode = lexicalWithBlocks.root + .children[4] as SerializedBlockNode - const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks + const textNodeInBlockNodeRichText = + blockNode.fields.richTextField.root.children[1].children[0] - const blockNode: SerializedBlockNode = lexicalField.root.children[4] as SerializedBlockNode - - const textNodeInBlockNodeRichText = - blockNode.fields.richTextField.root.children[1].children[0] - - expect(textNodeInBlockNodeRichText.text).toBe( - 'Some text below relationship node 1 inserted text', - ) - }).toPass({ - timeout: POLL_TOPASS_TIMEOUT, + expect(textNodeInBlockNodeRichText.text).toBe( + 'Some text below relationship node 1 inserted text', + ) + }, }) }) test('should be able to bold text using floating select toolbar', async () => { // Reproduces https://github.com/payloadcms/payload/issues/4025 - 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 { richTextField } = await navigateToLexicalFields() const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node" await lexicalBlock.scrollIntoViewIfNeeded() @@ -461,47 +397,29 @@ describe('lexicalBlocks', () => { 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 + await assertLexicalDoc({ + fn: async ({ lexicalWithBlocks }) => { + const blockNode: SerializedBlockNode = lexicalWithBlocks.root + .children[4] as SerializedBlockNode + const paragraphNodeInBlockNodeRichText = blockNode.fields.richTextField.root.children[1] - const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks - const blockNode: SerializedBlockNode = lexicalField.root.children[4] as SerializedBlockNode - const paragraphNodeInBlockNodeRichText = blockNode.fields.richTextField.root.children[1] + expect(paragraphNodeInBlockNodeRichText.children).toHaveLength(2) - expect(paragraphNodeInBlockNodeRichText.children).toHaveLength(2) + const textNode1: SerializedTextNode = paragraphNodeInBlockNodeRichText.children[0] + const boldNode: SerializedTextNode = paragraphNodeInBlockNodeRichText.children[1] - const textNode1: SerializedTextNode = paragraphNodeInBlockNodeRichText.children[0] - const boldNode: SerializedTextNode = paragraphNodeInBlockNodeRichText.children[1] + expect(textNode1.text).toBe('Some text below r') + expect(textNode1.format).toBe(0) - expect(textNode1.text).toBe('Some text below r') - expect(textNode1.format).toBe(0) - - expect(boldNode.text).toBe('elationship node 1') - expect(boldNode.format).toBe(1) - }).toPass({ - timeout: POLL_TOPASS_TIMEOUT, + expect(boldNode.text).toBe('elationship node 1') + expect(boldNode.format).toBe(1) + }, }) }) 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 - 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 { richTextField } = await navigateToLexicalFields() // Find span in contentEditable with text "Some text below relationship node" const spanInEditor = richTextField.locator('span').getByText('Upload Node:').first() @@ -554,41 +472,21 @@ describe('lexicalBlocks', () => { await expect(linkInEditor).toHaveAttribute('href', 'https://www.payloadcms.com') // Make sure it's being returned from the API as well - 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 - - expect( - ( - (lexicalField.root.children[0] as SerializedParagraphNode) - .children[1] as SerializedLinkNode - ).fields.url, - ).toBe('https://www.payloadcms.com') - }).toPass({ - timeout: POLL_TOPASS_TIMEOUT, + await assertLexicalDoc({ + fn: async ({ lexicalWithBlocks }) => { + expect( + ( + (lexicalWithBlocks.root.children[0] as SerializedParagraphNode) + .children[1] as SerializedLinkNode + ).fields.url, + ).toBe('https://www.payloadcms.com') + }, }) }) test('ensure slash menu is not hidden behind other blocks', async () => { // This test makes sure there are no z-index issues here - 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 { richTextField } = await navigateToLexicalFields() const lexicalBlock = richTextField.locator('.lexical-block').nth(2) // third: "Block Node, with RichText Field, with Relationship Node" 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 () => { - 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 { richTextField } = await navigateToLexicalFields() const lexicalBlock = richTextField.locator('.lexical-block').nth(3) // third: "Block Node, with Blocks Field, With RichText Field, With Relationship Node" await lexicalBlock.scrollIntoViewIfNeeded() @@ -700,47 +593,24 @@ describe('lexicalBlocks', () => { await saveDocAndAssert(page) - await expect(async () => { - /** - * Using the local API, check if the data was saved correctly and - * can be retrieved correctly - */ + await assertLexicalDoc({ + fn: async ({ lexicalWithBlocks }) => { + const blockNode: SerializedBlockNode = lexicalWithBlocks.root + .children[5] as SerializedBlockNode + const subBlocks = blockNode.fields.subBlocks - const lexicalDoc: LexicalField = ( - await payload.find({ - collection: lexicalFieldsSlug, - depth: 0, - overrideAccess: true, - where: { - title: { - equals: lexicalDocData.title, - }, - }, - }) - ).docs[0] as never + expect(subBlocks).toHaveLength(2) - const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks - const blockNode: SerializedBlockNode = lexicalField.root.children[5] as SerializedBlockNode - const subBlocks = blockNode.fields.subBlocks + const createdTextAreaBlock = subBlocks[1] - expect(subBlocks).toHaveLength(2) - - const createdTextAreaBlock = subBlocks[1] - - expect(createdTextAreaBlock.content).toBe('text123') - }).toPass({ - timeout: POLL_TOPASS_TIMEOUT, + expect(createdTextAreaBlock.content).toBe('text123') + }, }) }) // 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 () => { - 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 { richTextField } = await navigateToLexicalFields() const lastParagraph = richTextField.locator('p').last() await lastParagraph.scrollIntoViewIfNeeded() @@ -865,103 +735,70 @@ describe('lexicalBlocks', () => { await expect(paragraphInSubEditor).toHaveText('Some subText') // Check if the API result is populated correctly - Depth 0 - await expect(async () => { - const lexicalDoc: LexicalField = ( - await payload.find({ - collection: lexicalFieldsSlug, - depth: 0, - overrideAccess: true, - where: { - title: { - equals: lexicalDocData.title, + await assertLexicalDoc({ + fn: async ({ lexicalWithBlocks }) => { + const uploadDoc: Upload = ( + await payload.find({ + collection: 'uploads', + depth: 0, + overrideAccess: true, + where: { + filename: { + equals: 'payload.jpg', + }, }, - }, - }) - ).docs[0] as never + }) + ).docs[0] as never - const uploadDoc: Upload = ( - await payload.find({ - collection: 'uploads', - depth: 0, - overrideAccess: true, - where: { - filename: { - equals: 'payload.jpg', - }, - }, - }) - ).docs[0] as never + const richTextBlock: SerializedBlockNode = lexicalWithBlocks.root + .children[13] as SerializedBlockNode + 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 - const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks - const richTextBlock: SerializedBlockNode = lexicalField.root - .children[13] as SerializedBlockNode - 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 + const subSubRichTextField = subRichTextBlock.fields.subRichTextField + const subSubUploadField = subRichTextBlock.fields.subUploadField - const subSubRichTextField = subRichTextBlock.fields.subRichTextField - const subSubUploadField = subRichTextBlock.fields.subUploadField - - expect(subSubRichTextField.root.children[0].children[0].text).toBe('Some subText') - expect(subSubUploadField).toBe(uploadDoc.id) - }).toPass({ - timeout: POLL_TOPASS_TIMEOUT, + expect(subSubRichTextField.root.children[0].children[0].text).toBe('Some subText') + expect(subSubUploadField).toBe(uploadDoc.id) + }, }) // Check if the API result is populated correctly - Depth 1 - await expect(async () => { - // Now with depth 1 - const lexicalDocDepth1: LexicalField = ( - await payload.find({ - collection: lexicalFieldsSlug, - depth: 1, - overrideAccess: true, - where: { - title: { - equals: lexicalDocData.title, + await assertLexicalDoc({ + depth: 1, + fn: async ({ lexicalWithBlocks }) => { + const uploadDoc: Upload = ( + await payload.find({ + collection: 'uploads', + depth: 0, + overrideAccess: true, + where: { + filename: { + equals: 'payload.jpg', + }, }, - }, - }) - ).docs[0] as never + }) + ).docs[0] as never - const uploadDoc: Upload = ( - await payload.find({ - collection: 'uploads', - depth: 0, - overrideAccess: true, - where: { - filename: { - equals: 'payload.jpg', - }, - }, - }) - ).docs[0] as never + const richTextBlock2: SerializedBlockNode = lexicalWithBlocks.root + .children[13] as SerializedBlockNode + 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 - const lexicalField2: SerializedEditorState = lexicalDocDepth1.lexicalWithBlocks - const richTextBlock2: SerializedBlockNode = lexicalField2.root - .children[13] as SerializedBlockNode - 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 + const subSubRichTextField2 = subRichTextBlock2.fields.subRichTextField + const subSubUploadField2 = subRichTextBlock2.fields.subUploadField - const subSubRichTextField2 = subRichTextBlock2.fields.subRichTextField - const subSubUploadField2 = subRichTextBlock2.fields.subUploadField - - expect(subSubRichTextField2.root.children[0].children[0].text).toBe('Some subText') - expect(subSubUploadField2.id).toBe(uploadDoc.id) - expect(subSubUploadField2.filename).toBe(uploadDoc.filename) - }).toPass({ - timeout: POLL_TOPASS_TIMEOUT, + expect(subSubRichTextField2.root.children[0].children[0].text).toBe('Some subText') + expect(subSubUploadField2.id).toBe(uploadDoc.id) + expect(subSubUploadField2.filename).toBe(uploadDoc.filename) + }, }) }) 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 - 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 { richTextField } = await navigateToLexicalFields() await wait(1000) // Wait for form state requests to be done, to reduce flakes const radioButtonBlock1 = richTextField.locator('.lexical-block').nth(5) @@ -999,28 +836,16 @@ describe('lexicalBlocks', () => { 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 + await assertLexicalDoc({ + fn: async ({ lexicalWithBlocks }) => { + const radio1: SerializedBlockNode = lexicalWithBlocks.root + .children[8] as SerializedBlockNode + const radio2: SerializedBlockNode = lexicalWithBlocks.root + .children[9] as SerializedBlockNode - 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(radio2.fields.radioButtons).toBe('option3') - }).toPass({ - timeout: POLL_TOPASS_TIMEOUT, + expect(radio1.fields.radioButtons).toBe('option2') + expect(radio2.fields.radioButtons).toBe('option3') + }, }) }) @@ -1031,12 +856,7 @@ describe('lexicalBlocks', () => { // 2. Focus nested editor and write something // 3. In the issue, after writing one character, the cursor focuses back into the parent editor - 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 { richTextField } = await navigateToLexicalFields() /** * 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 () => { // 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() - - // 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) + const { richTextField } = await navigateToLexicalFields() await wait(1000) // Wait for form state requests to be done, to reduce flakes const conditionalArrayBlock = richTextField.locator('.lexical-block').nth(7) @@ -1158,12 +971,7 @@ describe('lexicalBlocks', () => { 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 - 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 { richTextField } = await navigateToLexicalFields() 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() @@ -1175,12 +983,7 @@ 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) + const { richTextField } = await navigateToLexicalFields() await wait(300) @@ -1226,13 +1029,7 @@ describe('lexicalBlocks', () => { // Reproduces https://github.com/payloadcms/payload/issues/6631 test('ensure tabs field within lexical block correctly loads and saves data', 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 { richTextField } = await navigateToLexicalFields() const tabsBlock = richTextField.locator('.lexical-block').nth(8) await wait(300) @@ -1276,28 +1073,14 @@ describe('lexicalBlocks', () => { await tab2Button.click() await expect(tab2Text1Field).toHaveValue('Some text2 changed') - await expect(async () => { - const lexicalDoc: LexicalField = ( - await payload.find({ - collection: lexicalFieldsSlug, - depth: 0, - overrideAccess: true, - where: { - title: { - equals: lexicalDocData.title, - }, - }, - }) - ).docs[0] as never + await assertLexicalDoc({ + fn: async ({ lexicalWithBlocks }) => { + const tabBlockData: SerializedBlockNode = lexicalWithBlocks.root + .children[13] as SerializedBlockNode - const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks - const tabBlockData: SerializedBlockNode = lexicalField.root - .children[13] as SerializedBlockNode - - expect(tabBlockData.fields.tab1.text1).toBe('Some text1 changed') - expect(tabBlockData.fields.tab2.text2).toBe('Some text2 changed') - }).toPass({ - timeout: POLL_TOPASS_TIMEOUT, + expect(tabBlockData.fields.tab1.text1).toBe('Some text1 changed') + expect(tabBlockData.fields.tab2.text2).toBe('Some text2 changed') + }, }) }) @@ -1343,4 +1126,145 @@ describe('lexicalBlocks', () => { ).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) + }, + }) + }) + }) }) diff --git a/test/fields/collections/Lexical/e2e/main/e2e.spec.ts b/test/fields/collections/Lexical/e2e/main/e2e.spec.ts index b02ea190d7..1865f76c54 100644 --- a/test/fields/collections/Lexical/e2e/main/e2e.spec.ts +++ b/test/fields/collections/Lexical/e2e/main/e2e.spec.ts @@ -69,7 +69,7 @@ describe('lexicalMain', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname })) + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname })) context = await browser.newContext() page = await context.newPage() diff --git a/test/fields/collections/Lexical/index.ts b/test/fields/collections/Lexical/index.ts index 2f8a364032..17ed231b6d 100644 --- a/test/fields/collections/Lexical/index.ts +++ b/test/fields/collections/Lexical/index.ts @@ -176,6 +176,25 @@ const editorConfig: ServerEditorConfig = { }, ], inlineBlocks: [ + { + slug: 'AvatarGroup', + interfaceName: 'AvatarGroupBlock', + fields: [ + { + name: 'avatars', + type: 'array', + minRows: 1, + maxRows: 6, + fields: [ + { + name: 'image', + type: 'upload', + relationTo: 'uploads', + }, + ], + }, + ], + }, { slug: 'myInlineBlock', admin: { diff --git a/test/fields/collections/Number/e2e.spec.ts b/test/fields/collections/Number/e2e.spec.ts index e195836991..0e11591521 100644 --- a/test/fields/collections/Number/e2e.spec.ts +++ b/test/fields/collections/Number/e2e.spec.ts @@ -38,7 +38,7 @@ describe('Number', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Point/e2e.spec.ts b/test/fields/collections/Point/e2e.spec.ts index 725433927d..cb62ce8e1b 100644 --- a/test/fields/collections/Point/e2e.spec.ts +++ b/test/fields/collections/Point/e2e.spec.ts @@ -37,7 +37,7 @@ describe('Point', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Radio/e2e.spec.ts b/test/fields/collections/Radio/e2e.spec.ts index 6ac99c452a..276d387503 100644 --- a/test/fields/collections/Radio/e2e.spec.ts +++ b/test/fields/collections/Radio/e2e.spec.ts @@ -31,7 +31,7 @@ describe('Radio', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Relationship/e2e.spec.ts b/test/fields/collections/Relationship/e2e.spec.ts index f7be4f348f..4cdbac5b24 100644 --- a/test/fields/collections/Relationship/e2e.spec.ts +++ b/test/fields/collections/Relationship/e2e.spec.ts @@ -42,7 +42,7 @@ describe('relationship', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, })) diff --git a/test/fields/collections/RichText/e2e.spec.ts b/test/fields/collections/RichText/e2e.spec.ts index 4bfc8ac3db..ec26154ee8 100644 --- a/test/fields/collections/RichText/e2e.spec.ts +++ b/test/fields/collections/RichText/e2e.spec.ts @@ -31,7 +31,7 @@ describe('Rich Text', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ serverURL } = await initPayloadE2ENoConfig({ + ;({ serverURL } = await initPayloadE2ENoConfig({ dirname, })) diff --git a/test/fields/collections/Row/e2e.spec.ts b/test/fields/collections/Row/e2e.spec.ts index 57cf05b0d2..612d311236 100644 --- a/test/fields/collections/Row/e2e.spec.ts +++ b/test/fields/collections/Row/e2e.spec.ts @@ -32,7 +32,7 @@ describe('Row', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Select/e2e.spec.ts b/test/fields/collections/Select/e2e.spec.ts index e98304cde6..ca05d6759a 100644 --- a/test/fields/collections/Select/e2e.spec.ts +++ b/test/fields/collections/Select/e2e.spec.ts @@ -35,7 +35,7 @@ describe('Radio', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Tabs/e2e.spec.ts b/test/fields/collections/Tabs/e2e.spec.ts index 5a009f8dbb..bcde89d35a 100644 --- a/test/fields/collections/Tabs/e2e.spec.ts +++ b/test/fields/collections/Tabs/e2e.spec.ts @@ -39,7 +39,7 @@ describe('Tabs', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Tabs2/e2e.spec.ts b/test/fields/collections/Tabs2/e2e.spec.ts index 913d20b5ed..1b8182137b 100644 --- a/test/fields/collections/Tabs2/e2e.spec.ts +++ b/test/fields/collections/Tabs2/e2e.spec.ts @@ -36,7 +36,7 @@ describe('Tabs', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Text/e2e.spec.ts b/test/fields/collections/Text/e2e.spec.ts index 418888e826..26fe940e97 100644 --- a/test/fields/collections/Text/e2e.spec.ts +++ b/test/fields/collections/Text/e2e.spec.ts @@ -43,7 +43,7 @@ describe('Text', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/UI/e2e.spec.ts b/test/fields/collections/UI/e2e.spec.ts index 54d499f099..1e21492ebb 100644 --- a/test/fields/collections/UI/e2e.spec.ts +++ b/test/fields/collections/UI/e2e.spec.ts @@ -31,7 +31,7 @@ describe('Radio', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/Upload/e2e.spec.ts b/test/fields/collections/Upload/e2e.spec.ts index 4121e0a12e..1951dce9a8 100644 --- a/test/fields/collections/Upload/e2e.spec.ts +++ b/test/fields/collections/Upload/e2e.spec.ts @@ -38,7 +38,7 @@ describe('Upload', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/collections/UploadRestricted/e2e.spec.ts b/test/fields/collections/UploadRestricted/e2e.spec.ts index 0c591e0d1f..a1fc51ef24 100644 --- a/test/fields/collections/UploadRestricted/e2e.spec.ts +++ b/test/fields/collections/UploadRestricted/e2e.spec.ts @@ -33,7 +33,7 @@ describe('Upload with restrictions', () => { beforeAll(async ({ browser }, testInfo) => { 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 - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname, // prebuild, })) diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index d5b4cf705b..84f948ee7e 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -3374,6 +3374,21 @@ export interface LexicalBlocksRadioButtonsBlock { blockName?: string | null; 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 * via the `definition` "auth". diff --git a/test/locked-documents/e2e.spec.ts b/test/locked-documents/e2e.spec.ts index 747c1cf7d5..024fe585ee 100644 --- a/test/locked-documents/e2e.spec.ts +++ b/test/locked-documents/e2e.spec.ts @@ -39,7 +39,7 @@ let serverURL: string describe('Locked Documents', () => { beforeAll(async ({ browser }, testInfo) => { testInfo.setTimeout(TEST_TIMEOUT_LONG) - ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname })) + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname })) globalUrl = new AdminUrlUtil(serverURL, 'menu') postsUrl = new AdminUrlUtil(serverURL, 'posts') diff --git a/test/playwright.config.ts b/test/playwright.config.ts index 1650f8fe94..ff2cdd619a 100644 --- a/test/playwright.config.ts +++ b/test/playwright.config.ts @@ -8,7 +8,7 @@ const dirname = path.dirname(filename) 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 export const TEST_TIMEOUT_LONG = 640000 * multiplier // 8*3 minutes - used as timeOut for the beforeAll