Skip to content

Commit

Permalink
Display composer only after isEncrypted is computed
Browse files Browse the repository at this point in the history
  • Loading branch information
florianduros committed Nov 19, 2024
1 parent 90cd420 commit 4d4e037
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 52 deletions.
10 changes: 9 additions & 1 deletion src/components/views/rooms/SendMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,11 @@ interface ISendMessageComposerProps extends MatrixClientProps {
toggleStickerPickerOpen: () => void;
}

export class SendMessageComposer extends React.Component<ISendMessageComposerProps> {
interface SendMessageComposerState {
isReady: boolean;
}

export class SendMessageComposer extends React.Component<ISendMessageComposerProps, SendMessageComposerState> {
public static contextType = RoomContext;
public declare context: React.ContextType<typeof RoomContext>;

Expand All @@ -257,6 +261,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
const parts = this.restoreStoredEditorState(partCreator) || [];
this.model = new EditorModel(parts, partCreator);
this.sendHistoryManager = new SendHistoryManager(this.props.room.roomId, "mx_cider_history_");
this.state = { isReady: false };
}

public async componentDidMount(): Promise<void> {
Expand All @@ -272,6 +277,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
{ leading: true, trailing: false },
);
}
this.setState({ isReady: true });
}

public componentDidUpdate(prevProps: ISendMessageComposerProps): void {
Expand Down Expand Up @@ -746,6 +752,8 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
};

public render(): React.ReactNode {
if (!this.state.isReady) return null;

const threadId =
this.props.relation?.rel_type === THREAD_RELATION_TYPE.name ? this.props.relation.event_id : undefined;
return (
Expand Down
73 changes: 41 additions & 32 deletions test/unit-tests/components/views/rooms/MessageComposer-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,19 +100,19 @@ describe("MessageComposer", () => {
describe("for a Room", () => {
const room = mkStubRoom("!roomId:server", "Room 1", cli);

it("Renders a SendMessageComposer and MessageComposerButtons by default", () => {
wrapAndRender({ room });
it("Renders a SendMessageComposer and MessageComposerButtons by default", async () => {
await wrapAndRender({ room });
expect(screen.getByLabelText("Send a message…")).toBeInTheDocument();
});

it("Does not render a SendMessageComposer or MessageComposerButtons when user has no permission", () => {
wrapAndRender({ room }, false);
it("Does not render a SendMessageComposer or MessageComposerButtons when user has no permission", async () => {
await wrapAndRender({ room }, false);
expect(screen.queryByLabelText("Send a message…")).not.toBeInTheDocument();
expect(screen.getByText("You do not have permission to post to this room")).toBeInTheDocument();
});

it("Does not render a SendMessageComposer or MessageComposerButtons when room is tombstoned", () => {
wrapAndRender(
it("Does not render a SendMessageComposer or MessageComposerButtons when room is tombstoned", async () => {
await wrapAndRender(
{ room },
true,
false,
Expand All @@ -135,15 +135,17 @@ describe("MessageComposer", () => {
let roomContext: IRoomState;
let resizeNotifier: ResizeNotifier;

beforeEach(() => {
beforeEach(async () => {
jest.useFakeTimers();
resizeNotifier = {
notifyTimelineHeightChanged: jest.fn(),
} as unknown as ResizeNotifier;
roomContext = wrapAndRender({
room,
resizeNotifier,
}).roomContext;
roomContext = (
await wrapAndRender({
room,
resizeNotifier,
})
).roomContext;
});

it("should call notifyTimelineHeightChanged() for the same context", () => {
Expand Down Expand Up @@ -185,8 +187,8 @@ describe("MessageComposer", () => {
[true, false].forEach((value: boolean) => {
describe(`when ${setting} = ${value}`, () => {
beforeEach(async () => {
SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value);
wrapAndRender({ room });
await SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value);
await wrapAndRender({ room });
await act(async () => {
await userEvent.click(screen.getByLabelText("More options"));
});
Expand Down Expand Up @@ -230,14 +232,14 @@ describe("MessageComposer", () => {
});
});

it("should not render the send button", () => {
wrapAndRender({ room });
it("should not render the send button", async () => {
await wrapAndRender({ room });
expect(screen.queryByLabelText("Send message")).not.toBeInTheDocument();
});

describe("when a message has been entered", () => {
beforeEach(async () => {
const renderResult = wrapAndRender({ room }).renderResult;
const renderResult = (await wrapAndRender({ room })).renderResult;
await addTextToComposerRTL(renderResult, "Hello");
});

Expand All @@ -259,7 +261,7 @@ describe("MessageComposer", () => {

describe("when a non-resize event occurred in UIStore", () => {
beforeEach(async () => {
wrapAndRender({ room });
await wrapAndRender({ room });
await openStickerPicker();
resizeCallback("test", {});
});
Expand All @@ -271,7 +273,7 @@ describe("MessageComposer", () => {

describe("when a resize to narrow event occurred in UIStore", () => {
beforeEach(async () => {
wrapAndRender({ room }, true, true);
await wrapAndRender({ room }, true, true);
await openStickerPicker();
resizeCallback(UI_EVENTS.Resize, {});
});
Expand All @@ -293,7 +295,7 @@ describe("MessageComposer", () => {

describe("when a resize to non-narrow event occurred in UIStore", () => {
beforeEach(async () => {
wrapAndRender({ room }, true, false);
await wrapAndRender({ room }, true, false);
await openStickerPicker();
resizeCallback(UI_EVENTS.Resize, {});
});
Expand All @@ -315,13 +317,13 @@ describe("MessageComposer", () => {
});

describe("when not replying to an event", () => {
it("should pass the expected placeholder to SendMessageComposer", () => {
wrapAndRender({ room });
it("should pass the expected placeholder to SendMessageComposer", async () => {
await wrapAndRender({ room });
expect(screen.getByLabelText("Send a message…")).toBeInTheDocument();
});

it("and an e2e status it should pass the expected placeholder to SendMessageComposer", () => {
wrapAndRender({
it("and an e2e status it should pass the expected placeholder to SendMessageComposer", async () => {
await wrapAndRender({
room,
e2eStatus: E2EStatus.Normal,
});
Expand All @@ -334,8 +336,8 @@ describe("MessageComposer", () => {
let props: Partial<React.ComponentProps<typeof MessageComposer>>;

const checkPlaceholder = (expected: string) => {
it("should pass the expected placeholder to SendMessageComposer", () => {
wrapAndRender(props);
it("should pass the expected placeholder to SendMessageComposer", async () => {
await wrapAndRender(props);
expect(screen.getByLabelText(expected)).toBeInTheDocument();
});
};
Expand Down Expand Up @@ -393,7 +395,7 @@ describe("MessageComposer", () => {

describe("when clicking start a voice message", () => {
beforeEach(async () => {
wrapAndRender({ room });
await wrapAndRender({ room });
await startVoiceMessage();
await flushPromises();
});
Expand All @@ -406,7 +408,7 @@ describe("MessageComposer", () => {
describe("when recording a voice broadcast and trying to start a voice message", () => {
beforeEach(async () => {
setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Started);
wrapAndRender({ room });
await wrapAndRender({ room });
await startVoiceMessage();
await waitEnoughCyclesForModal();
});
Expand All @@ -420,7 +422,7 @@ describe("MessageComposer", () => {
describe("when there is a stopped voice broadcast recording and trying to start a voice message", () => {
beforeEach(async () => {
setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Stopped);
wrapAndRender({ room });
await wrapAndRender({ room });
await startVoiceMessage();
await waitEnoughCyclesForModal();
});
Expand All @@ -436,7 +438,7 @@ describe("MessageComposer", () => {
const localRoom = new LocalRoom("!room:example.com", cli, cli.getUserId()!);

it("should not show the stickers button", async () => {
wrapAndRender({ room: localRoom });
await wrapAndRender({ room: localRoom });
await act(async () => {
await userEvent.click(screen.getByLabelText("More options"));
});
Expand All @@ -448,7 +450,7 @@ describe("MessageComposer", () => {
const room = mkStubRoom("!roomId:server", "Room 1", cli);
const messageText = "Test Text";
await SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true);
const { renderResult, rawComponent } = wrapAndRender({ room });
const { renderResult, rawComponent } = await wrapAndRender({ room }, true, false, undefined, true);
const { unmount, rerender } = renderResult;

await act(async () => {
Expand Down Expand Up @@ -490,11 +492,12 @@ describe("MessageComposer", () => {
}, 10000);
});

function wrapAndRender(
async function wrapAndRender(
props: Partial<React.ComponentProps<typeof MessageComposer>> = {},
canSendMessages = true,
narrow = false,
tombstone?: MatrixEvent,
ignoreWaitForRender = false,
) {
const mockClient = MatrixClientPeg.safeGet();
const roomId = "myroomid";
Expand Down Expand Up @@ -527,9 +530,15 @@ function wrapAndRender(
</RoomContext.Provider>
</MatrixClientContext.Provider>
);

const renderResult = render(getRawComponent(props, roomContext, mockClient));
if (!ignoreWaitForRender && canSendMessages && !tombstone) {
await waitFor(() => expect(renderResult.getByRole("textbox")).toBeInTheDocument());
}

return {
rawComponent: getRawComponent(props, roomContext, mockClient),
renderResult: render(getRawComponent(props, roomContext, mockClient)),
renderResult,
roomContext,
};
}
42 changes: 23 additions & 19 deletions test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx
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 { fireEvent, render, waitFor } from "jest-matrix-react";
import { fireEvent, render, waitFor, screen } from "jest-matrix-react";
import { IContent, MatrixClient, MsgType } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
import userEvent from "@testing-library/user-event";
Expand Down Expand Up @@ -369,12 +369,15 @@ describe("<SendMessageComposer/>", () => {
</RoomContext.Provider>
</MatrixClientContext.Provider>
);
const getComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => {
return render(getRawComponent(props, roomContext, client));
const getComponent = async (props = {}, roomContext = defaultRoomContext, client = mockClient) => {
const renderResult = render(getRawComponent(props, roomContext, client));
// Wait for the composer to be rendered
await waitFor(() => expect(screen.getByRole("textbox")).toBeInTheDocument());
return renderResult;
};

it("renders text and placeholder correctly", () => {
const { container } = getComponent({ placeholder: "placeholder string" });
it("renders text and placeholder correctly", async () => {
const { container } = await getComponent({ placeholder: "placeholder string" });

expect(container.querySelectorAll('[aria-label="placeholder string"]')).toHaveLength(1);

Expand All @@ -383,9 +386,9 @@ describe("<SendMessageComposer/>", () => {
expect(container.textContent).toBe("Test Text");
});

it("correctly persists state to and from localStorage", () => {
it("correctly persists state to and from localStorage", async () => {
const props = { replyToEvent: mockEvent };
const { container, unmount, rerender } = getComponent(props);
const { container, unmount, rerender } = await getComponent(props);

addTextToComposer(container, "Test Text");

Expand All @@ -403,7 +406,7 @@ describe("<SendMessageComposer/>", () => {

// ensure the correct model is re-loaded
rerender(getRawComponent(props));
expect(container.textContent).toBe("Test Text");
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent("Test Text"));
expect(spyDispatcher).toHaveBeenCalledWith({
action: "reply_to_event",
event: mockEvent,
Expand All @@ -417,8 +420,8 @@ describe("<SendMessageComposer/>", () => {
expect(container.textContent).toBe("");
});

it("persists state correctly without replyToEvent onbeforeunload", () => {
const { container } = getComponent();
it("persists state correctly without replyToEvent onbeforeunload", async () => {
const { container } = await getComponent();

addTextToComposer(container, "Hello World");

Expand All @@ -437,7 +440,7 @@ describe("<SendMessageComposer/>", () => {
it("persists to session history upon sending", async () => {
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });

const { container } = getComponent({ replyToEvent: mockEvent });
const { container } = await getComponent({ replyToEvent: mockEvent });

addTextToComposer(container, "This is a message");
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" });
Expand All @@ -458,15 +461,15 @@ describe("<SendMessageComposer/>", () => {
});
});

it("correctly sends a message", () => {
it("correctly sends a message", async () => {
mocked(doMaybeLocalRoomAction).mockImplementation(
<T,>(roomId: string, fn: (actualRoomId: string) => Promise<T>, _client?: MatrixClient) => {
return fn(roomId);
},
);

mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
const { container } = getComponent();
const { container } = await getComponent();

addTextToComposer(container, "test message");
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" });
Expand Down Expand Up @@ -495,7 +498,7 @@ describe("<SendMessageComposer/>", () => {
});

mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
const { container } = getComponent({ replyToEvent });
const { container } = await getComponent({ replyToEvent });

addTextToComposer(container, "/tableflip");
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" });
Expand All @@ -516,15 +519,15 @@ describe("<SendMessageComposer/>", () => {
);
});

it("shows chat effects on message sending", () => {
it("shows chat effects on message sending", async () => {
mocked(doMaybeLocalRoomAction).mockImplementation(
<T,>(roomId: string, fn: (actualRoomId: string) => Promise<T>, _client?: MatrixClient) => {
return fn(roomId);
},
);

mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
const { container } = getComponent();
const { container } = await getComponent();

addTextToComposer(container, "🎉");
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" });
Expand All @@ -538,15 +541,15 @@ describe("<SendMessageComposer/>", () => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: `effects.confetti` });
});

it("not to send chat effects on message sending for threads", () => {
it("not to send chat effects on message sending for threads", async () => {
mocked(doMaybeLocalRoomAction).mockImplementation(
<T,>(roomId: string, fn: (actualRoomId: string) => Promise<T>, _client?: MatrixClient) => {
return fn(roomId);
},
);

mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
const { container } = getComponent({
const { container } = await getComponent({
relation: {
rel_type: "m.thread",
event_id: "$yolo",
Expand Down Expand Up @@ -615,7 +618,8 @@ describe("<SendMessageComposer/>", () => {
<SendMessageComposer room={room} toggleStickerPickerOpen={jest.fn()} />
</MatrixClientContext.Provider>,
);

// Wait for the composer to be rendered
await waitFor(() => expect(screen.getByRole("textbox")).toBeInTheDocument());
const composer = container.querySelector<HTMLDivElement>(".mx_BasicMessageComposer_input")!;

// Does not trigger on keydown as that'll cause false negatives for global shortcuts
Expand Down

0 comments on commit 4d4e037

Please sign in to comment.