From d8ac7cf202154548636c0f914a0fa465c7ed6bc6 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 23 Feb 2022 14:03:46 +0000 Subject: [PATCH] Fix thread summary layout for narrow right panel timeline (#7838) --- res/css/views/right_panel/_TimelineCard.scss | 6 ++ res/css/views/rooms/_EventTile.scss | 8 ++- src/components/structures/FilePanel.tsx | 21 +++++- src/components/structures/MessagePanel.tsx | 9 ++- .../structures/NotificationPanel.tsx | 29 +++++++- src/components/structures/RoomView.tsx | 16 ++++- src/components/structures/ThreadPanel.tsx | 23 ++++-- src/components/structures/ThreadView.tsx | 49 ++++++++----- src/components/views/elements/Measured.tsx | 71 +++++++++++++++++++ .../views/right_panel/TimelineCard.tsx | 31 ++++++-- src/components/views/rooms/EventTile.tsx | 12 ++-- .../views/rooms/MessageComposer.tsx | 12 ++-- .../views/rooms/MessageComposerButtons.tsx | 13 ++-- src/contexts/RoomContext.ts | 1 + .../rooms/MessageComposerButtons-test.tsx | 15 ++-- 15 files changed, 248 insertions(+), 68 deletions(-) create mode 100644 src/components/views/elements/Measured.tsx diff --git a/res/css/views/right_panel/_TimelineCard.scss b/res/css/views/right_panel/_TimelineCard.scss index 5acdc1a8e9..2e42aeedfe 100644 --- a/res/css/views/right_panel/_TimelineCard.scss +++ b/res/css/views/right_panel/_TimelineCard.scss @@ -58,6 +58,12 @@ limitations under the License. padding-right: 36px; } + .mx_EventTile:not([data-layout="bubble"]) .mx_ThreadInfo { + margin-left: 36px; + margin-right: 0; + max-width: min(calc(100% - 36px), 600px); + } + .mx_GroupLayout .mx_EventTile > .mx_SenderProfile { margin-left: 36px; } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 88e34ca75f..d37ef79a6f 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -723,7 +723,7 @@ $left-gutter: 64px; .mx_ThreadInfo { min-width: 267px; max-width: min(calc(100% - 64px), 600px); - width: auto; + width: fit-content; height: 40px; position: relative; background-color: $system; @@ -777,6 +777,12 @@ $left-gutter: 64px; } } +.mx_MessagePanel_narrow .mx_ThreadInfo { + min-width: initial; + max-width: initial; + width: initial; +} + .mx_ThreadInfo_content { text-overflow: ellipsis; overflow: hidden; diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx index 9a61341c12..8466c26bac 100644 --- a/src/components/structures/FilePanel.tsx +++ b/src/components/structures/FilePanel.tsx @@ -1,6 +1,6 @@ /* Copyright 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019 - 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. @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { createRef } from 'react'; import { Filter } from 'matrix-js-sdk/src/filter'; import { EventTimelineSet, IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set"; import { Direction } from "matrix-js-sdk/src/models/event-timeline"; @@ -35,6 +35,7 @@ import TimelinePanel from "./TimelinePanel"; import Spinner from "../views/elements/Spinner"; import { Layout } from "../../settings/enums/Layout"; import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext'; +import Measured from '../views/elements/Measured'; interface IProps { roomId: string; @@ -44,6 +45,7 @@ interface IProps { interface IState { timelineSet: EventTimelineSet; + narrow: boolean; } /* @@ -51,14 +53,17 @@ interface IState { */ @replaceableComponent("structures.FilePanel") class FilePanel extends React.Component { + static contextType = RoomContext; + // This is used to track if a decrypted event was a live event and should be // added to the timeline. private decryptingEvents = new Set(); public noRoom: boolean; - static contextType = RoomContext; + private card = createRef(); state = { timelineSet: null, + narrow: false, }; private onRoomTimeline = ( @@ -184,6 +189,10 @@ class FilePanel extends React.Component { } }; + private onMeasurement = (narrow: boolean): void => { + this.setState({ narrow }); + }; + public async updateTimelineSet(roomId: string): Promise { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); @@ -256,12 +265,18 @@ class FilePanel extends React.Component { + { />; } + const classes = classNames(this.props.className, { + "mx_MessagePanel_narrow": this.context.narrow, + }); + return ( { +export default class NotificationPanel extends React.PureComponent { static contextType = RoomContext; + + private card = React.createRef(); + + constructor(props) { + super(props); + + this.state = { + narrow: false, + }; + } + + private onMeasurement = (narrow: boolean): void => { + this.setState({ narrow }); + }; + render() { const emptyState = (

{ _t("You're all caught up") }

@@ -65,8 +85,13 @@ export default class NotificationPanel extends React.PureComponent { return + { content } ; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index b49b02d7b0..8b7f8708e5 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2,7 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018, 2019 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019 - 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. @@ -104,6 +104,7 @@ import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; import { JoinRoomPayload } from "../../dispatcher/payloads/JoinRoomPayload"; import { DoAfterSyncPreparedPayload } from '../../dispatcher/payloads/DoAfterSyncPreparedPayload'; import FileDropTarget from './FileDropTarget'; +import Measured from '../views/elements/Measured'; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -211,6 +212,7 @@ export interface IRoomState { timelineRenderingType: TimelineRenderingType; threadId?: string; liveTimeline?: EventTimeline; + narrow: boolean; } @replaceableComponent("structures.RoomView") @@ -226,6 +228,7 @@ export class RoomView extends React.Component { private roomView = createRef(); private searchResultsPanel = createRef(); private messagePanel: TimelinePanel; + private roomViewBody = createRef(); static contextType = MatrixClientContext; @@ -271,6 +274,7 @@ export class RoomView extends React.Component { mainSplitContentType: MainSplitContentType.Timeline, timelineRenderingType: TimelineRenderingType.Room, liveTimeline: undefined, + narrow: false, }; this.dispatcherRef = dis.register(this.onAction); @@ -1730,6 +1734,10 @@ export class RoomView extends React.Component { TimelineRenderingType.Room, ); + private onMeasurement = (narrow: boolean): void => { + this.setState({ narrow }); + }; + render() { if (!this.state.room) { const loading = !this.state.matrixClientIsReady || this.state.roomLoading || this.state.peekLoading; @@ -2084,6 +2092,10 @@ export class RoomView extends React.Component { // Decide what to show in the main split let mainSplitBody = + { auxPanel }
@@ -2148,7 +2160,7 @@ export class RoomView extends React.Component { excludedRightPanelPhaseButtons={excludedRightPanelPhaseButtons} /> -
+
{ mainSplitBody }
diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index 72b075b9a1..d10b7c8fca 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -1,5 +1,5 @@ /* -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 - 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. @@ -37,6 +37,7 @@ import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext'; import TimelinePanel from './TimelinePanel'; import { Layout } from '../../settings/enums/Layout'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; +import Measured from '../views/elements/Measured'; async function getThreadTimelineSet( client: MatrixClient, @@ -213,12 +214,14 @@ const EmptyThread: React.FC = ({ filterOption, showAllThreads const ThreadPanel: React.FC = ({ roomId, onClose, permalinkCreator }) => { const mxClient = useContext(MatrixClientContext); const roomContext = useContext(RoomContext); - const [filterOption, setFilterOption] = useState(ThreadFilterType.All); - const ref = useRef(); + const timelinePanel = useRef(); + const card = useRef(); + const [filterOption, setFilterOption] = useState(ThreadFilterType.All); const [room, setRoom] = useState(mxClient.getRoom(roomId)); const [threadCount, setThreadCount] = useState(0); const [timelineSet, setTimelineSet] = useState(null); + const [narrow, setNarrow] = useState(false); useEffect(() => { setRoom(mxClient.getRoom(roomId)); @@ -257,7 +260,7 @@ const ThreadPanel: React.FC = ({ roomId, onClose, permalinkCreator }) => } function refreshTimeline() { - if (timelineSet) ref.current.refreshTimeline(); + if (timelineSet) timelinePanel.current.refreshTimeline(); } setThreadCount(room.threads.size); @@ -278,14 +281,15 @@ const ThreadPanel: React.FC = ({ roomId, onClose, permalinkCreator }) => }, [mxClient, room, filterOption]); useEffect(() => { - if (timelineSet) ref.current.refreshTimeline(); - }, [timelineSet, ref]); + if (timelineSet) timelinePanel.current.refreshTimeline(); + }, [timelineSet, timelinePanel]); return ( = ({ roomId, onClose, permalinkCreator }) => className="mx_ThreadPanel" onClose={onClose} withoutScrollContainer={true} + ref={card} > + { timelineSet && ( { public context!: React.ContextType; private dispatcherRef: string; - private timelinePanelRef = createRef(); - private cardRef = createRef(); private readonly layoutWatcherRef: string; + private timelinePanel = createRef(); + private card = createRef(); constructor(props: IProps) { super(props); + this.state = { layout: SettingsStore.getValue("layout"), + narrow: false, }; this.layoutWatcherRef = SettingsStore.watchSetting("layout", null, (...[,,, value]) => @@ -131,7 +136,7 @@ export default class ThreadView extends React.Component { editState: payload.event ? new EditorStateTransfer(payload.event) : null, }, () => { if (payload.event) { - this.timelinePanelRef.current?.scrollToEventIfNeeded(payload.event.getId()); + this.timelinePanel.current?.scrollToEventIfNeeded(payload.event.getId()); } }); break; @@ -181,7 +186,7 @@ export default class ThreadView extends React.Component { if (!thread.initialEventsFetched) { await thread.fetchInitialEvents(); } - this.timelinePanelRef.current?.refreshTimeline(); + this.timelinePanel.current?.refreshTimeline(); }); } }; @@ -209,6 +214,10 @@ export default class ThreadView extends React.Component { } }; + private onMeasurement = (narrow: boolean): void => { + this.setState({ narrow }); + }; + private onKeyDown = (ev: KeyboardEvent) => { let handled = false; @@ -230,15 +239,6 @@ export default class ThreadView extends React.Component { } }; - private renderThreadViewHeader = (): JSX.Element => { - return
- { _t("Thread") } - -
; - }; - private onPaginationRequest = async ( timelineWindow: TimelineWindow | null, direction = Direction.Backward, @@ -284,6 +284,15 @@ export default class ThreadView extends React.Component { }; } + private renderThreadViewHeader = (): JSX.Element => { + return
+ { _t("Thread") } + +
; + }; + public render(): JSX.Element { const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() @@ -303,20 +312,24 @@ export default class ThreadView extends React.Component { timelineRenderingType: TimelineRenderingType.Thread, threadId: this.state.thread?.id, liveTimeline: this.state?.thread?.timelineSet?.getLiveTimeline(), + narrow: this.state.narrow, }}> - + { this.state.thread &&
- + { + private static instanceCount = 0; + private readonly instanceId: number; + + static defaultProps = { + breakpoint: 500, + }; + + constructor(props) { + super(props); + + this.instanceId = Measured.instanceCount++; + } + + componentDidMount() { + UIStore.instance.on(`Measured${this.instanceId}`, this.onResize); + } + + componentDidUpdate(prevProps: Readonly) { + const previous = prevProps.sensor; + const current = this.props.sensor; + if (previous === current) return; + if (previous) { + UIStore.instance.stopTrackingElementDimensions(`Measured${this.instanceId}`); + } + if (current) { + UIStore.instance.trackElementDimensions(`Measured${this.instanceId}`, + this.props.sensor); + } + } + + componentWillUnmount() { + UIStore.instance.off(`Measured${this.instanceId}`, this.onResize); + UIStore.instance.stopTrackingElementDimensions(`Measured${this.instanceId}`); + } + + private onResize = (type: UI_EVENTS, entry: ResizeObserverEntry) => { + if (type !== UI_EVENTS.Resize) return; + this.props.onMeasurement(entry.contentRect.width <= this.props.breakpoint); + }; + + render() { + return null; + } +} diff --git a/src/components/views/right_panel/TimelineCard.tsx b/src/components/views/right_panel/TimelineCard.tsx index b03dfa4deb..72f3d42969 100644 --- a/src/components/views/right_panel/TimelineCard.tsx +++ b/src/components/views/right_panel/TimelineCard.tsx @@ -1,5 +1,5 @@ /* -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 - 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. @@ -42,6 +42,7 @@ import UploadBar from '../../structures/UploadBar'; import SettingsStore from '../../../settings/SettingsStore'; import JumpToBottomButton from '../rooms/JumpToBottomButton'; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import Measured from '../elements/Measured'; interface IProps { room: Room; @@ -55,6 +56,7 @@ interface IProps { showComposer?: boolean; composerRelation?: IEventRelation; } + interface IState { thread?: Thread; editState?: EditorStateTransfer; @@ -63,6 +65,7 @@ interface IState { isInitialEventHighlighted?: boolean; layout: Layout; atEndOfLiveTimeline: boolean; + narrow: boolean; // settings: showReadReceipts?: boolean; @@ -74,7 +77,8 @@ export default class TimelineCard extends React.Component { private dispatcherRef: string; private layoutWatcherRef: string; - private timelinePanelRef: React.RefObject = React.createRef(); + private timelinePanel = React.createRef(); + private card = React.createRef(); private roomStoreToken: EventSubscription; private readReceiptsSettingWatcher: string; @@ -84,6 +88,7 @@ export default class TimelineCard extends React.Component { showReadReceipts: SettingsStore.getValue("showReadReceipts", props.room.roomId), layout: SettingsStore.getValue("layout"), atEndOfLiveTimeline: true, + narrow: false, }; this.readReceiptsSettingWatcher = null; } @@ -134,7 +139,7 @@ export default class TimelineCard extends React.Component { editState: payload.event ? new EditorStateTransfer(payload.event) : null, }, () => { if (payload.event) { - this.timelinePanelRef.current?.scrollToEventIfNeeded(payload.event.getId()); + this.timelinePanel.current?.scrollToEventIfNeeded(payload.event.getId()); } }); break; @@ -157,7 +162,7 @@ export default class TimelineCard extends React.Component { }; private onScroll = (): void => { - const timelinePanel = this.timelinePanelRef.current; + const timelinePanel = this.timelinePanel.current; if (!timelinePanel) return; if (timelinePanel.isAtEndOfLiveTimeline()) { this.setState({ @@ -170,6 +175,10 @@ export default class TimelineCard extends React.Component { } }; + private onMeasurement = (narrow: boolean): void => { + this.setState({ narrow }); + }; + private jumpToLiveTimeline = () => { if (this.state.initialEventId && this.state.isInitialEventHighlighted) { // If we were viewing a highlighted event, firing view_room without @@ -181,7 +190,7 @@ export default class TimelineCard extends React.Component { }); } else { // Otherwise we have to jump manually - this.timelinePanelRef.current?.jumpToLiveTimeline(); + this.timelinePanel.current?.jumpToLiveTimeline(); dis.fire(Action.FocusSendMessageComposer); } }; @@ -210,22 +219,30 @@ export default class TimelineCard extends React.Component { />); } + const isUploading = ContentMessages.sharedInstance().getCurrentUploads(this.props.composerRelation).length > 0; + return ( +
{ jumpToBottom } { />
- { ContentMessages.sharedInstance().getCurrentUploads(this.props.composerRelation).length > 0 && ( + { isUploading && ( ) } diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index a5ae6c3230..bb51368c2f 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1,5 +1,5 @@ /* -Copyright 2015-2021 The Matrix.org Foundation C.I.C. +Copyright 2015 - 2022 The Matrix.org Foundation C.I.C. Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); @@ -677,6 +677,12 @@ export default class EventTile extends React.Component {

{ _t("From a thread") }

); } else if (this.state.threadReplyCount && this.props.mxEvent.isThreadRoot) { + let count: string | number = this.state.threadReplyCount; + if (!this.context.narrow) { + count = _t("%(count)s reply", { + count: this.state.threadReplyCount, + }); + } return ( { context => @@ -687,9 +693,7 @@ export default class EventTile extends React.Component { }} > - { _t("%(count)s reply", { - count: this.state.threadReplyCount, - }) } + { count } { this.renderThreadLastMessagePreview() }
diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index 4b2a9a3818..e7f319d676 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -1,5 +1,5 @@ /* -Copyright 2015-2021 The Matrix.org Foundation C.I.C. +Copyright 2015 - 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. @@ -54,7 +54,6 @@ import { ButtonEvent } from '../elements/AccessibleButton'; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; let instanceCount = 0; -const NARROW_MODE_BREAKPOINT = 500; interface ISendButtonProps { onClick: (ev: ButtonEvent) => void; @@ -88,7 +87,6 @@ interface IState { haveRecording: boolean; recordingTimeLeftSeconds?: number; me?: RoomMember; - narrowMode?: boolean; isMenuOpen: boolean; isStickerPickerOpen: boolean; showStickersButton: boolean; @@ -165,10 +163,9 @@ export default class MessageComposer extends React.Component { private onResize = (type: UI_EVENTS, entry: ResizeObserverEntry) => { if (type === UI_EVENTS.Resize) { - const narrowMode = entry.contentRect.width <= NARROW_MODE_BREAKPOINT; + const { narrow } = this.context; this.setState({ - narrowMode, - isMenuOpen: !narrowMode ? false : this.state.isMenuOpen, + isMenuOpen: !narrow ? false : this.state.isMenuOpen, isStickerPickerOpen: false, }); } @@ -476,11 +473,10 @@ export default class MessageComposer extends React.Component { isMenuOpen={this.state.isMenuOpen} isStickerPickerOpen={this.state.isStickerPickerOpen} menuPosition={menuPosition} - narrowMode={this.state.narrowMode} relation={this.props.relation} onRecordStartEndClick={() => { this.voiceRecordingButton.current?.onRecordStartEndClick(); - if (this.state.narrowMode) { + if (this.context.narrow) { this.toggleButtonMenu(); } }} diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx index 4e016cd2ef..6375519621 100644 --- a/src/components/views/rooms/MessageComposerButtons.tsx +++ b/src/components/views/rooms/MessageComposerButtons.tsx @@ -44,7 +44,6 @@ interface IProps { isMenuOpen: boolean; isStickerPickerOpen: boolean; menuPosition: AboveLeftOf; - narrowMode?: boolean; onRecordStartEndClick: () => void; relation?: IEventRelation; setStickerPickerOpen: (isStickerPickerOpen: boolean) => void; @@ -58,7 +57,7 @@ export const OverflowMenuContext = createContext(null const MessageComposerButtons: React.FC = (props: IProps) => { const matrixClient: MatrixClient = useContext(MatrixClientContext); - const { room, roomId } = useContext(RoomContext); + const { room, roomId, narrow } = useContext(RoomContext); if (props.haveRecording) { return null; @@ -66,14 +65,14 @@ const MessageComposerButtons: React.FC = (props: IProps) => { let mainButtons: ReactElement[]; let moreButtons: ReactElement[]; - if (props.narrowMode) { + if (narrow) { mainButtons = [ emojiButton(props), ]; moreButtons = [ uploadButton(props, roomId), showStickersButton(props), - voiceRecordingButton(props), + voiceRecordingButton(props, narrow), pollButton(room, props.relation), showLocationButton(props, room, roomId, matrixClient), ]; @@ -84,7 +83,7 @@ const MessageComposerButtons: React.FC = (props: IProps) => { ]; moreButtons = [ showStickersButton(props), - voiceRecordingButton(props), + voiceRecordingButton(props, narrow), pollButton(room, props.relation), showLocationButton(props, room, roomId, matrixClient), ]; @@ -260,10 +259,10 @@ function showStickersButton(props: IProps): ReactElement { ); } -function voiceRecordingButton(props: IProps): ReactElement { +function voiceRecordingButton(props: IProps, narrow: boolean): ReactElement { // XXX: recording UI does not work well in narrow mode, so hide for now return ( - props.narrowMode + narrow ? null : ({ timelineRenderingType: TimelineRenderingType.Room, threadId: undefined, liveTimeline: undefined, + narrow: false, }); RoomContext.displayName = "RoomContext"; export default RoomContext; diff --git a/test/components/views/rooms/MessageComposerButtons-test.tsx b/test/components/views/rooms/MessageComposerButtons-test.tsx index 7eba700422..b6fb8bbdab 100644 --- a/test/components/views/rooms/MessageComposerButtons-test.tsx +++ b/test/components/views/rooms/MessageComposerButtons-test.tsx @@ -38,11 +38,11 @@ describe("MessageComposerButtons", () => { const buttons = wrapAndRender( {}} />, + false, ); expect(buttonLabels(buttons)).toEqual([ @@ -56,11 +56,11 @@ describe("MessageComposerButtons", () => { const buttons = wrapAndRender( {}} />, + false, ); expect(buttonLabels(buttons)).toEqual([ @@ -80,11 +80,11 @@ describe("MessageComposerButtons", () => { const buttons = wrapAndRender( {}} />, + true, ); expect(buttonLabels(buttons)).toEqual([ @@ -97,11 +97,11 @@ describe("MessageComposerButtons", () => { const buttons = wrapAndRender( {}} />, + true, ); expect(buttonLabels(buttons)).toEqual([ @@ -117,7 +117,7 @@ describe("MessageComposerButtons", () => { }); }); -function wrapAndRender(component: React.ReactElement): ReactWrapper { +function wrapAndRender(component: React.ReactElement, narrow: boolean): ReactWrapper { const mockClient = MatrixClientPeg.matrixClient = createTestClient(); const roomId = "myroomid"; const mockRoom: any = { @@ -128,7 +128,7 @@ function wrapAndRender(component: React.ReactElement): ReactWrapper { return new RoomMember(roomId, userId); }, }; - const roomState = createRoomState(mockRoom); + const roomState = createRoomState(mockRoom, narrow); return mount( @@ -139,7 +139,7 @@ function wrapAndRender(component: React.ReactElement): ReactWrapper { ); } -function createRoomState(room: Room): IRoomState { +function createRoomState(room: Room, narrow: boolean): IRoomState { return { room: room, roomId: room.roomId, @@ -176,6 +176,7 @@ function createRoomState(room: Room): IRoomState { matrixClientIsReady: false, timelineRenderingType: TimelineRenderingType.Room, liveTimeline: undefined, + narrow, }; }