fix(richtext-lexical): editor re-mounting on save due to json key order not being preserved in postgres (#13962)
Fixes https://github.com/payloadcms/payload/issues/13904 When using Postgres, saving a document can cause the editor to re-mount. If the document includes a blocks field, this leads to the editor incorrectly resetting its value to the previous state. The issue occurs because the editor is re-mounted without recalculating the initial Lexical state. This re-mounting behavior is caused by how Postgres handles JSON storage: - With `jsonb` columns, unlike `json` columns, object key order is not guaranteed. - As a result, saving and reloading the Lexical editor state shifts key order, causing `JSON.stringify()` comparisons to fail. ## Solution To fix this, we now compare the incoming and previous editor state in a way that ignores key order. Specifically, we switched from `JSON.stringify()` to `dequal` for deep equality checks. ## Notes - This code only runs when external changes occur (e.g. after saving a document or when custom code modifies form fields). - Because this check is infrequent, performance impact is negligible. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1211488746010087
This commit is contained in:
@@ -8,6 +8,7 @@ import type { Config } from '../../../payload-types.js'
|
||||
|
||||
import { ensureCompilationIsDone, saveDocAndAssert } from '../../../../helpers.js'
|
||||
import { AdminUrlUtil } from '../../../../helpers/adminUrlUtil.js'
|
||||
import { assertNetworkRequests } from '../../../../helpers/e2e/assertNetworkRequests.js'
|
||||
import { initPayloadE2ENoConfig } from '../../../../helpers/initPayloadE2ENoConfig.js'
|
||||
import { reInitializeDB } from '../../../../helpers/reInitializeDB.js'
|
||||
import { TEST_TIMEOUT_LONG } from '../../../../playwright.config.js'
|
||||
@@ -123,5 +124,53 @@ describe('Lexical Fully Featured - database', () => {
|
||||
// @ts-expect-error unsafe access is fine in tests
|
||||
expect(uploadNode.value?.filename).toBe('payload-1.jpg')
|
||||
})
|
||||
|
||||
test('ensure block contents are not reset on save on both create and update', async ({
|
||||
page,
|
||||
}) => {
|
||||
await lexical.slashCommand('myblock')
|
||||
await expect(lexical.editor.locator('.lexical-block')).toBeVisible()
|
||||
|
||||
/**
|
||||
* Test on create
|
||||
*/
|
||||
await assertNetworkRequests(
|
||||
page,
|
||||
`/admin/collections/${lexicalFullyFeaturedSlug}`,
|
||||
async () => {
|
||||
await lexical.editor.locator('#field-someText').first().fill('Testing 123')
|
||||
},
|
||||
{
|
||||
minimumNumberOfRequests: 2,
|
||||
allowedNumberOfRequests: 3,
|
||||
},
|
||||
)
|
||||
|
||||
await expect(lexical.editor.locator('#field-someText')).toHaveValue('Testing 123')
|
||||
await saveDocAndAssert(page)
|
||||
await expect(lexical.editor.locator('#field-someText')).toHaveValue('Testing 123')
|
||||
await page.reload()
|
||||
await expect(lexical.editor.locator('#field-someText')).toHaveValue('Testing 123')
|
||||
|
||||
/**
|
||||
* Test on update (this is where the issue appeared)
|
||||
*/
|
||||
await assertNetworkRequests(
|
||||
page,
|
||||
`/admin/collections/${lexicalFullyFeaturedSlug}`,
|
||||
async () => {
|
||||
await lexical.editor.locator('#field-someText').first().fill('Updated text')
|
||||
},
|
||||
{
|
||||
minimumNumberOfRequests: 2,
|
||||
allowedNumberOfRequests: 2,
|
||||
},
|
||||
)
|
||||
await expect(lexical.editor.locator('#field-someText')).toHaveValue('Updated text')
|
||||
await saveDocAndAssert(page)
|
||||
await expect(lexical.editor.locator('#field-someText')).toHaveValue('Updated text')
|
||||
await page.reload()
|
||||
await expect(lexical.editor.locator('#field-someText')).toHaveValue('Updated text')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user