Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit d93a4c9

Browse files
committed
Add Keyboard Up test
1 parent d5a218b commit d93a4c9

File tree

2 files changed

+188
-27
lines changed

2 files changed

+188
-27
lines changed

src/components/views/rooms/wysiwyg_composer/utils/selection.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ export function isCaretAtEnd(editor: HTMLElement): boolean {
6666
return false;
6767
}
6868

69-
// When we are going cycling across all the timeline message with the keyboard
70-
// The caret is on the last message element but the focusNode and the anchorNode is the editor himself instead of the text in it.
69+
// When we are cycling across all the timeline message with the keyboard
70+
// The caret is on the last text element but focusNode and anchorNode refers to the editor div
7171
// In this case, the focusOffset & anchorOffset match the index + 1 of the selected text
7272
const isOnLastElement = selection.focusNode === editor && selection.focusOffset === editor.childNodes?.length;
7373
if (isOnLastElement) {

test/components/views/rooms/wysiwyg_composer/EditWysiwygComposer-test.tsx

+186-25
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ import RoomContext from "../../../../../src/contexts/RoomContext";
2323
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
2424
import { Action } from "../../../../../src/dispatcher/actions";
2525
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";
2734
import { EditWysiwygComposer } from "../../../../../src/components/views/rooms/wysiwyg_composer";
2835
import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer";
2936
import { Emoji } from "../../../../../src/components/views/rooms/wysiwyg_composer/components/Emoji";
@@ -32,38 +39,55 @@ import dis from "../../../../../src/dispatcher/dispatcher";
3239
import { ComposerInsertPayload, ComposerType } from "../../../../../src/dispatcher/payloads/ComposerInsertPayload";
3340
import { ActionPayload } from "../../../../../src/dispatcher/payloads";
3441
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";
3546

3647
describe("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,13 @@ 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");
185210
afterEach(() => {
186211
spyDispatcher.mockRestore();
187212
});
@@ -204,7 +229,6 @@ describe("EditWysiwygComposer", () => {
204229

205230
it("Should send message on save button click", async () => {
206231
// When
207-
const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch");
208232
fireEvent.input(screen.getByRole("textbox"), {
209233
data: "foo bar",
210234
inputType: "insertText",
@@ -318,4 +342,141 @@ describe("EditWysiwygComposer", () => {
318342
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(/🦫/));
319343
dis.unregister(dispatcherRef);
320344
});
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+
});
321482
});

0 commit comments

Comments
 (0)