From ccc60034a0cb3924e7ea2e1a564e91075e76003a Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Fri, 10 Jun 2022 15:49:19 -0400 Subject: [PATCH 1/8] Show chat panel when opening a video room with unread messages --- src/components/structures/RightPanel.tsx | 6 -- src/components/structures/RoomView.tsx | 8 +++ src/stores/right-panel/RightPanelStore.ts | 58 +++++++------------- test/components/structures/RoomView-test.tsx | 54 ++++++++++++++++-- 4 files changed, 77 insertions(+), 49 deletions(-) diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 5c2eaaf9a4e..863c15c4fe1 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -91,12 +91,6 @@ export default class RightPanel extends React.Component { currentCard = RightPanelStore.instance.currentCardForRoom(props.room.roomId); } - if (currentCard?.phase && !RightPanelStore.instance.isPhaseValid(currentCard.phase, !!props.room)) { - // XXX: We can probably get rid of this workaround once GroupView is dead, it's unmounting happens weirdly - // late causing the app to soft-crash due to lack of a room object being passed to a RightPanel - return null; // skip this update, we're about to be unmounted and don't have the appropriate props - } - return { cardState: currentCard?.state, phase: currentCard?.phase, diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 648122af629..4fb75e88a43 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1018,6 +1018,14 @@ export class RoomView extends React.Component { this.updatePermissions(room); this.checkWidgets(room); + if ( + this.getMainSplitContentType(room) !== MainSplitContentType.Timeline + && RoomNotificationStateStore.instance.getRoomState(room).isUnread + ) { + // Automatically open the chat panel to make unread messages easier to discover + RightPanelStore.instance.setCard({ phase: RightPanelPhases.Timeline }, true, room.roomId); + } + this.setState({ tombstone: this.getRoomTombstone(room), liveTimeline: room.getLiveTimeline(), diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index 341474293ec..47658280111 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -141,12 +141,13 @@ export default class RightPanelStore extends ReadyWatchingStore { const hist = this.byRoom[rId]?.history ?? []; hist[hist.length - 1].state = cardState; this.emitAndUpdateSettings(); - } else if (targetPhase !== this.currentCard?.phase) { - // Set right panel and erase history. - this.show(); - this.setRightPanelCache({ phase: targetPhase, state: cardState ?? {} }, rId); + } else if (targetPhase !== this.currentCard?.phase || !this.byRoom[rId]) { + // Set right panel and initialize/erase history + const history = [{ phase: targetPhase, state: cardState ?? {} }]; + this.byRoom[rId] = { history, isOpen: true }; + this.emitAndUpdateSettings(); } else { - this.show(); + this.show(rId); this.emitAndUpdateSettings(); } } @@ -156,7 +157,7 @@ export default class RightPanelStore extends ReadyWatchingStore { const rId = roomId ?? this.viewedRoomId; const history = cards.map(c => ({ phase: c.phase, state: c.state ?? {} })); this.byRoom[rId] = { history, isOpen: true }; - this.show(); + this.show(rId); this.emitAndUpdateSettings(); } @@ -187,7 +188,7 @@ export default class RightPanelStore extends ReadyWatchingStore { isOpen: !allowClose, }; } - this.show(); + this.show(rId); this.emitAndUpdateSettings(); } @@ -200,7 +201,7 @@ export default class RightPanelStore extends ReadyWatchingStore { return removedCard; } - public togglePanel(roomId: string = null) { + public togglePanel(roomId?: string) { const rId = roomId ?? this.viewedRoomId; if (!this.byRoom[rId]) return; @@ -208,15 +209,15 @@ export default class RightPanelStore extends ReadyWatchingStore { this.emitAndUpdateSettings(); } - public show() { - if (!this.isOpen) { - this.togglePanel(); + public show(roomId?: string) { + if (!this.isOpenForRoom(roomId ?? this.viewedRoomId)) { + this.togglePanel(roomId); } } - public hide() { - if (this.isOpen) { - this.togglePanel(); + public hide(roomId?: string) { + if (this.isOpenForRoom(roomId ?? this.viewedRoomId)) { + this.togglePanel(roomId); } } @@ -228,7 +229,7 @@ export default class RightPanelStore extends ReadyWatchingStore { this.byRoom[this.viewedRoomId] = this.byRoom[this.viewedRoomId] ?? convertToStatePanel(SettingsStore.getValue("RightPanel.phases", this.viewedRoomId), room); } else { - console.warn("Could not restore the right panel after load because there was no associated room object."); + logger.warn("Could not restore the right panel after load because there was no associated room object."); } } @@ -273,37 +274,31 @@ export default class RightPanelStore extends ReadyWatchingStore { case RightPanelPhases.ThreadView: if (!SettingsStore.getValue("feature_thread")) return false; if (!card.state.threadHeadEvent) { - console.warn("removed card from right panel because of missing threadHeadEvent in card state"); + logger.warn("removed card from right panel because of missing threadHeadEvent in card state"); } return !!card.state.threadHeadEvent; case RightPanelPhases.RoomMemberInfo: case RightPanelPhases.SpaceMemberInfo: case RightPanelPhases.EncryptionPanel: if (!card.state.member) { - console.warn("removed card from right panel because of missing member in card state"); + logger.warn("removed card from right panel because of missing member in card state"); } return !!card.state.member; case RightPanelPhases.Room3pidMemberInfo: case RightPanelPhases.Space3pidMemberInfo: if (!card.state.memberInfoEvent) { - console.warn("removed card from right panel because of missing memberInfoEvent in card state"); + logger.warn("removed card from right panel because of missing memberInfoEvent in card state"); } return !!card.state.memberInfoEvent; case RightPanelPhases.Widget: if (!card.state.widgetId) { - console.warn("removed card from right panel because of missing widgetId in card state"); + logger.warn("removed card from right panel because of missing widgetId in card state"); } return !!card.state.widgetId; } return true; } - private setRightPanelCache(card: IRightPanelCard, roomId?: string) { - const history = [{ phase: card.phase, state: card.state ?? {} }]; - this.byRoom[roomId ?? this.viewedRoomId] = { history, isOpen: true }; - this.emitAndUpdateSettings(); - } - private getVerificationRedirect(card: IRightPanelCard): IRightPanelCard { if (card.phase === RightPanelPhases.RoomMemberInfo && card.state) { // RightPanelPhases.RoomMemberInfo -> needs to be changed to RightPanelPhases.EncryptionPanel if there is a pending verification request @@ -322,18 +317,11 @@ export default class RightPanelStore extends ReadyWatchingStore { return null; } - public isPhaseValid(targetPhase: RightPanelPhases, isViewingRoom = this.isViewingRoom): boolean { + public isPhaseValid(targetPhase: RightPanelPhases): boolean { if (!RightPanelPhases[targetPhase]) { logger.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`); return false; } - if (!isViewingRoom) { - logger.warn( - `Tried to switch right panel to a room phase: ${targetPhase}, ` + - `but we are currently not viewing a room`, - ); - return false; - } return true; } @@ -386,10 +374,6 @@ export default class RightPanelStore extends ReadyWatchingStore { this.emitAndUpdateSettings(); } - private get isViewingRoom(): boolean { - return !!this.viewedRoomId; - } - public static get instance(): RightPanelStore { if (!RightPanelStore.internalInstance) { RightPanelStore.internalInstance = new RightPanelStore(); diff --git a/test/components/structures/RoomView-test.tsx b/test/components/structures/RoomView-test.tsx index ec318bfca2a..95842caab73 100644 --- a/test/components/structures/RoomView-test.tsx +++ b/test/components/structures/RoomView-test.tsx @@ -16,12 +16,13 @@ limitations under the License. import React from "react"; import { mount, ReactWrapper } from "enzyme"; +import { act } from "react-dom/test-utils"; import { mocked, MockedObject } from "jest-mock"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { stubClient, wrapInMatrixClientContext } from "../../test-utils"; +import { stubClient, mockPlatformPeg, unmockPlatformPeg, wrapInMatrixClientContext } from "../../test-utils"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import { Action } from "../../../src/dispatcher/actions"; import dis from "../../../src/dispatcher/dispatcher"; @@ -29,18 +30,25 @@ import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayloa import { RoomView as _RoomView } from "../../../src/components/structures/RoomView"; import ResizeNotifier from "../../../src/utils/ResizeNotifier"; import { RoomViewStore } from "../../../src/stores/RoomViewStore"; +import SettingsStore from "../../../src/settings/SettingsStore"; +import { SettingLevel } from "../../../src/settings/SettingLevel"; import DMRoomMap from "../../../src/utils/DMRoomMap"; +import { NotificationState } from "../../../src/stores/notifications/NotificationState"; +import RightPanelStore from "../../../src/stores/right-panel/RightPanelStore"; +import { RightPanelPhases } from "../../../src/stores/right-panel/RightPanelStorePhases"; const RoomView = wrapInMatrixClientContext(_RoomView); describe("RoomView", () => { let cli: MockedObject; let room: Room; - beforeEach(() => { + let roomCount = 0; + beforeEach(async () => { + mockPlatformPeg({ reload: () => {} }); stubClient(); cli = mocked(MatrixClientPeg.get()); - room = new Room("r1", cli, "@alice:example.com"); + room = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org"); room.getPendingEvents = () => []; cli.getRoom.mockReturnValue(room); // Re-emit certain events on the mocked client @@ -48,11 +56,22 @@ describe("RoomView", () => { room.on(RoomEvent.TimelineReset, (...args) => cli.emit(RoomEvent.TimelineReset, ...args)); DMRoomMap.makeShared(); + + RightPanelStore.instance.useUnitTestClient(cli); + // @ts-ignore + await RightPanelStore.instance.onReady(); + }); + + afterEach(async () => { + // @ts-ignore + await RightPanelStore.instance.onNotReady(); + unmockPlatformPeg(); + jest.restoreAllMocks(); }); const mountRoomView = async (): Promise => { if (RoomViewStore.instance.getRoomId() !== room.roomId) { - const switchRoomPromise = new Promise(resolve => { + const switchedRoom = new Promise(resolve => { const subscription = RoomViewStore.instance.addListener(() => { if (RoomViewStore.instance.getRoomId()) { subscription.remove(); @@ -67,10 +86,10 @@ describe("RoomView", () => { metricsTrigger: null, }); - await switchRoomPromise; + await switchedRoom; } - return mount( + const roomView = mount( { onRegistered={null} />, ); + await act(() => Promise.resolve()); // Allow state to settle + return roomView; }; const getRoomViewInstance = async (): Promise<_RoomView> => (await mountRoomView()).find(_RoomView).instance() as _RoomView; @@ -126,4 +147,25 @@ describe("RoomView", () => { room.getUnfilteredTimelineSet().resetLiveTimeline(); expect(roomViewInstance.state.liveTimeline).not.toEqual(oldTimeline); }); + + describe("video rooms", () => { + beforeEach(async () => { + // Make it a video room + room.isElementVideoRoom = () => true; + await SettingsStore.setValue("feature_video_rooms", null, SettingLevel.DEVICE, true); + }); + + it("normally doesn't open the chat panel", async () => { + jest.spyOn(NotificationState.prototype, "isUnread", "get").mockReturnValue(false); + await mountRoomView(); + expect(RightPanelStore.instance.isOpen).toEqual(false); + }); + + it("opens the chat panel if there are unread messages", async () => { + jest.spyOn(NotificationState.prototype, "isUnread", "get").mockReturnValue(true); + await mountRoomView(); + expect(RightPanelStore.instance.isOpen).toEqual(true); + expect(RightPanelStore.instance.currentCard.phase).toEqual(RightPanelPhases.Timeline); + }); + }); }); From 1708b351818ae2703fff4e7021bb9f00e44eccac Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 13 Jun 2022 13:49:33 -0400 Subject: [PATCH 2/8] Remove unnecessary calls to private methods in tests --- test/components/structures/RoomView-test.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/components/structures/RoomView-test.tsx b/test/components/structures/RoomView-test.tsx index 95842caab73..3b529ec7008 100644 --- a/test/components/structures/RoomView-test.tsx +++ b/test/components/structures/RoomView-test.tsx @@ -56,15 +56,10 @@ describe("RoomView", () => { room.on(RoomEvent.TimelineReset, (...args) => cli.emit(RoomEvent.TimelineReset, ...args)); DMRoomMap.makeShared(); - RightPanelStore.instance.useUnitTestClient(cli); - // @ts-ignore - await RightPanelStore.instance.onReady(); }); afterEach(async () => { - // @ts-ignore - await RightPanelStore.instance.onNotReady(); unmockPlatformPeg(); jest.restoreAllMocks(); }); From 8b2af5002fdae03d23816404810b4b6c4cedb32a Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 13 Jun 2022 13:54:46 -0400 Subject: [PATCH 3/8] Make room ID mandatory when toggling the right panel --- src/components/structures/LoggedInView.tsx | 2 +- src/components/structures/RightPanel.tsx | 2 +- src/components/structures/RoomView.tsx | 2 +- src/components/views/right_panel/EncryptionPanel.tsx | 2 +- src/components/views/right_panel/HeaderButtons.tsx | 4 ++-- src/components/views/right_panel/RoomHeaderButtons.tsx | 2 +- src/stores/right-panel/RightPanelStore.ts | 6 +++--- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index de60ca71fa1..87869b65511 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -493,7 +493,7 @@ class LoggedInView extends React.Component { break; case KeyBindingAction.ToggleRoomSidePanel: if (this.props.page_type === "room_view") { - RightPanelStore.instance.togglePanel(); + RightPanelStore.instance.togglePanel(null); handled = true; } break; diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 863c15c4fe1..599c2949890 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -136,7 +136,7 @@ export default class RightPanel extends React.Component { // When the user clicks close on the encryption panel cancel the pending request first if any this.state.cardState.verificationRequest.cancel(); } else { - RightPanelStore.instance.togglePanel(); + RightPanelStore.instance.togglePanel(this.props.room?.roomId); } }; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index feba67151dd..b2a2d4ecbce 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -361,7 +361,7 @@ export class RoomView extends React.Component { ) { // hide chat in right panel when the widget is minimized RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomSummary }); - RightPanelStore.instance.togglePanel(); + RightPanelStore.instance.togglePanel(this.state.roomId); } this.checkWidgets(this.state.room); }; diff --git a/src/components/views/right_panel/EncryptionPanel.tsx b/src/components/views/right_panel/EncryptionPanel.tsx index c2dfd7a379b..68915643b39 100644 --- a/src/components/views/right_panel/EncryptionPanel.tsx +++ b/src/components/views/right_panel/EncryptionPanel.tsx @@ -122,7 +122,7 @@ const EncryptionPanel: React.FC = (props: IProps) => { state: { member, verificationRequest: verificationRequest_ }, }); } - if (!RightPanelStore.instance.isOpen) RightPanelStore.instance.togglePanel(); + if (!RightPanelStore.instance.isOpen) RightPanelStore.instance.togglePanel(null); }, [member]); const requested = diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index a152a8871f6..9af2c2e01cb 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -70,10 +70,10 @@ export default abstract class HeaderButtons

extends React.Component) { const rps = RightPanelStore.instance; if (rps.currentCard.phase == phase && !cardState && rps.isOpen) { - rps.togglePanel(); + rps.togglePanel(null); } else { RightPanelStore.instance.setCard({ phase, state: cardState }); - if (!rps.isOpen) rps.togglePanel(); + if (!rps.isOpen) rps.togglePanel(null); } } diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index cb661d00a13..68f0c3b8064 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -211,7 +211,7 @@ export default class RoomHeaderButtons extends HeaderButtons { private onThreadsPanelClicked = (ev: ButtonEvent) => { if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) { - RightPanelStore.instance.togglePanel(); + RightPanelStore.instance.togglePanel(this.props.room?.roomId); } else { showThreadPanel(); PosthogTrackers.trackInteraction("WebRoomHeaderButtonsThreadsButton", ev); diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index 47658280111..cf177dd03a4 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -201,7 +201,7 @@ export default class RightPanelStore extends ReadyWatchingStore { return removedCard; } - public togglePanel(roomId?: string) { + public togglePanel(roomId: string | null) { const rId = roomId ?? this.viewedRoomId; if (!this.byRoom[rId]) return; @@ -209,13 +209,13 @@ export default class RightPanelStore extends ReadyWatchingStore { this.emitAndUpdateSettings(); } - public show(roomId?: string) { + public show(roomId: string | null) { if (!this.isOpenForRoom(roomId ?? this.viewedRoomId)) { this.togglePanel(roomId); } } - public hide(roomId?: string) { + public hide(roomId: string | null) { if (this.isOpenForRoom(roomId ?? this.viewedRoomId)) { this.togglePanel(roomId); } From ea067ba144cc8ca4d759a7bce498ce5171c427d2 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 13 Jun 2022 14:04:19 -0400 Subject: [PATCH 4/8] Restore the isViewingRoom check --- src/stores/right-panel/RightPanelStore.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index cf177dd03a4..9eece44608f 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -134,7 +134,7 @@ export default class RightPanelStore extends ReadyWatchingStore { const cardState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? null : card.state); // Checks for wrong SetRightPanelPhase requests - if (!this.isPhaseValid(targetPhase)) return; + if (!this.isPhaseValid(targetPhase, Boolean(rId))) return; if ((targetPhase === this.currentCardForRoom(rId)?.phase && !!cardState)) { // Update state: set right panel with a new state but keep the phase (don't know it this is ever needed...) @@ -173,7 +173,7 @@ export default class RightPanelStore extends ReadyWatchingStore { const pState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? null : card.state); // Checks for wrong SetRightPanelPhase requests - if (!this.isPhaseValid(targetPhase)) return; + if (!this.isPhaseValid(targetPhase, Boolean(rId))) return; const roomCache = this.byRoom[rId]; if (!!roomCache) { @@ -317,11 +317,18 @@ export default class RightPanelStore extends ReadyWatchingStore { return null; } - public isPhaseValid(targetPhase: RightPanelPhases): boolean { + private isPhaseValid(targetPhase: RightPanelPhases, isViewingRoom: boolean): boolean { if (!RightPanelPhases[targetPhase]) { logger.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`); return false; } + if (!isViewingRoom) { + logger.warn( + `Tried to switch right panel to a room phase: ${targetPhase}, ` + + `but we are currently not viewing a room`, + ); + return false; + } return true; } From 285d7c524fa147893c1053f7d70118ef387f6ede Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 13 Jun 2022 16:44:47 -0400 Subject: [PATCH 5/8] Test RightPanelStore --- src/stores/right-panel/RightPanelStore.ts | 19 +- .../right-panel/RightPanelStore-test.ts | 204 ++++++++++++++++++ 2 files changed, 214 insertions(+), 9 deletions(-) create mode 100644 test/stores/right-panel/RightPanelStore-test.ts diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index 9eece44608f..4cd2c0b3148 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -51,20 +51,21 @@ export default class RightPanelStore extends ReadyWatchingStore { } = {}; private viewedRoomId: Optional; - private constructor() { + // Public for tests + public constructor() { super(defaultDispatcher); } - protected async onReady(): Promise { + protected onReady = async (): Promise => { this.viewedRoomId = RoomViewStore.instance.getRoomId(); this.matrixClient.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); this.loadCacheFromSettings(); this.emitAndUpdateSettings(); - } + }; - protected async onNotReady(): Promise { + protected onNotReady = async (): Promise => { this.matrixClient.off(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); - } + }; protected onDispatcherAction(payload: ActionPayload) { if (payload.action !== Action.ActiveRoomChanged) return; @@ -141,7 +142,7 @@ export default class RightPanelStore extends ReadyWatchingStore { const hist = this.byRoom[rId]?.history ?? []; hist[hist.length - 1].state = cardState; this.emitAndUpdateSettings(); - } else if (targetPhase !== this.currentCard?.phase || !this.byRoom[rId]) { + } else if (targetPhase !== this.currentCardForRoom(rId)?.phase || !this.byRoom[rId]) { // Set right panel and initialize/erase history const history = [{ phase: targetPhase, state: cardState ?? {} }]; this.byRoom[rId] = { history, isOpen: true }; @@ -161,16 +162,16 @@ export default class RightPanelStore extends ReadyWatchingStore { this.emitAndUpdateSettings(); } + // Appends a card to the history and shows the right panel if not already visible public pushCard( card: IRightPanelCard, allowClose = true, roomId: string = null, ) { - // This function appends a card to the history and shows the right panel if now already visible. const rId = roomId ?? this.viewedRoomId; const redirect = this.getVerificationRedirect(card); const targetPhase = redirect?.phase ?? card.phase; - const pState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? null : card.state); + const pState = redirect?.state ?? card.state ?? {}; // Checks for wrong SetRightPanelPhase requests if (!this.isPhaseValid(targetPhase, Boolean(rId))) return; @@ -183,7 +184,7 @@ export default class RightPanelStore extends ReadyWatchingStore { } else { // setup room panel cache with the new card this.byRoom[rId] = { - history: [{ phase: targetPhase, state: pState ?? {} }], + history: [{ phase: targetPhase, state: pState }], // if there was no right panel store object the the panel was closed -> keep it closed, except if allowClose==false isOpen: !allowClose, }; diff --git a/test/stores/right-panel/RightPanelStore-test.ts b/test/stores/right-panel/RightPanelStore-test.ts new file mode 100644 index 00000000000..c202d8395c9 --- /dev/null +++ b/test/stores/right-panel/RightPanelStore-test.ts @@ -0,0 +1,204 @@ +/* +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 { mocked, MockedObject } from "jest-mock"; +import { MatrixClient } from "matrix-js-sdk/src/client"; + +import { stubClient } from "../../test-utils"; +import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; +import { Action } from "../../../src/dispatcher/actions"; +import defaultDispatcher from "../../../src/dispatcher/dispatcher"; +import { ActiveRoomChangedPayload } from "../../../src/dispatcher/payloads/ActiveRoomChangedPayload"; +import RightPanelStore from "../../../src/stores/right-panel/RightPanelStore"; +import { RightPanelPhases } from "../../../src/stores/right-panel/RightPanelStorePhases"; +import SettingsStore from "../../../src/settings/SettingsStore"; + +describe("RightPanelStore", () => { + // Mock out the settings store so the right panel store can't persist values between tests + jest.spyOn(SettingsStore, "setValue").mockImplementation(async () => {}); + + let store: RightPanelStore; + let cli: MockedObject; + beforeEach(() => { + stubClient(); + cli = mocked(MatrixClientPeg.get()); + + store = new RightPanelStore(); + store.useUnitTestClient(cli); + }); + + const viewRoom = async (roomId: string) => { + const roomChanged = new Promise(resolve => { + const ref = defaultDispatcher.register(payload => { + if (payload.action === Action.ActiveRoomChanged && payload.newRoomId === roomId) { + defaultDispatcher.unregister(ref); + resolve(); + } + }); + }); + + defaultDispatcher.dispatch({ + action: Action.ActiveRoomChanged, + oldRoomId: null, + newRoomId: roomId, + }); + + await roomChanged; + }; + + const setCard = (roomId: string, phase: RightPanelPhases) => store.setCard({ phase }, true, roomId); + + describe("isOpen", () => { + it("is false if no rooms are open", () => { + expect(store.isOpen).toEqual(false); + }); + it("is false if a room other than the current room is open", async () => { + await viewRoom("!1:example.org"); + setCard("!2:example.org", RightPanelPhases.RoomSummary); + expect(store.isOpen).toEqual(false); + }); + it("is true if the current room is open", async () => { + await viewRoom("!1:example.org"); + setCard("!1:example.org", RightPanelPhases.RoomSummary); + expect(store.isOpen).toEqual(true); + }); + }); + + describe("currentCard", () => { + it("has a phase of null if nothing is open", () => { + expect(store.currentCard.phase).toEqual(null); + }); + it("has a phase of null if the panel is open but in another room", async () => { + await viewRoom("!1:example.org"); + setCard("!2:example.org", RightPanelPhases.RoomSummary); + expect(store.currentCard.phase).toEqual(null); + }); + it("reflects the phase of the current room", async () => { + await viewRoom("!1:example.org"); + setCard("!1:example.org", RightPanelPhases.RoomSummary); + expect(store.currentCard.phase).toEqual(RightPanelPhases.RoomSummary); + }); + }); + + describe("setCard", () => { + it("does nothing if given no room ID and not viewing a room", () => { + store.setCard({ phase: RightPanelPhases.RoomSummary }, true); + expect(store.isOpen).toEqual(false); + expect(store.currentCard.phase).toEqual(null); + }); + it("does nothing if given an invalid state", async () => { + await viewRoom("!1:example.org"); + // Needs a member specified to be valid + store.setCard({ phase: RightPanelPhases.RoomMemberInfo }, true, "!1:example.org"); + expect(store.roomPhaseHistory).toEqual([]); + }); + it("only creates a single history entry if given the same card twice", async () => { + await viewRoom("!1:example.org"); + store.setCard({ phase: RightPanelPhases.RoomSummary }, true, "!1:example.org"); + store.setCard({ phase: RightPanelPhases.RoomSummary }, true, "!1:example.org"); + expect(store.roomPhaseHistory).toEqual([ + { phase: RightPanelPhases.RoomSummary, state: {} }, + ]); + }); + it("opens the panel in the given room with the correct phase", () => { + store.setCard({ phase: RightPanelPhases.RoomSummary }, true, "!1:example.org"); + expect(store.isOpenForRoom("!1:example.org")).toEqual(true); + expect(store.currentCardForRoom("!1:example.org").phase).toEqual(RightPanelPhases.RoomSummary); + }); + it("overwrites history if changing the phase", async () => { + await viewRoom("!1:example.org"); + store.setCard({ phase: RightPanelPhases.RoomSummary }, true, "!1:example.org"); + store.setCard({ phase: RightPanelPhases.RoomMemberList }, true, "!1:example.org"); + expect(store.roomPhaseHistory).toEqual([ + { phase: RightPanelPhases.RoomMemberList, state: {} }, + ]); + }); + }); + + describe("setCards", () => { + it("overwrites history", async () => { + await viewRoom("!1:example.org"); + store.setCard({ phase: RightPanelPhases.RoomMemberList }, true, "!1:example.org"); + store.setCards([ + { phase: RightPanelPhases.RoomSummary }, + { phase: RightPanelPhases.PinnedMessages }, + ], true, "!1:example.org"); + expect(store.roomPhaseHistory).toEqual([ + { phase: RightPanelPhases.RoomSummary, state: {} }, + { phase: RightPanelPhases.PinnedMessages, state: {} }, + ]); + }); + }); + + describe("pushCard", () => { + it("does nothing if given no room ID and not viewing a room", () => { + store.pushCard({ phase: RightPanelPhases.RoomSummary }, true); + expect(store.isOpen).toEqual(false); + expect(store.currentCard.phase).toEqual(null); + }); + it("opens the panel in the given room with the correct phase", () => { + store.pushCard({ phase: RightPanelPhases.RoomSummary }, true, "!1:example.org"); + expect(store.isOpenForRoom("!1:example.org")).toEqual(true); + expect(store.currentCardForRoom("!1:example.org").phase).toEqual(RightPanelPhases.RoomSummary); + }); + it("appends the phase to any phases that were there before", async () => { + await viewRoom("!1:example.org"); + store.setCard({ phase: RightPanelPhases.RoomSummary }, true, "!1:example.org"); + store.pushCard({ phase: RightPanelPhases.PinnedMessages }, true, "!1:example.org"); + expect(store.roomPhaseHistory).toEqual([ + { phase: RightPanelPhases.RoomSummary, state: {} }, + { phase: RightPanelPhases.PinnedMessages, state: {} }, + ]); + }); + }); + + describe("popCard", () => { + it("removes the most recent card", () => { + store.setCards([ + { phase: RightPanelPhases.RoomSummary }, + { phase: RightPanelPhases.PinnedMessages }, + ], true, "!1:example.org"); + expect(store.currentCardForRoom("!1:example.org").phase).toEqual(RightPanelPhases.PinnedMessages); + store.popCard("!1:example.org"); + expect(store.currentCardForRoom("!1:example.org").phase).toEqual(RightPanelPhases.RoomSummary); + }); + }); + + describe("togglePanel", () => { + it("does nothing if the room has no phase to open to", () => { + expect(store.isOpenForRoom("!1:example.org")).toEqual(false); + store.togglePanel("!1:example.org"); + expect(store.isOpenForRoom("!1:example.org")).toEqual(false); + }); + it("works if a room is specified", () => { + store.setCard({ phase: RightPanelPhases.RoomSummary }, true, "!1:example.org"); + expect(store.isOpenForRoom("!1:example.org")).toEqual(true); + store.togglePanel("!1:example.org"); + expect(store.isOpenForRoom("!1:example.org")).toEqual(false); + store.togglePanel("!1:example.org"); + expect(store.isOpenForRoom("!1:example.org")).toEqual(true); + }); + it("operates on the current room if no room is specified", async () => { + await viewRoom("!1:example.org"); + store.setCard({ phase: RightPanelPhases.RoomSummary }, true); + expect(store.isOpen).toEqual(true); + store.togglePanel(null); + expect(store.isOpen).toEqual(false); + store.togglePanel(null); + expect(store.isOpen).toEqual(true); + }); + }); +}); From 81bd44cb58b594ba3e604905e739fc5c85963b06 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Thu, 16 Jun 2022 14:05:09 -0400 Subject: [PATCH 6/8] Make the constructor private again --- src/stores/right-panel/RightPanelStore.ts | 3 +-- test/stores/right-panel/RightPanelStore-test.ts | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index 4cd2c0b3148..2c6af7fb3d1 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -51,8 +51,7 @@ export default class RightPanelStore extends ReadyWatchingStore { } = {}; private viewedRoomId: Optional; - // Public for tests - public constructor() { + private constructor() { super(defaultDispatcher); } diff --git a/test/stores/right-panel/RightPanelStore-test.ts b/test/stores/right-panel/RightPanelStore-test.ts index c202d8395c9..68139b6d407 100644 --- a/test/stores/right-panel/RightPanelStore-test.ts +++ b/test/stores/right-panel/RightPanelStore-test.ts @@ -36,6 +36,9 @@ describe("RightPanelStore", () => { stubClient(); cli = mocked(MatrixClientPeg.get()); + // @ts-ignore + // The constructor is private but we want to use it anyways to prevent + // state from being shared between tests store = new RightPanelStore(); store.useUnitTestClient(cli); }); From ad8538337860231c3036e7f009c8331153587665 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Thu, 16 Jun 2022 17:24:56 -0400 Subject: [PATCH 7/8] Add even more tests --- src/stores/ReadyWatchingStore.ts | 10 +- src/stores/right-panel/RightPanelStore.ts | 20 ++- .../components/structures/RightPanel-test.tsx | 148 +++++++++++------- .../right-panel/RightPanelStore-test.ts | 22 +++ 4 files changed, 133 insertions(+), 67 deletions(-) diff --git a/src/stores/ReadyWatchingStore.ts b/src/stores/ReadyWatchingStore.ts index a142693e62c..4060abfe5d7 100644 --- a/src/stores/ReadyWatchingStore.ts +++ b/src/stores/ReadyWatchingStore.ts @@ -73,11 +73,11 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro // Everything after this is unnecessary (we only need to know once we have a client) // and we intentionally don't set the client before this point to avoid stores // updating for every event emitted during the cached sync. - if (!(payload.prevState === SyncState.Prepared && payload.state !== SyncState.Prepared)) { - return; - } - - if (this.matrixClient !== payload.matrixClient) { + if ( + payload.prevState !== SyncState.Prepared + && payload.state === SyncState.Prepared + && this.matrixClient !== payload.matrixClient + ) { if (this.matrixClient) { await this.onNotReady(); } diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index 2c6af7fb3d1..2cfafb26ac2 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -222,14 +222,18 @@ export default class RightPanelStore extends ReadyWatchingStore { } private loadCacheFromSettings() { - const room = this.viewedRoomId && this.mxClient?.getRoom(this.viewedRoomId); - if (!!room) { - this.global = this.global ?? - convertToStatePanel(SettingsStore.getValue("RightPanel.phasesGlobal"), room); - this.byRoom[this.viewedRoomId] = this.byRoom[this.viewedRoomId] ?? - convertToStatePanel(SettingsStore.getValue("RightPanel.phases", this.viewedRoomId), room); - } else { - logger.warn("Could not restore the right panel after load because there was no associated room object."); + if (this.viewedRoomId) { + const room = this.mxClient?.getRoom(this.viewedRoomId); + if (!!room) { + this.global = this.global ?? + convertToStatePanel(SettingsStore.getValue("RightPanel.phasesGlobal"), room); + this.byRoom[this.viewedRoomId] = this.byRoom[this.viewedRoomId] ?? + convertToStatePanel(SettingsStore.getValue("RightPanel.phases", this.viewedRoomId), room); + } else { + logger.warn( + "Could not restore the right panel after load because there was no associated room object.", + ); + } } } diff --git a/test/components/structures/RightPanel-test.tsx b/test/components/structures/RightPanel-test.tsx index 7d68d1753db..9653fa6cd12 100644 --- a/test/components/structures/RightPanel-test.tsx +++ b/test/components/structures/RightPanel-test.tsx @@ -15,37 +15,113 @@ limitations under the License. */ import React from "react"; -import TestRenderer from "react-test-renderer"; +import { mount } from "enzyme"; import { jest } from "@jest/globals"; -import { Room } from "matrix-js-sdk/src/models/room"; +import { mocked, MockedObject } from "jest-mock"; +import { MatrixClient } from "matrix-js-sdk/src/client"; -import RightPanel from "../../../src/components/structures/RightPanel"; +import _RightPanel from "../../../src/components/structures/RightPanel"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import ResizeNotifier from "../../../src/utils/ResizeNotifier"; -import { stubClient } from "../../test-utils"; +import { stubClient, wrapInMatrixClientContext, mkRoom } from "../../test-utils"; import { Action } from "../../../src/dispatcher/actions"; import dis from "../../../src/dispatcher/dispatcher"; import DMRoomMap from "../../../src/utils/DMRoomMap"; -import MatrixClientContext from "../../../src/contexts/MatrixClientContext"; import SettingsStore from "../../../src/settings/SettingsStore"; import { RightPanelPhases } from "../../../src/stores/right-panel/RightPanelStorePhases"; import RightPanelStore from "../../../src/stores/right-panel/RightPanelStore"; import { UPDATE_EVENT } from "../../../src/stores/AsyncStore"; import { WidgetLayoutStore } from "../../../src/stores/widgets/WidgetLayoutStore"; +import RoomSummaryCard from "../../../src/components/views/right_panel/RoomSummaryCard"; +import MemberList from "../../../src/components/views/rooms/MemberList"; + +const RightPanel = wrapInMatrixClientContext(_RightPanel); describe("RightPanel", () => { - it("renders info from only one room during room changes", async () => { - stubClient(); - const cli = MatrixClientPeg.get(); - cli.hasLazyLoadMembersEnabled = () => false; + const resizeNotifier = new ResizeNotifier(); - // Init misc. startup deps + let cli: MockedObject; + beforeEach(() => { + stubClient(); + cli = mocked(MatrixClientPeg.get()); DMRoomMap.makeShared(); + }); + + afterEach(async () => { + const roomChanged = new Promise(resolve => { + const ref = dis.register(payload => { + if (payload.action === Action.ActiveRoomChanged) { + dis.unregister(ref); + resolve(); + } + }); + }); + dis.fire(Action.ViewHomePage); // Stop viewing any rooms + await roomChanged; + + dis.fire(Action.OnLoggedOut, true); // Shut down the stores + jest.restoreAllMocks(); + }); + + const spinUpStores = async () => { + // Selectively spin up the stores we need + WidgetLayoutStore.instance.useUnitTestClient(cli); + // @ts-ignore + // This is private but it's the only way to selectively enable stores + await WidgetLayoutStore.instance.onReady(); + + // @ts-ignore + // Make sure we start with a clean store + RightPanelStore.internalInstance = null; + RightPanelStore.instance.useUnitTestClient(cli); + // @ts-ignore + await RightPanelStore.instance.onReady(); + }; + + const waitForRpsUpdate = () => + new Promise(resolve => RightPanelStore.instance.once(UPDATE_EVENT, resolve)); + + it("navigates from room summary to member list", async () => { + const r1 = mkRoom(cli, "r1"); + cli.getRoom.mockImplementation(roomId => roomId === "r1" ? r1 : null); + + // Set up right panel state + const realGetValue = SettingsStore.getValue; + jest.spyOn(SettingsStore, "getValue").mockImplementation((name, roomId) => { + if (name !== "RightPanel.phases") return realGetValue(name, roomId); + if (roomId === "r1") { + return { + history: [{ phase: RightPanelPhases.RoomSummary }], + isOpen: true, + }; + } + return null; + }); + + await spinUpStores(); + const viewedRoom = waitForRpsUpdate(); + dis.dispatch({ + action: Action.ViewRoom, + room_id: "r1", + }); + await viewedRoom; + + const wrapper = mount(); + expect(wrapper.find(RoomSummaryCard).exists()).toEqual(true); + + const switchedPhases = waitForRpsUpdate(); + wrapper.find("AccessibleButton.mx_RoomSummaryCard_icon_people").simulate("click"); + await switchedPhases; + wrapper.update(); + + expect(wrapper.find(MemberList).exists()).toEqual(true); + }); - const r1 = new Room("r1", cli, "@name:example.com"); - const r2 = new Room("r2", cli, "@name:example.com"); + it("renders info from only one room during room changes", async () => { + const r1 = mkRoom(cli, "r1"); + const r2 = mkRoom(cli, "r2"); - jest.spyOn(cli, "getRoom").mockImplementation(roomId => { + cli.getRoom.mockImplementation(roomId => { if (roomId === "r1") return r1; if (roomId === "r2") return r2; return null; @@ -70,35 +146,12 @@ describe("RightPanel", () => { return null; }); - // Wake up various stores we rely on - WidgetLayoutStore.instance.useUnitTestClient(cli); - // @ts-ignore - await WidgetLayoutStore.instance.onReady(); - RightPanelStore.instance.useUnitTestClient(cli); - // @ts-ignore - await RightPanelStore.instance.onReady(); - - const resizeNotifier = new ResizeNotifier(); + await spinUpStores(); // Run initial render with room 1, and also running lifecycle methods - const renderer = TestRenderer.create( - - ); + const wrapper = mount(); // Wait for RPS room 1 updates to fire - const rpsUpdated = new Promise(resolve => { - const update = () => { - if ( - RightPanelStore.instance.currentCardForRoom("r1").phase !== - RightPanelPhases.RoomMemberList - ) return; - RightPanelStore.instance.off(UPDATE_EVENT, update); - resolve(); - }; - RightPanelStore.instance.on(UPDATE_EVENT, update); - }); + const rpsUpdated = waitForRpsUpdate(); dis.dispatch({ action: Action.ViewRoom, room_id: "r1", @@ -108,7 +161,7 @@ describe("RightPanel", () => { // After all that setup, now to the interesting part... // We want to verify that as we change to room 2, we should always have // the correct right panel state for whichever room we are showing. - const instance = renderer.root.instance; + const instance = wrapper.find(_RightPanel).instance() as _RightPanel; const rendered = new Promise(resolve => { jest.spyOn(instance, "render").mockImplementation(() => { const { props, state } = instance; @@ -127,21 +180,8 @@ describe("RightPanel", () => { action: Action.ViewRoom, room_id: "r2", }); - renderer.update( - - ); + wrapper.setProps({ room: r2 }); await rendered; }); - - afterAll(async () => { - // @ts-ignore - await WidgetLayoutStore.instance.onNotReady(); - // @ts-ignore - await RightPanelStore.instance.onNotReady(); - jest.restoreAllMocks(); - }); }); diff --git a/test/stores/right-panel/RightPanelStore-test.ts b/test/stores/right-panel/RightPanelStore-test.ts index 68139b6d407..30edeea9fc0 100644 --- a/test/stores/right-panel/RightPanelStore-test.ts +++ b/test/stores/right-panel/RightPanelStore-test.ts @@ -16,9 +16,11 @@ limitations under the License. import { mocked, MockedObject } from "jest-mock"; import { MatrixClient } from "matrix-js-sdk/src/client"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { stubClient } from "../../test-utils"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; +import DMRoomMap from "../../../src/utils/DMRoomMap"; import { Action } from "../../../src/dispatcher/actions"; import defaultDispatcher from "../../../src/dispatcher/dispatcher"; import { ActiveRoomChangedPayload } from "../../../src/dispatcher/payloads/ActiveRoomChangedPayload"; @@ -35,6 +37,7 @@ describe("RightPanelStore", () => { beforeEach(() => { stubClient(); cli = mocked(MatrixClientPeg.get()); + DMRoomMap.makeShared(); // @ts-ignore // The constructor is private but we want to use it anyways to prevent @@ -204,4 +207,23 @@ describe("RightPanelStore", () => { expect(store.isOpen).toEqual(true); }); }); + + it("doesn't restore member info cards when switching back to a room", async () => { + await viewRoom("!1:example.org"); + store.setCards([ + { + phase: RightPanelPhases.RoomMemberList, + }, + { + phase: RightPanelPhases.RoomMemberInfo, + state: { member: new RoomMember("!1:example.org", "@alice:example.org") }, + }, + ], true, "!1:example.org"); + expect(store.currentCardForRoom("!1:example.org").phase).toEqual(RightPanelPhases.RoomMemberInfo); + + // Switch away and back + await viewRoom("!2:example.org"); + await viewRoom("!1:example.org"); + expect(store.currentCardForRoom("!1:example.org").phase).toEqual(RightPanelPhases.RoomMemberList); + }); }); From 2b322409ba0b1ee61f0ec69567ea117f3824dd59 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Fri, 17 Jun 2022 09:45:32 -0400 Subject: [PATCH 8/8] Fix onReady --- src/stores/right-panel/RightPanelStore.ts | 24 ++++++++++++------- .../components/structures/RightPanel-test.tsx | 3 +-- .../right-panel/RightPanelStore-test.ts | 8 +++---- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index 2cfafb26ac2..b37691a5a98 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -45,26 +45,34 @@ import { RoomViewStore } from "../RoomViewStore"; export default class RightPanelStore extends ReadyWatchingStore { private static internalInstance: RightPanelStore; - private global?: IRightPanelForRoom = null; - private byRoom: { - [roomId: string]: IRightPanelForRoom; - } = {}; + private global?: IRightPanelForRoom; + private byRoom: { [roomId: string]: IRightPanelForRoom }; private viewedRoomId: Optional; private constructor() { super(defaultDispatcher); + this.reset(); } - protected onReady = async (): Promise => { + /** + * Resets the store. Intended for test usage only. + */ + public reset() { + this.global = null; + this.byRoom = {}; + this.viewedRoomId = null; + } + + protected async onReady(): Promise { this.viewedRoomId = RoomViewStore.instance.getRoomId(); this.matrixClient.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); this.loadCacheFromSettings(); this.emitAndUpdateSettings(); - }; + } - protected onNotReady = async (): Promise => { + protected async onNotReady(): Promise { this.matrixClient.off(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); - }; + } protected onDispatcherAction(payload: ActionPayload) { if (payload.action !== Action.ActiveRoomChanged) return; diff --git a/test/components/structures/RightPanel-test.tsx b/test/components/structures/RightPanel-test.tsx index 9653fa6cd12..17e952e7fbd 100644 --- a/test/components/structures/RightPanel-test.tsx +++ b/test/components/structures/RightPanel-test.tsx @@ -70,9 +70,8 @@ describe("RightPanel", () => { // This is private but it's the only way to selectively enable stores await WidgetLayoutStore.instance.onReady(); - // @ts-ignore // Make sure we start with a clean store - RightPanelStore.internalInstance = null; + RightPanelStore.instance.reset(); RightPanelStore.instance.useUnitTestClient(cli); // @ts-ignore await RightPanelStore.instance.onReady(); diff --git a/test/stores/right-panel/RightPanelStore-test.ts b/test/stores/right-panel/RightPanelStore-test.ts index 30edeea9fc0..e7168dd010e 100644 --- a/test/stores/right-panel/RightPanelStore-test.ts +++ b/test/stores/right-panel/RightPanelStore-test.ts @@ -32,17 +32,15 @@ describe("RightPanelStore", () => { // Mock out the settings store so the right panel store can't persist values between tests jest.spyOn(SettingsStore, "setValue").mockImplementation(async () => {}); - let store: RightPanelStore; + const store = RightPanelStore.instance; let cli: MockedObject; beforeEach(() => { stubClient(); cli = mocked(MatrixClientPeg.get()); DMRoomMap.makeShared(); - // @ts-ignore - // The constructor is private but we want to use it anyways to prevent - // state from being shared between tests - store = new RightPanelStore(); + // Make sure we start with a clean store + store.reset(); store.useUnitTestClient(cli); });