Skip to content
32 changes: 12 additions & 20 deletions packages/editor/src/internal-utils/operation-to-patches.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ const createDefaultValue = () =>
] as Array<PortableTextBlock>

describe(insertNodePatch.name, () => {
test('Scenario: Inserting block object on empty editor', () => {
test('Scenario: Inserting block object on empty editor uses atomic set', () => {
// When inserting into an empty array (beforeValue = []), we use atomic `set`
// instead of `setIfMissing + insert`. This handles the case where the field
// value is null (not just undefined or []), since setIfMissing treats null
// as "present" and would be a no-op.
expect(
insertNodePatch(
compileSchema(defineSchema({blockObjects: [{name: 'image'}]})),
Expand Down Expand Up @@ -126,19 +130,13 @@ describe(insertNodePatch.name, () => {
).toEqual([
{
path: [],
type: 'setIfMissing',
value: [],
},
{
path: [0],
type: 'insert',
items: [
type: 'set',
value: [
{
_key: 'k2',
_type: 'image',
},
],
position: 'before',
},
])
})
Expand Down Expand Up @@ -252,7 +250,9 @@ describe('operationToPatches', () => {
`)
})

it('produce correct insert block patch with an empty editor', () => {
it('produce correct insert block patch with an empty editor (uses atomic set)', () => {
// When inserting into an empty array, we use atomic `set` instead of
// `setIfMissing + insert` to handle null field values correctly.
editor.children = []
editor.onChange()
expect(
Expand All @@ -277,21 +277,13 @@ describe('operationToPatches', () => {
[
{
"path": [],
"type": "setIfMissing",
"value": [],
},
{
"items": [
"type": "set",
"value": [
{
"_key": "c130395c640c",
"_type": "someObject",
},
],
"path": [
0,
],
"position": "before",
"type": "insert",
},
]
`)
Expand Down
9 changes: 5 additions & 4 deletions packages/editor/src/internal-utils/operation-to-patches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,12 +293,13 @@ export function insertNodePatch(
),
]
}
// When inserting into an empty array (no targetKey), use atomic set instead of
// setIfMissing + insert. This handles the case where the field value is null
// (not just undefined or []), since setIfMissing treats null as "present".
return [
setIfMissing(beforeValue, []),
insert(
set(
[fromSlateBlock(operation.node as Descendant, schema.block.name)],
'before',
[operation.path[0]!],
[],
),
]
} else if (
Expand Down
8 changes: 5 additions & 3 deletions packages/editor/src/slate-plugins/slate-plugin.patches.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {insert, setIfMissing, unset, type Patch} from '@portabletext/patches'
import {set, setIfMissing, unset, type Patch} from '@portabletext/patches'
import type {PortableTextBlock} from '@portabletext/schema'
import {Editor, type Operation} from 'slate'
import type {EditorActor} from '../editor/editor-machine'
Expand Down Expand Up @@ -128,13 +128,15 @@ export function createPatchesPlugin({
return editor
}

// If the editor was empty and now isn't, insert the placeholder into it.
// If the editor was empty and now isn't, set the value atomically.
// Using set() instead of insert() handles the case where the field is null
// (not just undefined or []), since setIfMissing treats null as "present".
if (
editorWasEmpty &&
!editorIsEmpty &&
operation.type !== 'set_selection'
) {
patches.push(insert(previousValue, 'before', [0]))
patches.push(set(previousValue, []))
}

switch (operation.type) {
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/tests/change-subject.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ describe('change$', () => {
type: 'patch',
patch: expect.objectContaining({type: 'setIfMissing'}),
}),
// Uses atomic set for empty→non-empty transition to handle null field values
expect.objectContaining({
type: 'patch',
patch: expect.objectContaining({type: 'insert'}),
patch: expect.objectContaining({type: 'set'}),
}),
expect.objectContaining({
type: 'patch',
Expand Down
30 changes: 15 additions & 15 deletions packages/editor/tests/event.block.set.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ describe('event.block.set', () => {
])
expect(patches).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_key: 'k0',
Expand All @@ -61,8 +62,7 @@ describe('event.block.set', () => {
style: 'normal',
},
],
'before',
[0],
[],
),
insert(
[
Expand Down Expand Up @@ -149,7 +149,8 @@ describe('event.block.set', () => {
])
expect(patches).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_key: 'k0',
Expand All @@ -159,8 +160,7 @@ describe('event.block.set', () => {
style: 'normal',
},
],
'before',
[0],
[],
),
insert(
[
Expand Down Expand Up @@ -411,7 +411,8 @@ describe('event.block.set', () => {
])
expect(patches).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_key: 'k0',
Expand All @@ -421,8 +422,7 @@ describe('event.block.set', () => {
style: 'normal',
},
],
'before',
[0],
[],
),
insert(
[
Expand Down Expand Up @@ -471,7 +471,8 @@ describe('event.block.set', () => {

expect(patches.slice(7)).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_key: textBlockKey,
Expand All @@ -488,8 +489,7 @@ describe('event.block.set', () => {
style: 'normal',
},
],
'before',
[0],
[],
),
set('h1', [{_key: textBlockKey}, 'style']),
])
Expand Down Expand Up @@ -592,7 +592,8 @@ describe('event.block.set', () => {

expect(patches).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_key: 'k0',
Expand All @@ -602,8 +603,7 @@ describe('event.block.set', () => {
style: 'normal',
},
],
'before',
[0],
[],
),
set('bar', [{_key: 'k0'}, 'foo']),
])
Expand Down
24 changes: 12 additions & 12 deletions packages/editor/tests/event.block.unset.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ describe('event.block.unset', () => {

expect(patches).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_key: 'k0',
Expand All @@ -75,8 +76,7 @@ describe('event.block.unset', () => {
style: 'normal',
},
],
'before',
[0],
[],
),
insert(
[
Expand Down Expand Up @@ -137,7 +137,8 @@ describe('event.block.unset', () => {
])
expect(patches).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_key: 'k0',
Expand All @@ -147,8 +148,7 @@ describe('event.block.unset', () => {
style: 'normal',
},
],
'before',
[0],
[],
),
insert(
[
Expand Down Expand Up @@ -243,7 +243,8 @@ describe('event.block.unset', () => {

expect(patches).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_key: 'k0',
Expand All @@ -253,8 +254,7 @@ describe('event.block.unset', () => {
style: 'normal',
},
],
'before',
[0],
[],
),
insert(
[
Expand Down Expand Up @@ -390,7 +390,8 @@ describe('event.block.unset', () => {
])
expect(patches).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_key: 'k0',
Expand All @@ -400,8 +401,7 @@ describe('event.block.unset', () => {
markDefs: [],
},
],
'before',
[0],
[],
),
insert(
[
Expand Down
7 changes: 3 additions & 4 deletions packages/editor/tests/event.child.unset.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -390,10 +390,9 @@ describe('event.child.unset', () => {
},
{
origin: 'local',
type: 'insert',
path: [0],
position: 'before',
items: [
type: 'set',
path: [],
value: [
{
_key: blockKey,
_type: 'block',
Expand Down
7 changes: 3 additions & 4 deletions packages/editor/tests/event.delete.backward.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
applyAll,
insert,
set,
setIfMissing,
unset,
Expand Down Expand Up @@ -95,7 +94,8 @@ describe('event.delete.backward', () => {
expect(foreignValue).toEqual(expectedValue)
expect(patches.slice(1)).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_type: 'block',
Expand All @@ -105,8 +105,7 @@ describe('event.delete.backward', () => {
style: 'normal',
},
],
'before',
[0],
[],
),
set('bar', [{_key: 'k3'}, 'foo']),
])
Expand Down
7 changes: 3 additions & 4 deletions packages/editor/tests/event.delete.block.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
applyAll,
insert,
set,
setIfMissing,
unset,
Expand Down Expand Up @@ -87,7 +86,8 @@ describe('event.delete.block', () => {
expect(foreignValue).toEqual(expectedValue)
expect(patches.slice(1)).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_type: 'block',
Expand All @@ -97,8 +97,7 @@ describe('event.delete.block', () => {
style: 'normal',
},
],
'before',
[0],
[],
),
set('bar', [{_key: 'k3'}, 'foo']),
])
Expand Down
7 changes: 3 additions & 4 deletions packages/editor/tests/event.delete.forward.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
applyAll,
insert,
set,
setIfMissing,
unset,
Expand Down Expand Up @@ -91,7 +90,8 @@ describe('event.delete.forward', () => {
expect(foreignValue).toEqual(expectedValue)
expect(patches.slice(1)).toEqual([
setIfMissing([], []),
insert(
// Uses atomic set for empty→non-empty transition to handle null field values
set(
[
{
_type: 'block',
Expand All @@ -101,8 +101,7 @@ describe('event.delete.forward', () => {
style: 'normal',
},
],
'before',
[0],
[],
),
set('bar', [{_key: 'k3'}, 'foo']),
])
Expand Down
Loading