From e1f5250c0cb09d27243ae61e2ca475a163c99e4f Mon Sep 17 00:00:00 2001 From: Tasos Boulis Date: Mon, 14 Nov 2022 21:40:18 +0200 Subject: [PATCH] [MM-48080] Update tray icon instantly when the settings change (#2376) * Update wording and update tray icon instantly when the settings change * Add tests * Include new file --- .vscode/settings.json | 2 + i18n/en.json | 2 +- src/main/app/config.test.js | 4 +- src/main/app/config.ts | 4 + src/main/tray/tray.test.js | 193 +++++++++++++++++++++++ src/main/tray/tray.ts | 4 +- src/renderer/components/SettingsPage.tsx | 6 +- 7 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 src/main/tray/tray.test.js diff --git a/.vscode/settings.json b/.vscode/settings.json index ef5d27fd..4d62b5fb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,6 +22,7 @@ "FOCALBOARD", "gsettings", "ICONNAME", + "inputflash", "loadscreen", "mmjstool", "NOSERVERS", @@ -29,6 +30,7 @@ "officedocument", "openxmlformats", "presentationml", + "showunreadbadge", "spreadsheetml", "textbox", "UNCLOSEABLE", diff --git a/i18n/en.json b/i18n/en.json index 0769976d..a6a1cadd 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -196,9 +196,9 @@ "renderer.components.settingsPage.showUnreadBadge.description": "Regardless of this setting, mentions are always indicated with a red badge and item count on the {taskbar} icon.", "renderer.components.settingsPage.startAppOnLogin": "Start app on login", "renderer.components.settingsPage.startAppOnLogin.description": "If enabled, the app starts automatically when you log in to your machine.", + "renderer.components.settingsPage.trayIcon.color": "Icon color: ", "renderer.components.settingsPage.trayIcon.show": "Show icon in the notification area", "renderer.components.settingsPage.trayIcon.show.darwin": "Show {appName} icon in the menu bar", - "renderer.components.settingsPage.trayIcon.theme": "Icon theme: ", "renderer.components.settingsPage.trayIcon.theme.dark": "Dark", "renderer.components.settingsPage.trayIcon.theme.light": "Light", "renderer.components.settingsPage.trayIcon.theme.systemDefault": "Use system default", diff --git a/src/main/app/config.test.js b/src/main/app/config.test.js index 945041b0..73de4645 100644 --- a/src/main/app/config.test.js +++ b/src/main/app/config.test.js @@ -41,7 +41,9 @@ jest.mock('main/AutoLauncher', () => ({ jest.mock('main/badge', () => ({ setUnreadBadgeSetting: jest.fn(), })); -jest.mock('main/tray/tray', () => ({})); +jest.mock('main/tray/tray', () => ({ + refreshTrayImages: jest.fn(), +})); jest.mock('main/windows/windowManager', () => ({ handleUpdateConfig: jest.fn(), sendToRenderer: jest.fn(), diff --git a/src/main/app/config.ts b/src/main/app/config.ts index de71a6d2..9e29528c 100644 --- a/src/main/app/config.ts +++ b/src/main/app/config.ts @@ -67,6 +67,10 @@ export function handleConfigUpdate(newConfig: CombinedConfig) { setLoggingLevel(newConfig.logLevel as LogLevel); handleUpdateMenuEvent(); + if (newConfig.trayIconTheme) { + refreshTrayImages(newConfig.trayIconTheme); + } + ipcMain.emit(EMIT_CONFIGURATION, true, newConfig); } diff --git a/src/main/tray/tray.test.js b/src/main/tray/tray.test.js new file mode 100644 index 00000000..06cf04bf --- /dev/null +++ b/src/main/tray/tray.test.js @@ -0,0 +1,193 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {handleConfigUpdate} from 'main/app/config'; + +import AutoLauncher from 'main/AutoLauncher'; + +import * as tray from './tray'; + +jest.mock('path', () => ({ + join: (a, b) => b, + resolve: (a, b, c) => (c || b), +})); +jest.mock('electron', () => { + class NativeImageMock { + image; + + constructor(path) { + this.image = path; + return this; + } + + static createFromPath(path) { + return new NativeImageMock(path); + } + + setTemplateImage = () => jest.fn(); + } + return { + app: { + getAppPath: () => '/path/to/app', + isReady: jest.fn(), + setPath: jest.fn(), + }, + ipcMain: { + emit: jest.fn(), + handle: jest.fn(), + on: jest.fn(), + }, + nativeImage: NativeImageMock, + nativeTheme: { + shouldUseDarkColors: true, // the value doesn't matter + }, + }; +}); +jest.mock('main/app/utils', () => ({ + handleUpdateMenuEvent: jest.fn(), + updateSpellCheckerLocales: jest.fn(), + updateServerInfos: jest.fn(), + setLoggingLevel: jest.fn(), +})); +jest.mock('main/app/intercom', () => ({ + handleMainWindowIsShown: jest.fn(), +})); +jest.mock('main/AutoLauncher', () => ({ + enable: jest.fn(), + disable: jest.fn(), +})); +jest.mock('main/badge', () => ({ + setUnreadBadgeSetting: jest.fn(), +})); +jest.mock('main/windows/windowManager', () => ({ + handleUpdateConfig: jest.fn(), + sendToRenderer: jest.fn(), + initializeCurrentServerName: jest.fn(), +})); + +describe('main/tray', () => { + beforeEach(() => { + AutoLauncher.enable.mockResolvedValue({}); + AutoLauncher.disable.mockResolvedValue({}); + }); + describe('config changes', () => { + let spy; + beforeAll(() => { + spy = jest.spyOn(tray, 'refreshTrayImages').mockImplementation(); + }); + afterAll(() => { + spy.mockRestore(); + }); + it('should update the tray icon color immediately when the config is updated', () => { + handleConfigUpdate({ + trayIconTheme: 'light', + }); + expect(tray.refreshTrayImages).toHaveBeenCalledWith('light'); + }); + it('should update the tray icon color immediately when the config is updated', () => { + handleConfigUpdate({ + trayIconTheme: 'dark', + }); + expect(tray.refreshTrayImages).toHaveBeenCalledWith('dark'); + }); + }); + + describe('darwin', () => { + const darwinResultAllThemes = { + normal: 'osx/menuIcons/MenuIcon16Template.png', + unread: 'osx/menuIcons/MenuIconUnread16Template.png', + mention: 'osx/menuIcons/MenuIconUnread16Template.png', + }; + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { + value: 'darwin', + }); + const result = tray.refreshTrayImages('light'); + it.each(Object.keys(result))('match "%s"', (a) => { + expect(result[a].image).toBe(darwinResultAllThemes[a]); + }); + Object.defineProperty(process, 'platform', { + value: originalPlatform, + }); + }); + + describe('win32 - light', () => { + const theme = 'light'; + const winResultLight = { + normal: `windows/tray_${theme}.ico`, + unread: `windows/tray_${theme}_unread.ico`, + mention: `windows/tray_${theme}_mention.ico`, + }; + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { + value: 'win32', + }); + const result = tray.refreshTrayImages('light'); + it.each(Object.keys(result))('match "%s"', (a) => { + expect(result[a].image).toBe(winResultLight[a]); + }); + Object.defineProperty(process, 'platform', { + value: originalPlatform, + }); + }); + + describe('win32 - dark', () => { + const theme = 'dark'; + const winResultDark = { + normal: `windows/tray_${theme}.ico`, + unread: `windows/tray_${theme}_unread.ico`, + mention: `windows/tray_${theme}_mention.ico`, + }; + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { + value: 'win32', + }); + const result = tray.refreshTrayImages('dark'); + it.each(Object.keys(result))('match "%s"', (a) => { + expect(result[a].image).toBe(winResultDark[a]); + }); + Object.defineProperty(process, 'platform', { + value: originalPlatform, + }); + }); + + describe('linux - light', () => { + const theme = 'light'; + const linuxResultLight = { + normal: `top_bar_${theme}_16.png`, + unread: `top_bar_${theme}_unread_16.png`, + mention: `top_bar_${theme}_mention_16.png`, + }; + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { + value: 'linux', + }); + const result = tray.refreshTrayImages('light'); + it.each(Object.keys(result))('match "%s"', (a) => { + expect(result[a].image).toBe(linuxResultLight[a]); + }); + Object.defineProperty(process, 'platform', { + value: originalPlatform, + }); + }); + + describe('linux - dark', () => { + const theme = 'dark'; + const linuxResultDark = { + normal: `top_bar_${theme}_16.png`, + unread: `top_bar_${theme}_unread_16.png`, + mention: `top_bar_${theme}_mention_16.png`, + }; + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { + value: 'linux', + }); + const result = tray.refreshTrayImages('dark'); + it.each(Object.keys(result))('match "%s"', (a) => { + expect(result[a].image).toBe(linuxResultDark[a]); + }); + Object.defineProperty(process, 'platform', { + value: originalPlatform, + }); + }); +}); diff --git a/src/main/tray/tray.ts b/src/main/tray/tray.ts index 4eac47d5..fb12526b 100644 --- a/src/main/tray/tray.ts +++ b/src/main/tray/tray.ts @@ -74,8 +74,8 @@ export function refreshTrayImages(trayIconTheme: string) { return trayImages; } -export function setupTray(icontheme: string) { - refreshTrayImages(icontheme); +export function setupTray(iconTheme: string) { + refreshTrayImages(iconTheme); trayIcon = new Tray(trayImages.normal); if (process.platform === 'darwin') { systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', () => { diff --git a/src/renderer/components/SettingsPage.tsx b/src/renderer/components/SettingsPage.tsx index b21d7a78..50f1bef7 100644 --- a/src/renderer/components/SettingsPage.tsx +++ b/src/renderer/components/SettingsPage.tsx @@ -633,7 +633,7 @@ class SettingsPage extends React.PureComponent { options.push(
{ style={{marginLeft: '20px'}} > {window.process.platform === 'win32' && <>