Files
payload/test/lexical-mdx/int.spec.ts
Alessio Gravili d4f1add2ab feat(richtext-lexical): mdx support (#9160)
Supports bi-directional import/export between MDX <=> Lexical. JSX will
be mapped to lexical blocks back and forth.

This will allow editing our mdx docs in payload while keeping mdx as the
source of truth

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2024-11-17 15:03:45 -07:00

1520 lines
36 KiB
TypeScript

/* eslint jest/no-conditional-in-test: 0 */
import type {
BlockFields,
LexicalRichTextAdapter,
SanitizedServerEditorConfig,
SerializedBlockNode,
} from '@payloadcms/richtext-lexical'
import type { RichTextField, SanitizedConfig } from 'payload'
import type { MarkOptional } from 'ts-essentials'
import path from 'path'
import { fileURLToPath } from 'url'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
import { postsSlug } from './collections/Posts/index.js'
import { editorJSONToMDX, mdxToEditorJSON } from './mdx/hooks.js'
import { tableJson } from './tableJson.js'
import { textToRichText } from './textToRichText.js'
let config: SanitizedConfig
let editorConfig: SanitizedServerEditorConfig
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
type Tests = Array<{
blockNode?: {
fields: Omit<BlockFields, 'id'>
} & Omit<
MarkOptional<SerializedBlockNode, 'children' | 'fields' | 'format' | 'type' | 'version'>,
'fields'
>
debugFlag?: boolean
description?: string
ignoreSpacesAndNewlines?: boolean
input: string
inputAfterConvertFromEditorJSON?: string
rootChildren?: any[]
}>
describe('Lexical MDX', () => {
// --__--__--__--__--__--__--__--__--__
// Boilerplate test setup/teardown
// --__--__--__--__--__--__--__--__--__
beforeAll(async () => {
const { config: incomingConfig } = await initPayloadInt(dirname, undefined, false)
config = incomingConfig
const richTextField: RichTextField = config.collections
.find((collection) => collection.slug === postsSlug)
.fields.find(
(field) => 'name' in field && field.name === 'richText',
) as unknown as RichTextField
editorConfig = (richTextField.editor as LexicalRichTextAdapter).editorConfig
})
const INPUT_AND_OUTPUTBase: Tests = [
{
inputAfterConvertFromEditorJSON: `<PackageInstallOptions packageId="444" uniqueId="xxx" update/>`,
input: `
<PackageInstallOptions
packageId="444"
uniqueId="xxx" update/>
`,
blockNode: {
fields: {
blockType: 'PackageInstallOptions',
packageId: '444',
update: true,
uniqueId: 'xxx',
},
},
},
{
input: `
<PackageInstallOptions packageId="444">
ignored
</PackageInstallOptions>
`,
inputAfterConvertFromEditorJSON: `<PackageInstallOptions packageId="444"/>`,
blockNode: {
fields: {
blockType: 'PackageInstallOptions',
packageId: '444',
},
},
},
{
input: `
<PackageInstallOptions update packageId="444">
ignored
</PackageInstallOptions>
`,
inputAfterConvertFromEditorJSON: `<PackageInstallOptions packageId="444" update/>`,
blockNode: {
fields: {
blockType: 'PackageInstallOptions',
packageId: '444',
update: true,
},
},
},
{
input: `
<PackageInstallOptions
update
packageId="444">
ignored
</PackageInstallOptions>
`,
inputAfterConvertFromEditorJSON: `<PackageInstallOptions packageId="444" update/>`,
blockNode: {
fields: {
blockType: 'PackageInstallOptions',
packageId: '444',
update: true,
},
},
},
{
inputAfterConvertFromEditorJSON: `<PackageInstallOptions packageId="444" update/>`,
input: `
<PackageInstallOptions
update
packageId="444"
>
ignored
</PackageInstallOptions>
`,
blockNode: {
fields: {
blockType: 'PackageInstallOptions',
packageId: '444',
update: true,
},
},
},
{
inputAfterConvertFromEditorJSON: `<PackageInstallOptions packageId="444" someNestedObject={{"test":"hello"}} update/>`, // Not test - test is not part of the block
input: `
<PackageInstallOptions
update
packageId="444"
someNestedObject={{test: "hello"}} test={4}
>
ignored
</PackageInstallOptions>
`,
blockNode: {
fields: {
blockType: 'PackageInstallOptions',
packageId: '444',
update: true,
someNestedObject: { test: 'hello' },
},
},
},
{
inputAfterConvertFromEditorJSON: `<PackageInstallOptions packageId="444" update/>`,
input: `
<PackageInstallOptions
update
packageId="444"
>
ignored
<PackageInstallOptions
update
packageId="444"
someNestedObject={{test: "hello"}} test={4}
>
ignoredi
</PackageInstallOptions>
</PackageInstallOptions>
`,
blockNode: {
fields: {
blockType: 'PackageInstallOptions',
packageId: '444',
update: true,
},
},
},
{
inputAfterConvertFromEditorJSON: `<PackageInstallOptions packageId="444" update/>`,
input: `
<PackageInstallOptions
update
packageId="444"
>
ignored
<PackageInstallOptions
update
packageId="444"
someNestedObject={{test: "hello"}} test={4}
>
ignoredi
</PackageInstallOptions>
<TagThatImmediatelyClosesShouldBeCorrectlyHandledByContentSubTagStartAmount />
<Tag2 test="hello" />
</PackageInstallOptions>
`,
blockNode: {
fields: {
blockType: 'PackageInstallOptions',
packageId: '444',
update: true,
},
},
},
// TODO: Write test for this:
/*
<PackageInstallOptions
update
packageId="444"
>
not ignored
<PackageInstallOptions
update
packageId="444"
someNestedObject={{test: "hello"}} test={4}
>
not ignored
</PackageInstallOptions>
not ignored
</PackageInstallOptions>
*/
{
input: `
\`\`\`ts
hello\`\`\`
`,
inputAfterConvertFromEditorJSON: `
\`\`\`ts
hello
\`\`\`
`,
blockNode: {
fields: {
blockType: 'Code',
code: 'hello',
language: 'ts',
},
},
},
{
input: `
\`\`\`ts
hello\`\`\`
`,
inputAfterConvertFromEditorJSON: `
\`\`\`ts
hello
\`\`\`
`,
blockNode: {
fields: {
blockType: 'Code',
code: ' hello',
language: 'ts',
},
},
},
{
input: `
\`\`\`ts x\n hello\`\`\`
`,
inputAfterConvertFromEditorJSON: `
\`\`\`ts
x
hello
\`\`\`
`,
blockNode: {
fields: {
blockType: 'Code',
code: ' x\n hello',
language: 'ts',
},
},
},
{
input: `
\`\`\`ts hello\`\`\`
`,
blockNode: {
fields: {
blockType: 'Code',
code: 'ts hello',
language: '',
},
},
},
{
input: `
\`\`\`hello\`\`\`
`,
blockNode: {
fields: {
blockType: 'Code',
code: 'hello',
language: '',
},
},
},
{
input: `
\`\`\`ts
hello
\`\`\`
`,
blockNode: {
fields: {
blockType: 'Code',
code: 'hello',
language: 'ts',
},
},
},
{
input: `
\`\`\`ts hello
there1
\`\`\`
`,
inputAfterConvertFromEditorJSON: `
\`\`\`ts
hello
there1
\`\`\`
`,
blockNode: {
fields: {
blockType: 'Code',
code: ' hello\nthere1',
language: 'ts',
},
},
},
{
input: `
\`\`\`ts hello
there2
!!\`\`\`
`,
inputAfterConvertFromEditorJSON: `
\`\`\`ts
hello
there2
!!
\`\`\`
`,
blockNode: {
fields: {
blockType: 'Code',
code: ' hello\nthere2\n!!',
language: 'ts',
},
},
},
{
input: `
\`\`\`ts
Hello
there3\`\`\`
`,
inputAfterConvertFromEditorJSON: `
\`\`\`ts
Hello
there3
\`\`\`
`,
blockNode: {
fields: {
blockType: 'Code',
code: 'Hello\nthere3',
language: 'ts',
},
},
},
{
input: `
\`\`\`ts
Hello
\`\`\`ts
nested
\`\`\`!
there4\`\`\`
`,
inputAfterConvertFromEditorJSON: `
\`\`\`ts
Hello
\`\`\`ts
nested
\`\`\`!
there4
\`\`\`
`,
blockNode: {
fields: {
blockType: 'Code',
code: 'Hello\n```ts\nnested\n```!\nthere4',
language: 'ts',
},
},
},
{
ignoreSpacesAndNewlines: true,
input: `
| Option | Default route | Description |
| ----------------- | ----------------------- | ----------------------------------------------- |
| \`account\` | | The user's account page. |
| \`createFirstUser\` | \`/create-first-user\` | The page to create the first user. |
`,
inputAfterConvertFromEditorJSON: `
| Option | Default route | Description |
|---|---|---|
| \`account\` | | The user's account page. |
| \`createFirstUser\` | \`/create-first-user\` | The page to create the first user. |
`,
rootChildren: [tableJson],
},
{
input: `
<Banner>
children text
</Banner>
`,
blockNode: {
fields: {
blockType: 'Banner',
content: textToRichText('children text'),
},
},
},
{
input: `\`inline code\``,
rootChildren: [
{
children: [
{
detail: 0,
format: 16, // Format 16 => inline code
mode: 'normal',
style: '',
text: 'inline code',
type: 'text',
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
],
},
{
// This test ensures that the JSX within the code block is does not disrupt the main JSX parsing
input: `
<Banner>
\`https://<some link>.payloadcms.com/page\`
</Banner>
`,
blockNode: {
fields: {
blockType: 'Banner',
content: {
root: {
children: [
{
children: [
{
detail: 0,
format: 16, // Format 16 => inline code
mode: 'normal',
style: '',
text: 'https://<some link>.payloadcms.com/page',
type: 'text',
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
],
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
},
},
{
input: 'Hello <InlineCode>inline code</InlineCode> test.',
rootChildren: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Hello ',
type: 'text',
version: 1,
},
{
type: 'inlineBlock',
fields: {
code: 'inline code',
blockType: 'InlineCode',
},
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' test.',
type: 'text',
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
],
},
{
input: `
<Banner>
Some text 1 <InlineCode>code 1</InlineCode> some
text 2 <InlineCode>code 2</InlineCode> some text
3 <InlineCode>code 3</InlineCode> some text 4<InlineCode>code 4</InlineCode>
</Banner>
`,
description: 'Banner with inline codes, each line a paragraph',
blockNode: {
fields: {
blockType: 'Banner',
content: {
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Some text 1 ',
type: 'text',
version: 1,
},
{
type: 'inlineBlock',
fields: {
code: 'code 1',
blockType: 'InlineCode',
},
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' some',
type: 'text',
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'text 2 ',
type: 'text',
version: 1,
},
{
type: 'inlineBlock',
fields: {
code: 'code 2',
blockType: 'InlineCode',
},
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' some text',
type: 'text',
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '3 ',
type: 'text',
version: 1,
},
{
type: 'inlineBlock',
fields: {
code: 'code 3',
blockType: 'InlineCode',
},
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' some text 4',
type: 'text',
version: 1,
},
{
type: 'inlineBlock',
fields: {
code: 'code 4',
blockType: 'InlineCode',
},
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
],
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
},
},
{
input: `
<Banner>
Some text 1 <InlineCode>code 1</InlineCode> some
text 2 <InlineCode>code 2</InlineCode> some text
3 <InlineCode>code 3</InlineCode> some text 4<InlineCode>code 4</InlineCode>
</Banner>
`,
description: 'Banner with inline codes, three paragraphs',
blockNode: {
fields: {
blockType: 'Banner',
content: {
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Some text 1 ',
type: 'text',
version: 1,
},
{
type: 'inlineBlock',
fields: {
code: 'code 1',
blockType: 'InlineCode',
},
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' some',
type: 'text',
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'text 2 ',
type: 'text',
version: 1,
},
{
type: 'inlineBlock',
fields: {
code: 'code 2',
blockType: 'InlineCode',
},
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' some text',
type: 'text',
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '3 ',
type: 'text',
version: 1,
},
{
type: 'inlineBlock',
fields: {
code: 'code 3',
blockType: 'InlineCode',
},
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' some text 4',
type: 'text',
version: 1,
},
{
type: 'inlineBlock',
fields: {
code: 'code 4',
blockType: 'InlineCode',
},
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
],
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
},
},
{
input: `
Text before banner
<Banner>
test
</Banner>
`,
rootChildren: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Text before banner',
type: 'text',
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
{
type: 'block',
format: '',
fields: {
blockType: 'Banner',
content: {
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'test',
type: 'text',
version: 1,
},
],
format: '',
indent: 0,
textFormat: 0,
textStyle: '',
type: 'paragraph',
version: 1,
},
],
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
version: 2,
},
],
},
{
description: 'TextContainerNoTrim with nested, no-leftpad content',
input: `
<TextContainerNoTrim>
no indent
indent 2
indent 4
no indent
</TextContainerNoTrim>
`,
blockNode: {
fields: {
blockType: 'TextContainerNoTrim',
text: `no indent
indent 2
indent 4
no indent`,
},
},
},
{
description: 'TextContainer with nested, no-leftpad content',
input: `
<TextContainer>
no indent
indent 2
indent 4
no indent
</TextContainer>
`,
inputAfterConvertFromEditorJSON: `
<TextContainer>
no indent
indent 2
indent 4
no indent
</TextContainer>
`,
blockNode: {
fields: {
blockType: 'TextContainer',
text: `no indent
indent 2
indent 4
no indent`,
},
},
},
{
description: 'TextContainerNoTrim with nested, leftpad content',
input: `
<TextContainerNoTrim>
indent 2
indent 4
indent 6
indent 2
</TextContainerNoTrim>
`,
blockNode: {
fields: {
blockType: 'TextContainerNoTrim',
text: ` indent 2
indent 4
indent 6
indent 2`,
},
},
},
{
description: 'TextContainer with nested, leftpad content',
input: `
<TextContainer>
indent 2
indent 4
indent 6
indent 2
</TextContainer>
`,
blockNode: {
fields: {
blockType: 'TextContainer',
text: `indent 2
indent 4
indent 6
indent 2`,
},
},
},
{
input: `
Some text 1
<InlineCode>code 2</InlineCode>
`,
description: 'InlineCode after text, split by linebreak',
rootChildren: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Some text 1',
type: 'text',
version: 1,
},
{
type: 'linebreak',
version: 1,
},
{
type: 'inlineBlock',
fields: {
code: 'code 2',
blockType: 'InlineCode',
},
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
],
},
{
description: 'Code block with nested within Banner',
input: `
<Banner>
\`\`\`ts
indent 1;
\`\`\`
</Banner>
`,
blockNode: {
fields: {
blockType: 'Banner',
content: {
root: {
children: [
{
fields: {
blockType: 'Code',
code: ' indent 1;',
language: 'ts',
},
format: '',
type: 'block',
version: 2,
},
],
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
},
},
{
description: 'Code block with nested within Banner 2',
input: `
<Banner>
\`\`\`ts
indent 1;
\`\`\`
</Banner>
`,
inputAfterConvertFromEditorJSON: `
<Banner>
\`\`\`ts
indent 1;
\`\`\`
</Banner>
`,
blockNode: {
fields: {
blockType: 'Banner',
content: {
root: {
children: [
{
fields: {
blockType: 'Code',
code: ' indent 1;',
language: 'ts',
},
format: '',
type: 'block',
version: 2,
},
],
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
},
},
{
description: 'One-line Banner',
input: `
<Banner>Hi</Banner>
`,
inputAfterConvertFromEditorJSON: `
<Banner>
Hi
</Banner>
`,
blockNode: {
fields: {
blockType: 'Banner',
content: {
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Hi',
type: 'text',
version: 1,
},
],
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
],
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
},
},
{
description: 'Code block with nested within 2 Banners',
input: `
<Banner>
<Banner>
\`\`\`ts
indent 1;
\`\`\`
</Banner>
</Banner>
`,
inputAfterConvertFromEditorJSON: `
<Banner>
<Banner>
\`\`\`ts
indent 1;
\`\`\`
</Banner>
</Banner>
`,
blockNode: {
fields: {
blockType: 'Banner',
content: {
root: {
children: [
{
type: 'block',
format: '',
fields: {
blockType: 'Banner',
content: {
root: {
children: [
{
fields: {
blockType: 'Code',
code: ' indent 1;',
language: 'ts',
},
format: '',
type: 'block',
version: 2,
},
],
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
version: 2,
},
],
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
},
},
{
inputAfterConvertFromEditorJSON: `
<Banner>
Some line [Start of link line2](/some/link)
</Banner>
`,
input: `
<Banner>
Some line [Start of link
line2](/some/link)
</Banner>
`,
blockNode: {
fields: {
blockType: 'Banner',
content: {
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Some line ',
type: 'text',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Start of link line2',
type: 'text',
version: 1,
},
],
fields: {
linkType: 'custom',
newTab: false,
url: '/some/link',
},
format: '',
indent: 0,
type: 'link',
version: 3,
},
],
direction: null,
format: '',
indent: 0,
textFormat: 0,
textStyle: '',
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
},
},
{
inputAfterConvertFromEditorJSON: `
<Banner>
Text text [ Link ](/some/link) .
</Banner>
`,
input: `
<Banner>
Text text [ Link
](/some/link) .
</Banner>
`,
blockNode: {
fields: {
blockType: 'Banner',
content: {
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Text text ',
type: 'text',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' Link ',
type: 'text',
version: 1,
},
],
fields: {
linkType: 'custom',
newTab: false,
url: '/some/link',
},
format: '',
indent: 0,
type: 'link',
version: 3,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' .',
type: 'text',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
textFormat: 0,
textStyle: '',
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
},
},
]
const INPUT_AND_OUTPUT: Tests = INPUT_AND_OUTPUTBase //.filter((test) => test.debugFlag)
for (const {
input,
inputAfterConvertFromEditorJSON,
blockNode,
ignoreSpacesAndNewlines,
rootChildren,
description,
} of INPUT_AND_OUTPUT) {
let sanitizedInput = input
// Remove beginning and end newline of input if exists (since the input is a template string)
if (sanitizedInput.startsWith('\n')) {
sanitizedInput = sanitizedInput.slice(1)
}
if (sanitizedInput.endsWith('\n')) {
sanitizedInput = sanitizedInput.slice(0, -1)
}
let sanitizedInputAfterConvertFromEditorJSON = inputAfterConvertFromEditorJSON
if (sanitizedInputAfterConvertFromEditorJSON) {
if (sanitizedInputAfterConvertFromEditorJSON.startsWith('\n')) {
sanitizedInputAfterConvertFromEditorJSON = sanitizedInputAfterConvertFromEditorJSON.slice(1)
}
if (sanitizedInputAfterConvertFromEditorJSON.endsWith('\n')) {
sanitizedInputAfterConvertFromEditorJSON = sanitizedInputAfterConvertFromEditorJSON.slice(
0,
-1,
)
}
}
it(`can convert to editor JSON: ${description ?? sanitizedInput}"`, () => {
const result = mdxToEditorJSON({
mdxWithFrontmatter: sanitizedInput,
editorConfig,
})
if (blockNode) {
const receivedBlockNode: SerializedBlockNode = result.editorState.root
.children[0] as unknown as SerializedBlockNode
expect(receivedBlockNode).not.toBeNull()
// By doing it like this, the blockNode defined in the test does not need to have all the top-level properties. We only wanna compare keys that are defined in the test
const receivedBlockNodeToTest = {}
for (const key in blockNode) {
receivedBlockNodeToTest[key] = receivedBlockNode[key]
}
removeUndefinedAndIDRecursively(receivedBlockNodeToTest)
removeUndefinedAndIDRecursively(blockNode)
expect(receivedBlockNodeToTest).toStrictEqual(blockNode)
} else if (rootChildren) {
const receivedRootChildren = result.editorState.root.children
removeUndefinedAndIDRecursively(receivedRootChildren)
removeUndefinedAndIDRecursively(rootChildren)
expect(receivedRootChildren).toStrictEqual(rootChildren)
} else {
throw new Error('Test not configured properly')
}
})
it(`can convert from editor JSON: ${description ?? sanitizedInput}"`, () => {
const editorState = {
root: {
children: blockNode
? [
{
format: '',
type: 'block',
version: 2,
...blockNode,
},
]
: rootChildren,
format: '',
indent: 0,
type: 'root',
version: 1,
},
}
const result = editorJSONToMDX({
editorConfig,
editorState,
})
// Remove all spaces and newlines
const resultNoSpace = ignoreSpacesAndNewlines ? result.replace(/\s/g, '') : result
const inputNoSpace = ignoreSpacesAndNewlines
? (sanitizedInputAfterConvertFromEditorJSON ?? sanitizedInput).replace(/\s/g, '')
: (sanitizedInputAfterConvertFromEditorJSON ?? sanitizedInput)
console.log('resultNoSpace', resultNoSpace)
console.log('inputNoSpace', inputNoSpace)
expect(resultNoSpace).toBe(inputNoSpace)
})
}
})
function removeUndefinedAndIDRecursively(obj: object) {
for (const key in obj) {
const value = obj[key]
if (value && typeof value === 'object') {
removeUndefinedAndIDRecursively(value)
} else if (value === undefined) {
delete obj[key]
} else if (value === null) {
delete obj[key]
} else if (key === 'id') {
delete obj[key]
}
}
}