mirror of https://github.com/vector-im/riot-web
Display composer only after `isEncrypted` is computed
parent
90cd420f4a
commit
4d4e037391
|
@ -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>;
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -272,6 +277,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
|||
{ leading: true, trailing: false },
|
||||
);
|
||||
}
|
||||
this.setState({ isReady: true });
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: ISendMessageComposerProps): void {
|
||||
|
@ -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 (
|
||||
|
|
|
@ -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,
|
||||
|
@ -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", () => {
|
||||
|
@ -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"));
|
||||
});
|
||||
|
@ -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");
|
||||
});
|
||||
|
||||
|
@ -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", {});
|
||||
});
|
||||
|
@ -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, {});
|
||||
});
|
||||
|
@ -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, {});
|
||||
});
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
};
|
||||
|
@ -393,7 +395,7 @@ describe("MessageComposer", () => {
|
|||
|
||||
describe("when clicking start a voice message", () => {
|
||||
beforeEach(async () => {
|
||||
wrapAndRender({ room });
|
||||
await wrapAndRender({ room });
|
||||
await startVoiceMessage();
|
||||
await flushPromises();
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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"));
|
||||
});
|
||||
|
@ -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 () => {
|
||||
|
@ -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";
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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" });
|
||||
|
@ -458,7 +461,7 @@ 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);
|
||||
|
@ -466,7 +469,7 @@ describe("<SendMessageComposer/>", () => {
|
|||
);
|
||||
|
||||
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" });
|
||||
|
@ -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" });
|
||||
|
@ -516,7 +519,7 @@ 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);
|
||||
|
@ -524,7 +527,7 @@ describe("<SendMessageComposer/>", () => {
|
|||
);
|
||||
|
||||
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
||||
const { container } = getComponent();
|
||||
const { container } = await getComponent();
|
||||
|
||||
addTextToComposer(container, "🎉");
|
||||
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" });
|
||||
|
@ -538,7 +541,7 @@ 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);
|
||||
|
@ -546,7 +549,7 @@ describe("<SendMessageComposer/>", () => {
|
|||
);
|
||||
|
||||
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
||||
const { container } = getComponent({
|
||||
const { container } = await getComponent({
|
||||
relation: {
|
||||
rel_type: "m.thread",
|
||||
event_id: "$yolo",
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue