Repository: gitify-app/gitify
Pull request #1211 (2024-06-09 11:03:27)

refactor: consolidate open links fns


Patch:
diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx
index 7210cba7..b64da70a 100644
--- a/src/components/NotificationRow.tsx
+++ b/src/components/NotificationRow.tsx
@@ -8,29 +8,22 @@ import {
   ReadIcon,
   TagIcon,
 } from '@primer/octicons-react';
-import {
-  type FC,
-  type KeyboardEvent,
-  type MouseEvent,
-  useCallback,
-  useContext,
-} from 'react';
+import { type FC, type MouseEvent, useCallback, useContext } from 'react';
 
 import { AppContext } from '../context/App';
 import { PILL_CLASS_NAME } from '../styles/gitify';
 import { IconColor } from '../types';
 import type { Notification } from '../typesGitHub';
-import { openExternalLink } from '../utils/comms';
 import {
   formatForDisplay,
   formatNotificationUpdatedAt,
-  openInBrowser,
 } from '../utils/helpers';
 import {
   getNotificationTypeIcon,
   getNotificationTypeIconColor,
   getPullRequestReviewIcon,
 } from '../utils/icons';
+import { openNotification, openUserProfile } from '../utils/links';
 import { formatReason } from '../utils/reason';
 
 interface IProps {
@@ -49,8 +42,8 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
     notifications,
   } = useContext(AppContext);
 
-  const openNotification = useCallback(() => {
-    openInBrowser(notification);
+  const handleNotification = useCallback(() => {
+    openNotification(notification);
 
     if (settings.markAsDoneOnOpen) {
       markNotificationDone(notification.id, hostname);
@@ -67,15 +60,6 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
     unsubscribeNotification(notification.id, hostname);
   };
 
-  const openUserProfile = (
-    event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>,
-  ) => {
-    // Don't trigger onClick of parent element.
-    event.stopPropagation();
-
-    openExternalLink(notification.subject.user.html_url);
-  };
-
   const reason = formatReason(notification.reason);
   const NotificationIcon = getNotificationTypeIcon(notification.subject);
   const iconColor = getNotificationTypeIconColor(notification.subject);
@@ -116,8 +100,8 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
 
       <div
         className="flex-1 whitespace-nowrap overflow-hidden overflow-ellipsis"
-        onClick={() => openNotification()}
-        onKeyDown={() => openNotification()}
+        onClick={() => handleNotification()}
+        onKeyDown={() => handleNotification()}
       >
         <div
           className="mb-1 text-sm truncate cursor-pointer"
@@ -129,10 +113,14 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
 
         <div className="flex flex-wrap items-center text-xs text-capitalize gap-1">
           {notification.subject.user ? (
-            <div
+            <button
+              type="button"
               title="View User Profile"
-              onClick={openUserProfile}
-              onKeyDown={openUserProfile}
+              onClick={(event: MouseEvent<HTMLElement>) => {
+                // Don't trigger onClick of parent element.
+                event.stopPropagation();
+                openUserProfile(notification.subject.user);
+              }}
               className="flex-shrink-0"
             >
               <img
@@ -141,7 +129,7 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
                 title={notification.subject.user.login}
                 alt={`${notification.subject.user.login}'s avatar`}
               />
-            </div>
+            </button>
           ) : (
             <div>
               <FeedPersonIcon
diff --git a/src/components/Repository.tsx b/src/components/Repository.tsx
index fc66fa33..d55817fd 100644
--- a/src/components/Repository.tsx
+++ b/src/components/Repository.tsx
@@ -3,7 +3,7 @@ import { type FC, useCallback, useContext } from 'react';
 import { CSSTransition, TransitionGroup } from 'react-transition-group';
 import { AppContext } from '../context/App';
 import type { Notification } from '../typesGitHub';
-import { openExternalLink } from '../utils/comms';
+import { openRepository } from '../utils/links';
 import { NotificationRow } from './NotificationRow';
 
 interface IProps {
@@ -20,11 +20,6 @@ export const RepositoryNotifications: FC<IProps> = ({
   const { markRepoNotificationsRead, markRepoNotificationsDone } =
     useContext(AppContext);
 
-  const openBrowser = useCallback(() => {
-    const url = repoNotifications[0].repository.html_url;
-    openExternalLink(url);
-  }, [repoNotifications]);
-
   const markRepoAsRead = useCallback(() => {
     const repoSlug = repoNotifications[0].repository.full_name;
     markRepoNotificationsRead(repoSlug, hostname);
@@ -53,8 +48,8 @@ export const RepositoryNotifications: FC<IProps> = ({
           )}
           <span
             className="cursor-pointer truncate"
-            onClick={openBrowser}
-            onKeyDown={openBrowser}
+            onClick={() => openRepository(repoNotifications[0].repository)}
+            onKeyDown={() => openRepository(repoNotifications[0].repository)}
           >
             {repoName}
           </span>
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index 05a156aa..0d153ac1 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -4,13 +4,13 @@ import {
   SyncIcon,
   XCircleIcon,
 } from '@primer/octicons-react';
-import { type FC, useCallback, useContext, useMemo } from 'react';
+import { type FC, useContext, useMemo } from 'react';
 import { useLocation, useNavigate } from 'react-router-dom';
 import { Logo } from '../components/Logo';
 import { AppContext } from '../context/App';
 import { BUTTON_SIDEBAR_CLASS_NAME } from '../styles/gitify';
-import { openExternalLink, quitApp } from '../utils/comms';
-import { Constants } from '../utils/constants';
+import { quitApp } from '../utils/comms';
+import { openGitHubNotifications, openGitifyRepository } from '../utils/links';
 import { getNotificationCount } from '../utils/notifications';
 
 export const Sidebar: FC = () => {
@@ -20,14 +20,6 @@ export const Sidebar: FC = () => {
   const { notifications, fetchNotifications, isLoggedIn, status } =
     useContext(AppContext);
 
-  const onOpenBrowser = useCallback(() => {
-    openExternalLink(`https://github.com/${Constants.REPO_SLUG}`);
-  }, []);
-
-  const onOpenGitHubNotifications = useCallback(() => {
-    openExternalLink('https://github.com/notifications');
-  }, []);
-
   const toggleSettings = () => {
     if (location.pathname.startsWith('/settings')) {
       navigate('/', { replace: true });
@@ -47,7 +39,7 @@ export const Sidebar: FC = () => {
           type="button"
           className="w-5 my-3 mx-auto cursor-pointer outline-none"
           title="Open Gitify on GitHub"
-          onClick={onOpenBrowser}
+          onClick={() => openGitifyRepository()}
           data-testid="gitify-logo"
         >
           <Logo aria-label="Open Gitify" />
@@ -58,7 +50,7 @@ export const Sidebar: FC = () => {
           className={`flex justify-around self-stretch items-center my-1 py-1 px-2 text-xs font-extrabold cursor-pointer ${
             notificationsCount > 0 ? 'text-green-500' : 'text-white'
           }`}
-          onClick={onOpenGitHubNotifications}
+          onClick={() => openGitHubNotifications()}
           title={`${notificationsCount} Unread Notifications`}
         >
           <BellIcon
diff --git a/src/routes/Accounts.tsx b/src/routes/Accounts.tsx
index 7ed9a867..c89870fe 100644
--- a/src/routes/Accounts.tsx
+++ b/src/routes/Accounts.tsx
@@ -15,16 +15,17 @@ import { AuthMethodIcon } from '../components/icons/AuthMethodIcon';
 import { PlatformIcon } from '../components/icons/PlatformIcon';
 import { BUTTON_CLASS_NAME } from '../styles/gitify';
 import type { Account } from '../types';
-import { getAccountUUID, getDeveloperSettingsURL } from '../utils/auth/utils';
-import {
-  openExternalLink,
-  updateTrayIcon,
-  updateTrayTitle,
-} from '../utils/comms';
+import { getAccountUUID } from '../utils/auth/utils';
+import { updateTrayIcon, updateTrayTitle } from '../utils/comms';
 import {
   isOAuthAppLoggedIn,
   isPersonalAccessTokenLoggedIn,
 } from '../utils/helpers';
+import {
+  openAccountProfile,
+  openDeveloperSettings,
+  openHost,
+} from '../utils/links';
 
 export const AccountsRoute: FC = () => {
   const { auth, logoutFromAccount } = useContext(AppContext);
@@ -37,21 +38,6 @@ export const AccountsRoute: FC = () => {
     updateTrayTitle();
   }, []);
 
-  const openProfile = (account: Account) => {
-    const url = new URL(`https://${account.hostname}`);
-    url.pathname = account.user.login;
-    openExternalLink(url.toString());
-  };
-
-  const openHost = (hostname: string) => {
-    openExternalLink(`https://${hostname}`);
-  };
-
-  const openDeveloperSettings = (account: Account) => {
-    const url = getDeveloperSettingsURL(account);
-    openExternalLink(url);
-  };
-
   const loginWithPersonalAccessToken = useCallback(() => {
     return navigate('/login-personal-access-token', { replace: true });
   }, []);
@@ -95,7 +81,7 @@ export const AccountsRoute: FC = () => {
                     type="button"
                     className="cursor-pointer font-semibold mb-1 text-sm"
                     title="Open Profile"
-                    onClick={() => openProfile(account)}
+                    onClick={() => openAccountProfile(account)}
                   >
                     @{account.user.login}
                     <span
diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx
index c55271c0..8266860e 100644
--- a/src/routes/Settings.tsx
+++ b/src/routes/Settings.tsx
@@ -12,7 +12,6 @@ import { ipcRenderer } from 'electron';
 import {
   type FC,
   type MouseEvent,
-  useCallback,
   useContext,
   useEffect,
   useState,
@@ -23,8 +22,11 @@ import { RadioGroup } from '../components/fields/RadioGroup';
 import { AppContext } from '../context/App';
 import { BUTTON_CLASS_NAME } from '../styles/gitify';
 import { Theme } from '../types';
-import { getAppVersion, openExternalLink, quitApp } from '../utils/comms';
-import Constants from '../utils/constants';
+import { getAppVersion, quitApp } from '../utils/comms';
+import {
+  openGitHubParticipatingDocs,
+  openGitifyReleaseNotes,
+} from '../utils/links';
 import { isLinux, isMacOS } from '../utils/platform';
 import { setTheme } from '../utils/theme';
 
@@ -34,21 +36,6 @@ export const SettingsRoute: FC = () => {
 
   const [appVersion, setAppVersion] = useState<string | null>(null);
 
-  const openGitHubReleaseNotes = useCallback((version) => {
-    openExternalLink(
-      `https://github.com/${Constants.REPO_SLUG}/releases/tag/v${version}`,
-    );
-  }, []);
-
-  const openGitHubParticipatingDocs = (event: MouseEvent<HTMLElement>) => {
-    // Don't trigger onClick of parent element.
-    event.stopPropagation();
-
-    openExternalLink(
-      'https://docs.github.com/en/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/configuring-notifications#about-participating-and-watching-notifications',
-    );
-  };
-
   useEffect(() => {
     (async () => {
       const result = await getAppVersion();
@@ -186,7 +173,11 @@ export const SettingsRoute: FC = () => {
                   type="button"
                   className="text-blue-500 mx-1"
                   title="Open GitHub documentation for participating and watching notifications"
-                  onClick={openGitHubParticipatingDocs}
+                  onClick={(event: MouseEvent<HTMLElement>) => {
+                    // Don't trigger onClick of parent element.
+                    event.stopPropagation();
+                    openGitHubParticipatingDocs();
+                  }}
                 >
                   official docs
                 </button>
@@ -290,7 +281,7 @@ export const SettingsRoute: FC = () => {
           type="button"
           className="font-semibold cursor-pointer"
           title="View release notes"
-          onClick={() => openGitHubReleaseNotes(appVersion)}
+          onClick={() => openGitifyReleaseNotes(appVersion)}
         >
           Gitify v{appVersion}
         </button>
diff --git a/src/utils/auth/utils.ts b/src/utils/auth/utils.ts
index f4ab7fe4..a6c3f212 100644
--- a/src/utils/auth/utils.ts
+++ b/src/utils/auth/utils.ts
@@ -149,7 +149,7 @@ export function getDeveloperSettingsURL(account: Account): string {
       settingsURL.pathname = '/settings/developers';
       break;
     case 'Personal Access Token':
-      settingsURL.pathname = 'settings/tokens';
+      settingsURL.pathname = '/settings/tokens';
       break;
   }
   return settingsURL.toString();
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index e03028b2..9653b7d8 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -28,6 +28,8 @@ export const Constants = {
       'https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authenticating-to-the-rest-api-with-an-oauth-app',
     PAT_URL:
       'https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens',
+    PARTICIPATING_URL:
+      'https://docs.github.com/en/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/configuring-notifications#about-participating-and-watching-notifications',
   },
 };
 
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
index a69d121b..51c054c4 100644
--- a/src/utils/helpers.ts
+++ b/src/utils/helpers.ts
@@ -1,7 +1,6 @@
 import { formatDistanceToNowStrict, parseISO } from 'date-fns';
 import type { Account, AuthState } from '../types';
 import type { Notification } from '../typesGitHub';
-import { openExternalLink } from '../utils/comms';
 import { getHtmlUrl, getLatestDiscussion } from './api/client';
 import type { PlatformType } from './auth/types';
 import { Constants } from './constants';
@@ -183,9 +182,3 @@ export function formatNotificationUpdatedAt(
 
   return '';
 }
-
-export async function openInBrowser(notification: Notification) {
-  const url = await generateGitHubWebUrl(notification);
-
-  openExternalLink(url);
-}
diff --git a/src/utils/links.ts b/src/utils/links.ts
new file mode 100644
index 00000000..69cf8f8f
--- /dev/null
+++ b/src/utils/links.ts
@@ -0,0 +1,52 @@
+import type { Account } from '../types';
+import type { Notification, Repository, SubjectUser } from '../typesGitHub';
+import { getDeveloperSettingsURL } from './auth/utils';
+import { openExternalLink } from './comms';
+import Constants from './constants';
+import { generateGitHubWebUrl } from './helpers';
+
+export function openGitifyRepository() {
+  openExternalLink(`https://github.com/${Constants.REPO_SLUG}`);
+}
+
+export function openGitifyReleaseNotes(version: string) {
+  openExternalLink(
+    `https://github.com/${Constants.REPO_SLUG}/releases/tag/v${version}`,
+  );
+}
+
+export function openGitHubNotifications() {
+  openExternalLink('https://github.com/notifications');
+}
+
+export function openAccountProfile(account: Account) {
+  const url = new URL(`https://${account.hostname}`);
+  url.pathname = account.user.login;
+  openExternalLink(url.toString());
+}
+
+export function openUserProfile(user: SubjectUser) {
+  openExternalLink(user.html_url);
+}
+
+export function openHost(hostname: string) {
+  openExternalLink(`https://${hostname}`);
+}
+
+export function openDeveloperSettings(account: Account) {
+  const url = getDeveloperSettingsURL(account);
+  openExternalLink(url);
+}
+
+export function openRepository(repository: Repository) {
+  openExternalLink(repository.html_url);
+}
+
+export async function openNotification(notification: Notification) {
+  const url = await generateGitHubWebUrl(notification);
+  openExternalLink(url);
+}
+
+export function openGitHubParticipatingDocs() {
+  openExternalLink(Constants.GITHUB_DOCS.PARTICIPATING_URL);
+}
diff --git a/src/utils/notifications.ts b/src/utils/notifications.ts
index 2c5f2f49..6a9b6328 100644
--- a/src/utils/notifications.ts
+++ b/src/utils/notifications.ts
@@ -4,9 +4,9 @@ import type {
   SettingsState,
 } from '../types';
 import { Notification } from '../typesGitHub';
-import { openInBrowser } from '../utils/helpers';
 import { listNotificationsForAuthenticatedUser } from './api/client';
 import { hideWindow, showWindow, updateTrayIcon } from './comms';
+import { openNotification } from './links';
 import { isWindows } from './platform';
 import { getGitifySubjectDetails } from './subject';
 
@@ -91,7 +91,7 @@ export const raiseNativeNotification = (notifications: Notification[]) => {
   nativeNotification.onclick = () => {
     if (notifications.length === 1) {
       hideWindow();
-      openInBrowser(notifications[0]);
+      openNotification(notifications[0]);
     } else {
       showWindow();
     }


Test patch:
diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx
index e6f103df..a2a78392 100644
--- a/src/components/NotificationRow.test.tsx
+++ b/src/components/NotificationRow.test.tsx
@@ -4,12 +4,12 @@ import { AppContext } from '../context/App';
 import type { Milestone, UserType } from '../typesGitHub';
 import { mockSingleNotification } from '../utils/api/__mocks__/response-mocks';
 import * as comms from '../utils/comms';
-import * as helpers from '../utils/helpers';
+import * as links from '../utils/links';
 import { NotificationRow } from './NotificationRow';
 
 describe('components/NotificationRow.tsx', () => {
   beforeEach(() => {
-    jest.spyOn(helpers, 'openInBrowser');
+    jest.spyOn(links, 'openNotification');
   });
 
   afterEach(() => {
@@ -341,7 +341,7 @@ describe('components/NotificationRow.tsx', () => {
       );
 
       fireEvent.click(screen.getByRole('main'));
-      expect(helpers.openInBrowser).toHaveBeenCalledTimes(1);
+      expect(links.openNotification).toHaveBeenCalledTimes(1);
       expect(removeNotificationFromState).toHaveBeenCalledTimes(1);
     });
 
@@ -366,7 +366,7 @@ describe('components/NotificationRow.tsx', () => {
       );
 
       fireEvent.keyDown(screen.getByRole('main'));
-      expect(helpers.openInBrowser).toHaveBeenCalledTimes(1);
+      expect(links.openNotification).toHaveBeenCalledTimes(1);
       expect(removeNotificationFromState).toHaveBeenCalledTimes(1);
     });
 
@@ -391,7 +391,7 @@ describe('components/NotificationRow.tsx', () => {
       );
 
       fireEvent.click(screen.getByRole('main'));
-      expect(helpers.openInBrowser).toHaveBeenCalledTimes(1);
+      expect(links.openNotification).toHaveBeenCalledTimes(1);
       expect(markNotificationDone).toHaveBeenCalledTimes(1);
     });
 
diff --git a/src/components/__snapshots__/NotificationRow.test.tsx.snap b/src/components/__snapshots__/NotificationRow.test.tsx.snap
index d8901bfc..2a20cdc1 100644
--- a/src/components/__snapshots__/NotificationRow.test.tsx.snap
+++ b/src/components/__snapshots__/NotificationRow.test.tsx.snap
@@ -4852,9 +4852,10 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
           <div
             class="flex flex-wrap items-center text-xs text-capitalize gap-1"
           >
-            <div
+            <button
               class="flex-shrink-0"
               title="View User Profile"
+              type="button"
             >
               <img
                 alt="gitify-app's avatar"
@@ -4862,7 +4863,7 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
                 src="https://avatars.githubusercontent.com/u/133795385?s=200&v=4"
                 title="gitify-app"
               />
-            </div>
+            </button>
             <div
               title="You're watching the repository."
             >
@@ -5038,9 +5039,10 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
         <div
           class="flex flex-wrap items-center text-xs text-capitalize gap-1"
         >
-          <div
+          <button
             class="flex-shrink-0"
             title="View User Profile"
+            type="button"
           >
             <img
               alt="gitify-app's avatar"
@@ -5048,7 +5050,7 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
               src="https://avatars.githubusercontent.com/u/133795385?s=200&v=4"
               title="gitify-app"
             />
-          </div>
+          </button>
           <div
             title="You're watching the repository."
           >
@@ -5281,9 +5283,10 @@ exports[`components/NotificationRow.tsx should render itself & its children when
           <div
             class="flex flex-wrap items-center text-xs text-capitalize gap-1"
           >
-            <div
+            <button
               class="flex-shrink-0"
               title="View User Profile"
+              type="button"
             >
               <img
                 alt="gitify-app's avatar"
@@ -5291,7 +5294,7 @@ exports[`components/NotificationRow.tsx should render itself & its children when
                 src="https://avatars.githubusercontent.com/u/133795385?s=200&v=4"
                 title="gitify-app"
               />
-            </div>
+            </button>
             <div
               title="You're watching the repository."
             >
@@ -5467,9 +5470,10 @@ exports[`components/NotificationRow.tsx should render itself & its children when
         <div
           class="flex flex-wrap items-center text-xs text-capitalize gap-1"
         >
-          <div
+          <button
             class="flex-shrink-0"
             title="View User Profile"
+            type="button"
           >
             <img
               alt="gitify-app's avatar"
@@ -5477,7 +5481,7 @@ exports[`components/NotificationRow.tsx should render itself & its children when
               src="https://avatars.githubusercontent.com/u/133795385?s=200&v=4"
               title="gitify-app"
             />
-          </div>
+          </button>
           <div
             title="You're watching the repository."
           >
diff --git a/src/utils/links.test.ts b/src/utils/links.test.ts
new file mode 100644
index 00000000..1a5279b8
--- /dev/null
+++ b/src/utils/links.test.ts
@@ -0,0 +1,108 @@
+import { partialMockUser } from '../__mocks__/partial-mocks';
+import { mockGitHubCloudAccount } from '../__mocks__/state-mocks';
+import type { Repository } from '../typesGitHub';
+import { mockSingleNotification } from './api/__mocks__/response-mocks';
+import * as authUtils from './auth/utils';
+import * as comms from './comms';
+import Constants from './constants';
+import * as helpers from './helpers';
+import {
+  openAccountProfile,
+  openDeveloperSettings,
+  openGitHubNotifications,
+  openGitHubParticipatingDocs,
+  openGitifyReleaseNotes,
+  openGitifyRepository,
+  openHost,
+  openNotification,
+  openRepository,
+  openUserProfile,
+} from './links';
+
+describe('utils/links.ts', () => {
+  beforeEach(() => {
+    jest.spyOn(comms, 'openExternalLink');
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('openGitifyRepository', () => {
+    openGitifyRepository();
+    expect(comms.openExternalLink).toHaveBeenCalledWith(
+      'https://github.com/gitify-app/gitify',
+    );
+  });
+
+  it('openGitifyReleaseNotes', () => {
+    openGitifyReleaseNotes('1.0.0');
+    expect(comms.openExternalLink).toHaveBeenCalledWith(
+      'https://github.com/gitify-app/gitify/releases/tag/v1.0.0',
+    );
+  });
+
+  it('openGitHubNotifications', () => {
+    openGitHubNotifications();
+    expect(comms.openExternalLink).toHaveBeenCalledWith(
+      'https://github.com/notifications',
+    );
+  });
+
+  it('openAccountProfile', () => {
+    openAccountProfile(mockGitHubCloudAccount);
+    expect(comms.openExternalLink).toHaveBeenCalledWith(
+      'https://github.com/octocat',
+    );
+  });
+
+  it('openUserProfile', () => {
+    const mockUser = partialMockUser('mock-user');
+    openUserProfile(mockUser);
+    expect(comms.openExternalLink).toHaveBeenCalledWith(
+      'https://github.com/mock-user',
+    );
+  });
+
+  it('openHost', () => {
+    openHost('github.com');
+    expect(comms.openExternalLink).toHaveBeenCalledWith('https://github.com');
+  });
+
+  it('openDeveloperSettings', () => {
+    const mockSettingsURL = 'https://github.com/settings/tokens';
+
+    jest
+      .spyOn(authUtils, 'getDeveloperSettingsURL')
+      .mockReturnValue(mockSettingsURL);
+    openDeveloperSettings(mockGitHubCloudAccount);
+    expect(comms.openExternalLink).toHaveBeenCalledWith(mockSettingsURL);
+  });
+
+  it('openRepository', () => {
+    const mockHtmlUrl = 'https://github.com/gitify-app/gitify';
+
+    const repo = {
+      html_url: mockHtmlUrl,
+    } as Repository;
+
+    openRepository(repo);
+    expect(comms.openExternalLink).toHaveBeenCalledWith(mockHtmlUrl);
+  });
+
+  it('openNotification', async () => {
+    const mockNotificationUrl = mockSingleNotification.repository.html_url;
+    jest
+      .spyOn(helpers, 'generateGitHubWebUrl')
+      .mockResolvedValue(mockNotificationUrl);
+    await openNotification(mockSingleNotification);
+    expect(comms.openExternalLink).toHaveBeenCalledWith(mockNotificationUrl);
+  });
+
+  it('openParticipatingDocs', () => {
+    openGitHubParticipatingDocs();
+    expect(comms.openExternalLink).toHaveBeenCalledWith(
+      Constants.GITHUB_DOCS.PARTICIPATING_URL,
+    );
+  });
+});
diff --git a/src/utils/notifications.test.ts b/src/utils/notifications.test.ts
index 2979ce4c..580fbb06 100644
--- a/src/utils/notifications.test.ts
+++ b/src/utils/notifications.test.ts
@@ -8,7 +8,7 @@ import { defaultSettings } from '../context/App';
 import type { SettingsState } from '../types';
 import { mockGitHubNotifications } from './api/__mocks__/response-mocks';
 import * as comms from './comms';
-import * as helpers from './helpers';
+import * as links from './links';
 import * as notificationsHelpers from './notifications';
 import { filterNotifications } from './notifications';
 
@@ -112,7 +112,7 @@ describe('utils/notifications.ts', () => {
 
   it('should click on a native notification (with 1 notification)', () => {
     const hideWindowMock = jest.spyOn(comms, 'hideWindow');
-    jest.spyOn(helpers, 'openInBrowser');
+    jest.spyOn(links, 'openNotification');
 
     const nativeNotification: Notification =
       notificationsHelpers.raiseNativeNotification([
@@ -120,8 +120,8 @@ describe('utils/notifications.ts', () => {
       ]);
     nativeNotification.onclick(null);
 
-    expect(helpers.openInBrowser).toHaveBeenCalledTimes(1);
-    expect(helpers.openInBrowser).toHaveBeenLastCalledWith(
+    expect(links.openNotification).toHaveBeenCalledTimes(1);
+    expect(links.openNotification).toHaveBeenLastCalledWith(
       mockGitHubNotifications[0],
     );
     expect(hideWindowMock).toHaveBeenCalledTimes(1);