From 7190ea8e6f105a1b1058b0c2112ae537b4457d55 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Sat, 3 Oct 2026 12:05:54 -0400 Subject: [PATCH] fix(sonar): resolve S9383 floating promises --- src/main/index.ts | 2 +- src/main/menu.test.ts | 77 ++++++++++++++++++- src/main/menu.ts | 19 ++++- src/main/updater.test.ts | 16 ++++ src/main/updater.ts | 15 ++-- src/main/utils.test.ts | 31 ++++++++ src/main/utils.ts | 21 +++-- .../notifications/NotificationRow.tsx | 4 +- .../components/primitives/EmojiText.tsx | 2 +- src/renderer/components/primitives/Header.tsx | 2 +- .../components/settings/SettingsFooter.tsx | 2 +- src/renderer/hooks/useNotifications.ts | 4 +- .../routes/github/LoginWithDeviceFlow.tsx | 4 +- 13 files changed, 170 insertions(+), 29 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 591601354..d7735c725 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -52,7 +52,7 @@ app.setAsDefaultProtocolClient(protocol); const appUpdater = new AppUpdater(mb, menuBuilder); -app.whenReady().then(async () => { +void app.whenReady().then(async () => { await onFirstRunMaybe(); initializeAppLifecycle(mb, contextMenu, protocol); diff --git a/src/main/menu.test.ts b/src/main/menu.test.ts index 9ff9e8f8e..760399734 100644 --- a/src/main/menu.test.ts +++ b/src/main/menu.test.ts @@ -32,18 +32,24 @@ vi.mock('electron', () => { } satisfies Pick, MenuItem: MockMenuItem, shell: { - openExternal: vi.fn(), + openExternal: vi.fn(async () => undefined), } satisfies Pick, }; }); vi.mock('electron-updater', () => ({ autoUpdater: { - checkForUpdatesAndNotify: vi.fn(), + checkForUpdatesAndNotify: vi.fn(async () => undefined), quitAndInstall: vi.fn(), }, })); +const logErrorMock = vi.fn(); +vi.mock('../shared/logger', () => ({ + logError: (...args: unknown[]) => logErrorMock(...args), + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), +})); + vi.mock('./utils', () => ({ takeScreenshot: vi.fn(), openLogsDirectory: vi.fn(), @@ -92,7 +98,7 @@ describe('main/menu.ts', () => { menuItemInstances.length = 0; // Clear tracked instances menubar = { app: { quit: vi.fn() }, - showWindow: vi.fn(), + showWindow: vi.fn(async () => undefined), hideWindow: vi.fn(), refreshContextMenu: vi.fn(), tray: { @@ -303,6 +309,71 @@ describe('main/menu.ts', () => { expect(menubar.app.quit).toHaveBeenCalled(); }); + it('logs an error when checking for updates fails', async () => { + const cfg = getMenuItemConfigByLabel('Check for updates'); + vi.mocked(autoUpdater.checkForUpdatesAndNotify).mockRejectedValueOnce( + new Error('update failed'), + ); + + cfg?.click?.(); + + await vi.waitFor(() => + expect(logErrorMock).toHaveBeenCalledWith( + 'menu', + 'Failed to check for updates', + expect.any(Error), + ), + ); + }); + + it('logs an error when showing the window fails', async () => { + const cfg = getMenuItemConfigByLabel(`Show ${APPLICATION.NAME}`); + vi.mocked(menubar.showWindow).mockRejectedValueOnce(new Error('show failed')); + + cfg?.click?.(); + + await vi.waitFor(() => + expect(logErrorMock).toHaveBeenCalledWith( + 'menu', + 'Failed to show window', + expect.any(Error), + ), + ); + }); + + it('logs an error when opening the repository fails', async () => { + const template = buildAndGetTemplate(); + const devEntry = template.find((item) => item?.label === 'Developer') as TemplateItem; + const item = devEntry.submenu?.find((i) => i.label === 'Visit Repository'); + vi.mocked(shell.openExternal).mockRejectedValueOnce(new Error('open failed')); + + item?.click?.(); + + await vi.waitFor(() => + expect(logErrorMock).toHaveBeenCalledWith( + 'menu', + 'Failed to open repository in browser', + expect.any(Error), + ), + ); + }); + + it('logs an error when opening the website fails', async () => { + const template = buildAndGetTemplate(); + const item = template.find((i) => i.label === 'Visit Website'); + vi.mocked(shell.openExternal).mockRejectedValueOnce(new Error('open failed')); + + item?.click?.(); + + await vi.waitFor(() => + expect(logErrorMock).toHaveBeenCalledWith( + 'menu', + 'Failed to open website in browser', + expect.any(Error), + ), + ); + }); + it('show window menu item calls showWindow', () => { const cfg = getMenuItemConfigByLabel(`Show ${APPLICATION.NAME}`); cfg?.click?.(); diff --git a/src/main/menu.ts b/src/main/menu.ts index e14535ff0..58eb93728 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -3,6 +3,7 @@ import type { Menubar } from 'electron-menubar'; import { autoUpdater } from 'electron-updater'; import { APPLICATION } from '../shared/constants'; +import { logError, toError } from '../shared/logger'; import { isMacOS } from '../shared/platform'; import { resetApp } from './lifecycle/reset'; @@ -32,7 +33,9 @@ export default class MenuBuilder { label: 'Check for updates', enabled: true, click: () => { - autoUpdater.checkForUpdatesAndNotify(); + autoUpdater.checkForUpdatesAndNotify().catch((err) => { + logError('menu', 'Failed to check for updates', toError(err)); + }); }, }); @@ -61,7 +64,9 @@ export default class MenuBuilder { label: `Show ${APPLICATION.NAME}`, visible: true, click: () => { - this.menubar.showWindow(); + this.menubar.showWindow().catch((err) => { + logError('menu', 'Failed to show window', toError(err)); + }); }, }); @@ -110,7 +115,11 @@ export default class MenuBuilder { { label: 'Visit Repository', click: () => { - shell.openExternal(`${APPLICATION.GITHUB_BASE_URL}/${APPLICATION.REPO_SLUG}`); + shell + .openExternal(`${APPLICATION.GITHUB_BASE_URL}/${APPLICATION.REPO_SLUG}`) + .catch((err) => { + logError('menu', 'Failed to open repository in browser', toError(err)); + }); }, }, { @@ -125,7 +134,9 @@ export default class MenuBuilder { { label: 'Visit Website', click: () => { - shell.openExternal(APPLICATION.WEBSITE); + shell.openExternal(APPLICATION.WEBSITE).catch((err) => { + logError('menu', 'Failed to open website in browser', toError(err)); + }); }, }, { diff --git a/src/main/updater.test.ts b/src/main/updater.test.ts index dcd949a5d..17535618a 100644 --- a/src/main/updater.test.ts +++ b/src/main/updater.test.ts @@ -185,6 +185,22 @@ describe('main/updater.ts', () => { expect(autoUpdater.quitAndInstall).not.toHaveBeenCalled(); }); + + it('logs an error when the dialog fails to open', async () => { + vi.mocked(dialog.showMessageBox).mockRejectedValueOnce(new Error('dialog failed')); + + await updater.start(); + + emit('update-downloaded', { releaseName: 'v9.9.9' }); + + await vi.waitFor(() => + expect(logError).toHaveBeenCalledWith( + 'app updater', + 'Failed to show update ready dialog', + expect.any(Error), + ), + ); + }); }); describe('update event handlers & scheduling', () => { diff --git a/src/main/updater.ts b/src/main/updater.ts index de85912fc..fbb4e4aa1 100644 --- a/src/main/updater.ts +++ b/src/main/updater.ts @@ -218,10 +218,15 @@ export default class AppUpdater { detail: 'Restart to apply the update. You can also restart later from the tray menu.', }; - dialog.showMessageBox(dialogOpts).then((returnValue) => { - if (returnValue.response === 0) { - autoUpdater.quitAndInstall(); - } - }); + dialog + .showMessageBox(dialogOpts) + .then((returnValue) => { + if (returnValue.response === 0) { + autoUpdater.quitAndInstall(); + } + }) + .catch((err) => { + logError('app updater', 'Failed to show update ready dialog', toError(err)); + }); } } diff --git a/src/main/utils.test.ts b/src/main/utils.test.ts index 5f5445086..c8af44b5c 100644 --- a/src/main/utils.test.ts +++ b/src/main/utils.test.ts @@ -45,6 +45,7 @@ const logErrorMock = vi.fn(); vi.mock('../shared/logger', () => ({ logInfo: (...a: unknown[]) => logInfoMock(...a), logError: (...a: unknown[]) => logErrorMock(...a), + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), })); import { shell } from 'electron'; @@ -82,6 +83,22 @@ describe('main/utils', () => { expect.stringContaining('Screenshot saved'), ); }); + + it('logs an error when capturing the window fails', async () => { + const mb = { + window: { capturePage: () => Promise.reject(new Error('capture failed')) }, + }; + + await takeScreenshot(mb as unknown as Menubar); + + await vi.waitFor(() => + expect(logErrorMock).toHaveBeenCalledWith( + 'takeScreenshot', + 'Failed to capture window', + expect.any(Error), + ), + ); + }); }); describe('openLogsDirectory', () => { @@ -99,5 +116,19 @@ describe('main/utils', () => { expect.any(Error), ); }); + + it('logs an error when opening the directory fails', async () => { + vi.mocked(shell.openPath).mockRejectedValueOnce(new Error('open failed')); + + openLogsDirectory(); + + await vi.waitFor(() => + expect(logErrorMock).toHaveBeenCalledWith( + 'openLogsDirectory', + 'Failed to open logs directory', + expect.any(Error), + ), + ); + }); }); }); diff --git a/src/main/utils.ts b/src/main/utils.ts index 9b06ca898..a3f4065b1 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -7,7 +7,7 @@ import log from 'electron-log'; import type { Menubar } from 'electron-menubar'; import { APPLICATION } from '../shared/constants'; -import { logError, logInfo } from '../shared/logger'; +import { logError, logInfo, toError } from '../shared/logger'; /** * Returns true when the app is running in development mode (i.e. not packaged). @@ -34,11 +34,16 @@ export function takeScreenshot(mb: Menubar) { return; } - mb.window.capturePage().then((img) => { - fs.writeFile(capturedPicFilePath, img.toPNG(), () => - logInfo('takeScreenshot', `Screenshot saved ${capturedPicFilePath}`), - ); - }); + mb.window + .capturePage() + .then((img) => { + fs.writeFile(capturedPicFilePath, img.toPNG(), () => + logInfo('takeScreenshot', `Screenshot saved ${capturedPicFilePath}`), + ); + }) + .catch((err) => { + logError('takeScreenshot', 'Failed to capture window', toError(err)); + }); } /** @@ -58,5 +63,7 @@ export function openLogsDirectory() { } const logDirectory = path.dirname(logFilePath); - shell.openPath(logDirectory); + shell.openPath(logDirectory).catch((err) => { + logError('openLogsDirectory', 'Failed to open logs directory', toError(err)); + }); } diff --git a/src/renderer/components/notifications/NotificationRow.tsx b/src/renderer/components/notifications/NotificationRow.tsx index d3182a739..c5623529b 100644 --- a/src/renderer/components/notifications/NotificationRow.tsx +++ b/src/renderer/components/notifications/NotificationRow.tsx @@ -72,9 +72,9 @@ export const NotificationRow: FC = ({ }; const actionNotificationInteraction = () => { - openNotification(notification); + void openNotification(notification); - runAction(() => + void runAction(() => markAsDoneOnOpen ? markNotificationsAsDone([notification]) : markNotificationsAsRead([notification]), diff --git a/src/renderer/components/primitives/EmojiText.tsx b/src/renderer/components/primitives/EmojiText.tsx index 9d43a45e3..1618924cb 100644 --- a/src/renderer/components/primitives/EmojiText.tsx +++ b/src/renderer/components/primitives/EmojiText.tsx @@ -25,7 +25,7 @@ export const EmojiText: FC = ({ text }) => { } }; - updateEmojiText(); + void updateEmojiText(); return () => { mountedRef.current = false; diff --git a/src/renderer/components/primitives/Header.tsx b/src/renderer/components/primitives/Header.tsx index cdd5aa1bf..05aa53dde 100644 --- a/src/renderer/components/primitives/Header.tsx +++ b/src/renderer/components/primitives/Header.tsx @@ -29,7 +29,7 @@ export const Header: FC = (props: HeaderProps) => { onClick={() => { navigate(-1); if (props.fetchOnBack) { - fetchNotifications(); + void fetchNotifications(); } }} tooltipDirection="e" diff --git a/src/renderer/components/settings/SettingsFooter.tsx b/src/renderer/components/settings/SettingsFooter.tsx index 9baf5e145..94bea2d85 100644 --- a/src/renderer/components/settings/SettingsFooter.tsx +++ b/src/renderer/components/settings/SettingsFooter.tsx @@ -17,7 +17,7 @@ export const SettingsFooter: FC = () => { const { shortcuts } = useShortcutActions(); useEffect(() => { - (async () => { + void (async () => { const result = await getAppVersion(); setAppVersion(result); })(); diff --git a/src/renderer/hooks/useNotifications.ts b/src/renderer/hooks/useNotifications.ts index 7a2dfd649..bb6af5e67 100644 --- a/src/renderer/hooks/useNotifications.ts +++ b/src/renderer/hooks/useNotifications.ts @@ -346,11 +346,11 @@ export const useNotifications = ({ if (filteredDiffNotifications.length > 0) { if (playSoundNewNotifications) { - raiseSoundNotification(notificationVolume); + void raiseSoundNotification(notificationVolume); } if (showSystemNotifications) { - raiseNativeNotification(filteredDiffNotifications); + void raiseNativeNotification(filteredDiffNotifications); } } } diff --git a/src/renderer/routes/github/LoginWithDeviceFlow.tsx b/src/renderer/routes/github/LoginWithDeviceFlow.tsx index 68cd0c1e9..3fe90a7fe 100644 --- a/src/renderer/routes/github/LoginWithDeviceFlow.tsx +++ b/src/renderer/routes/github/LoginWithDeviceFlow.tsx @@ -71,7 +71,7 @@ export const GitHubLoginWithDeviceFlowRoute: FC = () => { }; if (scopeChoice) { - initializeDeviceFlow(); + void initializeDeviceFlow(); } }, [loginWithDeviceFlowStart, reAuthAccount, scopeChoice, forge]); @@ -118,7 +118,7 @@ export const GitHubLoginWithDeviceFlowRoute: FC = () => { } }; - startPolling(); + void startPolling(); return () => { isActive = false;