Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 9 additions & 128 deletions src/components/Controls/Controls.test.tsx
Original file line number Diff line number Diff line change
@@ -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<any>;
const mockUseLocalVideoToggle = useLocalVideoToggle as jest.Mock<any>;
const mockUseRoomState = useRoomState as jest.Mock<any>;
const mockUseScreenShareToggle = useScreenShareToggle as jest.Mock<any>;
const mockUseScreenShareParticipant = useScreenShareParticipant as jest.Mock<any>;

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(<Controls />);
expect(wrapper.find('CallEndIcon').exists()).toBe(false);
});

it('should render when connected to a room', () => {
mockUseRoomState.mockImplementation(() => 'connected');
const wrapper = shallow(<Controls />);
expect(wrapper.find('CallEndIcon').exists()).toBe(true);
});

it('should delete the token from redux when clicked', () => {
mockUseRoomState.mockImplementation(() => 'connected');
const wrapper = shallow(<Controls />);
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(<Controls />);
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(<Controls />);
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(<Controls />);
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(<Controls />);
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(<Controls />);
expect(wrapper.find('ScreenShareIcon').exists()).toBe(true);
});

it('should render correctly when the user is sharing their screen', () => {
mockUseScreenShareToggle.mockImplementation(() => [true, () => {}]);
const wrapper = shallow(<Controls />);
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(<Controls />);
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(<Controls />);
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);
});
});
77 changes: 8 additions & 69 deletions src/components/Controls/Controls.tsx
Original file line number Diff line number Diff line change
@@ -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%',
Expand All @@ -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 (
<div className={classes.container}>
<Tooltip
title={isAudioEnabled ? 'Mute Audio' : 'Unmute Audio'}
placement="top"
PopperProps={{ disablePortal: true }}
>
<Fab className={classes.fab} onClick={toggleAudioEnabled}>
{isAudioEnabled ? <Mic /> : <MicOff />}
</Fab>
</Tooltip>
<Tooltip
title={isVideoEnabled ? 'Mute Video' : 'Unmute Video'}
placement="top"
PopperProps={{ disablePortal: true }}
>
<Fab className={classes.fab} onClick={toggleVideoEnabled}>
{isVideoEnabled ? <Videocam /> : <VideocamOff />}
</Fab>
</Tooltip>
<ToggleAudioButton />
<ToggleVideoButton />
{roomState === 'connected' && (
<>
{navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia && (
<Tooltip
title={isScreenShared ? 'Stop Screen Sharing' : 'Share Screen'}
placement="top"
PopperProps={{ disablePortal: true }}
>
<Fab className={classes.fab} onClick={toggleScreenShare} disabled={disableScreenShareButton}>
{isScreenShared ? <StopScreenShare /> : <ScreenShare />}
</Fab>
</Tooltip>
)}
<Tooltip
title={'End Call'}
onClick={() => dispatch(receiveToken(''))}
placement="top"
PopperProps={{ disablePortal: true }}
>
<Fab className={classes.fab} color="primary">
<CallEnd />
</Fab>
</Tooltip>
<ToggleScreenShareButton />
<EndCallButton />
</>
)}
</div>
Expand Down
16 changes: 16 additions & 0 deletions src/components/Controls/EndCallButton/EndCallButton.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<EndCallButton />);
wrapper.simulate('click');
expect(receiveToken).toHaveBeenCalledWith('');
});
});
35 changes: 35 additions & 0 deletions src/components/Controls/EndCallButton/EndCallButton.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Tooltip
title={'End Call'}
onClick={() => dispatch(receiveToken(''))}
placement="top"
PopperProps={{ disablePortal: true }}
>
<Fab className={classes.fab} color="primary">
<CallEnd />
</Fab>
</Tooltip>
);
}
Original file line number Diff line number Diff line change
@@ -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<any>;

describe('the ToggleAudioButton component', () => {
it('should render correctly when audio is enabled', () => {
mockUseLocalAudioToggle.mockImplementation(() => [true, () => {}]);
const wrapper = shallow(<ToggleAudioButton />);
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(<ToggleAudioButton />);
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(<ToggleAudioButton />);
wrapper.find('WithStyles(ForwardRef(Fab))').simulate('click');
expect(mockFn).toHaveBeenCalled();
});
});
34 changes: 34 additions & 0 deletions src/components/Controls/ToggleAudioButton/ToggleAudioButton.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Tooltip
title={isAudioEnabled ? 'Mute Audio' : 'Unmute Audio'}
placement="top"
PopperProps={{ disablePortal: true }}
>
<Fab className={classes.fab} onClick={toggleAudioEnabled}>
{isAudioEnabled ? <Mic /> : <MicOff />}
</Fab>
</Tooltip>
);
}
Loading