Repository: electron/fiddle
Pull request #651 (2021-04-10 17:31:54)

refactor: replace sortedElectronMap() with sortVersions()
There are three places where `sortedElectronMap()` is used:

* `src/renderer/components/settings-electron.tsx` where its use appears to predate `appState.versionsToShow` because it is doing the exact same work. These extra steps can be removed by using `appState.versionsToShow` instead.

* `src/renderer/remote-loader.ts` which again looks like it's a post-refactor leftover. Its need is "has appState heard of version X?". It does this by sorting the array before calling `array.includes()` :slightly_smiling_face: These extra steps can be removed by using `appState.hasVersion()` instead.

* Last is `src/renderer/state.ts` which _does_ need to sort versions. However it does not need `sortedElectronMap()`'s transform step. It also doesn't need to pass an Object in as input -- it's simpler computationally to filter _before_ sorting, so this refactor does so. Happily, after removing the Object input and the transform step, what's left is a straightforward function that can be understood at-a-glance: `sortVersions(version: RunnableVersions[])`

*edit:* no new untested lines/branches. Coveralls is sad because this PR removes a few more lines from `src/` than it adds.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/38701670/badge)](https://coveralls.io/builds/38701670)

Coverage decreased (-0.02%) to 94.043% when pulling **c580ff39546abe8f7975c85a980be802c3a85e55 on refactor/sortedArrayMap** into **73f5fc2823cdc91f2164f06714f4d681a270fe76 on master**.


Patch:
diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx
index 6114e1b5..a3af5f72 100644
--- a/src/renderer/app.tsx
+++ b/src/renderer/app.tsx
@@ -21,8 +21,9 @@ import { EMPTY_EDITOR_CONTENT, SORTED_EDITORS } from './constants';
 import { FileManager } from './file-manager';
 import { RemoteLoader } from './remote-loader';
 import { Runner } from './runner';
+import { AppState } from './state';
+import { getElectronVersions } from './versions';
 import { TaskRunner } from './task-runner';
-import { appState } from './state';
 import { getTheme } from './themes';
 import { defaultDark, defaultLight } from './themes-defaults';
 
@@ -35,10 +36,10 @@ import { defaultDark, defaultLight } from './themes-defaults';
 export class App {
   public typeDefDisposable: MonacoType.IDisposable | null = null;
   public monaco: typeof MonacoType | null = null;
-  public state = appState;
-  public fileManager = new FileManager(appState);
-  public remoteLoader = new RemoteLoader(appState);
-  public runner = new Runner(appState);
+  public state = new AppState(getElectronVersions());
+  public fileManager = new FileManager(this.state);
+  public remoteLoader = new RemoteLoader(this.state);
+  public runner = new Runner(this.state);
   public readonly taskRunner: TaskRunner;
 
   constructor() {
diff --git a/src/renderer/binary.ts b/src/renderer/binary.ts
index e9970bd0..34808ff5 100644
--- a/src/renderer/binary.ts
+++ b/src/renderer/binary.ts
@@ -2,11 +2,15 @@ import * as fs from 'fs-extra';
 import * as path from 'path';
 import extract from 'extract-zip';
 
-import { Version, VersionSource, VersionState } from '../interfaces';
+import {
+  RunnableVersion,
+  Version,
+  VersionSource,
+  VersionState,
+} from '../interfaces';
 import { normalizeVersion } from '../utils/normalize-version';
 import { USER_DATA_PATH } from './constants';
 import { removeTypeDefsForVersion } from './fetch-types';
-import { AppState } from './state';
 import { download as electronDownload } from '@electron/get';
 
 // versions that are currently being downloaded
@@ -44,52 +48,37 @@ export function getVersionState(ver: Version): VersionState {
  * @param {string} iVersion
  * @returns {Promise<void>}
  */
-export function setupBinary(
-  appState: AppState,
-  iVersion: string,
-): Promise<void> {
-  const version = normalizeVersion(iVersion);
+export function setupBinary(ver: RunnableVersion): Promise<void> {
+  const { version } = ver;
 
   // Only remote versions can be downloaded
-  if (appState.versions[version].source !== VersionSource.remote) {
-    return Promise.resolve();
-  }
-
-  // If we already have it, then we're done
-  if (getIsDownloaded(version)) {
-    console.log(`Binary: Electron ${version} already downloaded.`);
-    appState.versions[version].state = VersionState.ready;
+  if (ver.source !== VersionSource.remote) {
     return Promise.resolve();
   }
 
   // Return a promise that resolves when the download completes
   let pending = downloading.get(version);
-  if (pending) {
-    console.log(`Binary: Electron ${version} already downloading.`);
-  } else {
-    console.log(`Binary: Electron ${version} not present, downloading`);
-    pending = downloadBinary(appState, version);
+  if (!pending) {
+    pending = downloadBinary(ver);
     downloading.set(version, pending);
   }
   return pending;
 }
 
-async function downloadBinary(
-  appState: AppState,
-  version: string,
-): Promise<void> {
-  appState.versions[version].state = VersionState.downloading;
+async function downloadBinary(ver: RunnableVersion): Promise<void> {
+  const { version } = ver;
+
+  ver.state = VersionState.downloading;
+  console.log(`Binary: Downloading Electron ${version}`);
+  const zipPath = await download(ver);
 
-  const zipPath = await download(appState, version);
   const extractPath = getDownloadPath(version);
-  console.log(
-    `Binary: Electron ${version} downloaded, now unpacking to ${extractPath}`,
-  );
+  console.log(`Binary: Unpacking ${version} to ${extractPath}`);
 
   try {
     unzipping.add(version);
     downloading.delete(version);
-    appState.versions[version].state = VersionState.unzipping;
+    ver.state = VersionState.unzipping;
 
     // Ensure the target path exists
     await fs.mkdirp(extractPath);
@@ -99,10 +88,10 @@ async function downloadBinary(
 
     const electronFiles = await unzip(zipPath, extractPath);
     console.log(`Binary: Unzipped ${version}`, electronFiles);
-    appState.versions[version].state = VersionState.ready;
+    ver.state = VersionState.ready;
   } catch (error) {
     console.warn(`Binary: Failure while unzipping ${version}`, error);
-    appState.versions[version].state = VersionState.unknown;
+    ver.state = VersionState.unknown;
   } finally {
     // This task is done, so remove it from the pending tasks list
     unzipping.delete(version);
@@ -204,28 +193,20 @@ export function getElectronBinaryPath(
 /**
  * Download an Electron version.
  *
- * @param {AppState} appState
- * @param {string} version
- * @returns {Promise<string>}
+ * @param {RunnableVersion} ver
+ * @returns {Promise<string>} path to the downloaded file
  */
-async function download(appState: AppState, version: string): Promise<string> {
-  const getProgressCallback = (progress: Progress) => {
-    const roundedProgress = Math.round(progress.percent * 100) / 100;
+async function download(ver: RunnableVersion): Promise<string> {
+  const { version } = ver;
 
-    if (roundedProgress !== appState.versions[version].downloadProgress) {
-      console.debug(
-        `Binary: Version ${version} download progress: ${progress.percent}`,
-      );
-      appState.versions[version].downloadProgress = roundedProgress;
+  const getProgressCallback = (progress: Progress) => {
+    const percent = Math.round(progress.percent * 100) / 100;
+    if (ver.downloadProgress !== percent) {
+      ver.downloadProgress = percent;
+      console.debug(`Binary: Version ${version} download progress: ${percent}`);
     }
   };
 
-  if (!appState.versions[version]) {
-    throw new Error(
-      `Version ${version} does not exist in state, cannot download`,
-    );
-  }
-
   const zipFilePath = await electronDownload(version, {
     downloadOptions: {
       quiet: true,
diff --git a/src/renderer/components/output.tsx b/src/renderer/components/output.tsx
index a4d52a32..77369fc7 100644
--- a/src/renderer/components/output.tsx
+++ b/src/renderer/components/output.tsx
@@ -1,3 +1,4 @@
+import { shell } from 'electron';
 import { autorun } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -113,7 +114,7 @@ export class Output extends React.Component<CommandsProps> {
         <div key={`${entry.timestamp}--${index}--${lineIndex}`}>
           <span style={style} className="output-message">
             {timestamp}
-            <a href={text}>{text}</a>
+            <a onClick={() => shell.openExternal(text)}>{text}</a>
           </span>
         </div>
       ) : (
diff --git a/src/renderer/components/settings-electron.tsx b/src/renderer/components/settings-electron.tsx
index 167dce4a..ca65f251 100644
--- a/src/renderer/components/settings-electron.tsx
+++ b/src/renderer/components/settings-electron.tsx
@@ -20,7 +20,6 @@ import {
   VersionState,
 } from '../../interfaces';
 import { normalizeVersion } from '../../utils/normalize-version';
-import { sortedElectronMap } from '../../utils/sorted-electron-map';
 import { AppState } from '../state';
 import { getReleaseChannel } from '../versions';
 
@@ -319,27 +318,13 @@ export class ElectronSettings extends React.Component<
    * @returns {Array<JSX.Element>}
    */
   private renderTableRows(): Array<JSX.Element | null> {
-    const { versions, channelsToShow, statesToShow } = this.props.appState;
-
-    return sortedElectronMap<JSX.Element | null>(versions, (key, item) => {
-      // Check if we want to show the version
-      if (!channelsToShow.includes(getReleaseChannel(item))) {
-        return null;
-      }
-
-      // Check if we want to show the state
-      if (!statesToShow.includes(item.state)) {
-        return null;
-      }
-
-      return (
-        <tr key={item.version}>
-          <td>{item.version}</td>
-          <td>{this.renderHumanState(item)}</td>
-          <td className="action">{this.renderAction(key, item)}</td>
-        </tr>
-      );
-    });
+    return this.props.appState.versionsToShow.map((item) => (
+      <tr key={item.version}>
+        <td>{item.version}</td>
+        <td>{this.renderHumanState(item)}</td>
+        <td className="action">{this.renderAction(item)}</td>
+      </tr>
+    ));
   }
 
   /**
@@ -376,8 +361,8 @@ export class ElectronSettings extends React.Component<
    * @param {RunnableVersion} item
    * @returns {JSX.Element}
    */
-  private renderAction(key: string, item: RunnableVersion): JSX.Element {
-    const { state, source } = item;
+  private renderAction(item: RunnableVersion): JSX.Element {
+    const { state, source, version } = item;
     const { appState } = this.props;
     const buttonProps: IButtonProps = {
       fill: true,
@@ -386,7 +371,7 @@ export class ElectronSettings extends React.Component<
 
     // Already downloaded
     if (state === 'ready') {
-      buttonProps.onClick = () => appState.removeVersion(key);
+      buttonProps.onClick = () => appState.removeVersion(version);
       buttonProps.icon = 'trash';
       buttonProps.text = source === VersionSource.local ? 'Remove' : 'Delete';
     } else if (state === 'downloading') {
@@ -399,7 +384,7 @@ export class ElectronSettings extends React.Component<
       buttonProps.loading = false;
       buttonProps.text = 'Download';
       buttonProps.icon = 'cloud-download';
-      buttonProps.onClick = () => appState.downloadVersion(key);
+      buttonProps.onClick = () => appState.downloadVersion(version);
     }
 
     return <Button {...buttonProps} type={undefined} />;
diff --git a/src/renderer/fetch-types.ts b/src/renderer/fetch-types.ts
index 8f7a8603..95edcec5 100644
--- a/src/renderer/fetch-types.ts
+++ b/src/renderer/fetch-types.ts
@@ -137,8 +137,7 @@ export async function updateEditorTypeDefinitions(
   };
 
   // If this method is called before we're ready, we'll delay this work a bit
-  if (!window.ElectronFiddle.app || !window.ElectronFiddle.app.monaco)
-    return defer();
+  if (!window.ElectronFiddle?.app?.monaco) return defer();
 
   const { app } = window.ElectronFiddle;
   const monaco: typeof MonacoType = app.monaco!;
diff --git a/src/renderer/remote-loader.ts b/src/renderer/remote-loader.ts
index fbf81802..90b51264 100644
--- a/src/renderer/remote-loader.ts
+++ b/src/renderer/remote-loader.ts
@@ -7,7 +7,6 @@ import {
 } from '../interfaces';
 import { FILENAME_KEYS } from '../shared-constants';
 import { getOctokit } from '../utils/octokit';
-import { sortedElectronMap } from '../utils/sorted-electron-map';
 import { ELECTRON_ORG, ELECTRON_REPO } from './constants';
 import { getTemplate } from './content';
 import { AppState } from './state';
@@ -146,11 +145,7 @@ export class RemoteLoader {
   public async setElectronVersionWithRef(ref: string): Promise<boolean> {
     const version = await this.getPackageVersionFromRef(ref);
 
-    const supportedVersions = sortedElectronMap(
-      this.appState.versions,
-      (k) => k,
-    );
-    if (!supportedVersions.includes(version)) {
+    if (!this.appState.hasVersion(version)) {
       this.handleLoadingFailed(
         new Error('Version of Electron in example not supported'),
       );
diff --git a/src/renderer/state.ts b/src/renderer/state.ts
index c0c0dfa1..84569491 100644
--- a/src/renderer/state.ts
+++ b/src/renderer/state.ts
@@ -1,5 +1,5 @@
 import * as fs from 'fs-extra';
-import { action, autorun, computed, observable, toJS, when } from 'mobx';
+import { action, autorun, computed, observable, when } from 'mobx';
 import { MosaicNode } from 'react-mosaic-component';
 
 import {
@@ -42,7 +42,7 @@ import { ipcRendererManager } from './ipc';
 import { activateTheme } from './themes';
 
 import { waitForEditorsToMount } from '../utils/editor-mounted';
-import { sortedElectronMap } from '../utils/sorted-electron-map';
+import { sortVersions } from '../utils/sort-versions';
 import { IPackageManager } from './npm';
 import {
   addLocalVersion,
@@ -53,9 +53,6 @@ import {
   saveLocalVersions,
 } from './versions';
 
-const knownVersions = getElectronVersions();
-const defaultVersion = getDefaultVersion(knownVersions);
-
 /**
  * The application's state. Exported as a singleton below.
  *
@@ -64,7 +61,6 @@ const defaultVersion = getDefaultVersion(knownVersions);
  */
 export class AppState {
   // -- Persisted settings ------------------
-  @observable public version: string = defaultVersion;
   @observable public theme: string | null = localStorage.getItem('theme');
   @observable public gitHubAvatarUrl: string | null = localStorage.getItem(
     'gitHubAvatarUrl',
@@ -116,10 +112,8 @@ export class AppState {
     (this.retrieve('acceleratorsToBlock') as Array<BlockableAccelerator>) || [];
   // -- Various session-only state ------------------
   @observable public gistId: string | undefined;
-  @observable public versions: Record<
-    string,
-    RunnableVersion
-  > = Object.fromEntries(knownVersions.map((ver) => [ver.version, ver]));
+  @observable public readonly versions: Record<string, RunnableVersion>;
+  @observable public version: string;
   @observable public output: Array<OutputEntry> = [];
   @observable public localPath: string | undefined;
   @observable public genericDialogOptions: GenericDialogOptions = {
@@ -165,9 +159,10 @@ export class AppState {
 
   private outputBuffer = '';
   private name: string;
+  private readonly defaultVersion: string;
   public appData: string;
 
-  constructor() {
+  constructor(versions: RunnableVersion[]) {
     // Bind all actions
     this.downloadVersion = this.downloadVersion.bind(this);
     this.pushError = this.pushError.bind(this);
@@ -192,10 +187,16 @@ export class AppState {
     this.hideChannels = this.hideChannels.bind(this);
     this.showChannels = this.showChannels.bind(this);
 
+    // init fields
+    this.versions = Object.fromEntries(versions.map((v) => [v.version, v]));
+    this.defaultVersion = getDefaultVersion(versions);
+    this.version = this.defaultVersion;
+
+    ipcRendererManager.removeAllListeners(IpcEvents.BEFORE_QUIT);
+    ipcRendererManager.removeAllListeners(IpcEvents.BISECT_COMMANDS_TOGGLE);
+    ipcRendererManager.removeAllListeners(IpcEvents.CLEAR_CONSOLE);
     ipcRendererManager.removeAllListeners(IpcEvents.OPEN_SETTINGS);
     ipcRendererManager.removeAllListeners(IpcEvents.SHOW_WELCOME_TOUR);
-    ipcRendererManager.removeAllListeners(IpcEvents.CLEAR_CONSOLE);
-    ipcRendererManager.removeAllListeners(IpcEvents.BISECT_COMMANDS_TOGGLE);
 
     ipcRendererManager.on(IpcEvents.OPEN_SETTINGS, this.toggleSettings);
     ipcRendererManager.on(IpcEvents.SHOW_WELCOME_TOUR, this.showTour);
@@ -291,6 +292,8 @@ export class AppState {
     ipcRendererManager.send(IpcEvents.BLOCK_ACCELERATORS, [
       ...this.acceleratorsToBlock,
     ]);
+
+    this.setVersion(this.version);
   }
 
   /**
@@ -325,11 +328,7 @@ export class AppState {
    * one that can be found.
    */
   @computed get currentElectronVersion(): RunnableVersion {
-    if (this.versions[this.version]) {
-      return this.versions[this.version];
-    } else {
-      return this.versions[defaultVersion];
-    }
+    return this.versions[this.version] || this.versions[this.defaultVersion];
   }
 
   /**
@@ -344,10 +343,7 @@ export class AppState {
       statesToShow.includes(ver.state) &&
       channelsToShow.includes(getReleaseChannel(ver));
 
-    return sortedElectronMap<RunnableVersion>(
-      versions,
-      (_key, item) => item,
-    ).filter(filter);
+    return sortVersions(Object.values(versions).filter(filter));
   }
 
   /**
@@ -360,7 +356,7 @@ export class AppState {
     this.isUpdatingElectronVersions = true;
 
     try {
-      this.setVersions(await getUpdatedElectronVersions());
+      this.addNewVersions(await getUpdatedElectronVersions());
     } catch (error) {
       console.warn(`State: Could not update Electron versions`, error);
     }
@@ -465,14 +461,13 @@ export class AppState {
 
   @action public addLocalVersion(input: Version) {
     addLocalVersion(input);
-    this.setVersions(getElectronVersions());
+    this.addNewVersions(getElectronVersions());
   }
 
-  @action private setVersions(versions: RunnableVersion[]) {
-    this.versions = Object.fromEntries(
-      versions.map((ver) => [ver.version, ver]),
-    );
-    this.updateVersionStates();
+  @action private addNewVersions(versions: RunnableVersion[]) {
+    for (const ver of versions) {
+      this.versions[ver.version] ||= ver;
+    }
   }
 
   /**
@@ -482,35 +477,27 @@ export class AppState {
    * @returns {Promise<void>}
    */
   @action public async removeVersion(input: string) {
-    const version = normalizeVersion(input);
-    const release = this.versions[version];
-
-    console.log(`State: Removing Electron ${version}`);
-
-    // Already not present?
-    if ((this.versions[version] || { state: '' }).state !== 'ready') {
-      console.log(`State: Version already removed, doing nothing`);
+    const ver = this.getVersion(input);
+    if (ver?.state !== VersionState.ready) {
+      console.log('State: Version already removed, doing nothing');
       return;
     }
 
-    // Update state
-    const updatedVersions = { ...this.versions };
+    const { version } = ver;
+    if (ver === this.currentElectronVersion) {
+      console.log(`State: Not removing active version ${version}`);
+      return;
+    }
 
     // Actually remove
-    if (release && release.source === VersionSource.local) {
-      delete updatedVersions[version];
-
-      const versionsAsArray = Object.keys(updatedVersions).map(
-        (k) => updatedVersions[k],
-      );
-
-      saveLocalVersions(versionsAsArray);
+    console.log(`State: Removing Electron ${version}`);
+    if (ver.source === VersionSource.local) {
+      delete this.versions[version];
+      saveLocalVersions(Object.values(this.versions));
     } else {
       await removeBinary(version);
-      updatedVersions[version].state = VersionState.unknown;
+      ver.state = VersionState.unknown;
     }
-
-    this.setVersions(Object.values(updatedVersions));
   }
 
   /**
@@ -520,27 +507,29 @@ export class AppState {
    * @returns {Promise<void>}
    */
   @action public async downloadVersion(input: string) {
-    const version = normalizeVersion(input);
-    console.log(`State: Downloading Electron ${version}`);
-
-    const release = this.versions[version] || { state: '', source: '' };
-    const isLocal = release.source === VersionSource.local;
-    const isReady = release.state === 'ready';
+    let ver = this.getVersion(input);
+
+    // ensure the version is tracked in 'this.versions'
+    if (!ver) {
+      ver = {
+        source: VersionSource.remote,
+        state: VersionState.unknown,
+        version: normalizeVersion(input),
+      };
+      ver.state = getVersionState(ver);
+      this.versions[ver.version] = ver;
+    }
 
-    // Fetch new binaries, maybe?
-    if (!isLocal && !isReady) {
-      console.log(`State: Fetching v${version}`);
-      const updatedVersions = { ...this.versions };
-      updatedVersions[version] = updatedVersions[version] || {};
-      this.versions = updatedVersions;
-
-      await setupBinary(this, version);
-      this.updateVersionStates();
-    } else {
-      console.log(
-        `State: Version ${version} already downloaded, doing nothing.`,
-      );
+    const isRemote = ver.source === VersionSource.remote;
+    const isReady = ver.state === VersionState.ready;
+    const { version } = ver;
+    if (!isRemote || isReady) {
+      console.log(`State: Already have version ${version}; not downloading.`);
+      return;
     }
+
+    console.log(`State: Downloading Electron ${version}`);
+    await setupBinary(ver);
   }
 
   public hasVersion(input: string): boolean {
@@ -548,7 +537,7 @@ export class AppState {
   }
 
   public getVersion(input: string): RunnableVersion | null {
-    return this.versions[input];
+    return this.versions[normalizeVersion(input)];
   }
 
   /**
@@ -558,16 +547,14 @@ export class AppState {
    * @returns {Promise<void>}
    */
   @action public async setVersion(input: string) {
-    const version = normalizeVersion(input);
-
-    if (!this.hasVersion(input)) {
-      console.warn(
-        `State: Called setVersion() with ${version}, which does not exist.`,
-      );
-      await this.setVersion(knownVersions[0].version);
+    const ver = this.getVersion(input);
+    if (!ver) {
+      console.warn(`State: setVersion() got an unknown version ${input}`);
+      await this.setVersion(this.versionsToShow[0].version);
       return;
     }
 
+    const { version } = ver;
     console.log(`State: Switching to Electron ${version}`);
 
     // Should we update the editor?
@@ -580,10 +567,8 @@ export class AppState {
     this.version = version;
 
     // Update TypeScript definitions
-    const versionObject = this.versions[version];
-
-    if (versionObject.source === VersionSource.local) {
-      const typePath = getLocalTypePathForVersion(versionObject);
+    if (ver.source === VersionSource.local) {
+      const typePath = getLocalTypePathForVersion(ver);
       console.info(
         `TypeDefs: Watching file for local version ${version} at path ${typePath}`,
       );
@@ -592,7 +577,7 @@ export class AppState {
           console.info(
             `TypeDefs: Noticed file change at ${typePath}. Updating editor typedefs.`,
           );
-          await updateEditorTypeDefinitions(versionObject);
+          await updateEditorTypeDefinitions(ver);
         });
       } catch (err) {
         console.info('TypeDefs: Unable to start watching.');
@@ -606,25 +591,12 @@ export class AppState {
         this.localTypeWatcher = undefined;
       }
     }
-    await updateEditorTypeDefinitions(versionObject);
+    await updateEditorTypeDefinitions(ver);
 
     // Fetch new binaries, maybe?
     await this.downloadVersion(version);
   }
 
-  /**
-   * Go and check which versions have already been downloaded.
-   *
-   * @returns {Promise<void>}
-   */
-  @action public updateVersionStates() {
-    const versions = toJS(this.versions);
-    for (const ver of Object.values(versions)) {
-      ver.state = getVersionState(ver);
-    }
-    this.versions = versions;
-  }
-
   /**
    * The equivalent of signing out.
    *
@@ -874,6 +846,3 @@ export class AppState {
     return JSON.parse(value || 'null') as T;
   }
 }
-
-export const appState = new AppState();
-appState.setVersion(appState.version);
diff --git a/src/utils/sorted-electron-map.ts b/src/utils/sort-versions.ts
similarity index 68%
rename from src/utils/sorted-electron-map.ts
rename to src/utils/sort-versions.ts
index 43dd023c..670a9a4a 100644
--- a/src/utils/sorted-electron-map.ts
+++ b/src/utils/sort-versions.ts
@@ -25,23 +25,20 @@ function compare(a: semver.SemVer | string, b: semver.SemVer | string) {
 }
 
 /**
- * Sorts Electron versions and returns the result of a map function.
+ * Inplace sorting of Versions
  *
- * @param {Record<string, RunnableVersion?>} versions
- * @param {(key: string, version: RunnableVersion?) => T} mapFn
- * @returns {Array<T>}
+ * @param {RunnableVersion[]} versions
+ * @returns {RunnableVersion[]}
  */
-export function sortedElectronMap<T>(
-  versions: Record<string, RunnableVersion>,
-  mapFn: (key: string, version: RunnableVersion) => T,
-) {
+export function sortVersions(versions: RunnableVersion[]): RunnableVersion[] {
   type VerSemRun = [
     ver: string,
     sem: semver.SemVer | null,
     run: RunnableVersion,
   ];
-  return Object.entries(versions)
-    .map(([ver, run]): VerSemRun => [ver, semver.parse(ver), run])
-    .sort(([vera, sema], [verb, semb]) => compare(sema || vera, semb || verb))
-    .map(([ver, _, run]) => mapFn(ver, run));
+  const sorted = versions
+    .map((run): VerSemRun => [run.version, semver.parse(run.version), run])
+    .sort(([vera, sema], [verb, semb]) => compare(sema || vera, semb || verb));
+  sorted.forEach(([_1, _2, run], idx) => (versions[idx] = run));
+  return versions;
 }


Test patch:
diff --git a/tests/renderer/app-spec.tsx b/tests/renderer/app-spec.tsx
index 376d8ac0..c8d0e4c3 100644
--- a/tests/renderer/app-spec.tsx
+++ b/tests/renderer/app-spec.tsx
@@ -10,17 +10,6 @@ import { defaultDark, defaultLight } from '../../src/renderer/themes-defaults';
 jest.mock('../../src/renderer/file-manager', () =>
   require('../mocks/file-manager'),
 );
-jest.mock('../../src/renderer/state', () => ({
-  appState: {
-    closedPanels: {},
-    getName: () => 'Test',
-    hideChannels: jest.fn(),
-    pushOutput: jest.fn(),
-    setVersion: jest.fn(),
-    showChannels: jest.fn(),
-    theme: 'defaultDark',
-  },
-}));
 jest.mock('../../src/renderer/components/header', () => ({
   Header: () => 'Header;',
 }));
diff --git a/tests/renderer/binary-spec.ts b/tests/renderer/binary-spec.ts
index 29cf4fbe..780a9c7a 100644
--- a/tests/renderer/binary-spec.ts
+++ b/tests/renderer/binary-spec.ts
@@ -30,16 +30,19 @@ jest.mock('../../src/renderer/fetch-types', () => ({
 }));
 
 describe('binary', () => {
-  let mockState: any = {};
   const fs = require('fs-extra');
+  let ver: RunnableVersion;
+  let version = '3.0.0';
 
   beforeEach(() => {
-    mockState = {
-      versions: {
-        '3.0.0': {
-          state: 'downloading',
-        },
-      },
+    // the download module holds state between runs,
+    // so use a unique version number in each test to
+    // prevent state pollution
+    version = semver.inc(version, 'patch')!;
+    ver = {
+      source: VersionSource.remote,
+      state: VersionState.unknown,
+      version,
     };
   });
 
@@ -51,7 +54,7 @@ describe('binary', () => {
     it('removes a version', async () => {
       (fs.existsSync as jest.Mock<any>).mockReturnValue(true);
 
-      await removeBinary('v3.0.0');
+      await removeBinary(version);
       expect(fs.remove).toHaveBeenCalled();
     });
 
@@ -61,14 +64,14 @@ describe('binary', () => {
         throw new Error('Bwap bwap');
       });
 
-      await removeBinary('v3.0.0');
+      await removeBinary(version);
       expect(fs.remove).toHaveBeenCalledTimes(4);
     });
 
     it("attempts to clean up the version's associated typedefs", async () => {
       (fs.existsSync as jest.Mock<any>).mockReturnValue(true);
 
-      await removeBinary('v3.0.0');
+      await removeBinary(version);
       expect(fs.remove).toHaveBeenCalled();
       expect(removeTypeDefsForVersion).toHaveBeenCalled();
     });
@@ -80,28 +83,12 @@ describe('binary', () => {
 
       (fs.existsSync as jest.Mock<any>).mockReturnValue(true);
 
-      await removeBinary('v3.0.0');
+      await removeBinary(version);
       expect(removeTypeDefsForVersion).toHaveBeenCalledTimes(4);
     });
   });
 
   describe('getVersionState()', () => {
-    let version = '3.0.0';
-    let ver: RunnableVersion;
-
-    beforeEach(() => {
-      // the download module holds state between runs,
-      // so use a unique version number in each test to
-      // prevent state pollution
-      version = semver.inc(version, 'patch')!;
-      ver = {
-        source: VersionSource.remote,
-        state: VersionState.unknown,
-        version,
-      };
-      mockState.versions[version] = ver;
-    });
-
     it(`returns ${VersionState.downloading} when downloading`, () => {
       // mock a download in progress
       let downloadResolve: any;
@@ -109,7 +96,7 @@ describe('binary', () => {
         new Promise((r) => (downloadResolve = r)),
       );
 
-      setupBinary(mockState, ver.version);
+      setupBinary(ver);
 
       expect(getVersionState(ver)).toBe(VersionState.downloading);
       downloadResolve();
@@ -122,7 +109,7 @@ describe('binary', () => {
       const unzipPromise = new Promise((r) => (unzipResolve = r));
       (extract as jest.Mock).mockReturnValueOnce(unzipPromise);
 
-      setupBinary(mockState, ver.version);
+      setupBinary(ver);
       await waitFor(() => getVersionState(ver) === VersionState.unzipping);
 
       expect(getVersionState(ver)).toBe(VersionState.unzipping);
@@ -196,46 +183,29 @@ describe('binary', () => {
       const localPath = '/fake/path';
       (electronDownload as jest.Mock).mockResolvedValue(localPath);
 
-      const version = '3.0.0';
-      mockState.versions[version] = {
-        source: VersionSource.local,
-        state: VersionState.unknown,
-        version,
-      };
-
-      await setupBinary(mockState, version);
+      ver.source = VersionSource.local;
+      ver.state = VersionState.unknown;
+      await setupBinary(ver);
 
       expect(electronDownload).not.toHaveBeenCalled();
-      expect(mockState.versions[version].state).toBe(VersionState.unknown);
+      expect(ver.state).toBe(VersionState.unknown);
     });
 
     it(`downloads a version it has not seen before`, async () => {
       (fs.existsSync as jest.Mock).mockReturnValueOnce(false);
-
       const localPath = '/fake/path';
       (electronDownload as jest.Mock).mockResolvedValue(localPath);
 
-      const version = '3.0.0';
-      mockState.versions[version] = {
-        source: VersionSource.remote,
-        state: VersionState.unknown,
-        version,
-      };
-      await setupBinary(mockState, version);
+      ver.source = VersionSource.remote;
+      ver.state = VersionState.unknown;
+      await setupBinary(ver);
 
       expect(electronDownload).toHaveBeenCalled();
-      expect(mockState.versions[version].state).toBe('ready');
+      expect(ver.state).toBe(VersionState.ready);
     });
 
     it('handles progress callbacks from electron/get', async () => {
       const percent = 50;
-      const version = '9.9.9';
-      const ver: RunnableVersion = {
-        source: VersionSource.remote,
-        state: VersionState.unknown,
-        version,
-      };
-      mockState.versions[version] = ver;
 
       // mock a download in progress...
       let downloadResolve: any;
@@ -247,11 +217,12 @@ describe('binary', () => {
         },
       );
 
-      setupBinary(mockState, version);
-      await waitFor(() => getVersionState(ver) === VersionState.downloading);
+      ver.source = VersionSource.remote;
+      ver.state = VersionState.unknown;
+      setupBinary(ver);
+      await waitFor(() => ver.state === VersionState.downloading);
 
       expect(ver.downloadProgress).not.toBe(percent);
-
       // send a progress update
       getProgressCallback({ percent });
       expect(ver.downloadProgress).toBe(percent);
@@ -262,66 +233,22 @@ describe('binary', () => {
       downloadResolve();
     });
 
-    it(`does not download a version again`, async () => {
-      (fs.existsSync as jest.Mock).mockReturnValue(true);
-
-      const version = '3.0.0';
-      mockState.versions[version] = {
-        source: VersionSource.remote,
-        state: VersionState.unknown,
-        version,
-      };
-
-      await setupBinary(mockState, version);
-
-      expect(electronDownload).not.toHaveBeenCalled();
-      expect(mockState.versions[version].state).toBe('ready');
-    });
-
     it(`returns the same promise if called twice for the same version`, async () => {
       (fs.existsSync as jest.Mock).mockReturnValue(false);
 
-      const version = '123.456.789';
-      mockState.versions[version] = {
-        source: VersionSource.remote,
-        state: VersionState.unknown,
-        version,
-      };
-
-      const prom1 = setupBinary(mockState, version);
-      const prom2 = setupBinary(mockState, version);
+      const prom1 = setupBinary(ver);
+      const prom2 = setupBinary(ver);
 
       expect(prom1).toBe(prom2);
     });
 
-    it(`waits if asked for a version that is already downloading`, async () => {
-      (fs.existsSync as jest.Mock).mockReturnValue(true);
-
-      const version = '3.0.0';
-      mockState.versions[version] = {
-        source: VersionSource.remote,
-        state: VersionState.downloading,
-        version,
-      };
-
-      await setupBinary(mockState, version);
-
-      expect(electronDownload).not.toHaveBeenCalled();
-      expect(mockState.versions[version].state).toBe('ready');
-    });
-
     it('handles an error in the zip file', async () => {
       (electronDownload as jest.Mock).mockReturnValue('/fake/path');
-      const version = '3.0.0';
-      mockState.versions[version] = {
-        source: VersionSource.remote,
-        state: VersionState.unknown,
-        version,
-      };
-
       (extract as jest.Mock).mockRejectedValue(new Error('bwap-bwap'));
 
-      await setupBinary(mockState, 'v3.0.0');
+      ver.source = VersionSource.remote;
+      ver.state = VersionState.unknown;
+      await setupBinary(ver);
 
       expect(extract as jest.Mock).toHaveBeenCalledTimes(1);
     });
diff --git a/tests/renderer/components/__snapshots__/settings-electron-spec.tsx.snap b/tests/renderer/components/__snapshots__/settings-electron-spec.tsx.snap
index 401141d8..7383d46b 100644
--- a/tests/renderer/components/__snapshots__/settings-electron-spec.tsx.snap
+++ b/tests/renderer/components/__snapshots__/settings-electron-spec.tsx.snap
@@ -177,6 +177,33 @@ exports[`ElectronSettings component renders 1`] = `
         </tr>
       </thead>
       <tbody>
+        <tr
+          key="3.0.0-nightly.1"
+        >
+          <td>
+            3.0.0-nightly.1
+          </td>
+          <td>
+            <span>
+              <Blueprint3.Icon
+                icon="box"
+              />
+               
+              Downloaded
+            </span>
+          </td>
+          <td
+            className="action"
+          >
+            <Blueprint3.Button
+              fill={true}
+              icon="trash"
+              onClick={[Function]}
+              small={true}
+              text="Delete"
+            />
+          </td>
+        </tr>
         <tr
           key="3.0.0"
         >
@@ -313,6 +340,35 @@ exports[`ElectronSettings component renders 1`] = `
             />
           </td>
         </tr>
+        <tr
+          key="1.8.7"
+        >
+          <td>
+            1.8.7
+          </td>
+          <td>
+            <span>
+              <Blueprint3.Icon
+                icon="cloud"
+              />
+               
+              Not downloaded
+            </span>
+          </td>
+          <td
+            className="action"
+          >
+            <Blueprint3.Button
+              disabled={false}
+              fill={true}
+              icon="cloud-download"
+              loading={false}
+              onClick={[Function]}
+              small={true}
+              text="Download"
+            />
+          </td>
+        </tr>
       </tbody>
     </HTMLTable>
   </Blueprint3.Callout>
diff --git a/tests/renderer/components/settings-electron-spec.tsx b/tests/renderer/components/settings-electron-spec.tsx
index 2e91b312..72267fbd 100644
--- a/tests/renderer/components/settings-electron-spec.tsx
+++ b/tests/renderer/components/settings-electron-spec.tsx
@@ -32,6 +32,7 @@ describe('ElectronSettings component', () => {
       toggleAddVersionDialog: jest.fn(),
       showChannels: jest.fn(),
       hideChannels: jest.fn(),
+      versionsToShow: mockVersionsArray,
     };
 
     // Render all the states
@@ -41,17 +42,23 @@ describe('ElectronSettings component', () => {
   });
 
   it('renders', () => {
-    store.versions['3.0.0-nightly.1'] = {
-      state: VersionState.ready,
-      version: '3.0.0-nightly.1',
-      source: VersionSource.local,
-    };
+    const moreVersions: RunnableVersion[] = [
+      {
+        source: VersionSource.local,
+        state: VersionState.ready,
+        version: '3.0.0',
+      },
+      {
+        source: VersionSource.remote,
+        state: VersionState.ready,
+        version: '3.0.0-nightly.1',
+      },
+    ];
 
-    store.versions['3.0.0'] = {
-      state: VersionState.ready,
-      version: '3.0.0',
-      source: VersionSource.local,
-    };
+    for (const ver of moreVersions) {
+      store.versions[ver.version] = ver;
+      store.versionsToShow.unshift(ver);
+    }
 
     const wrapper = shallow(<ElectronSettings appState={store} />);
 
@@ -82,15 +89,15 @@ describe('ElectronSettings component', () => {
   });
 
   it('handles downloading a version', async () => {
-    store.versions = {
-      '3.0.0': {
-        state: VersionState.unknown,
-        version: '3.0.0',
-        source: VersionSource.remote,
-      },
+    const version = '3.0.0';
+    const ver = {
+      source: VersionSource.remote,
+      state: VersionState.unknown,
+      version,
     };
-
     store.statesToShow.push(VersionState.unknown);
+    store.versions = { version: ver };
+    store.versionsToShow = [ver];
 
     const wrapper = mount(<ElectronSettings appState={store} />);
 
diff --git a/tests/renderer/remote-loader-spec.ts b/tests/renderer/remote-loader-spec.ts
index 3757719f..2da54621 100644
--- a/tests/renderer/remote-loader-spec.ts
+++ b/tests/renderer/remote-loader-spec.ts
@@ -91,6 +91,7 @@ class MockStore {
   };
   public channelsToShow = [ElectronReleaseChannel.stable];
   public setVersion = jest.fn();
+  public hasVersion = (version: string) => !!this.versions[version];
 }
 
 describe('RemoteLoader', () => {
diff --git a/tests/renderer/state-spec.ts b/tests/renderer/state-spec.ts
index fd98c789..8a268b20 100644
--- a/tests/renderer/state-spec.ts
+++ b/tests/renderer/state-spec.ts
@@ -8,6 +8,7 @@ import {
   GenericDialogType,
   PanelId,
   RunnableVersion,
+  Version,
   VersionSource,
   VersionState,
 } from '../../src/interfaces';
@@ -25,6 +26,7 @@ import {
 import { getTemplate, isContentUnchanged } from '../../src/renderer/content';
 import { ipcRendererManager } from '../../src/renderer/ipc';
 import { AppState } from '../../src/renderer/state';
+import { getElectronVersions } from '../../src/renderer/versions';
 import {
   getUpdatedElectronVersions,
   saveLocalVersions,
@@ -56,7 +58,7 @@ jest.mock('../../src/renderer/versions', () => {
 
   return {
     getUpdatedElectronVersions: jest.fn().mockResolvedValue(mockVersionsArray),
-    getElectronVersions: () => mockVersionsArray,
+    getElectronVersions: jest.fn(),
     getDefaultVersion: () => '2.0.2',
     addLocalVersion: jest.fn(),
     saveLocalVersions: jest.fn(),
@@ -81,9 +83,7 @@ describe('AppState', () => {
     );
     (getVersionState as jest.Mock).mockImplementation((v) => v.state);
 
-    appState = new AppState();
-    appState.versions = mockVersions;
-    appState.updateVersionStates = jest.fn();
+    appState = new AppState(mockVersionsArray);
 
     ipcRendererManager.removeAllListeners();
   });
@@ -200,14 +200,12 @@ describe('AppState', () => {
   describe('get currentElectronVersion()', () => {
     it('returns the current version', () => {
       appState.version = '2.0.2';
-      appState.versions = mockVersions;
 
       expect(appState.currentElectronVersion).toEqual(mockVersions['2.0.2']);
     });
 
     it('falls back to the defaultVersion', () => {
       appState.version = 'garbage';
-      appState.versions = mockVersions;
 
       expect(appState.currentElectronVersion).toEqual(mockVersions['2.0.2']);
     });
@@ -387,53 +385,69 @@ describe('AppState', () => {
   });
 
   describe('removeVersion()', () => {
-    it('removes a version', async () => {
-      appState.versions['2.0.2'].state = VersionState.ready;
-      await appState.removeVersion('v2.0.2');
+    let active: string;
+    let version: string;
+
+    beforeEach(() => {
+      active = appState.currentElectronVersion.version;
+      version = mockVersionsArray.find((v) => v.version !== active)!.version;
+    });
+
+    it('does not remove the active version', async () => {
+      await appState.removeVersion(active);
+      expect(removeBinary).not.toHaveBeenCalled();
+    });
 
-      expect(removeBinary).toHaveBeenCalledWith<any>('2.0.2');
+    it('removes a version', async () => {
+      appState.versions[version].state = VersionState.ready;
+      await appState.removeVersion(version);
+      expect(removeBinary).toHaveBeenCalledWith<any>(version);
     });
 
     it('does not remove it if not necessary', async () => {
-      appState.versions['2.0.2'].state = VersionState.unknown;
-      await appState.removeVersion('v2.0.2');
+      appState.versions[version].state = VersionState.unknown;
+      await appState.removeVersion(version);
       expect(removeBinary).toHaveBeenCalledTimes(0);
     });
 
     it('does not remove it if not necessary (version not existent)', async () => {
-      appState.versions['2.0.2'] = undefined as any;
-      await appState.removeVersion('v2.0.2');
+      await appState.removeVersion('-1.0.0');
       expect(removeBinary).toHaveBeenCalledTimes(0);
     });
 
-    it('removes (and not deletes) a local version', async () => {
-      appState.versions['/local/path'] = {
-        localPath: 'local/path',
-        name: 'local-foo',
-        source: VersionSource.local,
-        state: VersionState.ready,
-        version: '4.0.0',
-      };
+    it('removes (but does not delete) a local version', async () => {
+      const localPath = '/fake/path';
+
+      const ver = appState.versions[version];
+      ver.localPath = localPath;
+      ver.source = VersionSource.local;
+      ver.state = VersionState.ready;
 
-      await appState.removeVersion('/local/path');
+      await appState.removeVersion(version);
 
       expect(saveLocalVersions).toHaveBeenCalledTimes(1);
-      expect(appState.versions['/local/path']).toBeUndefined();
+      expect(appState.versions[version]).toBeUndefined();
       expect(removeBinary).toHaveBeenCalledTimes(0);
     });
   });
 
   describe('downloadVersion()', () => {
     it('downloads a version', async () => {
-      appState.versions['2.0.2'].state = VersionState.unknown;
+      const version = '2.0.2';
+      const ver = appState.versions[version];
 
-      await appState.downloadVersion('v2.0.2');
-      expect(setupBinary).toHaveBeenCalledWith<any>(appState, '2.0.2');
+      ver.state = VersionState.unknown;
+      await appState.downloadVersion(version);
+
+      expect(setupBinary).toHaveBeenCalledWith<any>(ver);
     });
 
     it('downloads an unknown version', async () => {
-      await appState.downloadVersion('v3.5');
-      expect(setupBinary).toHaveBeenCalledWith<any>(appState, '3.5');
+      const version = '3.5';
+      expect(appState.versions[version]).toBeUndefined();
+      await appState.downloadVersion(version);
+      expect(appState.versions[version]).not.toBeUndefined();
+      expect(setupBinary).toHaveBeenCalledWith<any>(appState.versions[version]);
     });
 
     it('does not download a version if already ready', async () => {
@@ -516,44 +530,19 @@ describe('AppState', () => {
 
   describe('addLocalVersion()', () => {
     it('refreshes version state', async () => {
-      appState.versions = {};
-
-      await appState.addLocalVersion({
+      const version = '4.0.0';
+      const ver: Version = {
         localPath: '/fake/path',
         name: 'local-foo',
-        version: '4.0.0',
-      });
-
-      // We just want to verify that the version state was
-      // refreshed - we didn't actually add the local version
-      // above, since versions.ts is mocked
-      expect(Object.keys(appState.versions)).toEqual(Object.keys(mockVersions));
-    });
-  });
-
-  describe('updateVersionStates()', () => {
-    beforeEach(() => {
-      ipcRendererManager.removeAllListeners();
-      (getVersionState as jest.Mock).mockImplementation(
-        (v: RunnableVersion) => {
-          if (v.version === '2.0.1') return VersionState.downloading;
-          if (v.version === '2.0.2') return VersionState.ready;
-          return VersionState.unknown;
-        },
-      );
-      appState = new AppState();
-    });
-
-    it('downloads a version if necessary', async () => {
-      await appState.updateVersionStates();
+        version,
+      };
 
-      expect(appState.versions['2.0.2'].state).toBe(VersionState.ready);
-    });
+      (getElectronVersions as jest.Mock).mockReturnValue([ver]);
 
-    it('keeps downloading state intact', async () => {
-      await appState.updateVersionStates();
+      await appState.addLocalVersion(ver);
 
-      expect(appState.versions['2.0.1'].state).toBe(VersionState.downloading);
+      expect(getElectronVersions).toHaveBeenCalledTimes(1);
+      expect(appState.getVersion(version)).toStrictEqual(ver);
     });
   });
 
diff --git a/tests/utils/sort-versions-spec.ts b/tests/utils/sort-versions-spec.ts
new file mode 100644
index 00000000..e55eb9b3
--- /dev/null
+++ b/tests/utils/sort-versions-spec.ts
@@ -0,0 +1,78 @@
+import {
+  VersionSource,
+  VersionState,
+  RunnableVersion,
+} from '../../src/interfaces';
+import { sortVersions } from '../../src/utils/sort-versions';
+
+describe('sort-versions', () => {
+  function makeVersion(version: string): RunnableVersion {
+    return {
+      source: VersionSource.remote,
+      state: VersionState.unknown,
+      version,
+    };
+  }
+
+  it('sorts electron versions', () => {
+    const unsorted: RunnableVersion[] = [
+      makeVersion('v1.0.0'),
+      makeVersion('v3.0.0'),
+      makeVersion('v2.0.0'),
+    ];
+
+    const sorted = sortVersions([...unsorted]);
+
+    expect(sorted).toStrictEqual<any>([
+      makeVersion('v3.0.0'),
+      makeVersion('v2.0.0'),
+      makeVersion('v1.0.0'),
+    ]);
+  });
+
+  it('sorts nightly and beta versions correctly', () => {
+    const unsorted: RunnableVersion[] = [
+      makeVersion('v2.0.0-nightly.20200101'),
+      makeVersion('v2.0.0-beta.1'),
+      makeVersion('v2.0.0-nightly.20200102'),
+      makeVersion('v3.0.0-beta.1'),
+      makeVersion('v3.0.0-nightly.20200105'),
+      makeVersion('v2.0.0-beta.2'),
+      makeVersion('v2.0.0-beta.3'),
+      makeVersion('v2.0.0'),
+      makeVersion('v3.0.0'),
+    ];
+
+    const sorted = sortVersions([...unsorted]);
+
+    expect(sorted).toStrictEqual<any>([
+      makeVersion('v3.0.0'),
+      makeVersion('v3.0.0-beta.1'),
+      makeVersion('v3.0.0-nightly.20200105'),
+      makeVersion('v2.0.0'),
+      makeVersion('v2.0.0-beta.3'),
+      makeVersion('v2.0.0-beta.2'),
+      makeVersion('v2.0.0-beta.1'),
+      makeVersion('v2.0.0-nightly.20200102'),
+      makeVersion('v2.0.0-nightly.20200101'),
+    ]);
+  });
+
+  it('handles invalid versions', () => {
+    const unsorted: RunnableVersion[] = [
+      makeVersion('moreGarbage'),
+      makeVersion('v1.0.0'),
+      makeVersion('v3.0.0'),
+      makeVersion('garbage'),
+    ];
+
+    const sorted = sortVersions([...unsorted]);
+
+    expect(sorted).toStrictEqual<any>([
+      makeVersion('garbage'),
+      makeVersion('moreGarbage'),
+      makeVersion('v3.0.0'),
+      makeVersion('v1.0.0'),
+    ]);
+  });
+});
diff --git a/tests/utils/sorted-electron-map-spec.ts b/tests/utils/sorted-electron-map-spec.ts
deleted file mode 100644
index fce6b841..00000000
--- a/tests/utils/sorted-electron-map-spec.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { sortedElectronMap } from '../../src/utils/sorted-electron-map';
-
-describe('sorted-electron-map', () => {
-  it('sorts a record of electron versions', () => {
-    const map: any = {
-      '1.0.0': {
-        version: 'v1.0.0',
-      },
-      '3.0.0': {
-        version: 'v3.0.0',
-      },
-      '2.0.0': {
-        version: 'v2.0.0',
-      },
-    };
-
-    const result = sortedElectronMap(map, (_k, e) => e);
-    expect(result).toStrictEqual<any>([
-      { version: 'v3.0.0' },
-      { version: 'v2.0.0' },
-      { version: 'v1.0.0' },
-    ]);
-  });
-
-  it('sorts nightly and beta versions correctly', () => {
-    const map: any = {
-      '2.0.0-nightly.20200101': {
-        version: 'v2.0.0-nightly.20200101',
-      },
-      '2.0.0-beta.1': {
-        version: 'v2.0.0-beta.1',
-      },
-      '2.0.0-nightly.20200102': {
-        version: 'v2.0.0-nightly.20200102',
-      },
-      '3.0.0-beta.1': {
-        version: 'v3.0.0-beta.1',
-      },
-      '3.0.0-nightly.20200105': {
-        version: 'v3.0.0-nightly.20200105',
-      },
-      '2.0.0-beta.2': {
-        version: 'v2.0.0-beta.2',
-      },
-      '2.0.0-beta.3': {
-        version: 'v2.0.0-beta.3',
-      },
-      '2.0.0': {
-        version: 'v2.0.0',
-      },
-      '3.0.0': {
-        version: 'v3.0.0',
-      },
-    };
-
-    const result = sortedElectronMap(map, (_k, e) => e);
-    expect(result).toStrictEqual<any>([
-      { version: 'v3.0.0' },
-      { version: 'v3.0.0-beta.1' },
-      { version: 'v3.0.0-nightly.20200105' },
-      { version: 'v2.0.0' },
-      { version: 'v2.0.0-beta.3' },
-      { version: 'v2.0.0-beta.2' },
-      { version: 'v2.0.0-beta.1' },
-      { version: 'v2.0.0-nightly.20200102' },
-      { version: 'v2.0.0-nightly.20200101' },
-    ]);
-  });
-
-  it('handles invalid versions', () => {
-    const map: any = {
-      moreGarbage: {
-        version: 'moreGarbage',
-      },
-      '1.0.0': {
-        version: 'v1.0.0',
-      },
-      '3.0.0': {
-        version: 'v3.0.0',
-      },
-      garbage: {
-        version: 'garbage',
-      },
-    };
-
-    const result = sortedElectronMap(map, (_k, e) => e);
-    expect(result).toStrictEqual<any>([
-      { version: 'garbage' },
-      { version: 'moreGarbage' },
-      { version: 'v3.0.0' },
-      { version: 'v1.0.0' },
-    ]);
-  });
-});