@portabletext/editor 2.7.2 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs +3 -1
- package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs.map +1 -1
- package/lib/_chunks-cjs/util.slice-blocks.cjs +60 -6
- package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
- package/lib/_chunks-dts/behavior.types.action.d.cts +95 -95
- package/lib/_chunks-dts/behavior.types.action.d.ts +95 -95
- package/lib/_chunks-es/selector.is-selecting-entire-blocks.js +4 -2
- package/lib/_chunks-es/selector.is-selecting-entire-blocks.js.map +1 -1
- package/lib/_chunks-es/util.slice-blocks.js +56 -5
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
- package/lib/index.cjs +94 -121
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +90 -118
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.d.cts +3 -3
- package/lib/plugins/index.d.ts +3 -3
- package/lib/selectors/index.d.cts +13 -3
- package/lib/selectors/index.d.ts +13 -3
- package/lib/utils/index.d.ts +2 -2
- package/package.json +13 -14
- package/src/behaviors/behavior.abstract.insert.ts +58 -1
- package/src/behaviors/behavior.core.annotations.ts +24 -2
- package/src/behaviors/behavior.core.ts +1 -1
- package/src/behaviors/behavior.types.event.ts +18 -18
- package/src/converters/converter.text-html.serialize.test.ts +27 -17
- package/src/converters/converter.text-plain.test.ts +1 -1
- package/src/editor/plugins/createWithEditableAPI.ts +16 -0
- package/src/internal-utils/parse-blocks.ts +2 -1
- package/src/operations/behavior.operation.annotation.add.ts +1 -12
- package/src/operations/behavior.operations.ts +0 -18
- package/src/selectors/selector.is-active-annotation.test.ts +320 -0
- package/src/selectors/selector.is-active-annotation.ts +24 -0
- package/src/utils/util.slice-blocks.test.ts +39 -5
- package/src/utils/util.slice-blocks.ts +36 -3
- package/src/editor/__tests__/PortableTextEditor.test.tsx +0 -430
- package/src/editor/__tests__/PortableTextEditorTester.tsx +0 -58
- package/src/editor/__tests__/RangeDecorations.test.tsx +0 -213
- package/src/editor/__tests__/insert-block.test.tsx +0 -224
- package/src/editor/__tests__/self-solving.test.tsx +0 -183
- package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +0 -298
- package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +0 -177
- package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +0 -538
- package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +0 -162
- package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +0 -65
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +0 -612
- package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +0 -103
- package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +0 -147
- package/src/internal-utils/__tests__/valueNormalization.test.tsx +0 -79
- package/src/operations/behavior.operation.insert-inline-object.ts +0 -59
- package/src/operations/behavior.operation.insert-span.ts +0 -48
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
import {createTestKeyGenerator} from '@portabletext/test'
|
|
2
|
-
import type {PortableTextBlock} from '@sanity/types'
|
|
3
|
-
import {render, waitFor} from '@testing-library/react'
|
|
4
|
-
import {createRef, type RefObject} from 'react'
|
|
5
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
6
|
-
import type {EditorSelection} from '../..'
|
|
7
|
-
import {PortableTextEditor} from '../PortableTextEditor'
|
|
8
|
-
import {PortableTextEditorTester} from './PortableTextEditorTester'
|
|
9
|
-
|
|
10
|
-
const helloBlock: PortableTextBlock = {
|
|
11
|
-
_key: '123',
|
|
12
|
-
_type: 'block',
|
|
13
|
-
markDefs: [],
|
|
14
|
-
children: [{_key: '567', _type: 'span', text: 'Hello', marks: []}],
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const renderPlaceholder = () => 'Jot something down here'
|
|
18
|
-
|
|
19
|
-
describe('initialization', () => {
|
|
20
|
-
it('receives initial onChange events and has custom placeholder', async () => {
|
|
21
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
22
|
-
const onChange = vi.fn()
|
|
23
|
-
const {container} = render(
|
|
24
|
-
<PortableTextEditorTester
|
|
25
|
-
keyGenerator={createTestKeyGenerator()}
|
|
26
|
-
onChange={onChange}
|
|
27
|
-
renderPlaceholder={renderPlaceholder}
|
|
28
|
-
ref={editorRef}
|
|
29
|
-
value={undefined}
|
|
30
|
-
/>,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
await waitFor(() => {
|
|
34
|
-
expect(editorRef.current).not.toBe(null)
|
|
35
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
36
|
-
expect(container).toMatchInlineSnapshot(`
|
|
37
|
-
<div>
|
|
38
|
-
<div
|
|
39
|
-
aria-describedby="desc_foo"
|
|
40
|
-
aria-multiline="true"
|
|
41
|
-
autocapitalize="false"
|
|
42
|
-
autocorrect="false"
|
|
43
|
-
class="pt-editable"
|
|
44
|
-
contenteditable="true"
|
|
45
|
-
data-read-only="false"
|
|
46
|
-
data-slate-editor="true"
|
|
47
|
-
data-slate-node="value"
|
|
48
|
-
role="textbox"
|
|
49
|
-
spellcheck="false"
|
|
50
|
-
style="position: relative; white-space: pre-wrap; word-wrap: break-word;"
|
|
51
|
-
zindex="-1"
|
|
52
|
-
>
|
|
53
|
-
<div
|
|
54
|
-
class="pt-block pt-text-block pt-text-block-style-normal"
|
|
55
|
-
data-block-key="k0"
|
|
56
|
-
data-block-name="block"
|
|
57
|
-
data-block-type="text"
|
|
58
|
-
data-slate-node="element"
|
|
59
|
-
data-style="normal"
|
|
60
|
-
>
|
|
61
|
-
<div>
|
|
62
|
-
<span
|
|
63
|
-
data-child-key="k1"
|
|
64
|
-
data-child-name="span"
|
|
65
|
-
data-child-type="span"
|
|
66
|
-
data-slate-node="text"
|
|
67
|
-
>
|
|
68
|
-
<span
|
|
69
|
-
contenteditable="false"
|
|
70
|
-
style="position: absolute; user-select: none; pointer-events: none; left: 0px; right: 0px;"
|
|
71
|
-
>
|
|
72
|
-
Jot something down here
|
|
73
|
-
</span>
|
|
74
|
-
<span
|
|
75
|
-
data-slate-leaf="true"
|
|
76
|
-
>
|
|
77
|
-
<span
|
|
78
|
-
data-slate-length="0"
|
|
79
|
-
data-slate-zero-width="n"
|
|
80
|
-
>
|
|
81
|
-
|
|
82
|
-
<br />
|
|
83
|
-
</span>
|
|
84
|
-
</span>
|
|
85
|
-
</span>
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
`)
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
it('takes value from props and confirms it by emitting value change event', async () => {
|
|
94
|
-
const initialValue = [helloBlock]
|
|
95
|
-
const onChange = vi.fn()
|
|
96
|
-
const editorRef = createRef<PortableTextEditor>()
|
|
97
|
-
render(
|
|
98
|
-
<PortableTextEditorTester
|
|
99
|
-
keyGenerator={createTestKeyGenerator()}
|
|
100
|
-
ref={editorRef}
|
|
101
|
-
onChange={onChange}
|
|
102
|
-
value={initialValue}
|
|
103
|
-
/>,
|
|
104
|
-
)
|
|
105
|
-
const normalizedEditorValue = [{...initialValue[0], style: 'normal'}]
|
|
106
|
-
await waitFor(() => {
|
|
107
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
108
|
-
type: 'value',
|
|
109
|
-
value: initialValue,
|
|
110
|
-
})
|
|
111
|
-
})
|
|
112
|
-
if (editorRef.current) {
|
|
113
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toStrictEqual(
|
|
114
|
-
normalizedEditorValue,
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('takes initial selection from props', async () => {
|
|
120
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
121
|
-
const initialValue = [helloBlock]
|
|
122
|
-
const initialSelection: EditorSelection = {
|
|
123
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
124
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
125
|
-
backward: false,
|
|
126
|
-
}
|
|
127
|
-
const onChange = vi.fn()
|
|
128
|
-
render(
|
|
129
|
-
<PortableTextEditorTester
|
|
130
|
-
keyGenerator={createTestKeyGenerator()}
|
|
131
|
-
onChange={onChange}
|
|
132
|
-
ref={editorRef}
|
|
133
|
-
selection={initialSelection}
|
|
134
|
-
value={initialValue}
|
|
135
|
-
/>,
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
await waitFor(() => {
|
|
139
|
-
if (editorRef.current) {
|
|
140
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
141
|
-
type: 'value',
|
|
142
|
-
value: initialValue,
|
|
143
|
-
})
|
|
144
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
145
|
-
}
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
await waitFor(() => {
|
|
149
|
-
if (editorRef.current) {
|
|
150
|
-
PortableTextEditor.focus(editorRef.current)
|
|
151
|
-
expect(
|
|
152
|
-
PortableTextEditor.getSelection(editorRef.current),
|
|
153
|
-
).toStrictEqual(initialSelection)
|
|
154
|
-
}
|
|
155
|
-
})
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
it('updates editor selection from new prop and keeps object equality in editor.getSelection()', async () => {
|
|
159
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
160
|
-
const initialValue = [helloBlock]
|
|
161
|
-
const initialSelection: EditorSelection = {
|
|
162
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
163
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
164
|
-
backward: false,
|
|
165
|
-
}
|
|
166
|
-
const newSelection: EditorSelection = {
|
|
167
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
168
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 3},
|
|
169
|
-
backward: false,
|
|
170
|
-
}
|
|
171
|
-
const onChange = vi.fn()
|
|
172
|
-
const {rerender} = render(
|
|
173
|
-
<PortableTextEditorTester
|
|
174
|
-
keyGenerator={createTestKeyGenerator()}
|
|
175
|
-
onChange={onChange}
|
|
176
|
-
ref={editorRef}
|
|
177
|
-
selection={initialSelection}
|
|
178
|
-
value={initialValue}
|
|
179
|
-
/>,
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
await waitFor(() => {
|
|
183
|
-
if (editorRef.current) {
|
|
184
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
185
|
-
type: 'value',
|
|
186
|
-
value: initialValue,
|
|
187
|
-
})
|
|
188
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
189
|
-
}
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
await waitFor(() => {
|
|
193
|
-
if (editorRef.current) {
|
|
194
|
-
const sel = PortableTextEditor.getSelection(editorRef.current)
|
|
195
|
-
PortableTextEditor.focus(editorRef.current)
|
|
196
|
-
|
|
197
|
-
// Test for object equality here!
|
|
198
|
-
const anotherSel = PortableTextEditor.getSelection(editorRef.current)
|
|
199
|
-
expect(
|
|
200
|
-
PortableTextEditor.getSelection(editorRef.current),
|
|
201
|
-
).toStrictEqual(initialSelection)
|
|
202
|
-
expect(sel).toBe(anotherSel)
|
|
203
|
-
}
|
|
204
|
-
})
|
|
205
|
-
rerender(
|
|
206
|
-
<PortableTextEditorTester
|
|
207
|
-
keyGenerator={createTestKeyGenerator()}
|
|
208
|
-
onChange={onChange}
|
|
209
|
-
ref={editorRef}
|
|
210
|
-
selection={newSelection}
|
|
211
|
-
value={initialValue}
|
|
212
|
-
/>,
|
|
213
|
-
)
|
|
214
|
-
waitFor(() => {
|
|
215
|
-
if (editorRef.current) {
|
|
216
|
-
expect(PortableTextEditor.getSelection(editorRef.current)).toEqual(
|
|
217
|
-
newSelection,
|
|
218
|
-
)
|
|
219
|
-
}
|
|
220
|
-
})
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
it('handles empty array value', async () => {
|
|
224
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
225
|
-
const initialValue: PortableTextBlock[] = []
|
|
226
|
-
const initialSelection: EditorSelection = {
|
|
227
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
228
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
229
|
-
}
|
|
230
|
-
const onChange = vi.fn()
|
|
231
|
-
render(
|
|
232
|
-
<PortableTextEditorTester
|
|
233
|
-
keyGenerator={createTestKeyGenerator()}
|
|
234
|
-
onChange={onChange}
|
|
235
|
-
ref={editorRef}
|
|
236
|
-
selection={initialSelection}
|
|
237
|
-
value={initialValue}
|
|
238
|
-
/>,
|
|
239
|
-
)
|
|
240
|
-
await waitFor(() => {
|
|
241
|
-
if (editorRef.current) {
|
|
242
|
-
expect(onChange).not.toHaveBeenCalledWith({
|
|
243
|
-
type: 'invalidValue',
|
|
244
|
-
value: initialValue,
|
|
245
|
-
resolution: {
|
|
246
|
-
action: 'Unset the value',
|
|
247
|
-
description:
|
|
248
|
-
'Editor value must be an array of Portable Text blocks, or undefined.',
|
|
249
|
-
item: initialValue,
|
|
250
|
-
patches: [
|
|
251
|
-
{
|
|
252
|
-
path: [],
|
|
253
|
-
type: 'unset',
|
|
254
|
-
},
|
|
255
|
-
],
|
|
256
|
-
},
|
|
257
|
-
})
|
|
258
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
259
|
-
type: 'value',
|
|
260
|
-
value: initialValue,
|
|
261
|
-
})
|
|
262
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
263
|
-
}
|
|
264
|
-
})
|
|
265
|
-
})
|
|
266
|
-
it('validates a non-initial value', async () => {
|
|
267
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
268
|
-
let value: PortableTextBlock[] = [helloBlock]
|
|
269
|
-
const initialSelection: EditorSelection = {
|
|
270
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
271
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
272
|
-
}
|
|
273
|
-
const onChange = vi.fn()
|
|
274
|
-
let _rerender: any
|
|
275
|
-
await waitFor(() => {
|
|
276
|
-
render(
|
|
277
|
-
<PortableTextEditorTester
|
|
278
|
-
keyGenerator={createTestKeyGenerator()}
|
|
279
|
-
onChange={onChange}
|
|
280
|
-
ref={editorRef}
|
|
281
|
-
selection={initialSelection}
|
|
282
|
-
value={value}
|
|
283
|
-
/>,
|
|
284
|
-
)
|
|
285
|
-
_rerender = render
|
|
286
|
-
})
|
|
287
|
-
await waitFor(() => {
|
|
288
|
-
expect(onChange).not.toHaveBeenCalledWith({
|
|
289
|
-
type: 'invalidValue',
|
|
290
|
-
value,
|
|
291
|
-
resolution: {
|
|
292
|
-
action: 'Unset the value',
|
|
293
|
-
description:
|
|
294
|
-
'Editor value must be an array of Portable Text blocks, or undefined.',
|
|
295
|
-
item: value,
|
|
296
|
-
patches: [
|
|
297
|
-
{
|
|
298
|
-
path: [],
|
|
299
|
-
type: 'unset',
|
|
300
|
-
},
|
|
301
|
-
],
|
|
302
|
-
},
|
|
303
|
-
})
|
|
304
|
-
expect(onChange).toHaveBeenCalledWith({type: 'value', value})
|
|
305
|
-
})
|
|
306
|
-
value = [{_type: 'banana', _key: '123'}]
|
|
307
|
-
const newOnChange = vi.fn()
|
|
308
|
-
_rerender(
|
|
309
|
-
<PortableTextEditorTester
|
|
310
|
-
keyGenerator={createTestKeyGenerator()}
|
|
311
|
-
onChange={newOnChange}
|
|
312
|
-
ref={editorRef}
|
|
313
|
-
selection={initialSelection}
|
|
314
|
-
value={value}
|
|
315
|
-
/>,
|
|
316
|
-
)
|
|
317
|
-
await waitFor(() => {
|
|
318
|
-
expect(newOnChange).toHaveBeenCalledWith({
|
|
319
|
-
type: 'invalidValue',
|
|
320
|
-
value,
|
|
321
|
-
resolution: {
|
|
322
|
-
action: 'Remove the block',
|
|
323
|
-
description: "Block with _key '123' has invalid _type 'banana'",
|
|
324
|
-
item: value[0],
|
|
325
|
-
patches: [
|
|
326
|
-
{
|
|
327
|
-
path: [{_key: '123'}],
|
|
328
|
-
type: 'unset',
|
|
329
|
-
},
|
|
330
|
-
],
|
|
331
|
-
i18n: {
|
|
332
|
-
action: 'inputs.portable-text.invalid-value.disallowed-type.action',
|
|
333
|
-
description:
|
|
334
|
-
'inputs.portable-text.invalid-value.disallowed-type.description',
|
|
335
|
-
values: {
|
|
336
|
-
key: '123',
|
|
337
|
-
typeName: 'banana',
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
})
|
|
342
|
-
})
|
|
343
|
-
})
|
|
344
|
-
it("doesn't crash when containing a invalid block somewhere inside the content", async () => {
|
|
345
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
346
|
-
const initialValue: PortableTextBlock[] = [
|
|
347
|
-
helloBlock,
|
|
348
|
-
{
|
|
349
|
-
_key: 'abc',
|
|
350
|
-
_type: 'block',
|
|
351
|
-
markDefs: [],
|
|
352
|
-
children: [{_key: 'def', _type: 'span', marks: []}],
|
|
353
|
-
},
|
|
354
|
-
]
|
|
355
|
-
const initialSelection: EditorSelection = {
|
|
356
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
357
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
358
|
-
}
|
|
359
|
-
const onChange = vi.fn()
|
|
360
|
-
render(
|
|
361
|
-
<PortableTextEditorTester
|
|
362
|
-
keyGenerator={createTestKeyGenerator()}
|
|
363
|
-
onChange={onChange}
|
|
364
|
-
ref={editorRef}
|
|
365
|
-
selection={initialSelection}
|
|
366
|
-
value={initialValue}
|
|
367
|
-
/>,
|
|
368
|
-
)
|
|
369
|
-
await waitFor(() => {
|
|
370
|
-
if (editorRef.current) {
|
|
371
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
372
|
-
type: 'invalidValue',
|
|
373
|
-
value: initialValue,
|
|
374
|
-
resolution: {
|
|
375
|
-
action: 'Write an empty text property to the object',
|
|
376
|
-
description:
|
|
377
|
-
"Child with _key 'def' in block with key 'abc' has missing or invalid text property!",
|
|
378
|
-
i18n: {
|
|
379
|
-
action:
|
|
380
|
-
'inputs.portable-text.invalid-value.invalid-span-text.action',
|
|
381
|
-
description:
|
|
382
|
-
'inputs.portable-text.invalid-value.invalid-span-text.description',
|
|
383
|
-
values: {
|
|
384
|
-
key: 'abc',
|
|
385
|
-
childKey: 'def',
|
|
386
|
-
},
|
|
387
|
-
},
|
|
388
|
-
item: {
|
|
389
|
-
_key: 'abc',
|
|
390
|
-
_type: 'block',
|
|
391
|
-
children: [
|
|
392
|
-
{
|
|
393
|
-
_key: 'def',
|
|
394
|
-
_type: 'span',
|
|
395
|
-
marks: [],
|
|
396
|
-
},
|
|
397
|
-
],
|
|
398
|
-
markDefs: [],
|
|
399
|
-
},
|
|
400
|
-
patches: [
|
|
401
|
-
{
|
|
402
|
-
path: [
|
|
403
|
-
{
|
|
404
|
-
_key: 'abc',
|
|
405
|
-
},
|
|
406
|
-
'children',
|
|
407
|
-
{
|
|
408
|
-
_key: 'def',
|
|
409
|
-
},
|
|
410
|
-
],
|
|
411
|
-
type: 'set',
|
|
412
|
-
value: {
|
|
413
|
-
_key: 'def',
|
|
414
|
-
_type: 'span',
|
|
415
|
-
marks: [],
|
|
416
|
-
text: '',
|
|
417
|
-
},
|
|
418
|
-
},
|
|
419
|
-
],
|
|
420
|
-
},
|
|
421
|
-
})
|
|
422
|
-
}
|
|
423
|
-
})
|
|
424
|
-
expect(onChange).not.toHaveBeenCalledWith({
|
|
425
|
-
type: 'value',
|
|
426
|
-
value: initialValue,
|
|
427
|
-
})
|
|
428
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
429
|
-
})
|
|
430
|
-
})
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import {forwardRef, useMemo, type ForwardedRef} from 'react'
|
|
2
|
-
import {vi} from 'vitest'
|
|
3
|
-
import {
|
|
4
|
-
defineSchema,
|
|
5
|
-
EditorProvider,
|
|
6
|
-
PortableTextEditable,
|
|
7
|
-
type PortableTextEditableProps,
|
|
8
|
-
type PortableTextEditor,
|
|
9
|
-
type PortableTextEditorProps,
|
|
10
|
-
type SchemaDefinition,
|
|
11
|
-
} from '../../index'
|
|
12
|
-
import {InternalChange$Plugin} from '../../plugins/plugin.internal.change-ref'
|
|
13
|
-
import {InternalPortableTextEditorRefPlugin} from '../../plugins/plugin.internal.portable-text-editor-ref'
|
|
14
|
-
|
|
15
|
-
export const schemaDefinition = defineSchema({
|
|
16
|
-
decorators: [{name: 'strong'}],
|
|
17
|
-
blockObjects: [
|
|
18
|
-
{name: 'custom image', fields: [{name: 'src', type: 'string'}]},
|
|
19
|
-
],
|
|
20
|
-
inlineObjects: [
|
|
21
|
-
{name: 'someObject', fields: [{name: 'color', type: 'string'}]},
|
|
22
|
-
],
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
export const PortableTextEditorTester = forwardRef(
|
|
26
|
-
function PortableTextEditorTester(
|
|
27
|
-
props: {
|
|
28
|
-
onChange?: PortableTextEditorProps['onChange']
|
|
29
|
-
rangeDecorations?: PortableTextEditableProps['rangeDecorations']
|
|
30
|
-
renderPlaceholder?: PortableTextEditableProps['renderPlaceholder']
|
|
31
|
-
schemaDefinition?: SchemaDefinition
|
|
32
|
-
selection?: PortableTextEditableProps['selection']
|
|
33
|
-
value?: PortableTextEditorProps['value']
|
|
34
|
-
keyGenerator: PortableTextEditorProps['keyGenerator']
|
|
35
|
-
},
|
|
36
|
-
ref: ForwardedRef<PortableTextEditor>,
|
|
37
|
-
) {
|
|
38
|
-
const onChange = useMemo(() => props.onChange || vi.fn(), [props.onChange])
|
|
39
|
-
return (
|
|
40
|
-
<EditorProvider
|
|
41
|
-
initialConfig={{
|
|
42
|
-
schemaDefinition: props.schemaDefinition ?? schemaDefinition,
|
|
43
|
-
keyGenerator: props.keyGenerator,
|
|
44
|
-
initialValue: props.value,
|
|
45
|
-
}}
|
|
46
|
-
>
|
|
47
|
-
<InternalChange$Plugin onChange={onChange} />
|
|
48
|
-
<InternalPortableTextEditorRefPlugin ref={ref} />
|
|
49
|
-
<PortableTextEditable
|
|
50
|
-
selection={props.selection || undefined}
|
|
51
|
-
rangeDecorations={props.rangeDecorations}
|
|
52
|
-
renderPlaceholder={props.renderPlaceholder}
|
|
53
|
-
aria-describedby="desc_foo"
|
|
54
|
-
/>
|
|
55
|
-
</EditorProvider>
|
|
56
|
-
)
|
|
57
|
-
},
|
|
58
|
-
)
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import {createTestKeyGenerator} from '@portabletext/test'
|
|
2
|
-
import type {PortableTextBlock} from '@sanity/types'
|
|
3
|
-
import {render, waitFor} from '@testing-library/react'
|
|
4
|
-
import {createRef, type ReactNode, type RefObject} from 'react'
|
|
5
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
6
|
-
import type {RangeDecoration} from '../..'
|
|
7
|
-
import type {PortableTextEditor} from '../PortableTextEditor'
|
|
8
|
-
import {PortableTextEditorTester} from './PortableTextEditorTester'
|
|
9
|
-
|
|
10
|
-
const helloBlock: PortableTextBlock = {
|
|
11
|
-
_key: '123',
|
|
12
|
-
_type: 'block',
|
|
13
|
-
markDefs: [],
|
|
14
|
-
children: [{_key: '567', _type: 'span', text: 'Hello', marks: []}],
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let rangeDecorationIteration = 0
|
|
18
|
-
|
|
19
|
-
const RangeDecorationTestComponent = ({children}: {children?: ReactNode}) => {
|
|
20
|
-
rangeDecorationIteration++
|
|
21
|
-
return <span data-testid="range-decoration">{children}</span>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
describe('RangeDecorations', () => {
|
|
25
|
-
it('only render range decorations as necessary', async () => {
|
|
26
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
27
|
-
const onChange = vi.fn()
|
|
28
|
-
const value = [helloBlock]
|
|
29
|
-
let rangeDecorations: RangeDecoration[] = [
|
|
30
|
-
{
|
|
31
|
-
component: RangeDecorationTestComponent,
|
|
32
|
-
selection: {
|
|
33
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
34
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
35
|
-
},
|
|
36
|
-
payload: {id: 'a'},
|
|
37
|
-
},
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
const {rerender} = await waitFor(() =>
|
|
41
|
-
render(
|
|
42
|
-
<PortableTextEditorTester
|
|
43
|
-
keyGenerator={createTestKeyGenerator()}
|
|
44
|
-
onChange={onChange}
|
|
45
|
-
rangeDecorations={rangeDecorations}
|
|
46
|
-
ref={editorRef}
|
|
47
|
-
value={value}
|
|
48
|
-
/>,
|
|
49
|
-
),
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
await waitFor(() => {
|
|
53
|
-
if (editorRef.current) {
|
|
54
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
55
|
-
type: 'value',
|
|
56
|
-
value,
|
|
57
|
-
})
|
|
58
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
await waitFor(() => {
|
|
63
|
-
expect([rangeDecorationIteration, 'initial']).toEqual([1, 'initial'])
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
// Re-render with the same range decorations
|
|
67
|
-
rerender(
|
|
68
|
-
<PortableTextEditorTester
|
|
69
|
-
keyGenerator={createTestKeyGenerator()}
|
|
70
|
-
onChange={onChange}
|
|
71
|
-
rangeDecorations={rangeDecorations}
|
|
72
|
-
ref={editorRef}
|
|
73
|
-
value={value}
|
|
74
|
-
/>,
|
|
75
|
-
)
|
|
76
|
-
await waitFor(() => {
|
|
77
|
-
expect([rangeDecorationIteration, 'initial']).toEqual([1, 'initial'])
|
|
78
|
-
})
|
|
79
|
-
// Update the range decorations, a new object with identical values
|
|
80
|
-
rangeDecorations = [
|
|
81
|
-
{
|
|
82
|
-
component: RangeDecorationTestComponent,
|
|
83
|
-
selection: {
|
|
84
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
85
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
86
|
-
},
|
|
87
|
-
payload: {id: 'a'},
|
|
88
|
-
},
|
|
89
|
-
]
|
|
90
|
-
rerender(
|
|
91
|
-
<PortableTextEditorTester
|
|
92
|
-
keyGenerator={createTestKeyGenerator()}
|
|
93
|
-
onChange={onChange}
|
|
94
|
-
rangeDecorations={rangeDecorations}
|
|
95
|
-
ref={editorRef}
|
|
96
|
-
value={value}
|
|
97
|
-
/>,
|
|
98
|
-
)
|
|
99
|
-
await waitFor(() => {
|
|
100
|
-
expect([rangeDecorationIteration, 'updated-with-equal-values']).toEqual([
|
|
101
|
-
1,
|
|
102
|
-
'updated-with-equal-values',
|
|
103
|
-
])
|
|
104
|
-
})
|
|
105
|
-
// Update the range decorations with a new offset
|
|
106
|
-
rangeDecorations = [
|
|
107
|
-
{
|
|
108
|
-
component: RangeDecorationTestComponent,
|
|
109
|
-
selection: {
|
|
110
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
111
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 4},
|
|
112
|
-
},
|
|
113
|
-
payload: {id: 'a'},
|
|
114
|
-
},
|
|
115
|
-
]
|
|
116
|
-
rerender(
|
|
117
|
-
<PortableTextEditorTester
|
|
118
|
-
keyGenerator={createTestKeyGenerator()}
|
|
119
|
-
onChange={onChange}
|
|
120
|
-
rangeDecorations={rangeDecorations}
|
|
121
|
-
ref={editorRef}
|
|
122
|
-
value={value}
|
|
123
|
-
/>,
|
|
124
|
-
)
|
|
125
|
-
await waitFor(() => {
|
|
126
|
-
expect([rangeDecorationIteration, 'updated-with-different']).toEqual([
|
|
127
|
-
2,
|
|
128
|
-
'updated-with-different',
|
|
129
|
-
])
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
// Update the range decorations with a new offset again
|
|
133
|
-
rangeDecorations = [
|
|
134
|
-
{
|
|
135
|
-
component: RangeDecorationTestComponent,
|
|
136
|
-
selection: {
|
|
137
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
138
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
139
|
-
},
|
|
140
|
-
payload: {id: 'a'},
|
|
141
|
-
},
|
|
142
|
-
]
|
|
143
|
-
rerender(
|
|
144
|
-
<PortableTextEditorTester
|
|
145
|
-
keyGenerator={createTestKeyGenerator()}
|
|
146
|
-
onChange={onChange}
|
|
147
|
-
rangeDecorations={rangeDecorations}
|
|
148
|
-
ref={editorRef}
|
|
149
|
-
value={value}
|
|
150
|
-
/>,
|
|
151
|
-
)
|
|
152
|
-
await waitFor(() => {
|
|
153
|
-
expect([rangeDecorationIteration, 'updated-with-different']).toEqual([
|
|
154
|
-
3,
|
|
155
|
-
'updated-with-different',
|
|
156
|
-
])
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
// Update the range decorations with a new payload
|
|
160
|
-
rangeDecorations = [
|
|
161
|
-
{
|
|
162
|
-
component: RangeDecorationTestComponent,
|
|
163
|
-
selection: {
|
|
164
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
165
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
166
|
-
},
|
|
167
|
-
payload: {id: 'b'},
|
|
168
|
-
},
|
|
169
|
-
]
|
|
170
|
-
rerender(
|
|
171
|
-
<PortableTextEditorTester
|
|
172
|
-
keyGenerator={createTestKeyGenerator()}
|
|
173
|
-
onChange={onChange}
|
|
174
|
-
rangeDecorations={rangeDecorations}
|
|
175
|
-
ref={editorRef}
|
|
176
|
-
value={value}
|
|
177
|
-
/>,
|
|
178
|
-
)
|
|
179
|
-
await waitFor(() => {
|
|
180
|
-
expect([
|
|
181
|
-
rangeDecorationIteration,
|
|
182
|
-
'updated-with-different-payload',
|
|
183
|
-
]).toEqual([4, 'updated-with-different-payload'])
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
// Update the range decorations with a new payload again
|
|
187
|
-
rangeDecorations = [
|
|
188
|
-
{
|
|
189
|
-
component: RangeDecorationTestComponent,
|
|
190
|
-
selection: {
|
|
191
|
-
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
192
|
-
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
193
|
-
},
|
|
194
|
-
payload: {id: 'c'},
|
|
195
|
-
},
|
|
196
|
-
]
|
|
197
|
-
rerender(
|
|
198
|
-
<PortableTextEditorTester
|
|
199
|
-
keyGenerator={createTestKeyGenerator()}
|
|
200
|
-
onChange={onChange}
|
|
201
|
-
rangeDecorations={rangeDecorations}
|
|
202
|
-
ref={editorRef}
|
|
203
|
-
value={value}
|
|
204
|
-
/>,
|
|
205
|
-
)
|
|
206
|
-
await waitFor(() => {
|
|
207
|
-
expect([
|
|
208
|
-
rangeDecorationIteration,
|
|
209
|
-
'updated-with-different-payload',
|
|
210
|
-
]).toEqual([5, 'updated-with-different-payload'])
|
|
211
|
-
})
|
|
212
|
-
})
|
|
213
|
-
})
|