@portabletext/editor 1.42.0 → 1.43.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.
Files changed (38) hide show
  1. package/lib/_chunks-cjs/editor-provider.cjs +522 -418
  2. package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
  3. package/lib/_chunks-cjs/selector.is-active-style.cjs +14 -0
  4. package/lib/_chunks-cjs/selector.is-active-style.cjs.map +1 -1
  5. package/lib/_chunks-cjs/selector.is-overlapping-selection.cjs +2 -2
  6. package/lib/_chunks-cjs/selector.is-overlapping-selection.cjs.map +1 -1
  7. package/lib/_chunks-cjs/util.selection-point-to-block-offset.cjs.map +1 -1
  8. package/lib/_chunks-es/editor-provider.js +527 -423
  9. package/lib/_chunks-es/editor-provider.js.map +1 -1
  10. package/lib/_chunks-es/selector.is-active-style.js +15 -1
  11. package/lib/_chunks-es/selector.is-active-style.js.map +1 -1
  12. package/lib/_chunks-es/selector.is-overlapping-selection.js +2 -2
  13. package/lib/_chunks-es/selector.is-overlapping-selection.js.map +1 -1
  14. package/lib/_chunks-es/util.selection-point-to-block-offset.js.map +1 -1
  15. package/lib/behaviors/index.d.cts +11 -11
  16. package/lib/behaviors/index.d.ts +11 -11
  17. package/lib/index.d.cts +1 -1
  18. package/lib/index.d.ts +1 -1
  19. package/lib/plugins/index.d.cts +1 -1
  20. package/lib/plugins/index.d.ts +1 -1
  21. package/lib/selectors/index.cjs +3 -8
  22. package/lib/selectors/index.cjs.map +1 -1
  23. package/lib/selectors/index.d.cts +1 -1
  24. package/lib/selectors/index.d.ts +1 -1
  25. package/lib/selectors/index.js +5 -11
  26. package/lib/selectors/index.js.map +1 -1
  27. package/lib/utils/index.d.cts +1 -1
  28. package/lib/utils/index.d.ts +1 -1
  29. package/package.json +7 -7
  30. package/src/behavior-actions/behavior.actions.ts +6 -0
  31. package/src/behaviors/behavior.default.ts +67 -1
  32. package/src/behaviors/behavior.perform-event.ts +266 -0
  33. package/src/editor/editor-machine.ts +31 -254
  34. package/src/editor/with-applying-behavior-actions.ts +13 -4
  35. package/src/internal-utils/parse-blocks.ts +14 -0
  36. package/src/plugins/plugin.markdown.test.tsx +64 -0
  37. package/src/selectors/selector.get-active-annotations.test.ts +28 -0
  38. package/src/selectors/selector.get-active-annotations.ts +15 -2
@@ -0,0 +1,266 @@
1
+ import type {Behavior, BehaviorEvent} from '.'
2
+ import {performAction} from '../behavior-actions/behavior.actions'
3
+ import type {EditorSchema} from '../editor/define-schema'
4
+ import type {EditorSnapshot} from '../editor/editor-snapshot'
5
+ import {
6
+ withApplyingBehaviorActions,
7
+ withApplyingBehaviorActionSet,
8
+ } from '../editor/with-applying-behavior-actions'
9
+ import {debugWithName} from '../internal-utils/debug'
10
+ import type {PortableTextSlateEditor} from '../types/editor'
11
+ import type {InternalBehaviorAction} from './behavior.types.action'
12
+ import {
13
+ isClipboardBehaviorEvent,
14
+ isCustomBehaviorEvent,
15
+ isDragBehaviorEvent,
16
+ isInputBehaviorEvent,
17
+ isKeyboardBehaviorEvent,
18
+ isMouseBehaviorEvent,
19
+ } from './behavior.types.event'
20
+
21
+ const debug = debugWithName('behaviors:event')
22
+
23
+ export function performEvent({
24
+ behaviors,
25
+ event,
26
+ editor,
27
+ keyGenerator,
28
+ schema,
29
+ getSnapshot,
30
+ nativeEvent,
31
+ defaultActionCallback,
32
+ }: {
33
+ behaviors: Array<Behavior>
34
+ event: BehaviorEvent
35
+ editor: PortableTextSlateEditor
36
+ keyGenerator: () => string
37
+ schema: EditorSchema
38
+ getSnapshot: () => EditorSnapshot
39
+ defaultActionCallback: (() => void) | undefined
40
+ nativeEvent:
41
+ | {
42
+ preventDefault: () => void
43
+ }
44
+ | undefined
45
+ }) {
46
+ debug(JSON.stringify(event, null, 2))
47
+
48
+ const defaultAction =
49
+ isCustomBehaviorEvent(event) ||
50
+ isClipboardBehaviorEvent(event) ||
51
+ isDragBehaviorEvent(event) ||
52
+ isInputBehaviorEvent(event) ||
53
+ isKeyboardBehaviorEvent(event) ||
54
+ isMouseBehaviorEvent(event) ||
55
+ event.type === 'deserialize' ||
56
+ event.type === 'serialize'
57
+ ? undefined
58
+ : ({
59
+ ...event,
60
+ editor,
61
+ } satisfies InternalBehaviorAction)
62
+
63
+ const eventBehaviors = behaviors.filter((behavior) => {
64
+ // Catches all events
65
+ if (behavior.on === '*') {
66
+ return true
67
+ }
68
+
69
+ const [listenedNamespace] =
70
+ behavior.on.includes('*') && behavior.on.includes('.')
71
+ ? behavior.on.split('.')
72
+ : [undefined]
73
+ const [eventNamespace] = event.type.includes('.')
74
+ ? event.type.split('.')
75
+ : [undefined]
76
+
77
+ // Handles scenarios like a Behavior listening for `select.*` and the event
78
+ // `select.block` is fired.
79
+ if (
80
+ listenedNamespace !== undefined &&
81
+ eventNamespace !== undefined &&
82
+ listenedNamespace === eventNamespace
83
+ ) {
84
+ return true
85
+ }
86
+
87
+ // Handles scenarios like a Behavior listening for `select.*` and the event
88
+ // `select` is fired.
89
+ if (
90
+ listenedNamespace !== undefined &&
91
+ eventNamespace === undefined &&
92
+ listenedNamespace === event.type
93
+ ) {
94
+ return true
95
+ }
96
+
97
+ return behavior.on === event.type
98
+ })
99
+
100
+ if (eventBehaviors.length === 0) {
101
+ if (defaultActionCallback) {
102
+ withApplyingBehaviorActions(editor, () => {
103
+ try {
104
+ defaultActionCallback()
105
+ } catch (error) {
106
+ console.error(
107
+ new Error(
108
+ `Performing action "${event.type}" failed due to: ${error.message}`,
109
+ ),
110
+ )
111
+ }
112
+ })
113
+ return
114
+ }
115
+
116
+ if (!defaultAction) {
117
+ return
118
+ }
119
+
120
+ withApplyingBehaviorActions(editor, () => {
121
+ try {
122
+ performAction({
123
+ context: {
124
+ keyGenerator,
125
+ schema,
126
+ },
127
+ action: defaultAction,
128
+ })
129
+ } catch (error) {
130
+ console.error(
131
+ new Error(
132
+ `Performing action "${defaultAction.type}" as a result of "${event.type}" failed due to: ${error.message}`,
133
+ ),
134
+ )
135
+ }
136
+ })
137
+ editor.onChange()
138
+ return
139
+ }
140
+
141
+ const editorSnapshot = getSnapshot()
142
+
143
+ let behaviorOverwritten = false
144
+
145
+ for (const eventBehavior of eventBehaviors) {
146
+ const shouldRun =
147
+ eventBehavior.guard === undefined ||
148
+ eventBehavior.guard({
149
+ context: editorSnapshot.context,
150
+ snapshot: editorSnapshot,
151
+ event,
152
+ })
153
+
154
+ if (!shouldRun) {
155
+ continue
156
+ }
157
+
158
+ const actionSets = eventBehavior.actions.map((actionSet) =>
159
+ actionSet(
160
+ {
161
+ context: editorSnapshot.context,
162
+ snapshot: editorSnapshot,
163
+ event,
164
+ },
165
+ shouldRun,
166
+ ),
167
+ )
168
+
169
+ for (const actionSet of actionSets) {
170
+ if (actionSet.length === 0) {
171
+ continue
172
+ }
173
+
174
+ behaviorOverwritten =
175
+ behaviorOverwritten ||
176
+ actionSet.some((action) => action.type !== 'effect')
177
+
178
+ withApplyingBehaviorActionSet(editor, () => {
179
+ for (const action of actionSet) {
180
+ if (action.type === 'raise') {
181
+ performEvent({
182
+ behaviors,
183
+ event: action.event,
184
+ editor,
185
+ keyGenerator,
186
+ schema,
187
+ getSnapshot,
188
+ defaultActionCallback: undefined,
189
+ nativeEvent: undefined,
190
+ })
191
+
192
+ continue
193
+ }
194
+
195
+ const internalAction = {
196
+ ...action,
197
+ editor,
198
+ }
199
+
200
+ try {
201
+ performAction({
202
+ context: {
203
+ keyGenerator,
204
+ schema,
205
+ },
206
+ action: internalAction,
207
+ })
208
+ } catch (error) {
209
+ console.error(
210
+ new Error(
211
+ `Performing action "${internalAction.type}" as a result of "${event.type}" failed due to: ${error.message}`,
212
+ ),
213
+ )
214
+ break
215
+ }
216
+ }
217
+ })
218
+ editor.onChange()
219
+ }
220
+
221
+ if (behaviorOverwritten) {
222
+ nativeEvent?.preventDefault()
223
+ break
224
+ }
225
+ }
226
+
227
+ if (!behaviorOverwritten) {
228
+ if (defaultActionCallback) {
229
+ withApplyingBehaviorActions(editor, () => {
230
+ try {
231
+ defaultActionCallback()
232
+ } catch (error) {
233
+ console.error(
234
+ new Error(
235
+ `Performing "${event.type}" failed due to: ${error.message}`,
236
+ ),
237
+ )
238
+ }
239
+ })
240
+ return
241
+ }
242
+
243
+ if (!defaultAction) {
244
+ return
245
+ }
246
+
247
+ withApplyingBehaviorActions(editor, () => {
248
+ try {
249
+ performAction({
250
+ context: {
251
+ keyGenerator,
252
+ schema,
253
+ },
254
+ action: defaultAction,
255
+ })
256
+ } catch (error) {
257
+ console.error(
258
+ new Error(
259
+ `Performing action "${defaultAction.type}" as a result of "${event.type}" failed due to: ${error.message}`,
260
+ ),
261
+ )
262
+ }
263
+ })
264
+ editor.onChange()
265
+ }
266
+ }
@@ -9,23 +9,16 @@ import {
9
9
  setup,
10
10
  type ActorRefFrom,
11
11
  } from 'xstate'
12
- import {performAction} from '../behavior-actions/behavior.actions'
13
12
  import {coreBehaviors} from '../behaviors/behavior.core'
14
13
  import {defaultBehaviors} from '../behaviors/behavior.default'
15
- import type {InternalBehaviorAction} from '../behaviors/behavior.types.action'
14
+ import {performEvent} from '../behaviors/behavior.perform-event'
16
15
  import type {Behavior} from '../behaviors/behavior.types.behavior'
17
- import {
18
- isClipboardBehaviorEvent,
19
- isCustomBehaviorEvent,
20
- isDragBehaviorEvent,
21
- isInputBehaviorEvent,
22
- isKeyboardBehaviorEvent,
23
- isMouseBehaviorEvent,
24
- type CustomBehaviorEvent,
25
- type ExternalSyntheticBehaviorEvent,
26
- type InternalBehaviorEvent,
27
- type NativeBehaviorEvent,
28
- type SyntheticBehaviorEvent,
16
+ import type {
17
+ CustomBehaviorEvent,
18
+ ExternalSyntheticBehaviorEvent,
19
+ InternalBehaviorEvent,
20
+ NativeBehaviorEvent,
21
+ SyntheticBehaviorEvent,
29
22
  } from '../behaviors/behavior.types.event'
30
23
  import type {Converter} from '../converters/converter.types'
31
24
  import type {EventPosition} from '../internal-utils/event-position'
@@ -37,10 +30,6 @@ import type {
37
30
  } from '../types/editor'
38
31
  import type {EditorSchema} from './define-schema'
39
32
  import {createEditorSnapshot} from './editor-snapshot'
40
- import {
41
- withApplyingBehaviorActions,
42
- withApplyingBehaviorActionSet,
43
- } from './with-applying-behavior-actions'
44
33
 
45
34
  export * from 'xstate/guards'
46
35
 
@@ -328,244 +317,32 @@ export const editorMachine = setup({
328
317
  'clear pending events': assign({
329
318
  pendingEvents: [],
330
319
  }),
331
- 'handle behavior event': enqueueActions(
332
- ({context, event, enqueue, self}) => {
333
- assertEvent(event, ['behavior event', 'custom behavior event'])
334
-
335
- const defaultAction =
336
- event.type === 'custom behavior event' ||
337
- isClipboardBehaviorEvent(event.behaviorEvent) ||
338
- isDragBehaviorEvent(event.behaviorEvent) ||
339
- isInputBehaviorEvent(event.behaviorEvent) ||
340
- isKeyboardBehaviorEvent(event.behaviorEvent) ||
341
- isMouseBehaviorEvent(event.behaviorEvent) ||
342
- event.behaviorEvent.type === 'deserialize' ||
343
- event.behaviorEvent.type === 'serialize'
344
- ? undefined
345
- : ({
346
- ...event.behaviorEvent,
347
- editor: event.editor,
348
- } satisfies InternalBehaviorAction)
349
- const defaultActionCallback =
320
+ 'handle behavior event': ({context, event, self}) => {
321
+ assertEvent(event, ['behavior event', 'custom behavior event'])
322
+
323
+ performEvent({
324
+ behaviors: [...context.behaviors.values(), ...defaultBehaviors],
325
+ event: event.behaviorEvent,
326
+ editor: event.editor,
327
+ keyGenerator: context.keyGenerator,
328
+ schema: context.schema,
329
+ getSnapshot: () =>
330
+ createEditorSnapshot({
331
+ converters: [...context.converters],
332
+ editor: event.editor,
333
+ keyGenerator: context.keyGenerator,
334
+ readOnly: self.getSnapshot().matches({'edit mode': 'read only'}),
335
+ schema: context.schema,
336
+ hasTag: (tag) => self.getSnapshot().hasTag(tag),
337
+ internalDrag: context.internalDrag,
338
+ }),
339
+ nativeEvent: event.nativeEvent,
340
+ defaultActionCallback:
350
341
  event.type === 'behavior event'
351
342
  ? event.defaultActionCallback
352
- : undefined
353
-
354
- const eventBehaviors = [
355
- ...context.behaviors.values(),
356
- ...defaultBehaviors,
357
- ].filter((behavior) => {
358
- // Catches all events
359
- if (behavior.on === '*') {
360
- return true
361
- }
362
-
363
- const [listenedNamespace] =
364
- behavior.on.includes('*') && behavior.on.includes('.')
365
- ? behavior.on.split('.')
366
- : [undefined]
367
- const [eventNamespace] = event.behaviorEvent.type.includes('.')
368
- ? event.behaviorEvent.type.split('.')
369
- : [undefined]
370
-
371
- // Handles scenarios like a Behavior listening for `select.*` and the event
372
- // `select.block` is fired.
373
- if (
374
- listenedNamespace !== undefined &&
375
- eventNamespace !== undefined &&
376
- listenedNamespace === eventNamespace
377
- ) {
378
- return true
379
- }
380
-
381
- // Handles scenarios like a Behavior listening for `select.*` and the event
382
- // `select` is fired.
383
- if (
384
- listenedNamespace !== undefined &&
385
- eventNamespace === undefined &&
386
- listenedNamespace === event.behaviorEvent.type
387
- ) {
388
- return true
389
- }
390
-
391
- return behavior.on === event.behaviorEvent.type
392
- })
393
-
394
- if (eventBehaviors.length === 0) {
395
- if (defaultActionCallback) {
396
- withApplyingBehaviorActions(event.editor, () => {
397
- try {
398
- defaultActionCallback()
399
- } catch (error) {
400
- console.error(
401
- new Error(
402
- `Performing action "${event.behaviorEvent.type}" failed due to: ${error.message}`,
403
- ),
404
- )
405
- }
406
- })
407
- return
408
- }
409
-
410
- if (!defaultAction) {
411
- return
412
- }
413
-
414
- withApplyingBehaviorActions(event.editor, () => {
415
- try {
416
- performAction({
417
- context: {
418
- keyGenerator: context.keyGenerator,
419
- schema: context.schema,
420
- },
421
- action: defaultAction,
422
- })
423
- } catch (error) {
424
- console.error(
425
- new Error(
426
- `Performing action "${defaultAction.type}" as a result of "${event.behaviorEvent.type}" failed due to: ${error.message}`,
427
- ),
428
- )
429
- }
430
- })
431
- event.editor.onChange()
432
- return
433
- }
434
-
435
- const editorSnapshot = createEditorSnapshot({
436
- converters: [...context.converters],
437
- editor: event.editor,
438
- keyGenerator: context.keyGenerator,
439
- readOnly: self.getSnapshot().matches({'edit mode': 'read only'}),
440
- schema: context.schema,
441
- hasTag: (tag) => self.getSnapshot().hasTag(tag),
442
- internalDrag: context.internalDrag,
443
- })
444
-
445
- let behaviorOverwritten = false
446
-
447
- for (const eventBehavior of eventBehaviors) {
448
- const shouldRun =
449
- eventBehavior.guard === undefined ||
450
- eventBehavior.guard({
451
- context: editorSnapshot.context,
452
- snapshot: editorSnapshot,
453
- event: event.behaviorEvent,
454
- })
455
-
456
- if (!shouldRun) {
457
- continue
458
- }
459
-
460
- const actionSets = eventBehavior.actions.map((actionSet) =>
461
- actionSet(
462
- {
463
- context: editorSnapshot.context,
464
- snapshot: editorSnapshot,
465
- event: event.behaviorEvent,
466
- },
467
- shouldRun,
468
- ),
469
- )
470
-
471
- for (const actionSet of actionSets) {
472
- behaviorOverwritten =
473
- behaviorOverwritten ||
474
- (actionSet.length > 0 &&
475
- actionSet.some((action) => action.type !== 'effect'))
476
-
477
- withApplyingBehaviorActionSet(event.editor, () => {
478
- for (const action of actionSet) {
479
- if (action.type === 'raise') {
480
- if (isCustomBehaviorEvent(action.event)) {
481
- enqueue.raise({
482
- type: 'custom behavior event',
483
- behaviorEvent: action.event as CustomBehaviorEvent,
484
- editor: event.editor,
485
- })
486
- } else {
487
- enqueue.raise({
488
- type: 'behavior event',
489
- behaviorEvent: action.event,
490
- editor: event.editor,
491
- })
492
- }
493
- continue
494
- }
495
-
496
- const internalAction = {
497
- ...action,
498
- editor: event.editor,
499
- }
500
-
501
- try {
502
- performAction({
503
- context: {
504
- keyGenerator: context.keyGenerator,
505
- schema: context.schema,
506
- },
507
- action: internalAction,
508
- })
509
- } catch (error) {
510
- console.error(
511
- new Error(
512
- `Performing action "${internalAction.type}" as a result of "${event.behaviorEvent.type}" failed due to: ${error.message}`,
513
- ),
514
- )
515
- break
516
- }
517
- }
518
- })
519
- event.editor.onChange()
520
- }
521
-
522
- if (behaviorOverwritten) {
523
- event.nativeEvent?.preventDefault()
524
- break
525
- }
526
- }
527
-
528
- if (!behaviorOverwritten) {
529
- if (defaultActionCallback) {
530
- withApplyingBehaviorActions(event.editor, () => {
531
- try {
532
- defaultActionCallback()
533
- } catch (error) {
534
- console.error(
535
- new Error(
536
- `Performing "${event.behaviorEvent.type}" failed due to: ${error.message}`,
537
- ),
538
- )
539
- }
540
- })
541
- return
542
- }
543
-
544
- if (!defaultAction) {
545
- return
546
- }
547
-
548
- withApplyingBehaviorActions(event.editor, () => {
549
- try {
550
- performAction({
551
- context: {
552
- keyGenerator: context.keyGenerator,
553
- schema: context.schema,
554
- },
555
- action: defaultAction,
556
- })
557
- } catch (error) {
558
- console.error(
559
- new Error(
560
- `Performing action "${defaultAction.type}" as a result of "${event.behaviorEvent.type}" failed due to: ${error.message}`,
561
- ),
562
- )
563
- }
564
- })
565
- event.editor.onChange()
566
- }
567
- },
568
- ),
343
+ : undefined,
344
+ })
345
+ },
569
346
  },
570
347
  }).createMachine({
571
348
  id: 'editor',
@@ -26,11 +26,20 @@ const CURRENT_BEHAVIOR_ACTION_SET: WeakMap<
26
26
 
27
27
  export function withApplyingBehaviorActionSet(editor: Editor, fn: () => void) {
28
28
  const current = CURRENT_BEHAVIOR_ACTION_SET.get(editor)
29
- CURRENT_BEHAVIOR_ACTION_SET.set(editor, {
30
- actionSetId: defaultKeyGenerator(),
31
- })
29
+
30
+ if (current) {
31
+ withApplyingBehaviorActions(editor, fn)
32
+ return
33
+ }
34
+
35
+ CURRENT_BEHAVIOR_ACTION_SET.set(
36
+ editor,
37
+ current ?? {
38
+ actionSetId: defaultKeyGenerator(),
39
+ },
40
+ )
32
41
  withApplyingBehaviorActions(editor, fn)
33
- CURRENT_BEHAVIOR_ACTION_SET.set(editor, current)
42
+ CURRENT_BEHAVIOR_ACTION_SET.set(editor, undefined)
34
43
  }
35
44
 
36
45
  export function getCurrentBehaviorActionSetId(editor: Editor) {
@@ -212,6 +212,20 @@ function parseTextBlock({
212
212
  return parsedBlock
213
213
  }
214
214
 
215
+ export function isSpan(
216
+ schema: EditorSchema,
217
+ child: PortableTextSpan | PortableTextObject,
218
+ ): child is PortableTextSpan {
219
+ return (
220
+ parseSpan({
221
+ span: child,
222
+ markDefKeyMap: new Map(),
223
+ context: {schema, keyGenerator: () => ''},
224
+ options: {refreshKeys: false},
225
+ }) !== undefined
226
+ )
227
+ }
228
+
215
229
  export function parseSpan({
216
230
  span,
217
231
  context,
@@ -0,0 +1,64 @@
1
+ import {page, userEvent} from '@vitest/browser/context'
2
+ import React from 'react'
3
+ import {describe, expect, test, vi} from 'vitest'
4
+ import {render} from 'vitest-browser-react'
5
+ import {PortableTextEditable, type Editor} from '..'
6
+ import {defineSchema} from '../editor/define-schema'
7
+ import {EditorProvider} from '../editor/editor-provider'
8
+ import {getTersePt} from '../internal-utils/terse-pt'
9
+ import {createTestKeyGenerator} from '../internal-utils/test-key-generator'
10
+ import {getTextMarks} from '../internal-utils/text-marks'
11
+ import {EditorRefPlugin} from './plugin.editor-ref'
12
+ import {MarkdownPlugin} from './plugin.markdown'
13
+
14
+ describe(MarkdownPlugin.name, () => {
15
+ test('Scenario: Undoing bold shortcut', async () => {
16
+ const editorRef = React.createRef<Editor>()
17
+
18
+ render(
19
+ <EditorProvider
20
+ initialConfig={{
21
+ keyGenerator: createTestKeyGenerator(),
22
+ schemaDefinition: defineSchema({decorators: [{name: 'strong'}]}),
23
+ }}
24
+ >
25
+ <EditorRefPlugin ref={editorRef} />
26
+ <PortableTextEditable />
27
+ <MarkdownPlugin
28
+ config={{
29
+ boldDecorator: () => 'strong',
30
+ }}
31
+ />
32
+ </EditorProvider>,
33
+ )
34
+
35
+ const locator = page.getByRole('textbox')
36
+
37
+ await vi.waitFor(() => expect.element(locator).toBeInTheDocument())
38
+
39
+ await userEvent.type(locator, '**Hello world!**')
40
+
41
+ await vi.waitFor(() => {
42
+ expect(
43
+ getTersePt(editorRef.current?.getSnapshot().context.value),
44
+ ).toEqual(['Hello world!'])
45
+ })
46
+
47
+ await vi.waitFor(() => {
48
+ expect(
49
+ getTextMarks(
50
+ editorRef.current!.getSnapshot().context.value,
51
+ 'Hello world!',
52
+ ),
53
+ ).toEqual(['strong'])
54
+ })
55
+
56
+ editorRef.current?.send({type: 'history.undo'})
57
+
58
+ await vi.waitFor(() => {
59
+ expect(
60
+ getTersePt(editorRef.current?.getSnapshot().context.value),
61
+ ).toEqual(['**Hello world!**'])
62
+ })
63
+ })
64
+ })