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

Commit dc7dfae

Browse files
authored
Merge branch 'develop' into fayed/decryption-error-ui
2 parents 79071c9 + 1f703b8 commit dc7dfae

File tree

6 files changed

+182
-941
lines changed

6 files changed

+182
-941
lines changed

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@
7272
"counterpart": "^0.18.6",
7373
"diff-dom": "^4.2.2",
7474
"diff-match-patch": "^1.0.5",
75-
"emojibase": "6.0.2",
76-
"emojibase-data": "7.0.0",
77-
"emojibase-regex": "6.0.0",
75+
"emojibase": "6.1.0",
76+
"emojibase-data": "7.0.1",
77+
"emojibase-regex": "6.0.1",
7878
"escape-html": "^1.0.3",
7979
"file-saver": "^2.0.5",
8080
"filesize": "6.1.0",
@@ -161,7 +161,7 @@
161161
"@types/lodash": "^4.14.168",
162162
"@types/modernizr": "^3.5.3",
163163
"@types/node": "^16",
164-
"@types/pako": "^1.0.1",
164+
"@types/pako": "^2.0.0",
165165
"@types/parse5": "^6.0.0",
166166
"@types/qrcode": "^1.3.5",
167167
"@types/react": "17.0.49",
@@ -176,7 +176,7 @@
176176
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
177177
"allchange": "^1.1.0",
178178
"axe-core": "4.4.3",
179-
"babel-jest": "^26.6.3",
179+
"babel-jest": "^29.0.0",
180180
"blob-polyfill": "^6.0.20211015",
181181
"chokidar": "^3.5.1",
182182
"cypress": "^10.3.0",
@@ -195,7 +195,7 @@
195195
"eslint-plugin-unicorn": "^44.0.2",
196196
"fetch-mock-jest": "^1.5.1",
197197
"fs-extra": "^11.0.0",
198-
"glob": "^7.1.6",
198+
"glob": "^8.0.0",
199199
"jest": "^29.2.2",
200200
"jest-canvas-mock": "^2.3.0",
201201
"jest-environment-jsdom": "^29.2.2",

src/Notifier.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
PermissionChanged as PermissionChangedEvent,
2828
} from "@matrix-org/analytics-events/types/typescript/PermissionChanged";
2929
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
30+
import { IRoomTimelineData } from "matrix-js-sdk/src/matrix";
3031

3132
import { MatrixClientPeg } from './MatrixClientPeg';
3233
import { PosthogAnalytics } from "./PosthogAnalytics";
@@ -217,7 +218,7 @@ export const Notifier = {
217218
this.boundOnRoomReceipt = this.boundOnRoomReceipt || this.onRoomReceipt.bind(this);
218219
this.boundOnEventDecrypted = this.boundOnEventDecrypted || this.onEventDecrypted.bind(this);
219220

220-
MatrixClientPeg.get().on(ClientEvent.Event, this.boundOnEvent);
221+
MatrixClientPeg.get().on(RoomEvent.Timeline, this.boundOnEvent);
221222
MatrixClientPeg.get().on(RoomEvent.Receipt, this.boundOnRoomReceipt);
222223
MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted);
223224
MatrixClientPeg.get().on(ClientEvent.Sync, this.boundOnSyncStateChange);
@@ -227,7 +228,7 @@ export const Notifier = {
227228

228229
stop: function(this: typeof Notifier) {
229230
if (MatrixClientPeg.get()) {
230-
MatrixClientPeg.get().removeListener(ClientEvent.Event, this.boundOnEvent);
231+
MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.boundOnEvent);
231232
MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.boundOnRoomReceipt);
232233
MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted);
233234
MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.boundOnSyncStateChange);
@@ -368,7 +369,15 @@ export const Notifier = {
368369
}
369370
},
370371

371-
onEvent: function(this: typeof Notifier, ev: MatrixEvent) {
372+
onEvent: function(
373+
this: typeof Notifier,
374+
ev: MatrixEvent,
375+
room: Room | undefined,
376+
toStartOfTimeline: boolean | undefined,
377+
removed: boolean,
378+
data: IRoomTimelineData,
379+
) {
380+
if (!data.liveEvent) return; // only notify for new things, not old.
372381
if (!this.isSyncing) return; // don't alert for any messages initially
373382
if (ev.getSender() === MatrixClientPeg.get().getUserId()) return;
374383

@@ -428,6 +437,11 @@ export const Notifier = {
428437
}
429438
}
430439
const room = MatrixClientPeg.get().getRoom(roomId);
440+
if (!room) {
441+
// e.g we are in the process of joining a room.
442+
// Seen in the cypress lazy-loading test.
443+
return;
444+
}
431445

432446
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
433447

src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,12 +324,13 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
324324
let privilegedUsersSection = <div>{ _t('No users have specific privileges in this room') }</div>;
325325
let mutedUsersSection;
326326
if (Object.keys(userLevels).length) {
327-
const privilegedUsers = [];
328-
const mutedUsers = [];
327+
const privilegedUsers: JSX.Element[] = [];
328+
const mutedUsers: JSX.Element[] = [];
329329

330330
Object.keys(userLevels).forEach((user) => {
331-
if (!Number.isInteger(userLevels[user])) { return; }
332-
const canChange = userLevels[user] < currentUserLevel && canChangeLevels;
331+
if (!Number.isInteger(userLevels[user])) return;
332+
const isMe = user === client.getUserId();
333+
const canChange = canChangeLevels && (userLevels[user] < currentUserLevel || isMe);
333334
if (userLevels[user] > defaultUserLevel) { // privileged
334335
privilegedUsers.push(
335336
<PowerSelector

test/Notifier-test.ts

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ limitations under the License.
1616

1717
import { mocked, MockedObject } from "jest-mock";
1818
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
19-
import { Room } from "matrix-js-sdk/src/models/room";
20-
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
19+
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
20+
import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event";
2121
import { SyncState } from "matrix-js-sdk/src/sync";
2222
import { waitFor } from "@testing-library/react";
2323

@@ -60,12 +60,19 @@ describe("Notifier", () => {
6060
let mockClient: MockedObject<MatrixClient>;
6161
let testRoom: Room;
6262
let accountDataEventKey: string;
63-
let accountDataStore = {};
63+
let accountDataStore: Record<string, MatrixEvent | undefined> = {};
6464

6565
let mockSettings: Record<string, boolean> = {};
6666

6767
const userId = "@bob:example.org";
6868

69+
const emitLiveEvent = (event: MatrixEvent): void => {
70+
mockClient!.emit(RoomEvent.Timeline, event, testRoom, false, false, {
71+
liveEvent: true,
72+
timeline: testRoom.getLiveTimeline(),
73+
});
74+
};
75+
6976
beforeEach(() => {
7077
accountDataStore = {};
7178
mockClient = getMockClientWithEventEmitter({
@@ -150,7 +157,7 @@ describe("Notifier", () => {
150157
});
151158

152159
it('does not create notifications before syncing has started', () => {
153-
mockClient!.emit(ClientEvent.Event, event);
160+
emitLiveEvent(event);
154161

155162
expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
156163
expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
@@ -160,7 +167,30 @@ describe("Notifier", () => {
160167
const ownEvent = new MatrixEvent({ sender: userId });
161168

162169
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
163-
mockClient!.emit(ClientEvent.Event, ownEvent);
170+
emitLiveEvent(ownEvent);
171+
172+
expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
173+
expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
174+
});
175+
176+
it('does not create notifications for non-live events (scrollback)', () => {
177+
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
178+
mockClient!.emit(RoomEvent.Timeline, event, testRoom, false, false, {
179+
liveEvent: false,
180+
timeline: testRoom.getLiveTimeline(),
181+
});
182+
183+
expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
184+
expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
185+
});
186+
187+
it('does not create notifications for rooms which cannot be obtained via client.getRoom', () => {
188+
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
189+
mockClient.getRoom.mockReturnValue(null);
190+
mockClient!.emit(RoomEvent.Timeline, event, testRoom, false, false, {
191+
liveEvent: true,
192+
timeline: testRoom.getLiveTimeline(),
193+
});
164194

165195
expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
166196
expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
@@ -175,15 +205,15 @@ describe("Notifier", () => {
175205
});
176206

177207
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
178-
mockClient!.emit(ClientEvent.Event, event);
208+
emitLiveEvent(event);
179209

180210
expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
181211
expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
182212
});
183213

184214
it('creates desktop notification when enabled', () => {
185215
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
186-
mockClient!.emit(ClientEvent.Event, event);
216+
emitLiveEvent(event);
187217

188218
expect(MockPlatform.displayNotification).toHaveBeenCalledWith(
189219
testRoom.name,
@@ -196,7 +226,7 @@ describe("Notifier", () => {
196226

197227
it('creates a loud notification when enabled', () => {
198228
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
199-
mockClient!.emit(ClientEvent.Event, event);
229+
emitLiveEvent(event);
200230

201231
expect(MockPlatform.loudNotification).toHaveBeenCalledWith(
202232
event, testRoom,
@@ -212,7 +242,7 @@ describe("Notifier", () => {
212242
});
213243

214244
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
215-
mockClient!.emit(ClientEvent.Event, event);
245+
emitLiveEvent(event);
216246

217247
// desktop notification created
218248
expect(MockPlatform.displayNotification).toHaveBeenCalled();
@@ -222,12 +252,13 @@ describe("Notifier", () => {
222252
});
223253

224254
describe("_displayPopupNotification", () => {
225-
it.each([
255+
const testCases: {event: IContent | undefined, count: number}[] = [
226256
{ event: { is_silenced: true }, count: 0 },
227257
{ event: { is_silenced: false }, count: 1 },
228258
{ event: undefined, count: 1 },
229-
])("does not dispatch when notifications are silenced", ({ event, count }) => {
230-
mockClient.setAccountData(accountDataEventKey, event);
259+
];
260+
it.each(testCases)("does not dispatch when notifications are silenced", ({ event, count }) => {
261+
mockClient.setAccountData(accountDataEventKey, event!);
231262
Notifier._displayPopupNotification(testEvent, testRoom);
232263
expect(MockPlatform.displayNotification).toHaveBeenCalledTimes(count);
233264
});
@@ -243,16 +274,17 @@ describe("Notifier", () => {
243274
});
244275

245276
describe("_playAudioNotification", () => {
246-
it.each([
277+
const testCases: {event: IContent | undefined, count: number}[] = [
247278
{ event: { is_silenced: true }, count: 0 },
248279
{ event: { is_silenced: false }, count: 1 },
249280
{ event: undefined, count: 1 },
250-
])("does not dispatch when notifications are silenced", ({ event, count }) => {
281+
];
282+
it.each(testCases)("does not dispatch when notifications are silenced", ({ event, count }) => {
251283
// It's not ideal to only look at whether this function has been called
252284
// but avoids starting to look into DOM stuff
253285
Notifier.getSoundForRoom = jest.fn();
254286

255-
mockClient.setAccountData(accountDataEventKey, event);
287+
mockClient.setAccountData(accountDataEventKey, event!);
256288
Notifier._playAudioNotification(testEvent, testRoom);
257289
expect(Notifier.getSoundForRoom).toHaveBeenCalledTimes(count);
258290
});
@@ -267,7 +299,7 @@ describe("Notifier", () => {
267299
notify: true,
268300
tweaks: {},
269301
});
270-
302+
Notifier.start();
271303
Notifier.onSyncStateChange(SyncState.Syncing);
272304
});
273305

@@ -283,7 +315,7 @@ describe("Notifier", () => {
283315
content: {},
284316
event: true,
285317
});
286-
Notifier.onEvent(callEvent);
318+
emitLiveEvent(callEvent);
287319
return callEvent;
288320
};
289321

test/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import React from "react";
1818
import { fireEvent, render, RenderResult } from "@testing-library/react";
1919
import { MatrixClient } from "matrix-js-sdk/src/client";
2020
import { EventType } from "matrix-js-sdk/src/@types/event";
21+
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
22+
import { Room } from "matrix-js-sdk/src/models/room";
23+
import { mocked } from "jest-mock";
2124

2225
import RolesRoomSettingsTab from "../../../../../../src/components/views/settings/tabs/room/RolesRoomSettingsTab";
2326
import { mkStubRoom, stubClient } from "../../../../../test-utils";
@@ -29,23 +32,52 @@ import { ElementCall } from "../../../../../../src/models/Call";
2932
describe("RolesRoomSettingsTab", () => {
3033
const roomId = "!room:example.com";
3134
let cli: MatrixClient;
35+
let room: Room;
3236

3337
const renderTab = (): RenderResult => {
3438
return render(<RolesRoomSettingsTab roomId={roomId} />);
3539
};
3640

37-
const getVoiceBroadcastsSelect = () => {
41+
const getVoiceBroadcastsSelect = (): HTMLElement => {
3842
return renderTab().container.querySelector("select[label='Voice broadcasts']");
3943
};
4044

41-
const getVoiceBroadcastsSelectedOption = () => {
45+
const getVoiceBroadcastsSelectedOption = (): HTMLElement => {
4246
return renderTab().container.querySelector("select[label='Voice broadcasts'] option:checked");
4347
};
4448

4549
beforeEach(() => {
4650
stubClient();
4751
cli = MatrixClientPeg.get();
48-
mkStubRoom(roomId, "test room", cli);
52+
room = mkStubRoom(roomId, "test room", cli);
53+
});
54+
55+
it("should allow an Admin to demote themselves but not others", () => {
56+
mocked(cli.getRoom).mockReturnValue(room);
57+
// @ts-ignore - mocked doesn't support overloads properly
58+
mocked(room.currentState.getStateEvents).mockImplementation((type, key) => {
59+
if (key === undefined) return [] as MatrixEvent[];
60+
if (type === "m.room.power_levels") {
61+
return new MatrixEvent({
62+
sender: "@sender:server",
63+
room_id: roomId,
64+
type: "m.room.power_levels",
65+
state_key: "",
66+
content: {
67+
users: {
68+
[cli.getUserId()]: 100,
69+
"@admin:server": 100,
70+
},
71+
},
72+
});
73+
}
74+
return null;
75+
});
76+
mocked(room.currentState.mayClientSendStateEvent).mockReturnValue(true);
77+
const { container } = renderTab();
78+
79+
expect(container.querySelector(`[placeholder="${cli.getUserId()}"]`)).not.toBeDisabled();
80+
expect(container.querySelector(`[placeholder="@admin:server"]`)).toBeDisabled();
4981
});
5082

5183
it("should initially show »Moderator« permission for »Voice broadcasts«", () => {
@@ -79,19 +111,19 @@ describe("RolesRoomSettingsTab", () => {
79111
});
80112
};
81113

82-
const getStartCallSelect = (tab: RenderResult) => {
114+
const getStartCallSelect = (tab: RenderResult): HTMLElement => {
83115
return tab.container.querySelector("select[label='Start Element Call calls']");
84116
};
85117

86-
const getStartCallSelectedOption = (tab: RenderResult) => {
118+
const getStartCallSelectedOption = (tab: RenderResult): HTMLElement => {
87119
return tab.container.querySelector("select[label='Start Element Call calls'] option:checked");
88120
};
89121

90-
const getJoinCallSelect = (tab: RenderResult) => {
122+
const getJoinCallSelect = (tab: RenderResult): HTMLElement => {
91123
return tab.container.querySelector("select[label='Join Element Call calls']");
92124
};
93125

94-
const getJoinCallSelectedOption = (tab: RenderResult) => {
126+
const getJoinCallSelectedOption = (tab: RenderResult): HTMLElement => {
95127
return tab.container.querySelector("select[label='Join Element Call calls'] option:checked");
96128
};
97129

0 commit comments

Comments
 (0)