Add test for plain mode
							parent
							
								
									f1610dae3d
								
							
						
					
					
						commit
						fb751c3c7b
					
				|  | @ -43,7 +43,13 @@ export function PlainTextComposer({ | |||
|     usePlainTextInitialization(initialContent, ref); | ||||
|     useSetCursorPosition(disabled, ref); | ||||
| 
 | ||||
|     return <div className={className} onInput={onInput} onPaste={onPaste} onKeyDown={onKeyDown}> | ||||
|     return <div | ||||
|         data-testid="PlainTextComposer" | ||||
|         className={className} | ||||
|         onInput={onInput} | ||||
|         onPaste={onPaste} | ||||
|         onKeyDown={onKeyDown} | ||||
|     > | ||||
|         <Editor ref={ref} disabled={disabled} /> | ||||
|         { children?.(ref, composerFunctions) } | ||||
|     </div>; | ||||
|  |  | |||
|  | @ -52,7 +52,7 @@ export const WysiwygComposer = memo(function WysiwygComposer( | |||
|     useSetCursorPosition(!isReady, ref); | ||||
| 
 | ||||
|     return ( | ||||
|         <div className={className}> | ||||
|         <div data-testid="WysiwygComposer" className={className}> | ||||
|             <FormattingButtons composer={wysiwyg} formattingStates={formattingStates} /> | ||||
|             <Editor ref={ref} disabled={!isReady} /> | ||||
|             { children?.(ref, wysiwyg) } | ||||
|  |  | |||
|  | @ -16,13 +16,10 @@ limitations under the License. | |||
| 
 | ||||
| import { RefObject, useEffect } from "react"; | ||||
| 
 | ||||
| import { setCursorPositionAtTheEnd } from "./utils"; | ||||
| 
 | ||||
| export function usePlainTextInitialization(initialContent: string, ref: RefObject<HTMLElement>) { | ||||
|     useEffect(() => { | ||||
|         if (ref.current) { | ||||
|             ref.current.innerText = initialContent; | ||||
|             setCursorPositionAtTheEnd(ref.current); | ||||
|         } | ||||
|     }, [ref, initialContent]); | ||||
| } | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ limitations under the License. | |||
| 
 | ||||
| import { KeyboardEvent, SyntheticEvent, useCallback, useRef } from "react"; | ||||
| 
 | ||||
| import { useInputEventProcessor } from "./useInputEventProcessor"; | ||||
| import { useSettingValue } from "../../../../../hooks/useSettings"; | ||||
| 
 | ||||
| function isDivElement(target: EventTarget): target is HTMLDivElement { | ||||
|     return target instanceof HTMLDivElement; | ||||
|  | @ -26,25 +26,25 @@ export function usePlainTextListeners(onChange: (content: string) => void, onSen | |||
|     const ref = useRef<HTMLDivElement>(); | ||||
|     const send = useCallback((() => { | ||||
|         if (ref.current) { | ||||
|             ref.current.innerText = ''; | ||||
|             ref.current.innerHTML = ''; | ||||
|         } | ||||
|         onSend(); | ||||
|     }), [ref, onSend]); | ||||
| 
 | ||||
|     const inputEventProcessor = useInputEventProcessor(send); | ||||
| 
 | ||||
|     const onInput = useCallback((event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>) => { | ||||
|         if (isDivElement(event.target)) { | ||||
|             onChange(event.target.innerText); | ||||
|             onChange(event.target.innerHTML); | ||||
|         } | ||||
|         inputEventProcessor(event.nativeEvent); | ||||
|     }, [onChange, inputEventProcessor]); | ||||
|     }, [onChange]); | ||||
| 
 | ||||
|     const isCtrlEnter = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend"); | ||||
|     const onKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => { | ||||
|         if (event.key === 'Enter') { | ||||
|         if (event.key === 'Enter' && !event.shiftKey && (!isCtrlEnter || (isCtrlEnter && event.ctrlKey))) { | ||||
|             event.preventDefault(); | ||||
|             event.stopPropagation(); | ||||
|             send(); | ||||
|         } | ||||
|     }, [send]); | ||||
|     }, [isCtrlEnter, send]); | ||||
| 
 | ||||
|     return { ref, onInput, onPaste: onInput, onKeyDown }; | ||||
| } | ||||
|  |  | |||
|  | @ -26,6 +26,8 @@ import { Action } from "../../../../../src/dispatcher/actions"; | |||
| import { IRoomState } from "../../../../../src/components/structures/RoomView"; | ||||
| import { createTestClient, getRoomContext, mkEvent, mkStubRoom } from "../../../../test-utils"; | ||||
| import { SendWysiwygComposer } from "../../../../../src/components/views/rooms/wysiwyg_composer"; | ||||
| import * as useComposerFunctions | ||||
|     from "../../../../../src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions"; | ||||
| 
 | ||||
| const mockClear = jest.fn(); | ||||
| 
 | ||||
|  | @ -68,86 +70,119 @@ describe('SendWysiwygComposer', () => { | |||
| 
 | ||||
|     const defaultRoomContext: IRoomState = getRoomContext(mockRoom, {}); | ||||
| 
 | ||||
|     const customRender = (onChange = (_content: string) => void 0, onSend = () => void 0, disabled = false) => { | ||||
|     const customRender = ( | ||||
|         onChange = (_content: string) => void 0, | ||||
|         onSend = () => void 0, | ||||
|         disabled = false, | ||||
|         isRichTextEnabled = true) => { | ||||
|         return render( | ||||
|             <MatrixClientContext.Provider value={mockClient}> | ||||
|                 <RoomContext.Provider value={defaultRoomContext}> | ||||
|                     <SendWysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} isRichTextEnabled={true} /> | ||||
|                     <SendWysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} isRichTextEnabled={isRichTextEnabled} /> | ||||
|                 </RoomContext.Provider> | ||||
|             </MatrixClientContext.Provider>, | ||||
|         ); | ||||
|     }; | ||||
| 
 | ||||
|     it('Should focus when receiving an Action.FocusSendMessageComposer action', async () => { | ||||
|         // Given we don't have focus
 | ||||
|         customRender(jest.fn(), jest.fn()); | ||||
|         screen.getByLabelText('Bold').focus(); | ||||
|         expect(screen.getByRole('textbox')).not.toHaveFocus(); | ||||
|     it('Should render WysiwygComposer when isRichTextEnabled is at true', () => { | ||||
|         // When
 | ||||
|         customRender(jest.fn(), jest.fn(), false, true); | ||||
| 
 | ||||
|         // When we send the right action
 | ||||
|         defaultDispatcher.dispatch({ | ||||
|             action: Action.FocusSendMessageComposer, | ||||
|             context: null, | ||||
|         }); | ||||
| 
 | ||||
|         // Then the component gets the focus
 | ||||
|         await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus()); | ||||
|         // Then
 | ||||
|         expect(screen.getByTestId('WysiwygComposer')).toBeTruthy(); | ||||
|     }); | ||||
| 
 | ||||
|     it('Should focus and clear when receiving an Action.ClearAndFocusSendMessageComposer', async () => { | ||||
|         // Given we don't have focus
 | ||||
|         customRender(jest.fn(), jest.fn()); | ||||
|         screen.getByLabelText('Bold').focus(); | ||||
|         expect(screen.getByRole('textbox')).not.toHaveFocus(); | ||||
|     it('Should render PlainTextComposer when isRichTextEnabled is at false', () => { | ||||
|         // When
 | ||||
|         customRender(jest.fn(), jest.fn(), false, false); | ||||
| 
 | ||||
|         // When we send the right action
 | ||||
|         defaultDispatcher.dispatch({ | ||||
|             action: Action.ClearAndFocusSendMessageComposer, | ||||
|             context: null, | ||||
|         }); | ||||
| 
 | ||||
|         // Then the component gets the focus
 | ||||
|         await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus()); | ||||
|         expect(mockClear).toBeCalledTimes(1); | ||||
|         // Then
 | ||||
|         expect(screen.getByTestId('PlainTextComposer')).toBeTruthy(); | ||||
|     }); | ||||
| 
 | ||||
|     it('Should focus when receiving a reply_to_event action', async () => { | ||||
|         // Given we don't have focus
 | ||||
|         customRender(jest.fn(), jest.fn()); | ||||
|         screen.getByLabelText('Bold').focus(); | ||||
|         expect(screen.getByRole('textbox')).not.toHaveFocus(); | ||||
|     describe.each([{ isRichTextEnabled: true }, { isRichTextEnabled: false }])( | ||||
|         'Should focus when receiving an Action.FocusSendMessageComposer action', | ||||
|         ({ isRichTextEnabled }) => { | ||||
|             afterEach(() => { | ||||
|                 jest.resetAllMocks(); | ||||
|             }); | ||||
| 
 | ||||
|         // When we send the right action
 | ||||
|         defaultDispatcher.dispatch({ | ||||
|             action: "reply_to_event", | ||||
|             context: null, | ||||
|             it('Should focus when receiving an Action.FocusSendMessageComposer action', async () => { | ||||
|             // Given we don't have focus
 | ||||
|                 customRender(jest.fn(), jest.fn(), false, isRichTextEnabled); | ||||
|                 document.head.focus(); | ||||
|                 //    screen.getByLabelText('Bold').focus();
 | ||||
|                 // expect(screen.getByRole('textbox')).not.toHaveFocus();
 | ||||
| 
 | ||||
|                 // When we send the right action
 | ||||
|                 defaultDispatcher.dispatch({ | ||||
|                     action: Action.FocusSendMessageComposer, | ||||
|                     context: null, | ||||
|                 }); | ||||
| 
 | ||||
|                 // Then the component gets the focus
 | ||||
|                 await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus()); | ||||
|             }); | ||||
| 
 | ||||
|             it('Should focus and clear when receiving an Action.ClearAndFocusSendMessageComposer', async () => { | ||||
|             // Given we don't have focus
 | ||||
|                 const mock = jest.spyOn(useComposerFunctions, 'useComposerFunctions'); | ||||
|                 mock.mockReturnValue({ clear: mockClear }); | ||||
|                 customRender(jest.fn(), jest.fn(), false, isRichTextEnabled); | ||||
|                 //   screen.getByLabelText('Bold').focus();
 | ||||
|                 //    expect(screen.getByRole('textbox')).not.toHaveFocus();
 | ||||
| 
 | ||||
|                 // When we send the right action
 | ||||
|                 defaultDispatcher.dispatch({ | ||||
|                     action: Action.ClearAndFocusSendMessageComposer, | ||||
|                     context: null, | ||||
|                 }); | ||||
| 
 | ||||
|                 // Then the component gets the focus
 | ||||
|                 await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus()); | ||||
|                 expect(mockClear).toBeCalledTimes(1); | ||||
| 
 | ||||
|                 mock.mockRestore(); | ||||
|             }); | ||||
| 
 | ||||
|             it('Should focus when receiving a reply_to_event action', async () => { | ||||
|             // Given we don't have focus
 | ||||
|                 customRender(jest.fn(), jest.fn(), false, isRichTextEnabled); | ||||
|                 //   screen.getByLabelText('Bold').focus();
 | ||||
|                 //   expect(screen.getByRole('textbox')).not.toHaveFocus();
 | ||||
| 
 | ||||
|                 // When we send the right action
 | ||||
|                 defaultDispatcher.dispatch({ | ||||
|                     action: "reply_to_event", | ||||
|                     context: null, | ||||
|                 }); | ||||
| 
 | ||||
|                 // Then the component gets the focus
 | ||||
|                 await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus()); | ||||
|             }); | ||||
| 
 | ||||
|             it('Should not focus when disabled', async () => { | ||||
|             // Given we don't have focus and we are disabled
 | ||||
|                 customRender(jest.fn(), jest.fn(), true, isRichTextEnabled); | ||||
|                 expect(screen.getByRole('textbox')).not.toHaveFocus(); | ||||
| 
 | ||||
|                 // When we send an action that would cause us to get focus
 | ||||
|                 defaultDispatcher.dispatch({ | ||||
|                     action: Action.FocusSendMessageComposer, | ||||
|                     context: null, | ||||
|                 }); | ||||
|                 // (Send a second event to exercise the clearTimeout logic)
 | ||||
|                 defaultDispatcher.dispatch({ | ||||
|                     action: Action.FocusSendMessageComposer, | ||||
|                     context: null, | ||||
|                 }); | ||||
| 
 | ||||
|                 // Wait for event dispatch to happen
 | ||||
|                 await new Promise((r) => setTimeout(r, 200)); | ||||
| 
 | ||||
|                 // Then we don't get it because we are disabled
 | ||||
|                 expect(screen.getByRole('textbox')).not.toHaveFocus(); | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         // Then the component gets the focus
 | ||||
|         await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus()); | ||||
|     }); | ||||
| 
 | ||||
|     it('Should not focus when disabled', async () => { | ||||
|         // Given we don't have focus and we are disabled
 | ||||
|         customRender(jest.fn(), jest.fn(), true); | ||||
|         expect(screen.getByRole('textbox')).not.toHaveFocus(); | ||||
| 
 | ||||
|         // When we send an action that would cause us to get focus
 | ||||
|         defaultDispatcher.dispatch({ | ||||
|             action: Action.FocusSendMessageComposer, | ||||
|             context: null, | ||||
|         }); | ||||
|         // (Send a second event to exercise the clearTimeout logic)
 | ||||
|         defaultDispatcher.dispatch({ | ||||
|             action: Action.FocusSendMessageComposer, | ||||
|             context: null, | ||||
|         }); | ||||
| 
 | ||||
|         // Wait for event dispatch to happen
 | ||||
|         await new Promise((r) => setTimeout(r, 200)); | ||||
| 
 | ||||
|         // Then we don't get it because we are disabled
 | ||||
|         expect(screen.getByRole('textbox')).not.toHaveFocus(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,94 @@ | |||
| /* | ||||
| Copyright 2022 The Matrix.org Foundation C.I.C. | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import { render, screen } from "@testing-library/react"; | ||||
| import userEvent from "@testing-library/user-event"; | ||||
| 
 | ||||
| import { PlainTextComposer } | ||||
|     from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer"; | ||||
| 
 | ||||
| // Work around missing ClipboardEvent type
 | ||||
| class MyClipboardEvent {} | ||||
| window.ClipboardEvent = MyClipboardEvent as any; | ||||
| 
 | ||||
| describe('PlainTextComposer', () => { | ||||
|     const customRender = ( | ||||
|         onChange = (_content: string) => void 0, | ||||
|         onSend = () => void 0, | ||||
|         disabled = false, | ||||
|         initialContent?: string) => { | ||||
|         return render( | ||||
|             <PlainTextComposer onChange={onChange} onSend={onSend} disabled={disabled} initialContent={initialContent} />, | ||||
|         ); | ||||
|     }; | ||||
| 
 | ||||
|     it('Should have contentEditable at false when disabled', () => { | ||||
|         // When
 | ||||
|         customRender(jest.fn(), jest.fn(), true); | ||||
| 
 | ||||
|         // Then
 | ||||
|         expect(screen.getByRole('textbox')).toHaveAttribute('contentEditable', "false"); | ||||
|     }); | ||||
| 
 | ||||
|     it('Should have focus', () => { | ||||
|         // When
 | ||||
|         customRender(jest.fn(), jest.fn(), false); | ||||
| 
 | ||||
|         // Then
 | ||||
|         expect(screen.getByRole('textbox')).toHaveFocus(); | ||||
|     }); | ||||
| 
 | ||||
|     it('Should call onChange handler', async () => { | ||||
|         // When
 | ||||
|         const content = 'content'; | ||||
|         const onChange = jest.fn(); | ||||
|         customRender(onChange, jest.fn()); | ||||
|         await userEvent.type(screen.getByRole('textbox'), content); | ||||
| 
 | ||||
|         // Then
 | ||||
|         expect(onChange).toBeCalledWith(content); | ||||
|     }); | ||||
| 
 | ||||
|     it('Should call onSend when Enter is pressed', async () => { | ||||
|         //When
 | ||||
|         const onSend = jest.fn(); | ||||
|         customRender(jest.fn(), onSend); | ||||
|         await userEvent.type(screen.getByRole('textbox'), '{enter}'); | ||||
| 
 | ||||
|         // Then it sends a message
 | ||||
|         expect(onSend).toBeCalledTimes(1); | ||||
|     }); | ||||
| 
 | ||||
|     it('Should clear textbox content when clear is called', async () => { | ||||
|         //When
 | ||||
|         let composer; | ||||
|         render( | ||||
|             <PlainTextComposer onChange={jest.fn()} onSend={jest.fn()}> | ||||
|                 { (ref, composerFunctions) => { | ||||
|                     composer = composerFunctions; | ||||
|                     return null; | ||||
|                 } } | ||||
|             </PlainTextComposer>, | ||||
|         ); | ||||
|         await userEvent.type(screen.getByRole('textbox'), 'content'); | ||||
|         expect(screen.getByRole('textbox').innerHTML).toBe('content'); | ||||
|         composer.clear(); | ||||
| 
 | ||||
|         // Then
 | ||||
|         expect(screen.getByRole('textbox').innerHTML).toBeFalsy(); | ||||
|     }); | ||||
| }); | ||||
|  | @ -19,10 +19,6 @@ import React from "react"; | |||
| import { render, screen } from "@testing-library/react"; | ||||
| import { InputEventProcessor, Wysiwyg, WysiwygProps } from "@matrix-org/matrix-wysiwyg"; | ||||
| 
 | ||||
| import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext"; | ||||
| import { IRoomState } from "../../../../../../src/components/structures/RoomView"; | ||||
| import { createTestClient, getRoomContext, mkEvent, mkStubRoom } from "../../../../../test-utils"; | ||||
| import RoomContext from "../../../../../../src/contexts/RoomContext"; | ||||
| import { WysiwygComposer } | ||||
|     from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer"; | ||||
| import SettingsStore from "../../../../../../src/settings/SettingsStore"; | ||||
|  | @ -54,36 +50,14 @@ jest.mock("@matrix-org/matrix-wysiwyg", () => ({ | |||
| })); | ||||
| 
 | ||||
| describe('WysiwygComposer', () => { | ||||
|     afterEach(() => { | ||||
|         jest.resetAllMocks(); | ||||
|     }); | ||||
| 
 | ||||
|     const mockClient = createTestClient(); | ||||
|     const mockEvent = mkEvent({ | ||||
|         type: "m.room.message", | ||||
|         room: 'myfakeroom', | ||||
|         user: 'myfakeuser', | ||||
|         content: { "msgtype": "m.text", "body": "Replying to this" }, | ||||
|         event: true, | ||||
|     }); | ||||
|     const mockRoom = mkStubRoom('myfakeroom', 'myfakeroom', mockClient) as any; | ||||
|     mockRoom.findEventById = jest.fn(eventId => { | ||||
|         return eventId === mockEvent.getId() ? mockEvent : null; | ||||
|     }); | ||||
| 
 | ||||
|     const defaultRoomContext: IRoomState = getRoomContext(mockRoom, {}); | ||||
| 
 | ||||
|     const customRender = ( | ||||
|         onChange = (_content: string) => void 0, | ||||
|         onSend = () => void 0, | ||||
|         disabled = false, | ||||
|         initialContent?: string) => { | ||||
|         return render( | ||||
|             <MatrixClientContext.Provider value={mockClient}> | ||||
|                 <RoomContext.Provider value={defaultRoomContext}> | ||||
|                     <WysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} initialContent={initialContent} /> | ||||
|                 </RoomContext.Provider> | ||||
|             </MatrixClientContext.Provider>, | ||||
|             <WysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} initialContent={initialContent} />, | ||||
| 
 | ||||
|         ); | ||||
|     }; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Florian Duros
						Florian Duros