Add Reply in thread
button to the right-click message context-menu (#9004)
This commit is contained in:
parent
dfa844a035
commit
787ace9dc5
13 changed files with 236 additions and 56 deletions
|
@ -212,4 +212,34 @@ describe("Threads", () => {
|
|||
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content")
|
||||
.should("contain", "I'm very good thanks :)");
|
||||
});
|
||||
|
||||
it("right panel behaves correctly", () => {
|
||||
// Create room
|
||||
let roomId: string;
|
||||
cy.createRoom({}).then(_roomId => {
|
||||
roomId = _roomId;
|
||||
cy.visit("/#/room/" + roomId);
|
||||
});
|
||||
// Send message
|
||||
cy.get(".mx_RoomView_body .mx_BasicMessageComposer_input").type("Hello Mr. Bot{enter}");
|
||||
|
||||
// Create thread
|
||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||
.realHover().find(".mx_MessageActionBar_threadButton").click();
|
||||
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
|
||||
|
||||
// Send message to thread
|
||||
cy.get(".mx_BaseCard .mx_BasicMessageComposer_input").type("Hello Mr. User{enter}");
|
||||
cy.get(".mx_BaseCard .mx_EventTile").should("contain", "Hello Mr. User");
|
||||
|
||||
// Close thread
|
||||
cy.get(".mx_BaseCard_close").click();
|
||||
|
||||
// Open existing thread
|
||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||
.realHover().find(".mx_MessageActionBar_threadButton").click();
|
||||
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
|
||||
cy.get(".mx_BaseCard .mx_EventTile").should("contain", "Hello Mr. Bot");
|
||||
cy.get(".mx_BaseCard .mx_EventTile").should("contain", "Hello Mr. User");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -107,6 +107,10 @@ limitations under the License.
|
|||
mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg');
|
||||
}
|
||||
|
||||
.mx_MessageContextMenu_iconReplyInThread::before {
|
||||
mask-image: url('$(res)/img/element-icons/message/thread.svg');
|
||||
}
|
||||
|
||||
.mx_MessageContextMenu_iconReact::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg');
|
||||
}
|
||||
|
|
|
@ -130,6 +130,10 @@ import { SnakedObject } from "../../utils/SnakedObject";
|
|||
import { leaveRoomBehaviour } from "../../utils/leave-behaviour";
|
||||
import VideoChannelStore from "../../stores/VideoChannelStore";
|
||||
import { IRoomStateEventsActionPayload } from "../../actions/MatrixActionCreators";
|
||||
import { ShowThreadPayload } from "../../dispatcher/payloads/ShowThreadPayload";
|
||||
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
|
||||
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
||||
import { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import { UseCaseSelection } from '../views/elements/UseCaseSelection';
|
||||
import { ValidatedServerConfig } from '../../utils/ValidatedServerConfig';
|
||||
import { isLocalRoom } from '../../utils/localRoom/isLocalRoom';
|
||||
|
@ -803,6 +807,41 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
hideAnalyticsToast();
|
||||
SettingsStore.setValue("pseudonymousAnalyticsOptIn", null, SettingLevel.ACCOUNT, false);
|
||||
break;
|
||||
case Action.ShowThread: {
|
||||
const {
|
||||
rootEvent,
|
||||
initialEvent,
|
||||
highlighted,
|
||||
scrollIntoView,
|
||||
push,
|
||||
} = payload as ShowThreadPayload;
|
||||
|
||||
const threadViewCard = {
|
||||
phase: RightPanelPhases.ThreadView,
|
||||
state: {
|
||||
threadHeadEvent: rootEvent,
|
||||
initialEvent: initialEvent,
|
||||
isInitialEventHighlighted: highlighted,
|
||||
initialEventScrollIntoView: scrollIntoView,
|
||||
},
|
||||
};
|
||||
if (push ?? false) {
|
||||
RightPanelStore.instance.pushCard(threadViewCard);
|
||||
} else {
|
||||
RightPanelStore.instance.setCards([
|
||||
{ phase: RightPanelPhases.ThreadPanel },
|
||||
threadViewCard,
|
||||
]);
|
||||
}
|
||||
|
||||
// Focus the composer
|
||||
dis.dispatch({
|
||||
action: Action.FocusSendMessageComposer,
|
||||
context: TimelineRenderingType.Thread,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -97,7 +97,6 @@ import RoomStatusBar from "./RoomStatusBar";
|
|||
import MessageComposer from '../views/rooms/MessageComposer';
|
||||
import JumpToBottomButton from "../views/rooms/JumpToBottomButton";
|
||||
import TopUnreadMessagesBar from "../views/rooms/TopUnreadMessagesBar";
|
||||
import { showThread } from '../../dispatcher/dispatch-actions/threads';
|
||||
import { fetchInitialEvent } from "../../utils/EventUtils";
|
||||
import { ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
|
||||
import AppsDrawer from '../views/rooms/AppsDrawer';
|
||||
|
@ -111,6 +110,7 @@ import FileDropTarget from './FileDropTarget';
|
|||
import Measured from '../views/elements/Measured';
|
||||
import { FocusComposerPayload } from '../../dispatcher/payloads/FocusComposerPayload';
|
||||
import { haveRendererForEvent } from "../../events/EventTileFactory";
|
||||
import { ShowThreadPayload } from "../../dispatcher/payloads/ShowThreadPayload";
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function(msg: string) {};
|
||||
|
@ -452,7 +452,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
const thread = initialEvent?.getThread();
|
||||
if (thread && !initialEvent?.isThreadRoot) {
|
||||
showThread({
|
||||
dis.dispatch<ShowThreadPayload>({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: thread.rootEvent,
|
||||
initialEvent,
|
||||
highlighted: RoomViewStore.instance.isInitialEventHighlighted(),
|
||||
|
@ -464,7 +465,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
newState.initialEventScrollIntoView = RoomViewStore.instance.initialEventScrollIntoView();
|
||||
|
||||
if (thread && initialEvent?.isThreadRoot) {
|
||||
showThread({
|
||||
dis.dispatch<ShowThreadPayload>({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: thread.rootEvent,
|
||||
initialEvent,
|
||||
highlighted: RoomViewStore.instance.isInitialEventHighlighted(),
|
||||
|
|
|
@ -16,12 +16,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef } from 'react';
|
||||
import React, { createRef, useContext } from 'react';
|
||||
import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { Relations } from 'matrix-js-sdk/src/models/relations';
|
||||
import { RoomMemberEvent } from "matrix-js-sdk/src/models/room-member";
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
import { Thread } from "matrix-js-sdk/src/models/thread";
|
||||
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
|
@ -58,6 +59,59 @@ import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenR
|
|||
import { createMapSiteLinkFromEvent } from '../../../utils/location';
|
||||
import { getForwardableEvent } from '../../../events/forward/getForwardableEvent';
|
||||
import { getShareableLocationEvent } from '../../../events/location/getShareableLocationEvent';
|
||||
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
|
||||
import { CardContext } from "../right_panel/context";
|
||||
import { UserTab } from "../dialogs/UserTab";
|
||||
|
||||
interface IReplyInThreadButton {
|
||||
mxEvent: MatrixEvent;
|
||||
closeMenu: () => void;
|
||||
}
|
||||
|
||||
const ReplyInThreadButton = ({ mxEvent, closeMenu }: IReplyInThreadButton) => {
|
||||
const context = useContext(CardContext);
|
||||
const relationType = mxEvent?.getRelation()?.rel_type;
|
||||
|
||||
// Can't create a thread from an event with an existing relation
|
||||
if (Boolean(relationType) && relationType !== RelationType.Thread) return;
|
||||
|
||||
const onClick = (): void => {
|
||||
if (!localStorage.getItem("mx_seen_feature_thread")) {
|
||||
localStorage.setItem("mx_seen_feature_thread", "true");
|
||||
}
|
||||
|
||||
if (!SettingsStore.getValue("feature_thread")) {
|
||||
dis.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Labs,
|
||||
});
|
||||
} else if (mxEvent.getThread() && !mxEvent.isThreadRoot) {
|
||||
dis.dispatch<ShowThreadPayload>({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: mxEvent.getThread().rootEvent,
|
||||
initialEvent: mxEvent,
|
||||
scroll_into_view: true,
|
||||
highlighted: true,
|
||||
push: context.isCard,
|
||||
});
|
||||
} else {
|
||||
dis.dispatch<ShowThreadPayload>({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: mxEvent,
|
||||
push: context.isCard,
|
||||
});
|
||||
}
|
||||
closeMenu();
|
||||
};
|
||||
|
||||
return (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconReplyInThread"
|
||||
label={_t("Reply in thread")}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IProps extends IPosition {
|
||||
chevronFace: ChevronFace;
|
||||
|
@ -582,6 +636,23 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
let replyInThreadButton: JSX.Element;
|
||||
if (
|
||||
rightClick &&
|
||||
contentActionable &&
|
||||
canSendMessages &&
|
||||
SettingsStore.getValue("feature_thread") &&
|
||||
Thread.hasServerSideSupport &&
|
||||
timelineRenderingType !== TimelineRenderingType.Thread
|
||||
) {
|
||||
replyInThreadButton = (
|
||||
<ReplyInThreadButton
|
||||
mxEvent={mxEvent}
|
||||
closeMenu={this.closeMenu}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let reactButton;
|
||||
if (rightClick && contentActionable && canReact) {
|
||||
reactButton = (
|
||||
|
@ -621,6 +692,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
<IconizedContextMenuOptionList>
|
||||
{ reactButton }
|
||||
{ replyButton }
|
||||
{ replyInThreadButton }
|
||||
{ editButton }
|
||||
</IconizedContextMenuOptionList>
|
||||
);
|
||||
|
|
|
@ -25,7 +25,7 @@ import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon';
|
|||
|
||||
import type { Relations } from 'matrix-js-sdk/src/models/relations';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import dis, { defaultDispatcher } from '../../../dispatcher/dispatcher';
|
||||
import ContextMenu, { aboveLeftOf, ContextMenuTooltipButton, useContextMenu } from '../../structures/ContextMenu';
|
||||
import { isContentActionable, canEditContent, editEvent, canCancel } from '../../../utils/EventUtils';
|
||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
|
@ -41,13 +41,13 @@ import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
|||
import ReplyChain from '../elements/ReplyChain';
|
||||
import ReactionPicker from "../emojipicker/ReactionPicker";
|
||||
import { CardContext } from '../right_panel/context';
|
||||
import { showThread } from "../../../dispatcher/dispatch-actions/threads";
|
||||
import { shouldDisplayReply } from '../../../utils/Reply';
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { ALTERNATE_KEY_NAME } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { UserTab } from '../dialogs/UserTab';
|
||||
import { Action } from '../../../dispatcher/actions';
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
|
||||
import useFavouriteMessages from '../../../hooks/useFavouriteMessages';
|
||||
|
||||
interface IOptionsButtonProps {
|
||||
|
@ -191,7 +191,8 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
|
|||
initialTabId: UserTab.Labs,
|
||||
});
|
||||
} else if (mxEvent.getThread() && !mxEvent.isThreadRoot) {
|
||||
showThread({
|
||||
defaultDispatcher.dispatch<ShowThreadPayload>({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: mxEvent.getThread().rootEvent,
|
||||
initialEvent: mxEvent,
|
||||
scroll_into_view: true,
|
||||
|
@ -199,7 +200,8 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
|
|||
push: context.isCard,
|
||||
});
|
||||
} else {
|
||||
showThread({
|
||||
defaultDispatcher.dispatch<ShowThreadPayload>({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: mxEvent,
|
||||
push: context.isCard,
|
||||
});
|
||||
|
|
|
@ -58,7 +58,6 @@ import MessageActionBar from "../messages/MessageActionBar";
|
|||
import ReactionsRow from '../messages/ReactionsRow';
|
||||
import { getEventDisplayInfo } from '../../../utils/EventRenderingUtils';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { showThread } from '../../../dispatcher/dispatch-actions/threads';
|
||||
import { MessagePreviewStore } from '../../../stores/room-list/MessagePreviewStore';
|
||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
||||
|
@ -80,6 +79,7 @@ import { haveRendererForEvent, isMessageEvent, renderTile } from "../../../event
|
|||
import ThreadSummary, { ThreadMessagePreview } from "./ThreadSummary";
|
||||
import { ReadReceiptGroup } from './ReadReceiptGroup';
|
||||
import { useTooltip } from "../../../utils/useTooltip";
|
||||
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
|
||||
import { isLocalRoom } from '../../../utils/localRoom/isLocalRoom';
|
||||
|
||||
export type GetRelationsForEvent = (eventId: string, relationType: string, eventType: string) => Relations;
|
||||
|
@ -1357,7 +1357,11 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
"onMouseEnter": () => this.setState({ hover: true }),
|
||||
"onMouseLeave": () => this.setState({ hover: false }),
|
||||
"onClick": (ev: MouseEvent) => {
|
||||
showThread({ rootEvent: this.props.mxEvent, push: true });
|
||||
dis.dispatch<ShowThreadPayload>({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: this.props.mxEvent,
|
||||
push: true,
|
||||
});
|
||||
const target = ev.currentTarget as HTMLElement;
|
||||
const index = Array.from(target.parentElement.children).indexOf(target);
|
||||
PosthogTrackers.trackInteraction("WebThreadsPanelThreadItem", ev, index);
|
||||
|
|
|
@ -21,7 +21,6 @@ import { IContent, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/model
|
|||
import { _t } from "../../../languageHandler";
|
||||
import { CardContext } from "../right_panel/context";
|
||||
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import { showThread } from "../../../dispatcher/dispatch-actions/threads";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { useTypedEventEmitter, useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
|
@ -29,6 +28,9 @@ import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewSto
|
|||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
|
@ -50,7 +52,8 @@ const ThreadSummary = ({ mxEvent, thread }: IProps) => {
|
|||
<AccessibleButton
|
||||
className="mx_ThreadSummary"
|
||||
onClick={(ev: ButtonEvent) => {
|
||||
showThread({
|
||||
defaultDispatcher.dispatch<ShowThreadPayload>({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: mxEvent,
|
||||
push: cardContext.isCard,
|
||||
});
|
||||
|
|
|
@ -326,4 +326,9 @@ export enum Action {
|
|||
* Fires with the PlatformSetPayload.
|
||||
*/
|
||||
PlatformSet = "platform_set",
|
||||
|
||||
/**
|
||||
* Fired when we want to view a thread, either a new one or an existing one
|
||||
*/
|
||||
ShowThread = "show_thread",
|
||||
}
|
||||
|
|
|
@ -14,46 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
||||
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
|
||||
import dis from "../dispatcher";
|
||||
import { Action } from "../actions";
|
||||
import { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
|
||||
export const showThread = (props: {
|
||||
rootEvent: MatrixEvent;
|
||||
initialEvent?: MatrixEvent;
|
||||
highlighted?: boolean;
|
||||
scroll_into_view?: boolean;
|
||||
push?: boolean;
|
||||
}) => {
|
||||
const push = props.push ?? false;
|
||||
const threadViewCard = {
|
||||
phase: RightPanelPhases.ThreadView,
|
||||
state: {
|
||||
threadHeadEvent: props.rootEvent,
|
||||
initialEvent: props.initialEvent,
|
||||
isInitialEventHighlighted: props.highlighted,
|
||||
initialEventScrollIntoView: props.scroll_into_view,
|
||||
},
|
||||
};
|
||||
if (push) {
|
||||
RightPanelStore.instance.pushCard(threadViewCard);
|
||||
} else {
|
||||
RightPanelStore.instance.setCards([
|
||||
{ phase: RightPanelPhases.ThreadPanel },
|
||||
threadViewCard,
|
||||
]);
|
||||
}
|
||||
|
||||
// Focus the composer
|
||||
dis.dispatch({
|
||||
action: Action.FocusSendMessageComposer,
|
||||
context: TimelineRenderingType.Thread,
|
||||
});
|
||||
};
|
||||
|
||||
export const showThreadPanel = () => {
|
||||
RightPanelStore.instance.setCard({ phase: RightPanelPhases.ThreadPanel });
|
||||
|
|
30
src/dispatcher/payloads/ShowThreadPayload.ts
Normal file
30
src/dispatcher/payloads/ShowThreadPayload.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import { ActionPayload } from "../payloads";
|
||||
import { Action } from "../actions";
|
||||
|
||||
export interface ShowThreadPayload extends ActionPayload {
|
||||
action: Action.ShowThread;
|
||||
|
||||
rootEvent: MatrixEvent;
|
||||
initialEvent?: MatrixEvent;
|
||||
highlighted?: boolean;
|
||||
scrollIntoView?: boolean;
|
||||
push?: boolean;
|
||||
}
|
|
@ -40,6 +40,7 @@ import { makeBeaconEvent, makeBeaconInfoEvent, makeLocationEvent, stubClient } f
|
|||
import dispatcher from '../../../../src/dispatcher/dispatcher';
|
||||
import SettingsStore from '../../../../src/settings/SettingsStore';
|
||||
import { ReadPinsEventId } from '../../../../src/components/views/right_panel/types';
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
|
||||
jest.mock("../../../../src/utils/strings", () => ({
|
||||
copyPlaintext: jest.fn(),
|
||||
|
@ -50,6 +51,7 @@ jest.mock("../../../../src/utils/EventUtils", () => ({
|
|||
...jest.requireActual("../../../../src/utils/EventUtils"),
|
||||
canEditContent: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../../../src/dispatcher/dispatcher');
|
||||
|
||||
const roomId = 'roomid';
|
||||
|
||||
|
@ -461,6 +463,29 @@ describe('MessageContextMenu', () => {
|
|||
const reactButton = menu.find('div[aria-label="View in room"]');
|
||||
expect(reactButton).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('creates a new thread on reply in thread click', () => {
|
||||
const eventContent = MessageEvent.from("hello");
|
||||
const mxEvent = new MatrixEvent(eventContent.serialize());
|
||||
|
||||
Thread.hasServerSideSupport = true;
|
||||
const context = {
|
||||
canSendMessages: true,
|
||||
};
|
||||
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(true);
|
||||
|
||||
const menu = createRightClickMenu(mxEvent, context);
|
||||
|
||||
const replyInThreadButton = menu.find('div[aria-label="Reply in thread"]');
|
||||
expect(replyInThreadButton).toHaveLength(1);
|
||||
replyInThreadButton.simulate("click");
|
||||
|
||||
expect(dispatcher.dispatch).toHaveBeenCalledWith({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: mxEvent,
|
||||
push: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -471,6 +496,10 @@ function createRightClickMenuWithContent(
|
|||
return createMenuWithContent(eventContent, { rightClick: true }, context);
|
||||
}
|
||||
|
||||
function createRightClickMenu(mxEvent: MatrixEvent, context?: Partial<IRoomState>): ReactWrapper {
|
||||
return createMenu(mxEvent, { rightClick: true }, context);
|
||||
}
|
||||
|
||||
function createMenuWithContent(
|
||||
eventContent: ExtensibleEvent,
|
||||
props?: Partial<React.ComponentProps<typeof MessageContextMenu>>,
|
||||
|
|
|
@ -41,12 +41,8 @@ import dispatcher from '../../../../src/dispatcher/dispatcher';
|
|||
import SettingsStore from '../../../../src/settings/SettingsStore';
|
||||
import { Action } from '../../../../src/dispatcher/actions';
|
||||
import { UserTab } from '../../../../src/components/views/dialogs/UserTab';
|
||||
import { showThread } from '../../../../src/dispatcher/dispatch-actions/threads';
|
||||
|
||||
jest.mock('../../../../src/dispatcher/dispatcher');
|
||||
jest.mock('../../../../src/dispatcher/dispatch-actions/threads', () => ({
|
||||
showThread: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('<MessageActionBar />', () => {
|
||||
const userId = '@alice:server.org';
|
||||
|
@ -447,7 +443,8 @@ describe('<MessageActionBar />', () => {
|
|||
fireEvent.click(getByLabelText('Reply in thread'));
|
||||
});
|
||||
|
||||
expect(showThread).toHaveBeenCalledWith({
|
||||
expect(dispatcher.dispatch).toHaveBeenCalledWith({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: alicesMessageEvent,
|
||||
push: false,
|
||||
});
|
||||
|
@ -475,7 +472,8 @@ describe('<MessageActionBar />', () => {
|
|||
fireEvent.click(getByLabelText('Reply in thread'));
|
||||
});
|
||||
|
||||
expect(showThread).toHaveBeenCalledWith({
|
||||
expect(dispatcher.dispatch).toHaveBeenCalledWith({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: alicesMessageEvent,
|
||||
initialEvent: threadReplyEvent,
|
||||
highlighted: true,
|
||||
|
|
Loading…
Reference in a new issue