diff --git a/playwright/e2e/read-receipts/index.ts b/playwright/e2e/read-receipts/index.ts index eab261042e..384cef1d5e 100644 --- a/playwright/e2e/read-receipts/index.ts +++ b/playwright/e2e/read-receipts/index.ts @@ -526,9 +526,10 @@ class Helpers { await expect(threadPanel).toBeVisible(); await threadPanel.evaluate(($panel) => { const $button = $panel.querySelector('[data-testid="base-card-back-button"]'); + const title = $panel.querySelector(".mx_BaseCard_header_title")?.textContent; // If the Threads back button is present then click it - the // threads button can open either threads list or thread panel - if ($button) { + if ($button && title !== "Threads") { $button.click(); } }); diff --git a/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png b/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png index 460eec3a8c..d8bab27faa 100644 Binary files a/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png and b/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png differ diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index 42077daded..b89851691a 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -30,6 +30,23 @@ import { ActiveRoomChangedPayload } from "../../dispatcher/payloads/ActiveRoomCh import { SdkContextClass } from "../../contexts/SDKContext"; import { MatrixClientPeg } from "../../MatrixClientPeg"; +/** + * @see RightPanelStore#generateHistoryForPhase + */ +function getPhasesForPhase(phase: IRightPanelCard["phase"]): RightPanelPhases[] { + switch (phase) { + case RightPanelPhases.ThreadPanel: + case RightPanelPhases.MemberList: + case RightPanelPhases.PinnedMessages: + return [RightPanelPhases.RoomSummary]; + case RightPanelPhases.MemberInfo: + case RightPanelPhases.ThreePidMemberInfo: + return [RightPanelPhases.RoomSummary, RightPanelPhases.MemberList]; + default: + return []; + } +} + /** * A class for tracking the state of the right panel between layouts and * sessions. This state includes a history for each room. Each history element @@ -134,16 +151,20 @@ export default class RightPanelStore extends ReadyWatchingStore { return { state: {}, phase: null }; } - // Setters + /** + * This function behaves as following: + * - If the same phase is sent along with a non-empty state, only the state is updated and history is retained. + * - If the provided phase is different to the current phase: + * - Existing history is thrown away. + * - New card is added along with a different history, see {@link generateHistoryForPhase} + * + * If the right panel was set, this function also shows the right panel. + */ public setCard(card: IRightPanelCard, allowClose = true, roomId?: string): void { const rId = roomId ?? this.viewedRoomId ?? ""; - // This function behaves as following: - // Update state: if the same phase is send but with a state - // Set right panel and erase history: if a "different to the current" phase is send (with or without a state) - // If the right panel is set, this function also shows the right panel. const redirect = this.getVerificationRedirect(card); const targetPhase = redirect?.phase ?? card.phase; - const cardState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? null : card.state); + const cardState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? undefined : card.state); // Checks for wrong SetRightPanelPhase requests if (!this.isPhaseValid(targetPhase, Boolean(rId))) return; @@ -155,7 +176,7 @@ export default class RightPanelStore extends ReadyWatchingStore { this.emitAndUpdateSettings(); } else if (targetPhase !== this.currentCardForRoom(rId)?.phase || !this.byRoom[rId]) { // Set right panel and initialize/erase history - const history = [{ phase: targetPhase, state: cardState ?? {} }]; + const history = this.generateHistoryForPhase(targetPhase!, cardState ?? {}); this.byRoom[rId] = { history, isOpen: true }; this.emitAndUpdateSettings(); } else { @@ -247,6 +268,31 @@ export default class RightPanelStore extends ReadyWatchingStore { } } + /** + * For a given phase, generates card history such that it looks + * similar to how an user typically would reach said phase in the app. + * eg: User would usually reach the memberlist via room-info panel, so + * that history is added. + */ + private generateHistoryForPhase( + phase: IRightPanelCard["phase"], + cardState?: Partial, + ): IRightPanelCard[] { + const card = { phase, state: cardState }; + if (!this.isCardStateValid(card)) { + /** + * If the card we're adding is not valid, then we just return + * an empty history. + * This is to avoid a scenario where, for eg, you set a member info + * card with invalid card state (no member) but the member list is + * shown since the created history is valid except for the last card. + */ + return []; + } + const cards = getPhasesForPhase(phase).map((p) => ({ phase: p, state: {} })); + return [...cards, card]; + } + private loadCacheFromSettings(): void { if (this.viewedRoomId) { const room = this.mxClient?.getRoom(this.viewedRoomId); diff --git a/test/unit-tests/stores/right-panel/RightPanelStore-test.ts b/test/unit-tests/stores/right-panel/RightPanelStore-test.ts index 1897649cee..8dc197405b 100644 --- a/test/unit-tests/stores/right-panel/RightPanelStore-test.ts +++ b/test/unit-tests/stores/right-panel/RightPanelStore-test.ts @@ -114,11 +114,14 @@ describe("RightPanelStore", () => { 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 () => { + it("history is generated for certain phases", async () => { await viewRoom("!1:example.org"); - store.setCard({ phase: RightPanelPhases.RoomSummary }, true, "!1:example.org"); + // Setting the memberlist card should also generate a history with room summary card store.setCard({ phase: RightPanelPhases.MemberList }, true, "!1:example.org"); - expect(store.roomPhaseHistory).toEqual([{ phase: RightPanelPhases.MemberList, state: {} }]); + expect(store.roomPhaseHistory).toEqual([ + { phase: RightPanelPhases.RoomSummary, state: {} }, + { phase: RightPanelPhases.MemberList, state: {} }, + ]); }); });