@@ -23,7 +23,14 @@ import RoomContext from "../../../../../src/contexts/RoomContext";
2323import defaultDispatcher from "../../../../../src/dispatcher/dispatcher" ;
2424import { Action } from "../../../../../src/dispatcher/actions" ;
2525import { 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" ;
2734import { EditWysiwygComposer } from "../../../../../src/components/views/rooms/wysiwyg_composer" ;
2835import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer" ;
2936import { Emoji } from "../../../../../src/components/views/rooms/wysiwyg_composer/components/Emoji" ;
@@ -32,38 +39,55 @@ import dis from "../../../../../src/dispatcher/dispatcher";
3239import { ComposerInsertPayload , ComposerType } from "../../../../../src/dispatcher/payloads/ComposerInsertPayload" ;
3340import { ActionPayload } from "../../../../../src/dispatcher/payloads" ;
3441import * 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" ;
3546
3647describe ( "EditWysiwygComposer" , ( ) => {
3748 afterEach ( ( ) => {
3849 jest . resetAllMocks ( ) ;
3950 } ) ;
4051
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 ) ;
5876
59- const defaultRoomContext : IRoomState = getRoomContext ( mockRoom , { } ) ;
77+ return { defaultRoomContext, editorStateTransfer, mockClient, mockEvent } ;
78+ }
6079
61- const editorStateTransfer = new EditorStateTransfer ( mockEvent ) ;
80+ const { editorStateTransfer, defaultRoomContext , mockClient , mockEvent } = createMocks ( ) ;
6281
63- const customRender = ( disabled = false , _editorStateTransfer = editorStateTransfer ) => {
82+ const customRender = (
83+ disabled = false ,
84+ _editorStateTransfer = editorStateTransfer ,
85+ client = mockClient ,
86+ roomContext = defaultRoomContext ,
87+ ) => {
6488 return render (
65- < MatrixClientContext . Provider value = { mockClient } >
66- < RoomContext . Provider value = { defaultRoomContext } >
89+ < MatrixClientContext . Provider value = { client } >
90+ < RoomContext . Provider value = { roomContext } >
6791 < EditWysiwygComposer disabled = { disabled } editorStateTransfer = { _editorStateTransfer } />
6892 </ RoomContext . Provider >
6993 </ MatrixClientContext . Provider > ,
@@ -176,12 +200,14 @@ describe("EditWysiwygComposer", () => {
176200 } ) ;
177201
178202 describe ( "Edit and save actions" , ( ) => {
203+ let spyDispatcher : jest . SpyInstance < void , [ payload : ActionPayload , sync ?: boolean ] > ;
179204 beforeEach ( async ( ) => {
205+ spyDispatcher = jest . spyOn ( defaultDispatcher , "dispatch" ) ;
180206 customRender ( ) ;
181207 await waitFor ( ( ) => expect ( screen . getByRole ( "textbox" ) ) . toHaveAttribute ( "contentEditable" , "true" ) ) ;
182208 } ) ;
183209
184- const spyDispatcher = jest . spyOn ( defaultDispatcher , "dispatch" ) ;
210+ // const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch");
185211 afterEach ( ( ) => {
186212 spyDispatcher . mockRestore ( ) ;
187213 } ) ;
@@ -204,7 +230,7 @@ describe("EditWysiwygComposer", () => {
204230
205231 it ( "Should send message on save button click" , async ( ) => {
206232 // When
207- const spyDispatcher = jest . spyOn ( defaultDispatcher , "dispatch" ) ;
233+ // const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch");
208234 fireEvent . input ( screen . getByRole ( "textbox" ) , {
209235 data : "foo bar" ,
210236 inputType : "insertText" ,
@@ -318,4 +344,141 @@ describe("EditWysiwygComposer", () => {
318344 await waitFor ( ( ) => expect ( screen . getByRole ( "textbox" ) ) . toHaveTextContent ( / 🦫 / ) ) ;
319345 dis . unregister ( dispatcherRef ) ;
320346 } ) ;
347+
348+ describe ( "Keyboard navigation" , ( ) => {
349+ const setup = async (
350+ editorState = editorStateTransfer ,
351+ client = createTestClient ( ) ,
352+ roomContext = defaultRoomContext ,
353+ ) => {
354+ const spyDispatcher = jest . spyOn ( defaultDispatcher , "dispatch" ) ;
355+ customRender ( false , editorState , client , roomContext ) ;
356+ await waitFor ( ( ) => expect ( screen . getByRole ( "textbox" ) ) . toHaveAttribute ( "contentEditable" , "true" ) ) ;
357+ return { textbox : screen . getByRole ( "textbox" ) , spyDispatcher } ;
358+ } ;
359+
360+ beforeEach ( ( ) => {
361+ mockPlatformPeg ( { overrideBrowserShortcuts : jest . fn ( ) . mockReturnValue ( false ) } ) ;
362+ jest . spyOn ( EventUtils , "findEditableEvent" ) . mockReturnValue ( mockEvent ) ;
363+ } ) ;
364+
365+ function select ( selection : SubSelection ) {
366+ return act ( async ( ) => {
367+ await setSelection ( selection ) ;
368+ // the event is not automatically fired by jest
369+ document . dispatchEvent ( new CustomEvent ( "selectionchange" ) ) ;
370+ } ) ;
371+ }
372+
373+ describe ( "Moving up" , ( ) => {
374+ it ( "Should not moving when caret is not at beginning of the text" , async ( ) => {
375+ // When
376+ const { textbox, spyDispatcher } = await setup ( ) ;
377+ const textNode = textbox . firstChild ;
378+ await select ( {
379+ anchorNode : textNode ,
380+ anchorOffset : 1 ,
381+ focusNode : textNode ,
382+ focusOffset : 2 ,
383+ isForward : true ,
384+ } ) ;
385+
386+ fireEvent . keyDown ( textbox , {
387+ key : "ArrowUp" ,
388+ } ) ;
389+
390+ // Then
391+ expect ( spyDispatcher ) . toBeCalledTimes ( 0 ) ;
392+ } ) ;
393+
394+ it ( "Should not moving when the content has changed" , async ( ) => {
395+ // When
396+ const { textbox, spyDispatcher } = await setup ( ) ;
397+ fireEvent . input ( textbox , {
398+ data : "word" ,
399+ inputType : "insertText" ,
400+ } ) ;
401+ const textNode = textbox . firstChild ;
402+ await select ( {
403+ anchorNode : textNode ,
404+ anchorOffset : 0 ,
405+ focusNode : textNode ,
406+ focusOffset : 0 ,
407+ isForward : true ,
408+ } ) ;
409+
410+ fireEvent . keyDown ( textbox , {
411+ key : "ArrowUp" ,
412+ } ) ;
413+
414+ // Then
415+ expect ( spyDispatcher ) . toBeCalledTimes ( 0 ) ;
416+ } ) ;
417+
418+ it ( "Should moving up" , async ( ) => {
419+ // When
420+ const { textbox, spyDispatcher } = await setup ( ) ;
421+ const textNode = textbox . firstChild ;
422+ await select ( {
423+ anchorNode : textNode ,
424+ anchorOffset : 0 ,
425+ focusNode : textNode ,
426+ focusOffset : 0 ,
427+ isForward : true ,
428+ } ) ;
429+
430+ fireEvent . keyDown ( textbox , {
431+ key : "ArrowUp" ,
432+ } ) ;
433+
434+ // Wait for event dispatch to happen
435+ await act ( async ( ) => {
436+ await flushPromises ( ) ;
437+ } ) ;
438+
439+ // Then
440+ await waitFor ( ( ) =>
441+ expect ( spyDispatcher ) . toBeCalledWith ( {
442+ action : Action . EditEvent ,
443+ event : mockEvent ,
444+ timelineRenderingType : defaultRoomContext . timelineRenderingType ,
445+ } ) ,
446+ ) ;
447+ } ) ;
448+
449+ it ( "Should moving up in list" , async ( ) => {
450+ // When
451+ const { mockEvent, defaultRoomContext, mockClient, editorStateTransfer } = createMocks (
452+ "<ul><li><strong>Content</strong></li><li>Other Content</li></ul>" ,
453+ ) ;
454+ jest . spyOn ( EventUtils , "findEditableEvent" ) . mockReturnValue ( mockEvent ) ;
455+ const { textbox, spyDispatcher } = await setup ( editorStateTransfer , mockClient , defaultRoomContext ) ;
456+
457+ const textNode = textbox . firstChild ;
458+ await select ( {
459+ anchorNode : textNode ,
460+ anchorOffset : 0 ,
461+ focusNode : textNode ,
462+ focusOffset : 0 ,
463+ isForward : true ,
464+ } ) ;
465+
466+ fireEvent . keyDown ( textbox , {
467+ key : "ArrowUp" ,
468+ } ) ;
469+
470+ // Wait for event dispatch to happen
471+ await act ( async ( ) => {
472+ await flushPromises ( ) ;
473+ } ) ;
474+
475+ // Then
476+ expect ( spyDispatcher ) . toBeCalledWith ( {
477+ action : Action . EditEvent ,
478+ event : mockEvent ,
479+ timelineRenderingType : defaultRoomContext . timelineRenderingType ,
480+ } ) ;
481+ } ) ;
482+ } ) ;
483+ } ) ;
321484} ) ;
0 commit comments