chore(richtext-lexical): add failing e2e test which ensures sub-richtext blocks work as intended

This commit is contained in:
Alessio Gravili
2024-04-14 02:18:16 -04:00
parent 2722d2f5ce
commit 3f2df643e7
4 changed files with 227 additions and 9 deletions

View File

@@ -1,6 +1,7 @@
import type { LexicalBlock } from '@payloadcms/richtext-lexical'
import type { ArrayField } from 'payload/types'
import { BlocksFeature } from '@payloadcms/richtext-lexical'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import { textFieldsSlug } from '../Text/shared.js'
@@ -113,12 +114,35 @@ export const RadioButtonsBlock: LexicalBlock = {
export const RichTextBlock: LexicalBlock = {
fields: [
{
name: 'richText',
name: 'richTextField',
type: 'richText',
editor: lexicalEditor(),
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
BlocksFeature({
blocks: [
{
fields: [
{
name: 'subRichTextField',
type: 'richText',
editor: lexicalEditor({}),
},
{
name: 'subUploadField',
type: 'upload',
relationTo: 'uploads',
},
],
slug: 'lexicalAndUploadBlock',
},
],
}),
],
}),
},
],
slug: 'richText',
slug: 'richTextBlock',
}
export const UploadAndRichTextBlock: LexicalBlock = {

View File

@@ -7,9 +7,10 @@ import { expect, test } from '@playwright/test'
import { initPayloadE2ENoConfig } from 'helpers/initPayloadE2ENoConfig.js'
import { reInitializeDB } from 'helpers/reInit.js'
import path from 'path'
import { wait } from 'payload/utilities'
import { fileURLToPath } from 'url'
import type { Config, LexicalField } from '../../payload-types.js'
import type { Config, LexicalField, Upload } from '../../payload-types.js'
import { initPageConsoleErrorCatch, saveDocAndAssert } from '../../../helpers.js'
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
@@ -147,6 +148,7 @@ describe('lexical', () => {
await payload.find({
collection: lexicalFieldsSlug,
depth: 0,
overrideAccess: true,
where: {
title: {
equals: lexicalDocData.title,
@@ -216,6 +218,7 @@ describe('lexical', () => {
await payload.find({
collection: lexicalFieldsSlug,
depth: 0,
overrideAccess: true,
where: {
title: {
equals: lexicalDocData.title,
@@ -366,6 +369,7 @@ describe('lexical', () => {
await payload.find({
collection: lexicalFieldsSlug,
depth: 0,
overrideAccess: true,
where: {
title: {
equals: lexicalDocData.title,
@@ -375,8 +379,11 @@ describe('lexical', () => {
).docs[0] as never
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
const blockNode: SerializedBlockNode = lexicalField.root.children[4] as SerializedBlockNode
const textNodeInBlockNodeRichText = blockNode.fields.richText.root.children[1].children[0]
const textNodeInBlockNodeRichText =
blockNode.fields.richTextField.root.children[1].children[0]
expect(textNodeInBlockNodeRichText.text).toBe(
'Some text below relationship node 1 inserted text',
@@ -443,6 +450,7 @@ describe('lexical', () => {
await payload.find({
collection: lexicalFieldsSlug,
depth: 0,
overrideAccess: true,
where: {
title: {
equals: lexicalDocData.title,
@@ -453,7 +461,7 @@ describe('lexical', () => {
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
const blockNode: SerializedBlockNode = lexicalField.root.children[4] as SerializedBlockNode
const paragraphNodeInBlockNodeRichText = blockNode.fields.richText.root.children[1]
const paragraphNodeInBlockNodeRichText = blockNode.fields.richTextField.root.children[1]
expect(paragraphNodeInBlockNodeRichText.children).toHaveLength(2)
@@ -534,6 +542,7 @@ describe('lexical', () => {
await payload.find({
collection: lexicalFieldsSlug,
depth: 0,
overrideAccess: true,
where: {
title: {
equals: lexicalDocData.title,
@@ -680,6 +689,7 @@ describe('lexical', () => {
await payload.find({
collection: lexicalFieldsSlug,
depth: 0,
overrideAccess: true,
where: {
title: {
equals: lexicalDocData.title,
@@ -702,6 +712,189 @@ describe('lexical', () => {
})
})
// 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(1) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()
const lastParagraph = richTextField.locator('p').last()
await lastParagraph.scrollIntoViewIfNeeded()
await expect(lastParagraph).toBeVisible()
/**
* Create new sub-block
*/
// type / to open the slash menu
await lastParagraph.click()
await page.keyboard.press('/')
await page.keyboard.type('Rich')
// Create Rich Text Block
const slashMenuPopover = page.locator('#slash-menu .slash-menu-popup')
await expect(slashMenuPopover).toBeVisible()
// Click 1. Button and ensure it's the Rich Text block creation button (it should be! Otherwise, sorting wouldn't work)
const richTextBlockSelectButton = slashMenuPopover.locator('button').first()
await expect(richTextBlockSelectButton).toBeVisible()
await expect(richTextBlockSelectButton).toContainText('Rich Text')
await richTextBlockSelectButton.click()
await expect(slashMenuPopover).toBeHidden()
const newRichTextBlock = richTextField
.locator('.lexical-block:not(.lexical-block .lexical-block)')
.last() // The :not(.lexical-block .lexical-block) makes sure this does not select sub-blocks
await newRichTextBlock.scrollIntoViewIfNeeded()
await expect(newRichTextBlock).toBeVisible()
// Ensure that sub-editor is empty
const newRichTextEditorParagraph = newRichTextBlock.locator('p').first()
await expect(newRichTextEditorParagraph).toBeVisible()
await expect(newRichTextEditorParagraph).toHaveText('')
await newRichTextEditorParagraph.click()
await page.keyboard.press('/')
await page.keyboard.type('Lexical')
await expect(slashMenuPopover).toBeVisible()
// Click 1. Button and ensure it's the Lexical And Upload block creation button (it should be! Otherwise, sorting wouldn't work)
const lexicalAndUploadBlockSelectButton = slashMenuPopover.locator('button').first()
await expect(lexicalAndUploadBlockSelectButton).toBeVisible()
await expect(lexicalAndUploadBlockSelectButton).toContainText('Lexical And Upload')
await lexicalAndUploadBlockSelectButton.click()
await expect(slashMenuPopover).toBeHidden()
// Ensure that sub-editor is created
const newSubLexicalAndUploadBlock = newRichTextBlock.locator('.lexical-block').first()
await newSubLexicalAndUploadBlock.scrollIntoViewIfNeeded()
await expect(newSubLexicalAndUploadBlock).toBeVisible()
// Type in newSubLexicalAndUploadBlock
const paragraphInSubEditor = newSubLexicalAndUploadBlock.locator('p').first()
await expect(paragraphInSubEditor).toBeVisible()
await paragraphInSubEditor.click()
await page.keyboard.type('Some subText')
// Upload something
const chooseExistingUploadButton = newSubLexicalAndUploadBlock
.locator('.upload__toggler.list-drawer__toggler')
.first()
await expect(chooseExistingUploadButton).toBeVisible()
await chooseExistingUploadButton.click()
await wait(500) // wait for drawer form state to initialize (it's a flake)
const uploadListDrawer = page.locator('dialog[id^=list-drawer_1_]').first() // IDs starting with list-drawer_1_ (there's some other symbol after the underscore)
await expect(uploadListDrawer).toBeVisible()
// find button which has a span with text "payload.jpg" and click it in playwright
const uploadButton = uploadListDrawer.locator('button').getByText('payload.jpg').first()
await expect(uploadButton).toBeVisible()
await uploadButton.click()
await expect(uploadListDrawer).toBeHidden()
// Check if the upload is there
await expect(
newSubLexicalAndUploadBlock.locator('.field-type.upload .file-meta__url a'),
).toHaveText('payload.jpg')
// save document and assert
await saveDocAndAssert(page)
await expect(
newSubLexicalAndUploadBlock.locator('.field-type.upload .file-meta__url a'),
).toHaveText('payload.jpg')
await expect(paragraphInSubEditor).toHaveText('Some subText')
// reload page and assert again
await page.reload()
await expect(
newSubLexicalAndUploadBlock.locator('.field-type.upload .file-meta__url a'),
).toHaveText('payload.jpg')
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,
},
},
})
).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 lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
const richTextBlock: SerializedBlockNode = lexicalField.root
.children[12] 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
expect(subSubRichTextField.root.children[0].children[0].text).toBe('Some subText')
expect(subSubUploadField).toBe(uploadDoc.id)
}).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
// 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,
},
},
})
).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 lexicalField2: SerializedEditorState = lexicalDocDepth1.lexicalWithBlocks
const richTextBlock2: SerializedBlockNode = lexicalField2.root
.children[12] 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
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,
})
})
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
@@ -750,6 +943,7 @@ describe('lexical', () => {
await payload.find({
collection: lexicalFieldsSlug,
depth: 0,
overrideAccess: true,
where: {
title: {
equals: lexicalDocData.title,

View File

@@ -110,7 +110,7 @@ export function generateLexicalRichText() {
version: 2,
fields: {
id: '65298b1ddb4ef8c744a7faab',
richText: {
richTextField: {
root: {
type: 'root',
format: '',
@@ -149,7 +149,7 @@ export function generateLexicalRichText() {
},
},
blockName: 'Block Node, with RichText Field, with Relationship Node',
blockType: 'richText',
blockType: 'richTextBlock',
},
},
{

View File

@@ -133,7 +133,7 @@ export async function openNav(page: Page): Promise<void> {
export async function openDocDrawer(page: Page, selector: string): Promise<void> {
await wait(300) // wait for parent form state to initialize
await page.locator(selector).click({ delay: 100 })
await wait(500) // wait for drawer form state to initializ
await wait(500) // wait for drawer form state to initialize
}
export async function closeNav(page: Page): Promise<void> {