Skip to content

Commit

Permalink
fix(portable-text-editor): fix sync issue with text spans (#4467)
Browse files Browse the repository at this point in the history
* fix(portable-text-editor): fix sync issue with text spans

When a span text is changed, it can't be updated only using setNodes on the node itself.

Use Slate delete and insertText for this.

* test(portable-text-editor): add some syncValue tests
  • Loading branch information
skogsmaskin authored May 12, 2023
1 parent 97b923e commit 036e68a
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* \@jest-environment ./test/setup/jsdom.jest.env.ts
*/
/* eslint-disable no-irregular-whitespace */
// eslint-disable-next-line import/no-unassigned-import
import '@testing-library/jest-dom/extend-expect'
import {render, waitFor} from '@testing-library/react'

import React from 'react'
import {PortableTextEditor} from '../PortableTextEditor'
import {PortableTextEditorTester, schemaType} from '../__tests__/PortableTextEditorTester'

const initialValue = [
{
_key: '77071c3af231',
_type: 'myTestBlockType',
children: [
{
_key: 'c001f0e92c1f0',
_type: 'span',
marks: [],
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ',
},
],
markDefs: [],
style: 'normal',
},
]

describe('useSyncValue', () => {
it('updates span text', async () => {
const editorRef: React.RefObject<PortableTextEditor> = React.createRef()
const onChange = jest.fn()
const syncedValue = [
{
_key: '77071c3af231',
_type: 'myTestBlockType',
children: [
{
_key: 'c001f0e92c1f0',
_type: 'span',
marks: [],
text: 'Lorem my ipsum!',
},
],
markDefs: [],
style: 'normal',
},
]
const {rerender} = render(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={initialValue}
/>
)
rerender(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={syncedValue}
/>
)
await waitFor(() => {
if (editorRef.current) {
expect(PortableTextEditor.getValue(editorRef.current)).toEqual(syncedValue)
}
})
})
it('replaces span nodes with different keys inside the same children array', async () => {
const editorRef: React.RefObject<PortableTextEditor> = React.createRef()
const onChange = jest.fn()
const syncedValue = [
{
_key: '77071c3af231',
_type: 'myTestBlockType',
children: [
{
_key: 'c001f0e92c1f0__NEW_KEY_YA!',
_type: 'span',
marks: [],
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ',
},
],
markDefs: [],
style: 'normal',
},
]
const {rerender} = render(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={initialValue}
/>
)
rerender(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={syncedValue}
/>
)
await waitFor(() => {
if (editorRef.current) {
expect(PortableTextEditor.getValue(editorRef.current)).toMatchInlineSnapshot(`
Array [
Object {
"_key": "77071c3af231",
"_type": "myTestBlockType",
"children": Array [
Object {
"_key": "c001f0e92c1f0__NEW_KEY_YA!",
"_type": "span",
"marks": Array [],
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
},
],
"markDefs": Array [],
"style": "normal",
},
]
`)
}
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import React, {useMemo, useRef} from 'react'
import {PortableTextBlock} from '@sanity/types'
import {isEqual} from 'lodash'
import {Editor, Transforms, Node, Descendant} from 'slate'
import {Editor, Transforms, Node, Descendant, Text} from 'slate'
import {useSlate} from '@sanity/slate-react'
import {PortableTextEditor} from '../PortableTextEditor'
import {EditorChange, PortableTextSlateEditor} from '../../types/editor'
Expand Down Expand Up @@ -250,21 +250,35 @@ function _updateBlock(
currentBlock.children.forEach((currentBlockChild, currentBlockChildIndex) => {
const oldBlockChild = oldBlock.children[currentBlockChildIndex]
const isChildChanged = !isEqual(currentBlockChild, oldBlockChild)
const isTextChanged = !isEqual(currentBlockChild.text, oldBlockChild.text)
const path = [currentBlockIndex, currentBlockChildIndex]
if (isChildChanged) {
// Update if this is the same child
if (currentBlockChild._key === oldBlockChild?._key) {
debug('Updating changed child', currentBlockChild)
debug('Updating changed child', currentBlockChild, oldBlockChild)
Transforms.setNodes(slateEditor, currentBlockChild as Partial<Node>, {
at: [currentBlockIndex, currentBlockChildIndex],
at: path,
})
// If it's a inline block, also update the void text node key
if (currentBlockChild._type !== 'span') {
const isSpanNode =
Text.isText(currentBlockChild) &&
currentBlockChild._type === 'span' &&
Text.isText(oldBlockChild) &&
oldBlockChild._type === 'span'
if (isSpanNode && isTextChanged) {
Transforms.delete(slateEditor, {
at: {focus: {path, offset: 0}, anchor: {path, offset: oldBlockChild.text.length}},
})
Transforms.insertText(slateEditor, currentBlockChild.text, {
at: path,
})
} else if (!isSpanNode) {
// If it's a inline block, also update the void text node key
debug('Updating changed inline object child', currentBlockChild)
Transforms.setNodes(
slateEditor,
{_key: `${currentBlock._key}-void-child`},
{
at: [currentBlockIndex, currentBlockChildIndex, 0],
at: [...path, 0],
voids: true,
}
)
Expand Down

2 comments on commit 036e68a

@vercel
Copy link

@vercel vercel bot commented on 036e68a May 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

performance-studio – ./

performance-studio.sanity.build
performance-studio-git-next.sanity.build

@vercel
Copy link

@vercel vercel bot commented on 036e68a May 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

test-studio – ./

test-studio.sanity.build
test-studio-git-next.sanity.build

Please sign in to comment.