diff --git a/src/components/Controls/Controls.test.tsx b/src/components/Controls/Controls.test.tsx index ca0b213de..788fc6899 100644 --- a/src/components/Controls/Controls.test.tsx +++ b/src/components/Controls/Controls.test.tsx @@ -1,144 +1,25 @@ import React from 'react'; -import { receiveToken } from '../../store/main/main'; import { shallow } from 'enzyme'; -import useLocalAudioToggle from '../../hooks/useLocalAudioToggle/useLocalAudioToggle'; -import useLocalVideoToggle from '../../hooks/useLocalVideoToggle/useLocalVideoToggle'; -import useRoomState from '../../hooks/useRoomState/useRoomState'; -import useScreenShareParticipant from '../../hooks/useScreenShareParticipant/useScreenShareParticipant'; -import useScreenShareToggle from '../../hooks/useScreenShareToggle/useScreenShareToggle'; import Controls from './Controls'; +import useRoomState from '../../hooks/useRoomState/useRoomState'; -jest.mock('../../hooks/useLocalAudioToggle/useLocalAudioToggle'); -jest.mock('../../hooks/useLocalVideoToggle/useLocalVideoToggle'); jest.mock('../../hooks/useRoomState/useRoomState'); -jest.mock('../../hooks/useScreenShareToggle/useScreenShareToggle'); -jest.mock('../../hooks/useScreenShareParticipant/useScreenShareParticipant'); -jest.mock('../../store/main/main'); -jest.mock('react-redux', () => ({ useDispatch: () => jest.fn() })); -const mockUseLocalAudioToggle = useLocalAudioToggle as jest.Mock; -const mockUseLocalVideoToggle = useLocalVideoToggle as jest.Mock; const mockUseRoomState = useRoomState as jest.Mock; -const mockUseScreenShareToggle = useScreenShareToggle as jest.Mock; -const mockUseScreenShareParticipant = useScreenShareParticipant as jest.Mock; - -Object.defineProperty(navigator, 'mediaDevices', { - value: { - getDisplayMedia: () => {}, - }, - configurable: true, -}); describe('the Controls component', () => { - beforeEach(() => { - mockUseLocalAudioToggle.mockImplementation(() => [true, () => {}]); - mockUseLocalVideoToggle.mockImplementation(() => [true, () => {}]); - mockUseScreenShareToggle.mockImplementation(() => [false, () => {}]); - }); - - describe('End Call button', () => { - it('should not render when not connected to a room', () => { - const wrapper = shallow(); - expect(wrapper.find('CallEndIcon').exists()).toBe(false); - }); - - it('should render when connected to a room', () => { - mockUseRoomState.mockImplementation(() => 'connected'); - const wrapper = shallow(); - expect(wrapper.find('CallEndIcon').exists()).toBe(true); - }); - - it('should delete the token from redux when clicked', () => { - mockUseRoomState.mockImplementation(() => 'connected'); - const wrapper = shallow(); - wrapper - .find('WithStyles(ForwardRef(Tooltip))') - .at(3) - .simulate('click'); - expect(receiveToken).toHaveBeenCalledWith(''); - }); - }); - - it('should render correctly when audio is enabled', () => { - mockUseLocalAudioToggle.mockImplementation(() => [true, () => {}]); - const wrapper = shallow(); - expect(wrapper.find('MicIcon').exists()).toBe(true); - expect(wrapper.find('MicOffIcon').exists()).toBe(false); - expect( - wrapper - .find('WithStyles(ForwardRef(Tooltip))') - .at(0) - .prop('title') - ).toBe('Mute Audio'); - }); - - it('should render correctly when audio is disabled', () => { - mockUseLocalAudioToggle.mockImplementation(() => [false, () => {}]); - const wrapper = shallow(); - expect(wrapper.find('MicIcon').exists()).toBe(false); - expect(wrapper.find('MicOffIcon').exists()).toBe(true); - expect( - wrapper - .find('WithStyles(ForwardRef(Tooltip))') - .at(0) - .prop('title') - ).toBe('Unmute Audio'); - }); - - it('should render correctly when video is enabled', () => { - mockUseLocalVideoToggle.mockImplementation(() => [true, () => {}]); - const wrapper = shallow(); - expect(wrapper.find('VideocamIcon').exists()).toBe(true); - expect(wrapper.find('VideocamOffIcon').exists()).toBe(false); - expect( - wrapper - .find('WithStyles(ForwardRef(Tooltip))') - .at(1) - .prop('title') - ).toBe('Mute Video'); - }); - - it('should render correctly when video is disabled', () => { - mockUseLocalVideoToggle.mockImplementation(() => [false, () => {}]); - const wrapper = shallow(); - expect(wrapper.find('VideocamIcon').exists()).toBe(false); - expect(wrapper.find('VideocamOffIcon').exists()).toBe(true); - expect( - wrapper - .find('WithStyles(ForwardRef(Tooltip))') - .at(1) - .prop('title') - ).toBe('Unmute Video'); - }); - - it('should render correctly when screenSharing is allowed', () => { - const wrapper = shallow(); - expect(wrapper.find('ScreenShareIcon').exists()).toBe(true); - }); - - it('should render correctly when the user is sharing their screen', () => { - mockUseScreenShareToggle.mockImplementation(() => [true, () => {}]); - const wrapper = shallow(); - expect(wrapper.find('StopScreenShareIcon').exists()).toBe(true); - }); - - it('should render correctly when another user is sharing their screen', () => { - mockUseScreenShareParticipant.mockImplementation(() => 'mockParticipant'); - mockUseScreenShareToggle.mockImplementation(() => [false, () => {}]); + it('should not render the ScreenShare and EndCall buttons when not connected to a room', () => { + mockUseRoomState.mockImplementation(() => 'disconnected'); const wrapper = shallow(); - expect( - wrapper - .find('WithStyles(ForwardRef(Fab))') - .at(2) - .prop('disabled') - ).toBe(true); + expect(wrapper.find('ToggleScreenShareButton').exists()).toBe(false); + expect(wrapper.find('EndCallButton').exists()).toBe(false); }); - it('should not render the screenshare button if screensharing is not supported', () => { - Object.defineProperty(navigator, 'mediaDevices', { value: { getDisplayMedia: undefined } }); + it('should render the ScreenShare and EndCall buttons when connected to a room', () => { + mockUseRoomState.mockImplementation(() => 'connected'); const wrapper = shallow(); - expect(wrapper.find('ScreenShareIcon').exists()).toBe(false); - expect(wrapper.find('StopScreenShareIcon').exists()).toBe(false); + expect(wrapper.find('ToggleScreenShareButton').exists()).toBe(true); + expect(wrapper.find('EndCallButton').exists()).toBe(true); }); }); diff --git a/src/components/Controls/Controls.tsx b/src/components/Controls/Controls.tsx index 596fda031..eb7647b82 100644 --- a/src/components/Controls/Controls.tsx +++ b/src/components/Controls/Controls.tsx @@ -1,34 +1,15 @@ import React from 'react'; import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; -import CallEnd from '@material-ui/icons/CallEnd'; -import Fab from '@material-ui/core/Fab'; -import Mic from '@material-ui/icons/Mic'; -import MicOff from '@material-ui/icons/MicOff'; -import Tooltip from '@material-ui/core/Tooltip'; -import ScreenShare from '@material-ui/icons/ScreenShare'; -import StopScreenShare from '@material-ui/icons/StopScreenShare'; -import Videocam from '@material-ui/icons/Videocam'; -import VideocamOff from '@material-ui/icons/VideocamOff'; +import EndCallButton from './EndCallButton/EndCallButton'; +import ToggleAudioButton from './ToggleAudioButton/ToggleAudioButton'; +import ToggleVideoButton from './ToggleVideoButton/ToggleVideoButton'; +import ToggleScreenShareButton from './ToogleScreenShareButton/ToggleScreenShareButton'; -import useLocalAudioToggle from '../../hooks/useLocalAudioToggle/useLocalAudioToggle'; -import useLocalVideoToggle from '../../hooks/useLocalVideoToggle/useLocalVideoToggle'; import useRoomState from '../../hooks/useRoomState/useRoomState'; -import { useDispatch } from 'react-redux'; -import { receiveToken } from '../../store/main/main'; -import useScreenShareToggle from '../../hooks/useScreenShareToggle/useScreenShareToggle'; -import useScreenShareParticipant from '../../hooks/useScreenShareParticipant/useScreenShareParticipant'; -import { useVideoContext } from '../../hooks/context'; - const useStyles = makeStyles((theme: Theme) => createStyles({ - fab: { - margin: theme.spacing(1), - }, - extendedIcon: { - marginRight: theme.spacing(1), - }, container: { position: 'absolute', right: '50%', @@ -41,58 +22,16 @@ const useStyles = makeStyles((theme: Theme) => export default function Controls() { const classes = useStyles(); - const dispatch = useDispatch(); - const [isAudioEnabled, toggleAudioEnabled] = useLocalAudioToggle(); - const [isVideoEnabled, toggleVideoEnabled] = useLocalVideoToggle(); - const [isScreenShared, toggleScreenShare] = useScreenShareToggle(); const roomState = useRoomState(); - const screenShareParticipant = useScreenShareParticipant(); - const { room } = useVideoContext(); - const disableScreenShareButton = screenShareParticipant && screenShareParticipant !== room.localParticipant; return (
- - - {isAudioEnabled ? : } - - - - - {isVideoEnabled ? : } - - + + {roomState === 'connected' && ( <> - {navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia && ( - - - {isScreenShared ? : } - - - )} - dispatch(receiveToken(''))} - placement="top" - PopperProps={{ disablePortal: true }} - > - - - - + + )}
diff --git a/src/components/Controls/EndCallButton/EndCallButton.test.tsx b/src/components/Controls/EndCallButton/EndCallButton.test.tsx new file mode 100644 index 000000000..e1209785d --- /dev/null +++ b/src/components/Controls/EndCallButton/EndCallButton.test.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { receiveToken } from '../../../store/main/main'; +import { shallow } from 'enzyme'; + +import EndCallButton from './EndCallButton'; + +jest.mock('../../../store/main/main'); +jest.mock('react-redux', () => ({ useDispatch: () => jest.fn() })); + +describe('End Call button', () => { + it('should delete the token from redux when clicked', () => { + const wrapper = shallow(); + wrapper.simulate('click'); + expect(receiveToken).toHaveBeenCalledWith(''); + }); +}); diff --git a/src/components/Controls/EndCallButton/EndCallButton.tsx b/src/components/Controls/EndCallButton/EndCallButton.tsx new file mode 100644 index 000000000..f15055ea2 --- /dev/null +++ b/src/components/Controls/EndCallButton/EndCallButton.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; + +import CallEnd from '@material-ui/icons/CallEnd'; +import Fab from '@material-ui/core/Fab'; +import Tooltip from '@material-ui/core/Tooltip'; + +import { receiveToken } from '../../../store/main/main'; +import { useDispatch } from 'react-redux'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + fab: { + margin: theme.spacing(1), + }, + }) +); + +export default function EndCallButton() { + const classes = useStyles(); + const dispatch = useDispatch(); + + return ( + dispatch(receiveToken(''))} + placement="top" + PopperProps={{ disablePortal: true }} + > + + + + + ); +} diff --git a/src/components/Controls/ToggleAudioButton/ToggleAudioButton.test.tsx b/src/components/Controls/ToggleAudioButton/ToggleAudioButton.test.tsx new file mode 100644 index 000000000..528ff877d --- /dev/null +++ b/src/components/Controls/ToggleAudioButton/ToggleAudioButton.test.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import useLocalAudioToggle from '../../../hooks/useLocalAudioToggle/useLocalAudioToggle'; + +import ToggleAudioButton from './ToggleAudioButton'; + +jest.mock('../../../hooks/useLocalAudioToggle/useLocalAudioToggle'); + +const mockUseLocalAudioToggle = useLocalAudioToggle as jest.Mock; + +describe('the ToggleAudioButton component', () => { + it('should render correctly when audio is enabled', () => { + mockUseLocalAudioToggle.mockImplementation(() => [true, () => {}]); + const wrapper = shallow(); + expect(wrapper.find('MicIcon').exists()).toBe(true); + expect(wrapper.find('MicOffIcon').exists()).toBe(false); + expect( + wrapper + .find('WithStyles(ForwardRef(Tooltip))') + .at(0) + .prop('title') + ).toBe('Mute Audio'); + }); + + it('should render correctly when audio is disabled', () => { + mockUseLocalAudioToggle.mockImplementation(() => [false, () => {}]); + const wrapper = shallow(); + expect(wrapper.find('MicIcon').exists()).toBe(false); + expect(wrapper.find('MicOffIcon').exists()).toBe(true); + expect( + wrapper + .find('WithStyles(ForwardRef(Tooltip))') + .at(0) + .prop('title') + ).toBe('Unmute Audio'); + }); + + it('should call the correct toggle function when clicked', () => { + const mockFn = jest.fn(); + mockUseLocalAudioToggle.mockImplementation(() => [false, mockFn]); + const wrapper = shallow(); + wrapper.find('WithStyles(ForwardRef(Fab))').simulate('click'); + expect(mockFn).toHaveBeenCalled(); + }); +}); diff --git a/src/components/Controls/ToggleAudioButton/ToggleAudioButton.tsx b/src/components/Controls/ToggleAudioButton/ToggleAudioButton.tsx new file mode 100644 index 000000000..9a486fc5b --- /dev/null +++ b/src/components/Controls/ToggleAudioButton/ToggleAudioButton.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; + +import Fab from '@material-ui/core/Fab'; +import Mic from '@material-ui/icons/Mic'; +import MicOff from '@material-ui/icons/MicOff'; +import Tooltip from '@material-ui/core/Tooltip'; + +import useLocalAudioToggle from '../../../hooks/useLocalAudioToggle/useLocalAudioToggle'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + fab: { + margin: theme.spacing(1), + }, + }) +); + +export default function ToggleAudioButton() { + const classes = useStyles(); + const [isAudioEnabled, toggleAudioEnabled] = useLocalAudioToggle(); + + return ( + + + {isAudioEnabled ? : } + + + ); +} diff --git a/src/components/Controls/ToggleVideoButton/ToggleVideoButton.test.tsx b/src/components/Controls/ToggleVideoButton/ToggleVideoButton.test.tsx new file mode 100644 index 000000000..2cd448b2b --- /dev/null +++ b/src/components/Controls/ToggleVideoButton/ToggleVideoButton.test.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import useLocalVideoToggle from '../../../hooks/useLocalVideoToggle/useLocalVideoToggle'; + +import ToggleVideoButton from './ToggleVideoButton'; + +jest.mock('../../../hooks/useLocalVideoToggle/useLocalVideoToggle'); + +const mockUseLocalVideoToggle = useLocalVideoToggle as jest.Mock; + +describe('the ToggleVideoButton component', () => { + it('should render correctly when video is enabled', () => { + mockUseLocalVideoToggle.mockImplementation(() => [true, () => {}]); + const wrapper = shallow(); + expect(wrapper.find('VideocamIcon').exists()).toBe(true); + expect(wrapper.find('VideocamOffIcon').exists()).toBe(false); + expect(wrapper.prop('title')).toBe('Mute Video'); + }); + + it('should render correctly when video is disabled', () => { + mockUseLocalVideoToggle.mockImplementation(() => [false, () => {}]); + const wrapper = shallow(); + expect(wrapper.find('VideocamIcon').exists()).toBe(false); + expect(wrapper.find('VideocamOffIcon').exists()).toBe(true); + expect(wrapper.prop('title')).toBe('Unmute Video'); + }); + + it('should call the correct toggle function when clicked', () => { + const mockFn = jest.fn(); + mockUseLocalVideoToggle.mockImplementation(() => [false, mockFn]); + const wrapper = shallow(); + wrapper.find('WithStyles(ForwardRef(Fab))').simulate('click'); + expect(mockFn).toHaveBeenCalled(); + }); +}); diff --git a/src/components/Controls/ToggleVideoButton/ToggleVideoButton.tsx b/src/components/Controls/ToggleVideoButton/ToggleVideoButton.tsx new file mode 100644 index 000000000..14b4096e6 --- /dev/null +++ b/src/components/Controls/ToggleVideoButton/ToggleVideoButton.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; + +import Fab from '@material-ui/core/Fab'; +import Tooltip from '@material-ui/core/Tooltip'; +import Videocam from '@material-ui/icons/Videocam'; +import VideocamOff from '@material-ui/icons/VideocamOff'; + +import useLocalVideoToggle from '../../../hooks/useLocalVideoToggle/useLocalVideoToggle'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + fab: { + margin: theme.spacing(1), + }, + }) +); + +export default function ToggleVideoButton() { + const classes = useStyles(); + const [isVideoEnabled, toggleVideoEnabled] = useLocalVideoToggle(); + + return ( + + + {isVideoEnabled ? : } + + + ); +} diff --git a/src/components/Controls/ToogleScreenShareButton/ToggleScreenShareButton.test.tsx b/src/components/Controls/ToogleScreenShareButton/ToggleScreenShareButton.test.tsx new file mode 100644 index 000000000..5c2c1c4d1 --- /dev/null +++ b/src/components/Controls/ToogleScreenShareButton/ToggleScreenShareButton.test.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import useScreenShareParticipant from '../../../hooks/useScreenShareParticipant/useScreenShareParticipant'; +import useScreenShareToggle from '../../../hooks/useScreenShareToggle/useScreenShareToggle'; + +import ToggleScreenShareButton from './ToggleScreenShareButton'; + +jest.mock('../../../hooks/useScreenShareToggle/useScreenShareToggle'); +jest.mock('../../../hooks/useScreenShareParticipant/useScreenShareParticipant'); + +const mockUseScreenShareToggle = useScreenShareToggle as jest.Mock; +const mockUseScreenShareParticipant = useScreenShareParticipant as jest.Mock; + +Object.defineProperty(navigator, 'mediaDevices', { + value: { + getDisplayMedia: () => {}, + }, + configurable: true, +}); + +describe('the ToggleScreenShareButton component', () => { + it('should render correctly when screenSharing is allowed', () => { + mockUseScreenShareToggle.mockImplementation(() => [false, () => {}]); + const wrapper = shallow(); + expect(wrapper.find('ScreenShareIcon').exists()).toBe(true); + }); + + it('should render correctly when the user is sharing their screen', () => { + mockUseScreenShareToggle.mockImplementation(() => [true, () => {}]); + const wrapper = shallow(); + expect(wrapper.find('StopScreenShareIcon').exists()).toBe(true); + }); + + it('should render correctly when another user is sharing their screen', () => { + mockUseScreenShareParticipant.mockImplementation(() => 'mockParticipant'); + mockUseScreenShareToggle.mockImplementation(() => [false, () => {}]); + const wrapper = shallow(); + expect(wrapper.find('WithStyles(ForwardRef(Fab))').prop('disabled')).toBe(true); + }); + + it('should call the correct toggle function when clicked', () => { + const mockFn = jest.fn(); + mockUseScreenShareToggle.mockImplementation(() => [false, mockFn]); + const wrapper = shallow(); + wrapper.find('WithStyles(ForwardRef(Fab))').simulate('click'); + expect(mockFn).toHaveBeenCalled(); + }); + + it('should not render the screenshare button if screensharing is not supported', () => { + Object.defineProperty(navigator, 'mediaDevices', { value: { getDisplayMedia: undefined } }); + const wrapper = shallow(); + expect(wrapper.find('ScreenShareIcon').exists()).toBe(false); + expect(wrapper.find('StopScreenShareIcon').exists()).toBe(false); + }); +}); diff --git a/src/components/Controls/ToogleScreenShareButton/ToggleScreenShareButton.tsx b/src/components/Controls/ToogleScreenShareButton/ToggleScreenShareButton.tsx new file mode 100644 index 000000000..289f99a84 --- /dev/null +++ b/src/components/Controls/ToogleScreenShareButton/ToggleScreenShareButton.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; + +import Fab from '@material-ui/core/Fab'; +import ScreenShare from '@material-ui/icons/ScreenShare'; +import StopScreenShare from '@material-ui/icons/StopScreenShare'; +import Tooltip from '@material-ui/core/Tooltip'; + +import useScreenShareToggle from '../../../hooks/useScreenShareToggle/useScreenShareToggle'; +import useScreenShareParticipant from '../../../hooks/useScreenShareParticipant/useScreenShareParticipant'; +import { useVideoContext } from '../../../hooks/context'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + fab: { + margin: theme.spacing(1), + }, + }) +); + +export default function ToggleScreenShareButton() { + const classes = useStyles(); + const [isScreenShared, toggleScreenShare] = useScreenShareToggle(); + const screenShareParticipant = useScreenShareParticipant(); + const { room } = useVideoContext(); + const disableScreenShareButton = screenShareParticipant && screenShareParticipant !== room.localParticipant; + + return ( + navigator.mediaDevices && + navigator.mediaDevices.getDisplayMedia && ( + + + {isScreenShared ? : } + + + ) + ); +}