diff --git a/i18n/en.json b/i18n/en.json index 9a9fec3f..f071da16 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -82,7 +82,9 @@ "main.menus.app.view.actualSize": "Actual Size", "main.menus.app.view.clearCacheAndReload": "Clear Cache and Reload", "main.menus.app.view.devToolsAppWrapper": "Developer Tools for Application Wrapper", + "main.menus.app.view.devToolsCurrentCallWidget": "Developer Tools for Call Widget", "main.menus.app.view.devToolsCurrentServer": "Developer Tools for Current Server", + "main.menus.app.view.devToolsSubMenu": "Developer Tools", "main.menus.app.view.downloads": "Downloads", "main.menus.app.view.find": "Find..", "main.menus.app.view.fullscreen": "Toggle Full Screen", diff --git a/src/main/menus/app.test.js b/src/main/menus/app.test.js index 5b3a931b..7f97c0c6 100644 --- a/src/main/menus/app.test.js +++ b/src/main/menus/app.test.js @@ -10,8 +10,27 @@ import ServerManager from 'common/servers/serverManager'; import ServerViewState from 'app/serverViewState'; +import CallsWidgetWindow from 'main/windows/callsWidgetWindow'; + import {createTemplate} from './app'; +jest.mock('fs-extra', () => ({ + readFileSync: jest.fn(), + writeFileSync: jest.fn(), + existsSync: jest.fn(), + copySync: jest.fn(), +})); + +jest.mock('electron-extension-installer', () => { + return () => ({ + REACT_DEVELOPER_TOOLS: 'react-developer-tools', + }); +}); + +jest.mock('electron-context-menu', () => { + return () => jest.fn(); +}); + jest.mock('electron', () => { class NotificationMock { static isSupported = jest.fn(); @@ -38,6 +57,9 @@ jest.mock('electron', () => { removeListener: jest.fn(), }, Notification: NotificationMock, + nativeImage: { + createFromPath: jest.fn(), + }, }; }); jest.mock('fs', () => ({ @@ -67,11 +89,19 @@ jest.mock('main/downloadsManager', () => ({ jest.mock('main/views/viewManager', () => ({})); jest.mock('main/windows/mainWindow', () => ({ sendToRenderer: jest.fn(), + on: jest.fn(), })); jest.mock('main/windows/settingsWindow', () => ({})); jest.mock('common/views/View', () => ({ getViewDisplayName: (name) => name, })); +jest.mock('main/AutoLauncher', () => ({ + enable: jest.fn(), + disable: jest.fn(), +})); +jest.mock('main/windows/callsWidgetWindow', () => ({ + isOpen: jest.fn(), +})); describe('main/menus/app', () => { const config = { @@ -305,4 +335,45 @@ describe('main/menus/app', () => { const helpSubmenu = menu.find((subMenu) => subMenu.id === 'help')?.submenu; expect(helpSubmenu).toContainObject({id: 'diagnostics'}); }); + + it('should show developer tools submenu', () => { + const menu = createTemplate(config); + + const appMenu = menu.find((item) => item.label === 'main.menus.app.view'); + + expect(appMenu).not.toBe(undefined); + + const devToolsSubMenu = appMenu.submenu.find((item) => item.label === 'main.menus.app.view.devToolsSubMenu'); + + expect(devToolsSubMenu.submenu.length).toBe(2); + expect(devToolsSubMenu.submenu[0].label).toBe('main.menus.app.view.devToolsAppWrapper'); + expect(devToolsSubMenu.submenu[1].label).toBe('main.menus.app.view.devToolsCurrentServer'); + }); + + it('should not show menu item if widget window is not open', () => { + const menu = createTemplate(config); + + const appMenu = menu.find((item) => item.label === 'main.menus.app.view'); + expect(appMenu).not.toBe(undefined); + + const devToolsSubMenu = appMenu.submenu.find((item) => item.label === 'main.menus.app.view.devToolsSubMenu'); + expect(devToolsSubMenu).not.toBe(undefined); + + const menuItem = devToolsSubMenu.submenu.find((item) => item.label === 'main.menus.app.view.devToolsCurrentCallWidget'); + expect(menuItem).toBe(undefined); + }); + + it('should show menu item if widget window is open', () => { + CallsWidgetWindow.isOpen = jest.fn(() => true); + const menu = createTemplate(config); + + const appMenu = menu.find((item) => item.label === 'main.menus.app.view'); + expect(appMenu).not.toBe(undefined); + + const devToolsSubMenu = appMenu.submenu.find((item) => item.label === 'main.menus.app.view.devToolsSubMenu'); + expect(devToolsSubMenu).not.toBe(undefined); + + const menuItem = devToolsSubMenu.submenu.find((item) => item.label === 'main.menus.app.view.devToolsCurrentCallWidget'); + expect(menuItem).not.toBe(undefined); + }); }); diff --git a/src/main/menus/app.ts b/src/main/menus/app.ts index f4ce963b..24cdb710 100644 --- a/src/main/menus/app.ts +++ b/src/main/menus/app.ts @@ -20,6 +20,7 @@ import downloadsManager from 'main/downloadsManager'; import Diagnostics from 'main/diagnostics'; import ViewManager from 'main/views/viewManager'; import SettingsWindow from 'main/windows/settingsWindow'; +import CallsWidgetWindow from 'main/windows/callsWidgetWindow'; export function createTemplate(config: Config, updateManager: UpdateManager) { const separatorItem: MenuItemConstructorOptions = { @@ -125,6 +126,43 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { }], }); + const devToolsSubMenu = [ + { + label: localizeMessage('main.menus.app.view.devToolsAppWrapper', 'Developer Tools for Application Wrapper'), + accelerator: (() => { + if (process.platform === 'darwin') { + return 'Alt+Command+I'; + } + return 'Ctrl+Shift+I'; + })(), + click(item: Electron.MenuItem, focusedWindow?: WebContents) { + if (focusedWindow) { + // toggledevtools opens it in the last known position, so sometimes it goes below the browserview + if (focusedWindow.isDevToolsOpened()) { + focusedWindow.closeDevTools(); + } else { + focusedWindow.openDevTools({mode: 'detach'}); + } + } + }, + }, + { + label: localizeMessage('main.menus.app.view.devToolsCurrentServer', 'Developer Tools for Current Server'), + click() { + ViewManager.getCurrentView()?.openDevTools(); + }, + }, + ]; + + if (CallsWidgetWindow.isOpen()) { + devToolsSubMenu.push({ + label: localizeMessage('main.menus.app.view.devToolsCurrentCallWidget', 'Developer Tools for Call Widget'), + click() { + CallsWidgetWindow.openDevTools(); + }, + }); + } + const viewSubMenu = [{ label: localizeMessage('main.menus.app.view.find', 'Find..'), accelerator: 'CmdOrCtrl+F', @@ -176,28 +214,8 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { return downloadsManager.openDownloadsDropdown(); }, }, separatorItem, { - label: localizeMessage('main.menus.app.view.devToolsAppWrapper', 'Developer Tools for Application Wrapper'), - accelerator: (() => { - if (process.platform === 'darwin') { - return 'Alt+Command+I'; - } - return 'Ctrl+Shift+I'; - })(), - click(item: Electron.MenuItem, focusedWindow?: WebContents) { - if (focusedWindow) { - // toggledevtools opens it in the last known position, so sometimes it goes below the browserview - if (focusedWindow.isDevToolsOpened()) { - focusedWindow.closeDevTools(); - } else { - focusedWindow.openDevTools({mode: 'detach'}); - } - } - }, - }, { - label: localizeMessage('main.menus.app.view.devToolsCurrentServer', 'Developer Tools for Current Server'), - click() { - ViewManager.getCurrentView()?.openDevTools(); - }, + label: localizeMessage('main.menus.app.view.devToolsSubMenu', 'Developer Tools'), + submenu: devToolsSubMenu, }]; if (process.platform !== 'darwin' && process.platform !== 'win32') { diff --git a/src/main/windows/callsWidgetWindow.test.js b/src/main/windows/callsWidgetWindow.test.js index dcb9f54a..5739f7e2 100644 --- a/src/main/windows/callsWidgetWindow.test.js +++ b/src/main/windows/callsWidgetWindow.test.js @@ -7,7 +7,7 @@ import {BrowserWindow, desktopCapturer, systemPreferences, ipcMain} from 'electr import ServerViewState from 'app/serverViewState'; -import {CALLS_WIDGET_SHARE_SCREEN} from 'common/communication'; +import {CALLS_WIDGET_SHARE_SCREEN, UPDATE_SHORTCUT_MENU} from 'common/communication'; import { MINIMUM_CALLS_WIDGET_WIDTH, MINIMUM_CALLS_WIDGET_HEIGHT, @@ -34,6 +34,7 @@ jest.mock('electron', () => ({ on: jest.fn(), off: jest.fn(), handle: jest.fn(), + emit: jest.fn(), }, desktopCapturer: { getSources: jest.fn(), @@ -114,6 +115,7 @@ describe('main/windows/callsWidgetWindow', () => { width: MINIMUM_CALLS_WIDGET_WIDTH, height: MINIMUM_CALLS_WIDGET_HEIGHT, }); + expect(ipcMain.emit).toHaveBeenCalledWith(UPDATE_SHORTCUT_MENU); }); it('should open dev tools when environment variable is set', async () => { @@ -796,4 +798,26 @@ describe('main/windows/callsWidgetWindow', () => { expect(view.sendToRenderer).toBeCalledWith('some-channel', 'thecallchannelid'); }); }); + + describe('isOpen', () => { + const callsWidgetWindow = new CallsWidgetWindow(); + + it('undefined', () => { + expect(callsWidgetWindow.isOpen()).toBe(false); + }); + + it('open', () => { + callsWidgetWindow.win = { + isDestroyed: jest.fn(() => false), + }; + expect(callsWidgetWindow.isOpen()).toBe(true); + }); + + it('destroyed', () => { + callsWidgetWindow.win = { + isDestroyed: jest.fn(() => true), + }; + expect(callsWidgetWindow.isOpen()).toBe(false); + }); + }); }); diff --git a/src/main/windows/callsWidgetWindow.ts b/src/main/windows/callsWidgetWindow.ts index 95efd2f6..84e939cd 100644 --- a/src/main/windows/callsWidgetWindow.ts +++ b/src/main/windows/callsWidgetWindow.ts @@ -28,6 +28,7 @@ import { CALLS_WIDGET_SHARE_SCREEN, DESKTOP_SOURCES_MODAL_REQUEST, GET_DESKTOP_SOURCES, + UPDATE_SHORTCUT_MENU, } from 'common/communication'; import {MattermostBrowserView} from 'main/views/MattermostBrowserView'; @@ -87,10 +88,18 @@ export class CallsWidgetWindow { return this.mainView?.view.server.id; } + public isOpen() { + return Boolean(this.win && !this.win.isDestroyed()); + } + /** * Helper functions */ + public openDevTools = () => { + this.win?.webContents.openDevTools({mode: 'detach'}); + } + getViewURL = () => { return this.mainView?.view.server.url; } @@ -203,6 +212,7 @@ export class CallsWidgetWindow { */ private onClosed = () => { + ipcMain.emit(UPDATE_SHORTCUT_MENU); delete this.win; delete this.mainView; delete this.options; @@ -238,9 +248,11 @@ export class CallsWidgetWindow { this.win.setMenuBarVisibility(false); if (process.env.MM_DEBUG_CALLS_WIDGET) { - this.win.webContents.openDevTools({mode: 'detach'}); + this.openDevTools(); } + ipcMain.emit(UPDATE_SHORTCUT_MENU); + this.setBounds(initialBounds); }