
* add call-join-request to calls widget * targetID -> callID --------- Co-authored-by: Mattermost Build <build@mattermost.com>
993 lines
34 KiB
JavaScript
993 lines
34 KiB
JavaScript
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
/* eslint-disable max-lines */
|
|
|
|
import {BrowserWindow, desktopCapturer, systemPreferences} from 'electron';
|
|
|
|
import ServerViewState from 'app/serverViewState';
|
|
|
|
import {CALLS_WIDGET_SHARE_SCREEN, CALLS_JOINED_CALL, CALLS_JOIN_REQUEST} from 'common/communication';
|
|
import {
|
|
MINIMUM_CALLS_WIDGET_WIDTH,
|
|
MINIMUM_CALLS_WIDGET_HEIGHT,
|
|
CALLS_PLUGIN_ID,
|
|
} from 'common/utils/constants';
|
|
import urlUtils from 'common/utils/url';
|
|
|
|
import MainWindow from 'main/windows/mainWindow';
|
|
import ViewManager from 'main/views/viewManager';
|
|
import {
|
|
resetScreensharePermissionsMacOS,
|
|
openScreensharePermissionsSettingsMacOS,
|
|
} from 'main/utils';
|
|
import WebContentsEventManager from 'main/views/webContentEvents';
|
|
|
|
import {CallsWidgetWindow} from './callsWidgetWindow';
|
|
|
|
jest.mock('electron', () => ({
|
|
app: {
|
|
getAppPath: () => '/path/to/app',
|
|
},
|
|
BrowserWindow: jest.fn(),
|
|
ipcMain: {
|
|
on: jest.fn(),
|
|
off: jest.fn(),
|
|
handle: jest.fn(),
|
|
},
|
|
desktopCapturer: {
|
|
getSources: jest.fn(),
|
|
},
|
|
systemPreferences: {
|
|
getUserDefault: jest.fn(),
|
|
getMediaAccessStatus: jest.fn(() => 'granted'),
|
|
},
|
|
}));
|
|
|
|
jest.mock('../views/webContentEvents', () => ({
|
|
addWebContentsEventListeners: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('common/utils/url', () => ({
|
|
isCallsPopOutURL: jest.fn(),
|
|
getFormattedPathName: jest.fn(),
|
|
parseURL: jest.fn(),
|
|
}));
|
|
jest.mock('main/windows/mainWindow', () => ({
|
|
get: jest.fn(),
|
|
focus: jest.fn(),
|
|
}));
|
|
jest.mock('app/serverViewState', () => ({
|
|
switchServer: jest.fn(),
|
|
}));
|
|
jest.mock('main/views/viewManager', () => ({
|
|
getView: jest.fn(),
|
|
}));
|
|
jest.mock('../utils', () => ({
|
|
openScreensharePermissionsSettingsMacOS: jest.fn(),
|
|
resetScreensharePermissionsMacOS: jest.fn(),
|
|
getLocalPreload: jest.fn(),
|
|
composeUserAgent: jest.fn(),
|
|
}));
|
|
|
|
describe('main/windows/callsWidgetWindow', () => {
|
|
describe('onShow', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.win = {
|
|
focus: jest.fn(),
|
|
setVisibleOnAllWorkspaces: jest.fn(),
|
|
setAlwaysOnTop: jest.fn(),
|
|
getBounds: jest.fn(),
|
|
setBounds: jest.fn(),
|
|
setMenuBarVisibility: jest.fn(),
|
|
webContents: {
|
|
openDevTools: jest.fn(),
|
|
},
|
|
};
|
|
const mainWindow = {
|
|
getBounds: jest.fn(),
|
|
};
|
|
|
|
beforeEach(() => {
|
|
mainWindow.getBounds.mockReturnValue({
|
|
x: 0,
|
|
y: 0,
|
|
width: 1280,
|
|
height: 720,
|
|
});
|
|
callsWidgetWindow.win.getBounds.mockReturnValue({
|
|
x: 0,
|
|
y: 0,
|
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
|
});
|
|
MainWindow.get.mockReturnValue(mainWindow);
|
|
});
|
|
|
|
it('should call certain functions upon showing the window', () => {
|
|
callsWidgetWindow.onShow();
|
|
expect(callsWidgetWindow.win.setAlwaysOnTop).toHaveBeenCalled();
|
|
expect(callsWidgetWindow.win.setBounds).toHaveBeenCalledWith({
|
|
x: 12,
|
|
y: 618,
|
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
|
});
|
|
});
|
|
|
|
it('should open dev tools when environment variable is set', async () => {
|
|
const originalEnv = process.env;
|
|
Object.defineProperty(process, 'env', {
|
|
value: {
|
|
MM_DEBUG_CALLS_WIDGET: 'true',
|
|
},
|
|
});
|
|
callsWidgetWindow.onShow();
|
|
expect(callsWidgetWindow.win.webContents.openDevTools).toHaveBeenCalled();
|
|
Object.defineProperty(process, 'env', {
|
|
value: originalEnv,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('close', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.win = {
|
|
on: jest.fn(),
|
|
close: jest.fn(),
|
|
isDestroyed: jest.fn(),
|
|
};
|
|
|
|
beforeEach(() => {
|
|
let closedListener;
|
|
callsWidgetWindow.win.on.mockImplementation((event, listener) => {
|
|
closedListener = listener;
|
|
});
|
|
callsWidgetWindow.win.close.mockImplementation(() => closedListener());
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('should close window', async () => {
|
|
await callsWidgetWindow.close();
|
|
expect(callsWidgetWindow.win.close).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not close if already destroyed', async () => {
|
|
callsWidgetWindow.win.isDestroyed.mockReturnValue(true);
|
|
await callsWidgetWindow.close();
|
|
expect(callsWidgetWindow.win.close).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('handleResize', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.win = {
|
|
getBounds: jest.fn(),
|
|
webContents: {
|
|
id: 'windowID',
|
|
getZoomFactor: jest.fn(),
|
|
},
|
|
};
|
|
callsWidgetWindow.setBounds = jest.fn();
|
|
const bounds = {
|
|
x: 12,
|
|
y: 720,
|
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
|
};
|
|
|
|
beforeEach(() => {
|
|
callsWidgetWindow.win.getBounds.mockReturnValue(bounds);
|
|
callsWidgetWindow.win.webContents.getZoomFactor.mockReturnValue(1.0);
|
|
});
|
|
|
|
it('should resize correctly', () => {
|
|
callsWidgetWindow.handleResize({
|
|
sender: {id: 'windowID'},
|
|
}, 'widget', {
|
|
element: 'calls-widget',
|
|
width: 300,
|
|
height: 100,
|
|
});
|
|
expect(callsWidgetWindow.setBounds).toHaveBeenCalledWith({
|
|
x: 12,
|
|
y: 720 - (100 - MINIMUM_CALLS_WIDGET_HEIGHT),
|
|
width: 300,
|
|
height: 100,
|
|
});
|
|
});
|
|
|
|
it('should resize correctly at 2x zoom', () => {
|
|
callsWidgetWindow.win.webContents.getZoomFactor.mockReturnValue(2.0);
|
|
callsWidgetWindow.handleResize({
|
|
sender: {id: 'windowID'},
|
|
}, 'widget', {
|
|
element: 'calls-widget',
|
|
width: 300,
|
|
height: 100,
|
|
});
|
|
expect(callsWidgetWindow.setBounds).toHaveBeenCalledWith({
|
|
x: 12,
|
|
y: 720 - (200 - MINIMUM_CALLS_WIDGET_HEIGHT),
|
|
width: 600,
|
|
height: 200,
|
|
});
|
|
});
|
|
|
|
it('should resize correctly at 0.5x zoom', () => {
|
|
callsWidgetWindow.win.webContents.getZoomFactor.mockReturnValue(0.5);
|
|
callsWidgetWindow.handleResize({
|
|
sender: {id: 'windowID'},
|
|
}, 'widget', {
|
|
element: 'calls-widget',
|
|
width: 300,
|
|
height: 100,
|
|
});
|
|
expect(callsWidgetWindow.setBounds).toHaveBeenCalledWith({
|
|
x: 12,
|
|
y: 720 - (50 - MINIMUM_CALLS_WIDGET_HEIGHT),
|
|
width: 150,
|
|
height: 50,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getWidgetURL', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
|
|
beforeEach(() => {
|
|
urlUtils.parseURL.mockImplementation((url) => new URL(url));
|
|
urlUtils.getFormattedPathName.mockImplementation((pn) => {
|
|
return pn.endsWith('/') ? pn.toLowerCase() : `${pn.toLowerCase()}/`;
|
|
});
|
|
callsWidgetWindow.options = {
|
|
callID: 'test-call-id',
|
|
channelURL: '/team/channel_id',
|
|
title: 'call test title #/&',
|
|
};
|
|
callsWidgetWindow.mainView = {
|
|
view: {
|
|
server: {
|
|
url: new URL('http://localhost:8065'),
|
|
},
|
|
},
|
|
};
|
|
});
|
|
|
|
it('getWidgetURL', () => {
|
|
const expected = `http://localhost:8065/plugins/${CALLS_PLUGIN_ID}/standalone/widget.html?call_id=test-call-id&title=call+test+title+%23%2F%26`;
|
|
expect(callsWidgetWindow.getWidgetURL()).toBe(expected);
|
|
});
|
|
|
|
it('getWidgetURL - under subpath', () => {
|
|
callsWidgetWindow.mainView = {
|
|
view: {
|
|
server: {
|
|
url: new URL('http://localhost:8065/subpath'),
|
|
},
|
|
},
|
|
};
|
|
|
|
const expected = `http://localhost:8065/subpath/plugins/${CALLS_PLUGIN_ID}/standalone/widget.html?call_id=test-call-id&title=call+test+title+%23%2F%26`;
|
|
expect(callsWidgetWindow.getWidgetURL()).toBe(expected);
|
|
});
|
|
|
|
it('getWidgetURL - with rootID', () => {
|
|
callsWidgetWindow.options = {
|
|
...callsWidgetWindow.options,
|
|
rootID: 'call_thread_id',
|
|
};
|
|
const expected = `http://localhost:8065/plugins/${CALLS_PLUGIN_ID}/standalone/widget.html?call_id=test-call-id&title=call+test+title+%23%2F%26&root_id=call_thread_id`;
|
|
expect(callsWidgetWindow.getWidgetURL()).toBe(expected);
|
|
});
|
|
});
|
|
|
|
it('handleShareScreen', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.isAllowedEvent = jest.fn();
|
|
callsWidgetWindow.win = {
|
|
webContents: {
|
|
id: 'goodID',
|
|
send: jest.fn(),
|
|
},
|
|
};
|
|
const message = {
|
|
callID: 'test-call-id',
|
|
};
|
|
|
|
callsWidgetWindow.isAllowedEvent.mockReturnValue(false);
|
|
callsWidgetWindow.handleShareScreen({
|
|
sender: {id: 'badID'},
|
|
}, message);
|
|
expect(callsWidgetWindow.win.webContents.send).not.toHaveBeenCalled();
|
|
|
|
callsWidgetWindow.isAllowedEvent.mockReturnValue(true);
|
|
callsWidgetWindow.handleShareScreen({
|
|
sender: {id: 'goodID'},
|
|
}, 'widget', message);
|
|
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith(CALLS_WIDGET_SHARE_SCREEN, message);
|
|
});
|
|
|
|
it('handleJoinedCall', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.isAllowedEvent = jest.fn();
|
|
callsWidgetWindow.mainView = {
|
|
webContentsId: 'goodID',
|
|
sendToRenderer: jest.fn(),
|
|
};
|
|
const message = {
|
|
callID: 'test-call-id',
|
|
};
|
|
|
|
callsWidgetWindow.isAllowedEvent.mockReturnValue(false);
|
|
callsWidgetWindow.handleJoinedCall({
|
|
sender: {id: 'badID'},
|
|
}, 'widget', message);
|
|
expect(callsWidgetWindow.mainView.sendToRenderer).not.toHaveBeenCalled();
|
|
|
|
callsWidgetWindow.isAllowedEvent.mockReturnValue(true);
|
|
callsWidgetWindow.handleJoinedCall({
|
|
sender: {id: 'goodID'},
|
|
}, 'widget', message);
|
|
expect(callsWidgetWindow.mainView.sendToRenderer).toHaveBeenCalledWith(CALLS_JOINED_CALL, message);
|
|
});
|
|
|
|
describe('onPopOutOpen', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
|
|
beforeEach(() => {
|
|
callsWidgetWindow.options = {callID: 'id'};
|
|
callsWidgetWindow.mainView = {
|
|
view: {
|
|
server: {
|
|
url: new URL('http://localhost:8065'),
|
|
},
|
|
},
|
|
};
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
delete callsWidgetWindow.options;
|
|
delete callsWidgetWindow.mainView;
|
|
});
|
|
|
|
it('should deny opening if there is no call attached', () => {
|
|
delete callsWidgetWindow.options;
|
|
delete callsWidgetWindow.mainView;
|
|
expect(callsWidgetWindow.onPopOutOpen({url: 'http://localhost:8065/popouturl'})).toHaveProperty('action', 'deny');
|
|
});
|
|
|
|
it('should pop out and make sure menu bar is disabled', () => {
|
|
urlUtils.isCallsPopOutURL.mockReturnValue(true);
|
|
expect(callsWidgetWindow.onPopOutOpen({url: 'http://localhost:8065/popouturl'})).toHaveProperty('action', 'allow');
|
|
expect(callsWidgetWindow.onPopOutOpen({url: 'http://localhost:8065/popouturl'}).overrideBrowserWindowOptions).toHaveProperty('autoHideMenuBar', true);
|
|
});
|
|
|
|
it('should not pop out when the URL does not match the calls popout URL', () => {
|
|
urlUtils.isCallsPopOutURL.mockReturnValue(false);
|
|
expect(callsWidgetWindow.onPopOutOpen({url: 'http://localhost:8065/notpopouturl'})).toHaveProperty('action', 'deny');
|
|
});
|
|
});
|
|
|
|
describe('handlePopOutFocus', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.popOut = {
|
|
isMinimized: jest.fn(),
|
|
restore: jest.fn(),
|
|
focus: jest.fn(),
|
|
};
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('should focus only if not minimized', () => {
|
|
callsWidgetWindow.popOut.isMinimized.mockReturnValue(false);
|
|
callsWidgetWindow.handlePopOutFocus();
|
|
expect(callsWidgetWindow.popOut.restore).not.toBeCalled();
|
|
expect(callsWidgetWindow.popOut.focus).toBeCalled();
|
|
});
|
|
|
|
it('should focus only if not minimized', () => {
|
|
callsWidgetWindow.popOut.isMinimized.mockReturnValue(true);
|
|
callsWidgetWindow.handlePopOutFocus();
|
|
expect(callsWidgetWindow.popOut.restore).toBeCalled();
|
|
expect(callsWidgetWindow.popOut.focus).toBeCalled();
|
|
});
|
|
});
|
|
|
|
it('onPopOutCreate - should attach correct listeners and should prevent redirects', () => {
|
|
let redirectListener;
|
|
let closedListener;
|
|
let frameFinishedLoadListener;
|
|
const popOut = {
|
|
on: (event, listener) => {
|
|
closedListener = listener;
|
|
},
|
|
webContents: {
|
|
on: (event, listener) => {
|
|
redirectListener = listener;
|
|
},
|
|
once: (event, listener) => {
|
|
frameFinishedLoadListener = listener;
|
|
},
|
|
id: 'webContentsId',
|
|
getURL: () => ('http://myurl.com'),
|
|
},
|
|
loadURL: jest.fn(),
|
|
};
|
|
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.onPopOutCreate(popOut);
|
|
expect(callsWidgetWindow.popOut).toBe(popOut);
|
|
expect(WebContentsEventManager.addWebContentsEventListeners).toHaveBeenCalledWith(popOut.webContents);
|
|
expect(redirectListener).toBeDefined();
|
|
expect(frameFinishedLoadListener).toBeDefined();
|
|
|
|
const event = {preventDefault: jest.fn()};
|
|
redirectListener(event);
|
|
expect(event.preventDefault).toHaveBeenCalled();
|
|
|
|
frameFinishedLoadListener();
|
|
expect(callsWidgetWindow.popOut.loadURL).toHaveBeenCalledTimes(1);
|
|
|
|
closedListener();
|
|
expect(callsWidgetWindow.popOut).not.toBeDefined();
|
|
});
|
|
|
|
it('getViewURL', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.mainView = {
|
|
view: {
|
|
server: {
|
|
url: new URL('http://localhost:8065/'),
|
|
},
|
|
},
|
|
};
|
|
expect(callsWidgetWindow.getViewURL().toString()).toBe('http://localhost:8065/');
|
|
});
|
|
|
|
describe('isAllowedEvent', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.mainView = {
|
|
webContentsId: 'mainViewID',
|
|
};
|
|
callsWidgetWindow.win = {
|
|
webContents: {
|
|
id: 'windowID',
|
|
},
|
|
};
|
|
|
|
it('should not allow on unknown sender id', () => {
|
|
expect(callsWidgetWindow.isAllowedEvent({
|
|
sender: {
|
|
id: 'senderID',
|
|
},
|
|
})).toEqual(false);
|
|
});
|
|
|
|
it('should allow on attached browser view', () => {
|
|
expect(callsWidgetWindow.isAllowedEvent({
|
|
sender: {
|
|
id: 'mainViewID',
|
|
},
|
|
})).toEqual(true);
|
|
});
|
|
|
|
it('should allow on widget window', () => {
|
|
expect(callsWidgetWindow.isAllowedEvent({
|
|
sender: {
|
|
id: 'windowID',
|
|
},
|
|
})).toEqual(true);
|
|
});
|
|
});
|
|
|
|
it('onNavigate', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.getWidgetURL = () => 'http://localhost:8065';
|
|
const ev = {preventDefault: jest.fn()};
|
|
|
|
callsWidgetWindow.onNavigate(ev, 'http://localhost:8065');
|
|
expect(ev.preventDefault).not.toHaveBeenCalled();
|
|
|
|
callsWidgetWindow.onNavigate(ev, 'http://localhost:8065/invalid/url');
|
|
expect(ev.preventDefault).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
describe('handleCreateCallsWidgetWindow', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.close = jest.fn();
|
|
callsWidgetWindow.getWidgetURL = jest.fn();
|
|
const view = {
|
|
name: 'server-1_view-messaging',
|
|
serverInfo: {
|
|
server: {
|
|
url: new URL('http://server-1.com'),
|
|
},
|
|
},
|
|
};
|
|
const browserWindow = {
|
|
on: jest.fn(),
|
|
once: jest.fn(),
|
|
loadURL: jest.fn().mockReturnValue(Promise.resolve()),
|
|
webContents: {
|
|
setWindowOpenHandler: jest.fn(),
|
|
on: jest.fn(),
|
|
id: 1,
|
|
openDevTools: jest.fn(),
|
|
},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
BrowserWindow.mockReturnValue(browserWindow);
|
|
callsWidgetWindow.close.mockReturnValue(Promise.resolve());
|
|
ViewManager.getView.mockReturnValue(view);
|
|
});
|
|
|
|
afterEach(() => {
|
|
delete callsWidgetWindow.win;
|
|
delete callsWidgetWindow.mainView;
|
|
delete callsWidgetWindow.options;
|
|
|
|
jest.resetAllMocks();
|
|
});
|
|
|
|
it('should create calls widget window', async () => {
|
|
expect(callsWidgetWindow.win).toBeUndefined();
|
|
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test'});
|
|
expect(callsWidgetWindow.win).toBeDefined();
|
|
});
|
|
|
|
it('should create with correct initial configuration', async () => {
|
|
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test'});
|
|
expect(BrowserWindow).toHaveBeenCalledWith(expect.objectContaining({
|
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
|
fullscreen: false,
|
|
resizable: false,
|
|
frame: false,
|
|
transparent: true,
|
|
show: false,
|
|
alwaysOnTop: true,
|
|
backgroundColor: '#00ffffff',
|
|
}));
|
|
});
|
|
|
|
it('should catch error when failing to load the URL', async () => {
|
|
const error = new Error('failed to load URL');
|
|
const promise = Promise.reject(error);
|
|
BrowserWindow.mockReturnValue({
|
|
...browserWindow,
|
|
loadURL: jest.fn().mockReturnValue(promise),
|
|
});
|
|
|
|
await expect(promise).rejects.toThrow(error);
|
|
});
|
|
|
|
it('should not create a new window if call is the same', async () => {
|
|
const window = {webContents: {id: 2}};
|
|
callsWidgetWindow.win = window;
|
|
callsWidgetWindow.options = {callID: 'test'};
|
|
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test'});
|
|
expect(callsWidgetWindow.win).toEqual(window);
|
|
});
|
|
|
|
it('should create a new window if switching calls', async () => {
|
|
const window = {webContents: {id: 2}};
|
|
callsWidgetWindow.win = window;
|
|
callsWidgetWindow.getCallID = jest.fn(() => 'test');
|
|
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test2'});
|
|
expect(callsWidgetWindow.win).not.toEqual(window);
|
|
});
|
|
});
|
|
|
|
describe('handleGetDesktopSources', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.win = {
|
|
webContents: {
|
|
send: jest.fn(),
|
|
},
|
|
};
|
|
const servers = [
|
|
{
|
|
name: 'server-1',
|
|
order: 1,
|
|
views: [
|
|
{
|
|
name: 'view-1',
|
|
order: 0,
|
|
isOpen: false,
|
|
},
|
|
{
|
|
name: 'view-2',
|
|
order: 2,
|
|
isOpen: true,
|
|
},
|
|
],
|
|
}, {
|
|
name: 'server-2',
|
|
order: 0,
|
|
views: [
|
|
{
|
|
name: 'view-1',
|
|
order: 0,
|
|
isOpen: false,
|
|
},
|
|
{
|
|
name: 'view-2',
|
|
order: 2,
|
|
isOpen: true,
|
|
},
|
|
],
|
|
lastActiveView: 2,
|
|
},
|
|
];
|
|
const map = servers.reduce((arr, item) => {
|
|
item.views.forEach((view) => {
|
|
arr.push([`${item.name}_${view.name}`, {
|
|
sendToRenderer: jest.fn(),
|
|
}]);
|
|
});
|
|
return arr;
|
|
}, []);
|
|
const views = new Map(map);
|
|
|
|
beforeEach(() => {
|
|
ViewManager.getView.mockImplementation((viewId) => views.get(viewId));
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
callsWidgetWindow.missingScreensharePermissions = undefined;
|
|
});
|
|
|
|
it('should send sources back', async () => {
|
|
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([
|
|
{
|
|
id: 'screen0',
|
|
thumbnail: {
|
|
toDataURL: jest.fn(),
|
|
},
|
|
},
|
|
{
|
|
id: 'window0',
|
|
thumbnail: {
|
|
toDataURL: jest.fn(),
|
|
},
|
|
},
|
|
]);
|
|
|
|
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
|
|
|
|
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('desktop-sources-result', [
|
|
{
|
|
id: 'screen0',
|
|
},
|
|
{
|
|
id: 'window0',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should send error with no sources', async () => {
|
|
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([]);
|
|
await callsWidgetWindow.handleGetDesktopSources('server-2_view-1', null);
|
|
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
|
|
err: 'screen-permissions',
|
|
});
|
|
expect(views.get('server-2_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
|
err: 'screen-permissions',
|
|
});
|
|
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should send error with no permissions', async () => {
|
|
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([
|
|
{
|
|
id: 'screen0',
|
|
thumbnail: {
|
|
toDataURL: jest.fn(),
|
|
},
|
|
},
|
|
]);
|
|
jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied');
|
|
|
|
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
|
|
|
|
expect(systemPreferences.getMediaAccessStatus).toHaveBeenCalledWith('screen');
|
|
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
|
|
err: 'screen-permissions',
|
|
});
|
|
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
|
err: 'screen-permissions',
|
|
});
|
|
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledTimes(1);
|
|
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('macos - no permissions', async () => {
|
|
const originalPlatform = process.platform;
|
|
Object.defineProperty(process, 'platform', {
|
|
value: 'darwin',
|
|
});
|
|
|
|
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([
|
|
{
|
|
id: 'screen0',
|
|
thumbnail: {
|
|
toDataURL: jest.fn(),
|
|
},
|
|
},
|
|
]);
|
|
jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied');
|
|
|
|
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
|
|
|
|
expect(callsWidgetWindow.missingScreensharePermissions).toBe(true);
|
|
expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(1);
|
|
expect(openScreensharePermissionsSettingsMacOS).toHaveBeenCalledTimes(0);
|
|
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
|
|
err: 'screen-permissions',
|
|
});
|
|
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
|
err: 'screen-permissions',
|
|
});
|
|
|
|
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
|
|
|
|
expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(2);
|
|
expect(openScreensharePermissionsSettingsMacOS).toHaveBeenCalledTimes(1);
|
|
|
|
Object.defineProperty(process, 'platform', {
|
|
value: originalPlatform,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('handleDesktopSourcesModalRequest', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.mainView = {
|
|
view: {
|
|
server: {
|
|
id: 'server-1',
|
|
},
|
|
},
|
|
sendToRenderer: jest.fn(),
|
|
};
|
|
const servers = [
|
|
{
|
|
name: 'server-1',
|
|
order: 1,
|
|
views: [
|
|
{
|
|
name: 'view-1',
|
|
order: 0,
|
|
isOpen: false,
|
|
},
|
|
{
|
|
name: 'view-2',
|
|
order: 2,
|
|
isOpen: true,
|
|
},
|
|
],
|
|
}, {
|
|
name: 'server-2',
|
|
order: 0,
|
|
views: [
|
|
{
|
|
name: 'view-1',
|
|
order: 0,
|
|
isOpen: false,
|
|
},
|
|
{
|
|
name: 'view-2',
|
|
order: 2,
|
|
isOpen: true,
|
|
},
|
|
],
|
|
lastActiveView: 2,
|
|
},
|
|
];
|
|
const map = servers.reduce((arr, item) => {
|
|
item.views.forEach((view) => {
|
|
arr.push([`${item.name}_${view.name}`, {}]);
|
|
});
|
|
return arr;
|
|
}, []);
|
|
const views = new Map(map);
|
|
|
|
beforeEach(() => {
|
|
ViewManager.getView.mockImplementation((viewId) => views.get(viewId));
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
});
|
|
|
|
it('should switch server', () => {
|
|
callsWidgetWindow.handleDesktopSourcesModalRequest();
|
|
expect(ServerViewState.switchServer).toHaveBeenCalledWith('server-1');
|
|
});
|
|
});
|
|
|
|
describe('handleCallsWidgetChannelLinkClick', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.mainView = {
|
|
view: {
|
|
server: {
|
|
id: 'server-2',
|
|
},
|
|
},
|
|
sendToRenderer: jest.fn(),
|
|
};
|
|
callsWidgetWindow.getChannelURL = jest.fn();
|
|
const servers = [
|
|
{
|
|
name: 'server-1',
|
|
order: 1,
|
|
views: [
|
|
{
|
|
name: 'view-1',
|
|
order: 0,
|
|
isOpen: false,
|
|
},
|
|
{
|
|
name: 'view-2',
|
|
order: 2,
|
|
isOpen: true,
|
|
},
|
|
],
|
|
}, {
|
|
name: 'server-2',
|
|
order: 0,
|
|
views: [
|
|
{
|
|
name: 'view-1',
|
|
order: 0,
|
|
isOpen: false,
|
|
},
|
|
{
|
|
name: 'view-2',
|
|
order: 2,
|
|
isOpen: true,
|
|
},
|
|
],
|
|
lastActiveView: 2,
|
|
},
|
|
];
|
|
const map = servers.reduce((arr, item) => {
|
|
item.views.forEach((view) => {
|
|
arr.push([`${item.name}_${view.name}`, {}]);
|
|
});
|
|
return arr;
|
|
}, []);
|
|
const views = new Map(map);
|
|
|
|
beforeEach(() => {
|
|
ViewManager.getView.mockImplementation((viewId) => views.get(viewId));
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
});
|
|
|
|
it('should switch server', () => {
|
|
callsWidgetWindow.handleCallsWidgetChannelLinkClick();
|
|
expect(ServerViewState.switchServer).toHaveBeenCalledWith('server-2');
|
|
});
|
|
});
|
|
|
|
describe('handleCallsError', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.mainView = {
|
|
view: {
|
|
server: {
|
|
id: 'server-2',
|
|
},
|
|
},
|
|
sendToRenderer: jest.fn(),
|
|
};
|
|
const focus = jest.fn();
|
|
|
|
beforeEach(() => {
|
|
MainWindow.get.mockReturnValue({focus});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
});
|
|
|
|
it('should focus view and propagate error to main view', () => {
|
|
callsWidgetWindow.handleCallsError('', {err: 'client-error'});
|
|
expect(ServerViewState.switchServer).toHaveBeenCalledWith('server-2');
|
|
expect(focus).toHaveBeenCalled();
|
|
expect(callsWidgetWindow.mainView.sendToRenderer).toHaveBeenCalledWith('calls-error', {err: 'client-error'});
|
|
});
|
|
});
|
|
|
|
describe('handleCallsLinkClick', () => {
|
|
const view = {
|
|
view: {
|
|
server: {
|
|
id: 'server-1',
|
|
},
|
|
},
|
|
sendToRenderer: jest.fn(),
|
|
};
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.mainView = view;
|
|
|
|
beforeEach(() => {
|
|
ViewManager.getView.mockReturnValue(view);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
});
|
|
|
|
it('should pass through the click link to browser history push', () => {
|
|
callsWidgetWindow.handleCallsLinkClick('', {link: '/other/subpath'});
|
|
expect(ServerViewState.switchServer).toHaveBeenCalledWith('server-1');
|
|
expect(view.sendToRenderer).toBeCalledWith('browser-history-push', '/other/subpath');
|
|
});
|
|
});
|
|
|
|
describe('genCallsEventHandler', () => {
|
|
const handler = jest.fn();
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('should not call handler if source is not allowed', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.isAllowedEvent = () => false;
|
|
callsWidgetWindow.genCallsEventHandler(handler)();
|
|
expect(handler).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should call handler if source is allowed', () => {
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.isAllowedEvent = () => true;
|
|
callsWidgetWindow.genCallsEventHandler(handler)();
|
|
expect(handler).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('handleCallsJoinRequest', () => {
|
|
const view = {
|
|
view: {
|
|
server: {
|
|
id: 'server-1',
|
|
},
|
|
},
|
|
sendToRenderer: jest.fn(),
|
|
};
|
|
const callsWidgetWindow = new CallsWidgetWindow();
|
|
callsWidgetWindow.mainView = view;
|
|
|
|
const focus = jest.fn();
|
|
|
|
beforeEach(() => {
|
|
MainWindow.get.mockReturnValue({focus});
|
|
ViewManager.getView.mockReturnValue(view);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
});
|
|
|
|
it('should pass through the join call callID to the webapp', () => {
|
|
callsWidgetWindow.handleCallsJoinRequest('', {callID: 'thecallchannelid'});
|
|
expect(ServerViewState.switchServer).toHaveBeenCalledWith('server-1');
|
|
expect(focus).toHaveBeenCalled();
|
|
expect(view.sendToRenderer).toBeCalledWith(CALLS_JOIN_REQUEST, {callID: 'thecallchannelid'});
|
|
});
|
|
});
|
|
});
|