Display info dialogs if unable to start voice broadcasts (#9453)

This commit is contained in:
Michael Weimann 2022-10-19 15:01:14 +02:00 committed by GitHub
parent 3c3df11d32
commit bb0c175b7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 546 additions and 160 deletions

View file

@ -147,7 +147,7 @@ describe("MessageComposer", () => {
beforeEach(() => {
SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value);
wrapper = wrapAndRender({ room, showVoiceBroadcastButton: true });
wrapper = wrapAndRender({ room });
});
it(`should pass the prop ${prop} = ${value}`, () => {
@ -174,17 +174,6 @@ describe("MessageComposer", () => {
});
});
[false, undefined].forEach((value) => {
it(`should pass showVoiceBroadcastButton = false if the MessageComposer prop is ${value}`, () => {
SettingsStore.setValue(Features.VoiceBroadcast, null, SettingLevel.DEVICE, true);
const wrapper = wrapAndRender({
room,
showVoiceBroadcastButton: value,
});
expect(wrapper.find(MessageComposerButtons).props().showVoiceBroadcastButton).toBe(false);
});
});
it("should not render the send button", () => {
const wrapper = wrapAndRender({ room });
expect(wrapper.find("SendButton")).toHaveLength(0);

View file

@ -250,7 +250,6 @@ function createRoomState(room: Room, narrow: boolean): IRoomState {
statusBarVisible: false,
canReact: false,
canSendMessages: false,
canSendVoiceBroadcasts: false,
layout: Layout.Group,
lowBandwidth: false,
alwaysShowTimestamps: false,

View file

@ -72,7 +72,6 @@ describe('<SendMessageComposer/>', () => {
statusBarVisible: false,
canReact: false,
canSendMessages: false,
canSendVoiceBroadcasts: false,
layout: Layout.Group,
lowBandwidth: false,
alwaysShowTimestamps: false,

View file

@ -91,7 +91,6 @@ describe('WysiwygComposer', () => {
statusBarVisible: false,
canReact: false,
canSendMessages: false,
canSendVoiceBroadcasts: false,
layout: Layout.Group,
lowBandwidth: false,
alwaysShowTimestamps: false,

View file

@ -123,7 +123,6 @@ describe('message', () => {
statusBarVisible: false,
canReact: false,
canSendMessages: false,
canSendVoiceBroadcasts: false,
layout: Layout.Group,
lowBandwidth: false,
alwaysShowTimestamps: false,

View file

@ -0,0 +1,70 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there already is a live broadcast of another user should show an info dialog 1`] = `
[MockFunction] {
"calls": Array [
Array [
[Function],
Object {
"description": <p>
Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.
</p>,
"hasCloseButton": true,
"title": "Can't start a new voice broadcast",
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
`;
exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there already is a live broadcast of the current user should show an info dialog 1`] = `
[MockFunction] {
"calls": Array [
Array [
[Function],
Object {
"description": <p>
You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.
</p>,
"hasCloseButton": true,
"title": "Can't start a new voice broadcast",
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
`;
exports[`startNewVoiceBroadcastRecording when the current user is not allowed to send voice broadcast info state events should show an info dialog 1`] = `
[MockFunction] {
"calls": Array [
Array [
[Function],
Object {
"description": <p>
You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.
</p>,
"hasCloseButton": true,
"title": "Can't start a new voice broadcast",
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
`;

View file

@ -0,0 +1,144 @@
/*
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 { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import {
hasRoomLiveVoiceBroadcast,
VoiceBroadcastInfoEventType,
VoiceBroadcastInfoState,
} from "../../../src/voice-broadcast";
import { mkEvent, stubClient } from "../../test-utils";
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
describe("hasRoomLiveVoiceBroadcast", () => {
const otherUserId = "@other:example.com";
const roomId = "!room:example.com";
let client: MatrixClient;
let room: Room;
const addVoiceBroadcastInfoEvent = (
state: VoiceBroadcastInfoState,
sender: string,
) => {
room.currentState.setStateEvents([
mkVoiceBroadcastInfoStateEvent(room.roomId, state, sender),
]);
};
const itShouldReturnTrueTrue = () => {
it("should return true/true", () => {
expect(hasRoomLiveVoiceBroadcast(room, client.getUserId())).toEqual({
hasBroadcast: true,
startedByUser: true,
});
});
};
const itShouldReturnTrueFalse = () => {
it("should return true/false", () => {
expect(hasRoomLiveVoiceBroadcast(room, client.getUserId())).toEqual({
hasBroadcast: true,
startedByUser: false,
});
});
};
const itShouldReturnFalseFalse = () => {
it("should return false/false", () => {
expect(hasRoomLiveVoiceBroadcast(room, client.getUserId())).toEqual({
hasBroadcast: false,
startedByUser: false,
});
});
};
beforeAll(() => {
client = stubClient();
});
beforeEach(() => {
room = new Room(roomId, client, client.getUserId());
});
describe("when there is no voice broadcast info at all", () => {
itShouldReturnFalseFalse();
});
describe("when the »state« prop is missing", () => {
beforeEach(() => {
room.currentState.setStateEvents([
mkEvent({
event: true,
room: room.roomId,
user: client.getUserId(),
type: VoiceBroadcastInfoEventType,
skey: client.getUserId(),
content: {},
}),
]);
});
itShouldReturnFalseFalse();
});
describe("when there is a live broadcast from the current and another user", () => {
beforeEach(() => {
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, client.getUserId());
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, otherUserId);
});
itShouldReturnTrueTrue();
});
describe("when there are only stopped info events", () => {
beforeEach(() => {
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, client.getUserId());
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, otherUserId);
});
itShouldReturnFalseFalse();
});
describe.each([
// all there are kind of live states
VoiceBroadcastInfoState.Started,
VoiceBroadcastInfoState.Paused,
VoiceBroadcastInfoState.Running,
])("when there is a live broadcast (%s) from the current user", (state: VoiceBroadcastInfoState) => {
beforeEach(() => {
addVoiceBroadcastInfoEvent(state, client.getUserId());
});
itShouldReturnTrueTrue();
});
describe("when there was a live broadcast, that has been stopped", () => {
beforeEach(() => {
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Running, client.getUserId());
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, client.getUserId());
});
itShouldReturnFalseFalse();
});
describe("when there is a live broadcast from another user", () => {
beforeEach(() => {
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Running, otherUserId);
});
itShouldReturnTrueFalse();
});
});

View file

@ -15,8 +15,9 @@ limitations under the License.
*/
import { mocked } from "jest-mock";
import { EventType, MatrixClient, MatrixEvent, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import { EventType, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import Modal from "../../../src/Modal";
import {
startNewVoiceBroadcastRecording,
VoiceBroadcastInfoEventType,
@ -25,46 +26,29 @@ import {
VoiceBroadcastRecording,
} from "../../../src/voice-broadcast";
import { mkEvent, stubClient } from "../../test-utils";
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
jest.mock("../../../src/voice-broadcast/models/VoiceBroadcastRecording", () => ({
VoiceBroadcastRecording: jest.fn(),
}));
jest.mock("../../../src/Modal");
describe("startNewVoiceBroadcastRecording", () => {
const roomId = "!room:example.com";
const otherUserId = "@other:example.com";
let client: MatrixClient;
let recordingsStore: VoiceBroadcastRecordingsStore;
let room: Room;
let roomOnStateEventsCallbackRegistered: Promise<void>;
let roomOnStateEventsCallbackRegisteredResolver: Function;
let roomOnStateEventsCallback: () => void;
let infoEvent: MatrixEvent;
let otherEvent: MatrixEvent;
let stateEvent: MatrixEvent;
let result: VoiceBroadcastRecording | null;
beforeEach(() => {
roomOnStateEventsCallbackRegistered = new Promise((resolve) => {
roomOnStateEventsCallbackRegisteredResolver = resolve;
});
room = {
currentState: {
getStateEvents: jest.fn().mockImplementation((type, userId) => {
if (type === VoiceBroadcastInfoEventType && userId === client.getUserId()) {
return stateEvent;
}
}),
},
on: jest.fn().mockImplementation((eventType, callback) => {
if (eventType === RoomStateEvent.Events) {
roomOnStateEventsCallback = callback;
roomOnStateEventsCallbackRegisteredResolver();
}
}),
off: jest.fn(),
} as unknown as Room;
client = stubClient();
room = new Room(roomId, client, client.getUserId());
jest.spyOn(room.currentState, "maySendStateEvent");
mocked(client.getRoom).mockImplementation((getRoomId: string) => {
if (getRoomId === roomId) {
return room;
@ -85,22 +69,14 @@ describe("startNewVoiceBroadcastRecording", () => {
setCurrent: jest.fn(),
} as unknown as VoiceBroadcastRecordingsStore;
infoEvent = mkEvent({
event: true,
type: VoiceBroadcastInfoEventType,
content: {
device_id: client.getDeviceId(),
state: VoiceBroadcastInfoState.Started,
},
user: client.getUserId(),
room: roomId,
});
infoEvent = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, client.getUserId());
otherEvent = mkEvent({
event: true,
type: EventType.RoomMember,
content: {},
user: client.getUserId(),
room: roomId,
skey: "",
});
mocked(VoiceBroadcastRecording).mockImplementation((
@ -115,29 +91,96 @@ describe("startNewVoiceBroadcastRecording", () => {
});
});
it("should create a new Voice Broadcast", (done) => {
let ok = false;
afterEach(() => {
jest.clearAllMocks();
});
startNewVoiceBroadcastRecording(roomId, client, recordingsStore).then((recording) => {
expect(ok).toBe(true);
expect(mocked(room.off)).toHaveBeenCalledWith(RoomStateEvent.Events, roomOnStateEventsCallback);
expect(recording.infoEvent).toBe(infoEvent);
expect(recording.start).toHaveBeenCalled();
done();
describe("when the current user is allowed to send voice broadcast info state events", () => {
beforeEach(() => {
mocked(room.currentState.maySendStateEvent).mockReturnValue(true);
});
roomOnStateEventsCallbackRegistered.then(() => {
// no state event, yet
roomOnStateEventsCallback();
describe("when there currently is no other broadcast", () => {
it("should create a new Voice Broadcast", async () => {
mocked(client.sendStateEvent).mockImplementation(async (
_roomId: string,
_eventType: string,
_content: any,
_stateKey = "",
) => {
setTimeout(() => {
// emit state events after resolving the promise
room.currentState.setStateEvents([otherEvent]);
room.currentState.setStateEvents([infoEvent]);
}, 0);
return { event_id: infoEvent.getId() };
});
const recording = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
// other state event
stateEvent = otherEvent;
roomOnStateEventsCallback();
expect(client.sendStateEvent).toHaveBeenCalledWith(
roomId,
VoiceBroadcastInfoEventType,
{
chunk_length: 300,
device_id: client.getDeviceId(),
state: VoiceBroadcastInfoState.Started,
},
client.getUserId(),
);
expect(recording.infoEvent).toBe(infoEvent);
expect(recording.start).toHaveBeenCalled();
});
});
// the expected Voice Broadcast Info event
stateEvent = infoEvent;
ok = true;
roomOnStateEventsCallback();
describe("when there already is a live broadcast of the current user", () => {
beforeEach(async () => {
room.currentState.setStateEvents([
mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Running, client.getUserId()),
]);
result = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
});
it("should not start a voice broadcast", () => {
expect(result).toBeNull();
});
it("should show an info dialog", () => {
expect(Modal.createDialog).toMatchSnapshot();
});
});
describe("when there already is a live broadcast of another user", () => {
beforeEach(async () => {
room.currentState.setStateEvents([
mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Running, otherUserId),
]);
result = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
});
it("should not start a voice broadcast", () => {
expect(result).toBeNull();
});
it("should show an info dialog", () => {
expect(Modal.createDialog).toMatchSnapshot();
});
});
});
describe("when the current user is not allowed to send voice broadcast info state events", () => {
beforeEach(async () => {
mocked(room.currentState.maySendStateEvent).mockReturnValue(false);
result = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
});
it("should not start a voice broadcast", () => {
expect(result).toBeNull();
});
it("should show an info dialog", () => {
expect(Modal.createDialog).toMatchSnapshot();
});
});
});

View file

@ -0,0 +1,37 @@
/*
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 { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../../../src/voice-broadcast";
import { mkEvent } from "../../test-utils";
export const mkVoiceBroadcastInfoStateEvent = (
roomId: string,
state: VoiceBroadcastInfoState,
sender: string,
): MatrixEvent => {
return mkEvent({
event: true,
room: roomId,
user: sender,
type: VoiceBroadcastInfoEventType,
skey: sender,
content: {
state,
},
});
};