diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 966caed4cc..577d50f629 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -365,5 +365,7 @@ @import "./views/voip/_PiPContainer.pcss"; @import "./views/voip/_VideoFeed.pcss"; @import "./voice-broadcast/atoms/_LiveBadge.pcss"; +@import "./voice-broadcast/atoms/_PlaybackControlButton.pcss"; @import "./voice-broadcast/atoms/_VoiceBroadcastHeader.pcss"; +@import "./voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss"; @import "./voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss"; diff --git a/res/css/voice-broadcast/atoms/_PlaybackControlButton.pcss b/res/css/voice-broadcast/atoms/_PlaybackControlButton.pcss new file mode 100644 index 0000000000..fc4c1386b6 --- /dev/null +++ b/res/css/voice-broadcast/atoms/_PlaybackControlButton.pcss @@ -0,0 +1,25 @@ +/* +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. +*/ + +.mx_BroadcastPlaybackControlButton { + align-items: center; + background-color: $background; + border-radius: 50%; + display: flex; + height: 32px; + justify-content: center; + width: 32px; +} diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss b/res/css/voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss new file mode 100644 index 0000000000..11921e1f95 --- /dev/null +++ b/res/css/voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss @@ -0,0 +1,27 @@ +/* +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. +*/ + +.mx_VoiceBroadcastPlaybackBody { + background-color: $quinary-content; + border-radius: 8px; + display: inline-block; + padding: 12px; +} + +.mx_VoiceBroadcastPlaybackBody_controls { + display: flex; + justify-content: center; +} diff --git a/src/components/atoms/Icon.tsx b/src/components/atoms/Icon.tsx index 5778022764..9f5f3e2d3c 100644 --- a/src/components/atoms/Icon.tsx +++ b/src/components/atoms/Icon.tsx @@ -17,13 +17,19 @@ limitations under the License. import React from "react"; import liveIcon from "../../../res/img/element-icons/live.svg"; +import pauseIcon from "../../../res/img/element-icons/pause.svg"; +import playIcon from "../../../res/img/element-icons/play.svg"; export enum IconType { Live, + Pause, + Play, } const iconTypeMap = new Map([ [IconType.Live, liveIcon], + [IconType.Pause, pauseIcon], + [IconType.Play, playIcon], ]); export enum IconColour { diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index 4e09bfac49..91807d568f 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -58,6 +58,10 @@ interface IProps extends Omit>>([ [M_POLL_START.altName, MPollBody], [M_BEACON_INFO.name, MBeaconBody], [M_BEACON_INFO.altName, MBeaconBody], - [VoiceBroadcastInfoEventType, VoiceBroadcastBody], ]); export default class MessageEvent extends React.Component implements IMediaBody, IOperableEventTile { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 48d7e9b8a3..2a8b398514 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -638,6 +638,8 @@ "See %(msgtype)s messages posted to this room": "See %(msgtype)s messages posted to this room", "See %(msgtype)s messages posted to your active room": "See %(msgtype)s messages posted to your active room", "Live": "Live", + "pause voice broadcast": "pause voice broadcast", + "resume voice broadcast": "resume voice broadcast", "Voice broadcast": "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", diff --git a/src/voice-broadcast/components/VoiceBroadcastBody.tsx b/src/voice-broadcast/components/VoiceBroadcastBody.tsx index 3be79ca882..b90448de8a 100644 --- a/src/voice-broadcast/components/VoiceBroadcastBody.tsx +++ b/src/voice-broadcast/components/VoiceBroadcastBody.tsx @@ -15,19 +15,42 @@ limitations under the License. */ import React from "react"; +import { MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix"; import { VoiceBroadcastRecordingBody, VoiceBroadcastRecordingsStore, + shouldDisplayAsVoiceBroadcastRecordingTile, + VoiceBroadcastInfoEventType, + VoiceBroadcastPlaybacksStore, + VoiceBroadcastPlaybackBody, + VoiceBroadcastInfoState, } from ".."; import { IBodyProps } from "../../components/views/messages/IBodyProps"; import { MatrixClientPeg } from "../../MatrixClientPeg"; export const VoiceBroadcastBody: React.FC = ({ mxEvent }) => { const client = MatrixClientPeg.get(); - const recording = VoiceBroadcastRecordingsStore.instance().getByInfoEvent(mxEvent, client); + const room = client.getRoom(mxEvent.getRoomId()); + const relations = room?.getUnfilteredTimelineSet()?.relations?.getChildEventsForEvent( + mxEvent.getId(), + RelationType.Reference, + VoiceBroadcastInfoEventType, + ); + const relatedEvents = relations?.getRelations(); + const state = !relatedEvents?.find((event: MatrixEvent) => { + return event.getContent()?.state === VoiceBroadcastInfoState.Stopped; + }) ? VoiceBroadcastInfoState.Started : VoiceBroadcastInfoState.Stopped; - return ; + } + + const playback = VoiceBroadcastPlaybacksStore.instance().getByInfoEvent(mxEvent); + return ; }; diff --git a/src/voice-broadcast/components/atoms/PlaybackControlButton.tsx b/src/voice-broadcast/components/atoms/PlaybackControlButton.tsx new file mode 100644 index 0000000000..b67e6b3e24 --- /dev/null +++ b/src/voice-broadcast/components/atoms/PlaybackControlButton.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 { VoiceBroadcastPlaybackState } from "../.."; +import { Icon, IconColour, IconType } from "../../../components/atoms/Icon"; +import AccessibleButton from "../../../components/views/elements/AccessibleButton"; +import { _t } from "../../../languageHandler"; + +const stateIconMap = new Map([ + [VoiceBroadcastPlaybackState.Playing, IconType.Pause], + [VoiceBroadcastPlaybackState.Paused, IconType.Play], + [VoiceBroadcastPlaybackState.Stopped, IconType.Play], +]); + +interface Props { + onClick: () => void; + state: VoiceBroadcastPlaybackState; +} + +export const PlaybackControlButton: React.FC = ({ + onClick, + state, +}) => { + const ariaLabel = state === VoiceBroadcastPlaybackState.Playing + ? _t("pause voice broadcast") + : _t("resume voice broadcast"); + + return + + ; +}; diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx new file mode 100644 index 0000000000..6ebc67ee63 --- /dev/null +++ b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx @@ -0,0 +1,56 @@ +/* +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 { + PlaybackControlButton, + VoiceBroadcastHeader, + VoiceBroadcastPlayback, +} from "../.."; +import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback"; + +interface VoiceBroadcastPlaybackBodyProps { + playback: VoiceBroadcastPlayback; +} + +export const VoiceBroadcastPlaybackBody: React.FC = ({ + playback, +}) => { + const { + roomName, + sender, + toggle, + playbackState, + } = useVoiceBroadcastPlayback(playback); + + return ( +
+ +
+ +
+
+ ); +}; diff --git a/src/voice-broadcast/hooks/useVoiceBroadcastPlayback.ts b/src/voice-broadcast/hooks/useVoiceBroadcastPlayback.ts new file mode 100644 index 0000000000..25fcb93d51 --- /dev/null +++ b/src/voice-broadcast/hooks/useVoiceBroadcastPlayback.ts @@ -0,0 +1,49 @@ +/* +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 { useState } from "react"; + +import { useTypedEventEmitter } from "../../hooks/useEventEmitter"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; +import { + VoiceBroadcastPlayback, + VoiceBroadcastPlaybackEvent, + VoiceBroadcastPlaybackState, +} from ".."; + +export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => { + const client = MatrixClientPeg.get(); + const room = client.getRoom(playback.infoEvent.getRoomId()); + const playbackToggle = () => { + playback.toggle(); + }; + + const [playbackState, setPlaybackState] = useState(playback.getState()); + useTypedEventEmitter( + playback, + VoiceBroadcastPlaybackEvent.StateChanged, + (state: VoiceBroadcastPlaybackState, _playback: VoiceBroadcastPlayback) => { + setPlaybackState(state); + }, + ); + + return { + roomName: room.name, + sender: playback.infoEvent.sender, + toggle: playbackToggle, + playbackState, + }; +}; diff --git a/src/voice-broadcast/index.ts b/src/voice-broadcast/index.ts index 12027c9b42..2a3bb6573f 100644 --- a/src/voice-broadcast/index.ts +++ b/src/voice-broadcast/index.ts @@ -21,13 +21,18 @@ limitations under the License. import { RelationType } from "matrix-js-sdk/src/matrix"; +export * from "./models/VoiceBroadcastPlayback"; +export * from "./models/VoiceBroadcastRecording"; export * from "./audio/VoiceBroadcastRecorder"; export * from "./components/VoiceBroadcastBody"; export * from "./components/atoms/LiveBadge"; +export * from "./components/atoms/PlaybackControlButton"; export * from "./components/atoms/VoiceBroadcastHeader"; +export * from "./components/molecules/VoiceBroadcastPlaybackBody"; export * from "./components/molecules/VoiceBroadcastRecordingBody"; -export * from "./models/VoiceBroadcastRecording"; +export * from "./stores/VoiceBroadcastPlaybacksStore"; export * from "./stores/VoiceBroadcastRecordingsStore"; +export * from "./utils/shouldDisplayAsVoiceBroadcastRecordingTile"; export * from "./utils/shouldDisplayAsVoiceBroadcastTile"; export * from "./utils/startNewVoiceBroadcastRecording"; export * from "./hooks/useVoiceBroadcastRecording"; diff --git a/src/voice-broadcast/models/VoiceBroadcastPlayback.ts b/src/voice-broadcast/models/VoiceBroadcastPlayback.ts new file mode 100644 index 0000000000..28bea62e10 --- /dev/null +++ b/src/voice-broadcast/models/VoiceBroadcastPlayback.ts @@ -0,0 +1,76 @@ +/* +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 { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter"; + +import { IDestroyable } from "../../utils/IDestroyable"; + +export enum VoiceBroadcastPlaybackState { + Paused, + Playing, + Stopped, +} + +export enum VoiceBroadcastPlaybackEvent { + StateChanged = "state_changed", +} + +interface EventMap { + [VoiceBroadcastPlaybackEvent.StateChanged]: (state: VoiceBroadcastPlaybackState) => void; +} + +export class VoiceBroadcastPlayback + extends TypedEventEmitter + implements IDestroyable { + private state = VoiceBroadcastPlaybackState.Stopped; + + public constructor( + public readonly infoEvent: MatrixEvent, + ) { + super(); + } + + public start() { + this.setState(VoiceBroadcastPlaybackState.Playing); + } + + public stop() { + this.setState(VoiceBroadcastPlaybackState.Stopped); + } + + public toggle() { + if (this.state === VoiceBroadcastPlaybackState.Stopped) { + this.setState(VoiceBroadcastPlaybackState.Playing); + return; + } + + this.setState(VoiceBroadcastPlaybackState.Stopped); + } + + public getState(): VoiceBroadcastPlaybackState { + return this.state; + } + + private setState(state: VoiceBroadcastPlaybackState): void { + this.state = state; + this.emit(VoiceBroadcastPlaybackEvent.StateChanged, state); + } + + public destroy(): void { + this.removeAllListeners(); + } +} diff --git a/src/voice-broadcast/stores/VoiceBroadcastPlaybacksStore.ts b/src/voice-broadcast/stores/VoiceBroadcastPlaybacksStore.ts new file mode 100644 index 0000000000..1fdc8a9da5 --- /dev/null +++ b/src/voice-broadcast/stores/VoiceBroadcastPlaybacksStore.ts @@ -0,0 +1,71 @@ +/* +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 { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter"; + +import { VoiceBroadcastPlayback } from ".."; + +export enum VoiceBroadcastPlaybacksStoreEvent { + CurrentChanged = "current_changed", +} + +interface EventMap { + [VoiceBroadcastPlaybacksStoreEvent.CurrentChanged]: (recording: VoiceBroadcastPlayback) => void; +} + +/** + * This store provides access to the current and specific Voice Broadcast playbacks. + */ +export class VoiceBroadcastPlaybacksStore extends TypedEventEmitter { + private current: VoiceBroadcastPlayback | null; + private playbacks = new Map(); + + public constructor() { + super(); + } + + public setCurrent(current: VoiceBroadcastPlayback): void { + if (this.current === current) return; + + this.current = current; + this.playbacks.set(current.infoEvent.getId(), current); + this.emit(VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, current); + } + + public getCurrent(): VoiceBroadcastPlayback { + return this.current; + } + + public getByInfoEvent(infoEvent: MatrixEvent): VoiceBroadcastPlayback { + const infoEventId = infoEvent.getId(); + + if (!this.playbacks.has(infoEventId)) { + this.playbacks.set(infoEventId, new VoiceBroadcastPlayback(infoEvent)); + } + + return this.playbacks.get(infoEventId); + } + + public static readonly _instance = new VoiceBroadcastPlaybacksStore(); + + /** + * TODO Michael W: replace when https://github.com/matrix-org/matrix-react-sdk/pull/9293 has been merged + */ + public static instance() { + return VoiceBroadcastPlaybacksStore._instance; + } +} diff --git a/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile.ts b/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile.ts new file mode 100644 index 0000000000..b9964b6f2a --- /dev/null +++ b/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile.ts @@ -0,0 +1,30 @@ +/* +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, MatrixEvent } from "matrix-js-sdk/src/matrix"; + +import { VoiceBroadcastInfoState } from ".."; + +export const shouldDisplayAsVoiceBroadcastRecordingTile = ( + state: VoiceBroadcastInfoState, + client: MatrixClient, + event: MatrixEvent, +): boolean => { + const userId = client.getUserId(); + return !!userId + && userId === event.getSender() + && state !== VoiceBroadcastInfoState.Stopped; +}; diff --git a/test/voice-broadcast/components/VoiceBroadcastBody-test.tsx b/test/voice-broadcast/components/VoiceBroadcastBody-test.tsx index 50b4625fb6..84edf644f3 100644 --- a/test/voice-broadcast/components/VoiceBroadcastBody-test.tsx +++ b/test/voice-broadcast/components/VoiceBroadcastBody-test.tsx @@ -26,6 +26,10 @@ import { VoiceBroadcastRecordingBody, VoiceBroadcastRecordingsStore, VoiceBroadcastRecording, + shouldDisplayAsVoiceBroadcastRecordingTile, + VoiceBroadcastPlaybackBody, + VoiceBroadcastPlayback, + VoiceBroadcastPlaybacksStore, } from "../../../src/voice-broadcast"; import { mkEvent, stubClient } from "../../test-utils"; @@ -33,11 +37,20 @@ jest.mock("../../../src/voice-broadcast/components/molecules/VoiceBroadcastRecor VoiceBroadcastRecordingBody: jest.fn(), })); +jest.mock("../../../src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody", () => ({ + VoiceBroadcastPlaybackBody: jest.fn(), +})); + +jest.mock("../../../src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile", () => ({ + shouldDisplayAsVoiceBroadcastRecordingTile: jest.fn(), +})); + describe("VoiceBroadcastBody", () => { const roomId = "!room:example.com"; let client: MatrixClient; let infoEvent: MatrixEvent; let testRecording: VoiceBroadcastRecording; + let testPlayback: VoiceBroadcastPlayback; const mkVoiceBroadcastInfoEvent = (state: VoiceBroadcastInfoState) => { return mkEvent({ @@ -66,12 +79,19 @@ describe("VoiceBroadcastBody", () => { client = stubClient(); infoEvent = mkVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started); testRecording = new VoiceBroadcastRecording(infoEvent, client); + testPlayback = new VoiceBroadcastPlayback(infoEvent); mocked(VoiceBroadcastRecordingBody).mockImplementation(({ recording }) => { if (testRecording === recording) { return
; } }); + mocked(VoiceBroadcastPlaybackBody).mockImplementation(({ playback }) => { + if (testPlayback === playback) { + return
; + } + }); + jest.spyOn(VoiceBroadcastRecordingsStore.instance(), "getByInfoEvent").mockImplementation( (getEvent: MatrixEvent, getClient: MatrixClient) => { if (getEvent === infoEvent && getClient === client) { @@ -79,12 +99,35 @@ describe("VoiceBroadcastBody", () => { } }, ); + + jest.spyOn(VoiceBroadcastPlaybacksStore.instance(), "getByInfoEvent").mockImplementation( + (getEvent: MatrixEvent) => { + if (getEvent === infoEvent) { + return testPlayback; + } + }, + ); }); - describe("when rendering a voice broadcast", () => { + describe("when displaying a voice broadcast recording", () => { + beforeEach(() => { + mocked(shouldDisplayAsVoiceBroadcastRecordingTile).mockReturnValue(true); + }); + it("should render a voice broadcast recording body", () => { renderVoiceBroadcast(); screen.getByTestId("voice-broadcast-recording-body"); }); }); + + describe("when displaying a voice broadcast playback", () => { + beforeEach(() => { + mocked(shouldDisplayAsVoiceBroadcastRecordingTile).mockReturnValue(false); + }); + + it("should render a voice broadcast playback body", () => { + renderVoiceBroadcast(); + screen.getByTestId("voice-broadcast-playback-body"); + }); + }); }); diff --git a/test/voice-broadcast/components/atoms/PlaybackControlButton-test.tsx b/test/voice-broadcast/components/atoms/PlaybackControlButton-test.tsx new file mode 100644 index 0000000000..f3e03d38f2 --- /dev/null +++ b/test/voice-broadcast/components/atoms/PlaybackControlButton-test.tsx @@ -0,0 +1,45 @@ +/* +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, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import { PlaybackControlButton, VoiceBroadcastPlaybackState } from "../../../../src/voice-broadcast"; + +describe("PlaybackControlButton", () => { + let onClick: () => void; + + beforeEach(() => { + onClick = jest.fn(); + }); + + it.each([ + [VoiceBroadcastPlaybackState.Playing], + [VoiceBroadcastPlaybackState.Paused], + [VoiceBroadcastPlaybackState.Stopped], + ])("should render state »%s« as expected", (state: VoiceBroadcastPlaybackState) => { + const result = render(); + expect(result.container).toMatchSnapshot(); + }); + + it("should call onClick on click", async () => { + render(); + const button = screen.getByLabelText("pause voice broadcast"); + await userEvent.click(button); + expect(onClick).toHaveBeenCalled(); + }); +}); diff --git a/test/voice-broadcast/components/atoms/__snapshots__/PlaybackControlButton-test.tsx.snap b/test/voice-broadcast/components/atoms/__snapshots__/PlaybackControlButton-test.tsx.snap new file mode 100644 index 0000000000..0e674c8000 --- /dev/null +++ b/test/voice-broadcast/components/atoms/__snapshots__/PlaybackControlButton-test.tsx.snap @@ -0,0 +1,55 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PlaybackControlButton should render state »0« as expected 1`] = ` +
+
+