Repository: gitify-app/gitify
Pull request #1376 (2024-07-13 19:10:57)

feat(settings): open links in foreground or background

afonsojramos: I suppose here is where @bmulholland's point comes into play the most. Why not just leave this as a cmd + click action, and remove the setting altogether? 
bmulholland: I still don't mind Cmd+Click, but do wonder about discoverability.

Patch:
diff --git a/src/__mocks__/state-mocks.ts b/src/__mocks__/state-mocks.ts
index 8498bf24..0065b239 100644
--- a/src/__mocks__/state-mocks.ts
+++ b/src/__mocks__/state-mocks.ts
@@ -5,6 +5,7 @@ import {
   type GitifyUser,
   GroupBy,
   type Hostname,
+  OpenPreference,
   type SettingsState,
   Theme,
   type Token,
@@ -88,6 +89,7 @@ const mockNotificationSettings = {
 };
 
 const mockSystemSettings = {
+  openLinks: OpenPreference.FOREGROUND,
   keyboardShortcut: true,
   showNotificationsCountInTray: false,
   showNotifications: true,
diff --git a/src/components/settings/SystemSettings.tsx b/src/components/settings/SystemSettings.tsx
index 08ba6ca3..1d87ad3f 100644
--- a/src/components/settings/SystemSettings.tsx
+++ b/src/components/settings/SystemSettings.tsx
@@ -1,9 +1,11 @@
 import { DeviceDesktopIcon } from '@primer/octicons-react';
 import { type FC, useContext } from 'react';
 import { AppContext } from '../../context/App';
+import type { OpenPreference } from '../../types';
 import Constants from '../../utils/constants';
 import { isLinux, isMacOS } from '../../utils/platform';
 import { Checkbox } from '../fields/Checkbox';
+import { RadioGroup } from '../fields/RadioGroup';
 import { Legend } from './Legend';
 
 export const SystemSettings: FC = () => {
@@ -12,6 +14,18 @@ export const SystemSettings: FC = () => {
   return (
     <fieldset>
       <Legend icon={DeviceDesktopIcon}>System</Legend>
+      <RadioGroup
+        name="openLinks"
+        label="Open Links:"
+        value={settings.openLinks}
+        options={[
+          { label: 'Foreground', value: 'FOREGROUND' },
+          { label: 'Background', value: 'BACKGROUND' },
+        ]}
+        onChange={(evt) => {
+          updateSetting('openLinks', evt.target.value as OpenPreference);
+        }}
+      />
       <Checkbox
         name="keyboardShortcutEnabled"
         label="Enable keyboard shortcut"
diff --git a/src/context/App.tsx b/src/context/App.tsx
index 2db248f7..1cca8edd 100644
--- a/src/context/App.tsx
+++ b/src/context/App.tsx
@@ -15,6 +15,7 @@ import {
   type AuthState,
   type GitifyError,
   GroupBy,
+  OpenPreference,
   type SettingsState,
   type SettingsValue,
   type Status,
@@ -69,6 +70,7 @@ const defaultNotificationSettings = {
 };
 
 const defaultSystemSettings = {
+  openLinks: OpenPreference.FOREGROUND,
   keyboardShortcut: true,
   showNotificationsCountInTray: false,
   showNotifications: true,
diff --git a/src/types.ts b/src/types.ts
index fd6f0822..a51c648e 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -51,7 +51,13 @@ export interface Account {
   user: GitifyUser | null;
 }
 
-export type SettingsValue = boolean | Theme | GroupBy | Reason[] | number;
+export type SettingsValue =
+  | boolean
+  | number
+  | GroupBy
+  | OpenPreference
+  | Reason[]
+  | Theme;
 
 export type SettingsState = AppearanceSettingsState &
   NotificationSettingsState &
@@ -76,6 +82,7 @@ interface NotificationSettingsState {
 }
 
 interface SystemSettingsState {
+  openLinks: OpenPreference;
   playSound: boolean;
   openAtStartup: boolean;
   showNotificationsCountInTray: boolean;
@@ -98,6 +105,11 @@ export enum Theme {
   DARK = 'DARK',
 }
 
+export enum OpenPreference {
+  FOREGROUND = 'FOREGROUND',
+  BACKGROUND = 'FOREGROUND',
+}
+
 export enum GroupBy {
   REPOSITORY = 'REPOSITORY',
   DATE = 'DATE',
diff --git a/src/utils/comms.ts b/src/utils/comms.ts
index 936bc429..13cef346 100644
--- a/src/utils/comms.ts
+++ b/src/utils/comms.ts
@@ -1,10 +1,16 @@
 import { ipcRenderer, shell } from 'electron';
-import type { Link } from '../types';
+import { type Link, OpenPreference } from '../types';
 import Constants from './constants';
+import { loadState } from './storage';
 
 export function openExternalLink(url: Link): void {
   if (url.toLowerCase().startsWith('https://')) {
-    shell.openExternal(url);
+    // Load the state from local storage to avoid having to pass settings as a parameter
+    const { settings } = loadState();
+
+    shell.openExternal(url, {
+      activate: settings.openLinks === OpenPreference.FOREGROUND,
+    });
   }
 }
 


Test patch:
diff --git a/src/components/AccountNotifications.test.tsx b/src/components/AccountNotifications.test.tsx
index 7f55c58f..3b970f4b 100644
--- a/src/components/AccountNotifications.test.tsx
+++ b/src/components/AccountNotifications.test.tsx
@@ -61,7 +61,9 @@ describe('components/AccountNotifications.tsx', () => {
   });
 
   it('should open profile when clicked', async () => {
-    const openAccountProfileMock = jest.spyOn(links, 'openAccountProfile');
+    const openAccountProfileMock = jest
+      .spyOn(links, 'openAccountProfile')
+      .mockImplementation();
 
     const props = {
       account: mockGitHubCloudAccount,
diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx
index 15a6abb5..6e480948 100644
--- a/src/components/NotificationRow.test.tsx
+++ b/src/components/NotificationRow.test.tsx
@@ -13,9 +13,8 @@ import * as links from '../utils/links';
 import { NotificationRow } from './NotificationRow';
 
 describe('components/NotificationRow.tsx', () => {
-  beforeEach(() => {
-    jest.spyOn(links, 'openNotification');
-  });
+  jest.spyOn(links, 'openNotification');
+  jest.spyOn(comms, 'openExternalLink').mockImplementation();
 
   afterEach(() => {
     jest.clearAllMocks();
diff --git a/src/components/RepositoryNotifications.test.tsx b/src/components/RepositoryNotifications.test.tsx
index 1e67b7ee..4a240333 100644
--- a/src/components/RepositoryNotifications.test.tsx
+++ b/src/components/RepositoryNotifications.test.tsx
@@ -37,7 +37,9 @@ describe('components/Repository.tsx', () => {
   });
 
   it('should open the browser when clicking on the repo name', () => {
-    const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
+    const openExternalLinkMock = jest
+      .spyOn(comms, 'openExternalLink')
+      .mockImplementation();
 
     render(
       <AppContext.Provider value={{}}>
diff --git a/src/components/Sidebar.test.tsx b/src/components/Sidebar.test.tsx
index 81e0b7b3..7958fd2e 100644
--- a/src/components/Sidebar.test.tsx
+++ b/src/components/Sidebar.test.tsx
@@ -15,8 +15,9 @@ jest.mock('react-router-dom', () => ({
 
 describe('components/Sidebar.tsx', () => {
   const fetchNotifications = jest.fn();
-
-  const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
+  const openExternalLinkMock = jest
+    .spyOn(comms, 'openExternalLink')
+    .mockImplementation();
 
   afterEach(() => {
     jest.clearAllMocks();
diff --git a/src/components/buttons/Button.test.tsx b/src/components/buttons/Button.test.tsx
index d83667e8..31caf6a1 100644
--- a/src/components/buttons/Button.test.tsx
+++ b/src/components/buttons/Button.test.tsx
@@ -1,11 +1,13 @@
 import { MarkGithubIcon } from '@primer/octicons-react';
 import { fireEvent, render, screen } from '@testing-library/react';
-import { shell } from 'electron';
 import type { Link } from '../../types';
+import * as comms from '../../utils/comms';
 import { Button, type IButton } from './Button';
 
 describe('components/buttons/Button.tsx', () => {
-  const openExternalMock = jest.spyOn(shell, 'openExternal');
+  const openExternalLinkMock = jest
+    .spyOn(comms, 'openExternalLink')
+    .mockImplementation();
 
   const props: IButton = {
     label: 'button',
@@ -39,6 +41,6 @@ describe('components/buttons/Button.tsx', () => {
     const buttonElement = screen.getByLabelText('button');
 
     fireEvent.click(buttonElement);
-    expect(openExternalMock).toHaveBeenCalledTimes(1);
+    expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
   });
 });
diff --git a/src/components/notification/NotificationFooter.test.tsx b/src/components/notification/NotificationFooter.test.tsx
index fd01cc04..970330c0 100644
--- a/src/components/notification/NotificationFooter.test.tsx
+++ b/src/components/notification/NotificationFooter.test.tsx
@@ -8,14 +8,9 @@ import { GroupBy, type Link } from '../../types';
 import type { UserType } from '../../typesGitHub';
 import { mockSingleNotification } from '../../utils/api/__mocks__/response-mocks';
 import * as comms from '../../utils/comms';
-import * as links from '../../utils/links';
 import { NotificationFooter } from './NotificationFooter';
 
 describe('components/notification/NotificationFooter.tsx', () => {
-  beforeEach(() => {
-    jest.spyOn(links, 'openNotification');
-  });
-
   afterEach(() => {
     jest.clearAllMocks();
   });
@@ -122,7 +117,9 @@ describe('components/notification/NotificationFooter.tsx', () => {
   });
 
   it('should open notification user profile', () => {
-    const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
+    const openExternalLinkMock = jest
+      .spyOn(comms, 'openExternalLink')
+      .mockImplementation();
 
     const props = {
       notification: {
diff --git a/src/components/notification/NotificationHeader.test.tsx b/src/components/notification/NotificationHeader.test.tsx
index 5216da20..9271aaf1 100644
--- a/src/components/notification/NotificationHeader.test.tsx
+++ b/src/components/notification/NotificationHeader.test.tsx
@@ -7,6 +7,10 @@ import * as comms from '../../utils/comms';
 import { NotificationHeader } from './NotificationHeader';
 
 describe('components/notification/NotificationHeader.tsx', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
   it('should render itself & its children - group by repositories', async () => {
     const props = {
       notification: mockSingleNotification,
@@ -38,7 +42,9 @@ describe('components/notification/NotificationHeader.tsx', () => {
   });
 
   it('should open notification user profile - group by date', () => {
-    const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
+    const openExternalLinkMock = jest
+      .spyOn(comms, 'openExternalLink')
+      .mockImplementation();
 
     const props = {
       notification: mockSingleNotification,
diff --git a/src/components/settings/NotificationSettings.test.tsx b/src/components/settings/NotificationSettings.test.tsx
index f1ac7ea6..6cfbd1f8 100644
--- a/src/components/settings/NotificationSettings.test.tsx
+++ b/src/components/settings/NotificationSettings.test.tsx
@@ -60,7 +60,9 @@ describe('routes/components/settings/NotificationSettings.tsx', () => {
   });
 
   it('should open official docs for showOnlyParticipating tooltip', async () => {
-    const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
+    const openExternalLinkMock = jest
+      .spyOn(comms, 'openExternalLink')
+      .mockImplementation();
 
     await act(async () => {
       render(
diff --git a/src/components/settings/SettingsFooter.test.tsx b/src/components/settings/SettingsFooter.test.tsx
index ddd6bf31..5f1f4178 100644
--- a/src/components/settings/SettingsFooter.test.tsx
+++ b/src/components/settings/SettingsFooter.test.tsx
@@ -81,7 +81,9 @@ describe('routes/components/settings/SettingsFooter.tsx', () => {
       ...originalEnv,
       NODE_ENV: 'production',
     };
-    const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
+    const openExternalLinkMock = jest
+      .spyOn(comms, 'openExternalLink')
+      .mockImplementation();
 
     await act(async () => {
       render(
diff --git a/src/components/settings/SystemSettings.test.tsx b/src/components/settings/SystemSettings.test.tsx
index 3a192f48..f424b79c 100644
--- a/src/components/settings/SystemSettings.test.tsx
+++ b/src/components/settings/SystemSettings.test.tsx
@@ -11,6 +11,29 @@ describe('routes/components/settings/SystemSettings.tsx', () => {
     jest.clearAllMocks();
   });
 
+  it('should change the open links radio group', async () => {
+    await act(async () => {
+      render(
+        <AppContext.Provider
+          value={{
+            auth: mockAuth,
+            settings: mockSettings,
+            updateSetting,
+          }}
+        >
+          <MemoryRouter>
+            <SystemSettings />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
+
+    fireEvent.click(screen.getByLabelText('Background'));
+
+    expect(updateSetting).toHaveBeenCalledTimes(1);
+    expect(updateSetting).toHaveBeenCalledWith('openLinks', 'BACKGROUND');
+  });
+
   it('should toggle the keyboardShortcut checkbox', async () => {
     await act(async () => {
       render(
diff --git a/src/context/App.test.tsx b/src/context/App.test.tsx
index 0abc4f27..32bbc23a 100644
--- a/src/context/App.test.tsx
+++ b/src/context/App.test.tsx
@@ -384,6 +384,7 @@ describe('context/App.tsx', () => {
           groupBy: 'REPOSITORY',
           filterReasons: [],
           zoomPercentage: 100,
+          openLinks: 'FOREGROUND',
         } as SettingsState,
       });
     });
@@ -440,6 +441,7 @@ describe('context/App.tsx', () => {
           groupBy: 'REPOSITORY',
           filterReasons: [],
           zoomPercentage: 100,
+          openLinks: 'FOREGROUND',
         } as SettingsState,
       });
     });
diff --git a/src/routes/Accounts.test.tsx b/src/routes/Accounts.test.tsx
index 573df05a..3b4e8462 100644
--- a/src/routes/Accounts.test.tsx
+++ b/src/routes/Accounts.test.tsx
@@ -73,7 +73,9 @@ describe('routes/Accounts.tsx', () => {
 
   describe('Account interactions', () => {
     it('open profile in external browser', async () => {
-      const openAccountProfileMock = jest.spyOn(links, 'openAccountProfile');
+      const openAccountProfileMock = jest
+        .spyOn(links, 'openAccountProfile')
+        .mockImplementation();
 
       await act(async () => {
         render(
@@ -101,7 +103,9 @@ describe('routes/Accounts.tsx', () => {
     });
 
     it('open host in external browser', async () => {
-      const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
+      const openExternalLinkMock = jest
+        .spyOn(comms, 'openExternalLink')
+        .mockImplementation();
 
       await act(async () => {
         render(
@@ -127,7 +131,9 @@ describe('routes/Accounts.tsx', () => {
     });
 
     it('open developer settings in external browser', async () => {
-      const openExternalLinkMock = jest.spyOn(comms, 'openExternalLink');
+      const openExternalLinkMock = jest
+        .spyOn(comms, 'openExternalLink')
+        .mockImplementation();
 
       await act(async () => {
         render(
diff --git a/src/routes/LoginWithOAuthApp.test.tsx b/src/routes/LoginWithOAuthApp.test.tsx
index 2f46a141..6647363c 100644
--- a/src/routes/LoginWithOAuthApp.test.tsx
+++ b/src/routes/LoginWithOAuthApp.test.tsx
@@ -1,8 +1,8 @@
 import { fireEvent, render, screen } from '@testing-library/react';
-import { shell } from 'electron';
 import { MemoryRouter } from 'react-router-dom';
 import { AppContext } from '../context/App';
 import type { AuthState, ClientID, ClientSecret, Hostname } from '../types';
+import * as comms from '../utils/comms';
 import { LoginWithOAuthApp, validate } from './LoginWithOAuthApp';
 
 const mockNavigate = jest.fn();
@@ -12,7 +12,9 @@ jest.mock('react-router-dom', () => ({
 }));
 
 describe('routes/LoginWithOAuthApp.tsx', () => {
-  const openExternalMock = jest.spyOn(shell, 'openExternal');
+  const openExternalLinkMock = jest
+    .spyOn(comms, 'openExternalLink')
+    .mockImplementation();
 
   const mockAuth: AuthState = {
     accounts: [],
@@ -84,7 +86,7 @@ describe('routes/LoginWithOAuthApp.tsx', () => {
 
       fireEvent.click(screen.getByText('Create new OAuth App'));
 
-      expect(openExternalMock).toHaveBeenCalledTimes(0);
+      expect(openExternalLinkMock).toHaveBeenCalledTimes(0);
     });
 
     it('should open in browser if hostname configured', async () => {
@@ -102,7 +104,7 @@ describe('routes/LoginWithOAuthApp.tsx', () => {
 
       fireEvent.click(screen.getByText('Create new OAuth App'));
 
-      expect(openExternalMock).toHaveBeenCalledTimes(1);
+      expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
     });
   });
 
@@ -143,6 +145,6 @@ describe('routes/LoginWithOAuthApp.tsx', () => {
 
     fireEvent.click(screen.getByLabelText('GitHub Docs'));
 
-    expect(openExternalMock).toHaveBeenCalledTimes(1);
+    expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
   });
 });
diff --git a/src/routes/LoginWithPersonalAccessToken.test.tsx b/src/routes/LoginWithPersonalAccessToken.test.tsx
index def9feb2..9343f816 100644
--- a/src/routes/LoginWithPersonalAccessToken.test.tsx
+++ b/src/routes/LoginWithPersonalAccessToken.test.tsx
@@ -5,9 +5,9 @@ import {
   screen,
   waitFor,
 } from '@testing-library/react';
-import { shell } from 'electron';
 import { MemoryRouter } from 'react-router-dom';
 import { AppContext } from '../context/App';
+import * as comms from '../utils/comms';
 import {
   LoginWithPersonalAccessToken,
   validate,
@@ -20,9 +20,10 @@ jest.mock('react-router-dom', () => ({
 }));
 
 describe('routes/LoginWithPersonalAccessToken.tsx', () => {
-  const openExternalMock = jest.spyOn(shell, 'openExternal');
-
   const mockValidateToken = jest.fn();
+  const openExternalLinkMock = jest
+    .spyOn(comms, 'openExternalLink')
+    .mockImplementation();
 
   afterEach(() => {
     jest.clearAllMocks();
@@ -88,7 +89,7 @@ describe('routes/LoginWithPersonalAccessToken.tsx', () => {
 
       fireEvent.click(screen.getByText('Generate a PAT'));
 
-      expect(openExternalMock).toHaveBeenCalledTimes(0);
+      expect(openExternalLinkMock).toHaveBeenCalledTimes(0);
     });
 
     it('should open in browser if hostname configured', async () => {
@@ -104,7 +105,7 @@ describe('routes/LoginWithPersonalAccessToken.tsx', () => {
 
       fireEvent.click(screen.getByText('Generate a PAT'));
 
-      expect(openExternalMock).toHaveBeenCalledTimes(1);
+      expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
     });
   });
 
@@ -198,6 +199,6 @@ describe('routes/LoginWithPersonalAccessToken.tsx', () => {
 
     fireEvent.click(screen.getByLabelText('GitHub Docs'));
 
-    expect(openExternalMock).toHaveBeenCalledTimes(1);
+    expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
   });
 });
diff --git a/src/routes/__snapshots__/Settings.test.tsx.snap b/src/routes/__snapshots__/Settings.test.tsx.snap
index a2fa1103..58ff65ec 100644
--- a/src/routes/__snapshots__/Settings.test.tsx.snap
+++ b/src/routes/__snapshots__/Settings.test.tsx.snap
@@ -555,6 +555,65 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = `
         </svg>
         System
       </legend>
+      <div
+        class="mt-3 mb-2 text-sm"
+      >
+        <div
+          class="flex items-start"
+        >
+          <div
+            class="mr-3"
+          >
+            <label
+              class="font-medium text-gray-700 dark:text-gray-200"
+              for="openLinks"
+            >
+              Open Links:
+            </label>
+          </div>
+          <div
+            aria-labelledby="openLinks"
+            class="flex items-center space-x-4"
+            role="group"
+          >
+            <div
+              class="flex items-center"
+            >
+              <input
+                checked=""
+                class="size-4 border-gray-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer"
+                id="openLinks_foreground"
+                name="openLinks"
+                type="radio"
+                value="FOREGROUND"
+              />
+              <label
+                class="ml-3 block text-sm font-medium text-gray-700 dark:text-white cursor-pointer"
+                for="openLinks_foreground"
+              >
+                Foreground
+              </label>
+            </div>
+            <div
+              class="flex items-center"
+            >
+              <input
+                class="size-4 border-gray-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer"
+                id="openLinks_background"
+                name="openLinks"
+                type="radio"
+                value="BACKGROUND"
+              />
+              <label
+                class="ml-3 block text-sm font-medium text-gray-700 dark:text-white cursor-pointer"
+                for="openLinks_background"
+              >
+                Background
+              </label>
+            </div>
+          </div>
+        </div>
+      </div>
       <div
         class="mt-3 mb-2 text-sm"
       >
diff --git a/src/utils/comms.test.ts b/src/utils/comms.test.ts
index 45ba3024..3c7402ba 100644
--- a/src/utils/comms.test.ts
+++ b/src/utils/comms.test.ts
@@ -1,4 +1,5 @@
 import { ipcRenderer, shell } from 'electron';
+import { mockSettings } from '../__mocks__/state-mocks';
 import type { Link } from '../types';
 import {
   getAppVersion,
@@ -9,6 +10,7 @@ import {
   showWindow,
   updateTrayIcon,
 } from './comms';
+import * as storage from './storage';
 
 describe('utils/comms.ts', () => {
   beforeEach(() => {
@@ -59,9 +61,15 @@ describe('utils/comms.ts', () => {
   });
 
   it('should open an external link', () => {
+    jest
+      .spyOn(storage, 'loadState')
+      .mockReturnValue({ settings: mockSettings });
+
     openExternalLink('https://www.gitify.io/' as Link);
     expect(shell.openExternal).toHaveBeenCalledTimes(1);
-    expect(shell.openExternal).toHaveBeenCalledWith('https://www.gitify.io/');
+    expect(shell.openExternal).toHaveBeenCalledWith('https://www.gitify.io/', {
+      activate: true,
+    });
   });
 
   it('should ignore opening external local links file:///', () => {
diff --git a/src/utils/links.test.ts b/src/utils/links.test.ts
index 316fff36..ee1bb5a5 100644
--- a/src/utils/links.test.ts
+++ b/src/utils/links.test.ts
@@ -23,9 +23,9 @@ import {
 } from './links';
 
 describe('utils/links.ts', () => {
-  beforeEach(() => {
-    jest.spyOn(comms, 'openExternalLink');
-  });
+  const openExternalLinkMock = jest
+    .spyOn(comms, 'openExternalLink')
+    .mockImplementation();
 
   afterEach(() => {
     jest.clearAllMocks();
@@ -33,42 +33,42 @@ describe('utils/links.ts', () => {
 
   it('openGitifyRepository', () => {
     openGitifyRepository();
-    expect(comms.openExternalLink).toHaveBeenCalledWith(
+    expect(openExternalLinkMock).toHaveBeenCalledWith(
       'https://github.com/gitify-app/gitify',
     );
   });
 
   it('openGitifyReleaseNotes', () => {
     openGitifyReleaseNotes('v1.0.0');
-    expect(comms.openExternalLink).toHaveBeenCalledWith(
+    expect(openExternalLinkMock).toHaveBeenCalledWith(
       'https://github.com/gitify-app/gitify/releases/tag/v1.0.0',
     );
   });
 
   it('openGitHubNotifications', () => {
     openGitHubNotifications();
-    expect(comms.openExternalLink).toHaveBeenCalledWith(
+    expect(openExternalLinkMock).toHaveBeenCalledWith(
       'https://github.com/notifications',
     );
   });
 
   it('openGitHubIssues', () => {
     openGitHubIssues();
-    expect(comms.openExternalLink).toHaveBeenCalledWith(
+    expect(openExternalLinkMock).toHaveBeenCalledWith(
       'https://github.com/issues',
     );
   });
 
   it('openGitHubPulls', () => {
     openGitHubPulls();
-    expect(comms.openExternalLink).toHaveBeenCalledWith(
+    expect(openExternalLinkMock).toHaveBeenCalledWith(
       'https://github.com/pulls',
     );
   });
 
   it('openAccountProfile', () => {
     openAccountProfile(mockGitHubCloudAccount);
-    expect(comms.openExternalLink).toHaveBeenCalledWith(
+    expect(openExternalLinkMock).toHaveBeenCalledWith(
       'https://github.com/octocat',
     );
   });
@@ -76,14 +76,14 @@ describe('utils/links.ts', () => {
   it('openUserProfile', () => {
     const mockUser = partialMockUser('mock-user');
     openUserProfile(mockUser);
-    expect(comms.openExternalLink).toHaveBeenCalledWith(
+    expect(openExternalLinkMock).toHaveBeenCalledWith(
       'https://github.com/mock-user',
     );
   });
 
   it('openHost', () => {
     openHost('github.com' as Hostname);
-    expect(comms.openExternalLink).toHaveBeenCalledWith('https://github.com');
+    expect(openExternalLinkMock).toHaveBeenCalledWith('https://github.com');
   });
 
   it('openDeveloperSettings', () => {
@@ -93,7 +93,7 @@ describe('utils/links.ts', () => {
       .spyOn(authUtils, 'getDeveloperSettingsURL')
       .mockReturnValue(mockSettingsURL);
     openDeveloperSettings(mockGitHubCloudAccount);
-    expect(comms.openExternalLink).toHaveBeenCalledWith(mockSettingsURL);
+    expect(openExternalLinkMock).toHaveBeenCalledWith(mockSettingsURL);
   });
 
   it('openRepository', () => {
@@ -104,7 +104,7 @@ describe('utils/links.ts', () => {
     } as Repository;
 
     openRepository(repo);
-    expect(comms.openExternalLink).toHaveBeenCalledWith(mockHtmlUrl);
+    expect(openExternalLinkMock).toHaveBeenCalledWith(mockHtmlUrl);
   });
 
   it('openNotification', async () => {
@@ -113,12 +113,12 @@ describe('utils/links.ts', () => {
       .spyOn(helpers, 'generateGitHubWebUrl')
       .mockResolvedValue(mockNotificationUrl);
     await openNotification(mockSingleNotification);
-    expect(comms.openExternalLink).toHaveBeenCalledWith(mockNotificationUrl);
+    expect(openExternalLinkMock).toHaveBeenCalledWith(mockNotificationUrl);
   });
 
   it('openParticipatingDocs', () => {
     openGitHubParticipatingDocs();
-    expect(comms.openExternalLink).toHaveBeenCalledWith(
+    expect(openExternalLinkMock).toHaveBeenCalledWith(
       Constants.GITHUB_DOCS.PARTICIPATING_URL,
     );
   });