Repository: gitify-app/gitify
Pull request #649 (2023-10-03 11:35:44)

chore(electron): Move off deprecated remote module
This is [a requirement for electron v14, which drops the module](https://www.electronjs.org/blog/electron-14-0/#removed-remote-module). We can do it before the upgrade, for smaller changes.


bmulholland: Current dependencies on/for this PR:
* main
  * **PR #613** <a href="https://app.graphite.dev/github/pr/gitify-app/gitify/613" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 
    * **PR #649** <a href="https://app.graphite.dev/github/pr/gitify-app/gitify/649" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>  👈

This comment was auto-generated by [Graphite](https://app.graphite.dev/github/pr/gitify-app/gitify/649?utm_source=stack-comment).
bmulholland: This introduces a new issue, probably on a check for isLinux: `[79860:1003/134934.778674:INFO:CONSOLE(2)] "Uncaught TypeError: Cannot read property 'process' of undefined"`
bmulholland: Fixed

Patch:
diff --git a/main.js b/main.js
index 9f03f89a..8de0d190 100644
--- a/main.js
+++ b/main.js
@@ -4,6 +4,8 @@ const { autoUpdater } = require('electron-updater');
 const { onFirstRunMaybe } = require('./first-run');
 const path = require('path');
 
+require('@electron/remote/main').initialize()
+
 app.setAppUserModelId('com.electron.gitify');
 
 const iconIdle = path.join(
@@ -75,6 +77,13 @@ menubarApp.on('ready', () => {
       }
     }
   });
+  ipcMain.handle('get-platform', async () => {
+    return process.platform;
+  });
+
+  ipcMain.handle('get-app-version', async () => {
+    return app.getVersion();
+  });
 
   menubarApp.window.webContents.on('devtools-opened', () => {
     menubarApp.window.setSize(800, 600);
diff --git a/package.json b/package.json
index 9e85b0cd..5b45f444 100644
--- a/package.json
+++ b/package.json
@@ -93,6 +93,7 @@
     "afterSign": "scripts/notarize.js"
   },
   "dependencies": {
+    "@electron/remote": "^2.0.11",
     "@primer/octicons-react": "19.8.0",
     "axios": "1.5.1",
     "date-fns": "2.30.0",
@@ -109,7 +110,7 @@
     "react-router": "6.16.0",
     "react-router-dom": "6.16.0",
     "react-transition-group": "4.4.5",
-    "ts-loader": "9.4.4",
+    "ts-loader": "9.5.0",
     "typescript": "5.2.2"
   },
   "devDependencies": {
@@ -118,7 +119,7 @@
     "@types/jest": "29.5.5",
     "@types/lodash": "4.14.199",
     "@types/node": "18.18.0",
-    "@types/react": "18.2.24",
+    "@types/react": "18.2.25",
     "@types/react-router-dom": "5.3.3",
     "@types/react-transition-group": "4.4.7",
     "autoprefixer": "10.4.16",
@@ -141,5 +142,5 @@
     "webpack-cli": "5.1.4",
     "webpack-merge": "5.9.0"
   },
-  "packageManager": "pnpm@8.8.0"
+  "packageManager": "pnpm@8.9.0"
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bcbe8673..215dc35c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,6 +5,9 @@ settings:
   excludeLinksFromLockfile: false
 
 dependencies:
+  '@electron/remote':
+    specifier: ^2.0.11
+    version: 2.0.11(electron@13.1.7)
   '@primer/octicons-react':
     specifier: 19.8.0
     version: 19.8.0(react@18.2.0)
@@ -54,8 +57,8 @@ dependencies:
     specifier: 4.4.5
     version: 4.4.5(react-dom@18.2.0)(react@18.2.0)
   ts-loader:
-    specifier: 9.4.4
-    version: 9.4.4(typescript@5.2.2)(webpack@5.88.2)
+    specifier: 9.5.0
+    version: 9.5.0(typescript@5.2.2)(webpack@5.88.2)
   typescript:
     specifier: 5.2.2
     version: 5.2.2
@@ -66,7 +69,7 @@ devDependencies:
     version: 14.0.0(react-dom@18.2.0)(react@18.2.0)
   '@testing-library/react-hooks':
     specifier: 8.0.1
-    version: 8.0.1(@types/react@18.2.24)(react-dom@18.2.0)(react-test-renderer@18.2.0)(react@18.2.0)
+    version: 8.0.1(@types/react@18.2.25)(react-dom@18.2.0)(react-test-renderer@18.2.0)(react@18.2.0)
   '@types/jest':
     specifier: 29.5.5
     version: 29.5.5
@@ -77,8 +80,8 @@ devDependencies:
     specifier: 18.18.0
     version: 18.18.0
   '@types/react':
-    specifier: 18.2.24
-    version: 18.2.24
+    specifier: 18.2.25
+    version: 18.2.25
   '@types/react-router-dom':
     specifier: 5.3.3
     version: 5.3.3
@@ -566,6 +569,14 @@ packages:
       - supports-color
     dev: true
 
+  /@electron/remote@2.0.11(electron@13.1.7):
+    resolution: {integrity: sha512-PYEs7W3GrQNuhgiMHjFEvL5MbAL6C7m1AwSAHGqC+xc33IdP7rcGtJSdTP2eg1ssyB3oI00KwTsiSlsQbAoXpA==}
+    peerDependencies:
+      electron: '>= 13.0.0'
+    dependencies:
+      electron: 13.1.7
+    dev: false
+
   /@electron/universal@1.4.1:
     resolution: {integrity: sha512-lE/U3UNw1YHuowNbTmKNs9UlS3En3cPgwM5MI+agIgr/B1hSze9NdOP0qn7boZaI9Lph8IDv3/24g9IxnJP7aQ==}
     engines: {node: '>=8.6'}
@@ -939,7 +950,7 @@ packages:
       pretty-format: 27.5.1
     dev: true
 
-  /@testing-library/react-hooks@8.0.1(@types/react@18.2.24)(react-dom@18.2.0)(react-test-renderer@18.2.0)(react@18.2.0):
+  /@testing-library/react-hooks@8.0.1(@types/react@18.2.25)(react-dom@18.2.0)(react-test-renderer@18.2.0)(react@18.2.0):
     resolution: {integrity: sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==}
     engines: {node: '>=12'}
     peerDependencies:
@@ -956,7 +967,7 @@ packages:
         optional: true
     dependencies:
       '@babel/runtime': 7.21.5
-      '@types/react': 18.2.24
+      '@types/react': 18.2.25
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       react-error-boundary: 3.1.4(react@18.2.0)
@@ -1121,14 +1132,14 @@ packages:
   /@types/react-dom@18.2.8:
     resolution: {integrity: sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==}
     dependencies:
-      '@types/react': 18.2.24
+      '@types/react': 18.2.25
     dev: true
 
   /@types/react-router-dom@5.3.3:
     resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==}
     dependencies:
       '@types/history': 4.7.11
-      '@types/react': 18.2.24
+      '@types/react': 18.2.25
       '@types/react-router': 5.1.20
     dev: true
 
@@ -1136,17 +1147,17 @@ packages:
     resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
     dependencies:
       '@types/history': 4.7.11
-      '@types/react': 18.2.24
+      '@types/react': 18.2.25
     dev: true
 
   /@types/react-transition-group@4.4.7:
     resolution: {integrity: sha512-ICCyBl5mvyqYp8Qeq9B5G/fyBSRC0zx3XM3sCC6KkcMsNeAHqXBKkmat4GqdJET5jtYUpZXrxI5flve5qhi2Eg==}
     dependencies:
-      '@types/react': 18.2.24
+      '@types/react': 18.2.25
     dev: true
 
-  /@types/react@18.2.24:
-    resolution: {integrity: sha512-Ee0Jt4sbJxMu1iDcetZEIKQr99J1Zfb6D4F3qfUWoR1JpInkY1Wdg4WwCyBjL257D0+jGqSl1twBjV8iCaC0Aw==}
+  /@types/react@18.2.25:
+    resolution: {integrity: sha512-24xqse6+VByVLIr+xWaQ9muX1B4bXJKXBbjszbld/UEDslGLY53+ZucF44HCmLbMPejTzGG9XgR+3m2/Wqu1kw==}
     dependencies:
       '@types/prop-types': 15.7.5
       '@types/scheduler': 0.16.4
@@ -2421,14 +2432,6 @@ packages:
     dependencies:
       once: 1.4.0
 
-  /enhanced-resolve@5.14.0:
-    resolution: {integrity: sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==}
-    engines: {node: '>=10.13.0'}
-    dependencies:
-      graceful-fs: 4.2.11
-      tapable: 2.2.1
-    dev: false
-
   /enhanced-resolve@5.15.0:
     resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==}
     engines: {node: '>=10.13.0'}
@@ -5130,6 +5133,11 @@ packages:
     resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
     engines: {node: '>=0.10.0'}
 
+  /source-map@0.7.4:
+    resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
+    engines: {node: '>= 8'}
+    dev: false
+
   /sprintf-js@1.0.3:
     resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
     dev: true
@@ -5498,17 +5506,18 @@ packages:
       yargs-parser: 21.1.1
     dev: true
 
-  /ts-loader@9.4.4(typescript@5.2.2)(webpack@5.88.2):
-    resolution: {integrity: sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==}
+  /ts-loader@9.5.0(typescript@5.2.2)(webpack@5.88.2):
+    resolution: {integrity: sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==}
     engines: {node: '>=12.0.0'}
     peerDependencies:
       typescript: '*'
       webpack: ^5.0.0
     dependencies:
       chalk: 4.1.2
-      enhanced-resolve: 5.14.0
+      enhanced-resolve: 5.15.0
       micromatch: 4.0.5
       semver: 7.5.4
+      source-map: 0.7.4
       typescript: 5.2.2
       webpack: 5.88.2(webpack-cli@5.1.4)
     dev: false
diff --git a/src/__mocks__/@electron/remote.js b/src/__mocks__/@electron/remote.js
new file mode 100644
index 00000000..5f7c6164
--- /dev/null
+++ b/src/__mocks__/@electron/remote.js
@@ -0,0 +1,38 @@
+let instance;
+
+class BrowserWindow {
+  constructor() {
+    if (!instance) {
+      instance = this;
+    }
+    return instance;
+  }
+  loadURL = jest.fn();
+  webContents = {
+    on: () => {},
+    session: {
+      clearStorageData: jest.fn(),
+    },
+  };
+  on() {}
+  close = jest.fn();
+  hide = jest.fn();
+  destroy = jest.fn();
+}
+
+const dialog = {
+  showErrorBox: jest.fn(),
+};
+
+module.exports = {
+  BrowserWindow: BrowserWindow,
+  dialog: dialog,
+  process: {
+    platform: 'darwin',
+  },
+  app: {
+    getLoginItemSettings: jest.fn(),
+    setLoginItemSettings: () => {},
+  },
+  getCurrentWindow: jest.fn(() => instance || new BrowserWindow()),
+};
diff --git a/src/__mocks__/electron.js b/src/__mocks__/electron.js
index 83fd9ad4..25a226c1 100644
--- a/src/__mocks__/electron.js
+++ b/src/__mocks__/electron.js
@@ -27,49 +27,21 @@ window.localStorage = {
 
 window.alert = jest.fn();
 
-let instance;
-
-class BrowserWindow {
-  constructor() {
-    if (!instance) {
-      instance = this;
-    }
-    return instance;
-  }
-  loadURL = jest.fn();
-  webContents = {
-    on: () => {},
-    session: {
-      clearStorageData: jest.fn(),
-    },
-  };
-  on() {}
-  close = jest.fn();
-  hide = jest.fn();
-  destroy = jest.fn();
-}
-
-const dialog = {
-  showErrorBox: jest.fn(),
-};
-
 module.exports = {
-  remote: {
-    BrowserWindow: BrowserWindow,
-    dialog: dialog,
-    process: {
-      platform: 'darwin',
-    },
-    app: {
-      getVersion: () => '0.0.1',
-      getLoginItemSettings: jest.fn(),
-      setLoginItemSettings: () => {},
-    },
-    getCurrentWindow: jest.fn(() => instance || new BrowserWindow()),
-  },
   ipcRenderer: {
     send: jest.fn(),
     on: jest.fn(),
+    sendSync: jest.fn(),
+    invoke: jest.fn((channel, ...args) => {
+      switch (channel) {
+        case 'get-platform':
+          return Promise.resolve('darwin');
+        case 'get-app-version':
+          return Promise.resolve('0.0.1');
+        default:
+          return Promise.reject(new Error(`Unknown channel: ${channel}`));
+      }
+    }),
   },
   shell: {
     openExternal: jest.fn(),
diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx
index a7b929fb..f04da658 100644
--- a/src/routes/Settings.tsx
+++ b/src/routes/Settings.tsx
@@ -1,5 +1,5 @@
-import React, { useCallback, useContext } from 'react';
-import { ipcRenderer, remote } from 'electron';
+import React, { useCallback, useContext, useState, useEffect } from 'react';
+import { ipcRenderer } from 'electron';
 import { useNavigate } from 'react-router-dom';
 import { ArrowLeftIcon } from '@primer/octicons-react';
 
@@ -13,12 +13,23 @@ import { IconQuit } from '../icons/Quit';
 import { updateTrayIcon } from '../utils/comms';
 import { setAppearance } from '../utils/appearance';
 
-const isLinux = remote.process.platform === 'linux';
-
 export const SettingsRoute: React.FC = () => {
   const { settings, updateSetting, logout } = useContext(AppContext);
   const navigate = useNavigate();
 
+  const [isLinux, setIsLinux] = useState<boolean>(false);
+  const [appVersion, setAppVersion] = useState<string | null>(null);
+
+  useEffect(() => {
+    ipcRenderer.invoke('get-platform').then((result: string) => {
+      setIsLinux(result === 'linux');
+    });
+
+    ipcRenderer.invoke('get-app-version').then((result: string) => {
+      setAppVersion(result);
+    });
+  }, []);
+
   ipcRenderer.on('update-native-theme', (_, updatedAppearance: Appearance) => {
     if (settings.appearance === Appearance.SYSTEM) {
       setAppearance(updatedAppearance);
@@ -116,9 +127,7 @@ export const SettingsRoute: React.FC = () => {
       </div>
 
       <div className="flex justify-between items-center bg-gray-200 dark:bg-gray-darker py-4 px-8">
-        <small className="font-semibold">
-          Gitify v{remote.app.getVersion()}
-        </small>
+        <small className="font-semibold">Gitify v{appVersion}</small>
 
         <div>
           <button
diff --git a/src/utils/auth.ts b/src/utils/auth.ts
index 991b19f5..a5d3d405 100644
--- a/src/utils/auth.ts
+++ b/src/utils/auth.ts
@@ -1,7 +1,6 @@
 import { generateGitHubAPIUrl } from './helpers';
 
-const { remote } = require('electron');
-const BrowserWindow = remote.BrowserWindow;
+const { BrowserWindow } = require('@electron/remote');
 
 import { apiRequest, apiRequestAuth } from '../utils/api-requests';
 import { AuthResponse, AuthState, AuthTokenResponse } from '../types';
diff --git a/src/utils/comms.ts b/src/utils/comms.ts
index 6b2b5a33..69c03e48 100644
--- a/src/utils/comms.ts
+++ b/src/utils/comms.ts
@@ -1,4 +1,5 @@
-const { ipcRenderer, remote, shell } = require('electron');
+const { ipcRenderer, shell } = require('electron');
+const remote = require('@electron/remote');
 
 export function openExternalLink(url: string): void {
   shell.openExternal(url);
diff --git a/src/utils/notifications.ts b/src/utils/notifications.ts
index 09350c87..6af31d95 100644
--- a/src/utils/notifications.ts
+++ b/src/utils/notifications.ts
@@ -1,4 +1,4 @@
-const { remote } = require('electron');
+const remote = require('@electron/remote');
 
 import { openInBrowser } from '../utils/helpers';
 import { reOpenWindow, updateTrayIcon } from './comms';


Test patch:
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 3658fd8a..909d37d1 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -1,6 +1,10 @@
 name: Run Tests
 
-on: [push, pull_request]
+on: 
+  push:
+    branches:
+      - main
+  pull_request:
 
 jobs:
   run-unit-tests:
diff --git a/src/routes/Settings.test.tsx b/src/routes/Settings.test.tsx
index 185b7ac2..a5aedeac 100644
--- a/src/routes/Settings.test.tsx
+++ b/src/routes/Settings.test.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import TestRenderer from 'react-test-renderer';
+import TestRenderer, { act } from 'react-test-renderer';
 import { render, fireEvent } from '@testing-library/react';
 import { Router } from 'react-router';
 import { MemoryRouter } from 'react-router-dom';
@@ -26,29 +26,38 @@ describe('routes/Settings.tsx', () => {
     updateSetting.mockReset();
   });
 
-  it('should render itself & its children', () => {
-    const tree = TestRenderer.create(
-      <AppContext.Provider value={{ settings: mockSettings }}>
-        <MemoryRouter>
-          <SettingsRoute />
-        </MemoryRouter>
-      </AppContext.Provider>,
-    );
+  it('should render itself & its children', async () => {
+    let tree: TestRenderer;
+
+    await act(async () => {
+      tree = TestRenderer.create(
+        <AppContext.Provider value={{ settings: mockSettings }}>
+          <MemoryRouter>
+            <SettingsRoute />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+    });
     expect(tree).toMatchSnapshot();
   });
 
-  it('should press the logout', () => {
+  it('should press the logout', async () => {
     const logoutMock = jest.fn();
-
-    const { getByLabelText } = render(
-      <AppContext.Provider
-        value={{ settings: mockSettings, logout: logoutMock }}
-      >
-        <Router location={history.location} navigator={history}>
-          <SettingsRoute />
-        </Router>
-      </AppContext.Provider>,
-    );
+    let getByLabelText;
+
+    await act(async () => {
+      const { getByLabelText: getByLabelTextLocal } = render(
+        <AppContext.Provider
+          value={{ settings: mockSettings, logout: logoutMock }}
+        >
+          <Router location={history.location} navigator={history}>
+            <SettingsRoute />
+          </Router>
+        </AppContext.Provider>,
+      );
+
+      getByLabelText = getByLabelTextLocal;
+    });
 
     fireEvent.click(getByLabelText('Logout'));
 
@@ -59,26 +68,37 @@ describe('routes/Settings.tsx', () => {
     expect(mockNavigate).toHaveBeenNthCalledWith(1, -1);
   });
 
-  it('should go back by pressing the icon', () => {
-    const { getByLabelText } = render(
-      <AppContext.Provider value={{ settings: mockSettings }}>
-        <Router location={history.location} navigator={history}>
-          <SettingsRoute />
-        </Router>
-      </AppContext.Provider>,
-    );
+  it('should go back by pressing the icon', async () => {
+    let getByLabelText;
+
+    await act(async () => {
+      const { getByLabelText: getByLabelTextLocal } = render(
+        <AppContext.Provider value={{ settings: mockSettings }}>
+          <Router location={history.location} navigator={history}>
+            <SettingsRoute />
+          </Router>
+        </AppContext.Provider>,
+      );
+
+      getByLabelText = getByLabelTextLocal;
+    });
     fireEvent.click(getByLabelText('Go Back'));
     expect(mockNavigate).toHaveBeenNthCalledWith(1, -1);
   });
 
-  it('should toggle the showOnlyParticipating checkbox', () => {
-    const { getByLabelText } = render(
-      <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
-        <MemoryRouter>
-          <SettingsRoute />
-        </MemoryRouter>
-      </AppContext.Provider>,
-    );
+  it('should toggle the showOnlyParticipating checkbox', async () => {
+    let getByLabelText;
+
+    await act(async () => {
+      const { getByLabelText: getByLabelTextLocal } = render(
+        <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
+          <MemoryRouter>
+            <SettingsRoute />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+      getByLabelText = getByLabelTextLocal;
+    });
 
     fireEvent.click(getByLabelText('Show only participating'), {
       target: { checked: true },
@@ -88,14 +108,19 @@ describe('routes/Settings.tsx', () => {
     expect(updateSetting).toHaveBeenCalledWith('participating', false);
   });
 
-  it('should toggle the playSound checkbox', () => {
-    const { getByLabelText } = render(
-      <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
-        <MemoryRouter>
-          <SettingsRoute />
-        </MemoryRouter>
-      </AppContext.Provider>,
-    );
+  it('should toggle the playSound checkbox', async () => {
+    let getByLabelText;
+
+    await act(async () => {
+      const { getByLabelText: getByLabelTextLocal } = render(
+        <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
+          <MemoryRouter>
+            <SettingsRoute />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+      getByLabelText = getByLabelTextLocal;
+    });
 
     fireEvent.click(getByLabelText('Play sound'), {
       target: { checked: true },
@@ -105,14 +130,19 @@ describe('routes/Settings.tsx', () => {
     expect(updateSetting).toHaveBeenCalledWith('playSound', false);
   });
 
-  it('should toggle the showNotifications checkbox', () => {
-    const { getByLabelText } = render(
-      <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
-        <MemoryRouter>
-          <SettingsRoute />
-        </MemoryRouter>
-      </AppContext.Provider>,
-    );
+  it('should toggle the showNotifications checkbox', async () => {
+    let getByLabelText;
+
+    await act(async () => {
+      const { getByLabelText: getByLabelTextLocal } = render(
+        <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
+          <MemoryRouter>
+            <SettingsRoute />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+      getByLabelText = getByLabelTextLocal;
+    });
 
     fireEvent.click(getByLabelText('Show notifications'), {
       target: { checked: true },
@@ -122,14 +152,19 @@ describe('routes/Settings.tsx', () => {
     expect(updateSetting).toHaveBeenCalledWith('showNotifications', false);
   });
 
-  it('should toggle the onClickMarkAsRead checkbox', () => {
-    const { getByLabelText } = render(
-      <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
-        <MemoryRouter>
-          <SettingsRoute />
-        </MemoryRouter>
-      </AppContext.Provider>,
-    );
+  it('should toggle the onClickMarkAsRead checkbox', async () => {
+    let getByLabelText;
+
+    await act(async () => {
+      const { getByLabelText: getByLabelTextLocal } = render(
+        <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
+          <MemoryRouter>
+            <SettingsRoute />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+      getByLabelText = getByLabelTextLocal;
+    });
 
     fireEvent.click(getByLabelText('Mark as read on click'), {
       target: { checked: true },
@@ -139,14 +174,19 @@ describe('routes/Settings.tsx', () => {
     expect(updateSetting).toHaveBeenCalledWith('markOnClick', false);
   });
 
-  it('should toggle the openAtStartup checkbox', () => {
-    const { getByLabelText } = render(
-      <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
-        <MemoryRouter>
-          <SettingsRoute />
-        </MemoryRouter>
-      </AppContext.Provider>,
-    );
+  it('should toggle the openAtStartup checkbox', async () => {
+    let getByLabelText;
+
+    await act(async () => {
+      const { getByLabelText: getByLabelTextLocal } = render(
+        <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
+          <MemoryRouter>
+            <SettingsRoute />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+      getByLabelText = getByLabelTextLocal;
+    });
 
     fireEvent.click(getByLabelText('Open at startup'), {
       target: { checked: true },
@@ -156,14 +196,19 @@ describe('routes/Settings.tsx', () => {
     expect(updateSetting).toHaveBeenCalledWith('openAtStartup', false);
   });
 
-  it('should change the appearance radio group', () => {
-    const { getByLabelText } = render(
-      <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
-        <MemoryRouter>
-          <SettingsRoute />
-        </MemoryRouter>
-      </AppContext.Provider>,
-    );
+  it('should change the appearance radio group', async () => {
+    let getByLabelText;
+
+    await act(async () => {
+      const { getByLabelText: getByLabelTextLocal } = render(
+        <AppContext.Provider value={{ settings: mockSettings, updateSetting }}>
+          <MemoryRouter>
+            <SettingsRoute />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+      getByLabelText = getByLabelTextLocal;
+    });
 
     fireEvent.click(getByLabelText('Light'));
 
@@ -171,28 +216,40 @@ describe('routes/Settings.tsx', () => {
     expect(updateSetting).toHaveBeenCalledWith('appearance', 'LIGHT');
   });
 
-  it('should go to the enterprise login route', () => {
-    const { getByLabelText } = render(
-      <AppContext.Provider value={{ settings: mockSettings }}>
-        <Router location={history.location} navigator={history}>
-          <SettingsRoute />
-        </Router>
-      </AppContext.Provider>,
-    );
+  it('should go to the enterprise login route', async () => {
+    let getByLabelText;
+
+    await act(async () => {
+      const { getByLabelText: getByLabelTextLocal } = render(
+        <AppContext.Provider value={{ settings: mockSettings }}>
+          <Router location={history.location} navigator={history}>
+            <SettingsRoute />
+          </Router>
+        </AppContext.Provider>,
+      );
+      getByLabelText = getByLabelTextLocal;
+    });
+
     fireEvent.click(getByLabelText('Login with GitHub Enterprise'));
     expect(mockNavigate).toHaveBeenNthCalledWith(1, '/login-enterprise', {
       replace: true,
     });
   });
 
-  it('should quit the app', () => {
-    const { getByLabelText } = render(
-      <AppContext.Provider value={{ settings: mockSettings }}>
-        <MemoryRouter>
-          <SettingsRoute />
-        </MemoryRouter>
-      </AppContext.Provider>,
-    );
+  it('should quit the app', async () => {
+    let getByLabelText;
+
+    await act(async () => {
+      const { getByLabelText: getByLabelTextLocal } = render(
+        <AppContext.Provider value={{ settings: mockSettings }}>
+          <MemoryRouter>
+            <SettingsRoute />
+          </MemoryRouter>
+        </AppContext.Provider>,
+      );
+      getByLabelText = getByLabelTextLocal;
+    });
+
     fireEvent.click(getByLabelText('Quit Gitify'));
     expect(ipcRenderer.send).toHaveBeenCalledWith('app-quit');
   });
diff --git a/src/utils/auth.test.ts b/src/utils/auth.test.ts
index 4072e17c..95d55888 100644
--- a/src/utils/auth.test.ts
+++ b/src/utils/auth.test.ts
@@ -1,6 +1,6 @@
 import { AxiosPromise, AxiosResponse } from 'axios';
 
-import { remote } from 'electron';
+import remote from '@electron/remote';
 const browserWindow = new remote.BrowserWindow();
 
 import * as auth from './auth';
diff --git a/src/utils/comms.test.ts b/src/utils/comms.test.ts
index ce6ff012..95ea3275 100644
--- a/src/utils/comms.test.ts
+++ b/src/utils/comms.test.ts
@@ -6,7 +6,9 @@ import {
   restoreSetting,
 } from './comms';
 
-const { ipcRenderer, remote, shell } = require('electron');
+const { ipcRenderer, shell } = require('electron');
+
+const remote = require('@electron/remote');
 
 describe('utils/comms.ts', () => {
   beforeEach(function () {