@@ -23,7 +23,14 @@ import RoomContext from "../../../../../src/contexts/RoomContext";
23
23
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher" ;
24
24
import { Action } from "../../../../../src/dispatcher/actions" ;
25
25
import { IRoomState } from "../../../../../src/components/structures/RoomView" ;
26
- import { createTestClient , flushPromises , getRoomContext , mkEvent , mkStubRoom } from "../../../../test-utils" ;
26
+ import {
27
+ createTestClient ,
28
+ flushPromises ,
29
+ getRoomContext ,
30
+ mkEvent ,
31
+ mkStubRoom ,
32
+ mockPlatformPeg ,
33
+ } from "../../../../test-utils" ;
27
34
import { EditWysiwygComposer } from "../../../../../src/components/views/rooms/wysiwyg_composer" ;
28
35
import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer" ;
29
36
import { Emoji } from "../../../../../src/components/views/rooms/wysiwyg_composer/components/Emoji" ;
@@ -32,38 +39,55 @@ import dis from "../../../../../src/dispatcher/dispatcher";
32
39
import { ComposerInsertPayload , ComposerType } from "../../../../../src/dispatcher/payloads/ComposerInsertPayload" ;
33
40
import { ActionPayload } from "../../../../../src/dispatcher/payloads" ;
34
41
import * as EmojiButton from "../../../../../src/components/views/rooms/EmojiButton" ;
42
+ import { setSelection } from "../../../../../src/components/views/rooms/wysiwyg_composer/utils/selection" ;
43
+ import { EventTimeline } from "../../../../../../matrix-js-sdk" ;
44
+ import * as EventUtils from "../../../../../src/utils/EventUtils" ;
45
+ import { SubSelection } from "../../../../../src/components/views/rooms/wysiwyg_composer/types" ;
35
46
36
47
describe ( "EditWysiwygComposer" , ( ) => {
37
48
afterEach ( ( ) => {
38
49
jest . resetAllMocks ( ) ;
39
50
} ) ;
40
51
41
- const mockClient = createTestClient ( ) ;
42
- const mockEvent = mkEvent ( {
43
- type : "m.room.message" ,
44
- room : "myfakeroom" ,
45
- user : "myfakeuser" ,
46
- content : {
47
- msgtype : "m.text" ,
48
- body : "Replying to this" ,
49
- format : "org.matrix.custom.html" ,
50
- formatted_body : "Replying <b>to</b> this new content" ,
51
- } ,
52
- event : true ,
53
- } ) ;
54
- const mockRoom = mkStubRoom ( "myfakeroom" , "myfakeroom" , mockClient ) as any ;
55
- mockRoom . findEventById = jest . fn ( ( eventId ) => {
56
- return eventId === mockEvent . getId ( ) ? mockEvent : null ;
57
- } ) ;
52
+ function createMocks ( eventContent = "Replying <strong>to</strong> this new content" ) {
53
+ const mockClient = createTestClient ( ) ;
54
+ const mockEvent = mkEvent ( {
55
+ type : "m.room.message" ,
56
+ room : "myfakeroom" ,
57
+ user : "myfakeuser" ,
58
+ content : {
59
+ msgtype : "m.text" ,
60
+ body : "Replying to this" ,
61
+ format : "org.matrix.custom.html" ,
62
+ formatted_body : eventContent ,
63
+ } ,
64
+ event : true ,
65
+ } ) ;
66
+ const mockRoom = mkStubRoom ( "myfakeroom" , "myfakeroom" , mockClient ) as any ;
67
+ mockRoom . findEventById = jest . fn ( ( eventId ) => {
68
+ return eventId === mockEvent . getId ( ) ? mockEvent : null ;
69
+ } ) ;
70
+
71
+ const defaultRoomContext : IRoomState = getRoomContext ( mockRoom , {
72
+ liveTimeline : { getEvents : ( ) => [ ] } as unknown as EventTimeline ,
73
+ } ) ;
74
+
75
+ const editorStateTransfer = new EditorStateTransfer ( mockEvent ) ;
58
76
59
- const defaultRoomContext : IRoomState = getRoomContext ( mockRoom , { } ) ;
77
+ return { defaultRoomContext, editorStateTransfer, mockClient, mockEvent } ;
78
+ }
60
79
61
- const editorStateTransfer = new EditorStateTransfer ( mockEvent ) ;
80
+ const { editorStateTransfer, defaultRoomContext , mockClient , mockEvent } = createMocks ( ) ;
62
81
63
- const customRender = ( disabled = false , _editorStateTransfer = editorStateTransfer ) => {
82
+ const customRender = (
83
+ disabled = false ,
84
+ _editorStateTransfer = editorStateTransfer ,
85
+ client = mockClient ,
86
+ roomContext = defaultRoomContext ,
87
+ ) => {
64
88
return render (
65
- < MatrixClientContext . Provider value = { mockClient } >
66
- < RoomContext . Provider value = { defaultRoomContext } >
89
+ < MatrixClientContext . Provider value = { client } >
90
+ < RoomContext . Provider value = { roomContext } >
67
91
< EditWysiwygComposer disabled = { disabled } editorStateTransfer = { _editorStateTransfer } />
68
92
</ RoomContext . Provider >
69
93
</ MatrixClientContext . Provider > ,
@@ -176,12 +200,13 @@ describe("EditWysiwygComposer", () => {
176
200
} ) ;
177
201
178
202
describe ( "Edit and save actions" , ( ) => {
203
+ let spyDispatcher : jest . SpyInstance < void , [ payload : ActionPayload , sync ?: boolean ] > ;
179
204
beforeEach ( async ( ) => {
205
+ spyDispatcher = jest . spyOn ( defaultDispatcher , "dispatch" ) ;
180
206
customRender ( ) ;
181
207
await waitFor ( ( ) => expect ( screen . getByRole ( "textbox" ) ) . toHaveAttribute ( "contentEditable" , "true" ) ) ;
182
208
} ) ;
183
209
184
- const spyDispatcher = jest . spyOn ( defaultDispatcher , "dispatch" ) ;
185
210
afterEach ( ( ) => {
186
211
spyDispatcher . mockRestore ( ) ;
187
212
} ) ;
@@ -204,7 +229,6 @@ describe("EditWysiwygComposer", () => {
204
229
205
230
it ( "Should send message on save button click" , async ( ) => {
206
231
// When
207
- const spyDispatcher = jest . spyOn ( defaultDispatcher , "dispatch" ) ;
208
232
fireEvent . input ( screen . getByRole ( "textbox" ) , {
209
233
data : "foo bar" ,
210
234
inputType : "insertText" ,
@@ -318,4 +342,141 @@ describe("EditWysiwygComposer", () => {
318
342
await waitFor ( ( ) => expect ( screen . getByRole ( "textbox" ) ) . toHaveTextContent ( / 🦫 / ) ) ;
319
343
dis . unregister ( dispatcherRef ) ;
320
344
} ) ;
345
+
346
+ describe ( "Keyboard navigation" , ( ) => {
347
+ const setup = async (
348
+ editorState = editorStateTransfer ,
349
+ client = createTestClient ( ) ,
350
+ roomContext = defaultRoomContext ,
351
+ ) => {
352
+ const spyDispatcher = jest . spyOn ( defaultDispatcher , "dispatch" ) ;
353
+ customRender ( false , editorState , client , roomContext ) ;
354
+ await waitFor ( ( ) => expect ( screen . getByRole ( "textbox" ) ) . toHaveAttribute ( "contentEditable" , "true" ) ) ;
355
+ return { textbox : screen . getByRole ( "textbox" ) , spyDispatcher } ;
356
+ } ;
357
+
358
+ beforeEach ( ( ) => {
359
+ mockPlatformPeg ( { overrideBrowserShortcuts : jest . fn ( ) . mockReturnValue ( false ) } ) ;
360
+ jest . spyOn ( EventUtils , "findEditableEvent" ) . mockReturnValue ( mockEvent ) ;
361
+ } ) ;
362
+
363
+ function select ( selection : SubSelection ) {
364
+ return act ( async ( ) => {
365
+ await setSelection ( selection ) ;
366
+ // the event is not automatically fired by jest
367
+ document . dispatchEvent ( new CustomEvent ( "selectionchange" ) ) ;
368
+ } ) ;
369
+ }
370
+
371
+ describe ( "Moving up" , ( ) => {
372
+ it ( "Should not moving when caret is not at beginning of the text" , async ( ) => {
373
+ // When
374
+ const { textbox, spyDispatcher } = await setup ( ) ;
375
+ const textNode = textbox . firstChild ;
376
+ await select ( {
377
+ anchorNode : textNode ,
378
+ anchorOffset : 1 ,
379
+ focusNode : textNode ,
380
+ focusOffset : 2 ,
381
+ isForward : true ,
382
+ } ) ;
383
+
384
+ fireEvent . keyDown ( textbox , {
385
+ key : "ArrowUp" ,
386
+ } ) ;
387
+
388
+ // Then
389
+ expect ( spyDispatcher ) . toBeCalledTimes ( 0 ) ;
390
+ } ) ;
391
+
392
+ it ( "Should not moving when the content has changed" , async ( ) => {
393
+ // When
394
+ const { textbox, spyDispatcher } = await setup ( ) ;
395
+ fireEvent . input ( textbox , {
396
+ data : "word" ,
397
+ inputType : "insertText" ,
398
+ } ) ;
399
+ const textNode = textbox . firstChild ;
400
+ await select ( {
401
+ anchorNode : textNode ,
402
+ anchorOffset : 0 ,
403
+ focusNode : textNode ,
404
+ focusOffset : 0 ,
405
+ isForward : true ,
406
+ } ) ;
407
+
408
+ fireEvent . keyDown ( textbox , {
409
+ key : "ArrowUp" ,
410
+ } ) ;
411
+
412
+ // Then
413
+ expect ( spyDispatcher ) . toBeCalledTimes ( 0 ) ;
414
+ } ) ;
415
+
416
+ it ( "Should moving up" , async ( ) => {
417
+ // When
418
+ const { textbox, spyDispatcher } = await setup ( ) ;
419
+ const textNode = textbox . firstChild ;
420
+ await select ( {
421
+ anchorNode : textNode ,
422
+ anchorOffset : 0 ,
423
+ focusNode : textNode ,
424
+ focusOffset : 0 ,
425
+ isForward : true ,
426
+ } ) ;
427
+
428
+ fireEvent . keyDown ( textbox , {
429
+ key : "ArrowUp" ,
430
+ } ) ;
431
+
432
+ // Wait for event dispatch to happen
433
+ await act ( async ( ) => {
434
+ await flushPromises ( ) ;
435
+ } ) ;
436
+
437
+ // Then
438
+ await waitFor ( ( ) =>
439
+ expect ( spyDispatcher ) . toBeCalledWith ( {
440
+ action : Action . EditEvent ,
441
+ event : mockEvent ,
442
+ timelineRenderingType : defaultRoomContext . timelineRenderingType ,
443
+ } ) ,
444
+ ) ;
445
+ } ) ;
446
+
447
+ it ( "Should moving up in list" , async ( ) => {
448
+ // When
449
+ const { mockEvent, defaultRoomContext, mockClient, editorStateTransfer } = createMocks (
450
+ "<ul><li><strong>Content</strong></li><li>Other Content</li></ul>" ,
451
+ ) ;
452
+ jest . spyOn ( EventUtils , "findEditableEvent" ) . mockReturnValue ( mockEvent ) ;
453
+ const { textbox, spyDispatcher } = await setup ( editorStateTransfer , mockClient , defaultRoomContext ) ;
454
+
455
+ const textNode = textbox . firstChild ;
456
+ await select ( {
457
+ anchorNode : textNode ,
458
+ anchorOffset : 0 ,
459
+ focusNode : textNode ,
460
+ focusOffset : 0 ,
461
+ isForward : true ,
462
+ } ) ;
463
+
464
+ fireEvent . keyDown ( textbox , {
465
+ key : "ArrowUp" ,
466
+ } ) ;
467
+
468
+ // Wait for event dispatch to happen
469
+ await act ( async ( ) => {
470
+ await flushPromises ( ) ;
471
+ } ) ;
472
+
473
+ // Then
474
+ expect ( spyDispatcher ) . toBeCalledWith ( {
475
+ action : Action . EditEvent ,
476
+ event : mockEvent ,
477
+ timelineRenderingType : defaultRoomContext . timelineRenderingType ,
478
+ } ) ;
479
+ } ) ;
480
+ } ) ;
481
+ } ) ;
321
482
} ) ;
0 commit comments