@portabletext/editor 2.21.3 → 3.0.1

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.
Files changed (95) hide show
  1. package/lib/_chunks-dts/index.d.ts +49 -209
  2. package/lib/_chunks-es/selector.is-at-the-start-of-block.js +103 -20
  3. package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
  4. package/lib/_chunks-es/{util.get-text-block-text.js → util.slice-blocks.js} +73 -24
  5. package/lib/_chunks-es/util.slice-blocks.js.map +1 -0
  6. package/lib/_chunks-es/util.slice-text-block.js +13 -2
  7. package/lib/_chunks-es/util.slice-text-block.js.map +1 -1
  8. package/lib/behaviors/index.d.ts +1 -1
  9. package/lib/index.d.ts +2 -2
  10. package/lib/index.js +339 -341
  11. package/lib/index.js.map +1 -1
  12. package/lib/plugins/index.d.ts +2 -133
  13. package/lib/plugins/index.js +2 -796
  14. package/lib/plugins/index.js.map +1 -1
  15. package/lib/selectors/index.d.ts +2 -24
  16. package/lib/selectors/index.js +28 -130
  17. package/lib/selectors/index.js.map +1 -1
  18. package/lib/utils/index.d.ts +6 -4
  19. package/lib/utils/index.js +98 -9
  20. package/lib/utils/index.js.map +1 -1
  21. package/package.json +1 -3
  22. package/src/behaviors/behavior.abstract.split.ts +1 -0
  23. package/src/behaviors/behavior.perform-event.ts +7 -7
  24. package/src/converters/converter.portable-text.ts +1 -0
  25. package/src/converters/converter.text-html.ts +1 -0
  26. package/src/converters/converter.text-plain.ts +1 -0
  27. package/src/editor/Editable.tsx +1 -0
  28. package/src/editor/PortableTextEditor.tsx +0 -19
  29. package/src/editor/create-editor.ts +0 -3
  30. package/src/editor/editor-machine.ts +0 -10
  31. package/src/editor/event-to-change.tsx +5 -1
  32. package/src/editor/plugins/create-with-event-listeners.ts +30 -6
  33. package/src/editor/plugins/createWithObjectKeys.ts +2 -1
  34. package/src/editor/plugins/createWithPatches.ts +3 -3
  35. package/src/editor/plugins/createWithPlaceholderBlock.ts +2 -1
  36. package/src/editor/plugins/createWithPortableTextMarkModel.ts +2 -1
  37. package/src/editor/plugins/with-plugins.ts +10 -14
  38. package/src/editor/relay-machine.ts +0 -4
  39. package/src/editor/sync-machine.ts +2 -2
  40. package/src/editor.ts +0 -4
  41. package/src/history/behavior.operation.history.redo.ts +67 -0
  42. package/src/history/behavior.operation.history.undo.ts +71 -0
  43. package/src/history/event.history.undo.test.tsx +672 -0
  44. package/src/history/history.preserving-keys.test.tsx +112 -0
  45. package/src/history/remote-patches.ts +20 -0
  46. package/src/history/slate-plugin.history.ts +146 -0
  47. package/src/history/slate-plugin.redoing.ts +21 -0
  48. package/src/history/slate-plugin.undoing.ts +21 -0
  49. package/src/history/slate-plugin.without-history.ts +23 -0
  50. package/src/history/transform-operation.ts +245 -0
  51. package/src/history/undo-redo-collaboration.test.tsx +541 -0
  52. package/src/history/undo-redo.feature +125 -0
  53. package/src/history/undo-redo.test.tsx +195 -0
  54. package/src/history/undo-step.ts +148 -0
  55. package/src/index.ts +0 -1
  56. package/src/internal-utils/operation-to-patches.test.ts +23 -25
  57. package/src/internal-utils/operation-to-patches.ts +31 -22
  58. package/src/internal-utils/selection-text.test.ts +3 -0
  59. package/src/internal-utils/selection-text.ts +5 -2
  60. package/src/internal-utils/values.ts +23 -11
  61. package/src/operations/behavior.operation.block.set.ts +1 -0
  62. package/src/operations/behavior.operation.block.unset.ts +2 -0
  63. package/src/operations/behavior.operation.insert.block.ts +1 -0
  64. package/src/operations/behavior.operations.ts +2 -4
  65. package/src/plugins/index.ts +0 -3
  66. package/src/selectors/index.ts +0 -3
  67. package/src/test/vitest/step-definitions.tsx +57 -0
  68. package/src/test/vitest/test-editor.tsx +1 -1
  69. package/src/utils/parse-blocks.test.ts +296 -16
  70. package/src/utils/parse-blocks.ts +81 -22
  71. package/src/utils/util.merge-text-blocks.ts +5 -1
  72. package/src/utils/util.slice-blocks.ts +24 -10
  73. package/lib/_chunks-es/selector.get-selection-text.js +0 -92
  74. package/lib/_chunks-es/selector.get-selection-text.js.map +0 -1
  75. package/lib/_chunks-es/selector.get-text-before.js +0 -36
  76. package/lib/_chunks-es/selector.get-text-before.js.map +0 -1
  77. package/lib/_chunks-es/util.get-text-block-text.js.map +0 -1
  78. package/lib/_chunks-es/util.is-empty-text-block.js +0 -40
  79. package/lib/_chunks-es/util.is-empty-text-block.js.map +0 -1
  80. package/lib/_chunks-es/util.merge-text-blocks.js +0 -101
  81. package/lib/_chunks-es/util.merge-text-blocks.js.map +0 -1
  82. package/src/editor/plugins/createWithMaxBlocks.ts +0 -53
  83. package/src/editor/plugins/createWithUndoRedo.ts +0 -628
  84. package/src/editor/with-undo-step.ts +0 -37
  85. package/src/editor/withUndoRedo.ts +0 -34
  86. package/src/editor-event-listener.tsx +0 -28
  87. package/src/plugins/plugin.decorator-shortcut.ts +0 -238
  88. package/src/plugins/plugin.markdown.test.tsx +0 -42
  89. package/src/plugins/plugin.markdown.tsx +0 -131
  90. package/src/plugins/plugin.one-line.tsx +0 -123
  91. package/src/selectors/selector.get-list-state.test.ts +0 -189
  92. package/src/selectors/selector.get-list-state.ts +0 -96
  93. package/src/selectors/selector.get-selected-slice.ts +0 -13
  94. package/src/selectors/selector.get-trimmed-selection.test.ts +0 -657
  95. package/src/selectors/selector.get-trimmed-selection.ts +0 -189
@@ -0,0 +1,672 @@
1
+ import {defineSchema} from '@portabletext/schema'
2
+ import {getTersePt} from '@portabletext/test'
3
+ import {describe, expect, test, vi} from 'vitest'
4
+ import {userEvent} from 'vitest/browser'
5
+ import {execute, forward, raise} from '../behaviors/behavior.types.action'
6
+ import {defineBehavior} from '../behaviors/behavior.types.behavior'
7
+ import {BehaviorPlugin} from '../plugins/plugin.behavior'
8
+ import {getFirstBlock, getFocusBlock} from '../selectors'
9
+ import {createTestEditor} from '../test/vitest'
10
+ import type {EditorSelection} from '../types/editor'
11
+
12
+ describe('event.history.undo', () => {
13
+ test('Scenario: Undoing action sets', async () => {
14
+ const {editor, locator} = await createTestEditor({
15
+ children: (
16
+ <BehaviorPlugin
17
+ behaviors={[
18
+ defineBehavior({
19
+ on: 'insert.text',
20
+ guard: ({event}) => event.text === 'x',
21
+ actions: [
22
+ // The 'x' is inserted in its own undo step
23
+ ({event}) => [execute(event)],
24
+ // And then deleted again and replaced with 'y*' in another undo step
25
+ () => [
26
+ execute({type: 'delete.backward', unit: 'character'}),
27
+ execute({type: 'insert.text', text: 'y'}),
28
+ execute({type: 'insert.text', text: '*'}),
29
+ ],
30
+ // And finally 'z' gets its own undo step as well
31
+ () => [execute({type: 'insert.text', text: 'z'})],
32
+ ],
33
+ }),
34
+ ]}
35
+ />
36
+ ),
37
+ schemaDefinition: defineSchema({decorators: [{name: 'strong'}]}),
38
+ })
39
+
40
+ await userEvent.type(locator, 'x')
41
+
42
+ await vi.waitFor(() => {
43
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['y*z'])
44
+ })
45
+
46
+ editor.send({type: 'history.undo'})
47
+
48
+ await vi.waitFor(() => {
49
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['y*'])
50
+ })
51
+
52
+ editor.send({type: 'history.undo'})
53
+
54
+ await vi.waitFor(() => {
55
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['x'])
56
+ })
57
+
58
+ editor.send({type: 'history.undo'})
59
+
60
+ await vi.waitFor(() => {
61
+ expect(getTersePt(editor.getSnapshot().context)).toEqual([''])
62
+ })
63
+ })
64
+
65
+ test('Scenario: Undoing one-action action sets', async () => {
66
+ const {editor, locator} = await createTestEditor({
67
+ children: (
68
+ <BehaviorPlugin
69
+ behaviors={[
70
+ defineBehavior({
71
+ on: 'insert.text',
72
+ guard: ({event}) => event.text === 'a',
73
+ actions: [
74
+ () => [raise({type: 'insert.text', text: 'b'})],
75
+ () => [raise({type: 'insert.text', text: 'c'})],
76
+ ],
77
+ }),
78
+ ]}
79
+ />
80
+ ),
81
+ })
82
+
83
+ await userEvent.type(locator, 'a')
84
+
85
+ await vi.waitFor(() => {
86
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['bc'])
87
+ })
88
+
89
+ editor.send({type: 'history.undo'})
90
+
91
+ await vi.waitFor(() => {
92
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['b'])
93
+ })
94
+ })
95
+
96
+ test('Scenario: Undoing `insert.text` after `delete`', async () => {
97
+ const {editor, locator} = await createTestEditor({
98
+ children: (
99
+ <BehaviorPlugin
100
+ behaviors={[
101
+ defineBehavior({
102
+ on: 'insert.text',
103
+ guard: ({snapshot, event}) => {
104
+ if (event.text !== 'c') {
105
+ return false
106
+ }
107
+
108
+ const focusBlock = getFocusBlock(snapshot)
109
+
110
+ if (!focusBlock) {
111
+ return false
112
+ }
113
+
114
+ return {focusBlock}
115
+ },
116
+ actions: [
117
+ ({event}) => [forward(event)],
118
+ (_, {focusBlock}) => [
119
+ raise({
120
+ type: 'delete',
121
+ at: {
122
+ anchor: {
123
+ path: focusBlock.path,
124
+ offset: 0,
125
+ },
126
+ focus: {
127
+ path: focusBlock.path,
128
+ offset: 3,
129
+ },
130
+ },
131
+ }),
132
+ ],
133
+ () => [raise({type: 'insert.text', text: 'd'})],
134
+ ],
135
+ }),
136
+ ]}
137
+ />
138
+ ),
139
+ })
140
+
141
+ await userEvent.type(locator, 'a')
142
+ await userEvent.type(locator, 'b')
143
+ await userEvent.type(locator, 'c')
144
+
145
+ await vi.waitFor(() => {
146
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['d'])
147
+ })
148
+
149
+ editor.send({type: 'history.undo'})
150
+
151
+ await vi.waitFor(() => {
152
+ expect(getTersePt(editor.getSnapshot().context)).toEqual([''])
153
+ })
154
+
155
+ editor.send({type: 'history.undo'})
156
+
157
+ await vi.waitFor(() => {
158
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['abc'])
159
+ })
160
+ })
161
+
162
+ test('Scenario: Undoing raised action sets', async () => {
163
+ const {editor, locator} = await createTestEditor({
164
+ children: (
165
+ <BehaviorPlugin
166
+ behaviors={[
167
+ defineBehavior({
168
+ on: 'insert.text',
169
+ guard: ({event}) => event.text === 'x',
170
+ actions: [
171
+ // This gets its own undo step
172
+ () => [execute({type: 'insert.text', text: 'y'})],
173
+ // And this also gets its own undo step
174
+ () => [execute({type: 'insert.text', text: 'z'})],
175
+ ],
176
+ }),
177
+ defineBehavior({
178
+ on: 'insert.text',
179
+ guard: ({event}) => event.text === 'a',
180
+ actions: [
181
+ // Since this Behavior doesn't do any `execute` actions,
182
+ // it will not squash the undo stack
183
+ () => [raise({type: 'insert.text', text: 'x'})],
184
+ ],
185
+ }),
186
+ ]}
187
+ />
188
+ ),
189
+ })
190
+
191
+ await userEvent.type(locator, 'a')
192
+
193
+ await vi.waitFor(() => {
194
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['yz'])
195
+ })
196
+
197
+ editor.send({type: 'history.undo'})
198
+
199
+ await vi.waitFor(() => {
200
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['y'])
201
+ })
202
+ })
203
+
204
+ test('Scenario: Undoing recursive raises', async () => {
205
+ const {editor, locator} = await createTestEditor({
206
+ children: (
207
+ <BehaviorPlugin
208
+ behaviors={[
209
+ defineBehavior({
210
+ on: 'insert.text',
211
+ guard: ({event}) => event.text === 'b',
212
+ actions: [() => [raise({type: 'insert.text', text: 'B'})]],
213
+ }),
214
+ defineBehavior({
215
+ on: 'insert.text',
216
+ guard: ({event}) => event.text === 'a',
217
+ actions: [
218
+ () => [
219
+ raise({type: 'insert.text', text: 'b'}),
220
+ raise({type: 'insert.break'}),
221
+ raise({type: 'insert.text', text: 'c'}),
222
+ ],
223
+ ],
224
+ }),
225
+ ]}
226
+ />
227
+ ),
228
+ })
229
+
230
+ await userEvent.type(locator, 'a')
231
+
232
+ await vi.waitFor(() => {
233
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['B', 'c'])
234
+ })
235
+
236
+ editor.send({type: 'history.undo'})
237
+
238
+ await vi.waitFor(() => {
239
+ expect(getTersePt(editor.getSnapshot().context)).toEqual([''])
240
+ })
241
+ })
242
+
243
+ test('Scenario: A lonely `forward` action does not squash the recursive undo stack', async () => {
244
+ const {editor, locator} = await createTestEditor({
245
+ children: (
246
+ <BehaviorPlugin
247
+ behaviors={[
248
+ defineBehavior({
249
+ on: 'insert.text',
250
+ guard: ({event}) => event.text === 'a',
251
+ actions: [({event}) => [forward(event)]],
252
+ }),
253
+ defineBehavior({
254
+ on: 'insert.text',
255
+ guard: ({event}) => event.text === 'a',
256
+ actions: [
257
+ // 'A' is inserted in its own undo step
258
+ () => [execute({type: 'insert.text', text: 'A'})],
259
+ // 'B' is inserted in its own undo step
260
+ () => [execute({type: 'insert.text', text: 'B'})],
261
+ ],
262
+ }),
263
+ ]}
264
+ />
265
+ ),
266
+ })
267
+
268
+ await userEvent.type(locator, 'a')
269
+
270
+ await vi.waitFor(() => {
271
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['AB'])
272
+ })
273
+
274
+ editor.send({type: 'history.undo'})
275
+
276
+ await vi.waitFor(() => {
277
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['A'])
278
+ })
279
+ })
280
+
281
+ describe('Scenario Outline: Custom events', () => {
282
+ test('Scenario: execute', async () => {
283
+ const {editor} = await createTestEditor({
284
+ children: (
285
+ <BehaviorPlugin
286
+ behaviors={[
287
+ defineBehavior<{text: string}>({
288
+ on: 'custom.insert block',
289
+ actions: [
290
+ ({event}) => [
291
+ execute({
292
+ type: 'insert.block',
293
+ block: {
294
+ _type: 'block',
295
+ children: [
296
+ {
297
+ _type: 'span',
298
+ text: event.text,
299
+ },
300
+ ],
301
+ },
302
+ placement: 'auto',
303
+ select: 'end',
304
+ }),
305
+ ],
306
+ ],
307
+ }),
308
+ ]}
309
+ />
310
+ ),
311
+ })
312
+
313
+ editor.send({type: 'custom.insert block', text: 'foo'})
314
+ editor.send({type: 'custom.insert block', text: 'bar'})
315
+
316
+ await vi.waitFor(() => {
317
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['foobar'])
318
+ })
319
+
320
+ editor.send({type: 'history.undo'})
321
+
322
+ await vi.waitFor(() => {
323
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['foo'])
324
+ })
325
+ })
326
+
327
+ test('Scenario: forward', async () => {
328
+ const {editor} = await createTestEditor({
329
+ children: (
330
+ <BehaviorPlugin
331
+ behaviors={[
332
+ defineBehavior<{text: string}>({
333
+ on: 'custom.insert block',
334
+ actions: [
335
+ ({event}) => [
336
+ forward({
337
+ type: 'insert.block',
338
+ block: {
339
+ _type: 'block',
340
+ children: [
341
+ {
342
+ _type: 'span',
343
+ text: event.text,
344
+ },
345
+ ],
346
+ },
347
+ placement: 'auto',
348
+ select: 'end',
349
+ }),
350
+ ],
351
+ ],
352
+ }),
353
+ ]}
354
+ />
355
+ ),
356
+ })
357
+
358
+ editor.send({type: 'custom.insert block', text: 'foo'})
359
+ editor.send({type: 'custom.insert block', text: 'bar'})
360
+
361
+ await vi.waitFor(() => {
362
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['foobar'])
363
+ })
364
+
365
+ editor.send({type: 'history.undo'})
366
+
367
+ await vi.waitFor(() => {
368
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['foo'])
369
+ })
370
+ })
371
+
372
+ test('Scenario: raise', async () => {
373
+ const {editor} = await createTestEditor({
374
+ children: (
375
+ <BehaviorPlugin
376
+ behaviors={[
377
+ defineBehavior<{text: string}>({
378
+ on: 'custom.insert block',
379
+ actions: [
380
+ ({event}) => [
381
+ raise({
382
+ type: 'insert.block',
383
+ block: {
384
+ _type: 'block',
385
+ children: [
386
+ {
387
+ _type: 'span',
388
+ text: event.text,
389
+ },
390
+ ],
391
+ },
392
+ placement: 'auto',
393
+ select: 'end',
394
+ }),
395
+ ],
396
+ ],
397
+ }),
398
+ ]}
399
+ />
400
+ ),
401
+ })
402
+
403
+ editor.send({type: 'custom.insert block', text: 'foo'})
404
+ editor.send({type: 'custom.insert block', text: 'bar'})
405
+
406
+ await vi.waitFor(() => {
407
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['foobar'])
408
+ })
409
+
410
+ editor.send({type: 'history.undo'})
411
+
412
+ await vi.waitFor(() => {
413
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['foo'])
414
+ })
415
+ })
416
+ })
417
+
418
+ test('Scenario: `forward` in one step, `raise` in another', async () => {
419
+ const {editor, locator} = await createTestEditor({
420
+ children: (
421
+ <BehaviorPlugin
422
+ behaviors={[
423
+ defineBehavior({
424
+ on: 'insert.text',
425
+ guard: ({event}) => event.text === 'a',
426
+ actions: [
427
+ ({event}) => [forward(event)],
428
+ () => [
429
+ raise({type: 'delete.backward', unit: 'character'}),
430
+ raise({type: 'insert.text', text: 'b'}),
431
+ ],
432
+ ],
433
+ }),
434
+ ]}
435
+ />
436
+ ),
437
+ })
438
+
439
+ await userEvent.type(locator, 'a')
440
+
441
+ await vi.waitFor(() => {
442
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['b'])
443
+ })
444
+
445
+ editor.send({type: 'history.undo'})
446
+
447
+ await vi.waitFor(() => {
448
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['a'])
449
+ })
450
+ })
451
+
452
+ test('Scenario: `forward` twice in same step', async () => {
453
+ const {editor, locator} = await createTestEditor({
454
+ children: (
455
+ <BehaviorPlugin
456
+ behaviors={[
457
+ defineBehavior({
458
+ on: 'insert.text',
459
+ guard: ({event}) => event.text === 'a',
460
+ actions: [({event}) => [forward(event), forward(event)]],
461
+ }),
462
+ ]}
463
+ />
464
+ ),
465
+ })
466
+
467
+ await userEvent.type(locator, 'a')
468
+
469
+ await vi.waitFor(() => {
470
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['aa'])
471
+ })
472
+
473
+ editor.send({type: 'history.undo'})
474
+
475
+ await vi.waitFor(() => {
476
+ expect(getTersePt(editor.getSnapshot().context)).toEqual([''])
477
+ })
478
+ })
479
+
480
+ test('Scenario: `forward` twice in separate steps', async () => {
481
+ const {editor, locator} = await createTestEditor({
482
+ children: (
483
+ <BehaviorPlugin
484
+ behaviors={[
485
+ defineBehavior({
486
+ on: 'insert.text',
487
+ guard: ({event}) => event.text === 'a',
488
+ actions: [
489
+ ({event}) => [forward(event)],
490
+ ({event}) => [forward(event)],
491
+ ],
492
+ }),
493
+ ]}
494
+ />
495
+ ),
496
+ })
497
+
498
+ await userEvent.type(locator, 'a')
499
+
500
+ await vi.waitFor(() => {
501
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['aa'])
502
+ })
503
+
504
+ editor.send({type: 'history.undo'})
505
+
506
+ await vi.waitFor(() => {
507
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['a'])
508
+ })
509
+ })
510
+
511
+ test('Scenario: two `forward`s in separate steps does not squash the undo stack', async () => {
512
+ const {editor, locator} = await createTestEditor({
513
+ children: (
514
+ <BehaviorPlugin
515
+ behaviors={[
516
+ defineBehavior({
517
+ on: 'insert.text',
518
+ guard: ({event}) => event.text === 'a',
519
+ actions: [
520
+ ({event}) => [forward(event)],
521
+ ({event}) => [forward(event)],
522
+ ],
523
+ }),
524
+ defineBehavior({
525
+ on: 'insert.text',
526
+ guard: ({event}) => event.text === 'a',
527
+ actions: [
528
+ // 'A' is inserted in its own undo step
529
+ () => [raise({type: 'insert.text', text: 'A'})],
530
+ // 'B' is inserted in its own undo step
531
+ () => [raise({type: 'insert.text', text: 'B'})],
532
+ ],
533
+ }),
534
+ ]}
535
+ />
536
+ ),
537
+ })
538
+
539
+ await userEvent.type(locator, 'a')
540
+
541
+ await vi.waitFor(() => {
542
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['ABAB'])
543
+ })
544
+
545
+ editor.send({type: 'history.undo'})
546
+
547
+ await vi.waitFor(() => {
548
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['ABA'])
549
+ })
550
+
551
+ editor.send({type: 'history.undo'})
552
+
553
+ await vi.waitFor(() => {
554
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['AB'])
555
+ })
556
+
557
+ editor.send({type: 'history.undo'})
558
+
559
+ await vi.waitFor(() => {
560
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['A'])
561
+ })
562
+
563
+ editor.send({type: 'history.undo'})
564
+
565
+ await vi.waitFor(() => {
566
+ expect(getTersePt(editor.getSnapshot().context)).toEqual([''])
567
+ })
568
+ })
569
+
570
+ test('Scenario: Undo after moving cursor after ended Behavior', async () => {
571
+ const {editor, locator} = await createTestEditor({
572
+ children: (
573
+ <BehaviorPlugin
574
+ behaviors={[
575
+ defineBehavior({
576
+ on: 'insert.text',
577
+ guard: ({event}) => event.text === 'a',
578
+ actions: [
579
+ ({event}) => [forward(event)],
580
+ () => [
581
+ raise({type: 'delete.backward', unit: 'character'}),
582
+ raise({type: 'insert.text', text: 'b'}),
583
+ ],
584
+ ],
585
+ }),
586
+ ]}
587
+ />
588
+ ),
589
+ })
590
+
591
+ await userEvent.type(locator, 'a')
592
+
593
+ await vi.waitFor(() => {
594
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['b'])
595
+ })
596
+
597
+ await userEvent.keyboard('{ControlOrMeta>}{z}{/ControlOrMeta}')
598
+
599
+ await vi.waitFor(() => {
600
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['a'])
601
+ })
602
+
603
+ await userEvent.keyboard('{ArrowLeft}')
604
+
605
+ await userEvent.keyboard('{ControlOrMeta>}{z}{/ControlOrMeta}')
606
+
607
+ await vi.waitFor(() => {
608
+ expect(getTersePt(editor.getSnapshot().context)).toEqual([''])
609
+ })
610
+ })
611
+
612
+ test('Scenario: Undo after sending `select` after ended Behavior', async () => {
613
+ const {editor, locator} = await createTestEditor({
614
+ children: (
615
+ <BehaviorPlugin
616
+ behaviors={[
617
+ defineBehavior({
618
+ on: 'insert.text',
619
+ guard: ({event}) => event.text === 'a',
620
+ actions: [
621
+ ({event}) => [forward(event)],
622
+ () => [
623
+ raise({type: 'delete.backward', unit: 'character'}),
624
+ raise({type: 'insert.text', text: 'b'}),
625
+ ],
626
+ ],
627
+ }),
628
+ defineBehavior<{
629
+ at: EditorSelection
630
+ }>({
631
+ on: 'custom.select',
632
+ actions: [({event}) => [raise({type: 'select', at: event.at})]],
633
+ }),
634
+ ]}
635
+ />
636
+ ),
637
+ })
638
+
639
+ await userEvent.type(locator, 'a')
640
+
641
+ await vi.waitFor(() => {
642
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['b'])
643
+ })
644
+
645
+ editor.send({type: 'history.undo'})
646
+
647
+ await vi.waitFor(() => {
648
+ expect(getTersePt(editor.getSnapshot().context)).toEqual(['a'])
649
+ })
650
+
651
+ const firstBlock = getFirstBlock(editor.getSnapshot())
652
+
653
+ if (!firstBlock) {
654
+ throw new Error('First block not found')
655
+ }
656
+
657
+ // Provoke the creation of a new undo step
658
+ editor.send({
659
+ type: 'custom.select',
660
+ at: {
661
+ anchor: {path: firstBlock.path, offset: 0},
662
+ focus: {path: firstBlock.path, offset: 0},
663
+ },
664
+ })
665
+
666
+ editor.send({type: 'history.undo'})
667
+
668
+ await vi.waitFor(() => {
669
+ expect(getTersePt(editor.getSnapshot().context)).toEqual([''])
670
+ })
671
+ })
672
+ })