Skip to content

Commit 694f08a

Browse files
authored
fix: update context menus (#1735)
Signed-off-by: Adam Setch <[email protected]>
1 parent 97c2a04 commit 694f08a

File tree

5 files changed

+162
-28
lines changed

5 files changed

+162
-28
lines changed

src/main/main.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { app, globalShortcut, ipcMain as ipc, nativeTheme } from 'electron';
22
import log from 'electron-log';
33
import { menubar } from 'menubar';
44

5+
import { APPLICATION } from '../shared/constants';
56
import { onFirstRunMaybe } from './first-run';
67
import { TrayIcons } from './icons';
78
import MenuBuilder from './menu';
@@ -39,7 +40,8 @@ const contextMenu = menuBuilder.buildMenu();
3940
* https://github.com/electron/update-electron-app
4041
*/
4142
if (process.platform === 'darwin' || process.platform === 'win32') {
42-
new Updater(mb, menuBuilder);
43+
const updater = new Updater(mb, menuBuilder);
44+
updater.initialize();
4345
}
4446

4547
let shouldUseAlternateIdleIcon = false;
@@ -48,7 +50,7 @@ app.whenReady().then(async () => {
4850
await onFirstRunMaybe();
4951

5052
mb.on('ready', () => {
51-
mb.app.setAppUserModelId('com.electron.gitify');
53+
mb.app.setAppUserModelId(APPLICATION.ID);
5254

5355
/**
5456
* TODO: Remove @electron/remote use - see #650
@@ -58,7 +60,7 @@ app.whenReady().then(async () => {
5860
require('@electron/remote/main').enable(mb.window.webContents);
5961

6062
// Tray configuration
61-
mb.tray.setToolTip('Gitify');
63+
mb.tray.setToolTip(APPLICATION.NAME);
6264
mb.tray.setIgnoreDoubleClickEvents(true);
6365
mb.tray.on('right-click', (_event, bounds) => {
6466
mb.tray.popUpContextMenu(contextMenu, { x: bounds.x, y: bounds.y });
@@ -114,7 +116,7 @@ app.whenReady().then(async () => {
114116
ipc.on('gitify:icon-active', () => {
115117
if (!mb.tray.isDestroyed()) {
116118
mb.tray.setImage(
117-
menuBuilder.isUpdateAvailableMenuVisible()
119+
menuBuilder.isUpdateAvailable()
118120
? TrayIcons.activeUpdateIcon
119121
: TrayIcons.active,
120122
);
@@ -125,13 +127,13 @@ app.whenReady().then(async () => {
125127
if (!mb.tray.isDestroyed()) {
126128
if (shouldUseAlternateIdleIcon) {
127129
mb.tray.setImage(
128-
menuBuilder.isUpdateAvailableMenuVisible()
130+
menuBuilder.isUpdateAvailable()
129131
? TrayIcons.idleAlternateUpdateIcon
130132
: TrayIcons.idleAlternate,
131133
);
132134
} else {
133135
mb.tray.setImage(
134-
menuBuilder.isUpdateAvailableMenuVisible()
136+
menuBuilder.isUpdateAvailable()
135137
? TrayIcons.idleUpdateIcon
136138
: TrayIcons.idle,
137139
);

src/main/menu.test.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { Menu, MenuItem } from 'electron';
2+
import type { Menubar } from 'menubar';
3+
import MenuBuilder from './menu';
4+
5+
jest.mock('electron', () => ({
6+
Menu: {
7+
buildFromTemplate: jest.fn(),
8+
},
9+
MenuItem: jest.fn(),
10+
}));
11+
12+
describe('main/menu.ts', () => {
13+
let menubar: Menubar;
14+
let menuBuilder: MenuBuilder;
15+
16+
beforeEach(() => {
17+
menuBuilder = new MenuBuilder(menubar);
18+
});
19+
20+
it('should create menu items correctly', () => {
21+
expect(MenuItem).toHaveBeenCalledWith({
22+
label: 'Check for updates',
23+
enabled: true,
24+
click: expect.any(Function),
25+
});
26+
27+
expect(MenuItem).toHaveBeenCalledWith({
28+
label: 'No updates available',
29+
enabled: false,
30+
visible: false,
31+
});
32+
33+
expect(MenuItem).toHaveBeenCalledWith({
34+
label: 'An update is available',
35+
enabled: false,
36+
visible: false,
37+
});
38+
39+
expect(MenuItem).toHaveBeenCalledWith({
40+
label: 'Restart to install update',
41+
enabled: true,
42+
visible: false,
43+
click: expect.any(Function),
44+
});
45+
});
46+
47+
it('should build menu correctly', () => {
48+
menuBuilder.buildMenu();
49+
expect(Menu.buildFromTemplate).toHaveBeenCalledWith(expect.any(Array));
50+
});
51+
52+
it('should enable check for updates menu item', () => {
53+
menuBuilder.setCheckForUpdatesMenuEnabled(true);
54+
// biome-ignore lint/complexity/useLiteralKeys: This is a test
55+
expect(menuBuilder['checkForUpdatesMenuItem'].enabled).toBe(true);
56+
});
57+
58+
it('should disable check for updates menu item', () => {
59+
menuBuilder.setCheckForUpdatesMenuEnabled(false);
60+
// biome-ignore lint/complexity/useLiteralKeys: This is a test
61+
expect(menuBuilder['checkForUpdatesMenuItem'].enabled).toBe(false);
62+
});
63+
64+
it('should show no update available menu item', () => {
65+
menuBuilder.setNoUpdateAvailableMenuVisibility(true);
66+
// biome-ignore lint/complexity/useLiteralKeys: This is a test
67+
expect(menuBuilder['noUpdateAvailableMenuItem'].visible).toBe(true);
68+
});
69+
70+
it('should hide no update available menu item', () => {
71+
menuBuilder.setNoUpdateAvailableMenuVisibility(false);
72+
// biome-ignore lint/complexity/useLiteralKeys: This is a test
73+
expect(menuBuilder['noUpdateAvailableMenuItem'].visible).toBe(false);
74+
});
75+
76+
it('should show update available menu item', () => {
77+
menuBuilder.setUpdateAvailableMenuVisibility(true);
78+
// biome-ignore lint/complexity/useLiteralKeys: This is a test
79+
expect(menuBuilder['updateAvailableMenuItem'].visible).toBe(true);
80+
});
81+
82+
it('should hide update available menu item', () => {
83+
menuBuilder.setUpdateAvailableMenuVisibility(false);
84+
// biome-ignore lint/complexity/useLiteralKeys: This is a test
85+
expect(menuBuilder['updateAvailableMenuItem'].visible).toBe(false);
86+
});
87+
88+
it('should show update ready for install menu item', () => {
89+
menuBuilder.setUpdateReadyForInstallMenuVisibility(true);
90+
// biome-ignore lint/complexity/useLiteralKeys: This is a test
91+
expect(menuBuilder['updateReadyForInstallMenuItem'].visible).toBe(true);
92+
});
93+
94+
it('should show update ready for install menu item', () => {
95+
menuBuilder.setUpdateReadyForInstallMenuVisibility(false);
96+
// biome-ignore lint/complexity/useLiteralKeys: This is a test
97+
expect(menuBuilder['updateReadyForInstallMenuItem'].visible).toBe(false);
98+
});
99+
});

src/main/menu.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import type { Menubar } from 'menubar';
44
import { openLogsDirectory, resetApp, takeScreenshot } from './utils';
55

66
export default class MenuBuilder {
7-
private checkForUpdatesMenuItem: MenuItem;
8-
private updateAvailableMenuItem: MenuItem;
9-
private updateReadyForInstallMenuItem: MenuItem;
7+
private readonly checkForUpdatesMenuItem: MenuItem;
8+
private readonly noUpdateAvailableMenuItem: MenuItem;
9+
private readonly updateAvailableMenuItem: MenuItem;
10+
private readonly updateReadyForInstallMenuItem: MenuItem;
1011

1112
menubar: Menubar;
1213

@@ -21,14 +22,21 @@ export default class MenuBuilder {
2122
},
2223
});
2324

25+
this.noUpdateAvailableMenuItem = new MenuItem({
26+
label: 'No updates available',
27+
enabled: false,
28+
visible: false,
29+
});
30+
2431
this.updateAvailableMenuItem = new MenuItem({
2532
label: 'An update is available',
2633
enabled: false,
2734
visible: false,
2835
});
2936

3037
this.updateReadyForInstallMenuItem = new MenuItem({
31-
label: 'Restart to update',
38+
label: 'Restart to install update',
39+
enabled: true,
3240
visible: false,
3341
click: () => {
3442
autoUpdater.quitAndInstall();
@@ -39,6 +47,7 @@ export default class MenuBuilder {
3947
buildMenu(): Menu {
4048
const contextMenu = Menu.buildFromTemplate([
4149
this.checkForUpdatesMenuItem,
50+
this.noUpdateAvailableMenuItem,
4251
this.updateAvailableMenuItem,
4352
this.updateReadyForInstallMenuItem,
4453
{ type: 'separator' },
@@ -88,15 +97,21 @@ export default class MenuBuilder {
8897
this.checkForUpdatesMenuItem.enabled = enabled;
8998
}
9099

91-
setUpdateAvailableMenuEnabled(enabled: boolean) {
92-
this.updateAvailableMenuItem.enabled = enabled;
100+
setNoUpdateAvailableMenuVisibility(isVisible: boolean) {
101+
this.noUpdateAvailableMenuItem.visible = isVisible;
102+
}
103+
104+
setUpdateAvailableMenuVisibility(isVisible: boolean) {
105+
this.updateAvailableMenuItem.visible = isVisible;
93106
}
94107

95-
setUpdateReadyForInstallMenuEnabled(enabled: boolean) {
96-
this.updateReadyForInstallMenuItem.enabled = enabled;
108+
setUpdateReadyForInstallMenuVisibility(isVisible: boolean) {
109+
this.updateReadyForInstallMenuItem.visible = isVisible;
97110
}
98111

99-
isUpdateAvailableMenuVisible() {
100-
return this.updateAvailableMenuItem.visible;
112+
isUpdateAvailable() {
113+
return (
114+
this.updateAvailableMenuItem.visible || this.updateReadyForInstallMenuItem
115+
);
101116
}
102117
}

src/main/updater.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ import { autoUpdater } from 'electron-updater';
33
import type { Menubar } from 'menubar';
44
import { updateElectronApp } from 'update-electron-app';
55

6+
import { APPLICATION } from '../shared/constants';
67
import { logError, logInfo } from '../shared/logger';
78
import type MenuBuilder from './menu';
89

910
export default class Updater {
10-
menubar: Menubar;
11-
menuBuilder: MenuBuilder;
11+
private readonly menubar: Menubar;
12+
private readonly menuBuilder: MenuBuilder;
1213

1314
constructor(menubar: Menubar, menuBuilder: MenuBuilder) {
1415
this.menubar = menubar;
1516
this.menuBuilder = menuBuilder;
17+
}
1618

19+
initialize(): void {
1720
updateElectronApp({
1821
updateInterval: '24 hours',
1922
logger: log,
@@ -23,32 +26,37 @@ export default class Updater {
2326
logInfo('auto updater', 'Checking for update');
2427

2528
this.menuBuilder.setCheckForUpdatesMenuEnabled(false);
29+
this.menuBuilder.setNoUpdateAvailableMenuVisibility(false);
2630
});
2731

2832
autoUpdater.on('update-available', () => {
2933
logInfo('auto updater', 'New update available');
3034

31-
this.menubar.tray.setToolTip('Gitify\nA new update is available');
32-
menuBuilder.setUpdateAvailableMenuEnabled(true);
35+
this.setTooltipWithStatus('A new update is available');
36+
this.menuBuilder.setUpdateAvailableMenuVisibility(true);
3337
});
3438

3539
autoUpdater.on('download-progress', (progressObj) => {
36-
this.menubar.tray.setToolTip(
37-
`Gitify\nDownloading update: ${progressObj.percent.toFixed(2)} %`,
40+
this.setTooltipWithStatus(
41+
`Downloading update: ${progressObj.percent.toFixed(2)}%`,
3842
);
3943
});
4044

4145
autoUpdater.on('update-downloaded', () => {
4246
logInfo('auto updater', 'Update downloaded');
4347

44-
this.menubar.tray.setToolTip('Gitify\nA new update is ready to install');
45-
menuBuilder.setUpdateReadyForInstallMenuEnabled(true);
48+
this.setTooltipWithStatus('A new update is ready to install');
49+
this.menuBuilder.setUpdateAvailableMenuVisibility(false);
50+
this.menuBuilder.setUpdateReadyForInstallMenuVisibility(true);
4651
});
4752

4853
autoUpdater.on('update-not-available', () => {
4954
logInfo('auto updater', 'Update not available');
5055

51-
this.resetState();
56+
this.menuBuilder.setCheckForUpdatesMenuEnabled(true);
57+
this.menuBuilder.setNoUpdateAvailableMenuVisibility(true);
58+
this.menuBuilder.setUpdateAvailableMenuVisibility(false);
59+
this.menuBuilder.setUpdateReadyForInstallMenuVisibility(false);
5260
});
5361

5462
autoUpdater.on('update-cancelled', () => {
@@ -64,10 +72,15 @@ export default class Updater {
6472
});
6573
}
6674

75+
private setTooltipWithStatus(status: string) {
76+
this.menubar.tray.setToolTip(`${APPLICATION.NAME}\n${status}`);
77+
}
78+
6779
private resetState() {
68-
this.menubar.tray.setToolTip('Gitify');
80+
this.menubar.tray.setToolTip(APPLICATION.NAME);
6981
this.menuBuilder.setCheckForUpdatesMenuEnabled(true);
70-
this.menuBuilder.setUpdateAvailableMenuEnabled(false);
71-
this.menuBuilder.setUpdateReadyForInstallMenuEnabled(false);
82+
this.menuBuilder.setNoUpdateAvailableMenuVisibility(false);
83+
this.menuBuilder.setUpdateAvailableMenuVisibility(false);
84+
this.menuBuilder.setUpdateReadyForInstallMenuVisibility(false);
7285
}
7386
}

src/shared/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const APPLICATION = {
2+
ID: 'com.electron.gitify',
3+
4+
NAME: 'Gitify',
5+
};

0 commit comments

Comments
 (0)