fix(richtext-lexical): Blocks: unnecessary saving node value when initially opening a document & new lexical tests (#4059)
* chore: new lexical int tests and working test structure * chore: more int tests, and better lexical collection structure * fix(richtext-lexical): Blocks: unnecessary saving node value when initially opening a document
This commit is contained in:
@@ -71,6 +71,25 @@ export const BlockContent: React.FC<Props> = (props) => {
|
||||
|
||||
const onFormChange = useCallback(
|
||||
({ fields: formFields, formData }: { fields: Fields; formData: Data }) => {
|
||||
// Recursively remove all undefined values from even being present in formData, as they will
|
||||
// cause isDeepEqual to return false if, for example, formData has a key that fields.data
|
||||
// does not have, even if it's undefined.
|
||||
// Currently, this happens if a block has another sub-blocks field. Inside of formData, that sub-blocks field has an undefined blockName property.
|
||||
// Inside of fields.data however, that sub-blocks blockName property does not exist at all.
|
||||
function removeUndefinedRecursively(obj: any) {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (obj[key] && typeof obj[key] === 'object') {
|
||||
removeUndefinedRecursively(obj[key])
|
||||
} else if (obj[key] === undefined) {
|
||||
delete obj[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
removeUndefinedRecursively(formData)
|
||||
removeUndefinedRecursively(fields.data)
|
||||
|
||||
// Only update if the data has actually changed. Otherwise, we may be triggering an unnecessary value change,
|
||||
// which would trigger the "Leave without saving" dialog unnecessarily
|
||||
if (!isDeepEqual(fields.data, formData)) {
|
||||
editor.update(() => {
|
||||
const node: BlockNode = $getNodeByKey(nodeKey)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { generateLexicalRichText } from './generateLexicalRichText'
|
||||
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData'
|
||||
|
||||
export const lexicalRichTextDoc = {
|
||||
export const lexicalDocData = {
|
||||
title: 'Rich Text',
|
||||
richTextLexicalCustomFields: generateLexicalRichText(),
|
||||
richTextLexicalWithLexicalPluginData: payloadPluginLexicalData,
|
||||
lexicalWithBlocks: generateLexicalRichText(),
|
||||
}
|
||||
|
||||
@@ -104,9 +104,9 @@ export function generateLexicalRichText() {
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 1,
|
||||
relationTo: 'text-fields',
|
||||
relationTo: 'rich-text-fields',
|
||||
value: {
|
||||
id: '{{TEXT_DOC_ID}}',
|
||||
id: '{{RICH_TEXT_DOC_ID}}',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,958 +0,0 @@
|
||||
export const payloadPluginLexicalData = {
|
||||
words: 49,
|
||||
preview:
|
||||
'paragraph text bold italic underline and all subscript superscript code internal link external link…',
|
||||
comments: [],
|
||||
characters: 493,
|
||||
jsonContent: {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'paragraph text ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 1,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'bold',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 2,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'italic',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 8,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'underline',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' and ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 11,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'all',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 32,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'subscript',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 64,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'superscript',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 16,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'code',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'internal link',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'link',
|
||||
version: 2,
|
||||
attributes: {
|
||||
newTab: true,
|
||||
linkType: 'internal',
|
||||
doc: {
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
relationTo: 'text-fields',
|
||||
data: {}, // populated data
|
||||
},
|
||||
text: 'internal link',
|
||||
},
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'external link',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'link',
|
||||
version: 2,
|
||||
attributes: {
|
||||
newTab: true,
|
||||
nofollow: false,
|
||||
url: 'https://fewfwef.de',
|
||||
linkType: 'custom',
|
||||
text: 'external link',
|
||||
},
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' s. ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 4,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'strikethrough',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'heading 1',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'heading',
|
||||
version: 1,
|
||||
tag: 'h1',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'heading 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'heading',
|
||||
version: 1,
|
||||
tag: 'h2',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'bullet list ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 3',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list',
|
||||
version: 1,
|
||||
listType: 'bullet',
|
||||
start: 1,
|
||||
tag: 'ul',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'ordered list',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 3',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list',
|
||||
version: 1,
|
||||
listType: 'number',
|
||||
start: 1,
|
||||
tag: 'ol',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'check list',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 3',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list',
|
||||
version: 1,
|
||||
listType: 'check',
|
||||
start: 1,
|
||||
tag: 'ul',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'quoteeee',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'quote',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'code block line ',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '1',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
highlightType: 'number',
|
||||
},
|
||||
{
|
||||
type: 'linebreak',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'code block line ',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
highlightType: 'number',
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'code',
|
||||
version: 1,
|
||||
language: 'javascript',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'Upload:',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
type: 'upload',
|
||||
version: 1,
|
||||
rawImagePayload: {
|
||||
value: {
|
||||
id: '{{UPLOAD_DOC_ID}}',
|
||||
},
|
||||
relationTo: 'uploads',
|
||||
},
|
||||
caption: {
|
||||
editorState: {
|
||||
root: {
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'upload caption',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'root',
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
showCaption: true,
|
||||
data: {}, // populated upload data
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table top left',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 3,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table top right',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablerow',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table bottom left',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table bottom right',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 0,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablerow',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'table',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
cells: [
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'kafuj',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'iussu',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
],
|
||||
height: null,
|
||||
id: 'tnied',
|
||||
},
|
||||
{
|
||||
cells: [
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'hpnnv',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'ndteg',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'normal',
|
||||
width: null,
|
||||
},
|
||||
],
|
||||
height: null,
|
||||
id: 'rxyey',
|
||||
},
|
||||
{
|
||||
cells: [
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'rtueq',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'vrzoi',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'normal',
|
||||
width: null,
|
||||
},
|
||||
],
|
||||
height: null,
|
||||
id: 'qzglv',
|
||||
},
|
||||
],
|
||||
type: 'tablesheet',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'youtube:',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'youtube',
|
||||
version: 1,
|
||||
videoID: '3Nwt3qu0_UY',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
equation: '3+3',
|
||||
inline: true,
|
||||
type: 'equation',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'collapsible title',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'collapsible-title',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'collabsible conteent',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'collapsible-content',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'collapsible-container',
|
||||
version: 1,
|
||||
open: true,
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
type: 'horizontalrule',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -19,8 +19,6 @@ import {
|
||||
TextBlock,
|
||||
UploadAndRichTextBlock,
|
||||
} from './blocks'
|
||||
import { generateLexicalRichText } from './generateLexicalRichText'
|
||||
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData'
|
||||
|
||||
export const LexicalFields: CollectionConfig = {
|
||||
slug: lexicalFieldsSlug,
|
||||
@@ -38,12 +36,12 @@ export const LexicalFields: CollectionConfig = {
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'richTextLexicalSimple',
|
||||
name: 'lexicalSimple',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
TestRecorderFeature(),
|
||||
//TestRecorderFeature(),
|
||||
TreeviewFeature(),
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
@@ -59,15 +57,15 @@ export const LexicalFields: CollectionConfig = {
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'richTextLexicalCustomFields',
|
||||
name: 'lexicalWithBlocks',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
TestRecorderFeature(),
|
||||
//TestRecorderFeature(),
|
||||
TreeviewFeature(),
|
||||
HTMLConverterFeature(),
|
||||
//HTMLConverterFeature(),
|
||||
LinkFeature({
|
||||
fields: [
|
||||
{
|
||||
@@ -109,44 +107,5 @@ export const LexicalFields: CollectionConfig = {
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'richTextLexicalWithLexicalPluginData',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
LexicalPluginToLexicalFeature(),
|
||||
TreeviewFeature(),
|
||||
LinkFeature({
|
||||
fields: [
|
||||
{
|
||||
name: 'rel',
|
||||
label: 'Rel Attribute',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
options: ['noopener', 'noreferrer', 'nofollow'],
|
||||
admin: {
|
||||
description:
|
||||
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
UploadFeature({
|
||||
collections: {
|
||||
uploads: {
|
||||
fields: [
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor(),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
6
test/fields/collections/LexicalMigrate/data.ts
Normal file
6
test/fields/collections/LexicalMigrate/data.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData'
|
||||
|
||||
export const lexicalMigrateDocData = {
|
||||
title: 'Rich Text',
|
||||
lexicalWithLexicalPluginData: payloadPluginLexicalData,
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export const LexicalMigrateFields: CollectionConfig = {
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'richTextLexicalWithLexicalPluginData',
|
||||
name: 'lexicalWithLexicalPluginData',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
@@ -69,5 +69,5 @@ export const LexicalMigrateFields: CollectionConfig = {
|
||||
|
||||
export const LexicalRichTextDoc = {
|
||||
title: 'Rich Text',
|
||||
richTextLexicalWithLexicalPluginData: payloadPluginLexicalData,
|
||||
lexicalWithLexicalPluginData: payloadPluginLexicalData,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { defaultRichTextValue } from '../../../../packages/richtext-lexical/src'
|
||||
import { generateLexicalRichText } from './generateLexicalRichText'
|
||||
import { generateSlateRichText } from './generateSlateRichText'
|
||||
|
||||
@@ -20,19 +21,19 @@ export const richTextBlocks = [
|
||||
],
|
||||
},
|
||||
]
|
||||
export const richTextDoc = {
|
||||
export const richTextDocData = {
|
||||
title: 'Rich Text',
|
||||
selectHasMany: ['one', 'five'],
|
||||
richText: generateSlateRichText(),
|
||||
richTextReadOnly: generateSlateRichText(),
|
||||
richTextCustomFields: generateSlateRichText(),
|
||||
richTextLexicalCustomFields: generateLexicalRichText(),
|
||||
lexicalCustomFields: generateLexicalRichText(),
|
||||
blocks: richTextBlocks,
|
||||
}
|
||||
|
||||
export const richTextBulletsDoc = {
|
||||
export const richTextBulletsDocData = {
|
||||
title: 'Bullets and Indentation',
|
||||
richTextLexicalCustomFields: generateLexicalRichText(),
|
||||
lexicalCustomFields: generateLexicalRichText(),
|
||||
richText: [
|
||||
{
|
||||
type: 'ul',
|
||||
|
||||
@@ -12,8 +12,6 @@ import { lexicalHTML } from '../../../../packages/richtext-lexical/src/field/fea
|
||||
import { slateEditor } from '../../../../packages/richtext-slate/src'
|
||||
import { richTextFieldsSlug } from '../../slugs'
|
||||
import { RelationshipBlock, SelectFieldBlock, TextBlock, UploadAndRichTextBlock } from './blocks'
|
||||
import { generateLexicalRichText } from './generateLexicalRichText'
|
||||
import { generateSlateRichText } from './generateSlateRichText'
|
||||
|
||||
const RichTextFields: CollectionConfig = {
|
||||
slug: richTextFieldsSlug,
|
||||
@@ -30,7 +28,7 @@ const RichTextFields: CollectionConfig = {
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'richTextLexicalCustomFields',
|
||||
name: 'lexicalCustomFields',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
editor: lexicalEditor({
|
||||
@@ -72,9 +70,9 @@ const RichTextFields: CollectionConfig = {
|
||||
],
|
||||
}),
|
||||
},
|
||||
lexicalHTML('richTextLexicalCustomFields', { name: 'richTextLexicalCustomFields_htmll' }),
|
||||
lexicalHTML('lexicalCustomFields', { name: 'lexicalCustomFields_html' }),
|
||||
{
|
||||
name: 'richTextLexical',
|
||||
name: 'lexical',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
description: 'This rich text field uses the lexical editor.',
|
||||
|
||||
@@ -13,7 +13,6 @@ import { RESTClient } from '../helpers/rest'
|
||||
import { jsonDoc } from './collections/JSON'
|
||||
import { numberDoc } from './collections/Number'
|
||||
import { textDoc } from './collections/Text/shared'
|
||||
import { lexicalE2E } from './lexicalE2E'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
import {
|
||||
collapsibleFieldsSlug,
|
||||
@@ -195,6 +194,7 @@ describe('fields', () => {
|
||||
url = new AdminUrlUtil(serverURL, 'indexed-fields')
|
||||
})
|
||||
|
||||
// TODO: This test is flaky
|
||||
test('should display unique constraint error in ui', async () => {
|
||||
const uniqueText = 'uniqueText'
|
||||
await payload.create({
|
||||
@@ -796,7 +796,6 @@ describe('fields', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('lexical', lexicalE2E(client, page, serverURL))
|
||||
describe('richText', () => {
|
||||
async function navigateToRichTextFields() {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { IndexDirection, IndexOptions } from 'mongoose'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
|
||||
import type { MongooseAdapter } from '../../packages/db-mongodb/src/index'
|
||||
import type { SanitizedConfig } from '../../packages/payload/src/config/types'
|
||||
import type { PaginatedDocs } from '../../packages/payload/src/database/types'
|
||||
import type { RichTextField } from './payload-types'
|
||||
|
||||
@@ -27,11 +28,11 @@ import { defaultText } from './collections/Text/shared'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
import { arrayFieldsSlug, groupFieldsSlug, relationshipFieldsSlug, tabsFieldsSlug } from './slugs'
|
||||
|
||||
let client
|
||||
let client: RESTClient
|
||||
let graphQLClient: GraphQLClient
|
||||
let serverURL
|
||||
let config
|
||||
let token
|
||||
let serverURL: string
|
||||
let config: SanitizedConfig
|
||||
let token: string
|
||||
|
||||
describe('Fields', () => {
|
||||
beforeAll(async () => {
|
||||
|
||||
61
test/fields/lexical.e2e.spec.ts
Normal file
61
test/fields/lexical.e2e.spec.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
import payload from '../../packages/payload/src'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
|
||||
import { initPayloadE2E } from '../helpers/configHelpers'
|
||||
import { RESTClient } from '../helpers/rest'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
|
||||
const { beforeAll, describe, beforeEach } = test
|
||||
|
||||
let client: RESTClient
|
||||
let page: Page
|
||||
let serverURL: string
|
||||
|
||||
async function navigateToRichTextFields() {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
|
||||
await page.goto(url.list)
|
||||
await page.locator('.row-1 .cell-title a').click()
|
||||
}
|
||||
async function navigateToLexicalFields() {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'lexical-fields')
|
||||
await page.goto(url.list)
|
||||
await page.locator('.row-1 .cell-title a').click()
|
||||
}
|
||||
|
||||
describe('lexical', () => {
|
||||
beforeAll(async ({ browser }) => {
|
||||
const config = await initPayloadE2E(__dirname)
|
||||
serverURL = config.serverURL
|
||||
client = new RESTClient(null, { serverURL, defaultSlug: 'rich-text-fields' })
|
||||
await client.login()
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await clearAndSeedEverything(payload)
|
||||
await client.logout()
|
||||
client = new RESTClient(null, { serverURL, defaultSlug: 'rich-text-fields' })
|
||||
await client.login()
|
||||
})
|
||||
|
||||
test('should not warn about unsaved changes when navigating to lexical editor with blocks node and then leaving the page without actually changing anything', async () => {
|
||||
// This used to be an issue in the past, due to the node.setFields function in the blocks node being called unnecessarily when it's initialized after opening the document
|
||||
// Other than the annoying unsaved changed prompt, this can also cause unnecessary auto-saves, when drafts & autosave is enabled
|
||||
|
||||
await navigateToLexicalFields()
|
||||
|
||||
await expect(
|
||||
page.locator('.rich-text-lexical').nth(1).locator('.lexical-block').first(),
|
||||
).toBeVisible()
|
||||
|
||||
// Navigate to some different page, away from the current document
|
||||
await page.locator('.app-header__step-nav').first().locator('a').first().click()
|
||||
|
||||
// Make sure .leave-without-saving__content (the "Leave without saving") is not visible
|
||||
await expect(page.locator('.leave-without-saving__content').first()).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
395
test/fields/lexical.int.spec.ts
Normal file
395
test/fields/lexical.int.spec.ts
Normal file
@@ -0,0 +1,395 @@
|
||||
import type { SerializedEditorState } from 'lexical'
|
||||
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
|
||||
import type { SanitizedConfig } from '../../packages/payload/src/config/types'
|
||||
import type { PaginatedDocs } from '../../packages/payload/src/database/types'
|
||||
import type {
|
||||
SerializedBlockNode,
|
||||
SerializedLinkNode,
|
||||
SerializedUploadNode,
|
||||
} from '../../packages/richtext-lexical/src'
|
||||
import type { SerializedRelationshipNode } from '../../packages/richtext-lexical/src'
|
||||
import type { RichTextField } from './payload-types'
|
||||
|
||||
import payload from '../../packages/payload/src'
|
||||
import { initPayloadTest } from '../helpers/configHelpers'
|
||||
import { RESTClient } from '../helpers/rest'
|
||||
import configPromise from '../uploads/config'
|
||||
import { arrayDoc } from './collections/Array'
|
||||
import { lexicalDocData } from './collections/Lexical/data'
|
||||
import { richTextDocData } from './collections/RichText/data'
|
||||
import { generateLexicalRichText } from './collections/RichText/generateLexicalRichText'
|
||||
import { textDoc } from './collections/Text/shared'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
import {
|
||||
arrayFieldsSlug,
|
||||
lexicalFieldsSlug,
|
||||
richTextFieldsSlug,
|
||||
textFieldsSlug,
|
||||
uploadsSlug,
|
||||
} from './slugs'
|
||||
|
||||
let client: RESTClient
|
||||
let graphQLClient: GraphQLClient
|
||||
let serverURL: string
|
||||
let config: SanitizedConfig
|
||||
let token: string
|
||||
|
||||
let createdArrayDocID: number | string = null
|
||||
let createdJPGDocID: number | string = null
|
||||
let createdTextDocID: number | string = null
|
||||
let createdRichTextDocID: number | string = null
|
||||
|
||||
describe('Lexical', () => {
|
||||
beforeAll(async () => {
|
||||
;({ serverURL } = await initPayloadTest({ __dirname, init: { local: false } }))
|
||||
config = await configPromise
|
||||
|
||||
client = new RESTClient(config, { defaultSlug: richTextFieldsSlug, serverURL })
|
||||
const graphQLURL = `${serverURL}${config.routes.api}${config.routes.graphQL}`
|
||||
graphQLClient = new GraphQLClient(graphQLURL)
|
||||
token = await client.login()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await clearAndSeedEverything(payload)
|
||||
client = new RESTClient(config, { defaultSlug: richTextFieldsSlug, serverURL })
|
||||
await client.login()
|
||||
|
||||
createdArrayDocID = (
|
||||
await payload.find({
|
||||
collection: arrayFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
exists: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
).docs[0].id
|
||||
|
||||
createdJPGDocID = (
|
||||
await payload.find({
|
||||
collection: uploadsSlug,
|
||||
where: {
|
||||
id: {
|
||||
exists: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
).docs[0].id
|
||||
|
||||
createdTextDocID = (
|
||||
await payload.find({
|
||||
collection: textFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
exists: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
).docs[0].id
|
||||
|
||||
createdRichTextDocID = (
|
||||
await payload.find({
|
||||
collection: richTextFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
exists: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
).docs[0].id
|
||||
})
|
||||
|
||||
describe('basic', () => {
|
||||
it('should allow querying on lexical content', async () => {
|
||||
const richTextDoc: RichTextField = (
|
||||
await payload.find({
|
||||
collection: richTextFieldsSlug,
|
||||
where: {
|
||||
title: {
|
||||
equals: richTextDocData.title,
|
||||
},
|
||||
},
|
||||
depth: 0,
|
||||
})
|
||||
).docs[0] as never
|
||||
|
||||
expect(richTextDoc?.lexicalCustomFields).toStrictEqual(
|
||||
JSON.parse(
|
||||
JSON.stringify(generateLexicalRichText())
|
||||
.replace(
|
||||
/"\{\{ARRAY_DOC_ID\}\}"/g,
|
||||
payload.db.defaultIDType === 'number'
|
||||
? `${createdArrayDocID}`
|
||||
: `"${createdArrayDocID}"`,
|
||||
)
|
||||
.replace(
|
||||
/"\{\{UPLOAD_DOC_ID\}\}"/g,
|
||||
payload.db.defaultIDType === 'number' ? `${createdJPGDocID}` : `"${createdJPGDocID}"`,
|
||||
)
|
||||
.replace(
|
||||
/"\{\{TEXT_DOC_ID\}\}"/g,
|
||||
payload.db.defaultIDType === 'number'
|
||||
? `${createdTextDocID}`
|
||||
: `"${createdTextDocID}"`,
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
it('should populate respect depth parameter and populate link node relationship', async () => {
|
||||
const richTextDoc: RichTextField = (
|
||||
await payload.find({
|
||||
collection: richTextFieldsSlug,
|
||||
where: {
|
||||
title: {
|
||||
equals: richTextDocData.title,
|
||||
},
|
||||
},
|
||||
depth: 1,
|
||||
})
|
||||
).docs[0] as never
|
||||
|
||||
const seededDocument = JSON.parse(
|
||||
JSON.stringify(generateLexicalRichText())
|
||||
.replace(
|
||||
/"\{\{ARRAY_DOC_ID\}\}"/g,
|
||||
payload.db.defaultIDType === 'number'
|
||||
? `${createdArrayDocID}`
|
||||
: `"${createdArrayDocID}"`,
|
||||
)
|
||||
.replace(
|
||||
/"\{\{UPLOAD_DOC_ID\}\}"/g,
|
||||
payload.db.defaultIDType === 'number' ? `${createdJPGDocID}` : `"${createdJPGDocID}"`,
|
||||
)
|
||||
.replace(
|
||||
/"\{\{TEXT_DOC_ID\}\}"/g,
|
||||
payload.db.defaultIDType === 'number' ? `${createdTextDocID}` : `"${createdTextDocID}"`,
|
||||
),
|
||||
)
|
||||
|
||||
expect(richTextDoc?.lexicalCustomFields).not.toStrictEqual(seededDocument) // The whole seededDocument should not match, as richTextDoc should now contain populated documents not present in the seeded document
|
||||
expect(richTextDoc?.lexicalCustomFields).toMatchObject(seededDocument) // subset of seededDocument should match
|
||||
|
||||
const lexical: SerializedEditorState = richTextDoc?.lexicalCustomFields as never
|
||||
|
||||
const linkNode: SerializedLinkNode = lexical.root.children[1].children[3]
|
||||
expect(linkNode.fields.doc.value.items[1].text).toStrictEqual(arrayDoc.items[1].text)
|
||||
})
|
||||
|
||||
it('should populate relationship node', async () => {
|
||||
const richTextDoc: RichTextField = (
|
||||
await payload.find({
|
||||
collection: richTextFieldsSlug,
|
||||
where: {
|
||||
title: {
|
||||
equals: richTextDocData.title,
|
||||
},
|
||||
},
|
||||
depth: 1,
|
||||
})
|
||||
).docs[0] as never
|
||||
|
||||
const relationshipNode: SerializedRelationshipNode =
|
||||
richTextDoc.lexicalCustomFields.root.children.find((node) => node.type === 'relationship')
|
||||
|
||||
expect(relationshipNode.value.text).toStrictEqual(textDoc.text)
|
||||
})
|
||||
|
||||
it('should respect GraphQL rich text depth parameter and populate upload node', async () => {
|
||||
const query = `query {
|
||||
RichTextFields {
|
||||
docs {
|
||||
lexicalCustomFields(depth: 2)
|
||||
}
|
||||
}
|
||||
}`
|
||||
const response: {
|
||||
RichTextFields: PaginatedDocs<RichTextField>
|
||||
} = await graphQLClient.request(
|
||||
query,
|
||||
{},
|
||||
{
|
||||
Authorization: `JWT ${token}`,
|
||||
},
|
||||
)
|
||||
|
||||
const { docs } = response.RichTextFields
|
||||
|
||||
const uploadNode: SerializedUploadNode = docs[0].lexicalCustomFields.root.children.find(
|
||||
(node) => node.type === 'upload',
|
||||
)
|
||||
expect(uploadNode.value.media.filename).toStrictEqual('payload.png')
|
||||
})
|
||||
})
|
||||
|
||||
describe('advanced - blocks', () => {
|
||||
it('should not populate relationships in blocks if depth is 0', async () => {
|
||||
const lexicalDoc: RichTextField = (
|
||||
await payload.find({
|
||||
collection: lexicalFieldsSlug,
|
||||
where: {
|
||||
title: {
|
||||
equals: lexicalDocData.title,
|
||||
},
|
||||
},
|
||||
depth: 0,
|
||||
})
|
||||
).docs[0] as never
|
||||
|
||||
const lexicalField: SerializedEditorState = lexicalDoc?.lexicalWithBlocks as never
|
||||
|
||||
const relationshipBlockNode: SerializedBlockNode = lexicalField.root.children[2] as never
|
||||
|
||||
/**
|
||||
* Depth 1 population:
|
||||
*/
|
||||
expect(relationshipBlockNode.fields.data.rel).toStrictEqual(createdJPGDocID)
|
||||
})
|
||||
|
||||
it('should populate relationships in blocks with depth=1', async () => {
|
||||
const lexicalDoc: RichTextField = (
|
||||
await payload.find({
|
||||
collection: lexicalFieldsSlug,
|
||||
where: {
|
||||
title: {
|
||||
equals: lexicalDocData.title,
|
||||
},
|
||||
},
|
||||
depth: 1,
|
||||
})
|
||||
).docs[0] as never
|
||||
|
||||
const lexicalField: SerializedEditorState = lexicalDoc?.lexicalWithBlocks as never
|
||||
|
||||
const relationshipBlockNode: SerializedBlockNode = lexicalField.root.children[2] as never
|
||||
|
||||
/**
|
||||
* Depth 1 population:
|
||||
*/
|
||||
expect(relationshipBlockNode.fields.data.rel.filename).toStrictEqual('payload.jpg')
|
||||
})
|
||||
|
||||
it('should not populate relationship nodes inside of a sub-editor from a blocks node with 0 depth', async () => {
|
||||
const lexicalDoc: RichTextField = (
|
||||
await payload.find({
|
||||
collection: lexicalFieldsSlug,
|
||||
where: {
|
||||
title: {
|
||||
equals: lexicalDocData.title,
|
||||
},
|
||||
},
|
||||
depth: 0,
|
||||
})
|
||||
).docs[0] as never
|
||||
|
||||
const lexicalField: SerializedEditorState = lexicalDoc?.lexicalWithBlocks as never
|
||||
|
||||
const subEditorBlockNode: SerializedBlockNode = lexicalField.root.children[3] as never
|
||||
|
||||
const subEditor: SerializedEditorState = subEditorBlockNode.fields.data.richText
|
||||
|
||||
const subEditorRelationshipNode: SerializedRelationshipNode = subEditor.root
|
||||
.children[0] as never
|
||||
|
||||
/**
|
||||
* Depth 1 population:
|
||||
*/
|
||||
expect(subEditorRelationshipNode.value.id).toStrictEqual(createdRichTextDocID)
|
||||
// But the value should not be populated and only have the id field:
|
||||
expect(Object.keys(subEditorRelationshipNode.value)).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should populate relationship nodes inside of a sub-editor from a blocks node with 1 depth', async () => {
|
||||
const lexicalDoc: RichTextField = (
|
||||
await payload.find({
|
||||
collection: lexicalFieldsSlug,
|
||||
where: {
|
||||
title: {
|
||||
equals: lexicalDocData.title,
|
||||
},
|
||||
},
|
||||
depth: 1,
|
||||
})
|
||||
).docs[0] as never
|
||||
|
||||
const lexicalField: SerializedEditorState = lexicalDoc?.lexicalWithBlocks as never
|
||||
|
||||
const subEditorBlockNode: SerializedBlockNode = lexicalField.root.children[3] as never
|
||||
|
||||
const subEditor: SerializedEditorState = subEditorBlockNode.fields.data.richText
|
||||
|
||||
const subEditorRelationshipNode: SerializedRelationshipNode = subEditor.root
|
||||
.children[0] as never
|
||||
|
||||
/**
|
||||
* Depth 1 population:
|
||||
*/
|
||||
expect(subEditorRelationshipNode.value.id).toStrictEqual(createdRichTextDocID)
|
||||
expect(subEditorRelationshipNode.value.title).toStrictEqual(richTextDocData.title)
|
||||
|
||||
// Make sure that the referenced, popular document is NOT populated (that would require depth > 2):
|
||||
|
||||
const populatedDocEditorState: SerializedEditorState = subEditorRelationshipNode.value
|
||||
.lexicalCustomFields as never
|
||||
|
||||
const populatedDocEditorRelationshipNode: SerializedRelationshipNode = populatedDocEditorState
|
||||
.root.children[2] as never
|
||||
|
||||
//console.log('populatedDocEditorRelatonshipNode:', populatedDocEditorRelationshipNode)
|
||||
|
||||
/**
|
||||
* Depth 2 population:
|
||||
*/
|
||||
expect(populatedDocEditorRelationshipNode.value.id).toStrictEqual(createdTextDocID)
|
||||
// But the value should not be populated and only have the id field - that's because it would require a depth of 2
|
||||
expect(Object.keys(populatedDocEditorRelationshipNode.value)).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should populate relationship nodes inside of a sub-editor from a blocks node with depth 2', async () => {
|
||||
const lexicalDoc: RichTextField = (
|
||||
await payload.find({
|
||||
collection: lexicalFieldsSlug,
|
||||
where: {
|
||||
title: {
|
||||
equals: lexicalDocData.title,
|
||||
},
|
||||
},
|
||||
depth: 2,
|
||||
})
|
||||
).docs[0] as never
|
||||
|
||||
const lexicalField: SerializedEditorState = lexicalDoc?.lexicalWithBlocks as never
|
||||
|
||||
const subEditorBlockNode: SerializedBlockNode = lexicalField.root.children[3] as never
|
||||
|
||||
const subEditor: SerializedEditorState = subEditorBlockNode.fields.data.richText
|
||||
|
||||
const subEditorRelationshipNode: SerializedRelationshipNode = subEditor.root
|
||||
.children[0] as never
|
||||
|
||||
/**
|
||||
* Depth 1 population:
|
||||
*/
|
||||
expect(subEditorRelationshipNode.value.id).toStrictEqual(createdRichTextDocID)
|
||||
expect(subEditorRelationshipNode.value.title).toStrictEqual(richTextDocData.title)
|
||||
|
||||
// Make sure that the referenced, popular document is NOT populated (that would require depth > 2):
|
||||
|
||||
const populatedDocEditorState: SerializedEditorState = subEditorRelationshipNode.value
|
||||
.lexicalCustomFields as never
|
||||
|
||||
const populatedDocEditorRelationshipNode: SerializedRelationshipNode = populatedDocEditorState
|
||||
.root.children[2] as never
|
||||
|
||||
/**
|
||||
* Depth 2 population:
|
||||
*/
|
||||
expect(populatedDocEditorRelationshipNode.value.id).toStrictEqual(createdTextDocID)
|
||||
// Should now be populated (length 12)
|
||||
expect(populatedDocEditorRelationshipNode.value.text).toStrictEqual(textDoc.text)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
import type { RESTClient } from '../helpers/rest'
|
||||
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
|
||||
|
||||
const { describe } = test
|
||||
|
||||
export const lexicalE2E = (client: RESTClient, page: Page, serverURL: string) => {
|
||||
async function navigateToRichTextFields() {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
|
||||
await page.goto(url.list)
|
||||
await page.locator('.row-1 .cell-title a').click()
|
||||
}
|
||||
|
||||
return () => {
|
||||
describe('todo', () => {
|
||||
test.skip('todo', async () => {
|
||||
await navigateToRichTextFields()
|
||||
|
||||
await page.locator('todo').first().click()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,16 +12,18 @@ import { conditionalLogicDoc } from './collections/ConditionalLogic'
|
||||
import { dateDoc } from './collections/Date'
|
||||
import { groupDoc } from './collections/Group'
|
||||
import { jsonDoc } from './collections/JSON'
|
||||
import { lexicalRichTextDoc } from './collections/Lexical/data'
|
||||
import { lexicalDocData } from './collections/Lexical/data'
|
||||
import { lexicalMigrateDocData } from './collections/LexicalMigrate/data'
|
||||
import { numberDoc } from './collections/Number'
|
||||
import { pointDoc } from './collections/Point'
|
||||
import { radiosDoc } from './collections/Radio'
|
||||
import { richTextBulletsDoc, richTextDoc } from './collections/RichText/data'
|
||||
import { richTextBulletsDocData, richTextDocData } from './collections/RichText/data'
|
||||
import { selectsDoc } from './collections/Select'
|
||||
import { tabsDoc } from './collections/Tabs'
|
||||
import { textDoc } from './collections/Text/shared'
|
||||
import { uploadsDoc } from './collections/Upload'
|
||||
import {
|
||||
arrayFieldsSlug,
|
||||
blockFieldsSlug,
|
||||
codeFieldsSlug,
|
||||
collapsibleFieldsSlug,
|
||||
@@ -55,7 +57,7 @@ export async function clearAndSeedEverything(_payload: Payload) {
|
||||
const [jpgFile, pngFile] = await Promise.all([getFileByPath(jpgPath), getFileByPath(pngPath)])
|
||||
|
||||
const [createdArrayDoc, createdTextDoc, createdPNGDoc] = await Promise.all([
|
||||
_payload.create({ collection: 'array-fields', data: arrayDoc }),
|
||||
_payload.create({ collection: arrayFieldsSlug, data: arrayDoc }),
|
||||
_payload.create({ collection: textFieldsSlug, data: textDoc }),
|
||||
_payload.create({ collection: uploadsSlug, data: {}, file: pngFile }),
|
||||
])
|
||||
@@ -79,13 +81,13 @@ export async function clearAndSeedEverything(_payload: Payload) {
|
||||
_payload.db.defaultIDType === 'number' ? createdTextDoc.id : `"${createdTextDoc.id}"`
|
||||
|
||||
const richTextDocWithRelId = JSON.parse(
|
||||
JSON.stringify(richTextDoc)
|
||||
JSON.stringify(richTextDocData)
|
||||
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
|
||||
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
|
||||
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`),
|
||||
)
|
||||
const richTextBulletsDocWithRelId = JSON.parse(
|
||||
JSON.stringify(richTextBulletsDoc)
|
||||
JSON.stringify(richTextBulletsDocData)
|
||||
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
|
||||
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
|
||||
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`),
|
||||
@@ -98,11 +100,32 @@ export async function clearAndSeedEverything(_payload: Payload) {
|
||||
blocksDocWithRichText.blocks[0].richText = richTextDocWithRelationship.richText
|
||||
blocksDocWithRichText.localizedBlocks[0].richText = richTextDocWithRelationship.richText
|
||||
|
||||
const lexicalRichTextDocWithRelId = JSON.parse(
|
||||
JSON.stringify(lexicalRichTextDoc)
|
||||
await _payload.create({ collection: richTextFieldsSlug, data: richTextBulletsDocWithRelId })
|
||||
|
||||
const createdRichTextDoc = await _payload.create({
|
||||
collection: richTextFieldsSlug,
|
||||
data: richTextDocWithRelationship,
|
||||
})
|
||||
|
||||
const formattedRichTextDocID =
|
||||
_payload.db.defaultIDType === 'number'
|
||||
? createdRichTextDoc.id
|
||||
: `"${createdRichTextDoc.id}"`
|
||||
|
||||
const lexicalDocWithRelId = JSON.parse(
|
||||
JSON.stringify(lexicalDocData)
|
||||
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
|
||||
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
|
||||
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`),
|
||||
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`)
|
||||
.replace(/"\{\{RICH_TEXT_DOC_ID\}\}"/g, `${formattedRichTextDocID}`),
|
||||
)
|
||||
|
||||
const lexicalMigrateDocWithRelId = JSON.parse(
|
||||
JSON.stringify(lexicalMigrateDocData)
|
||||
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
|
||||
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
|
||||
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`)
|
||||
.replace(/"\{\{RICH_TEXT_DOC_ID\}\}"/g, `${formattedRichTextDocID}`),
|
||||
)
|
||||
|
||||
await Promise.all([
|
||||
@@ -126,15 +149,12 @@ export async function clearAndSeedEverything(_payload: Payload) {
|
||||
|
||||
_payload.create({ collection: blockFieldsSlug, data: blocksDocWithRichText }),
|
||||
|
||||
_payload.create({ collection: lexicalFieldsSlug, data: lexicalRichTextDocWithRelId }),
|
||||
_payload.create({ collection: lexicalFieldsSlug, data: lexicalDocWithRelId }),
|
||||
_payload.create({
|
||||
collection: lexicalMigrateFieldsSlug,
|
||||
data: lexicalRichTextDocWithRelId,
|
||||
data: lexicalMigrateDocWithRelId,
|
||||
}),
|
||||
|
||||
_payload.create({ collection: richTextFieldsSlug, data: richTextBulletsDocWithRelId }),
|
||||
_payload.create({ collection: richTextFieldsSlug, data: richTextDocWithRelationship }),
|
||||
|
||||
_payload.create({ collection: numberFieldsSlug, data: { number: 2 } }),
|
||||
_payload.create({ collection: numberFieldsSlug, data: { number: 3 } }),
|
||||
_payload.create({ collection: numberFieldsSlug, data: numberDoc }),
|
||||
|
||||
Reference in New Issue
Block a user