You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-11-04 11:51:45 +03:00 
			
		
		
		
	Merge pull request #9879 from matrix-org/gsouquet/threads-forceenablelabsflag
Switch threads on for everyone
This commit is contained in:
		@@ -77,7 +77,7 @@ describe("Polls", () => {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
        cy.enableLabsFeature("feature_threadstable");
 | 
			
		||||
        cy.enableLabsFeature("feature_threadenabled");
 | 
			
		||||
        cy.window().then((win) => {
 | 
			
		||||
            win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ describe("Threads", () => {
 | 
			
		||||
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
        // Default threads to ON for this spec
 | 
			
		||||
        cy.enableLabsFeature("feature_threadstable");
 | 
			
		||||
        cy.enableLabsFeature("feature_threadenabled");
 | 
			
		||||
        cy.window().then((win) => {
 | 
			
		||||
            win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -218,7 +218,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
 | 
			
		||||
        opts.pendingEventOrdering = PendingEventOrdering.Detached;
 | 
			
		||||
        opts.lazyLoadMembers = true;
 | 
			
		||||
        opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
 | 
			
		||||
        opts.experimentalThreadSupport = SettingsStore.getValue("feature_threadstable");
 | 
			
		||||
        opts.experimentalThreadSupport = SettingsStore.getValue("feature_threadenabled");
 | 
			
		||||
 | 
			
		||||
        if (SettingsStore.getValue("feature_sliding_sync")) {
 | 
			
		||||
            const proxyUrl = SettingsStore.getValue("feature_sliding_sync_proxy_url");
 | 
			
		||||
 
 | 
			
		||||
@@ -287,7 +287,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
 | 
			
		||||
        // and we check this in a hot code path. This is also cached in our
 | 
			
		||||
        // RoomContext, however we still need a fallback for roomless MessagePanels.
 | 
			
		||||
        this._showHiddenEvents = SettingsStore.getValue("showHiddenEventsInTimeline");
 | 
			
		||||
        this.threadsEnabled = SettingsStore.getValue("feature_threadstable");
 | 
			
		||||
        this.threadsEnabled = SettingsStore.getValue("feature_threadenabled");
 | 
			
		||||
 | 
			
		||||
        this.showTypingNotificationsWatcherRef = SettingsStore.watchSetting(
 | 
			
		||||
            "showTypingNotifications",
 | 
			
		||||
 
 | 
			
		||||
@@ -100,7 +100,7 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
 | 
			
		||||
                                return b.length - a.length;
 | 
			
		||||
                            });
 | 
			
		||||
 | 
			
		||||
                            if (SettingsStore.getValue("feature_threadstable")) {
 | 
			
		||||
                            if (SettingsStore.getValue("feature_threadenabled")) {
 | 
			
		||||
                                // Process all thread roots returned in this batch of search results
 | 
			
		||||
                                // XXX: This won't work for results coming from Seshat which won't include the bundled relationship
 | 
			
		||||
                                for (const result of results.results) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1182,7 +1182,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
 | 
			
		||||
        CHAT_EFFECTS.forEach((effect) => {
 | 
			
		||||
            if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) {
 | 
			
		||||
                // For initial threads launch, chat effects are disabled see #19731
 | 
			
		||||
                if (!SettingsStore.getValue("feature_threadstable") || !ev.isRelation(THREAD_RELATION_TYPE.name)) {
 | 
			
		||||
                if (!SettingsStore.getValue("feature_threadenabled") || !ev.isRelation(THREAD_RELATION_TYPE.name)) {
 | 
			
		||||
                    dis.dispatch({ action: `effects.${effect.command}` });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -249,7 +249,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
 | 
			
		||||
    const openFeedback = shouldShowFeedback()
 | 
			
		||||
        ? () => {
 | 
			
		||||
              Modal.createDialog(BetaFeedbackDialog, {
 | 
			
		||||
                  featureId: "feature_threadstable",
 | 
			
		||||
                  featureId: "feature_threadenabled",
 | 
			
		||||
              });
 | 
			
		||||
          }
 | 
			
		||||
        : null;
 | 
			
		||||
 
 | 
			
		||||
@@ -1688,7 +1688,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
 | 
			
		||||
        is very tied to the main room timeline, we are forcing the timeline to
 | 
			
		||||
        send read receipts for threaded events */
 | 
			
		||||
        const isThreadTimeline = this.context.timelineRenderingType === TimelineRenderingType.Thread;
 | 
			
		||||
        if (SettingsStore.getValue("feature_threadstable") && isThreadTimeline) {
 | 
			
		||||
        if (SettingsStore.getValue("feature_threadenabled") && isThreadTimeline) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        const index = this.state.events.findIndex((ev) => ev.getId() === evId);
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,7 @@ const ReplyInThreadButton = ({ mxEvent, closeMenu }: IReplyInThreadButton) => {
 | 
			
		||||
    if (Boolean(relationType) && relationType !== RelationType.Thread) return null;
 | 
			
		||||
 | 
			
		||||
    const onClick = (): void => {
 | 
			
		||||
        if (!SettingsStore.getValue("feature_threadstable")) {
 | 
			
		||||
        if (!SettingsStore.getValue("feature_threadenabled")) {
 | 
			
		||||
            dis.dispatch({
 | 
			
		||||
                action: Action.ViewUserSettings,
 | 
			
		||||
                initialTabId: UserTab.Labs,
 | 
			
		||||
@@ -640,7 +640,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
 | 
			
		||||
            rightClick &&
 | 
			
		||||
            contentActionable &&
 | 
			
		||||
            canSendMessages &&
 | 
			
		||||
            SettingsStore.getValue("feature_threadstable") &&
 | 
			
		||||
            SettingsStore.getValue("feature_threadenabled") &&
 | 
			
		||||
            Thread.hasServerSideSupport &&
 | 
			
		||||
            timelineRenderingType !== TimelineRenderingType.Thread
 | 
			
		||||
        ) {
 | 
			
		||||
 
 | 
			
		||||
@@ -204,7 +204,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
 | 
			
		||||
 | 
			
		||||
    const relationType = mxEvent?.getRelation()?.rel_type;
 | 
			
		||||
    const hasARelation = !!relationType && relationType !== RelationType.Thread;
 | 
			
		||||
    const threadsEnabled = SettingsStore.getValue("feature_threadstable");
 | 
			
		||||
    const threadsEnabled = SettingsStore.getValue("feature_threadenabled");
 | 
			
		||||
 | 
			
		||||
    if (!threadsEnabled && !Thread.hasServerSideSupport) {
 | 
			
		||||
        // hide the prompt if the user would only have degraded mode
 | 
			
		||||
@@ -216,7 +216,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        if (!SettingsStore.getValue("feature_threadstable")) {
 | 
			
		||||
        if (!SettingsStore.getValue("feature_threadenabled")) {
 | 
			
		||||
            dis.dispatch({
 | 
			
		||||
                action: Action.ViewUserSettings,
 | 
			
		||||
                initialTabId: UserTab.Labs,
 | 
			
		||||
@@ -252,7 +252,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {!hasARelation && (
 | 
			
		||||
                        <div className="mx_Tooltip_sub">
 | 
			
		||||
                            {SettingsStore.getValue("feature_threadstable")
 | 
			
		||||
                            {SettingsStore.getValue("feature_threadenabled")
 | 
			
		||||
                                ? _t("Beta feature")
 | 
			
		||||
                                : _t("Beta feature. Click to learn more.")}
 | 
			
		||||
                        </div>
 | 
			
		||||
@@ -548,7 +548,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            } else if (
 | 
			
		||||
                SettingsStore.getValue("feature_threadstable") &&
 | 
			
		||||
                SettingsStore.getValue("feature_threadenabled") &&
 | 
			
		||||
                // Show thread icon even for deleted messages, but only within main timeline
 | 
			
		||||
                this.context.timelineRenderingType === TimelineRenderingType.Room &&
 | 
			
		||||
                this.props.mxEvent.getThread()
 | 
			
		||||
 
 | 
			
		||||
@@ -324,7 +324,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
 | 
			
		||||
        );
 | 
			
		||||
        rightPanelPhaseButtons.set(
 | 
			
		||||
            RightPanelPhases.ThreadPanel,
 | 
			
		||||
            SettingsStore.getValue("feature_threadstable") ? (
 | 
			
		||||
            SettingsStore.getValue("feature_threadenabled") ? (
 | 
			
		||||
                <HeaderButton
 | 
			
		||||
                    key={RightPanelPhases.ThreadPanel}
 | 
			
		||||
                    name="threadsButton"
 | 
			
		||||
 
 | 
			
		||||
@@ -387,7 +387,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (SettingsStore.getValue("feature_threadstable")) {
 | 
			
		||||
        if (SettingsStore.getValue("feature_threadenabled")) {
 | 
			
		||||
            this.props.mxEvent.on(ThreadEvent.Update, this.updateThread);
 | 
			
		||||
 | 
			
		||||
            if (this.thread && !this.supportsThreadNotifications) {
 | 
			
		||||
@@ -470,7 +470,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
 | 
			
		||||
        if (this.props.showReactions) {
 | 
			
		||||
            this.props.mxEvent.removeListener(MatrixEventEvent.RelationsCreated, this.onReactionsCreated);
 | 
			
		||||
        }
 | 
			
		||||
        if (SettingsStore.getValue("feature_threadstable")) {
 | 
			
		||||
        if (SettingsStore.getValue("feature_threadenabled")) {
 | 
			
		||||
            this.props.mxEvent.off(ThreadEvent.Update, this.updateThread);
 | 
			
		||||
        }
 | 
			
		||||
        this.threadState?.off(NotificationStateEvents.Update, this.onThreadStateUpdate);
 | 
			
		||||
@@ -501,7 +501,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private get thread(): Thread | null {
 | 
			
		||||
        if (!SettingsStore.getValue("feature_threadstable")) {
 | 
			
		||||
        if (!SettingsStore.getValue("feature_threadenabled")) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ export default class SearchResultTile extends React.Component<IProps> {
 | 
			
		||||
        const layout = SettingsStore.getValue("layout");
 | 
			
		||||
        const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
 | 
			
		||||
        const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps");
 | 
			
		||||
        const threadsEnabled = SettingsStore.getValue("feature_threadstable");
 | 
			
		||||
        const threadsEnabled = SettingsStore.getValue("feature_threadenabled");
 | 
			
		||||
 | 
			
		||||
        for (let j = 0; j < timeline.length; j++) {
 | 
			
		||||
            const mxEv = timeline[j];
 | 
			
		||||
 
 | 
			
		||||
@@ -436,7 +436,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
 | 
			
		||||
                    // For initial threads launch, chat effects are disabled
 | 
			
		||||
                    // see #19731
 | 
			
		||||
                    const isNotThread = this.props.relation?.rel_type !== THREAD_RELATION_TYPE.name;
 | 
			
		||||
                    if (!SettingsStore.getValue("feature_threadstable") || isNotThread) {
 | 
			
		||||
                    if (!SettingsStore.getValue("feature_threadenabled") || isNotThread) {
 | 
			
		||||
                        dis.dispatch({ action: `effects.${effect.command}` });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -112,7 +112,7 @@ export async function sendMessage(
 | 
			
		||||
            // For initial threads launch, chat effects are disabled
 | 
			
		||||
            // see #19731
 | 
			
		||||
            const isNotThread = relation?.rel_type !== THREAD_RELATION_TYPE.name;
 | 
			
		||||
            if (!SettingsStore.getValue("feature_threadstable") || isNotThread) {
 | 
			
		||||
            if (!SettingsStore.getValue("feature_threadenabled") || isNotThread) {
 | 
			
		||||
                dis.dispatch({ action: `effects.${effect.command}` });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -257,13 +257,13 @@ export const SETTINGS: { [setting: string]: ISetting } = {
 | 
			
		||||
        supportedLevels: LEVELS_FEATURE,
 | 
			
		||||
        default: false,
 | 
			
		||||
    },
 | 
			
		||||
    "feature_threadstable": {
 | 
			
		||||
    "feature_threadenabled": {
 | 
			
		||||
        isFeature: true,
 | 
			
		||||
        labsGroup: LabGroup.Messaging,
 | 
			
		||||
        controller: new ThreadBetaController(),
 | 
			
		||||
        displayName: _td("Threaded messages"),
 | 
			
		||||
        supportedLevels: LEVELS_FEATURE,
 | 
			
		||||
        default: false,
 | 
			
		||||
        default: true,
 | 
			
		||||
        betaInfo: {
 | 
			
		||||
            title: _td("Threaded messages"),
 | 
			
		||||
            caption: () => (
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ export default class TypingStore {
 | 
			
		||||
        if (SettingsStore.getValue("lowBandwidth")) return;
 | 
			
		||||
        // Disable typing notification for threads for the initial launch
 | 
			
		||||
        // before we figure out a better user experience for them
 | 
			
		||||
        if (SettingsStore.getValue("feature_threadstable") && threadId) return;
 | 
			
		||||
        if (SettingsStore.getValue("feature_threadenabled") && threadId) return;
 | 
			
		||||
 | 
			
		||||
        let currentTyping = this.typingStates[roomId];
 | 
			
		||||
        if ((!isTyping && !currentTyping) || (currentTyping && currentTyping.isTyping === isTyping)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -278,10 +278,10 @@ export default class RightPanelStore extends ReadyWatchingStore {
 | 
			
		||||
        // (A nicer fix could be to indicate, that the right panel is loading if there is missing state data and re-emit if the data is available)
 | 
			
		||||
        switch (card.phase) {
 | 
			
		||||
            case RightPanelPhases.ThreadPanel:
 | 
			
		||||
                if (!SettingsStore.getValue("feature_threadstable")) return false;
 | 
			
		||||
                if (!SettingsStore.getValue("feature_threadenabled")) return false;
 | 
			
		||||
                break;
 | 
			
		||||
            case RightPanelPhases.ThreadView:
 | 
			
		||||
                if (!SettingsStore.getValue("feature_threadstable")) return false;
 | 
			
		||||
                if (!SettingsStore.getValue("feature_threadenabled")) return false;
 | 
			
		||||
                if (!card.state.threadHeadEvent) {
 | 
			
		||||
                    logger.warn("removed card from right panel because of missing threadHeadEvent in card state");
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -236,7 +236,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
 | 
			
		||||
                        // For initial threads launch, chat effects are disabled
 | 
			
		||||
                        // see #19731
 | 
			
		||||
                        const isNotThread = content["m.relates_to"].rel_type !== THREAD_RELATION_TYPE.name;
 | 
			
		||||
                        if (!SettingsStore.getValue("feature_threadstable") || isNotThread) {
 | 
			
		||||
                        if (!SettingsStore.getValue("feature_threadenabled") || isNotThread) {
 | 
			
		||||
                            dis.dispatch({ action: `effects.${effect.command}` });
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -176,7 +176,7 @@ export function makeReplyMixIn(ev?: MatrixEvent): IEventRelation {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (ev.threadRootId) {
 | 
			
		||||
        if (SettingsStore.getValue("feature_threadstable")) {
 | 
			
		||||
        if (SettingsStore.getValue("feature_threadenabled")) {
 | 
			
		||||
            mixin.is_falling_back = false;
 | 
			
		||||
        } else {
 | 
			
		||||
            // Clients that do not offer a threading UI should behave as follows when replying, for best interaction
 | 
			
		||||
@@ -203,7 +203,7 @@ export function shouldDisplayReply(event: MatrixEvent): boolean {
 | 
			
		||||
 | 
			
		||||
    const relation = event.getRelation();
 | 
			
		||||
    if (
 | 
			
		||||
        SettingsStore.getValue("feature_threadstable") &&
 | 
			
		||||
        SettingsStore.getValue("feature_threadenabled") &&
 | 
			
		||||
        relation?.rel_type === THREAD_RELATION_TYPE.name &&
 | 
			
		||||
        relation?.is_falling_back
 | 
			
		||||
    ) {
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ export default class HTMLExporter extends Exporter {
 | 
			
		||||
        this.mediaOmitText = !this.exportOptions.attachmentsIncluded
 | 
			
		||||
            ? _t("Media omitted")
 | 
			
		||||
            : _t("Media omitted - file size limit exceeded");
 | 
			
		||||
        this.threadsEnabled = SettingsStore.getValue("feature_threadstable");
 | 
			
		||||
        this.threadsEnabled = SettingsStore.getValue("feature_threadenabled");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected async getRoomAvatar() {
 | 
			
		||||
 
 | 
			
		||||
@@ -174,7 +174,7 @@ describe("TimelinePanel", () => {
 | 
			
		||||
            const getValueCopy = SettingsStore.getValue;
 | 
			
		||||
            SettingsStore.getValue = jest.fn().mockImplementation((name: string) => {
 | 
			
		||||
                if (name === "sendReadReceipts") return true;
 | 
			
		||||
                if (name === "feature_threadstable") return false;
 | 
			
		||||
                if (name === "feature_threadenabled") return false;
 | 
			
		||||
                return getValueCopy(name);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@@ -188,7 +188,7 @@ describe("TimelinePanel", () => {
 | 
			
		||||
            const getValueCopy = SettingsStore.getValue;
 | 
			
		||||
            SettingsStore.getValue = jest.fn().mockImplementation((name: string) => {
 | 
			
		||||
                if (name === "sendReadReceipts") return false;
 | 
			
		||||
                if (name === "feature_threadstable") return false;
 | 
			
		||||
                if (name === "feature_threadenabled") return false;
 | 
			
		||||
                return getValueCopy(name);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@@ -365,7 +365,7 @@ describe("TimelinePanel", () => {
 | 
			
		||||
            client.supportsExperimentalThreads = () => true;
 | 
			
		||||
            const getValueCopy = SettingsStore.getValue;
 | 
			
		||||
            SettingsStore.getValue = jest.fn().mockImplementation((name: string) => {
 | 
			
		||||
                if (name === "feature_threadstable") return true;
 | 
			
		||||
                if (name === "feature_threadenabled") return true;
 | 
			
		||||
                return getValueCopy(name);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@@ -520,7 +520,7 @@ describe("TimelinePanel", () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("renders when the last message is an undecryptable thread root", async () => {
 | 
			
		||||
        jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => name === "feature_threadstable");
 | 
			
		||||
        jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => name === "feature_threadenabled");
 | 
			
		||||
 | 
			
		||||
        const client = MatrixClientPeg.get();
 | 
			
		||||
        client.isRoomEncrypted = () => true;
 | 
			
		||||
 
 | 
			
		||||
@@ -389,7 +389,7 @@ describe("<MessageActionBar />", () => {
 | 
			
		||||
        describe("when threads feature is not enabled", () => {
 | 
			
		||||
            beforeEach(() => {
 | 
			
		||||
                jest.spyOn(SettingsStore, "getValue").mockImplementation(
 | 
			
		||||
                    (setting) => setting !== "feature_threadstable",
 | 
			
		||||
                    (setting) => setting !== "feature_threadenabled",
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@@ -435,7 +435,7 @@ describe("<MessageActionBar />", () => {
 | 
			
		||||
        describe("when threads feature is enabled", () => {
 | 
			
		||||
            beforeEach(() => {
 | 
			
		||||
                jest.spyOn(SettingsStore, "getValue").mockImplementation(
 | 
			
		||||
                    (setting) => setting === "feature_threadstable",
 | 
			
		||||
                    (setting) => setting === "feature_threadenabled",
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ describe("RoomHeaderButtons-test.tsx", function () {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
 | 
			
		||||
            if (name === "feature_threadstable") return true;
 | 
			
		||||
            if (name === "feature_threadenabled") return true;
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,7 @@ describe("EventTile", () => {
 | 
			
		||||
 | 
			
		||||
        jest.spyOn(client, "getRoom").mockReturnValue(room);
 | 
			
		||||
        jest.spyOn(client, "decryptEventIfNeeded").mockResolvedValue();
 | 
			
		||||
        jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => name === "feature_threadstable");
 | 
			
		||||
        jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => name === "feature_threadenabled");
 | 
			
		||||
 | 
			
		||||
        mxEvent = mkMessage({
 | 
			
		||||
            room: room.roomId,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user