From ab560bba4070586502eb1231ab7fef35e9bff54a Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Thu, 15 Dec 2022 12:43:01 +0100 Subject: [PATCH] Implement small broadcast PiP (#9755) --- res/css/voice-broadcast/atoms/_LiveBadge.pcss | 2 +- .../atoms/_VoiceBroadcastControl.pcss | 1 + .../atoms/_VoiceBroadcastHeader.pcss | 10 +- .../molecules/_VoiceBroadcastBody.pcss | 25 ++ src/components/views/voip/PipView.tsx | 11 +- src/i18n/strings/en_EN.json | 6 +- .../components/atoms/VoiceBroadcastHeader.tsx | 18 +- .../atoms/VoiceBroadcastPlaybackControl.tsx | 53 +++ .../molecules/VoiceBroadcastPlaybackBody.tsx | 32 +- .../VoiceBroadcastSmallPlaybackBody.tsx | 52 +++ src/voice-broadcast/index.ts | 2 + test/components/views/voip/PipView-test.tsx | 26 +- .../VoiceBroadcastPlaybackControl-test.tsx | 53 +++ .../VoiceBroadcastHeader-test.tsx.snap | 32 +- ...oiceBroadcastPlaybackControl-test.tsx.snap | 61 +++ .../VoiceBroadcastSmallPlaybackBody-test.tsx | 132 ++++++ .../VoiceBroadcastPlaybackBody-test.tsx.snap | 48 ++- ...oiceBroadcastPreRecordingPip-test.tsx.snap | 8 +- .../VoiceBroadcastRecordingBody-test.tsx.snap | 16 +- .../VoiceBroadcastRecordingPip-test.tsx.snap | 16 +- ...ceBroadcastSmallPlaybackBody-test.tsx.snap | 408 ++++++++++++++++++ 21 files changed, 938 insertions(+), 74 deletions(-) create mode 100644 src/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl.tsx create mode 100644 src/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody.tsx create mode 100644 test/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl-test.tsx create mode 100644 test/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastPlaybackControl-test.tsx.snap create mode 100644 test/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody-test.tsx create mode 100644 test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastSmallPlaybackBody-test.tsx.snap diff --git a/res/css/voice-broadcast/atoms/_LiveBadge.pcss b/res/css/voice-broadcast/atoms/_LiveBadge.pcss index 9b7759d94d..eb0dbd8e4b 100644 --- a/res/css/voice-broadcast/atoms/_LiveBadge.pcss +++ b/res/css/voice-broadcast/atoms/_LiveBadge.pcss @@ -19,7 +19,7 @@ limitations under the License. background-color: $alert; border-radius: 2px; color: $live-badge-color; - display: flex; + display: inline-flex; font-size: $font-12px; font-weight: $font-semi-bold; gap: $spacing-4; diff --git a/res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss b/res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss index efe4b97445..7e31afdd47 100644 --- a/res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss +++ b/res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss @@ -20,6 +20,7 @@ limitations under the License. border-radius: 50%; color: $secondary-content; display: flex; + flex: 0 0 32px; height: 32px; justify-content: center; width: 32px; diff --git a/res/css/voice-broadcast/atoms/_VoiceBroadcastHeader.pcss b/res/css/voice-broadcast/atoms/_VoiceBroadcastHeader.pcss index 90092a35ac..a30beb27b6 100644 --- a/res/css/voice-broadcast/atoms/_VoiceBroadcastHeader.pcss +++ b/res/css/voice-broadcast/atoms/_VoiceBroadcastHeader.pcss @@ -17,7 +17,7 @@ limitations under the License. gap: $spacing-8; line-height: 20px; margin-bottom: $spacing-16; - width: 266px; + min-width: 0; } .mx_VoiceBroadcastHeader_content { @@ -25,9 +25,17 @@ limitations under the License. min-width: 0; } +.mx_VoiceBroadcastHeader_room_wrapper { + align-items: center; + display: flex; + gap: 4px; + justify-content: flex-start; +} + .mx_VoiceBroadcastHeader_room { font-size: $font-12px; font-weight: $font-semi-bold; + min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss b/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss index 3d463cbc9b..cc53c674e5 100644 --- a/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss +++ b/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss @@ -21,6 +21,7 @@ limitations under the License. display: inline-block; font-size: $font-12px; padding: $spacing-12; + width: 271px; .mx_Clock { line-height: 1; @@ -32,6 +33,24 @@ limitations under the License. box-shadow: 0 2px 8px 0 #0000004a; } +.mx_VoiceBroadcastBody--small { + display: flex; + gap: $spacing-8; + width: 192px; + + .mx_VoiceBroadcastHeader { + margin-bottom: 0; + } + + .mx_VoiceBroadcastControl { + align-self: center; + } + + .mx_LiveBadge { + margin-top: 4px; + } +} + .mx_VoiceBroadcastBody_divider { background-color: $quinary-content; border: 0; @@ -56,3 +75,9 @@ limitations under the License. display: flex; gap: $spacing-8; } + +.mx_VoiceBroadcastBody__small-close { + right: 8px; + position: absolute; + top: 8px; +} diff --git a/src/components/views/voip/PipView.tsx b/src/components/views/voip/PipView.tsx index 7142222a8b..fd51c866cc 100644 --- a/src/components/views/voip/PipView.tsx +++ b/src/components/views/voip/PipView.tsx @@ -45,6 +45,7 @@ import { VoiceBroadcastPreRecordingPip, VoiceBroadcastRecording, VoiceBroadcastRecordingPip, + VoiceBroadcastSmallPlaybackBody, } from "../../../voice-broadcast"; import { useCurrentVoiceBroadcastPlayback } from "../../../voice-broadcast/hooks/useCurrentVoiceBroadcastPlayback"; @@ -335,9 +336,17 @@ class PipView extends React.Component { } private createVoiceBroadcastPlaybackPipContent(voiceBroadcastPlayback: VoiceBroadcastPlayback): CreatePipChildren { + if (this.state.viewedRoomId === voiceBroadcastPlayback.infoEvent.getRoomId()) { + return ({ onStartMoving }) => ( +
+ +
+ ); + } + return ({ onStartMoving }) => (
- +
); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e82e8f18fe..146fbafd85 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -653,16 +653,16 @@ "Stop live broadcasting?": "Stop live broadcasting?", "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.", "Yes, stop broadcast": "Yes, stop broadcast", - "play voice broadcast": "play voice broadcast", - "resume voice broadcast": "resume voice broadcast", - "pause voice broadcast": "pause voice broadcast", "30s backward": "30s backward", "30s forward": "30s forward", "Go live": "Go live", + "resume voice broadcast": "resume voice broadcast", + "pause voice broadcast": "pause voice broadcast", "Change input device": "Change input device", "Live": "Live", "Voice broadcast": "Voice broadcast", "Buffering…": "Buffering…", + "play voice broadcast": "play voice broadcast", "Cannot reach homeserver": "Cannot reach homeserver", "Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin", "Your %(brand)s is misconfigured": "Your %(brand)s is misconfigured", diff --git a/src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx b/src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx index 3814399b93..c0fdb20fd2 100644 --- a/src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx +++ b/src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx @@ -34,12 +34,14 @@ import AccessibleTooltipButton from "../../../components/views/elements/Accessib interface VoiceBroadcastHeaderProps { linkToRoom?: boolean; live?: VoiceBroadcastLiveness; + liveBadgePosition?: "middle" | "right"; onCloseClick?: () => void; onMicrophoneLineClick?: ((e: ButtonEvent) => void | Promise) | null; room: Room; microphoneLabel?: string; showBroadcast?: boolean; showBuffering?: boolean; + bufferingPosition?: "line" | "title"; timeLeft?: number; showClose?: boolean; } @@ -47,12 +49,14 @@ interface VoiceBroadcastHeaderProps { export const VoiceBroadcastHeader: React.FC = ({ linkToRoom = false, live = "not-live", + liveBadgePosition = "right", onCloseClick = () => {}, onMicrophoneLineClick = null, room, microphoneLabel, showBroadcast = false, showBuffering = false, + bufferingPosition = "line", showClose = false, timeLeft, }) => { @@ -78,7 +82,7 @@ export const VoiceBroadcastHeader: React.FC = ({ ); - const buffering = showBuffering && ( + const bufferingLine = showBuffering && bufferingPosition === "line" && (
{_t("Buffering…")} @@ -110,7 +114,12 @@ export const VoiceBroadcastHeader: React.FC = ({ }; let roomAvatar = ; - let roomName =
{room.name}
; + let roomName = ( +
+
{room.name}
+ {showBuffering && bufferingPosition === "title" && } +
+ ); if (linkToRoom) { roomAvatar = {roomAvatar}; @@ -126,9 +135,10 @@ export const VoiceBroadcastHeader: React.FC = ({ {microphoneLine} {timeLeftLine} {broadcast} - {buffering} + {bufferingLine} + {liveBadgePosition === "middle" && liveBadge}
- {liveBadge} + {liveBadgePosition === "right" && liveBadge} {closeButton} ); diff --git a/src/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl.tsx b/src/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl.tsx new file mode 100644 index 0000000000..d2c00fcd85 --- /dev/null +++ b/src/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl.tsx @@ -0,0 +1,53 @@ +/* +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 React from "react"; + +import { Icon as PlayIcon } from "../../../../res/img/element-icons/play.svg"; +import { Icon as PauseIcon } from "../../../../res/img/element-icons/pause.svg"; +import { _t } from "../../../languageHandler"; +import { VoiceBroadcastControl, VoiceBroadcastPlaybackState } from "../.."; + +interface Props { + onClick: () => void; + state: VoiceBroadcastPlaybackState; +} + +export const VoiceBroadcastPlaybackControl: React.FC = ({ onClick, state }) => { + let controlIcon: React.FC>; + let controlLabel: string; + let className = ""; + + switch (state) { + case VoiceBroadcastPlaybackState.Stopped: + controlIcon = PlayIcon; + className = "mx_VoiceBroadcastControl-play"; + controlLabel = _t("play voice broadcast"); + break; + case VoiceBroadcastPlaybackState.Paused: + controlIcon = PlayIcon; + className = "mx_VoiceBroadcastControl-play"; + controlLabel = _t("resume voice broadcast"); + break; + case VoiceBroadcastPlaybackState.Buffering: + case VoiceBroadcastPlaybackState.Playing: + controlIcon = PauseIcon; + controlLabel = _t("pause voice broadcast"); + break; + } + + return ; +}; diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx index 54c4e90412..2ea53ef14e 100644 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx +++ b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx @@ -18,14 +18,12 @@ import React, { ReactElement } from "react"; import classNames from "classnames"; import { - VoiceBroadcastControl, VoiceBroadcastHeader, VoiceBroadcastPlayback, + VoiceBroadcastPlaybackControl, VoiceBroadcastPlaybackState, } from "../.."; import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback"; -import { Icon as PlayIcon } from "../../../../res/img/element-icons/play.svg"; -import { Icon as PauseIcon } from "../../../../res/img/element-icons/pause.svg"; import { Icon as Back30sIcon } from "../../../../res/img/element-icons/Back30s.svg"; import { Icon as Forward30sIcon } from "../../../../res/img/element-icons/Forward30s.svg"; import { _t } from "../../../languageHandler"; @@ -43,32 +41,6 @@ interface VoiceBroadcastPlaybackBodyProps { export const VoiceBroadcastPlaybackBody: React.FC = ({ pip = false, playback }) => { const { times, liveness, playbackState, room, sender, toggle } = useVoiceBroadcastPlayback(playback); - let controlIcon: React.FC>; - let controlLabel: string; - let className = ""; - - switch (playbackState) { - case VoiceBroadcastPlaybackState.Stopped: - controlIcon = PlayIcon; - className = "mx_VoiceBroadcastControl-play"; - controlLabel = _t("play voice broadcast"); - break; - case VoiceBroadcastPlaybackState.Paused: - controlIcon = PlayIcon; - className = "mx_VoiceBroadcastControl-play"; - controlLabel = _t("resume voice broadcast"); - break; - case VoiceBroadcastPlaybackState.Buffering: - case VoiceBroadcastPlaybackState.Playing: - controlIcon = PauseIcon; - controlLabel = _t("pause voice broadcast"); - break; - } - - const control = ( - - ); - let seekBackwardButton: ReactElement | null = null; let seekForwardButton: ReactElement | null = null; @@ -107,7 +79,7 @@ export const VoiceBroadcastPlaybackBody: React.FC
{seekBackwardButton} - {control} + {seekForwardButton}
diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody.tsx new file mode 100644 index 0000000000..b67b95d0b9 --- /dev/null +++ b/src/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody.tsx @@ -0,0 +1,52 @@ +/* +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 React from "react"; + +import { + VoiceBroadcastHeader, + VoiceBroadcastPlayback, + VoiceBroadcastPlaybackControl, + VoiceBroadcastPlaybackState, +} from "../.."; +import AccessibleButton from "../../../components/views/elements/AccessibleButton"; +import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback"; +import { Icon as XIcon } from "../../../../res/img/element-icons/cancel-rounded.svg"; + +interface VoiceBroadcastSmallPlaybackBodyProps { + playback: VoiceBroadcastPlayback; +} + +export const VoiceBroadcastSmallPlaybackBody: React.FC = ({ playback }) => { + const { liveness, playbackState, room, sender, toggle } = useVoiceBroadcastPlayback(playback); + return ( +
+ + + playback.stop()}> + + +
+ ); +}; diff --git a/src/voice-broadcast/index.ts b/src/voice-broadcast/index.ts index f71ce077ad..952a3969af 100644 --- a/src/voice-broadcast/index.ts +++ b/src/voice-broadcast/index.ts @@ -29,8 +29,10 @@ export * from "./components/VoiceBroadcastBody"; export * from "./components/atoms/LiveBadge"; export * from "./components/atoms/VoiceBroadcastControl"; export * from "./components/atoms/VoiceBroadcastHeader"; +export * from "./components/atoms/VoiceBroadcastPlaybackControl"; export * from "./components/atoms/VoiceBroadcastRoomSubtitle"; export * from "./components/molecules/VoiceBroadcastPlaybackBody"; +export * from "./components/molecules/VoiceBroadcastSmallPlaybackBody"; export * from "./components/molecules/VoiceBroadcastPreRecordingPip"; export * from "./components/molecules/VoiceBroadcastRecordingBody"; export * from "./components/molecules/VoiceBroadcastRecordingPip"; diff --git a/test/components/views/voip/PipView-test.tsx b/test/components/views/voip/PipView-test.tsx index 531cad314d..43b922ad31 100644 --- a/test/components/views/voip/PipView-test.tsx +++ b/test/components/views/voip/PipView-test.tsx @@ -193,7 +193,7 @@ describe("PipView", () => { new RoomViewStore(defaultDispatcher, sdkContext); }; - const startVoiceBroadcastPlayback = (room: Room): MatrixEvent => { + const mkVoiceBroadcast = (room: Room): MatrixEvent => { const infoEvent = makeVoiceBroadcastInfoStateEvent(); room.currentState.setStateEvents([infoEvent]); defaultDispatcher.dispatch( @@ -278,7 +278,7 @@ describe("PipView", () => { describe("when there is a voice broadcast playback and pre-recording", () => { beforeEach(() => { - startVoiceBroadcastPlayback(room); + mkVoiceBroadcast(room); setUpVoiceBroadcastPreRecording(); renderPip(); }); @@ -301,13 +301,31 @@ describe("PipView", () => { }); }); + describe("when listening to a voice broadcast in a room and then switching to another room", () => { + beforeEach(async () => { + setUpRoomViewStore(); + viewRoom(room.roomId); + mkVoiceBroadcast(room); + await voiceBroadcastPlaybacksStore.getCurrent()?.start(); + viewRoom(room2.roomId); + renderPip(); + }); + + it("should render the small voice broadcast playback PiP", () => { + // check for the „pause voice broadcast“ button + expect(screen.getByLabelText("pause voice broadcast")).toBeInTheDocument(); + // check for the absence of the „30s forward“ button + expect(screen.queryByLabelText("30s forward")).not.toBeInTheDocument(); + }); + }); + describe("when viewing a room with a live voice broadcast", () => { - let startEvent: MatrixEvent | null = null; + let startEvent!: MatrixEvent; beforeEach(() => { setUpRoomViewStore(); viewRoom(room.roomId); - startEvent = startVoiceBroadcastPlayback(room); + startEvent = mkVoiceBroadcast(room); renderPip(); }); diff --git a/test/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl-test.tsx b/test/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl-test.tsx new file mode 100644 index 0000000000..a70530a476 --- /dev/null +++ b/test/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl-test.tsx @@ -0,0 +1,53 @@ +/* +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 React from "react"; +import { render, RenderResult, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import { VoiceBroadcastPlaybackControl, VoiceBroadcastPlaybackState } from "../../../../src/voice-broadcast"; + +describe("", () => { + const renderControl = (state: VoiceBroadcastPlaybackState): { result: RenderResult; onClick: () => void } => { + const onClick = jest.fn(); + return { + onClick, + result: render(), + }; + }; + + it.each([ + VoiceBroadcastPlaybackState.Stopped, + VoiceBroadcastPlaybackState.Paused, + VoiceBroadcastPlaybackState.Buffering, + VoiceBroadcastPlaybackState.Playing, + ])("should render state %s as expected", (state: VoiceBroadcastPlaybackState) => { + expect(renderControl(state).result.container).toMatchSnapshot(); + }); + + describe("when clicking the control", () => { + let onClick: () => void; + + beforeEach(async () => { + onClick = renderControl(VoiceBroadcastPlaybackState.Playing).onClick; + await userEvent.click(screen.getByLabelText("pause voice broadcast")); + }); + + it("should invoke the onClick callback", () => { + expect(onClick).toHaveBeenCalled(); + }); + }); +}); diff --git a/test/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastHeader-test.tsx.snap b/test/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastHeader-test.tsx.snap index 0d05d5a280..7ce66a9ea8 100644 --- a/test/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastHeader-test.tsx.snap +++ b/test/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastHeader-test.tsx.snap @@ -15,9 +15,13 @@ exports[`VoiceBroadcastHeader when rendering a buffering live broadcast header w class="mx_VoiceBroadcastHeader_content" >
- !room:example.com +
+ !room:example.com +
- !room:example.com +
+ !room:example.com +
- !room:example.com +
+ !room:example.com +
- !room:example.com +
+ !room:example.com +
should render state 0 as expected 1`] = ` +
+
+
+
+
+`; + +exports[` should render state 1 as expected 1`] = ` +
+
+
+
+
+`; + +exports[` should render state 2 as expected 1`] = ` +
+
+
+
+
+`; + +exports[` should render state 3 as expected 1`] = ` +
+
+
+
+
+`; diff --git a/test/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody-test.tsx b/test/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody-test.tsx new file mode 100644 index 0000000000..31d8ee9311 --- /dev/null +++ b/test/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody-test.tsx @@ -0,0 +1,132 @@ +/* +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 React from "react"; +import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; +import { render, RenderResult } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { mocked } from "jest-mock"; + +import { + VoiceBroadcastInfoState, + VoiceBroadcastLiveness, + VoiceBroadcastPlayback, + VoiceBroadcastSmallPlaybackBody, + VoiceBroadcastPlaybackState, +} from "../../../../src/voice-broadcast"; +import { stubClient } from "../../../test-utils"; +import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils"; + +// mock RoomAvatar, because it is doing too much fancy stuff +jest.mock("../../../../src/components/views/avatars/RoomAvatar", () => ({ + __esModule: true, + default: jest.fn().mockImplementation(({ room }) => { + return
room avatar: {room.name}
; + }), +})); + +describe("", () => { + const userId = "@user:example.com"; + const roomId = "!room:example.com"; + let client: MatrixClient; + let infoEvent: MatrixEvent; + let playback: VoiceBroadcastPlayback; + let renderResult: RenderResult; + + beforeAll(() => { + client = stubClient(); + mocked(client.relations).mockClear(); + mocked(client.relations).mockResolvedValue({ events: [] }); + + infoEvent = mkVoiceBroadcastInfoStateEvent( + roomId, + VoiceBroadcastInfoState.Stopped, + userId, + client.getDeviceId()!, + ); + }); + + beforeEach(() => { + playback = new VoiceBroadcastPlayback(infoEvent, client); + jest.spyOn(playback, "toggle").mockImplementation(() => Promise.resolve()); + jest.spyOn(playback, "getLiveness"); + jest.spyOn(playback, "getState"); + }); + + describe("when rendering a buffering broadcast", () => { + beforeEach(() => { + mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Buffering); + mocked(playback.getLiveness).mockReturnValue("live"); + renderResult = render(); + }); + + it("should render as expected", () => { + expect(renderResult.container).toMatchSnapshot(); + }); + }); + + describe("when rendering a playing broadcast", () => { + beforeEach(() => { + mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Playing); + mocked(playback.getLiveness).mockReturnValue("not-live"); + renderResult = render(); + }); + + it("should render as expected", () => { + expect(renderResult.container).toMatchSnapshot(); + }); + }); + + describe(`when rendering a stopped broadcast`, () => { + beforeEach(() => { + mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Stopped); + mocked(playback.getLiveness).mockReturnValue("not-live"); + renderResult = render(); + }); + + it("should render as expected", () => { + expect(renderResult.container).toMatchSnapshot(); + }); + + describe("and clicking the play button", () => { + beforeEach(async () => { + await userEvent.click(renderResult.getByLabelText("play voice broadcast")); + }); + + it("should toggle the playback", () => { + expect(playback.toggle).toHaveBeenCalled(); + }); + }); + }); + + describe.each([ + { state: VoiceBroadcastPlaybackState.Paused, liveness: "not-live" }, + { state: VoiceBroadcastPlaybackState.Playing, liveness: "live" }, + ] as Array<{ state: VoiceBroadcastPlaybackState; liveness: VoiceBroadcastLiveness }>)( + "when rendering a %s/%s broadcast", + ({ state, liveness }) => { + beforeEach(() => { + mocked(playback.getState).mockReturnValue(state); + mocked(playback.getLiveness).mockReturnValue(liveness); + renderResult = render(); + }); + + it("should render as expected", () => { + expect(renderResult.container).toMatchSnapshot(); + }); + }, + ); +}); diff --git a/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap b/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap index da350d259d..2ead27d5a2 100644 --- a/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap +++ b/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap @@ -18,9 +18,13 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 0/not-live broadcast should class="mx_VoiceBroadcastHeader_content" >
- My room +
+ My room +
- My room +
+ My room +
- My room +
+ My room +
- My room +
+ My room +
- My room +
+ My room +
- My room +
+ My room +
- !room@example.com +
+ !room@example.com +
- My room +
+ My room +
- My room +
+ My room +
- My room +
+ My room +
- My room +
+ My room +
when rendering a { state: 0, liveness: 'not-live' }/%s broadcast should render as expected 1`] = ` +
+
+
+
+
+ room avatar: + My room +
+
+
+
+
+
+ My room +
+
+
+
+
+ + @user:example.com + +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[` when rendering a { state: 1, liveness: 'live' }/%s broadcast should render as expected 1`] = ` +
+
+
+
+
+ room avatar: + My room +
+
+
+
+
+
+ My room +
+
+
+
+
+ + @user:example.com + +
+
+
+ Live +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[` when rendering a buffering broadcast should render as expected 1`] = ` +
+
+
+
+
+ room avatar: + My room +
+
+
+
+
+
+ My room +
+
+
+
+
+
+
+
+ + @user:example.com + +
+
+
+ Live +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[` when rendering a playing broadcast should render as expected 1`] = ` +
+
+
+
+
+ room avatar: + My room +
+
+
+
+
+
+ My room +
+
+
+
+
+ + @user:example.com + +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[` when rendering a stopped broadcast should render as expected 1`] = ` +
+
+
+
+
+ room avatar: + My room +
+
+
+
+
+
+ My room +
+
+
+
+
+ + @user:example.com + +
+
+
+
+
+
+
+
+
+
+
+`;