Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
77 changes: 74 additions & 3 deletions src/main/menu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,24 @@ vi.mock('electron', () => {
} satisfies Pick<typeof Electron.Menu, 'buildFromTemplate'>,
MenuItem: MockMenuItem,
shell: {
openExternal: vi.fn(),
openExternal: vi.fn(async () => undefined),
} satisfies Pick<Electron.Shell, 'openExternal'>,
};
});

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(),
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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?.();
Expand Down
19 changes: 15 additions & 4 deletions src/main/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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));
});
},
});

Expand Down Expand Up @@ -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));
});
},
});

Expand Down Expand Up @@ -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));
});
},
},
{
Expand All @@ -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));
});
},
},
{
Expand Down
16 changes: 16 additions & 0 deletions src/main/updater.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
15 changes: 10 additions & 5 deletions src/main/updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});
}
}
31 changes: 31 additions & 0 deletions src/main/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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', () => {
Expand All @@ -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),
),
);
});
});
});
21 changes: 14 additions & 7 deletions src/main/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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));
});
}

/**
Expand All @@ -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));
});
}
4 changes: 2 additions & 2 deletions src/renderer/components/notifications/NotificationRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ export const NotificationRow: FC<NotificationRowProps> = ({
};

const actionNotificationInteraction = () => {
openNotification(notification);
void openNotification(notification);

runAction(() =>
void runAction(() =>
markAsDoneOnOpen
? markNotificationsAsDone([notification])
: markNotificationsAsRead([notification]),
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/primitives/EmojiText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const EmojiText: FC<IEmojiText> = ({ text }) => {
}
};

updateEmojiText();
void updateEmojiText();

return () => {
mountedRef.current = false;
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/primitives/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const Header: FC<HeaderProps> = (props: HeaderProps) => {
onClick={() => {
navigate(-1);
if (props.fetchOnBack) {
fetchNotifications();
void fetchNotifications();
}
}}
tooltipDirection="e"
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/settings/SettingsFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const SettingsFooter: FC = () => {
const { shortcuts } = useShortcutActions();

useEffect(() => {
(async () => {
void (async () => {
const result = await getAppVersion();
setAppVersion(result);
})();
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/hooks/useNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/routes/github/LoginWithDeviceFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const GitHubLoginWithDeviceFlowRoute: FC = () => {
};

if (scopeChoice) {
initializeDeviceFlow();
void initializeDeviceFlow();
}
}, [loginWithDeviceFlowStart, reAuthAccount, scopeChoice, forge]);

Expand Down Expand Up @@ -124,7 +124,7 @@ export const GitHubLoginWithDeviceFlowRoute: FC = () => {
}
};

startPolling();
void startPolling();

return () => {
isActive = false;
Expand Down
Loading