Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/components/views/rooms/BasicMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,11 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>

private onKeyDown = (event: React.KeyboardEvent): void => {
if (!this.editorRef.current) return;
// Ignore any keypress while doing IME compositions to prevent cursor position issues
// This matches the behavior in SendMessageComposer and EditMessageComposer
if (this.isComposing(event)) {
return;
}
if (this.isSafari && event.which == 229) {
// Swallow the extra keyDown by Safari
event.stopPropagation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/

import React from "react";
import { render, screen } from "jest-matrix-react";
import { fireEvent, render, screen } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";

Expand Down Expand Up @@ -125,6 +125,73 @@ describe("BasicMessageComposer", () => {
expect(spy).toHaveBeenCalledWith(room.roomId, null, false);
spy.mockRestore();
});

it("should ignore keydown events during IME composition", () => {
const model = new EditorModel([], pc, renderer);
render(<BasicMessageComposer model={model} room={room} />);
const input = screen.getByRole("textbox");

// Start IME composition
fireEvent.compositionStart(input);

// Simulate Tab key during IME composition
// The keydown should be ignored, so we check that the model state doesn't change
const initialAutoComplete = model.autoComplete;
const initialPartsLength = model.parts.length;

// Create a keyboard event with isComposing flag
const tabKeyEvent = new KeyboardEvent("keydown", {
key: "Tab",
bubbles: true,
cancelable: true,
});
Object.defineProperty(tabKeyEvent, "isComposing", {
value: true,
writable: false,
});

// Fire the keydown event with isComposing flag
fireEvent.keyDown(input, {
...tabKeyEvent,
nativeEvent: tabKeyEvent,
} as unknown as React.KeyboardEvent);

// During IME composition, the keydown should be ignored
// The model should not have changed
expect(model.autoComplete).toBe(initialAutoComplete);
expect(model.parts.length).toBe(initialPartsLength);

// End IME composition
fireEvent.compositionEnd(input);
});

it("should handle keydown events normally when not composing", () => {
const model = new EditorModel([], pc, renderer);
render(<BasicMessageComposer model={model} room={room} />);
const input = screen.getByRole("textbox");

// Simulate Tab key when NOT composing
const tabKeyEvent = new KeyboardEvent("keydown", {
key: "Tab",
bubbles: true,
cancelable: true,
});
Object.defineProperty(tabKeyEvent, "isComposing", {
value: false,
writable: false,
});

// Fire the keydown event without isComposing flag
fireEvent.keyDown(input, {
...tabKeyEvent,
nativeEvent: tabKeyEvent,
} as unknown as React.KeyboardEvent);

// The event should be processed normally (not ignored)
// We can't easily verify tabCompleteName was called since it's private,
// but the important thing is that the event wasn't ignored
// The test passes if no errors are thrown and the event is handled
});
});

function generateMockDataTransferForString(string: string): DataTransfer {
Expand Down
Loading