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

Commit ea45c3d

Browse files
authored
Merge branch 'andybalaam/dynamic-predecessor-in-roomprovider' into andybalaam/dynamic-predecessor-in-spaceprovider
2 parents fc6d1d7 + 3dcf9cb commit ea45c3d

File tree

11 files changed

+525
-5
lines changed

11 files changed

+525
-5
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
Copyright 2022 - 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+
/// <reference types="cypress" />
18+
19+
import { HomeserverInstance } from "../../plugins/utils/homeserver";
20+
import { MatrixClient } from "../../global";
21+
22+
describe("Poll history", () => {
23+
let homeserver: HomeserverInstance;
24+
25+
type CreatePollOptions = {
26+
title: string;
27+
options: {
28+
"id": string;
29+
"org.matrix.msc1767.text": string;
30+
}[];
31+
};
32+
const createPoll = async ({ title, options }: CreatePollOptions, roomId, client: MatrixClient) => {
33+
return await client.sendEvent(roomId, "org.matrix.msc3381.poll.start", {
34+
"org.matrix.msc3381.poll.start": {
35+
question: {
36+
"org.matrix.msc1767.text": title,
37+
"body": title,
38+
"msgtype": "m.text",
39+
},
40+
kind: "org.matrix.msc3381.poll.disclosed",
41+
max_selections: 1,
42+
answers: options,
43+
},
44+
"org.matrix.msc1767.text": "poll fallback text",
45+
});
46+
};
47+
48+
const botVoteForOption = async (
49+
bot: MatrixClient,
50+
roomId: string,
51+
pollId: string,
52+
optionId: string,
53+
): Promise<void> => {
54+
// We can't use the js-sdk types for this stuff directly, so manually construct the event.
55+
await bot.sendEvent(roomId, "org.matrix.msc3381.poll.response", {
56+
"m.relates_to": {
57+
rel_type: "m.reference",
58+
event_id: pollId,
59+
},
60+
"org.matrix.msc3381.poll.response": {
61+
answers: [optionId],
62+
},
63+
});
64+
};
65+
66+
const endPoll = async (bot: MatrixClient, roomId: string, pollId: string): Promise<void> => {
67+
// We can't use the js-sdk types for this stuff directly, so manually construct the event.
68+
await bot.sendEvent(roomId, "org.matrix.msc3381.poll.end", {
69+
"m.relates_to": {
70+
rel_type: "m.reference",
71+
event_id: pollId,
72+
},
73+
"org.matrix.msc1767.text": "The poll has ended",
74+
});
75+
};
76+
77+
function openPollHistory(): void {
78+
cy.get('.mx_HeaderButtons [aria-label="Room info"]').click();
79+
cy.get(".mx_RoomSummaryCard").within(() => {
80+
cy.contains("Polls history").click();
81+
});
82+
}
83+
84+
beforeEach(() => {
85+
cy.window().then((win) => {
86+
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
87+
});
88+
cy.startHomeserver("default").then((data) => {
89+
homeserver = data;
90+
91+
cy.enableLabsFeature("feature_poll_history");
92+
93+
cy.initTestUser(homeserver, "Tom");
94+
});
95+
});
96+
97+
afterEach(() => {
98+
cy.stopHomeserver(homeserver);
99+
});
100+
101+
it("Should display active and past polls", () => {
102+
let bot: MatrixClient;
103+
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
104+
bot = _bot;
105+
});
106+
107+
const pollParams1 = {
108+
title: "Does the polls feature work?",
109+
options: ["Yes", "No", "Maybe"].map((option) => ({
110+
"id": option,
111+
"org.matrix.msc1767.text": option,
112+
})),
113+
};
114+
115+
const pollParams2 = {
116+
title: "Which way",
117+
options: ["Left", "Right"].map((option) => ({
118+
"id": option,
119+
"org.matrix.msc1767.text": option,
120+
})),
121+
};
122+
123+
cy.createRoom({}).as("roomId");
124+
125+
cy.get<string>("@roomId").then((roomId) => {
126+
cy.inviteUser(roomId, bot.getUserId());
127+
cy.visit("/#/room/" + roomId);
128+
// wait until Bob joined
129+
cy.contains(".mx_TextualEvent", "BotBob joined the room").should("exist");
130+
});
131+
132+
// active poll
133+
cy.get<string>("@roomId")
134+
.then(async (roomId) => {
135+
const { event_id: pollId } = await createPoll(pollParams1, roomId, bot);
136+
await botVoteForOption(bot, roomId, pollId, pollParams1.options[1].id);
137+
return pollId;
138+
})
139+
.as("pollId1");
140+
141+
// ended poll
142+
cy.get<string>("@roomId")
143+
.then(async (roomId) => {
144+
const { event_id: pollId } = await createPoll(pollParams2, roomId, bot);
145+
await botVoteForOption(bot, roomId, pollId, pollParams1.options[1].id);
146+
await endPoll(bot, roomId, pollId);
147+
return pollId;
148+
})
149+
.as("pollId2");
150+
151+
openPollHistory();
152+
153+
// these polls are also in the timeline
154+
// focus on the poll history dialog
155+
cy.get(".mx_Dialog").within(() => {
156+
// active poll is in active polls list
157+
// open poll detail
158+
cy.contains(pollParams1.title).click();
159+
160+
// vote in the poll
161+
cy.contains("Yes").click();
162+
cy.get('[data-testid="totalVotes"]').should("have.text", "Based on 2 votes");
163+
164+
// navigate back to list
165+
cy.contains("Active polls").click();
166+
167+
// go to past polls list
168+
cy.contains("Past polls").click();
169+
170+
cy.contains(pollParams2.title).should("exist");
171+
});
172+
173+
// end poll1 while dialog is open
174+
cy.all([cy.get<string>("@roomId"), cy.get<string>("@pollId1")]).then(async ([roomId, pollId]) => {
175+
return endPoll(bot, roomId, pollId);
176+
});
177+
178+
cy.get(".mx_Dialog").within(() => {
179+
// both ended polls are in past polls list
180+
cy.contains(pollParams2.title).should("exist");
181+
cy.contains(pollParams1.title).should("exist");
182+
183+
cy.contains("Active polls").click();
184+
185+
// no more active polls
186+
cy.contains("There are no active polls in this room").should("exist");
187+
});
188+
});
189+
});

src/components/structures/SpaceHierarchy.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import { Alignment } from "../views/elements/Tooltip";
6767
import { getTopic } from "../../hooks/room/useTopic";
6868
import { SdkContextClass } from "../../contexts/SDKContext";
6969
import { getDisplayAliasForAliasSet } from "../../Rooms";
70+
import SettingsStore from "../../settings/SettingsStore";
7071

7172
interface IProps {
7273
space: Room;
@@ -425,7 +426,11 @@ interface IHierarchyLevelProps {
425426
}
426427

427428
export const toLocalRoom = (cli: MatrixClient, room: IHierarchyRoom, hierarchy: RoomHierarchy): IHierarchyRoom => {
428-
const history = cli.getRoomUpgradeHistory(room.room_id, true);
429+
const history = cli.getRoomUpgradeHistory(
430+
room.room_id,
431+
true,
432+
SettingsStore.getValue("feature_dynamic_room_predecessors"),
433+
);
429434

430435
// Pick latest room that is actually part of the hierarchy
431436
let cliRoom = null;

src/components/views/dialogs/AddExistingToSpaceDialog.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import ProgressBar from "../elements/ProgressBar";
3939
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
4040
import QueryMatcher from "../../../autocomplete/QueryMatcher";
4141
import LazyRenderList from "../elements/LazyRenderList";
42+
import { useSettingValue } from "../../../hooks/useSettings";
4243

4344
// These values match CSS
4445
const ROW_HEIGHT = 32 + 12;
@@ -135,7 +136,11 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
135136
onFinished,
136137
}) => {
137138
const cli = useContext(MatrixClientContext);
138-
const visibleRooms = useMemo(() => cli.getVisibleRooms().filter((r) => r.getMyMembership() === "join"), [cli]);
139+
const msc3946ProcessDynamicPredecessor = useSettingValue<boolean>("feature_dynamic_room_predecessors");
140+
const visibleRooms = useMemo(
141+
() => cli.getVisibleRooms(msc3946ProcessDynamicPredecessor).filter((r) => r.getMyMembership() === "join"),
142+
[cli, msc3946ProcessDynamicPredecessor],
143+
);
139144

140145
const scrollRef = useRef<AutoHideScrollbar<"div">>();
141146
const [scrollState, setScrollState] = useState<IScrollState>({

src/components/views/dialogs/ForwardDialog.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,16 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
227227
const lcQuery = query.toLowerCase();
228228

229229
const previewLayout = useSettingValue<Layout>("layout");
230+
const msc3946DynamicRoomPredecessors = useSettingValue<boolean>("feature_dynamic_room_predecessors");
230231

231232
let rooms = useMemo(
232233
() =>
233-
sortRooms(cli.getVisibleRooms().filter((room) => room.getMyMembership() === "join" && !room.isSpaceRoom())),
234-
[cli],
234+
sortRooms(
235+
cli
236+
.getVisibleRooms(msc3946DynamicRoomPredecessors)
237+
.filter((room) => room.getMyMembership() === "join" && !room.isSpaceRoom()),
238+
),
239+
[cli, msc3946DynamicRoomPredecessors],
235240
);
236241

237242
if (lcQuery) {

src/stores/OwnBeaconStore.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
} from "../utils/beacon";
4444
import { getCurrentPosition } from "../utils/beacon";
4545
import { doMaybeLocalRoomAction } from "../utils/local-room";
46+
import SettingsStore from "../settings/SettingsStore";
4647

4748
const isOwnBeacon = (beacon: Beacon, userId: string): boolean => beacon.beaconInfoOwner === userId;
4849

@@ -119,6 +120,10 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
119120
* when the target is stationary
120121
*/
121122
private lastPublishedPositionTimestamp?: number;
123+
/**
124+
* Ref returned from watchSetting for the MSC3946 labs flag
125+
*/
126+
private dynamicWatcherRef: string | undefined;
122127

123128
public constructor() {
124129
super(defaultDispatcher);
@@ -142,7 +147,12 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
142147
this.matrixClient.removeListener(BeaconEvent.Update, this.onUpdateBeacon);
143148
this.matrixClient.removeListener(BeaconEvent.Destroy, this.onDestroyBeacon);
144149
this.matrixClient.removeListener(RoomStateEvent.Members, this.onRoomStateMembers);
150+
SettingsStore.unwatchSetting(this.dynamicWatcherRef ?? "");
151+
152+
this.clearBeacons();
153+
}
145154

155+
private clearBeacons(): void {
146156
this.beacons.forEach((beacon) => beacon.destroy());
147157

148158
this.stopPollingLocation();
@@ -159,6 +169,11 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
159169
this.matrixClient.on(BeaconEvent.Update, this.onUpdateBeacon);
160170
this.matrixClient.on(BeaconEvent.Destroy, this.onDestroyBeacon);
161171
this.matrixClient.on(RoomStateEvent.Members, this.onRoomStateMembers);
172+
this.dynamicWatcherRef = SettingsStore.watchSetting(
173+
"feature_dynamic_room_predecessors",
174+
null,
175+
this.reinitialiseBeaconState,
176+
);
162177

163178
this.initialiseBeaconState();
164179
}
@@ -308,9 +323,19 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
308323
);
309324
}
310325

326+
/**
327+
* @internal public for test only
328+
*/
329+
public reinitialiseBeaconState = (): void => {
330+
this.clearBeacons();
331+
this.initialiseBeaconState();
332+
};
333+
311334
private initialiseBeaconState = (): void => {
312335
const userId = this.matrixClient.getUserId()!;
313-
const visibleRooms = this.matrixClient.getVisibleRooms();
336+
const visibleRooms = this.matrixClient.getVisibleRooms(
337+
SettingsStore.getValue("feature_dynamic_room_predecessors"),
338+
);
314339

315340
visibleRooms.forEach((room) => {
316341
const roomState = room.currentState;

test/components/structures/SpaceHierarchy-test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ limitations under the License.
1515
*/
1616

1717
import React from "react";
18+
import { mocked } from "jest-mock";
1819
import { render } from "@testing-library/react";
1920
import { MatrixClient } from "matrix-js-sdk/src/client";
2021
import { Room } from "matrix-js-sdk/src/models/room";
@@ -28,6 +29,7 @@ import { HierarchyLevel, showRoom, toLocalRoom } from "../../../src/components/s
2829
import { Action } from "../../../src/dispatcher/actions";
2930
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
3031
import DMRoomMap from "../../../src/utils/DMRoomMap";
32+
import SettingsStore from "../../../src/settings/SettingsStore";
3133

3234
// Fake random strings to give a predictable snapshot for checkbox IDs
3335
jest.mock("matrix-js-sdk/src/randomstring", () => {
@@ -128,6 +130,34 @@ describe("SpaceHierarchy", () => {
128130
const localRoomV3 = toLocalRoom(client, { room_id: roomV3.roomId } as IHierarchyRoom, hierarchy);
129131
expect(localRoomV3.room_id).toEqual(roomV3.roomId);
130132
});
133+
134+
describe("If the feature_dynamic_room_predecessors is not enabled", () => {
135+
beforeEach(() => {
136+
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
137+
});
138+
it("Passes through the dynamic predecessor setting", async () => {
139+
mocked(client.getRoomUpgradeHistory).mockClear();
140+
const hierarchy = { roomMap: new Map([]) } as RoomHierarchy;
141+
toLocalRoom(client, { room_id: roomV1.roomId } as IHierarchyRoom, hierarchy);
142+
expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(roomV1.roomId, true, false);
143+
});
144+
});
145+
146+
describe("If the feature_dynamic_room_predecessors is enabled", () => {
147+
beforeEach(() => {
148+
// Turn on feature_dynamic_room_predecessors setting
149+
jest.spyOn(SettingsStore, "getValue").mockImplementation(
150+
(settingName) => settingName === "feature_dynamic_room_predecessors",
151+
);
152+
});
153+
154+
it("Passes through the dynamic predecessor setting", async () => {
155+
mocked(client.getRoomUpgradeHistory).mockClear();
156+
const hierarchy = { roomMap: new Map([]) } as RoomHierarchy;
157+
toLocalRoom(client, { room_id: roomV1.roomId } as IHierarchyRoom, hierarchy);
158+
expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(roomV1.roomId, true, true);
159+
});
160+
});
131161
});
132162

133163
describe("<HierarchyLevel />", () => {

test/components/views/beacon/RoomLiveShareWarning-test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ describe("<RoomLiveShareWarning />", () => {
4444
getUserId: jest.fn().mockReturnValue(aliceId),
4545
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
4646
sendEvent: jest.fn(),
47+
isGuest: jest.fn().mockReturnValue(false),
4748
});
4849

4950
// 14.03.2022 16:15

0 commit comments

Comments
 (0)