Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ahoyapps 960 stop video on disconnect #401

Merged
merged 5 commits into from
Feb 2, 2021
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
59 changes: 53 additions & 6 deletions src/components/Buttons/EndCallButton/EndCallButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,60 @@ import { shallow } from 'enzyme';

import EndCallButton from './EndCallButton';

const mockRoom: any = { disconnect: jest.fn() };
jest.mock('../../../hooks/useVideoContext/useVideoContext', () => () => ({ room: mockRoom }));
const mockVideoContext = {
room: {
disconnect: jest.fn(),
},
isSharingScreen: false,
toggleScreenShare: jest.fn(),
removeLocalAudioTrack: jest.fn(),
removeLocalVideoTrack: jest.fn(),
};

jest.mock('../../../hooks/useVideoContext/useVideoContext', () => () => mockVideoContext);

describe('End Call button', () => {
it('should disconnect from the room when clicked', () => {
const wrapper = shallow(<EndCallButton />);
wrapper.simulate('click');
expect(mockRoom.disconnect).toHaveBeenCalled();
describe('when it is clicked', () => {
describe('while sharing screen', () => {
let wrapper;

beforeAll(() => {
jest.clearAllMocks();
mockVideoContext.isSharingScreen = true;
wrapper = shallow(<EndCallButton />);
wrapper.simulate('click');
});

it('should stop local audio tracks', () => {
expect(mockVideoContext.removeLocalAudioTrack).toHaveBeenCalled();
});

it('should stop local video tracks', () => {
expect(mockVideoContext.removeLocalVideoTrack).toHaveBeenCalled();
});

it('should toggle screen sharing off', () => {
expect(mockVideoContext.toggleScreenShare).toHaveBeenCalled();
});

it('should disconnect from the room ', () => {
expect(mockVideoContext.room.disconnect).toHaveBeenCalled();
});
});

describe('while not sharing screen', () => {
let wrapper;

beforeAll(() => {
jest.clearAllMocks();
mockVideoContext.isSharingScreen = false;
wrapper = shallow(<EndCallButton />);
wrapper.simulate('click');
});

it('should not toggle screen sharing', () => {
expect(mockVideoContext.toggleScreenShare).not.toHaveBeenCalled();
});
});
});
});
13 changes: 11 additions & 2 deletions src/components/Buttons/EndCallButton/EndCallButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,19 @@ const useStyles = makeStyles((theme: Theme) =>

export default function EndCallButton(props: { className?: string }) {
const classes = useStyles();
const { room } = useVideoContext();
const { room, isSharingScreen, toggleScreenShare, removeLocalAudioTrack, removeLocalVideoTrack } = useVideoContext();

const handleClick = () => {
if (isSharingScreen) {
toggleScreenShare();
}
removeLocalAudioTrack();
removeLocalVideoTrack();
room.disconnect();
};

return (
<Button onClick={() => room.disconnect()} className={clsx(classes.button, props.className)} data-cy-disconnect>
<Button onClick={handleClick} className={clsx(classes.button, props.className)} data-cy-disconnect>
Disconnect
</Button>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import useLocalVideoToggle from '../../../hooks/useLocalVideoToggle/useLocalVide
import ToggleVideoButton from './ToggleVideoButton';
import VideoOffIcon from '../../../icons/VideoOffIcon';
import VideoOnIcon from '../../../icons/VideoOnIcon';
import { useHasVideoInputDevices } from '../../../hooks/deviceHooks/deviceHooks';
import useDevices from '../../../hooks/useDevices/useDevices';

jest.mock('../../../hooks/deviceHooks/deviceHooks');
jest.mock('../../../hooks/useDevices/useDevices');
jest.mock('../../../hooks/useLocalVideoToggle/useLocalVideoToggle');

const mockUseLocalVideoToggle = useLocalVideoToggle as jest.Mock<any>;
const mockUseHasVideoInputDevices = useHasVideoInputDevices as jest.Mock<any>;
const mockUseDevices = useDevices as jest.Mock<any>;

describe('the ToggleVideoButton component', () => {
beforeAll(() => {
mockUseHasVideoInputDevices.mockImplementation(() => true);
mockUseDevices.mockImplementation(() => ({ hasVideoInputDevices: true }));
});

it('should render correctly when video is enabled', () => {
Expand All @@ -34,7 +34,7 @@ describe('the ToggleVideoButton component', () => {

it('should render correctly when no video devices exist', () => {
mockUseLocalVideoToggle.mockImplementation(() => [true, () => {}]);
mockUseHasVideoInputDevices.mockImplementationOnce(() => false);
mockUseDevices.mockImplementationOnce(() => ({ hasVideoInputDevices: false }));
const wrapper = shallow(<ToggleVideoButton />);
expect(wrapper.prop('startIcon')).toEqual(<VideoOnIcon />);
expect(wrapper.prop('disabled')).toEqual(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import Button from '@material-ui/core/Button';
import VideoOffIcon from '../../../icons/VideoOffIcon';
import VideoOnIcon from '../../../icons/VideoOnIcon';

import { useHasVideoInputDevices } from '../../../hooks/deviceHooks/deviceHooks';
import useDevices from '../../../hooks/useDevices/useDevices';
import useLocalVideoToggle from '../../../hooks/useLocalVideoToggle/useLocalVideoToggle';

export default function ToggleVideoButton(props: { disabled?: boolean; className?: string }) {
const [isVideoEnabled, toggleVideoEnabled] = useLocalVideoToggle();
const lastClickTimeRef = useRef(0);
const hasVideoDevices = useHasVideoInputDevices();
const { hasVideoInputDevices } = useDevices();

const toggleVideo = useCallback(() => {
if (Date.now() - lastClickTimeRef.current > 500) {
Expand All @@ -23,10 +23,10 @@ export default function ToggleVideoButton(props: { disabled?: boolean; className
<Button
className={props.className}
onClick={toggleVideo}
disabled={!hasVideoDevices || props.disabled}
disabled={!hasVideoInputDevices || props.disabled}
startIcon={isVideoEnabled ? <VideoOnIcon /> : <VideoOffIcon />}
>
{!hasVideoDevices ? 'No Video' : isVideoEnabled ? 'Stop Video' : 'Start Video'}
{!hasVideoInputDevices ? 'No Video' : isVideoEnabled ? 'Stop Video' : 'Start Video'}
</Button>
);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React from 'react';
import AudioInputList from './AudioInputList';
import { DEFAULT_VIDEO_CONSTRAINTS, SELECTED_AUDIO_INPUT_KEY } from '../../../constants';
import { SELECTED_AUDIO_INPUT_KEY } from '../../../constants';
import { Select, Typography } from '@material-ui/core';
import { shallow } from 'enzyme';
import { useAudioInputDevices } from '../../../hooks/deviceHooks/deviceHooks';
import useDevices from '../../../hooks/useDevices/useDevices';
import useVideoContext from '../../../hooks/useVideoContext/useVideoContext';

jest.mock('../../../hooks/useVideoContext/useVideoContext');
jest.mock('../../../hooks/deviceHooks/deviceHooks');
jest.mock('../../../hooks/useDevices/useDevices');

const mockUseVideoContext = useVideoContext as jest.Mock<any>;
const mockUseAudioInputDevices = useAudioInputDevices as jest.Mock<any>;
const mockUseDevices = useDevices as jest.Mock<any>;
const mockGetLocalAudiotrack = jest.fn(() => Promise.resolve);

const mockDevice = {
Expand All @@ -35,7 +35,7 @@ mockUseVideoContext.mockImplementation(() => ({

describe('the AudioInputList component', () => {
it('should display the name of the local audio track when only one is avaiable', () => {
mockUseAudioInputDevices.mockImplementation(() => [mockDevice]);
mockUseDevices.mockImplementation(() => ({ audioInputDevices: [mockDevice] }));
const wrapper = shallow(<AudioInputList />);
expect(wrapper.find(Select).exists()).toBe(false);
expect(
Expand All @@ -47,7 +47,7 @@ describe('the AudioInputList component', () => {
});

it('should display "No Local Audio" when there is no local audio track', () => {
mockUseAudioInputDevices.mockImplementation(() => [mockDevice]);
mockUseDevices.mockImplementation(() => ({ audioInputDevices: [mockDevice] }));
mockUseVideoContext.mockImplementationOnce(() => ({
room: {},
getLocalAudioTrack: mockGetLocalAudiotrack,
Expand All @@ -63,7 +63,7 @@ describe('the AudioInputList component', () => {
});

it('should render a Select menu when there are multiple audio input devices', () => {
mockUseAudioInputDevices.mockImplementation(() => [mockDevice, mockDevice]);
mockUseDevices.mockImplementation(() => ({ audioInputDevices: [mockDevice, mockDevice] }));
const wrapper = shallow(<AudioInputList />);
expect(wrapper.find(Select).exists()).toBe(true);
expect(
Expand All @@ -75,15 +75,15 @@ describe('the AudioInputList component', () => {
});

it('should save the deviceId in localStorage when the audio input device is changed', () => {
mockUseAudioInputDevices.mockImplementation(() => [mockDevice, mockDevice]);
mockUseDevices.mockImplementation(() => ({ audioInputDevices: [mockDevice, mockDevice] }));
const wrapper = shallow(<AudioInputList />);
expect(window.localStorage.getItem(SELECTED_AUDIO_INPUT_KEY)).toBe(undefined);
wrapper.find(Select).simulate('change', { target: { value: 'mockDeviceID' } });
expect(window.localStorage.getItem(SELECTED_AUDIO_INPUT_KEY)).toBe('mockDeviceID');
});

it('should call track.restart with the new deviceId when the audio input device is changed', () => {
mockUseAudioInputDevices.mockImplementation(() => [mockDevice, mockDevice]);
mockUseDevices.mockImplementation(() => ({ audioInputDevices: [mockDevice, mockDevice] }));
const wrapper = shallow(<AudioInputList />);
wrapper.find(Select).simulate('change', { target: { value: 'mockDeviceID' } });
expect(mockLocalTrack.restart).toHaveBeenCalledWith({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import AudioLevelIndicator from '../../AudioLevelIndicator/AudioLevelIndicator';
import { LocalAudioTrack } from 'twilio-video';
import { FormControl, MenuItem, Typography, Select, Grid } from '@material-ui/core';
import { SELECTED_AUDIO_INPUT_KEY } from '../../../constants';
import { useAudioInputDevices } from '../../../hooks/deviceHooks/deviceHooks';
import useDevices from '../../../hooks/useDevices/useDevices';
import useMediaStreamTrack from '../../../hooks/useMediaStreamTrack/useMediaStreamTrack';
import useVideoContext from '../../../hooks/useVideoContext/useVideoContext';

export default function AudioInputList() {
const audioInputDevices = useAudioInputDevices();
const { audioInputDevices } = useDevices();
const { localTracks } = useVideoContext();

const localAudioTrack = localTracks.find(track => track.kind === 'audio') as LocalAudioTrack;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import React from 'react';
import AudioOutputList from './AudioOutputList';
import { Select, Typography } from '@material-ui/core';
import { shallow } from 'enzyme';
import { useAudioOutputDevices } from '../../../hooks/deviceHooks/deviceHooks';
import useDevices from '../../../hooks/useDevices/useDevices';
import { useAppState } from '../../../state';

jest.mock('../../../state');
jest.mock('../../../hooks/deviceHooks/deviceHooks');
jest.mock('../../../hooks/useDevices/useDevices');

const mockUseAppState = useAppState as jest.Mock<any>;
const mockUseAudioOutputDevices = useAudioOutputDevices as jest.Mock<any>;
const mockUseDevices = useDevices as jest.Mock<any>;

mockUseAppState.mockImplementation(() => ({ activeSinkId: '123' }));

Expand All @@ -20,7 +20,7 @@ const mockDevice = {

describe('the AudioOutputList component', () => {
it('should display the name of the active output device if only one is available', () => {
mockUseAudioOutputDevices.mockImplementation(() => [mockDevice]);
mockUseDevices.mockImplementation(() => ({ audioOutputDevices: [mockDevice] }));
const wrapper = shallow(<AudioOutputList />);
expect(wrapper.find(Select).exists()).toBe(false);
expect(
Expand All @@ -32,7 +32,7 @@ describe('the AudioOutputList component', () => {
});

it('should display "System Default Audio Output" when no audio output devices are available', () => {
mockUseAudioOutputDevices.mockImplementation(() => []);
mockUseDevices.mockImplementation(() => ({ audioOutputDevices: [] }));
const wrapper = shallow(<AudioOutputList />);
expect(wrapper.find(Select).exists()).toBe(false);
expect(
Expand All @@ -44,7 +44,7 @@ describe('the AudioOutputList component', () => {
});

it('should display a Select menu when multiple audio output devices are available', () => {
mockUseAudioOutputDevices.mockImplementation(() => [mockDevice, mockDevice]);
mockUseDevices.mockImplementation(() => ({ audioOutputDevices: [mockDevice, mockDevice] }));
const wrapper = shallow(<AudioOutputList />);
expect(wrapper.find(Select).exists()).toBe(true);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import { FormControl, MenuItem, Typography, Select } from '@material-ui/core';
import { useAppState } from '../../../state';
import { useAudioOutputDevices } from '../../../hooks/deviceHooks/deviceHooks';
import useDevices from '../../../hooks/useDevices/useDevices';

export default function AudioOutputList() {
const audioOutputDevices = useAudioOutputDevices();
const { audioOutputDevices } = useDevices();
const { activeSinkId, setActiveSinkId } = useAppState();
const activeOutputLabel = audioOutputDevices.find(device => device.deviceId === activeSinkId)?.label;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import React from 'react';
import { DEFAULT_VIDEO_CONSTRAINTS, SELECTED_VIDEO_INPUT_KEY } from '../../../constants';
import { Select, Typography } from '@material-ui/core';
import { shallow } from 'enzyme';
import useDevices from '../../../hooks/useDevices/useDevices';
import useVideoContext from '../../../hooks/useVideoContext/useVideoContext';
import { useVideoInputDevices } from '../../../hooks/deviceHooks/deviceHooks';
import VideoInputList from './VideoInputList';

jest.mock('../../../hooks/useVideoContext/useVideoContext');
jest.mock('../../../hooks/deviceHooks/deviceHooks');
jest.mock('../../../hooks/useDevices/useDevices');

const mockUseVideoContext = useVideoContext as jest.Mock<any>;
const mockUseVideoInputDevices = useVideoInputDevices as jest.Mock<any>;
const mockUseDevices = useDevices as jest.Mock<any>;
const mockGetLocalVideotrack = jest.fn(() => Promise.resolve);

const mockDevice = {
Expand Down Expand Up @@ -41,7 +41,7 @@ describe('the VideoInputList component', () => {

describe('with only one video input device', () => {
it('should not display a Select menu and instead display the name of the local video track', () => {
mockUseVideoInputDevices.mockImplementation(() => [mockDevice]);
mockUseDevices.mockImplementation(() => ({ videoInputDevices: [mockDevice] }));
const wrapper = shallow(<VideoInputList />);
expect(wrapper.find(Select).exists()).toBe(false);
expect(
Expand All @@ -53,7 +53,7 @@ describe('the VideoInputList component', () => {
});

it('should display "No Local Video" when there is no local video track', () => {
mockUseVideoInputDevices.mockImplementation(() => [mockDevice]);
mockUseDevices.mockImplementation(() => ({ videoInputDevices: [mockDevice] }));
mockUseVideoContext.mockImplementationOnce(() => ({
room: {},
getLocalVideoTrack: mockGetLocalVideotrack,
Expand All @@ -70,7 +70,7 @@ describe('the VideoInputList component', () => {
});

it('should render a Select menu when there are multiple video input devices', () => {
mockUseVideoInputDevices.mockImplementation(() => [mockDevice, mockDevice]);
mockUseDevices.mockImplementation(() => ({ videoInputDevices: [mockDevice, mockDevice] }));
const wrapper = shallow(<VideoInputList />);
expect(wrapper.find(Select).exists()).toBe(true);
expect(
Expand All @@ -82,15 +82,15 @@ describe('the VideoInputList component', () => {
});

it('should save the deviceId in localStorage when the video input device is changed', () => {
mockUseVideoInputDevices.mockImplementation(() => [mockDevice, mockDevice]);
mockUseDevices.mockImplementation(() => ({ videoInputDevices: [mockDevice, mockDevice] }));
const wrapper = shallow(<VideoInputList />);
expect(window.localStorage.getItem(SELECTED_VIDEO_INPUT_KEY)).toBe(undefined);
wrapper.find(Select).simulate('change', { target: { value: 'mockDeviceID' } });
expect(window.localStorage.getItem(SELECTED_VIDEO_INPUT_KEY)).toBe('mockDeviceID');
});

it('should call track.restart with the new deviceId when the video input device is changed', () => {
mockUseVideoInputDevices.mockImplementation(() => [mockDevice, mockDevice]);
mockUseDevices.mockImplementation(() => ({ videoInputDevices: [mockDevice, mockDevice] }));
const wrapper = shallow(<VideoInputList />);
wrapper.find(Select).simulate('change', { target: { value: 'mockDeviceID' } });
expect(mockLocalTrack.restart).toHaveBeenCalledWith({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { FormControl, MenuItem, Typography, Select } from '@material-ui/core';
import { LocalVideoTrack } from 'twilio-video';
import { makeStyles } from '@material-ui/core/styles';
import VideoTrack from '../../VideoTrack/VideoTrack';
import useDevices from '../../../hooks/useDevices/useDevices';
import useMediaStreamTrack from '../../../hooks/useMediaStreamTrack/useMediaStreamTrack';
import useVideoContext from '../../../hooks/useVideoContext/useVideoContext';
import { useVideoInputDevices } from '../../../hooks/deviceHooks/deviceHooks';

const useStyles = makeStyles({
preview: {
Expand All @@ -21,7 +21,7 @@ const useStyles = makeStyles({

export default function VideoInputList() {
const classes = useStyles();
const videoInputDevices = useVideoInputDevices();
const { videoInputDevices } = useDevices();
const { localTracks } = useVideoContext();

const localVideoTrack = localTracks.find(track => track.kind === 'video') as LocalVideoTrack;
Expand Down
Loading