Skip to content

Commit

Permalink
fix(richtext-lexical): upload, relationship and block node insertion …
Browse files Browse the repository at this point in the history
…fails sometimes
  • Loading branch information
AlessioGr committed May 16, 2024
1 parent 9556d1b commit 6083870
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
INSERT_BLOCK_COMMAND,
(payload: InsertBlockPayload) => {
editor.update(() => {
const blockNode = $createBlockNode(payload)

const selection = $getSelection() || $getPreviousSelection()

if ($isRangeSelection(selection)) {
const blockNode = $createBlockNode(payload)
// Insert blocks node BEFORE potentially removing focusNode, as $insertNodeToNearestRoot errors if the focusNode doesn't exist
$insertNodeToNearestRoot(blockNode)

const { focus } = selection
const focusNode = focus.getNode()

Expand All @@ -53,8 +55,6 @@ export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
) {
focusNode.remove()
}

$insertNodeToNearestRoot(blockNode)
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ export const RelationshipPlugin: PluginComponent<RelationshipFeatureProps> = ({
return editor.registerCommand<RelationshipData>(
INSERT_RELATIONSHIP_COMMAND,
(payload) => {
const relationshipNode = $createRelationshipNode(payload)

const selection = $getSelection() || $getPreviousSelection()

if ($isRangeSelection(selection)) {
const relationshipNode = $createRelationshipNode(payload)
// Insert relationship node BEFORE potentially removing focusNode, as $insertNodeToNearestRoot errors if the focusNode doesn't exist
$insertNodeToNearestRoot(relationshipNode)

const { focus } = selection
const focusNode = focus.getNode()

Expand All @@ -68,8 +70,6 @@ export const RelationshipPlugin: PluginComponent<RelationshipFeatureProps> = ({
) {
focusNode.remove()
}

$insertNodeToNearestRoot(relationshipNode)
}

return true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
'use client'
import type { BaseSelection } from 'lexical'
import type { ClientCollectionConfig } from 'payload/types'

import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,19 @@ export const UploadPlugin: PluginComponentWithAnchor<UploadFeaturePropsClient> =
INSERT_UPLOAD_COMMAND,
(payload: InsertUploadPayload) => {
editor.update(() => {
const uploadNode = $createUploadNode({
data: {
fields: payload.fields,
relationTo: payload.relationTo,
value: payload.value,
},
})

const selection = $getSelection() || $getPreviousSelection()

if ($isRangeSelection(selection)) {
const uploadNode = $createUploadNode({
data: {
fields: payload.fields,
relationTo: payload.relationTo,
value: payload.value,
},
})
// Insert upload node BEFORE potentially removing focusNode, as $insertNodeToNearestRoot errors if the focusNode doesn't exist
$insertNodeToNearestRoot(uploadNode)

const { focus } = selection
const focusNode = focus.getNode()

Expand All @@ -68,8 +70,6 @@ export const UploadPlugin: PluginComponentWithAnchor<UploadFeaturePropsClient> =
) {
focusNode.remove()
}

$insertNodeToNearestRoot(uploadNode)
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const uploadValidation = (

const collection = props?.collections[node.relationTo]

if (!collection.fields?.length) {
if (!collection?.fields?.length) {
return true
}

Expand Down
4 changes: 2 additions & 2 deletions test/fields/collections/Array/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ describe('Array', () => {
await reInitializeDB({
serverURL,
snapshotKey: 'fieldsArrayTest',
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
})
await ensureAutoLoginAndCompilationIsDone({ page, serverURL })
})
beforeEach(async () => {
await reInitializeDB({
serverURL,
snapshotKey: 'fieldsArrayTest',
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
})

if (client) {
Expand Down
4 changes: 2 additions & 2 deletions test/fields/collections/Blocks/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ describe('Block fields', () => {
await reInitializeDB({
serverURL,
snapshotKey: 'blockFieldsTest',
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
})
await ensureAutoLoginAndCompilationIsDone({ page, serverURL })
})
beforeEach(async () => {
await reInitializeDB({
serverURL,
snapshotKey: 'blockFieldsTest',
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
})

if (client) {
Expand Down
90 changes: 86 additions & 4 deletions test/fields/collections/Lexical/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe('lexical', () => {
await reInitializeDB({
serverURL,
snapshotKey: 'fieldsLexicalTest',
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
})
await ensureAutoLoginAndCompilationIsDone({ page, serverURL })
})
Expand All @@ -86,7 +86,10 @@ describe('lexical', () => {
await reInitializeDB({
serverURL,
snapshotKey: 'fieldsLexicalTest',
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
uploadsDir: [
path.resolve(dirname, './collections/Upload/uploads'),
path.resolve(dirname, './collections/Upload2/uploads2'),
],
})

if (client) {
Expand Down Expand Up @@ -356,6 +359,85 @@ describe('lexical', () => {
await expect(reactSelect.locator('.rs__value-container').first()).toHaveText('Option 3')
})

// This reproduces an issue where if you create an upload node, the document drawer opens, you select a collection other than the default one, create a NEW upload document and save, it throws a lexical error
test('ensure creation of new upload document within upload node works', 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 upload node
*/
// type / to open the slash menu
await lastParagraph.click()
await page.keyboard.press('/')
await page.keyboard.type('Upload')

// Create Upload node
const slashMenuPopover = page.locator('#slash-menu .slash-menu-popup')
await expect(slashMenuPopover).toBeVisible()

const uploadSelectButton = slashMenuPopover.locator('button').nth(1)
await expect(uploadSelectButton).toBeVisible()
await expect(uploadSelectButton).toContainText('Upload')
await uploadSelectButton.click()
await expect(slashMenuPopover).toBeHidden()

await wait(500) // wait for drawer form state to initialize (it's a flake)
const uploadListDrawer = page.locator('dialog[id^=list-drawer_1_]').first() // IDs starting with list-drawer_1_ (there's some other symbol after the underscore)
await expect(uploadListDrawer).toBeVisible()
await wait(500)

await uploadListDrawer.locator('.rs__control .value-container').first().click()
await wait(500)
await expect(uploadListDrawer.locator('.rs__option').nth(1)).toBeVisible()
await expect(uploadListDrawer.locator('.rs__option').nth(1)).toContainText('Upload 2')
await uploadListDrawer.locator('.rs__option').nth(1).click()

// wait till the text appears in uploadListDrawer: "No Uploads 2 found. Either no Uploads 2 exist yet or none match the filters you've specified above."
await expect(
uploadListDrawer.getByText(
"No Uploads 2 found. Either no Uploads 2 exist yet or none match the filters you've specified above.",
),
).toBeVisible()

await uploadListDrawer.getByText('Create New').first().click()
const createUploadDrawer = page.locator('dialog[id^=doc-drawer_uploads2_]').first() // IDs starting with list-drawer_1_ (there's some other symbol after the underscore)
await expect(createUploadDrawer).toBeVisible()
await wait(500)

const input = createUploadDrawer.locator('.file-field__upload input[type="file"]').first()
await expect(input).toBeAttached()

await input.setInputFiles(path.resolve(dirname, './collections/Upload/payload.jpg'))
await expect(createUploadDrawer.locator('.file-field .file-field__filename')).toHaveValue(
'payload.jpg',
)
await wait(500)
await createUploadDrawer.getByText('Save').first().click()
await expect(createUploadDrawer).toBeHidden()
await expect(uploadListDrawer).toBeHidden()
await wait(500)
await saveDocAndAssert(page)

// second one should be the newly created one
const secondUploadNode = richTextField.locator('.lexical-upload').nth(1)
await secondUploadNode.scrollIntoViewIfNeeded()
await expect(secondUploadNode).toBeVisible()

await expect(secondUploadNode.locator('.lexical-upload__bottomRow')).toContainText(
'payload.jpg',
)
await expect(secondUploadNode.locator('.lexical-upload__collectionLabel')).toContainText(
'Upload 2',
)
})

describe('nested lexical editor in block', () => {
test('should type and save typed text', async () => {
await navigateToLexicalFields()
Expand Down Expand Up @@ -850,7 +932,7 @@ describe('lexical', () => {

const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
const richTextBlock: SerializedBlockNode = lexicalField.root
.children[12] as SerializedBlockNode
.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

Expand Down Expand Up @@ -894,7 +976,7 @@ describe('lexical', () => {

const lexicalField2: SerializedEditorState = lexicalDocDepth1.lexicalWithBlocks
const richTextBlock2: SerializedBlockNode = lexicalField2.root
.children[12] as SerializedBlockNode
.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

Expand Down
4 changes: 2 additions & 2 deletions test/fields/collections/Relationship/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ describe('relationship', () => {
await reInitializeDB({
serverURL,
snapshotKey: 'fieldsRelationshipTest',
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
})
await ensureAutoLoginAndCompilationIsDone({ page, serverURL })
})
beforeEach(async () => {
await reInitializeDB({
serverURL,
snapshotKey: 'fieldsRelationshipTest',
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
})

if (client) {
Expand Down
4 changes: 2 additions & 2 deletions test/fields/collections/RichText/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ describe('Rich Text', () => {
await reInitializeDB({
serverURL,
snapshotKey: 'fieldsRichTextTest',
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
})
await ensureAutoLoginAndCompilationIsDone({ page, serverURL })
})
beforeEach(async () => {
await reInitializeDB({
serverURL,
snapshotKey: 'fieldsRichTextTest',
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
})

if (client) {
Expand Down
3 changes: 2 additions & 1 deletion test/helpers/reInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const handler: PayloadHandler = async (req) => {
collectionSlugs: payload.config.collections.map(({ slug }) => slug),
seedFunction: payload.config.onInit,
snapshotKey: String(data.snapshotKey),
uploadsDir: String(data.uploadsDir),
// uploadsDir can be string or stringlist
uploadsDir: data.uploadsDir as string | string[],
})

return Response.json(
Expand Down
2 changes: 1 addition & 1 deletion test/helpers/reInitializeDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const reInitializeDB = async ({
}: {
serverURL: string
snapshotKey: string
uploadsDir?: string
uploadsDir?: string | string[]
}) => {
await fetch(`${serverURL}/api${path}`, {
method: 'post',
Expand Down

0 comments on commit 6083870

Please sign in to comment.