Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 4ee57a3

Browse files
authored
Support dynamic room predecessors in BreadcrumbsStore (#10295)
* Tests for BreadcrumbsStore.meetsRoomRequirements * Tests for appending rooms to BreadcrumbsStore * Support dynamic room predecessors in BreadcrumbsStore
1 parent bee4759 commit 4ee57a3

File tree

2 files changed

+221
-2
lines changed

2 files changed

+221
-2
lines changed

Diff for: src/stores/BreadcrumbsStore.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,17 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
6565
return !!this.state.enabled && this.meetsRoomRequirement;
6666
}
6767

68+
/**
69+
* Do we have enough rooms to justify showing the breadcrumbs?
70+
* (Or is the labs feature enabled?)
71+
*
72+
* @returns true if there are at least 20 visible rooms or
73+
* feature_breadcrumbs_v2 is enabled.
74+
*/
6875
public get meetsRoomRequirement(): boolean {
6976
if (SettingsStore.getValue("feature_breadcrumbs_v2")) return true;
70-
return this.matrixClient?.getVisibleRooms().length >= 20;
77+
const msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors");
78+
return this.matrixClient?.getVisibleRooms(msc3946ProcessDynamicPredecessor).length >= 20;
7179
}
7280

7381
protected async onAction(payload: SettingUpdatedPayload | ViewRoomPayload | JoinRoomPayload): Promise<void> {
@@ -138,10 +146,11 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
138146
private async appendRoom(room: Room): Promise<void> {
139147
let updated = false;
140148
const rooms = (this.state.rooms || []).slice(); // cheap clone
149+
const msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors");
141150

142151
// If the room is upgraded, use that room instead. We'll also splice out
143152
// any children of the room.
144-
const history = this.matrixClient.getRoomUpgradeHistory(room.roomId);
153+
const history = this.matrixClient.getRoomUpgradeHistory(room.roomId, false, msc3946ProcessDynamicPredecessor);
145154
if (history.length > 1) {
146155
room = history[history.length - 1]; // Last room is most recent in history
147156

Diff for: test/stores/BreadcrumbsStore-test.ts

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { mocked } from "jest-mock";
18+
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
19+
import { act } from "react-dom/test-utils";
20+
21+
import { createTestClient, flushPromises, setupAsyncStoreWithClient } from "../test-utils";
22+
import SettingsStore from "../../src/settings/SettingsStore";
23+
import { BreadcrumbsStore } from "../../src/stores/BreadcrumbsStore";
24+
import { Action } from "../../src/dispatcher/actions";
25+
import { defaultDispatcher } from "../../src/dispatcher/dispatcher";
26+
27+
describe("BreadcrumbsStore", () => {
28+
let store: BreadcrumbsStore;
29+
const client: MatrixClient = createTestClient();
30+
31+
beforeEach(() => {
32+
jest.resetAllMocks();
33+
store = BreadcrumbsStore.instance;
34+
setupAsyncStoreWithClient(store, client);
35+
jest.spyOn(SettingsStore, "setValue").mockImplementation(() => Promise.resolve());
36+
});
37+
38+
describe("If the feature_breadcrumbs_v2 feature is not enabled", () => {
39+
beforeEach(() => {
40+
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
41+
});
42+
43+
it("does not meet room requirements if there are not enough rooms", () => {
44+
// We don't have enough rooms, so we don't meet requirements
45+
mocked(client.getVisibleRooms).mockReturnValue(fakeRooms(2));
46+
expect(store.meetsRoomRequirement).toBe(false);
47+
});
48+
49+
it("meets room requirements if there are enough rooms", () => {
50+
// We do have enough rooms to show breadcrumbs
51+
mocked(client.getVisibleRooms).mockReturnValue(fakeRooms(25));
52+
expect(store.meetsRoomRequirement).toBe(true);
53+
});
54+
55+
describe("And the feature_dynamic_room_predecessors is enabled", () => {
56+
beforeEach(() => {
57+
// Turn on feature_dynamic_room_predecessors setting
58+
jest.spyOn(SettingsStore, "getValue").mockImplementation(
59+
(settingName) => settingName === "feature_dynamic_room_predecessors",
60+
);
61+
});
62+
63+
it("passes through the dynamic room precessors flag", () => {
64+
mocked(client.getVisibleRooms).mockReturnValue(fakeRooms(25));
65+
store.meetsRoomRequirement;
66+
expect(client.getVisibleRooms).toHaveBeenCalledWith(true);
67+
});
68+
});
69+
70+
describe("And the feature_dynamic_room_predecessors is not enabled", () => {
71+
it("passes through the dynamic room precessors flag", () => {
72+
mocked(client.getVisibleRooms).mockReturnValue(fakeRooms(25));
73+
store.meetsRoomRequirement;
74+
expect(client.getVisibleRooms).toHaveBeenCalledWith(false);
75+
});
76+
});
77+
});
78+
79+
describe("If the feature_breadcrumbs_v2 feature is enabled", () => {
80+
beforeEach(() => {
81+
// Turn on feature_breadcrumbs_v2 setting
82+
jest.spyOn(SettingsStore, "getValue").mockImplementation(
83+
(settingName) => settingName === "feature_breadcrumbs_v2",
84+
);
85+
});
86+
87+
it("always meets room requirements", () => {
88+
// With enough rooms, we meet requirements
89+
mocked(client.getVisibleRooms).mockReturnValue(fakeRooms(25));
90+
expect(store.meetsRoomRequirement).toBe(true);
91+
92+
// And even with not enough we do, because the feature is enabled.
93+
mocked(client.getVisibleRooms).mockReturnValue(fakeRooms(2));
94+
expect(store.meetsRoomRequirement).toBe(true);
95+
});
96+
});
97+
98+
describe("If the feature_dynamic_room_predecessors is not enabled", () => {
99+
beforeEach(() => {
100+
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
101+
});
102+
103+
it("Appends a room when you join", async () => {
104+
// Sanity: no rooms initially
105+
expect(store.rooms).toEqual([]);
106+
107+
// Given a room
108+
const room = fakeRoom();
109+
mocked(client.getRoom).mockReturnValue(room);
110+
mocked(client.getRoomUpgradeHistory).mockReturnValue([]);
111+
112+
// When we hear that we have joined it
113+
await dispatchJoinRoom(room.roomId);
114+
115+
// It is stored in the store's room list
116+
expect(store.rooms.map((r) => r.roomId)).toEqual([room.roomId]);
117+
});
118+
119+
it("Replaces the old room when a newer one joins", async () => {
120+
// Given an old room and a new room
121+
const oldRoom = fakeRoom();
122+
const newRoom = fakeRoom();
123+
mocked(client.getRoom).mockImplementation((roomId) => {
124+
if (roomId === oldRoom.roomId) return oldRoom;
125+
return newRoom;
126+
});
127+
// Where the new one is a predecessor of the old one
128+
mocked(client.getRoomUpgradeHistory).mockReturnValue([oldRoom, newRoom]);
129+
130+
// When we hear that we joined the old room, then the new one
131+
await dispatchJoinRoom(oldRoom.roomId);
132+
await dispatchJoinRoom(newRoom.roomId);
133+
134+
// The store only has the new one
135+
expect(store.rooms.map((r) => r.roomId)).toEqual([newRoom.roomId]);
136+
});
137+
138+
it("Passes through the dynamic predecessor setting", async () => {
139+
// Given a room
140+
const room = fakeRoom();
141+
mocked(client.getRoom).mockReturnValue(room);
142+
mocked(client.getRoomUpgradeHistory).mockReturnValue([]);
143+
144+
// When we signal that we have joined
145+
await dispatchJoinRoom(room.roomId);
146+
147+
// We pass the value of the dynamic predecessor setting through
148+
expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, false, false);
149+
});
150+
});
151+
152+
describe("If the feature_dynamic_room_predecessors is enabled", () => {
153+
beforeEach(() => {
154+
// Turn on feature_dynamic_room_predecessors setting
155+
jest.spyOn(SettingsStore, "getValue").mockImplementation(
156+
(settingName) => settingName === "feature_dynamic_room_predecessors",
157+
);
158+
});
159+
160+
it("Passes through the dynamic predecessor setting", async () => {
161+
// Given a room
162+
const room = fakeRoom();
163+
mocked(client.getRoom).mockReturnValue(room);
164+
mocked(client.getRoomUpgradeHistory).mockReturnValue([]);
165+
166+
// When we signal that we have joined
167+
await dispatchJoinRoom(room.roomId);
168+
169+
// We pass the value of the dynamic predecessor setting through
170+
expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, false, true);
171+
});
172+
});
173+
174+
/**
175+
* Send a JoinRoom event via the dispatcher, and wait for it to process.
176+
*/
177+
async function dispatchJoinRoom(roomId: string) {
178+
defaultDispatcher.dispatch(
179+
{
180+
action: Action.JoinRoom,
181+
roomId,
182+
metricsTrigger: null,
183+
},
184+
true, // synchronous dispatch
185+
);
186+
187+
// Wait for event dispatch to happen
188+
await act(async () => {
189+
await flushPromises();
190+
});
191+
}
192+
193+
/**
194+
* Create as many fake rooms in an array as you ask for.
195+
*/
196+
function fakeRooms(howMany: number): Array<Room> {
197+
const ret = [];
198+
for (let i = 0; i < howMany; i++) {
199+
ret.push(fakeRoom());
200+
}
201+
return ret;
202+
}
203+
204+
let roomIdx = 0;
205+
206+
function fakeRoom(): Room {
207+
roomIdx++;
208+
return new Room(`room${roomIdx}`, client, "@user:example.com");
209+
}
210+
});

0 commit comments

Comments
 (0)