@portabletext/editor 3.2.4 → 3.2.6

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.
@@ -2,6 +2,7 @@ import {compileSchema, defineSchema} from '@portabletext/schema'
2
2
  import {createTestKeyGenerator} from '@portabletext/test'
3
3
  import {describe, expect, test} from 'vitest'
4
4
  import {applyOperationToPortableText} from './apply-operation-to-portable-text'
5
+ import {VOID_CHILD_KEY} from './values'
5
6
 
6
7
  function createContext() {
7
8
  const keyGenerator = createTestKeyGenerator()
@@ -14,162 +15,1847 @@ function createContext() {
14
15
  }
15
16
 
16
17
  describe(applyOperationToPortableText.name, () => {
17
- test('setting block object properties', () => {
18
- expect(
19
- applyOperationToPortableText(
20
- createContext(),
21
- [
18
+ describe('insert_node', () => {
19
+ test('inserting a text block at the root', () => {
20
+ const keyGenerator = createTestKeyGenerator()
21
+ const k0 = keyGenerator()
22
+ const k1 = keyGenerator()
23
+ const k2 = keyGenerator()
24
+ const k3 = keyGenerator()
25
+
26
+ expect(
27
+ applyOperationToPortableText(
28
+ createContext(),
29
+ [
30
+ {
31
+ _type: 'block',
32
+ _key: k0,
33
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
34
+ },
35
+ ],
22
36
  {
23
- _type: 'image',
24
- _key: 'k0',
37
+ type: 'insert_node',
38
+ path: [1],
39
+ node: {
40
+ _type: 'block',
41
+ _key: k2,
42
+ children: [{_type: 'span', _key: k3, text: 'World'}],
43
+ },
25
44
  },
26
- ],
45
+ ),
46
+ ).toEqual([
27
47
  {
28
- type: 'set_node',
29
- path: [0],
30
- properties: {},
31
- newProperties: {
32
- value: {src: 'https://example.com/image.jpg'},
33
- },
34
- },
35
- ),
36
- ).toEqual([
37
- {
38
- _type: 'image',
39
- _key: 'k0',
40
- src: 'https://example.com/image.jpg',
41
- },
42
- ])
48
+ _type: 'block',
49
+ _key: k0,
50
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
51
+ },
52
+ {
53
+ _type: 'block',
54
+ _key: k2,
55
+ children: [{_type: 'span', _key: k3, text: 'World'}],
56
+ },
57
+ ])
58
+ })
59
+
60
+ test('inserting a text block at the beginning', () => {
61
+ const keyGenerator = createTestKeyGenerator()
62
+ const k0 = keyGenerator()
63
+ const k1 = keyGenerator()
64
+ const k2 = keyGenerator()
65
+ const k3 = keyGenerator()
66
+
67
+ expect(
68
+ applyOperationToPortableText(
69
+ createContext(),
70
+ [
71
+ {
72
+ _type: 'block',
73
+ _key: k0,
74
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
75
+ },
76
+ ],
77
+ {
78
+ type: 'insert_node',
79
+ path: [0],
80
+ node: {
81
+ _type: 'block',
82
+ _key: k2,
83
+ children: [{_type: 'span', _key: k3, text: 'World'}],
84
+ },
85
+ },
86
+ ),
87
+ ).toEqual([
88
+ {
89
+ _type: 'block',
90
+ _key: k2,
91
+ children: [{_type: 'span', _key: k3, text: 'World'}],
92
+ },
93
+ {
94
+ _type: 'block',
95
+ _key: k0,
96
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
97
+ },
98
+ ])
99
+ })
100
+
101
+ test('inserting a block object at the root', () => {
102
+ const keyGenerator = createTestKeyGenerator()
103
+ const k0 = keyGenerator()
104
+ const k1 = keyGenerator()
105
+ const k2 = keyGenerator()
106
+
107
+ expect(
108
+ applyOperationToPortableText(
109
+ createContext(),
110
+ [
111
+ {
112
+ _type: 'block',
113
+ _key: k0,
114
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
115
+ },
116
+ ],
117
+ {
118
+ type: 'insert_node',
119
+ path: [1],
120
+ node: {
121
+ _type: 'image',
122
+ _key: k2,
123
+ children: [{text: '', _key: VOID_CHILD_KEY, _type: 'span'}],
124
+ value: {src: 'https://example.com/image.jpg'},
125
+ },
126
+ },
127
+ ),
128
+ ).toEqual([
129
+ {
130
+ _type: 'block',
131
+ _key: k0,
132
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
133
+ },
134
+ {
135
+ _type: 'image',
136
+ _key: k2,
137
+ src: 'https://example.com/image.jpg',
138
+ },
139
+ ])
140
+ })
141
+
142
+ test('inserting a span into a block', () => {
143
+ const keyGenerator = createTestKeyGenerator()
144
+ const k0 = keyGenerator()
145
+ const k1 = keyGenerator()
146
+ const k2 = keyGenerator()
147
+
148
+ expect(
149
+ applyOperationToPortableText(
150
+ createContext(),
151
+ [
152
+ {
153
+ _type: 'block',
154
+ _key: k0,
155
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
156
+ },
157
+ ],
158
+ {
159
+ type: 'insert_node',
160
+ path: [0, 1],
161
+ node: {_type: 'span', _key: k2, text: ' World'},
162
+ },
163
+ ),
164
+ ).toEqual([
165
+ {
166
+ _type: 'block',
167
+ _key: k0,
168
+ children: [
169
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
170
+ {_type: 'span', _key: k2, text: ' World'},
171
+ ],
172
+ },
173
+ ])
174
+ })
175
+
176
+ test('inserting an inline object into a block', () => {
177
+ const keyGenerator = createTestKeyGenerator()
178
+ const k0 = keyGenerator()
179
+ const k1 = keyGenerator()
180
+ const k2 = keyGenerator()
181
+ expect(
182
+ applyOperationToPortableText(
183
+ createContext(),
184
+ [
185
+ {
186
+ _type: 'block',
187
+ _key: k0,
188
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
189
+ },
190
+ ],
191
+ {
192
+ type: 'insert_node',
193
+ path: [0, 1],
194
+ node: {
195
+ _type: 'stock-ticker',
196
+ _key: k2,
197
+ __inline: true,
198
+ children: [{text: '', _key: VOID_CHILD_KEY, _type: 'span'}],
199
+ value: {symbol: 'AAPL'},
200
+ },
201
+ },
202
+ ),
203
+ ).toEqual([
204
+ {
205
+ _type: 'block',
206
+ _key: k0,
207
+ children: [
208
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
209
+ {_type: 'stock-ticker', _key: k2, symbol: 'AAPL'},
210
+ ],
211
+ },
212
+ ])
213
+ })
43
214
  })
44
215
 
45
- test('updating block object properties', () => {
46
- expect(
47
- applyOperationToPortableText(
48
- createContext(),
49
- [
216
+ describe('insert_text', () => {
217
+ test('inserting text at the beginning of a span', () => {
218
+ const keyGenerator = createTestKeyGenerator()
219
+ const k0 = keyGenerator()
220
+ const k1 = keyGenerator()
221
+
222
+ expect(
223
+ applyOperationToPortableText(
224
+ createContext(),
225
+ [
226
+ {
227
+ _type: 'block',
228
+ _key: k0,
229
+ children: [{_type: 'span', _key: k1, text: 'World', marks: []}],
230
+ },
231
+ ],
50
232
  {
51
- _type: 'image',
52
- _key: 'k0',
53
- src: 'https://example.com/image.jpg',
233
+ type: 'insert_text',
234
+ path: [0, 0],
235
+ offset: 0,
236
+ text: 'Hello ',
54
237
  },
55
- ],
238
+ ),
239
+ ).toEqual([
56
240
  {
57
- type: 'set_node',
58
- path: [0],
59
- properties: {
60
- value: {src: 'https://example.com/image.jpg'},
241
+ _type: 'block',
242
+ _key: k0,
243
+ children: [{_type: 'span', _key: k1, text: 'Hello World', marks: []}],
244
+ },
245
+ ])
246
+ })
247
+
248
+ test('inserting text in the middle of a span', () => {
249
+ const keyGenerator = createTestKeyGenerator()
250
+ const k0 = keyGenerator()
251
+ const k1 = keyGenerator()
252
+
253
+ expect(
254
+ applyOperationToPortableText(
255
+ createContext(),
256
+ [
257
+ {
258
+ _type: 'block',
259
+ _key: k0,
260
+ children: [{_type: 'span', _key: k1, text: 'Helo', marks: []}],
261
+ },
262
+ ],
263
+ {
264
+ type: 'insert_text',
265
+ path: [0, 0],
266
+ offset: 2,
267
+ text: 'l',
61
268
  },
62
- newProperties: {
63
- value: {
269
+ ),
270
+ ).toEqual([
271
+ {
272
+ _type: 'block',
273
+ _key: k0,
274
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
275
+ },
276
+ ])
277
+ })
278
+
279
+ test('inserting text at the end of a span', () => {
280
+ const keyGenerator = createTestKeyGenerator()
281
+ const k0 = keyGenerator()
282
+ const k1 = keyGenerator()
283
+
284
+ expect(
285
+ applyOperationToPortableText(
286
+ createContext(),
287
+ [
288
+ {
289
+ _type: 'block',
290
+ _key: k0,
291
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
292
+ },
293
+ ],
294
+ {
295
+ type: 'insert_text',
296
+ path: [0, 0],
297
+ offset: 5,
298
+ text: ' World',
299
+ },
300
+ ),
301
+ ).toEqual([
302
+ {
303
+ _type: 'block',
304
+ _key: k0,
305
+ children: [{_type: 'span', _key: k1, text: 'Hello World', marks: []}],
306
+ },
307
+ ])
308
+ })
309
+
310
+ test('inserting empty text does nothing', () => {
311
+ const keyGenerator = createTestKeyGenerator()
312
+ const k0 = keyGenerator()
313
+ const k1 = keyGenerator()
314
+ const original = [
315
+ {
316
+ _type: 'block',
317
+ _key: k0,
318
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
319
+ },
320
+ ]
321
+
322
+ expect(
323
+ applyOperationToPortableText(createContext(), original, {
324
+ type: 'insert_text',
325
+ path: [0, 0],
326
+ offset: 0,
327
+ text: '',
328
+ }),
329
+ ).toEqual(original)
330
+ })
331
+ })
332
+
333
+ describe('remove_text', () => {
334
+ test('removing text from the beginning of a span', () => {
335
+ const keyGenerator = createTestKeyGenerator()
336
+ const k0 = keyGenerator()
337
+ const k1 = keyGenerator()
338
+
339
+ expect(
340
+ applyOperationToPortableText(
341
+ createContext(),
342
+ [
343
+ {
344
+ _type: 'block',
345
+ _key: k0,
346
+ children: [
347
+ {_type: 'span', _key: k1, text: 'Hello World', marks: []},
348
+ ],
349
+ },
350
+ ],
351
+ {
352
+ type: 'remove_text',
353
+ path: [0, 0],
354
+ offset: 0,
355
+ text: 'Hello ',
356
+ },
357
+ ),
358
+ ).toEqual([
359
+ {
360
+ _type: 'block',
361
+ _key: k0,
362
+ children: [{_type: 'span', _key: k1, text: 'World', marks: []}],
363
+ },
364
+ ])
365
+ })
366
+
367
+ test('removing text from the middle of a span', () => {
368
+ const keyGenerator = createTestKeyGenerator()
369
+ const k0 = keyGenerator()
370
+ const k1 = keyGenerator()
371
+
372
+ expect(
373
+ applyOperationToPortableText(
374
+ createContext(),
375
+ [
376
+ {
377
+ _type: 'block',
378
+ _key: k0,
379
+ children: [
380
+ {_type: 'span', _key: k1, text: 'Hello World', marks: []},
381
+ ],
382
+ },
383
+ ],
384
+ {
385
+ type: 'remove_text',
386
+ path: [0, 0],
387
+ offset: 5,
388
+ text: ' ',
389
+ },
390
+ ),
391
+ ).toEqual([
392
+ {
393
+ _type: 'block',
394
+ _key: k0,
395
+ children: [{_type: 'span', _key: k1, text: 'HelloWorld', marks: []}],
396
+ },
397
+ ])
398
+ })
399
+
400
+ test('removing text from the end of a span', () => {
401
+ const keyGenerator = createTestKeyGenerator()
402
+ const k0 = keyGenerator()
403
+ const k1 = keyGenerator()
404
+
405
+ expect(
406
+ applyOperationToPortableText(
407
+ createContext(),
408
+ [
409
+ {
410
+ _type: 'block',
411
+ _key: k0,
412
+ children: [
413
+ {_type: 'span', _key: k1, text: 'Hello World', marks: []},
414
+ ],
415
+ },
416
+ ],
417
+ {
418
+ type: 'remove_text',
419
+ path: [0, 0],
420
+ offset: 5,
421
+ text: ' World',
422
+ },
423
+ ),
424
+ ).toEqual([
425
+ {
426
+ _type: 'block',
427
+ _key: k0,
428
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
429
+ },
430
+ ])
431
+ })
432
+
433
+ test('removing empty text does nothing', () => {
434
+ const keyGenerator = createTestKeyGenerator()
435
+ const k0 = keyGenerator()
436
+ const k1 = keyGenerator()
437
+ const original = [
438
+ {
439
+ _type: 'block',
440
+ _key: k0,
441
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
442
+ },
443
+ ]
444
+
445
+ expect(
446
+ applyOperationToPortableText(createContext(), original, {
447
+ type: 'remove_text',
448
+ path: [0, 0],
449
+ offset: 0,
450
+ text: '',
451
+ }),
452
+ ).toEqual(original)
453
+ })
454
+ })
455
+
456
+ describe('remove_node', () => {
457
+ test('removing a block from the root', () => {
458
+ const keyGenerator = createTestKeyGenerator()
459
+ const k0 = keyGenerator()
460
+ const k1 = keyGenerator()
461
+ const k2 = keyGenerator()
462
+ const k3 = keyGenerator()
463
+
464
+ expect(
465
+ applyOperationToPortableText(
466
+ createContext(),
467
+ [
468
+ {
469
+ _type: 'block',
470
+ _key: k0,
471
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
472
+ },
473
+ {
474
+ _type: 'block',
475
+ _key: k2,
476
+ children: [{_type: 'span', _key: k3, text: 'World', marks: []}],
477
+ },
478
+ ],
479
+ {
480
+ type: 'remove_node',
481
+ path: [1],
482
+ node: {
483
+ _type: 'block',
484
+ _key: k2,
485
+ children: [{_type: 'span', _key: k3, text: 'World', marks: []}],
486
+ },
487
+ },
488
+ ),
489
+ ).toEqual([
490
+ {
491
+ _type: 'block',
492
+ _key: k0,
493
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
494
+ },
495
+ ])
496
+ })
497
+
498
+ test('removing a span from a block', () => {
499
+ const keyGenerator = createTestKeyGenerator()
500
+ const k0 = keyGenerator()
501
+ const k1 = keyGenerator()
502
+ const k2 = keyGenerator()
503
+
504
+ expect(
505
+ applyOperationToPortableText(
506
+ createContext(),
507
+ [
508
+ {
509
+ _type: 'block',
510
+ _key: k0,
511
+ children: [
512
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
513
+ {_type: 'span', _key: k2, text: ' World', marks: []},
514
+ ],
515
+ },
516
+ ],
517
+ {
518
+ type: 'remove_node',
519
+ path: [0, 1],
520
+ node: {_type: 'span', _key: k2, text: ' World', marks: []},
521
+ },
522
+ ),
523
+ ).toEqual([
524
+ {
525
+ _type: 'block',
526
+ _key: k0,
527
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
528
+ },
529
+ ])
530
+ })
531
+
532
+ test('removing the first span from a block', () => {
533
+ const keyGenerator = createTestKeyGenerator()
534
+ const k0 = keyGenerator()
535
+ const k1 = keyGenerator()
536
+ const k2 = keyGenerator()
537
+
538
+ expect(
539
+ applyOperationToPortableText(
540
+ createContext(),
541
+ [
542
+ {
543
+ _type: 'block',
544
+ _key: k0,
545
+ children: [
546
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
547
+ {_type: 'span', _key: k2, text: ' World', marks: []},
548
+ ],
549
+ },
550
+ ],
551
+ {
552
+ type: 'remove_node',
553
+ path: [0, 0],
554
+ node: {_type: 'span', _key: k1, text: 'Hello', marks: []},
555
+ },
556
+ ),
557
+ ).toEqual([
558
+ {
559
+ _type: 'block',
560
+ _key: k0,
561
+ children: [{_type: 'span', _key: k2, text: ' World', marks: []}],
562
+ },
563
+ ])
564
+ })
565
+
566
+ test('removing a block object from root', () => {
567
+ const keyGenerator = createTestKeyGenerator()
568
+ const k0 = keyGenerator()
569
+ const k1 = keyGenerator()
570
+ const k2 = keyGenerator()
571
+ const voidChild = keyGenerator()
572
+
573
+ expect(
574
+ applyOperationToPortableText(
575
+ createContext(),
576
+ [
577
+ {
578
+ _type: 'block',
579
+ _key: k0,
580
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
581
+ },
582
+ {
583
+ _type: 'image',
584
+ _key: k2,
64
585
  src: 'https://example.com/image.jpg',
65
- alt: 'An image',
586
+ },
587
+ ],
588
+ {
589
+ type: 'remove_node',
590
+ path: [1],
591
+ node: {
592
+ _type: 'image',
593
+ _key: k2,
594
+ children: [{_type: 'span', _key: voidChild, text: ''}],
595
+ value: {src: 'https://example.com/image.jpg'},
66
596
  },
67
597
  },
598
+ ),
599
+ ).toEqual([
600
+ {
601
+ _type: 'block',
602
+ _key: k0,
603
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
68
604
  },
69
- ),
70
- ).toEqual([
71
- {
72
- _type: 'image',
73
- _key: 'k0',
74
- src: 'https://example.com/image.jpg',
75
- alt: 'An image',
76
- },
77
- ])
605
+ ])
606
+ })
607
+
608
+ test('removing an inline object from a block', () => {
609
+ const keyGenerator = createTestKeyGenerator()
610
+ const k0 = keyGenerator()
611
+ const k1 = keyGenerator()
612
+ const k2 = keyGenerator()
613
+ const k3 = keyGenerator()
614
+ const voidChild = keyGenerator()
615
+
616
+ expect(
617
+ applyOperationToPortableText(
618
+ createContext(),
619
+ [
620
+ {
621
+ _type: 'block',
622
+ _key: k0,
623
+ children: [
624
+ {_type: 'span', _key: k1, text: 'Hello ', marks: []},
625
+ {_type: 'stock-ticker', _key: k2, symbol: 'AAPL'},
626
+ {_type: 'span', _key: k3, text: ' World', marks: []},
627
+ ],
628
+ },
629
+ ],
630
+ {
631
+ type: 'remove_node',
632
+ path: [0, 1],
633
+ node: {
634
+ _type: 'stock-ticker',
635
+ _key: k2,
636
+ __inline: true,
637
+ children: [{_type: 'span', _key: voidChild, text: ''}],
638
+ value: {symbol: 'AAPL'},
639
+ },
640
+ },
641
+ ),
642
+ ).toEqual([
643
+ {
644
+ _type: 'block',
645
+ _key: k0,
646
+ children: [
647
+ {_type: 'span', _key: k1, text: 'Hello ', marks: []},
648
+ {_type: 'span', _key: k3, text: ' World', marks: []},
649
+ ],
650
+ },
651
+ ])
652
+ })
78
653
  })
79
654
 
80
- test('removing block object properties', () => {
81
- expect(
82
- applyOperationToPortableText(
83
- createContext(),
84
- [{_type: 'image', _key: 'k0', alt: 'An image'}],
655
+ describe('merge_node', () => {
656
+ test('merging two spans', () => {
657
+ const keyGenerator = createTestKeyGenerator()
658
+ const k0 = keyGenerator()
659
+ const k1 = keyGenerator()
660
+ const k2 = keyGenerator()
661
+
662
+ expect(
663
+ applyOperationToPortableText(
664
+ createContext(),
665
+ [
666
+ {
667
+ _type: 'block',
668
+ _key: k0,
669
+ children: [
670
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
671
+ {_type: 'span', _key: k2, text: ' World', marks: []},
672
+ ],
673
+ },
674
+ ],
675
+ {
676
+ type: 'merge_node',
677
+ path: [0, 1],
678
+ position: 5,
679
+ properties: {},
680
+ },
681
+ ),
682
+ ).toEqual([
85
683
  {
86
- type: 'set_node',
87
- path: [0],
88
- properties: {
89
- value: {
90
- alt: 'An image',
684
+ _type: 'block',
685
+ _key: k0,
686
+ children: [{_type: 'span', _key: k1, text: 'Hello World', marks: []}],
687
+ },
688
+ ])
689
+ })
690
+
691
+ test('merging two blocks', () => {
692
+ const keyGenerator = createTestKeyGenerator()
693
+ const k0 = keyGenerator()
694
+ const k1 = keyGenerator()
695
+ const k2 = keyGenerator()
696
+ const k3 = keyGenerator()
697
+
698
+ expect(
699
+ applyOperationToPortableText(
700
+ createContext(),
701
+ [
702
+ {
703
+ _type: 'block',
704
+ _key: k0,
705
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
706
+ },
707
+ {
708
+ _type: 'block',
709
+ _key: k2,
710
+ children: [{_type: 'span', _key: k3, text: ' World', marks: []}],
91
711
  },
712
+ ],
713
+ {
714
+ type: 'merge_node',
715
+ path: [1],
716
+ position: 1,
717
+ properties: {},
92
718
  },
93
- newProperties: {value: {}},
719
+ ),
720
+ ).toEqual([
721
+ {
722
+ _type: 'block',
723
+ _key: k0,
724
+ children: [
725
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
726
+ {_type: 'span', _key: k3, text: ' World', marks: []},
727
+ ],
94
728
  },
95
- ),
96
- ).toEqual([{_type: 'image', _key: 'k0'}])
729
+ ])
730
+ })
97
731
  })
98
732
 
99
- test('updating block object _key', () => {
100
- expect(
101
- applyOperationToPortableText(
102
- createContext(),
103
- [
733
+ describe('split_node', () => {
734
+ test('splitting a span', () => {
735
+ const keyGenerator = createTestKeyGenerator()
736
+ const k0 = keyGenerator()
737
+ const k1 = keyGenerator()
738
+ const k2 = keyGenerator()
739
+
740
+ expect(
741
+ applyOperationToPortableText(
742
+ createContext(),
743
+ [
744
+ {
745
+ _type: 'block',
746
+ _key: k0,
747
+ children: [
748
+ {_type: 'span', _key: k1, text: 'Hello World', marks: []},
749
+ ],
750
+ },
751
+ ],
104
752
  {
105
- _type: 'image',
106
- _key: 'k0',
107
- src: 'https://example.com/image.jpg',
753
+ type: 'split_node',
754
+ path: [0, 0],
755
+ position: 5,
756
+ properties: {_type: 'span', _key: k2},
757
+ },
758
+ ),
759
+ ).toEqual([
760
+ {
761
+ _type: 'block',
762
+ _key: k0,
763
+ children: [
764
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
765
+ {_type: 'span', _key: k2, text: ' World'},
766
+ ],
767
+ },
768
+ ])
769
+ })
770
+
771
+ test('splitting a block', () => {
772
+ const keyGenerator = createTestKeyGenerator()
773
+ const k0 = keyGenerator()
774
+ const k1 = keyGenerator()
775
+ const k2 = keyGenerator()
776
+ const k3 = keyGenerator()
777
+
778
+ expect(
779
+ applyOperationToPortableText(
780
+ createContext(),
781
+ [
782
+ {
783
+ _type: 'block',
784
+ _key: k0,
785
+ children: [
786
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
787
+ {_type: 'span', _key: k2, text: ' World', marks: []},
788
+ ],
789
+ },
790
+ ],
791
+ {
792
+ type: 'split_node',
793
+ path: [0],
794
+ position: 1,
795
+ properties: {_key: k3},
796
+ },
797
+ ),
798
+ ).toEqual([
799
+ {
800
+ _type: 'block',
801
+ _key: k0,
802
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
803
+ },
804
+ {
805
+ _type: 'block',
806
+ _key: k3,
807
+ children: [{_type: 'span', _key: k2, text: ' World', marks: []}],
808
+ },
809
+ ])
810
+ })
811
+
812
+ test('splitting a span at the beginning', () => {
813
+ const keyGenerator = createTestKeyGenerator()
814
+ const k0 = keyGenerator()
815
+ const k1 = keyGenerator()
816
+ const k2 = keyGenerator()
817
+
818
+ expect(
819
+ applyOperationToPortableText(
820
+ createContext(),
821
+ [
822
+ {
823
+ _type: 'block',
824
+ _key: k0,
825
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
826
+ },
827
+ ],
828
+ {
829
+ type: 'split_node',
830
+ path: [0, 0],
831
+ position: 0,
832
+ properties: {_type: 'span', _key: k2},
833
+ },
834
+ ),
835
+ ).toEqual([
836
+ {
837
+ _type: 'block',
838
+ _key: k0,
839
+ children: [
840
+ {_type: 'span', _key: k1, text: '', marks: []},
841
+ {_type: 'span', _key: k2, text: 'Hello'},
842
+ ],
843
+ },
844
+ ])
845
+ })
846
+
847
+ describe('splitting text block', () => {
848
+ const keyGenerator = createTestKeyGenerator()
849
+ const k0 = keyGenerator()
850
+ const k1 = keyGenerator()
851
+ const k2 = keyGenerator()
852
+ const k3 = keyGenerator()
853
+ const block = {
854
+ _type: 'block',
855
+ _key: k0,
856
+ children: [
857
+ {_type: 'span', _key: k1, text: 'Hello, ', marks: []},
858
+ {_type: 'span', _key: k2, text: 'World', marks: [k3]},
859
+ ],
860
+ markDefs: [
861
+ {
862
+ _key: k3,
863
+ _type: 'link',
864
+ href: 'https://example.com',
108
865
  },
109
866
  ],
867
+ style: 'h1',
868
+ }
869
+
870
+ test('no properties', () => {
871
+ expect(
872
+ applyOperationToPortableText(createContext(), [block], {
873
+ type: 'split_node',
874
+ path: [0],
875
+ position: 1,
876
+ properties: {},
877
+ }),
878
+ ).toEqual([
879
+ {
880
+ ...block,
881
+ children: [block.children[0]],
882
+ },
883
+ {
884
+ _type: 'block',
885
+ children: [block.children[1]],
886
+ },
887
+ ])
888
+ })
889
+
890
+ test('with properties', () => {
891
+ const k4 = keyGenerator()
892
+
893
+ expect(
894
+ applyOperationToPortableText(createContext(), [block], {
895
+ type: 'split_node',
896
+ path: [0],
897
+ position: 1,
898
+ properties: {
899
+ _type: 'block',
900
+ _key: k4,
901
+ markDefs: block.markDefs,
902
+ style: block.style,
903
+ },
904
+ }),
905
+ ).toEqual([
906
+ {
907
+ ...block,
908
+ children: [block.children[0]],
909
+ },
910
+ {
911
+ _type: 'block',
912
+ _key: k4,
913
+ children: [block.children[1]],
914
+ markDefs: block.markDefs,
915
+ style: block.style,
916
+ },
917
+ ])
918
+ })
919
+ })
920
+
921
+ test('splitting a block at position 0', () => {
922
+ const keyGenerator = createTestKeyGenerator()
923
+ const k0 = keyGenerator()
924
+ const k1 = keyGenerator()
925
+ const k2 = keyGenerator()
926
+ const k3 = keyGenerator()
927
+
928
+ expect(
929
+ applyOperationToPortableText(
930
+ createContext(),
931
+ [
932
+ {
933
+ _type: 'block',
934
+ _key: k0,
935
+ children: [
936
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
937
+ {_type: 'span', _key: k2, text: ' World', marks: []},
938
+ ],
939
+ },
940
+ ],
941
+ {
942
+ type: 'split_node',
943
+ path: [0],
944
+ position: 0,
945
+ properties: {_key: k3},
946
+ },
947
+ ),
948
+ ).toEqual([
110
949
  {
111
- type: 'set_node',
112
- path: [0],
113
- properties: {_key: 'k0'},
114
- newProperties: {_key: 'k1'},
115
- },
116
- ),
117
- ).toEqual([
118
- {
119
- _type: 'image',
120
- _key: 'k1',
121
- src: 'https://example.com/image.jpg',
122
- },
123
- ])
950
+ _type: 'block',
951
+ _key: k0,
952
+ children: [],
953
+ },
954
+ {
955
+ _type: 'block',
956
+ _key: k3,
957
+ children: [
958
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
959
+ {_type: 'span', _key: k2, text: ' World', marks: []},
960
+ ],
961
+ },
962
+ ])
963
+ })
964
+
965
+ test('splitting a block after all children', () => {
966
+ const keyGenerator = createTestKeyGenerator()
967
+ const k0 = keyGenerator()
968
+ const k1 = keyGenerator()
969
+ const k2 = keyGenerator()
970
+ const k3 = keyGenerator()
971
+
972
+ expect(
973
+ applyOperationToPortableText(
974
+ createContext(),
975
+ [
976
+ {
977
+ _type: 'block',
978
+ _key: k0,
979
+ children: [
980
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
981
+ {_type: 'span', _key: k2, text: ' World', marks: []},
982
+ ],
983
+ },
984
+ ],
985
+ {
986
+ type: 'split_node',
987
+ path: [0],
988
+ position: 2,
989
+ properties: {_key: k3},
990
+ },
991
+ ),
992
+ ).toEqual([
993
+ {
994
+ _type: 'block',
995
+ _key: k0,
996
+ children: [
997
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
998
+ {_type: 'span', _key: k2, text: ' World', marks: []},
999
+ ],
1000
+ },
1001
+ {
1002
+ _type: 'block',
1003
+ _key: k3,
1004
+ children: [],
1005
+ },
1006
+ ])
1007
+ })
1008
+
1009
+ test('splitting a span at the end', () => {
1010
+ const keyGenerator = createTestKeyGenerator()
1011
+ const k0 = keyGenerator()
1012
+ const k1 = keyGenerator()
1013
+ const k2 = keyGenerator()
1014
+
1015
+ expect(
1016
+ applyOperationToPortableText(
1017
+ createContext(),
1018
+ [
1019
+ {
1020
+ _type: 'block',
1021
+ _key: k0,
1022
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1023
+ },
1024
+ ],
1025
+ {
1026
+ type: 'split_node',
1027
+ path: [0, 0],
1028
+ position: 5,
1029
+ properties: {_type: 'span', _key: k2},
1030
+ },
1031
+ ),
1032
+ ).toEqual([
1033
+ {
1034
+ _type: 'block',
1035
+ _key: k0,
1036
+ children: [
1037
+ {_type: 'span', _key: k1, text: 'Hello', marks: []},
1038
+ {_type: 'span', _key: k2, text: ''},
1039
+ ],
1040
+ },
1041
+ ])
1042
+ })
124
1043
  })
125
1044
 
126
- test('updating inline object properties', () => {
127
- expect(
128
- applyOperationToPortableText(
129
- createContext(),
130
- [
1045
+ describe('move_node', () => {
1046
+ test('moving a block to the end', () => {
1047
+ const keyGenerator = createTestKeyGenerator()
1048
+ const k0 = keyGenerator()
1049
+ const k1 = keyGenerator()
1050
+ const k2 = keyGenerator()
1051
+ const k3 = keyGenerator()
1052
+ const k4 = keyGenerator()
1053
+ const k5 = keyGenerator()
1054
+
1055
+ expect(
1056
+ applyOperationToPortableText(
1057
+ createContext(),
1058
+ [
1059
+ {
1060
+ _type: 'block',
1061
+ _key: k0,
1062
+ children: [{_type: 'span', _key: k1, text: 'First', marks: []}],
1063
+ },
1064
+ {
1065
+ _type: 'block',
1066
+ _key: k2,
1067
+ children: [{_type: 'span', _key: k3, text: 'Second', marks: []}],
1068
+ },
1069
+ {
1070
+ _type: 'block',
1071
+ _key: k4,
1072
+ children: [{_type: 'span', _key: k5, text: 'Third', marks: []}],
1073
+ },
1074
+ ],
131
1075
  {
132
- _key: 'k0',
133
- _type: 'block',
134
- children: [
135
- {
136
- _key: 'k1',
137
- _type: 'span',
138
- text: '',
1076
+ type: 'move_node',
1077
+ path: [0],
1078
+ newPath: [2],
1079
+ },
1080
+ ),
1081
+ ).toEqual([
1082
+ {
1083
+ _type: 'block',
1084
+ _key: k2,
1085
+ children: [{_type: 'span', _key: k3, text: 'Second', marks: []}],
1086
+ },
1087
+ {
1088
+ _type: 'block',
1089
+ _key: k4,
1090
+ children: [{_type: 'span', _key: k5, text: 'Third', marks: []}],
1091
+ },
1092
+ {
1093
+ _type: 'block',
1094
+ _key: k0,
1095
+ children: [{_type: 'span', _key: k1, text: 'First', marks: []}],
1096
+ },
1097
+ ])
1098
+ })
1099
+
1100
+ test('moving a block to the beginning', () => {
1101
+ const keyGenerator = createTestKeyGenerator()
1102
+ const k0 = keyGenerator()
1103
+ const k1 = keyGenerator()
1104
+ const k2 = keyGenerator()
1105
+ const k3 = keyGenerator()
1106
+
1107
+ expect(
1108
+ applyOperationToPortableText(
1109
+ createContext(),
1110
+ [
1111
+ {
1112
+ _type: 'block',
1113
+ _key: k0,
1114
+ children: [{_type: 'span', _key: k1, text: 'First', marks: []}],
1115
+ },
1116
+ {
1117
+ _type: 'block',
1118
+ _key: k2,
1119
+ children: [{_type: 'span', _key: k3, text: 'Second', marks: []}],
1120
+ },
1121
+ ],
1122
+ {
1123
+ type: 'move_node',
1124
+ path: [1],
1125
+ newPath: [0],
1126
+ },
1127
+ ),
1128
+ ).toEqual([
1129
+ {
1130
+ _type: 'block',
1131
+ _key: k2,
1132
+ children: [{_type: 'span', _key: k3, text: 'Second', marks: []}],
1133
+ },
1134
+ {
1135
+ _type: 'block',
1136
+ _key: k0,
1137
+ children: [{_type: 'span', _key: k1, text: 'First', marks: []}],
1138
+ },
1139
+ ])
1140
+ })
1141
+
1142
+ test('moving a span within a block', () => {
1143
+ const keyGenerator = createTestKeyGenerator()
1144
+ const k0 = keyGenerator()
1145
+ const k1 = keyGenerator()
1146
+ const k2 = keyGenerator()
1147
+ const k3 = keyGenerator()
1148
+
1149
+ expect(
1150
+ applyOperationToPortableText(
1151
+ createContext(),
1152
+ [
1153
+ {
1154
+ _type: 'block',
1155
+ _key: k0,
1156
+ children: [
1157
+ {_type: 'span', _key: k1, text: 'First', marks: []},
1158
+ {_type: 'span', _key: k2, text: 'Second', marks: []},
1159
+ {_type: 'span', _key: k3, text: 'Third', marks: []},
1160
+ ],
1161
+ },
1162
+ ],
1163
+ {
1164
+ type: 'move_node',
1165
+ path: [0, 0],
1166
+ newPath: [0, 2],
1167
+ },
1168
+ ),
1169
+ ).toEqual([
1170
+ {
1171
+ _type: 'block',
1172
+ _key: k0,
1173
+ children: [
1174
+ {_type: 'span', _key: k2, text: 'Second', marks: []},
1175
+ {_type: 'span', _key: k3, text: 'Third', marks: []},
1176
+ {_type: 'span', _key: k1, text: 'First', marks: []},
1177
+ ],
1178
+ },
1179
+ ])
1180
+ })
1181
+
1182
+ test('moving a span from one block to another', () => {
1183
+ const keyGenerator = createTestKeyGenerator()
1184
+ const k0 = keyGenerator()
1185
+ const k1 = keyGenerator()
1186
+ const k2 = keyGenerator()
1187
+ const k3 = keyGenerator()
1188
+ const k4 = keyGenerator()
1189
+
1190
+ expect(
1191
+ applyOperationToPortableText(
1192
+ createContext(),
1193
+ [
1194
+ {
1195
+ _type: 'block',
1196
+ _key: k0,
1197
+ children: [
1198
+ {_type: 'span', _key: k1, text: 'First', marks: []},
1199
+ {_type: 'span', _key: k2, text: 'Second', marks: []},
1200
+ ],
1201
+ },
1202
+ {
1203
+ _type: 'block',
1204
+ _key: k3,
1205
+ children: [{_type: 'span', _key: k4, text: 'Third', marks: []}],
1206
+ },
1207
+ ],
1208
+ {
1209
+ type: 'move_node',
1210
+ path: [0, 1],
1211
+ newPath: [1, 0],
1212
+ },
1213
+ ),
1214
+ ).toEqual([
1215
+ {
1216
+ _type: 'block',
1217
+ _key: k0,
1218
+ children: [{_type: 'span', _key: k1, text: 'First', marks: []}],
1219
+ },
1220
+ {
1221
+ _type: 'block',
1222
+ _key: k3,
1223
+ children: [
1224
+ {_type: 'span', _key: k2, text: 'Second', marks: []},
1225
+ {_type: 'span', _key: k4, text: 'Third', marks: []},
1226
+ ],
1227
+ },
1228
+ ])
1229
+ })
1230
+
1231
+ test('moving a block object', () => {
1232
+ const keyGenerator = createTestKeyGenerator()
1233
+ const k0 = keyGenerator()
1234
+ const k1 = keyGenerator()
1235
+ const k2 = keyGenerator()
1236
+
1237
+ expect(
1238
+ applyOperationToPortableText(
1239
+ createContext(),
1240
+ [
1241
+ {
1242
+ _type: 'block',
1243
+ _key: k0,
1244
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1245
+ },
1246
+ {
1247
+ _type: 'image',
1248
+ _key: k2,
1249
+ src: 'https://example.com/image.jpg',
1250
+ },
1251
+ ],
1252
+ {
1253
+ type: 'move_node',
1254
+ path: [1],
1255
+ newPath: [0],
1256
+ },
1257
+ ),
1258
+ ).toEqual([
1259
+ {
1260
+ _type: 'image',
1261
+ _key: k2,
1262
+ src: 'https://example.com/image.jpg',
1263
+ },
1264
+ {
1265
+ _type: 'block',
1266
+ _key: k0,
1267
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1268
+ },
1269
+ ])
1270
+ })
1271
+ })
1272
+
1273
+ describe('set_node', () => {
1274
+ test('setting block object properties', () => {
1275
+ const keyGenerator = createTestKeyGenerator()
1276
+ const k0 = keyGenerator()
1277
+
1278
+ expect(
1279
+ applyOperationToPortableText(
1280
+ createContext(),
1281
+ [
1282
+ {
1283
+ _type: 'image',
1284
+ _key: k0,
1285
+ },
1286
+ ],
1287
+ {
1288
+ type: 'set_node',
1289
+ path: [0],
1290
+ properties: {},
1291
+ newProperties: {
1292
+ value: {src: 'https://example.com/image.jpg'},
1293
+ },
1294
+ },
1295
+ ),
1296
+ ).toEqual([
1297
+ {
1298
+ _type: 'image',
1299
+ _key: k0,
1300
+ src: 'https://example.com/image.jpg',
1301
+ },
1302
+ ])
1303
+ })
1304
+
1305
+ test('updating block object properties', () => {
1306
+ const keyGenerator = createTestKeyGenerator()
1307
+ const k0 = keyGenerator()
1308
+
1309
+ expect(
1310
+ applyOperationToPortableText(
1311
+ createContext(),
1312
+ [
1313
+ {
1314
+ _type: 'image',
1315
+ _key: k0,
1316
+ src: 'https://example.com/image.jpg',
1317
+ },
1318
+ ],
1319
+ {
1320
+ type: 'set_node',
1321
+ path: [0],
1322
+ properties: {
1323
+ value: {src: 'https://example.com/image.jpg'},
1324
+ },
1325
+ newProperties: {
1326
+ value: {
1327
+ src: 'https://example.com/image.jpg',
1328
+ alt: 'An image',
139
1329
  },
140
- {
141
- _key: 'k2',
142
- _type: 'stock ticker',
1330
+ },
1331
+ },
1332
+ ),
1333
+ ).toEqual([
1334
+ {
1335
+ _type: 'image',
1336
+ _key: k0,
1337
+ src: 'https://example.com/image.jpg',
1338
+ alt: 'An image',
1339
+ },
1340
+ ])
1341
+ })
1342
+
1343
+ test('removing block object properties', () => {
1344
+ const keyGenerator = createTestKeyGenerator()
1345
+ const k0 = keyGenerator()
1346
+
1347
+ expect(
1348
+ applyOperationToPortableText(
1349
+ createContext(),
1350
+ [{_type: 'image', _key: k0, alt: 'An image'}],
1351
+ {
1352
+ type: 'set_node',
1353
+ path: [0],
1354
+ properties: {
1355
+ value: {
1356
+ alt: 'An image',
143
1357
  },
144
- {
145
- _key: 'k3',
146
- _type: 'span',
147
- text: '',
1358
+ },
1359
+ newProperties: {value: {}},
1360
+ },
1361
+ ),
1362
+ ).toEqual([{_type: 'image', _key: k0}])
1363
+ })
1364
+
1365
+ test('updating block object _key', () => {
1366
+ const keyGenerator = createTestKeyGenerator()
1367
+ const k0 = keyGenerator()
1368
+ const k1 = keyGenerator()
1369
+
1370
+ expect(
1371
+ applyOperationToPortableText(
1372
+ createContext(),
1373
+ [
1374
+ {
1375
+ _type: 'image',
1376
+ _key: k0,
1377
+ src: 'https://example.com/image.jpg',
1378
+ },
1379
+ ],
1380
+ {
1381
+ type: 'set_node',
1382
+ path: [0],
1383
+ properties: {_key: k0},
1384
+ newProperties: {_key: k1},
1385
+ },
1386
+ ),
1387
+ ).toEqual([
1388
+ {
1389
+ _type: 'image',
1390
+ _key: k1,
1391
+ src: 'https://example.com/image.jpg',
1392
+ },
1393
+ ])
1394
+ })
1395
+
1396
+ test('updating inline object properties', () => {
1397
+ const keyGenerator = createTestKeyGenerator()
1398
+ const k0 = keyGenerator()
1399
+ const k1 = keyGenerator()
1400
+ const k2 = keyGenerator()
1401
+ const k3 = keyGenerator()
1402
+
1403
+ expect(
1404
+ applyOperationToPortableText(
1405
+ createContext(),
1406
+ [
1407
+ {
1408
+ _key: k0,
1409
+ _type: 'block',
1410
+ children: [
1411
+ {
1412
+ _key: k1,
1413
+ _type: 'span',
1414
+ text: '',
1415
+ },
1416
+ {
1417
+ _key: k2,
1418
+ _type: 'stock ticker',
1419
+ },
1420
+ {
1421
+ _key: k3,
1422
+ _type: 'span',
1423
+ text: '',
1424
+ },
1425
+ ],
1426
+ },
1427
+ ],
1428
+ {
1429
+ type: 'set_node',
1430
+ path: [0, 1],
1431
+ properties: {},
1432
+ newProperties: {
1433
+ value: {
1434
+ symbol: 'AAPL',
148
1435
  },
149
- ],
1436
+ },
150
1437
  },
151
- ],
1438
+ ),
1439
+ ).toEqual([
152
1440
  {
153
- type: 'set_node',
154
- path: [0, 1],
155
- properties: {},
156
- newProperties: {
157
- value: {
158
- symbol: 'AAPL',
1441
+ _type: 'block',
1442
+ _key: k0,
1443
+ children: [
1444
+ {_type: 'span', _key: k1, text: ''},
1445
+ {_type: 'stock ticker', _key: k2, symbol: 'AAPL'},
1446
+ {_type: 'span', _key: k3, text: ''},
1447
+ ],
1448
+ },
1449
+ ])
1450
+ })
1451
+
1452
+ test('setting text block style', () => {
1453
+ const keyGenerator = createTestKeyGenerator()
1454
+ const k0 = keyGenerator()
1455
+ const k1 = keyGenerator()
1456
+
1457
+ expect(
1458
+ applyOperationToPortableText(
1459
+ createContext(),
1460
+ [
1461
+ {
1462
+ _type: 'block',
1463
+ _key: k0,
1464
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
159
1465
  },
1466
+ ],
1467
+ {
1468
+ type: 'set_node',
1469
+ path: [0],
1470
+ properties: {},
1471
+ newProperties: {style: 'h1'},
160
1472
  },
1473
+ ),
1474
+ ).toEqual([
1475
+ {
1476
+ _type: 'block',
1477
+ _key: k0,
1478
+ style: 'h1',
1479
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
161
1480
  },
162
- ),
163
- ).toEqual([
164
- {
1481
+ ])
1482
+ })
1483
+
1484
+ test('setting span marks', () => {
1485
+ const keyGenerator = createTestKeyGenerator()
1486
+ const k0 = keyGenerator()
1487
+ const k1 = keyGenerator()
1488
+
1489
+ expect(
1490
+ applyOperationToPortableText(
1491
+ createContext(),
1492
+ [
1493
+ {
1494
+ _type: 'block',
1495
+ _key: k0,
1496
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1497
+ },
1498
+ ],
1499
+ {
1500
+ type: 'set_node',
1501
+ path: [0, 0],
1502
+ properties: {marks: []},
1503
+ newProperties: {marks: ['strong']},
1504
+ },
1505
+ ),
1506
+ ).toEqual([
1507
+ {
1508
+ _type: 'block',
1509
+ _key: k0,
1510
+ children: [
1511
+ {_type: 'span', _key: k1, text: 'Hello', marks: ['strong']},
1512
+ ],
1513
+ },
1514
+ ])
1515
+ })
1516
+
1517
+ test('updating span _key', () => {
1518
+ const keyGenerator = createTestKeyGenerator()
1519
+ const k0 = keyGenerator()
1520
+ const k1 = keyGenerator()
1521
+ const k2 = keyGenerator()
1522
+
1523
+ expect(
1524
+ applyOperationToPortableText(
1525
+ createContext(),
1526
+ [
1527
+ {
1528
+ _type: 'block',
1529
+ _key: k0,
1530
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1531
+ },
1532
+ ],
1533
+ {
1534
+ type: 'set_node',
1535
+ path: [0, 0],
1536
+ properties: {_key: k1},
1537
+ newProperties: {_key: k2},
1538
+ },
1539
+ ),
1540
+ ).toEqual([
1541
+ {
1542
+ _type: 'block',
1543
+ _key: k0,
1544
+ children: [{_type: 'span', _key: k2, text: 'Hello', marks: []}],
1545
+ },
1546
+ ])
1547
+ })
1548
+
1549
+ test('removing text block style', () => {
1550
+ const keyGenerator = createTestKeyGenerator()
1551
+ const k0 = keyGenerator()
1552
+ const k1 = keyGenerator()
1553
+
1554
+ expect(
1555
+ applyOperationToPortableText(
1556
+ createContext(),
1557
+ [
1558
+ {
1559
+ _type: 'block',
1560
+ _key: k0,
1561
+ style: 'h1',
1562
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1563
+ },
1564
+ ],
1565
+ {
1566
+ type: 'set_node',
1567
+ path: [0],
1568
+ properties: {style: 'h1'},
1569
+ newProperties: {},
1570
+ },
1571
+ ),
1572
+ ).toEqual([
1573
+ {
1574
+ _type: 'block',
1575
+ _key: k0,
1576
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1577
+ },
1578
+ ])
1579
+ })
1580
+
1581
+ test('setting text block markDefs', () => {
1582
+ const keyGenerator = createTestKeyGenerator()
1583
+ const k0 = keyGenerator()
1584
+ const k1 = keyGenerator()
1585
+ const linkKey = keyGenerator()
1586
+
1587
+ expect(
1588
+ applyOperationToPortableText(
1589
+ createContext(),
1590
+ [
1591
+ {
1592
+ _type: 'block',
1593
+ _key: k0,
1594
+ children: [
1595
+ {_type: 'span', _key: k1, text: 'Hello', marks: [linkKey]},
1596
+ ],
1597
+ },
1598
+ ],
1599
+ {
1600
+ type: 'set_node',
1601
+ path: [0],
1602
+ properties: {},
1603
+ newProperties: {
1604
+ markDefs: [
1605
+ {_key: linkKey, _type: 'link', href: 'https://example.com'},
1606
+ ],
1607
+ },
1608
+ },
1609
+ ),
1610
+ ).toEqual([
1611
+ {
1612
+ _type: 'block',
1613
+ _key: k0,
1614
+ markDefs: [
1615
+ {_key: linkKey, _type: 'link', href: 'https://example.com'},
1616
+ ],
1617
+ children: [
1618
+ {_type: 'span', _key: k1, text: 'Hello', marks: [linkKey]},
1619
+ ],
1620
+ },
1621
+ ])
1622
+ })
1623
+ })
1624
+
1625
+ describe('immutability', () => {
1626
+ test('original value is not mutated', () => {
1627
+ const keyGenerator = createTestKeyGenerator()
1628
+ const k0 = keyGenerator()
1629
+ const k1 = keyGenerator()
1630
+ const original = [
1631
+ {
1632
+ _type: 'block',
1633
+ _key: k0,
1634
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1635
+ },
1636
+ ]
1637
+ const originalJson = JSON.stringify(original)
1638
+
1639
+ applyOperationToPortableText(createContext(), original, {
1640
+ type: 'insert_text',
1641
+ path: [0, 0],
1642
+ offset: 5,
1643
+ text: ' World',
1644
+ })
1645
+
1646
+ expect(JSON.stringify(original)).toBe(originalJson)
1647
+ })
1648
+
1649
+ test('nested objects are not mutated', () => {
1650
+ const keyGenerator = createTestKeyGenerator()
1651
+ const k0 = keyGenerator()
1652
+ const k1 = keyGenerator()
1653
+ const span = {
1654
+ _type: 'span',
1655
+ _key: k1,
1656
+ text: 'Hello',
1657
+ marks: [] as string[],
1658
+ }
1659
+ const block = {
165
1660
  _type: 'block',
166
- _key: 'k0',
167
- children: [
168
- {_type: 'span', _key: 'k1', text: ''},
169
- {_type: 'stock ticker', _key: 'k2', symbol: 'AAPL'},
170
- {_type: 'span', _key: 'k3', text: ''},
171
- ],
172
- },
173
- ])
1661
+ _key: k0,
1662
+ children: [span],
1663
+ }
1664
+ const original = [block]
1665
+ const originalSpanText = span.text
1666
+
1667
+ applyOperationToPortableText(createContext(), original, {
1668
+ type: 'insert_text',
1669
+ path: [0, 0],
1670
+ offset: 5,
1671
+ text: ' World',
1672
+ })
1673
+
1674
+ expect(span.text).toBe(originalSpanText)
1675
+ })
1676
+ })
1677
+
1678
+ describe('edge cases', () => {
1679
+ test('returns original value on error', () => {
1680
+ const keyGenerator = createTestKeyGenerator()
1681
+ const k0 = keyGenerator()
1682
+ const k1 = keyGenerator()
1683
+ const k2 = keyGenerator()
1684
+ const k3 = keyGenerator()
1685
+ const original = [
1686
+ {
1687
+ _type: 'block',
1688
+ _key: k0,
1689
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1690
+ },
1691
+ ]
1692
+
1693
+ expect(
1694
+ applyOperationToPortableText(createContext(), original, {
1695
+ type: 'insert_node',
1696
+ path: [100],
1697
+ node: {
1698
+ _type: 'block',
1699
+ _key: k2,
1700
+ children: [{_type: 'span', _key: k3, text: 'World', marks: []}],
1701
+ },
1702
+ }),
1703
+ ).toEqual(original)
1704
+ })
1705
+
1706
+ test('handles empty value array', () => {
1707
+ const keyGenerator = createTestKeyGenerator()
1708
+ const k0 = keyGenerator()
1709
+ const k1 = keyGenerator()
1710
+
1711
+ expect(
1712
+ applyOperationToPortableText(createContext(), [], {
1713
+ type: 'insert_node',
1714
+ path: [0],
1715
+ node: {
1716
+ _type: 'block',
1717
+ _key: k0,
1718
+ children: [{_type: 'span', _key: k1, text: 'Hello'}],
1719
+ },
1720
+ }),
1721
+ ).toEqual([
1722
+ {
1723
+ _type: 'block',
1724
+ _key: k0,
1725
+ children: [{_type: 'span', _key: k1, text: 'Hello'}],
1726
+ },
1727
+ ])
1728
+ })
1729
+
1730
+ test('remove_node on non-existent path returns original', () => {
1731
+ const keyGenerator = createTestKeyGenerator()
1732
+ const k0 = keyGenerator()
1733
+ const k1 = keyGenerator()
1734
+ const original = [
1735
+ {
1736
+ _type: 'block',
1737
+ _key: k0,
1738
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1739
+ },
1740
+ ]
1741
+
1742
+ expect(
1743
+ applyOperationToPortableText(createContext(), original, {
1744
+ type: 'remove_node',
1745
+ path: [5],
1746
+ node: {_type: 'block', _key: 'nonexistent', children: []},
1747
+ }),
1748
+ ).toEqual(original)
1749
+ })
1750
+
1751
+ test('merge_node with no previous block returns original', () => {
1752
+ const keyGenerator = createTestKeyGenerator()
1753
+ const k0 = keyGenerator()
1754
+ const k1 = keyGenerator()
1755
+ const original = [
1756
+ {
1757
+ _type: 'block',
1758
+ _key: k0,
1759
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1760
+ },
1761
+ ]
1762
+
1763
+ // Trying to merge the first block (no previous to merge with)
1764
+ expect(
1765
+ applyOperationToPortableText(createContext(), original, {
1766
+ type: 'merge_node',
1767
+ path: [0],
1768
+ position: 0,
1769
+ properties: {},
1770
+ }),
1771
+ ).toEqual(original)
1772
+ })
1773
+
1774
+ test('split_node on block object returns original', () => {
1775
+ const keyGenerator = createTestKeyGenerator()
1776
+ const k0 = keyGenerator()
1777
+ const original = [
1778
+ {
1779
+ _type: 'image',
1780
+ _key: k0,
1781
+ src: 'https://example.com/image.jpg',
1782
+ },
1783
+ ]
1784
+
1785
+ expect(
1786
+ applyOperationToPortableText(createContext(), original, {
1787
+ type: 'split_node',
1788
+ path: [0],
1789
+ position: 0,
1790
+ properties: {},
1791
+ }),
1792
+ ).toEqual(original)
1793
+ })
1794
+
1795
+ test('insert_text on non-existent span returns original', () => {
1796
+ const keyGenerator = createTestKeyGenerator()
1797
+ const k0 = keyGenerator()
1798
+ const k1 = keyGenerator()
1799
+ const original = [
1800
+ {
1801
+ _type: 'block',
1802
+ _key: k0,
1803
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1804
+ },
1805
+ ]
1806
+
1807
+ expect(
1808
+ applyOperationToPortableText(createContext(), original, {
1809
+ type: 'insert_text',
1810
+ path: [0, 5],
1811
+ offset: 0,
1812
+ text: 'World',
1813
+ }),
1814
+ ).toEqual(original)
1815
+ })
1816
+
1817
+ test('set_node on root path returns original', () => {
1818
+ const keyGenerator = createTestKeyGenerator()
1819
+ const k0 = keyGenerator()
1820
+ const k1 = keyGenerator()
1821
+ const original = [
1822
+ {
1823
+ _type: 'block',
1824
+ _key: k0,
1825
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1826
+ },
1827
+ ]
1828
+
1829
+ expect(
1830
+ applyOperationToPortableText(createContext(), original, {
1831
+ type: 'set_node',
1832
+ path: [],
1833
+ properties: {},
1834
+ newProperties: {style: 'h1'},
1835
+ }),
1836
+ ).toEqual(original)
1837
+ })
1838
+
1839
+ test('move_node to ancestor path returns original', () => {
1840
+ const keyGenerator = createTestKeyGenerator()
1841
+ const k0 = keyGenerator()
1842
+ const k1 = keyGenerator()
1843
+ const original = [
1844
+ {
1845
+ _type: 'block',
1846
+ _key: k0,
1847
+ children: [{_type: 'span', _key: k1, text: 'Hello', marks: []}],
1848
+ },
1849
+ ]
1850
+
1851
+ // This would be an invalid move (moving a node inside itself)
1852
+ expect(
1853
+ applyOperationToPortableText(createContext(), original, {
1854
+ type: 'move_node',
1855
+ path: [0],
1856
+ newPath: [0, 0],
1857
+ }),
1858
+ ).toEqual(original)
1859
+ })
174
1860
  })
175
1861
  })