[MM-46993] Implement CallsWidgetWindow (#2265)

* Initial implementation of CallsWidgetWindow

* Refactor + implement widget resizing logic

* Add tests

* Enable screen sharing

* Channel link

* Add more tests

* Move constants to common file

* Extract boundsDiff into util

* Set background color on initialization

* Fix channel link

* Support installations under a subpath

* Fix path, caching issues and pass title

* [MM-48142] Fix remaining call state issues in main window (#2349)

* Update widget URL to new format

* Slightly bump widget dimensions to account for border

* Fix call state on parent window
This commit is contained in:
Claudio Costa
2022-11-07 09:40:13 +01:00
committed by GitHub
parent c319038704
commit 47edeea601
12 changed files with 953 additions and 6 deletions

View File

@@ -0,0 +1,286 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {EventEmitter} from 'events';
import {BrowserWindow} from 'electron';
import {CALLS_WIDGET_SHARE_SCREEN, CALLS_JOINED_CALL} from 'common/communication';
import {
MINIMUM_CALLS_WIDGET_WIDTH,
MINIMUM_CALLS_WIDGET_HEIGHT,
CALLS_PLUGIN_ID,
} from 'common/utils/constants';
import CallsWidgetWindow from './callsWidgetWindow';
jest.mock('electron', () => ({
BrowserWindow: jest.fn(),
ipcMain: {
on: jest.fn(),
off: jest.fn(),
},
}));
describe('main/windows/callsWidgetWindow', () => {
describe('create CallsWidgetWindow', () => {
const widgetConfig = {
callID: 'test-call-id',
siteURL: 'http://localhost:8065',
title: '',
serverName: 'test-server-name',
channelURL: '/team/channel_id',
};
const mainWindow = {
getBounds: jest.fn(),
};
const mainView = {
view: {
webContents: {
send: jest.fn(),
},
},
};
const baseWindow = new EventEmitter();
baseWindow.loadURL = jest.fn();
baseWindow.focus = jest.fn();
baseWindow.setVisibleOnAllWorkspaces = jest.fn();
baseWindow.setAlwaysOnTop = jest.fn();
baseWindow.setBackgroundColor = jest.fn();
baseWindow.setMenuBarVisibility = jest.fn();
baseWindow.setBounds = jest.fn();
beforeEach(() => {
mainWindow.getBounds.mockImplementation(() => {
return {
x: 0,
y: 0,
width: 1280,
height: 720,
};
});
baseWindow.getBounds = jest.fn(() => {
return {
x: 0,
y: 0,
width: MINIMUM_CALLS_WIDGET_WIDTH,
height: MINIMUM_CALLS_WIDGET_HEIGHT,
};
});
baseWindow.loadURL.mockImplementation(() => ({
catch: jest.fn(),
}));
BrowserWindow.mockImplementation(() => baseWindow);
});
afterEach(() => {
jest.resetAllMocks();
});
it('verify initial configuration', () => {
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
expect(widgetWindow).toBeDefined();
expect(BrowserWindow).toHaveBeenCalledWith(expect.objectContaining({
width: MINIMUM_CALLS_WIDGET_WIDTH,
height: MINIMUM_CALLS_WIDGET_HEIGHT,
minWidth: MINIMUM_CALLS_WIDGET_WIDTH,
minHeight: MINIMUM_CALLS_WIDGET_HEIGHT,
fullscreen: false,
resizable: false,
frame: false,
transparent: true,
show: false,
alwaysOnTop: true,
backgroundColor: '#00ffffff',
}));
});
it('showing window', () => {
baseWindow.show = jest.fn(() => {
baseWindow.emit('show');
});
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
widgetWindow.win.emit('ready-to-show');
expect(widgetWindow.win.show).toHaveBeenCalled();
expect(widgetWindow.win.setAlwaysOnTop).toHaveBeenCalled();
expect(widgetWindow.win.setBounds).toHaveBeenCalledWith({
x: 12,
y: 618,
width: MINIMUM_CALLS_WIDGET_WIDTH,
height: MINIMUM_CALLS_WIDGET_HEIGHT,
});
});
it('loadURL error', () => {
baseWindow.show = jest.fn(() => {
baseWindow.emit('show');
});
baseWindow.loadURL = jest.fn(() => {
return Promise.reject(new Error('failed to load URL'));
});
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
expect(widgetWindow.win.loadURL).toHaveBeenCalled();
});
it('open devTools', () => {
process.env.MM_DEBUG_CALLS_WIDGET = 'true';
baseWindow.show = jest.fn(() => {
baseWindow.emit('show');
});
baseWindow.webContents = {
openDevTools: jest.fn(),
};
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
widgetWindow.win.emit('ready-to-show');
expect(widgetWindow.win.webContents.openDevTools).toHaveBeenCalled();
});
it('closing window', () => {
baseWindow.close = jest.fn(() => {
baseWindow.emit('closed');
});
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
widgetWindow.close();
expect(widgetWindow.win.close).toHaveBeenCalled();
});
it('resize', () => {
baseWindow.show = jest.fn(() => {
baseWindow.emit('show');
});
let winBounds = {
x: 0,
y: 0,
width: MINIMUM_CALLS_WIDGET_WIDTH,
height: MINIMUM_CALLS_WIDGET_HEIGHT,
};
baseWindow.getBounds = jest.fn(() => {
return winBounds;
});
baseWindow.setBounds = jest.fn((bounds) => {
winBounds = bounds;
});
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
widgetWindow.win.emit('ready-to-show');
expect(baseWindow.setBounds).toHaveBeenCalledTimes(2);
widgetWindow.onResize(null, {
element: 'calls-widget-menu',
height: 100,
});
expect(baseWindow.setBounds).toHaveBeenCalledWith({
x: 12,
y: 518,
width: MINIMUM_CALLS_WIDGET_WIDTH,
height: MINIMUM_CALLS_WIDGET_HEIGHT + 100,
});
widgetWindow.onResize(null, {
element: 'calls-widget-audio-menu',
width: 100,
});
expect(baseWindow.setBounds).toHaveBeenCalledWith({
x: 12,
y: 518,
width: MINIMUM_CALLS_WIDGET_WIDTH + 100,
height: MINIMUM_CALLS_WIDGET_HEIGHT + 100,
});
widgetWindow.onResize(null, {
element: 'calls-widget-audio-menu',
width: 0,
});
expect(baseWindow.setBounds).toHaveBeenCalledWith({
x: 12,
y: 518,
width: MINIMUM_CALLS_WIDGET_WIDTH,
height: MINIMUM_CALLS_WIDGET_HEIGHT + 100,
});
widgetWindow.onResize(null, {
element: 'calls-widget-menu',
height: 0,
});
expect(baseWindow.setBounds).toHaveBeenCalledWith({
x: 12,
y: 618,
width: MINIMUM_CALLS_WIDGET_WIDTH,
height: MINIMUM_CALLS_WIDGET_HEIGHT,
});
});
it('getServerName', () => {
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
expect(widgetWindow.getServerName()).toBe('test-server-name');
});
it('getChannelURL', () => {
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
expect(widgetWindow.getChannelURL()).toBe('/team/channel_id');
});
it('getChannelURL', () => {
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
expect(widgetWindow.getCallID()).toBe('test-call-id');
});
it('getWidgetURL', () => {
const config = {
...widgetConfig,
siteURL: 'http://localhost:8065/subpath',
title: 'call test title #/&',
};
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, config);
const expected = `${config.siteURL}/plugins/${CALLS_PLUGIN_ID}/standalone/widget.html?call_id=${config.callID}&title=call+test+title+%23%2F%26`;
expect(widgetWindow.getWidgetURL()).toBe(expected);
});
it('onShareScreen', () => {
baseWindow.webContents = {
send: jest.fn(),
};
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
const message = {
sourceID: 'test-source-id',
withAudio: false,
};
widgetWindow.onShareScreen(null, '', message);
expect(widgetWindow.win.webContents.send).toHaveBeenCalledWith(CALLS_WIDGET_SHARE_SCREEN, message);
});
it('onJoinedCall', () => {
baseWindow.webContents = {
send: jest.fn(),
};
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
const message = {
callID: 'test-call-id',
};
widgetWindow.onJoinedCall(null, message);
expect(widgetWindow.mainView.view.webContents.send).toHaveBeenCalledWith(CALLS_JOINED_CALL, message);
});
});
});