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

Commit 3d5d377

Browse files
committed
Add Keyboard Up test
1 parent d5a218b commit 3d5d377

File tree

2 files changed

+190
-27
lines changed

2 files changed

+190
-27
lines changed

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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 188 additions & 25 deletions
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,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

Comments
 (0)