Compare commits

..
Author SHA1 Message Date
Felix Rieseberg 0a423bc294 feat: Add progress to settings page, too 2020-03-24 11:40:09 -07:00
Felix Rieseberg 98a358b756 Merge pull request #358 from electron/download-progress
Refactor BinaryManager, add download progress indicator
2020-03-24 11:14:55 -07:00
Felix Rieseberg 9b55b2ef26 Merge pull request #357 from electron/no-notarize
build: Only notarize during make
2020-03-23 10:45:16 -07:00
Felix Rieseberg 5b91177348 Merge pull request #359 from electron/update-releases
build: update Electron releases JSON
2020-03-23 10:44:51 -07:00
malept f8c43a07f7 build: update Electron releases JSON 2020-03-23 00:04:50 +00:00
Felix Rieseberg 5eba9cdbe2 feat: A fancy spinner for downloading 2020-03-17 18:11:05 -07:00
Felix Rieseberg 8ee42c290e test: Fix the tests post-refactor 2020-03-17 17:58:25 -07:00
Felix Rieseberg c2b3954dfa chore: Refactor BinaryManager, no more classes 2020-03-17 17:07:25 -07:00
Felix Rieseberg e61599e9c5 chore: Better named interfaces 2020-03-17 16:24:58 -07:00
Felix RiesebergandMark Lee 1fed772a27 Update forge.config.js
Co-Authored-By: Mark Lee <malept@users.noreply.github.com>
2020-03-17 16:13:32 -07:00
Felix RiesebergandMark Lee de85d0f324 Update forge.config.js
Co-Authored-By: Mark Lee <malept@users.noreply.github.com>
2020-03-17 16:13:16 -07:00
Erick Zhao c89671d99d feat: improved bisect commands UX (#352) 2020-03-16 10:55:34 -07:00
Felix Rieseberg 155b901ade build: Only notarize during make 2020-03-16 10:47:00 -07:00
github-actions[bot]andmalept 8e098deab8 build: update Electron releases JSON (#356)
Co-authored-by: malept <electron-bot@users.noreply.github.com>
2020-03-16 02:18:36 +02:00
dependabot[bot] 2b5ea7d467 build(deps): bump acorn from 5.7.1 to 5.7.4
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.1 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.1...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-13 23:49:10 -07:00
Vlad Hashimoto 91efa5a009 v0.13.1 2020-03-13 16:45:08 -07:00
Erick Zhao 4ec5bec719 fix: load unsave handlers only after editors mount (#351) 2020-03-12 11:53:29 -07:00
Erick Zhao 84ac41f147 feat: more sensible version filtering (#344) 2020-03-12 11:46:32 -07:00
Erick Zhao e9a2985291 fix: preserve 'downloading' version state post-refresh (#345) 2020-03-12 11:46:24 -07:00
Erick ZhaoandFelix Rieseberg 9b28919203 feat: show inactive window on quit warning (#348)
Co-authored-by: Felix Rieseberg <felix@felixrieseberg.com>
2020-03-12 11:13:08 -07:00
Erick Zhao 8d43836787 fix: ensure you have at least one editor open (#347) 2020-03-11 12:54:16 -07:00
Shelley Vohr 48d0dadf5d chore: fix test:report bailing (#349) 2020-03-11 12:32:03 -07:00
Erick Zhao 2f9ef21251 fix: ensure hidden panels work on first load (#346) 2020-03-11 12:12:09 -07:00
60 changed files with 1304 additions and 1034 deletions
+10
View File
@@ -106,11 +106,21 @@ function notarizeMaybe() {
return;
}
if (process.env.npm_lifecycle_event !== 'publish') {
console.log(`Not in "publish" mode, skipping notarization`);
return;
}
if (!process.env.CI) {
console.log(`Not in CI, skipping notarization`);
return;
}
if (!process.env.TRAVIS_TAG && !process.env.NOTARIZE_WITHOUT_TAG) {
console.log(`Not a tag, not notarizing`);
return;
}
if (!process.env.APPLE_ID || !process.env.APPLE_ID_PASSWORD) {
console.warn('Should be notarizing, but environment variables APPLE_ID or APPLE_ID_PASSWORD are missing!');
return;
+1
View File
@@ -5,6 +5,7 @@
"testURL": "http://localhost",
"testRegex": "(-spec)\\.(ts|tsx)$",
"resetMocks": true,
"bail": true,
"resetModules": true,
"snapshotSerializers": [
"enzyme-to-json/serializer"
+185 -237
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "electron-fiddle",
"productName": "Electron Fiddle",
"version": "0.13.0",
"version": "0.13.1",
"description": "The easiest way to get started with Electron",
"repository": "https://github.com/electron/fiddle",
"main": "./dist/src/main/main",
@@ -22,7 +22,7 @@
"test": "jest --config=jest.json",
"test:ci": "jest --config=jest.json --coverage --runInBand",
"test:coverage": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"test:report": "jest --config=jest.json --json --outputFile=report.json | exit 0",
"test:report": "jest --config=jest.json --json --bail=false --outputFile=report.json | true",
"tsc": "tsc --noEmit -p .",
"electron-releases": "node --unhandled-rejections=strict ./tools/fetch-releases.js"
},
@@ -39,12 +39,12 @@
"dependencies": {
"@blueprintjs/core": "^3.24.0",
"@blueprintjs/select": "^3.12.0",
"@electron/get": "^1.9.0",
"@octokit/rest": "^16.43.1",
"@sentry/electron": "^1.2.1",
"classnames": "^2.2.6",
"electron-default-menu": "^1.0.1",
"electron-devtools-installer": "^2.2.4",
"electron-download": "^4.1.1",
"electron-squirrel-startup": "^1.0.0",
"extract-zip": "^1.6.7",
"fix-path": "^3.0.0",
+7 -5
View File
@@ -4,13 +4,14 @@ export type Files = Map<string, string>;
export type FileTransform = (files: Files) => Promise<Files>;
export enum ElectronVersionState {
export enum VersionState {
ready = 'ready',
downloading = 'downloading',
unzipping = 'unzipping',
unknown = 'unknown'
}
export enum ElectronVersionSource {
export enum VersionSource {
remote = 'remote',
local = 'local'
}
@@ -29,9 +30,10 @@ export interface EditorValues {
package?: string;
}
export interface ElectronVersion extends Version {
state: ElectronVersionState;
source: ElectronVersionSource;
export interface RunnableVersion extends Version {
state: VersionState;
source: VersionSource;
downloadProgress?: number;
}
export interface SetFiddleOptions {
+3 -1
View File
@@ -23,6 +23,7 @@ export enum IpcEvents {
LOAD_LOCAL_VERSION_FOLDER = 'LOAD_LOCAL_VERSION_FOLDER',
SHOW_LOCAL_VERSION_FOLDER_DIALOG = 'SHOW_LOCAL_VERSION_FOLDER_DIALOG',
BISECT_COMMANDS_TOGGLE = 'BISECT_COMMANDS_TOGGLE',
SHOW_INACTIVE = 'SHOW_INACTIVE'
}
export const ipcMainEvents = [
@@ -30,7 +31,8 @@ export const ipcMainEvents = [
IpcEvents.FS_SAVE_FIDDLE,
IpcEvents.SHOW_WARNING_DIALOG,
IpcEvents.SHOW_CONFIRMATION_DIALOG,
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG,
IpcEvents.SHOW_INACTIVE
];
export const ipcRendererEvents = [
+4
View File
@@ -52,6 +52,10 @@
.action {
text-align: right;
}
.bp3-progress-bar {
margin-top: 8px;
}
}
.settings-content {
+6
View File
@@ -1,5 +1,7 @@
import { BrowserWindow, shell } from 'electron';
import { IpcEvents } from '../ipc-events';
import { createContextMenu } from './context-menu';
import { ipcMainManager } from './ipc';
// Keep a global reference of the window objects, if we don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
@@ -61,6 +63,10 @@ export function createMainWindow(): Electron.BrowserWindow {
shell.openExternal(url);
});
ipcMainManager.on(IpcEvents.SHOW_INACTIVE, () => {
browserWindow.showInactive();
});
browserWindows.push(browserWindow);
return browserWindow;
-22
View File
@@ -78,7 +78,6 @@ export class App {
// once loaded, we have a "saved" state
this.state.isUnsaved = false;
this.setupUnsavedOnChangeListener();
return true;
}
@@ -177,30 +176,9 @@ export class App {
ipcRenderer.send(WEBCONTENTS_READY_FOR_IPC_SIGNAL);
// TODO: A timer here is terrible. Let's fix this
// and ensure we actually do it once Editors have mounted.
setTimeout(() => {
this.setupUnsavedOnChangeListener();
}, 1500);
return rendered;
}
/**
* If the editor is changed for the first time, we'll
* set `isUnsaved` to true. That way, the app can warn you
* if you're about to throw things away.
*/
public setupUnsavedOnChangeListener() {
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
const editor = window.ElectronFiddle.editors[key];
const disposable = editor.onDidChangeModelContent(() => {
this.state.isUnsaved = true;
disposable.dispose();
});
});
}
/**
* Loads theme CSS into the HTML document.
*
+227 -210
View File
@@ -1,242 +1,259 @@
import * as fsType from 'fs-extra';
import * as path from 'path';
import { VersionState } from '../interfaces';
import { fancyImport } from '../utils/import';
import { normalizeVersion } from '../utils/normalize-version';
import { USER_DATA_PATH } from './constants';
import { getOfflineTypeDefinitionPath } from './fetch-types';
import { removeTypeDefsForVersion } from './fetch-types';
import { AppState } from './state';
/**
* The binary manager takes care of downloading Electron versions
* General setup, called with a version. Is called during construction
* to ensure that we always have or download at least one version.
*
* @export
* @class BinaryManager
* @param {string} iVersion
* @returns {Promise<void>}
*/
export class BinaryManager {
public state: Record<string, 'ready' | 'downloading'> = {};
export async function setupBinary(appState: AppState, iVersion: string): Promise<void> {
const version = normalizeVersion(iVersion);
const fs = await fancyImport<typeof fsType>('fs-extra');
/**
* Remove a version from disk. Does not update state. We'll try up to
* four times before giving up if an error occurs.
*
* @param {string} iVersion
* @returns {Promise<void>}
*/
public async remove(iVersion: string): Promise<void> {
const version = normalizeVersion(iVersion);
const fs = await fancyImport<typeof fsType>('fs-extra');
let isDeleted = false;
await fs.mkdirp(getDownloadPath(version));
// utility to re-run removal functions upon failure
// due to windows filesystem lockfile jank
const rerunner = async (func: () => Promise<void>, counter: number = 1) => {
try {
await func();
} catch (error) {
console.warn(`Binary Manager: failed to run ${func.name} for ${version}, but failed`, error);
if (counter < 4) {
console.log(`Binary Manager: Trying again to run ${func.name}`);
await rerunner(func, counter + 1);
}
}
};
const binaryCleaner = async () => {
if (await this.getIsDownloaded(version)) {
// This is necessary since we're messing with .asar files inside
// the Electron binaries. Electron, powering Fiddle, will try to
// "correct" our calls, but we don't want that right here.
process.noAsar = true;
await fs.remove(this.getDownloadPath(version));
process.noAsar = false;
isDeleted = true;
}
};
const typeDefsCleaner = async () => {
await this.removeTypeDefsForVersion(version);
};
await rerunner(binaryCleaner);
if (isDeleted) {
await rerunner(typeDefsCleaner);
}
const { state } = appState.versions[version];
if (state === VersionState.downloading || state === VersionState.unzipping) {
console.log(`Binary: Electron ${version} already downloading.`);
return;
}
/**
* General setup, called with a version. Is called during construction
* to ensure that we always have or download at least one version.
*
* @param {string} iVersion
* @returns {Promise<void>}
*/
public async setup(iVersion: string): Promise<void> {
const version = normalizeVersion(iVersion);
const fs = await fancyImport<typeof fsType>('fs-extra');
const { promisify } = await import('util');
const eDownload = promisify(require('electron-download'));
if (await getIsDownloaded(version)) {
console.log(`Binary: Electron ${version} already downloaded.`);
appState.versions[version].state = VersionState.ready;
return;
}
await fs.mkdirp(this.getDownloadPath(version));
console.log(`Binary: Electron ${version} not present, downloading`);
appState.versions[version].state = VersionState.downloading;
if (this.state[version] === 'downloading') {
console.log(`BinaryManager: Electron ${version} already downloading.`);
return;
}
const zipPath = await download(appState, version);
const extractPath = getDownloadPath(version);
console.log(`Binary: Electron ${version} downloaded, now unpacking to ${extractPath}`);
if (await this.getIsDownloaded(version)) {
console.log(`BinaryManager: Electron ${version} already downloaded.`);
this.state[version] = 'ready';
return;
}
try {
appState.versions[version].state = VersionState.unzipping;
console.log(`BinaryManager: Electron ${version} not present, downloading`);
this.state[version] = 'downloading';
// Ensure the target path is empty
await fs.emptyDir(extractPath);
const zipPath = await eDownload({ version });
const extractPath = this.getDownloadPath(version);
console.log(`BinaryManager: Electron ${version} downloaded, now unpacking to ${extractPath}`);
const electronFiles = await unzip(zipPath, extractPath);
console.log(`Unzipped ${version}`, electronFiles);
} catch (error) {
console.warn(`Failure while unzipping ${version}`, error);
appState.versions[version].state = VersionState.unknown;
return;
}
appState.versions[version].state = VersionState.ready;
}
/**
* Remove a version from disk. Does not update state. We'll try up to
* four times before giving up if an error occurs.
*
* @param {string} iVersion
* @returns {Promise<void>}
*/
export async function removeBinary(iVersion: string) {
const version = normalizeVersion(iVersion);
const fs = await fancyImport<typeof fsType>('fs-extra');
let isDeleted = false;
// utility to re-run removal functions upon failure
// due to windows filesystem lockfile jank
const rerunner = async (func: () => Promise<void>, counter: number = 1) => {
try {
// Ensure the target path is empty
await fs.emptyDir(extractPath);
const electronFiles = await this.unzip(zipPath, extractPath);
console.log(`Unzipped ${version}`, electronFiles);
await func();
} catch (error) {
console.warn(`Failure while unzipping ${version}`, error);
// TODO: Handle this case
}
this.state[version] = 'ready';
}
/**
* Gets the expected path for the binary of a given Electron version
*
* @param {string} version
* @param {string} dir
* @returns {string}
*/
public getElectronBinaryPath(
version: string,
dir: string = this.getDownloadPath(version),
): string {
switch (process.platform) {
case 'darwin':
return path.join(dir, 'Electron.app/Contents/MacOS/Electron');
case 'freebsd':
case 'linux':
return path.join(dir, 'electron');
case 'win32':
return path.join(dir, 'electron.exe');
default:
throw new Error(`Electron builds are not available for ${process.platform}`);
}
}
/**
* Returns an array of all versions downloaded to disk
*
* @returns {Promise<Array<string>>}
*/
public async getDownloadedVersions(): Promise<Array<string>> {
const fs = await fancyImport<typeof fsType>('fs-extra');
const downloadPath = path.join(USER_DATA_PATH, 'electron-bin');
console.log(`BinaryManager: Checking for downloaded versions`);
try {
const directories = await fs.readdir(downloadPath);
const knownVersions: Array<string> = [];
for (const directory of directories) {
if (await this.getIsDownloaded(directory)) {
knownVersions.push(directory);
}
}
return knownVersions;
} catch (error) {
console.warn(`Could not read known Electron versions`);
return [];
}
}
public getDownloadingVersions() {
return Object.entries(this.state)
.filter(([_, state]) => state === 'downloading')
.map(([version, _]) => version);
}
/**
* Did we already download a given version?
*
* @param {string} version
* @param {string} dir
* @returns {boolean}
*/
public async getIsDownloaded(version: string, dir?: string): Promise<boolean> {
const expectedPath = this.getElectronBinaryPath(version, dir);
const fs = await fancyImport<typeof fsType>('fs-extra');
return fs.existsSync(expectedPath);
}
public async removeTypeDefsForVersion(version: string) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const _version = normalizeVersion(version);
const typeDefsDir = path.dirname(getOfflineTypeDefinitionPath(_version));
if (fs.existsSync(typeDefsDir)) {
try {
await fs.remove(typeDefsDir);
} catch (error) {
throw error;
console.warn(`Binary Manager: failed to run ${func.name} for ${version}, but failed`, error);
if (counter < 4) {
console.log(`Binary Manager: Trying again to run ${func.name}`);
await rerunner(func, counter + 1);
}
}
}
/**
* Gets the expected path for a given Electron version
*
* @param {string} version
* @returns {string}
*/
private getDownloadPath(version: string): string {
return path.join(USER_DATA_PATH, 'electron-bin', version);
}
/**
* Unzips an electron package so that we can actually use it.
*
* @param {string} zipPath
* @param {string} extractPath
* @returns {Promise<void>}
*/
private unzip(zipPath: string, extractPath: string): Promise<void> {
return new Promise(async (resolve, reject) => {
const extract = (await fancyImport<any>('extract-zip')).default;
};
const binaryCleaner = async () => {
if (await getIsDownloaded(version)) {
// This is necessary since we're messing with .asar files inside
// the Electron binaries. Electron, powering Fiddle, will try to
// "correct" our calls, but we don't want that right here.
process.noAsar = true;
await fs.remove(getDownloadPath(version));
process.noAsar = false;
const options = {
dir: extractPath,
};
isDeleted = true;
}
};
extract(zipPath, options, (error: Error) => {
if (error) {
reject(error);
return;
}
const typeDefsCleaner = async () => {
await removeTypeDefsForVersion(version);
};
console.log(`BinaryManager: Unpacked!`);
process.noAsar = false;
await rerunner(binaryCleaner);
resolve();
});
});
if (isDeleted) {
await rerunner(typeDefsCleaner);
}
}
/* Did we already download a given version?
*
* @param {string} version
* @param {string} dir
* @returns {boolean}
*/
export async function getIsDownloaded(version: string, dir?: string): Promise<boolean> {
const expectedPath = getElectronBinaryPath(version, dir);
const fs = await fancyImport<typeof fsType>('fs-extra');
return fs.existsSync(expectedPath);
}
/**
* Gets the expected path for the binary of a given Electron version
*
* @param {string} version
* @param {string} dir
* @returns {string}
*/
export function getElectronBinaryPath(
version: string,
dir: string = getDownloadPath(version),
): string {
switch (process.platform) {
case 'darwin':
return path.join(dir, 'Electron.app/Contents/MacOS/Electron');
case 'freebsd':
case 'linux':
return path.join(dir, 'electron');
case 'win32':
return path.join(dir, 'electron.exe');
default:
throw new Error(`Electron builds are not available for ${process.platform}`);
}
}
export function getDownloadingVersions(appState: AppState) {
return Object.entries(appState.versions)
.filter(([_, { state }]) => state === 'downloading')
.map(([version, _]) => version);
}
/**
* Returns an array of all versions downloaded to disk
*
* @returns {Promise<Array<string>>}
*/
export async function getDownloadedVersions(): Promise<Array<string>> {
const fs = await fancyImport<typeof fsType>('fs-extra');
const downloadPath = path.join(USER_DATA_PATH, 'electron-bin');
console.log(`Binary: Checking for downloaded versions`);
try {
const directories = await fs.readdir(downloadPath);
const knownVersions: Array<string> = [];
for (const directory of directories) {
if (await getIsDownloaded(directory)) {
knownVersions.push(directory);
}
}
return knownVersions;
} catch (error) {
console.warn(`Could not read known Electron versions`);
return [];
}
}
/**
* Download an Electron version.
*
* @param {AppState} appState
* @param {string} version
* @returns {Promise<string>}
*/
async function download(appState: AppState, version: string): Promise<string> {
const { download: electronDownload } = await import('@electron/get');
const getProgressCallback = (progress: Progress) => {
const roundedProgress = Math.round(progress.percent * 100) / 100;
if (roundedProgress !== appState.versions[version].downloadProgress) {
console.debug(`Binary: Version ${version} download progress: ${progress.percent}`);
appState.versions[version].downloadProgress = roundedProgress;
}
};
if (!appState.versions[version]) {
throw new Error(`Version ${version} does not exist in state, cannot download`);
}
const zipFilePath = await electronDownload(version, {
downloadOptions: {
quiet: true,
getProgressCallback
}
});
return zipFilePath;
}
/**
* Gets the expected path for a given Electron version
*
* @param {string} version
* @returns {string}
*/
function getDownloadPath(version: string): string {
return path.join(USER_DATA_PATH, 'electron-bin', version);
}
/**
* Unzips an electron package so that we can actually use it.
*
* @param {string} zipPath
* @param {string} extractPath
* @returns {Promise<void>}
*/
function unzip(zipPath: string, extractPath: string): Promise<void> {
return new Promise(async (resolve, reject) => {
const extract = (await fancyImport<any>('extract-zip')).default;
process.noAsar = true;
const options = {
dir: extractPath,
};
extract(zipPath, options, (error: Error) => {
if (error) {
reject(error);
return;
}
console.log(`Binary: Unpacked!`);
process.noAsar = false;
resolve();
});
});
}
interface Progress {
percent: number;
transferred: number;
total: number;
}
+3 -3
View File
@@ -1,12 +1,12 @@
import { ElectronVersion } from '../interfaces';
import { RunnableVersion } from '../interfaces';
export class Bisector {
public revList: Array<ElectronVersion>;
public revList: Array<RunnableVersion>;
public minRev: number;
public maxRev: number;
private pivot: number;
constructor(revList: Array<ElectronVersion>) {
constructor(revList: Array<RunnableVersion>) {
this.getCurrentVersion = this.getCurrentVersion.bind(this);
this.continue = this.continue.bind(this);
this.calculatePivot = this.calculatePivot.bind(this);
+6 -1
View File
@@ -2,7 +2,7 @@ import { Button } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { GenericDialogType } from '../../../src/interfaces';
import { GenericDialogType, VersionState } from '../../../src/interfaces';
import { AppState } from '../state';
interface BisectHandlerProps {
@@ -19,6 +19,8 @@ export class BisectHandler extends React.Component<BisectHandlerProps> {
}
public continueBisect(isGood: boolean) {
window.ElectronFiddle.app.runner.stop();
const { appState } = this.props;
const response = appState.Bisector!.continue(isGood);
@@ -49,15 +51,18 @@ export class BisectHandler extends React.Component<BisectHandlerProps> {
public render() {
const { appState } = this.props;
if (!!appState.Bisector) {
const isDownloading = appState.currentElectronVersion.state === VersionState.downloading;
return (
<>
<Button
icon={'thumbs-up'}
onClick={() => this.continueBisect(true)}
disabled={isDownloading}
/>
<Button
icon={'thumbs-down'}
onClick={() => this.continueBisect(false)}
disabled={isDownloading}
/>
<Button
icon={'cross'}
@@ -77,6 +77,7 @@ export class EditorDropdown extends React.Component<EditorDropdownProps, EditorD
text={TITLE_MAP[id]}
id={id}
onClick={this.onItemClick}
disabled={appState.mosaicArrangement === id} // can't hide last editor panel
/>
);
}
+9 -6
View File
@@ -2,7 +2,7 @@ import { Button, IButtonProps, Spinner } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersionState } from '../../interfaces';
import { VersionState } from '../../interfaces';
import { AppState } from '../state';
export interface RunnerState {
@@ -25,13 +25,17 @@ export class Runner extends React.Component<RunnerProps, RunnerState> {
const { isRunning, currentElectronVersion } = this.props.appState;
const state = currentElectronVersion && currentElectronVersion.state;
const props: IButtonProps = { className: 'button-run' };
const props: IButtonProps = { className: 'button-run', disabled: true };
if (state === ElectronVersionState.downloading) {
if (state === VersionState.downloading) {
props.text = 'Downloading';
props.disabled = true;
props.icon = <Spinner size={16} value={currentElectronVersion?.downloadProgress} />;
} else if (state === VersionState.unzipping) {
props.text = 'Unzipping';
props.icon = <Spinner size={16} />;
} else if (state === ElectronVersionState.ready) {
} else if (state === VersionState.ready) {
props.disabled = false;
if (isRunning) {
props.active = true;
props.text = 'Stop';
@@ -44,7 +48,6 @@ export class Runner extends React.Component<RunnerProps, RunnerState> {
}
} else {
props.text = 'Checking status';
props.disabled = true;
props.icon = <Spinner size={16} />;
}
@@ -8,6 +8,7 @@ import * as semver from 'semver';
import { Version } from '../../interfaces';
import { IpcEvents } from '../../ipc-events';
import { getElectronNameForPlatform } from '../../utils/electron-name';
import { getIsDownloaded } from '../binary';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
@@ -58,8 +59,7 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public async setFolderPath(folderPath: string) {
const { binaryManager } = this.props.appState;
const isValidElectron = !!await binaryManager.getIsDownloaded('custom', folderPath);
const isValidElectron = !!await getIsDownloaded('custom', folderPath);
this.setState({ folderPath, isValidElectron });
}
+8 -8
View File
@@ -2,7 +2,7 @@ import { Button, ButtonGroup, Callout, Dialog, Label } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion } from '../../interfaces';
import { RunnableVersion } from '../../interfaces';
import { Bisector } from '../bisect';
import { AppState } from '../state';
import { VersionSelect } from './version-select';
@@ -14,7 +14,7 @@ export interface BisectDialogProps {
export interface BisectDialogState {
startIndex: number;
endIndex: number;
allVersions: Array<ElectronVersion>;
allVersions: Array<RunnableVersion>;
showHelp?: boolean;
}
@@ -44,11 +44,11 @@ export class BisectDialog extends React.Component<BisectDialogProps, BisectDialo
};
}
public onBeginSelect(version: ElectronVersion) {
public onBeginSelect(version: RunnableVersion) {
this.setState({ startIndex: this.state.allVersions.indexOf(version) });
}
public onEndSelect(version: ElectronVersion) {
public onEndSelect(version: RunnableVersion) {
this.setState({ endIndex: this.state.allVersions.indexOf(version) });
}
@@ -208,10 +208,10 @@ export class BisectDialog extends React.Component<BisectDialogProps, BisectDialo
/**
* Should an item in the "earliest version" dropdown be disabled?
*
* @param {ElectronVersion} version
* @param {RunnableVersion} version
* @returns {boolean}
*/
public isEarliestItemDisabled(version: ElectronVersion): boolean {
public isEarliestItemDisabled(version: RunnableVersion): boolean {
const { allVersions, endIndex } = this.state;
// In the array, "newer" versions will have a lower index.
@@ -225,10 +225,10 @@ export class BisectDialog extends React.Component<BisectDialogProps, BisectDialo
/**
* Should an item in the "latest version" dropdown be disabled?
*
* @param {ElectronVersion} version
* @param {RunnableVersion} version
* @returns {boolean}
*/
public isLatestItemDisabled(version: ElectronVersion): boolean {
public isLatestItemDisabled(version: RunnableVersion): boolean {
const { allVersions, startIndex } = this.state;
return allVersions.indexOf(version) > startIndex - 1;
+2 -4
View File
@@ -143,8 +143,6 @@ export class Editor extends React.Component<EditorProps> {
const backup = appState.getAndRemoveEditorValueBackup(id);
console.log(id);
if (backup) {
console.log(`Editor: Backup found, restoring state`);
@@ -155,8 +153,8 @@ export class Editor extends React.Component<EditorProps> {
// If there's a model, use the model. No model? Use the value
if (backup.model) {
this.editor.setModel(backup.model);
} else if (typeof backup.value !== 'undefined') {
this.createModel(backup.value);
} else {
this.createModel(backup.value ?? '');
}
} else {
const value = await getContent(id, version);
+19 -8
View File
@@ -66,8 +66,6 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
this.setFocused = this.setFocused.bind(this);
this.state = { monacoOptions: defaultMonacoOptions };
this.loadMonaco();
}
/**
@@ -75,7 +73,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
*
* @memberof Editors
*/
public componentDidMount() {
public async componentDidMount() {
ipcRendererManager.on(IpcEvents.MONACO_EXECUTE_COMMAND, (_event, cmd: string) => {
this.executeCommand(cmd);
});
@@ -95,6 +93,8 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
});
this.setState({ isMounted: true });
await this.loadMonaco();
this.props.appState.isUnsaved = false;
}
public componentWillUnmount() {
@@ -167,10 +167,22 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
public renderToolbar(
{ title }: MosaicWindowProps<MosaicId>, id: MosaicId
): JSX.Element {
const { appState } = this.props;
const docsDemoGoHomeMaybe = id === PanelId.docsDemo
? <DocsDemoGoHomeButton id={id} appState={this.props.appState} />
? <DocsDemoGoHomeButton id={id} appState={appState} />
: null;
// only show toolbar controls if we have more than 1 visible editor
// Mosaic arrangement is type string if 1 editor, object otherwise
const toolbarControlsMaybe =
(typeof appState.mosaicArrangement !== 'string') &&
(
<>
<MaximizeButton id={id} appState={appState} />
<RemoveButton id={id} appState={appState} />
</>
);
return (
<div>
{/* Left */}
@@ -184,8 +196,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
{/* Right */}
<div className='mosaic-controls'>
{docsDemoGoHomeMaybe}
<MaximizeButton id={id} appState={this.props.appState} />
<RemoveButton id={id} appState={this.props.appState} />
{toolbarControlsMaybe}
</div>
</div>
);
@@ -280,7 +291,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
* We're doing things a bit roundabout to ensure that we're not overloading the
* mobx state with a gigantic Monaco tree.
*/
public async loadMonaco(): Promise<void> {
public async loadMonaco() {
const { app } = window.ElectronFiddle;
const loader = require('monaco-loader');
const monaco = app.monaco || await loader();
@@ -298,7 +309,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
this.setState({ monaco });
}
activateTheme(monaco, undefined, this.props.appState.theme);
await activateTheme(monaco, undefined, this.props.appState.theme);
}
/**
+75 -55
View File
@@ -7,12 +7,14 @@ import {
HTMLTable,
IButtonProps,
Icon,
IconName
IconName,
Tooltip,
ProgressBar
} from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../interfaces';
import { RunnableVersion, VersionSource, VersionState } from '../../interfaces';
import { normalizeVersion } from '../../utils/normalize-version';
import { sortedElectronMap } from '../../utils/sorted-electron-map';
import { AppState } from '../state';
@@ -69,7 +71,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
if (!checked) {
appState.statesToShow = appState.statesToShow.filter((s) => s !== id);
} else {
appState.statesToShow.push(id as ElectronVersionState);
appState.statesToShow.push(id as VersionState);
}
}
@@ -202,7 +204,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
*/
private renderVersionStateOptions(): JSX.Element {
const { appState } = this.props;
const getIsChecked = (state: ElectronVersionState) => {
const getIsChecked = (state: VersionState) => {
return appState.statesToShow.includes(state);
};
@@ -210,22 +212,29 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
<FormGroup
label='Include Electron versions that are:'
>
<Tooltip
content='Always enabled'
position='bottom'
intent='primary'
>
<Checkbox
checked={getIsChecked(VersionState.ready)}
label='Ready'
id='ready'
onChange={this.handleStateChange}
inline={true}
disabled={true}
/>
</Tooltip>
<Checkbox
checked={getIsChecked(ElectronVersionState.downloading)}
checked={getIsChecked(VersionState.downloading)}
label='Downloading'
id='downloading'
onChange={this.handleStateChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronVersionState.ready)}
label='Downloaded'
id='ready'
onChange={this.handleStateChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronVersionState.unknown)}
checked={getIsChecked(VersionState.unknown)}
label='Not Downloaded'
id='unknown'
onChange={this.handleStateChange}
@@ -243,42 +252,47 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
*/
private renderVersionChannelOptions(): JSX.Element {
const { appState } = this.props;
const getIsChecked = (channel: ElectronReleaseChannel) => {
return appState.channelsToShow.includes(channel);
};
const getIsCurrentVersionReleaseChannel = (channel: ElectronReleaseChannel) => {
return getReleaseChannel(appState.version) === channel;
};
const channels = {
stable: ElectronReleaseChannel.stable,
beta: ElectronReleaseChannel.beta,
nightly: ElectronReleaseChannel.nightly,
unsupported: ElectronReleaseChannel.unsupported
};
return (
<FormGroup
label='Include Electron versions from these release channels:'
>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.stable)}
label='Stable'
id='Stable'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.beta)}
label='Beta'
id='Beta'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.nightly)}
label='Nightly'
id='Nightly'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.unsupported)}
label='Unsupported'
id='Unsupported'
onChange={this.handleChannelChange}
inline={true}
/>
{
// tslint:disable-next-line:jsx-no-multiline-js
Object.entries(channels).map(([_, channel]) => (
<Tooltip
content={`Can't disable channel of selected version (${appState.version})`}
disabled={!getIsCurrentVersionReleaseChannel(channel)}
position='bottom'
intent='primary'
key={channel}
>
<Checkbox
checked={getIsChecked(channel)}
label={channel}
id={channel}
onChange={this.handleChannelChange}
disabled={getIsCurrentVersionReleaseChannel(channel)}
inline={true}
/>
</Tooltip>
))
}
</FormGroup>
);
}
@@ -342,25 +356,25 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
/**
* Returns a human-readable state indicator for an Electron version
*
* @param {ElectronVersion} item
* @param {RunnableVersion} item
* @returns {JSX.Element}
*/
private renderHumanState(item: ElectronVersion): JSX.Element {
private renderHumanState(item: RunnableVersion): JSX.Element {
const { state } = item;
let icon: IconName = 'box';
if (state === VersionState.downloading || state === VersionState.unzipping) {
return <ProgressBar value={item.downloadProgress} />;
}
let humanState = 'Downloaded';
if (state === ElectronVersionState.downloading) {
icon = 'cloud-download';
humanState = 'Downloading';
} else if (state === ElectronVersionState.unknown) {
icon = 'cloud';
if (state === VersionState.unknown) {
humanState = 'Not downloaded';
}
return (
<span>
<Icon icon={icon} /> {humanState}
{humanState}
</span>
);
}
@@ -370,10 +384,10 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
*
* @private
* @param {string} key
* @param {ElectronVersion} item
* @param {RunnableVersion} item
* @returns {JSX.Element}
*/
private renderAction(key: string, item: ElectronVersion): JSX.Element {
private renderAction(key: string, item: RunnableVersion): JSX.Element {
const { state, source } = item;
const { appState } = this.props;
const buttonProps: IButtonProps = {
@@ -382,17 +396,23 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
};
// Already downloaded
if (state === 'ready') {
if (state === VersionState.ready) {
buttonProps.disabled = false;
buttonProps.onClick = () => appState.removeVersion(key);
buttonProps.icon = 'trash';
buttonProps.text = source === ElectronVersionSource.local
buttonProps.text = source === VersionSource.local
? 'Remove'
: 'Delete';
} else if (state === 'downloading') {
} else if (state === VersionState.downloading) {
buttonProps.disabled = true;
buttonProps.loading = true;
buttonProps.text = 'Downloading';
buttonProps.icon = 'cloud-download';
} else if (state === VersionState.unzipping) {
buttonProps.disabled = true;
buttonProps.loading = true;
buttonProps.text = 'Unzipping';
buttonProps.icon = 'cloud-download';
} else {
buttonProps.disabled = false;
buttonProps.loading = false;
@@ -19,7 +19,7 @@ const ThemeSelect = Select.ofType<LoadedFiddleTheme>();
* version.
*
* @param {string} query
* @param {ElectronVersion} { version }
* @param {RunnableVersion} { version }
* @returns
*/
export const filterItem: ItemPredicate<LoadedFiddleTheme> = (query, { name }) => {
@@ -31,7 +31,7 @@ export const filterItem: ItemPredicate<LoadedFiddleTheme> = (query, { name }) =>
* Helper method: Returns the <Select /> <MenuItem /> for Electron
* versions.
*
* @param {ElectronVersion} item
* @param {RunnableVersion} item
* @param {IItemRendererProps} { handleClick, modifiers, query }
* @returns
*/
+17 -17
View File
@@ -3,30 +3,30 @@ import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../interfaces';
import { RunnableVersion, VersionSource, VersionState } from '../../interfaces';
import { highlightText } from '../../utils/highlight-text';
import { AppState } from '../state';
const ElectronVersionSelect = Select.ofType<ElectronVersion>();
const ElectronVersionSelect = Select.ofType<RunnableVersion>();
/**
* Helper method: Returns the <Select /> label for an Electron
* version.
*
* @param {ElectronVersion} { source, state }
* @param {RunnableVersion} { source, state }
* @returns {string}
*/
export function getItemLabel({ source, state, name }: ElectronVersion): string {
export function getItemLabel({ source, state, name }: RunnableVersion): string {
let label = '';
if (source === ElectronVersionSource.local) {
if (source === VersionSource.local) {
label = name || 'Local';
} else {
if (state === ElectronVersionState.unknown) {
if (state === VersionState.unknown) {
label = `Not downloaded`;
} else if (state === ElectronVersionState.ready) {
} else if (state === VersionState.ready) {
label = `Downloaded`;
} else if (state === ElectronVersionState.downloading) {
} else if (state === VersionState.downloading) {
label = `Downloading`;
}
}
@@ -38,10 +38,10 @@ export function getItemLabel({ source, state, name }: ElectronVersion): string {
* Helper method: Returns the <Select /> icon for an Electron
* version.
*
* @param {ElectronVersion} { state }
* @param {RunnableVersion} { state }
* @returns
*/
export function getItemIcon({ state }: ElectronVersion) {
export function getItemIcon({ state }: RunnableVersion) {
return state === 'ready'
? 'saved'
: state === 'downloading' ? 'cloud-download' : 'cloud';
@@ -52,10 +52,10 @@ export function getItemIcon({ state }: ElectronVersion) {
* version.
*
* @param {string} query
* @param {ElectronVersion} { version }
* @param {RunnableVersion} { version }
* @returns
*/
export const filterItem: ItemPredicate<ElectronVersion> = (query, { version }) => {
export const filterItem: ItemPredicate<RunnableVersion> = (query, { version }) => {
return version.toLowerCase().includes(query.toLowerCase());
};
@@ -63,11 +63,11 @@ export const filterItem: ItemPredicate<ElectronVersion> = (query, { version }) =
* Helper method: Returns the <Select /> <MenuItem /> for Electron
* versions.
*
* @param {ElectronVersion} item
* @param {RunnableVersion} item
* @param {IItemRendererProps} { handleClick, modifiers, query }
* @returns
*/
export const renderItem: ItemRenderer<ElectronVersion> = (item, { handleClick, modifiers, query }) => {
export const renderItem: ItemRenderer<RunnableVersion> = (item, { handleClick, modifiers, query }) => {
if (!modifiers.matchesPredicate) {
return null;
}
@@ -92,10 +92,10 @@ export interface VersionSelectState {
export interface VersionSelectProps {
appState: AppState;
disabled?: boolean;
currentVersion: ElectronVersion;
onVersionSelect: (version: ElectronVersion) => void;
currentVersion: RunnableVersion;
onVersionSelect: (version: RunnableVersion) => void;
buttonGroupProps?: IButtonGroupProps;
itemDisabled?: keyof ElectronVersion | ((item: ElectronVersion, index: number) => boolean);
itemDisabled?: keyof RunnableVersion | ((item: RunnableVersion, index: number) => boolean);
}
/**
+1 -3
View File
@@ -21,9 +21,7 @@ export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
};
export const DEFAULT_CLOSED_PANELS: Partial<Record<MosaicId, EditorBackup | true>> = {
docsDemo: true,
preload: {},
css: {}
docsDemo: true
};
export const ELECTRON_ORG = 'electron';
+28 -7
View File
@@ -2,9 +2,10 @@ import * as fsType from 'fs-extra';
import * as MonacoType from 'monaco-editor';
import * as path from 'path';
import { ElectronVersion, ElectronVersionSource } from '../interfaces';
import { RunnableVersion, VersionSource } from '../interfaces';
import { callIn } from '../utils/call-in';
import { fancyImport } from '../utils/import';
import { normalizeVersion } from '../utils/normalize-version';
import { USER_DATA_PATH } from './constants';
const definitionPath = path.join(USER_DATA_PATH, 'electron-typedef');
@@ -36,6 +37,26 @@ export async function fetchTypeDefinitions(version: string): Promise<string> {
}
}
/**
* Removes the type definition for a given version
*
* @param version
*/
export async function removeTypeDefsForVersion(version: string) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const _version = normalizeVersion(version);
const typeDefsDir = path.dirname(getOfflineTypeDefinitionPath(_version));
if (fs.existsSync(typeDefsDir)) {
try {
await fs.remove(typeDefsDir);
} catch (error) {
throw error;
}
}
}
/**
* Get the path for offline TypeScript definitions
*
@@ -64,7 +85,7 @@ export async function getOfflineTypeDefinitions(version: string): Promise<boolea
* @param {string} version
* @returns {void}
*/
export async function getDownloadedVersionTypeDefs(version: ElectronVersion): Promise<string | null> {
export async function getDownloadedVersionTypeDefs(version: RunnableVersion): Promise<string | null> {
const fs = await fancyImport<typeof fsType>('fs-extra');
await fs.mkdirp(definitionPath);
const offlinePath = getOfflineTypeDefinitionPath(version.version);
@@ -91,8 +112,8 @@ export async function getDownloadedVersionTypeDefs(version: ElectronVersion): Pr
}
}
export async function getLocalVersionTypeDefs(version: ElectronVersion) {
if (version.source === ElectronVersionSource.local && !!version.localPath) {
export async function getLocalVersionTypeDefs(version: RunnableVersion) {
if (version.source === VersionSource.local && !!version.localPath) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const typesPath = getLocalTypePathForVersion(version);
if (!!typesPath && fs.existsSync(typesPath)) {
@@ -107,7 +128,7 @@ export async function getLocalVersionTypeDefs(version: ElectronVersion) {
*
* @param {string} version
*/
export async function updateEditorTypeDefinitions(version: ElectronVersion, i: number = 0): Promise<void> {
export async function updateEditorTypeDefinitions(version: RunnableVersion, i: number = 0): Promise<void> {
const defer = async (): Promise<void> => {
if (i > 10) {
console.warn(`Fetch Types: Failed, dependencies do not exist`);
@@ -125,7 +146,7 @@ export async function updateEditorTypeDefinitions(version: ElectronVersion, i: n
const monaco: typeof MonacoType = app.monaco!;
const typeDefDisposable: MonacoType.IDisposable = app.typeDefDisposable!;
const getTypeDefs = (version.source === ElectronVersionSource.local) ?
const getTypeDefs = (version.source === VersionSource.local) ?
getLocalVersionTypeDefs : getDownloadedVersionTypeDefs;
const typeDefs = await getTypeDefs(version);
@@ -143,7 +164,7 @@ export async function updateEditorTypeDefinitions(version: ElectronVersion, i: n
}
}
export function getLocalTypePathForVersion(version: ElectronVersion) {
export function getLocalTypePathForVersion(version: RunnableVersion) {
if (version.localPath) {
return path.join(
version.localPath,
-1
View File
@@ -110,7 +110,6 @@ export class FileManager {
}
this.appState.isUnsaved = false;
window.ElectronFiddle.app.setupUnsavedOnChangeListener();
}
}
+5 -4
View File
@@ -6,6 +6,7 @@ import { IpcEvents } from '../ipc-events';
import { getAppDataDir } from '../utils/app-data-dir';
import { PackageJsonOptions } from '../utils/get-package';
import { maybePlural } from '../utils/plural-maybe';
import { getElectronBinaryPath, getIsDownloaded } from './binary';
import { ipcRendererManager } from './ipc';
import { findModulesInEditors, getIsNpmInstalled, installModules, npmRun } from './npm';
import { AppState } from './state';
@@ -44,7 +45,7 @@ export class Runner {
public async run(): Promise<boolean> {
const { fileManager, getEditorValues } = window.ElectronFiddle.app;
const options = { includeDependencies: false, includeElectron: false };
const { binaryManager, currentElectronVersion } = this.appState;
const { currentElectronVersion } = this.appState;
const { version, localPath } = currentElectronVersion;
if (this.appState.isClearingConsoleOnRun) {
@@ -65,7 +66,7 @@ export class Runner {
return false;
}
const isReady = await binaryManager.getIsDownloaded(version, localPath);
const isReady = await getIsDownloaded(version, localPath);
if (!isReady) {
console.warn(`Runner: Binary ${version} not ready`);
@@ -184,9 +185,9 @@ export class Runner {
* @memberof Runner
*/
public async execute(dir: string): Promise<void> {
const { currentElectronVersion, pushOutput, binaryManager } = this.appState;
const { currentElectronVersion, pushOutput } = this.appState;
const { version, localPath } = currentElectronVersion;
const binaryPath = binaryManager.getElectronBinaryPath(version, localPath);
const binaryPath = getElectronBinaryPath(version, localPath);
console.log(`Runner: Binary ${binaryPath} ready, launching`);
const env = { ...process.env };
+39 -29
View File
@@ -6,15 +6,15 @@ import {
ALL_MOSAICS,
DocsDemoPage,
EditorId,
ElectronVersion,
ElectronVersionSource,
ElectronVersionState,
GenericDialogOptions,
GenericDialogType,
MosaicId,
OutputEntry,
OutputOptions,
Version
RunnableVersion,
Version,
VersionSource,
VersionState
} from '../interfaces';
import { IpcEvents } from '../ipc-events';
import { arrayToStringMap } from '../utils/array-to-stringmap';
@@ -24,7 +24,7 @@ import { getName } from '../utils/get-title';
import { fancyImport } from '../utils/import';
import { normalizeVersion } from '../utils/normalize-version';
import { isEditorBackup, isEditorId, isPanelId } from '../utils/type-checks';
import { BinaryManager } from './binary';
import { getDownloadedVersions, getDownloadingVersions, removeBinary, setupBinary } from './binary';
import { Bisector } from './bisect';
import { DEFAULT_CLOSED_PANELS, DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
import { getContent, isContentUnchanged } from './content';
@@ -78,19 +78,19 @@ export class AppState {
@observable public channelsToShow: Array<ElectronReleaseChannel> =
this.retrieve('channelsToShow') as Array<ElectronReleaseChannel>
|| [ElectronReleaseChannel.stable, ElectronReleaseChannel.beta];
@observable public statesToShow: Array<ElectronVersionState> =
this.retrieve('statesToShow') as Array<ElectronVersionState>
|| [ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.unknown];
@observable public statesToShow: Array<VersionState> =
this.retrieve('statesToShow') as Array<VersionState>
|| [VersionState.downloading, VersionState.ready, VersionState.unknown];
@observable public isKeepingUserDataDirs: boolean = !!this.retrieve('isKeepingUserDataDirs');
@observable public isEnablingElectronLogging: boolean = !!this.retrieve('isEnablingElectronLogging');
@observable public isClearingConsoleOnRun: boolean = !!this.retrieve('isClearingConsoleOnRun');
@observable public executionFlags: Array<string> =
this.retrieve('executionFlags') as Array<string> === null ?
[] : this.retrieve('executionFlags') as Array<string>;
[] : this.retrieve('executionFlags') as Array<string>;
// -- Various session-only state ------------------
@observable public gistId: string = '';
@observable public versions: Record<string, ElectronVersion> = arrayToStringMap(knownVersions);
@observable public versions: Record<string, RunnableVersion> = arrayToStringMap(knownVersions);
@observable public output: Array<OutputEntry> = [];
@observable public localPath: string | undefined;
@observable public genericDialogOptions = { type: GenericDialogType.warning, label: '', ok: 'Okay', cancel: 'Cancel' };
@@ -99,12 +99,11 @@ export class AppState {
@observable public templateName: string | undefined;
@observable public currentDocsDemoPage: DocsDemoPage = DocsDemoPage.DEFAULT;
@observable public localTypeWatcher: fsType.FSWatcher | undefined;
@observable public binaryManager: BinaryManager = new BinaryManager();
@observable public Bisector: Bisector | undefined;
@observable public isPublishing: boolean = false;
@observable public isRunning: boolean = false;
@observable public isUnsaved: boolean = false;
@observable public isUnsaved: boolean;
@observable public isUpdatingElectronVersions: boolean = false;
// -- Various "isShowing" settings ------------------
@@ -167,8 +166,11 @@ export class AppState {
autorun(() => this.save('statesToShow', this.statesToShow));
autorun(() => {
if (this.isUnsaved) {
if (typeof this.isUnsaved === 'undefined') return;
if (!!this.isUnsaved) {
window.onbeforeunload = () => {
ipcRendererManager.send(IpcEvents.SHOW_INACTIVE);
this.setGenericDialogOptions({
type: GenericDialogType.warning,
label: `The current Fiddle is unsaved. Do you want to exit anyway?`,
@@ -199,6 +201,15 @@ export class AppState {
};
} else {
window.onbeforeunload = null;
// set up editor listeners to verify if unsaved
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
const editor = window.ElectronFiddle.editors[key];
const disposable = editor.onDidChangeModelContent(() => {
this.isUnsaved = true;
disposable.dispose();
});
});
}
});
@@ -210,10 +221,10 @@ export class AppState {
}
/**
* Returns the current ElectronVersion or the first
* Returns the current RunnableVersion or the first
* one that can be found.
*/
@computed get currentElectronVersion(): ElectronVersion {
@computed get currentElectronVersion(): RunnableVersion {
if (this.versions[this.version]) {
return this.versions[this.version];
} else {
@@ -225,8 +236,8 @@ export class AppState {
* Returns an array of Electron versions to show given the
* current settings for states and channels to display
*/
@computed get versionsToShow(): Array<ElectronVersion> {
return sortedElectronMap<ElectronVersion>(this.versions, (_key, item) => item)
@computed get versionsToShow(): Array<RunnableVersion> {
return sortedElectronMap<RunnableVersion>(this.versions, (_key, item) => item)
.filter((item) => {
if (!item) {
return false;
@@ -376,7 +387,7 @@ export class AppState {
const updatedVersions = { ...this.versions };
// Actually remove
if (release && release.source === ElectronVersionSource.local) {
if (release && release.source === VersionSource.local) {
delete updatedVersions[version];
const versionsAsArray = Object
@@ -385,8 +396,8 @@ export class AppState {
saveLocalVersions(versionsAsArray);
} else {
await this.binaryManager.remove(version);
updatedVersions[version].state = ElectronVersionState.unknown;
await removeBinary(version);
updatedVersions[version].state = VersionState.unknown;
}
this.versions = updatedVersions;
@@ -404,18 +415,17 @@ export class AppState {
console.log(`State: Downloading Electron ${version}`);
const release = this.versions[version] || { state: '', source: '' };
const isLocal = release.source === ElectronVersionSource.local;
const isLocal = release.source === VersionSource.local;
const isReady = release.state === 'ready';
// Fetch new binaries, maybe?
if (!isLocal && !isReady) {
console.log(`State: Instructing BinaryManager to fetch v${version}`);
console.log(`State: Fetching v${version}`);
const updatedVersions = { ...this.versions };
updatedVersions[version] = updatedVersions[version] || {};
updatedVersions[version].state = ElectronVersionState.downloading;
this.versions = updatedVersions;
await this.binaryManager.setup(version);
await setupBinary(this, version);
this.updateDownloadedVersionState();
} else {
console.log(`State: Version ${version} already downloaded, doing nothing.`);
@@ -451,7 +461,7 @@ export class AppState {
// Update TypeScript definitions
const versionObject = this.versions[version];
if (versionObject.source === ElectronVersionSource.local) {
if (versionObject.source === VersionSource.local) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const typePath = getLocalTypePathForVersion(versionObject);
console.info(`TypeDefs: Watching file for local version ${version} at path ${typePath}`);
@@ -481,17 +491,17 @@ export class AppState {
const updatedVersions = { ...this.versions };
// Keep state of currently downloading binaries first
const downloadingVersions = this.binaryManager.getDownloadingVersions();
const downloadingVersions = getDownloadingVersions(this);
(downloadingVersions || []).forEach((version) => {
if (updatedVersions[version]) {
updatedVersions[version].state = ElectronVersionState.downloading;
updatedVersions[version].state = VersionState.downloading;
}
});
const downloadedVersions = await this.binaryManager.getDownloadedVersions();
const downloadedVersions = await getDownloadedVersions();
(downloadedVersions || []).forEach((version) => {
if (updatedVersions[version]) {
updatedVersions[version].state = ElectronVersionState.ready;
updatedVersions[version].state = VersionState.ready;
}
});
+3 -3
View File
@@ -5,7 +5,7 @@ import {
} from 'electron';
import { autorun } from 'mobx';
import { ElectronVersion, ElectronVersionState } from '../interfaces';
import { RunnableVersion, VersionState } from '../interfaces';
import { getNiceGreeting } from '../utils/nice-greeting';
import { sortedElectronMap } from '../utils/sorted-electron-map';
import { AppState } from './state';
@@ -22,11 +22,11 @@ const {
* Helper method: Returns an icon (emoji, lol) for an Electron
* version.
*
* @param {Partial<ElectronVersion>} { state }
* @param {Partial<RunnableVersion>} { state }
* @returns {string}
*/
export function getItemIcon(
{ state }: Partial<ElectronVersion> = { state: ElectronVersionState.unknown }
{ state }: Partial<RunnableVersion> = { state: VersionState.unknown }
) {
return state === 'ready'
? '💾'
+18 -19
View File
@@ -1,5 +1,5 @@
import semver from 'semver';
import { ElectronVersion, ElectronVersionSource, ElectronVersionState, Version } from '../interfaces';
import { RunnableVersion, Version, VersionSource, VersionState } from '../interfaces';
import { normalizeVersion } from '../utils/normalize-version';
export const enum ElectronReleaseChannel {
@@ -12,11 +12,11 @@ export const enum ElectronReleaseChannel {
/**
* Returns a sensible default version string.
*
* @param {Array<ElectronVersion>} knownVersions
* @param {Array<RunnableVersion>} knownVersions
* @returns {string}
*/
export function getDefaultVersion(
knownVersions: Array<ElectronVersion> = []
knownVersions: Array<RunnableVersion> = []
): string {
const ls = localStorage.getItem('version');
@@ -52,7 +52,6 @@ export function getDefaultVersion(
export function getReleaseChannel(
input: Version | string
): ElectronReleaseChannel {
const tag = (typeof input === 'string') ? input : (input.version || '');
if (tag.includes('beta')) {
@@ -128,20 +127,20 @@ function saveVersions(key: VersionKeys, versions: Array<Version>) {
*
* @returns {Array<Version>}
*/
export function getElectronVersions(): Array<ElectronVersion> {
const known: Array<ElectronVersion> = getKnownVersions().map((version) => {
export function getElectronVersions(): Array<RunnableVersion> {
const known: Array<RunnableVersion> = getKnownVersions().map((version) => {
return {
...version,
source: ElectronVersionSource.remote,
state: ElectronVersionState.unknown
source: VersionSource.remote,
state: VersionState.unknown
};
});
const local: Array<ElectronVersion> = getLocalVersions().map((version) => {
const local: Array<RunnableVersion> = getLocalVersions().map((version) => {
return {
...version,
source: ElectronVersionSource.local,
state: ElectronVersionState.ready
source: VersionSource.local,
state: VersionState.ready
};
});
@@ -182,10 +181,10 @@ export function getLocalVersions(): Array<Version> {
*
* @param {Array<Version>} versions
*/
export function saveLocalVersions(versions: Array<Version | ElectronVersion>) {
export function saveLocalVersions(versions: Array<Version | RunnableVersion>) {
const filteredVersions = versions.filter((v) => {
if (isElectronVersion(v)) {
return v.source === ElectronVersionSource.local;
return v.source === VersionSource.local;
}
return true;
@@ -218,10 +217,10 @@ export function saveKnownVersions(versions: Array<Version>) {
* saved after.
*
* @export
* @returns {Promise<Array<ElectronVersion>>}
* @returns {Promise<Array<RunnableVersion>>}
*/
export async function getUpdatedElectronVersions(
): Promise<Array<ElectronVersion>> {
): Promise<Array<RunnableVersion>> {
try {
await fetchVersions();
} catch (error) {
@@ -241,7 +240,7 @@ export async function fetchVersions() {
const data = await response.json();
// pre-0.24.0 versions were technically 'atom-shell' and cannot
// be downloaded with electron-download
// be downloaded with @electron/get
const MIN_DOWNLOAD_VERSION = '0.24.0';
const output = data
@@ -290,7 +289,7 @@ export function migrateVersions(input: Array<any> = []): Array<Version> {
}
export function isElectronVersion(
input: Version | ElectronVersion
): input is ElectronVersion {
return (input as ElectronVersion).source !== undefined;
input: Version | RunnableVersion
): input is RunnableVersion {
return (input as RunnableVersion).source !== undefined;
}
+5 -5
View File
@@ -1,16 +1,16 @@
import { ElectronVersion } from '../interfaces';
import { RunnableVersion } from '../interfaces';
import { normalizeVersion } from './normalize-version';
/**
* Takes an array of GitHub releases and returns a StringMap of
* Electron releases.
*
* @param {Array<ElectronVersion>} input
* @returns {Record<string, ElectronVersion>}
* @param {Array<RunnableVersion>} input
* @returns {Record<string, RunnableVersion>}
*/
export function arrayToStringMap(
input: Array<ElectronVersion>
): Record<string, ElectronVersion> {
input: Array<RunnableVersion>
): Record<string, RunnableVersion> {
const output = {};
input.forEach((version) => {
+5 -5
View File
@@ -1,18 +1,18 @@
import * as semver from 'semver';
import { ElectronVersion } from '../interfaces';
import { RunnableVersion } from '../interfaces';
/**
* Sorts Electron versions and returns the result of a
* map function.
*
* @param {Record<string, ElectronVersion>} versions
* @param {(key: string, version: ElectronVersion) => void} mapFn
* @param {Record<string, RunnableVersion>} versions
* @param {(key: string, version: RunnableVersion) => void} mapFn
* @returns {Array<T>}
*/
export function sortedElectronMap<T>(
versions: Record<string, ElectronVersion>,
mapFn: (key: string, version: ElectronVersion) => T
versions: Record<string, RunnableVersion>,
mapFn: (key: string, version: RunnableVersion) => T
) {
return Object.keys(versions)
.sort((a, b) => {
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,4 +1,4 @@
{
"expectedVersionCount": 322,
"lastElectronVersion": "9.0.0-beta.6"
"expectedVersionCount": 325,
"lastElectronVersion": "9.0.0-beta.9"
}
+8
View File
@@ -2,7 +2,9 @@
* @jest-environment node
*/
import { IpcEvents } from '../../src/ipc-events';
import { createContextMenu } from '../../src/main/context-menu';
import { ipcMainManager } from '../../src/main/ipc';
import {
browserWindows, getMainWindowOptions, getOrCreateMainWindow
} from '../../src/main/windows';
@@ -95,5 +97,11 @@ describe('windows', () => {
(getOrCreateMainWindow().webContents as any).emit('will-navigate', e);
expect(e.preventDefault).toHaveBeenCalled();
});
it('shows the window on IPC event', () => {
const w = getOrCreateMainWindow();
ipcMainManager.emit(IpcEvents.SHOW_INACTIVE);
expect(w.showInactive).toHaveBeenCalled();
});
});
});
-6
View File
@@ -1,6 +0,0 @@
export class MockBinaryManager {
public remove = jest.fn();
public setup = jest.fn();
public getDownloadedVersions = jest.fn();
public getDownloadingVersions = jest.fn();
}
+12 -8
View File
@@ -1,20 +1,24 @@
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../src/interfaces';
import { RunnableVersion, VersionSource, VersionState } from '../../src/interfaces';
import { arrayToStringMap } from '../../src/utils/array-to-stringmap';
export const mockVersionsArray = [
{
state: ElectronVersionState.ready,
state: VersionState.ready,
version: '2.0.2',
source: ElectronVersionSource.remote
source: VersionSource.remote
}, {
state: ElectronVersionState.ready,
state: VersionState.ready,
version: '2.0.1',
source: ElectronVersionSource.remote
source: VersionSource.remote
}, {
state: ElectronVersionState.ready,
state: VersionState.ready,
version: '1.8.7',
source: ElectronVersionSource.remote
source: VersionSource.remote
}, {
state: VersionState.ready,
version: '1.8.6',
source: VersionSource.remote
}
];
export const mockVersions: Record<string, ElectronVersion> = arrayToStringMap(mockVersionsArray);
export const mockVersions: Record<string, RunnableVersion> = arrayToStringMap(mockVersionsArray);
-21
View File
@@ -40,11 +40,9 @@ describe('Editors component', () => {
const app = new App();
const result = (await app.setup()) as HTMLDivElement;
app.setupUnsavedOnChangeListener = jest.fn();
jest.runAllTimers();
expect(result.innerHTML).toBe('Dialogs;Header;OutputEditorsWrapper;');
expect(app.setupUnsavedOnChangeListener).toHaveBeenCalled();
jest.useRealTimers();
});
@@ -174,7 +172,6 @@ describe('Editors component', () => {
const app = new App();
(app.state as Partial<AppState>) = new MockState();
app.state.isUnsaved = false;
app.setupUnsavedOnChangeListener = jest.fn();
app.setEditorValues = jest.fn();
const editorValues = {
@@ -190,7 +187,6 @@ describe('Editors component', () => {
})
.then(() => {
expect(app.state.isUnsaved).toBe(false);
expect(app.setupUnsavedOnChangeListener).toHaveBeenCalled();
done();
});
});
@@ -336,23 +332,6 @@ describe('Editors component', () => {
});
});
describe('setupUnsavedOnChangeListener()', () => {
it('listens for model change events', async () => {
const app = new App();
app.setupUnsavedOnChangeListener();
const fn = window.ElectronFiddle.editors!.renderer!
.onDidChangeModelContent;
const call = (fn as jest.Mock<any>).mock.calls[0];
const cb = call[0];
cb();
expect(app.state.isUnsaved).toBe(true);
});
});
describe('setupResizeListener()', () => {
it('attaches to the handler', () => {
window.addEventListener = jest.fn();
+65 -72
View File
@@ -1,5 +1,11 @@
import { BinaryManager } from '../../src/renderer/binary';
import { USER_DATA_PATH } from '../../src/renderer/constants';
import {
getDownloadedVersions,
getDownloadingVersions,
getElectronBinaryPath,
removeBinary,
setupBinary
} from '../../src/renderer/binary';
import { removeTypeDefsForVersion } from '../../src/renderer/fetch-types';
import { overridePlatform, resetPlatform } from '../utils';
import * as path from 'path';
@@ -22,15 +28,25 @@ jest.mock('../../src/utils/import', () => ({
}
}
}));
jest.mock('electron-download', () => {
return jest.fn();
});
jest.mock('../../src/renderer/fetch-types', () => ({
removeTypeDefsForVersion: jest.fn()
}));
jest.mock('@electron/get', () => ({
download: jest.fn()
}));
describe('binary', () => {
let binaryManager = new BinaryManager();
let mockState: any = {};
beforeEach(() => {
binaryManager = new BinaryManager();
mockState = {
versions: {
'3.0.0': {
state: 'downloading'
}
}
};
});
afterEach(() => {
@@ -43,7 +59,7 @@ describe('binary', () => {
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
await binaryManager.remove('v3.0.0');
await removeBinary('v3.0.0');
expect(fs.remove).toHaveBeenCalled();
});
@@ -55,77 +71,51 @@ describe('binary', () => {
throw new Error('Bwap bwap');
});
await binaryManager.remove('v3.0.0');
await removeBinary('v3.0.0');
expect(fs.remove).toHaveBeenCalledTimes(4);
});
it('attempts to clean up the version\'s associated typedefs', async () => {
const fs = require('fs-extra');
binaryManager.removeTypeDefsForVersion = jest.fn();
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
await binaryManager.remove('v3.0.0');
await removeBinary('v3.0.0');
expect(fs.remove).toHaveBeenCalled();
expect(binaryManager.removeTypeDefsForVersion).toHaveBeenCalled();
expect(removeTypeDefsForVersion).toHaveBeenCalled();
});
it('retries typedef cleanup upon failure', async () => {
const fs = require('fs-extra');
binaryManager.removeTypeDefsForVersion = jest.fn().mockImplementation(() => {
(removeTypeDefsForVersion as jest.Mock).mockImplementation(() => {
throw new Error('Bwap bwap');
});
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
await binaryManager.remove('v3.0.0');
expect(binaryManager.removeTypeDefsForVersion).toHaveBeenCalledTimes(4);
await removeBinary('v3.0.0');
expect(removeTypeDefsForVersion).toHaveBeenCalledTimes(4);
});
});
describe('removeTypeDefsForVersion()', () => {
it('removes a version\'s typedefs', async () => {
const fs = require('fs-extra');
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
await binaryManager.removeTypeDefsForVersion('v3.0.0');
expect(fs.remove).toHaveBeenCalledWith(path.join(USER_DATA_PATH, 'electron-typedef', '3.0.0'));
});
it('throws upon fs failure', async () => {
const fs = require('fs-extra');
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
(fs.remove as jest.Mock<any>).mockImplementation(() => {
throw new Error('Bwap bwap');
});
expect.assertions(1);
try {
await binaryManager.removeTypeDefsForVersion('v3.0.0');
} catch (e) {
expect(e).toEqual(new Error('Bwap bwap'));
}
});
});
describe('getDownloadedVersions()', () => {
it('finds downloaded versions', async () => {
const fs = require('fs-extra');
(fs.readdir as jest.Mock<any>).mockReturnValue([ 'v3.0.0' ]);
(fs.readdir as jest.Mock<any>).mockReturnValue(['v3.0.0']);
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
const result = await binaryManager.getDownloadedVersions();
const result = await getDownloadedVersions();
expect(result).toEqual([ 'v3.0.0' ]);
expect(result).toEqual(['v3.0.0']);
});
it('is okay without versions ', async () => {
const fs = require('fs-extra');
(fs.readdir as jest.Mock<any>).mockReturnValue([]);
const result = await binaryManager.getDownloadedVersions();
const result = await getDownloadedVersions();
expect(result).toEqual([]);
});
@@ -135,16 +125,14 @@ describe('binary', () => {
(fs.readdir as jest.Mock<any>).mockImplementationOnce(() => {
throw new Error('💩');
});
const result = await binaryManager.getDownloadedVersions();
const result = await getDownloadedVersions();
expect(result).toEqual([]);
});
});
describe('getDownloadingVersions()', () => {
it('returns currently downloading versions', () => {
binaryManager.state['3.0.0'] = 'downloading';
const result = binaryManager.getDownloadingVersions();
const result = getDownloadingVersions(mockState as any);
expect(result).toEqual(['3.0.0']);
});
});
@@ -153,21 +141,21 @@ describe('binary', () => {
it('returns the correct path on Windows', () => {
overridePlatform('win32');
const result = binaryManager.getElectronBinaryPath('v3.0.0');
const result = getElectronBinaryPath('v3.0.0');
expect(result).toBe(path.join('user/data/electron-bin/v3.0.0/electron.exe'));
});
it('returns the correct path on Linux', () => {
overridePlatform('linux');
const result = binaryManager.getElectronBinaryPath('v3.0.0');
const result = getElectronBinaryPath('v3.0.0');
expect(result).toBe(path.join('user/data/electron-bin/v3.0.0/electron'));
});
it('returns the correct path on macOS', () => {
overridePlatform('darwin');
const result = binaryManager.getElectronBinaryPath('v3.0.0');
const result = getElectronBinaryPath('v3.0.0');
const expected = 'user/data/electron-bin/v3.0.0/Electron.app/Contents/MacOS/Electron';
expect(result).toBe(path.join(expected));
});
@@ -175,51 +163,56 @@ describe('binary', () => {
it('throws on other platforms', () => {
overridePlatform('bleepbloop');
expect(() => binaryManager.getElectronBinaryPath('v3.0.0'))
expect(() => getElectronBinaryPath('v3.0.0'))
.toThrow();
});
});
describe('setup()', () => {
describe('setupBinary()', () => {
it(`downloads a version it hasn't seen before`, async () => {
const eDownload = require('electron-download');
eDownload.mockImplementationOnce((_p: any, c: any) => c(undefined, '/fake/path'));
const { download } = require('@electron/get');
download.mockReturnValue('/fake/path');
await binaryManager.setup('v3.0.0');
mockState.versions['3.0.0'] = { state: 'unknown' };
await setupBinary(mockState, 'v3.0.0');
expect(eDownload).toHaveBeenCalled();
expect(binaryManager.state['3.0.0']).toBe('ready');
expect(download).toHaveBeenCalled();
expect(mockState.versions['3.0.0'].state).toBe('ready');
});
it(`does not download a version again`, async () => {
binaryManager.getIsDownloaded = jest.fn(async () => true);
const fs = require('fs-extra');
(fs.existsSync as jest.Mock).mockReturnValue(true);
const { download } = require('@electron/get');
await binaryManager.setup('v3.0.0');
mockState.versions['3.0.0'] = { state: 'unknown' };
await setupBinary(mockState, 'v3.0.0');
expect(binaryManager.getIsDownloaded).toHaveBeenCalled();
expect(require('electron-download')).toHaveBeenCalledTimes(0);
expect(binaryManager.state['3.0.0']).toBe('ready');
expect(download).toHaveBeenCalledTimes(0);
expect(mockState.versions['3.0.0'].state).toBe('ready');
});
it(`does not download a version while already downloading`, async () => {
binaryManager.getIsDownloaded = jest.fn(async () => true);
binaryManager.state['3.0.0'] = 'downloading';
const fs = require('fs-extra');
const { download } = require('@electron/get');
await binaryManager.setup('v3.0.0');
(fs.existsSync as jest.Mock).mockReturnValue(true);
mockState.versions['3.0.0'].state = 'downloading';
expect(binaryManager.getIsDownloaded).toHaveBeenCalledTimes(0);
expect(require('electron-download')).toHaveBeenCalledTimes(0);
expect(binaryManager.state['3.0.0']).toBe('downloading');
await setupBinary(mockState, 'v3.0.0');
expect(download).toHaveBeenCalledTimes(0);
expect(mockState.versions['3.0.0'].state).toBe('downloading');
});
it('handles an error in the zip file', async () => {
const eDownload = require('electron-download');
eDownload.mockImplementationOnce((_p: any, c: any) => c(undefined, '/fake/path'));
const { download } = require('@electron/get');
download.mockReturnValue('/fake/path');
const mockZip = require('extract-zip');
mockZip.mockImplementationOnce((_a: any, _b: any, c: any) => c(new Error('bwap-bwap')));
await binaryManager.setup('v3.0.0');
await setupBinary(mockState, 'v3.0.0');
});
});
});
+3 -3
View File
@@ -1,11 +1,11 @@
import { ElectronVersionSource, ElectronVersionState } from '../../src/interfaces';
import { VersionSource, VersionState } from '../../src/interfaces';
import { Bisector } from '../../src/renderer/bisect';
const generateVersionRange = (rangeLength: number) =>
(new Array(rangeLength)).fill(0).map((_, i) => ({
state: ElectronVersionState.ready,
state: VersionState.ready,
version: `${i + 1}.0.0`,
source: ElectronVersionSource.local
source: VersionSource.local
}));
describe('bisect', () => {
@@ -1,19 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Bisect commands component renders bisect dialog button if no bisect instance 1`] = `
<Blueprint3.Button
icon="git-branch"
text="Bisect"
/>
`;
exports[`Bisect commands component renders helper buttons if bisect instance is active 1`] = `
exports[`Bisect commands component disables helper buttons if Electron binary is downloading 1`] = `
<Fragment>
<Blueprint3.Button
disabled={true}
icon="thumbs-up"
onClick={[Function]}
/>
<Blueprint3.Button
disabled={true}
icon="thumbs-down"
onClick={[Function]}
/>
<Blueprint3.Button
icon="cross"
onClick={[Function]}
>
Cancel Bisect
</Blueprint3.Button>
</Fragment>
`;
exports[`Bisect commands component renders bisect dialog button if no bisect instance 1`] = `
<Blueprint3.Button
icon="git-branch"
text="Bisect"
/>
`;
exports[`Bisect commands component renders helper buttons if bisect instance is active 1`] = `
<Fragment>
<Blueprint3.Button
disabled={false}
icon="thumbs-up"
onClick={[Function]}
/>
<Blueprint3.Button
disabled={false}
icon="thumbs-down"
onClick={[Function]}
/>
@@ -1,5 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EditorDropdown component disables hide button if only one editor open 1`] = `
Array [
<Blueprint3.MenuItem
disabled={false}
icon="eye-off"
id="main"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Main Process (main.js)"
/>,
<Blueprint3.MenuItem
disabled={false}
icon="eye-off"
id="renderer"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Renderer Process (renderer.js)"
/>,
<Blueprint3.MenuItem
disabled={false}
icon="eye-off"
id="preload"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Preload (preload.js)"
/>,
<Blueprint3.MenuItem
disabled={true}
icon="eye-open"
id="html"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="HTML (index.html)"
/>,
<Blueprint3.MenuItem
disabled={false}
icon="eye-off"
id="css"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Stylesheet (styles.css)"
/>,
]
`;
exports[`EditorDropdown component renders 1`] = `
<Fragment>
<Blueprint3.Popover
@@ -16,6 +16,7 @@ exports[`Runner component renders "checking status" 1`] = `
exports[`Runner component renders default 1`] = `
<Blueprint3.Button
className="button-run"
disabled={false}
icon="play"
onClick={[MockFunction]}
text="Run"
@@ -39,6 +40,7 @@ exports[`Runner component renders running 1`] = `
<Blueprint3.Button
active={true}
className="button-run"
disabled={false}
icon="stop"
onClick={[MockFunction]}
text="Stop"
@@ -26,6 +26,11 @@ exports[`VersionSelect component renders 1`] = `
"state": "unknown",
"version": "1.0.0",
},
"1.8.6": Object {
"source": "remote",
"state": "ready",
"version": "1.8.6",
},
"1.8.7": Object {
"source": "remote",
"state": "ready",
@@ -64,6 +69,11 @@ exports[`VersionSelect component renders 1`] = `
"state": "ready",
"version": "1.8.7",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.6",
},
Object {
"source": "remote",
"state": "unknown",
@@ -1,5 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Editors component does not render toolbar controls if only one editor exists 1`] = `
<div>
<div>
<h5>
Main Process (main.js)
</h5>
</div>
<div />
<div
className="mosaic-controls"
/>
</div>
`;
exports[`Editors component renders 1`] = `
<div
className="focused__undefined mosaic mosaic-drop-target"
@@ -1200,70 +1214,72 @@ exports[`Editors component renders a toolbar 1`] = `
<div
className="mosaic-controls"
>
<MaximizeButton
appState={
Object {
"currentDocsDemoPage": "DEFAULT",
"isSettingsShowing": false,
"isTokenDialogShowing": false,
"mosaicArrangement": Object {
"direction": "row",
"first": Object {
"direction": "column",
"first": "main",
<React.Fragment>
<MaximizeButton
appState={
Object {
"currentDocsDemoPage": "DEFAULT",
"isSettingsShowing": false,
"isTokenDialogShowing": false,
"mosaicArrangement": Object {
"direction": "row",
"first": Object {
"direction": "column",
"first": "main",
"second": Object {
"direction": "column",
"first": "renderer",
"second": "preload",
},
},
"second": Object {
"direction": "column",
"first": "renderer",
"second": "preload",
"first": "html",
"second": Object {
"direction": "column",
"first": "css",
"second": "docsDemo",
},
},
},
"second": Object {
"direction": "column",
"first": "html",
"second": Object {
"direction": "column",
"first": "css",
"second": "docsDemo",
},
},
},
"setGenericDialogOptions": [Function],
"setGenericDialogOptions": [Function],
}
}
}
id="main"
/>
<RemoveButton
appState={
Object {
"currentDocsDemoPage": "DEFAULT",
"isSettingsShowing": false,
"isTokenDialogShowing": false,
"mosaicArrangement": Object {
"direction": "row",
"first": Object {
"direction": "column",
"first": "main",
id="main"
/>
<RemoveButton
appState={
Object {
"currentDocsDemoPage": "DEFAULT",
"isSettingsShowing": false,
"isTokenDialogShowing": false,
"mosaicArrangement": Object {
"direction": "row",
"first": Object {
"direction": "column",
"first": "main",
"second": Object {
"direction": "column",
"first": "renderer",
"second": "preload",
},
},
"second": Object {
"direction": "column",
"first": "renderer",
"second": "preload",
"first": "html",
"second": Object {
"direction": "column",
"first": "css",
"second": "docsDemo",
},
},
},
"second": Object {
"direction": "column",
"first": "html",
"second": Object {
"direction": "column",
"first": "css",
"second": "docsDemo",
},
},
},
"setGenericDialogOptions": [Function],
"setGenericDialogOptions": [Function],
}
}
}
id="main"
/>
id="main"
/>
</React.Fragment>
</div>
</div>
`;
@@ -11,38 +11,103 @@ exports[`ElectronSettings component renders 1`] = `
<Blueprint3.FormGroup
label="Include Electron versions from these release channels:"
>
<Blueprint3.Checkbox
checked={true}
id="Stable"
inline={true}
label="Stable"
onChange={[Function]}
/>
<Blueprint3.Checkbox
checked={true}
id="Beta"
inline={true}
label="Beta"
onChange={[Function]}
/>
<Blueprint3.Checkbox
checked={false}
id="Nightly"
inline={true}
label="Nightly"
onChange={[Function]}
/>
<Blueprint3.Checkbox
checked={false}
id="Unsupported"
inline={true}
label="Unsupported"
onChange={[Function]}
/>
<Blueprint3.Tooltip
content="Can't disable channel of selected version (2.0.1)"
disabled={false}
hoverCloseDelay={0}
hoverOpenDelay={100}
intent="primary"
key="Stable"
position="bottom"
transitionDuration={100}
>
<Blueprint3.Checkbox
checked={true}
disabled={true}
id="Stable"
inline={true}
label="Stable"
onChange={[Function]}
/>
</Blueprint3.Tooltip>
<Blueprint3.Tooltip
content="Can't disable channel of selected version (2.0.1)"
disabled={true}
hoverCloseDelay={0}
hoverOpenDelay={100}
intent="primary"
key="Beta"
position="bottom"
transitionDuration={100}
>
<Blueprint3.Checkbox
checked={true}
disabled={false}
id="Beta"
inline={true}
label="Beta"
onChange={[Function]}
/>
</Blueprint3.Tooltip>
<Blueprint3.Tooltip
content="Can't disable channel of selected version (2.0.1)"
disabled={true}
hoverCloseDelay={0}
hoverOpenDelay={100}
intent="primary"
key="Nightly"
position="bottom"
transitionDuration={100}
>
<Blueprint3.Checkbox
checked={false}
disabled={false}
id="Nightly"
inline={true}
label="Nightly"
onChange={[Function]}
/>
</Blueprint3.Tooltip>
<Blueprint3.Tooltip
content="Can't disable channel of selected version (2.0.1)"
disabled={true}
hoverCloseDelay={0}
hoverOpenDelay={100}
intent="primary"
key="Unsupported"
position="bottom"
transitionDuration={100}
>
<Blueprint3.Checkbox
checked={false}
disabled={false}
id="Unsupported"
inline={true}
label="Unsupported"
onChange={[Function]}
/>
</Blueprint3.Tooltip>
</Blueprint3.FormGroup>
<Blueprint3.FormGroup
label="Include Electron versions that are:"
>
<Blueprint3.Tooltip
content="Always enabled"
hoverCloseDelay={0}
hoverOpenDelay={100}
intent="primary"
position="bottom"
transitionDuration={100}
>
<Blueprint3.Checkbox
checked={true}
disabled={true}
id="ready"
inline={true}
label="Ready"
onChange={[Function]}
/>
</Blueprint3.Tooltip>
<Blueprint3.Checkbox
checked={true}
id="downloading"
@@ -50,13 +115,6 @@ exports[`ElectronSettings component renders 1`] = `
label="Downloading"
onChange={[Function]}
/>
<Blueprint3.Checkbox
checked={true}
id="ready"
inline={true}
label="Downloaded"
onChange={[Function]}
/>
<Blueprint3.Checkbox
checked={false}
id="unknown"
@@ -122,10 +180,6 @@ exports[`ElectronSettings component renders 1`] = `
</td>
<td>
<span>
<Blueprint3.Icon
icon="box"
/>
Downloaded
</span>
</td>
@@ -133,6 +187,7 @@ exports[`ElectronSettings component renders 1`] = `
className="action"
>
<Blueprint3.Button
disabled={false}
fill={true}
icon="trash"
onClick={[Function]}
@@ -148,13 +203,7 @@ exports[`ElectronSettings component renders 1`] = `
2.0.2
</td>
<td>
<span>
<Blueprint3.Icon
icon="cloud-download"
/>
Downloading
</span>
<Blueprint3.ProgressBar />
</td>
<td
className="action"
@@ -177,10 +226,6 @@ exports[`ElectronSettings component renders 1`] = `
</td>
<td>
<span>
<Blueprint3.Icon
icon="box"
/>
Downloaded
</span>
</td>
@@ -188,6 +233,7 @@ exports[`ElectronSettings component renders 1`] = `
className="action"
>
<Blueprint3.Button
disabled={false}
fill={true}
icon="trash"
onClick={[Function]}
@@ -1,5 +1,6 @@
import { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react';
import { VersionState } from '../../../src/interfaces';
import { BisectHandler } from '../../../src/renderer/components/commands-bisect';
describe('Bisect commands component', () => {
@@ -11,6 +12,9 @@ describe('Bisect commands component', () => {
continue: jest.fn(),
getCurrentVersion: jest.fn()
},
currentElectronVersion: {
state: VersionState.ready
},
setVersion: jest.fn(),
version: '1.0.0',
pushOutput: jest.fn(),
@@ -24,6 +28,12 @@ describe('Bisect commands component', () => {
expect(wrapper).toMatchSnapshot();
});
it('disables helper buttons if Electron binary is downloading', () => {
store.currentElectronVersion.state = VersionState.downloading;
const wrapper = shallow(<BisectHandler appState={store} />);
expect(wrapper).toMatchSnapshot();
});
it('renders bisect dialog button if no bisect instance', () => {
delete store.Bisector;
const wrapper = shallow(<BisectHandler appState={store} />);
@@ -51,6 +61,20 @@ describe('Bisect commands component', () => {
});
describe('continueBisect()', () => {
it('closes the currently running app', () => {
const wrapper = shallow(<BisectHandler appState={store} />);
const instance: BisectHandler = wrapper.instance() as any;
store.Bisector.continue.mockReturnValue({
version: '2.0.0'
});
const mockStop = jest.fn();
window.ElectronFiddle.app.runner.stop = mockStop;
instance.continueBisect(true);
expect(mockStop).toHaveBeenCalled();
});
it('sets version assigned by bisect algorithm', () => {
const wrapper = shallow(<BisectHandler appState={store} />);
const instance: BisectHandler = wrapper.instance() as any;
@@ -68,7 +92,7 @@ describe('Bisect commands component', () => {
instance.terminateBisect = jest.fn();
// same value is only returned when there is only 1 version left
store.Bisector.continue.mockReturnValue([{version: 'minVer'}, {version: 'maxVer'}]);
store.Bisector.continue.mockReturnValue([{ version: 'minVer' }, { version: 'maxVer' }]);
instance.continueBisect(true);
expect(store.setVersion).not.toHaveBeenCalled();
expect(instance.terminateBisect).toHaveBeenCalled();
@@ -16,7 +16,7 @@ describe('EditorDropdown component', () => {
store = {
hideAndBackupMosaic: jest.fn(),
showMosaic: jest.fn(),
closedPanels: {}
closedPanels: {},
};
(getVisibleMosaics as jest.Mock).mockReturnValue([ EditorId.html, EditorId.renderer ]);
@@ -46,4 +46,15 @@ describe('EditorDropdown component', () => {
expect(store.hideAndBackupMosaic).toHaveBeenCalledTimes(1);
expect(store.showMosaic).toHaveBeenCalledTimes(1);
});
it('disables hide button if only one editor open', () => {
store.mosaicArrangement = 'html';
(getVisibleMosaics as jest.Mock).mockReturnValue([ EditorId.html ]);
const wrapper = mount(<EditorDropdown appState={store} />);
const instance = wrapper.instance() as EditorDropdown;
const menu = instance.renderMenuItems();
expect(menu).toMatchSnapshot();
});
});
@@ -1,7 +1,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { ElectronVersionState } from '../../../src/interfaces';
import { VersionState } from '../../../src/interfaces';
import { Runner } from '../../../src/renderer/components/commands-runner';
import { ipcRendererManager } from '../../../src/renderer/ipc';
import { ElectronFiddleMock } from '../../mocks/electron-fiddle';
@@ -42,13 +42,13 @@ describe('Runner component', () => {
});
it('renders downloading', () => {
store.versions['2.0.2'].state = ElectronVersionState.downloading;
store.versions['2.0.2'].state = VersionState.downloading;
const wrapper = shallow(<Runner appState={store} />);
expect(wrapper).toMatchSnapshot();
});
it('renders "checking status"', () => {
store.versions['2.0.2'].state = ElectronVersionState.unknown;
store.versions['2.0.2'].state = VersionState.unknown;
const wrapper = shallow(<Runner appState={store} />);
expect(wrapper).toMatchSnapshot();
});
@@ -1,13 +1,13 @@
import { mount, shallow } from 'enzyme';
import * as React from 'react';
import { ElectronVersionSource, ElectronVersionState } from '../../../src/interfaces';
import { VersionSource, VersionState } from '../../../src/interfaces';
import { VersionChooser } from '../../../src/renderer/components/commands-version-chooser';
import { ElectronReleaseChannel } from '../../../src/renderer/versions';
import { mockVersions } from '../../mocks/electron-versions';
const { unknown } = ElectronVersionState;
const { remote } = ElectronVersionSource;
const { unknown } = VersionState;
const { remote } = VersionSource;
describe('VersionSelect component', () => {
let store: any;
@@ -37,7 +37,7 @@ describe('VersionSelect component', () => {
versionsToShow: Object.values(versions).filter((v) => !!v),
versions,
channelsToShow: [ ElectronReleaseChannel.stable, ElectronReleaseChannel.beta ],
statesToShow: [ ElectronVersionState.ready, ElectronVersionState.downloading ],
statesToShow: [ VersionState.ready, VersionState.downloading ],
setVersion: jest.fn(),
get currentElectronVersion() {
return mockVersions['2.0.2'];
@@ -2,10 +2,13 @@ import { shallow } from 'enzyme';
import * as React from 'react';
import { IpcEvents } from '../../../src/ipc-events';
import { getIsDownloaded } from '../../../src/renderer/binary';
import { AddVersionDialog } from '../../../src/renderer/components/dialog-add-version';
import { ipcRendererManager } from '../../../src/renderer/ipc';
import { overridePlatform, resetPlatform } from '../../utils';
jest.mock('../../../src/renderer/ipc');
jest.mock('../../../src/renderer/binary');
describe('AddVersionDialog component', () => {
let store: any;
@@ -66,7 +69,7 @@ describe('AddVersionDialog component', () => {
describe('setFolderPath()', () => {
it('does something', async () => {
store.binaryManager.getIsDownloaded.mockResolvedValue(true);
(getIsDownloaded as jest.Mock).mockResolvedValue(true);
const wrapper = shallow(<AddVersionDialog appState={store} />);
await (wrapper.instance() as any).setFolderPath('/test/');
@@ -1,6 +1,6 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { ElectronVersionSource, ElectronVersionState } from '../../../src/interfaces';
import { VersionSource, VersionState } from '../../../src/interfaces';
import { Bisector } from '../../../src/renderer/bisect';
import { BisectDialog } from '../../../src/renderer/components/dialog-bisect';
import { ElectronReleaseChannel } from '../../../src/renderer/versions';
@@ -12,9 +12,9 @@ describe('BisectDialog component', () => {
const generateVersionRange = (rangeLength: number) =>
(new Array(rangeLength)).fill(0).map((_, i) => ({
state: ElectronVersionState.ready,
state: VersionState.ready,
version: `${i + 1}.0.0`,
source: ElectronVersionSource.local
source: VersionSource.local
}));
beforeEach(() => {
@@ -24,7 +24,7 @@ describe('BisectDialog component', () => {
versions,
versionsToShow: versions,
channelsToShow: [ElectronReleaseChannel.stable],
statesToShow: [ElectronVersionState.ready],
statesToShow: [VersionState.ready],
setVersion: jest.fn()
};
});
+45 -42
View File
@@ -101,56 +101,59 @@ describe('Editor component', () => {
expect(monaco.editor.createModel).toHaveBeenCalled();
});
it('attempts to restore a backup if available', async () => {
store.getAndRemoveEditorValueBackup.mockReturnValueOnce({
model: true,
viewState: true
describe('backups', async () => {
it('attempts to restore a backup if contains a model', async () => {
store.getAndRemoveEditorValueBackup.mockReturnValueOnce({
model: true,
viewState: true
});
const wrapper = shallow(
<Editor
appState={store}
monaco={monaco}
monacoOptions={{}}
id={EditorId.main}
editorDidMount={() => undefined}
setFocused={() => undefined}
/>
);
const instance: any = wrapper.instance();
instance.containerRef.current = 'ref';
await instance.initMonaco();
expect(instance.editor.restoreViewState).toHaveBeenCalledTimes(1);
expect(instance.editor.setModel).toHaveBeenCalledTimes(1);
});
const wrapper = shallow(
<Editor
appState={store}
monaco={monaco}
monacoOptions={{}}
id={EditorId.main}
editorDidMount={() => undefined}
setFocused={() => undefined}
/>
);
const instance: any = wrapper.instance();
it('attempts to restore a backup if contains a string value', async () => {
store.getAndRemoveEditorValueBackup.mockReturnValueOnce({
value: 'hello'
});
instance.containerRef.current = 'ref';
await instance.initMonaco();
const wrapper = shallow(
<Editor
appState={store}
monaco={monaco}
monacoOptions={{}}
id={EditorId.main}
editorDidMount={() => undefined}
setFocused={() => undefined}
/>
);
const instance: any = wrapper.instance();
expect(instance.editor.restoreViewState).toHaveBeenCalledTimes(1);
expect(instance.editor.setModel).toHaveBeenCalledTimes(1);
});
instance.containerRef.current = 'ref';
await instance.initMonaco();
it('attempts to restore a backup value if available', async () => {
store.getAndRemoveEditorValueBackup.mockReturnValueOnce({
value: 'hello'
expect(instance.editor.restoreViewState).toHaveBeenCalledTimes(0);
expect(instance.editor.setModel).toHaveBeenCalledTimes(1);
expect(monaco.editor.createModel).toHaveBeenCalledWith('hello', 'javascript');
});
const wrapper = shallow(
<Editor
appState={store}
monaco={monaco}
monacoOptions={{}}
id={EditorId.main}
editorDidMount={() => undefined}
setFocused={() => undefined}
/>
);
const instance: any = wrapper.instance();
instance.containerRef.current = 'ref';
await instance.initMonaco();
expect(instance.editor.restoreViewState).toHaveBeenCalledTimes(0);
expect(instance.editor.setModel).toHaveBeenCalledTimes(1);
expect(monaco.editor.createModel).toHaveBeenCalledWith('hello', 'javascript');
});
it('initializes with a fixed tab size', async () => {
const didMount = jest.fn();
const wrapper = shallow(
@@ -122,6 +122,18 @@ describe('Editors component', () => {
expect(toolbar).toMatchSnapshot();
});
it('does not render toolbar controls if only one editor exists', () => {
store.mosaicArrangement = EditorId.main;
const wrapper = shallow(<Editors appState={store} />);
const instance: Editors = wrapper.instance() as any;
const toolbar = instance.renderToolbar(
{ title: TITLE_MAP[EditorId.main] } as any,
EditorId.main
);
expect(toolbar).toMatchSnapshot();
});
it('componentWillUnmount() unsubscribes the layout reaction', () => {
const wrapper = shallow(<Editors appState={store} />);
const instance: Editors = wrapper.instance() as any;
@@ -1,7 +1,7 @@
import { mount, shallow } from 'enzyme';
import * as React from 'react';
import { ElectronVersionSource, ElectronVersionState } from '../../../src/interfaces';
import { VersionSource, VersionState } from '../../../src/interfaces';
import { ElectronSettings } from '../../../src/renderer/components/settings-electron';
import { ElectronReleaseChannel } from '../../../src/renderer/versions';
import { mockVersions } from '../../mocks/electron-versions';
@@ -14,7 +14,7 @@ describe('ElectronSettings component', () => {
version: '2.0.1',
versions: { ...mockVersions },
channelsToShow: [ ElectronReleaseChannel.stable, ElectronReleaseChannel.beta ],
statesToShow: [ ElectronVersionState.ready, ElectronVersionState.downloading ],
statesToShow: [ VersionState.ready, VersionState.downloading ],
downloadVersion: jest.fn(),
removeVersion: jest.fn(),
updateElectronVersions: jest.fn(),
@@ -25,19 +25,20 @@ describe('ElectronSettings component', () => {
store.versions['2.0.2'].state = 'downloading';
store.versions['2.0.1'].state = 'ready';
store.versions['1.8.7'].state = 'unknown';
store.versions['1.8.6'].state = 'unzipping';
});
it('renders', () => {
store.versions['3.0.0-nightly.1'] = {
state: ElectronVersionState.ready,
state: VersionState.ready,
version: '3.0.0-nightly.1',
source: ElectronVersionSource.local
source: VersionSource.local
};
store.versions['3.0.0'] = {
state: ElectronVersionState.ready,
state: VersionState.ready,
version: '3.0.0',
source: ElectronVersionSource.local
source: VersionSource.local
};
const wrapper = shallow(<ElectronSettings appState={store} />);
@@ -47,15 +48,15 @@ describe('ElectronSettings component', () => {
it('handles removing a version', async () => {
store.versions['3.0.0-nightly.1'] = {
state: ElectronVersionState.ready,
state: VersionState.ready,
version: '3.0.0-nightly.1',
source: ElectronVersionSource.local
source: VersionSource.local
};
store.versions['3.0.0'] = {
state: ElectronVersionState.ready,
state: VersionState.ready,
version: '3.0.0',
source: ElectronVersionSource.local
source: VersionSource.local
};
const wrapper = mount(<ElectronSettings appState={store} />);
@@ -71,13 +72,13 @@ describe('ElectronSettings component', () => {
it('handles downloading a version', async () => {
store.versions = {
'3.0.0': {
state: ElectronVersionState.unknown,
state: VersionState.unknown,
version: '3.0.0',
source: ElectronVersionSource.remote
source: VersionSource.remote
}
};
store.statesToShow.push(ElectronVersionState.unknown);
store.statesToShow.push(VersionState.unknown);
const wrapper = mount(<ElectronSettings appState={store} />);
@@ -94,7 +95,7 @@ describe('ElectronSettings component', () => {
const instance = wrapper.instance() as any;
await instance.handleDeleteAll();
expect(store.removeVersion).toHaveBeenCalledTimes(2);
expect(store.removeVersion).toHaveBeenCalledTimes(3);
});
it('handles the downloadAll()', async () => {
@@ -139,21 +140,21 @@ describe('ElectronSettings component', () => {
const instance = wrapper.instance() as any;
await instance.handleStateChange({
currentTarget: {
id: ElectronVersionState.ready,
id: VersionState.ready,
checked: false
}
});
await instance.handleStateChange({
currentTarget: {
id: ElectronVersionState.unknown,
id: VersionState.unknown,
checked: true
}
});
expect(store.statesToShow).toEqual([
ElectronVersionState.downloading,
ElectronVersionState.unknown
VersionState.downloading,
VersionState.unknown
]);
});
});
@@ -1,13 +1,13 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../../src/interfaces';
import { RunnableVersion, VersionSource, VersionState } from '../../../src/interfaces';
import { filterItem, getItemIcon, getItemLabel, renderItem, VersionSelect } from '../../../src/renderer/components/version-select';
import { ElectronReleaseChannel } from '../../../src/renderer/versions';
import { mockVersions } from '../../mocks/electron-versions';
const { ready, unknown, downloading } = ElectronVersionState;
const { remote, local } = ElectronVersionSource;
const { ready, unknown, downloading } = VersionState;
const { remote, local } = VersionSource;
describe('VersionSelect component', () => {
let store: any;
@@ -34,15 +34,15 @@ describe('VersionSelect component', () => {
'3.0.0-unsupported': { ...mockVersion2 }
},
channelsToShow: [ ElectronReleaseChannel.stable, ElectronReleaseChannel.beta ],
statesToShow: [ ElectronVersionState.ready, ElectronVersionState.downloading ],
statesToShow: [ VersionState.ready, VersionState.downloading ],
setVersion: jest.fn(),
get currentElectronVersion() {
get currentRunnableVersion() {
return mockVersions['2.0.2'];
}
};
});
const onVersionSelect = () => ({});
const onVersionSelect = () => ({});
it('renders', () => {
const wrapper = shallow(
@@ -77,7 +77,7 @@ describe('VersionSelect component', () => {
describe('getItemLabel()', () => {
it('returns the correct label for a local version', () => {
const input: ElectronVersion = {
const input: RunnableVersion = {
...mockVersion1,
source: local,
};
@@ -87,7 +87,7 @@ describe('VersionSelect component', () => {
});
it('returns the correct label for a version not downloaded', () => {
const input: ElectronVersion = {
const input: RunnableVersion = {
...mockVersion1,
state: unknown
};
@@ -96,7 +96,7 @@ describe('VersionSelect component', () => {
});
it('returns the correct label for a version downloaded', () => {
const input: ElectronVersion = {
const input: RunnableVersion = {
...mockVersion1,
state: ready
};
@@ -105,7 +105,7 @@ describe('VersionSelect component', () => {
});
it('returns the correct label for a version downloading', () => {
const input: ElectronVersion = {
const input: RunnableVersion = {
...mockVersion1,
state: downloading
};
+43 -18
View File
@@ -1,10 +1,10 @@
import {
ElectronVersion,
ElectronVersionSource,
ElectronVersionState
RunnableVersion,
VersionSource,
VersionState
} from '../../src/interfaces';
import { USER_DATA_PATH } from '../../src/renderer/constants';
import {
fetchTypeDefinitions,
getDownloadedVersionTypeDefs,
@@ -12,12 +12,12 @@ import {
getLocalVersionTypeDefs,
getOfflineTypeDefinitionPath,
getOfflineTypeDefinitions,
removeTypeDefsForVersion,
updateEditorTypeDefinitions
} from '../../src/renderer/fetch-types';
import * as path from 'path';
import { ElectronFiddleMock } from '../mocks/electron-fiddle';
import * as path from 'path';
jest.mock('fs-extra');
jest.mock('extract-zip', () => {
return jest.fn((_a, _b, c) => c());
@@ -92,8 +92,8 @@ describe('fetch-types', () => {
describe('getDownloadedVersionTypeDefs()', () => {
const version = {
state: ElectronVersionState.ready,
source: ElectronVersionSource.remote,
state: VersionState.ready,
source: VersionSource.remote,
version: '3.0.0'
};
@@ -178,11 +178,11 @@ describe('fetch-types', () => {
});
describe('getLocalVersionTypeDefs()', () => {
let version: ElectronVersion;
let version: RunnableVersion;
beforeEach(() => {
version = {
state: ElectronVersionState.ready,
source: ElectronVersionSource.local,
state: VersionState.ready,
source: VersionSource.local,
version: '3.0.0',
localPath: 'somePath'
};
@@ -198,7 +198,7 @@ describe('fetch-types', () => {
});
it('returns null for remote path', async () => {
version.source = ElectronVersionSource.remote;
version.source = VersionSource.remote;
const result = await getLocalVersionTypeDefs(version);
expect(result).toBeNull();
});
@@ -211,11 +211,11 @@ describe('fetch-types', () => {
});
describe('getLocalTypePathForVersion()', () => {
let version: ElectronVersion;
let version: RunnableVersion;
beforeEach(() => {
version = {
state: ElectronVersionState.ready,
source: ElectronVersionSource.local,
state: VersionState.ready,
source: VersionSource.local,
version: '3.0.0',
localPath: 'somePath'
};
@@ -241,13 +241,13 @@ describe('fetch-types', () => {
});
describe('updateEditorTypeDefinitions()', () => {
let version: ElectronVersion;
let version: RunnableVersion;
beforeEach(() => {
(global as any).window = window || {};
(window as any).ElectronFiddle = new ElectronFiddleMock();
version = {
state: ElectronVersionState.ready,
source: ElectronVersionSource.remote,
state: VersionState.ready,
source: VersionSource.remote,
version: '3.0.0'
};
});
@@ -302,4 +302,29 @@ describe('fetch-types', () => {
expect(errored).toBe(false);
});
});
describe('removeTypeDefsForVersion()', () => {
it('removes a version\'s typedefs', async () => {
const fs = require('fs-extra');
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
await removeTypeDefsForVersion('v3.0.0');
expect(fs.remove).toHaveBeenCalledWith(path.join(USER_DATA_PATH, 'electron-typedef', '3.0.0'));
});
it('throws upon fs failure', async () => {
const fs = require('fs-extra');
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
(fs.remove as jest.Mock<any>).mockImplementation(() => {
throw new Error('Bwap bwap');
});
expect.assertions(1);
try {
await removeTypeDefsForVersion('v3.0.0');
} catch (e) {
expect(e).toEqual(new Error('Bwap bwap'));
}
});
});
});
+9 -7
View File
@@ -2,6 +2,7 @@ import { spawn } from 'child_process';
import * as path from 'path';
import { IpcEvents } from '../../src/ipc-events';
import { getIsDownloaded } from '../../src/renderer/binary';
import { ipcRendererManager } from '../../src/renderer/ipc';
import {
findModulesInEditors,
@@ -17,6 +18,10 @@ import { mockVersions } from '../mocks/electron-versions';
jest.mock('../../src/renderer/npm');
jest.mock('../../src/renderer/file-manager');
jest.mock('../../src/renderer/binary', () => ({
getIsDownloaded: jest.fn(),
getElectronBinaryPath: jest.fn()
}));
jest.mock('fs-extra');
jest.mock('child_process');
@@ -39,10 +44,6 @@ describe('Runner component', () => {
pushOutput: jest.fn(),
clearConsole: jest.fn(),
pushError: jest.fn(),
binaryManager: {
getIsDownloaded: jest.fn(() => true),
getElectronBinaryPath: jest.fn((version: string) => `/fake/path/${version}/electron`)
},
get currentElectronVersion() {
return mockVersions['2.0.2'];
},
@@ -50,6 +51,7 @@ describe('Runner component', () => {
};
(window as any).ElectronFiddle = new ElectronFiddleMock();
(getIsDownloaded as jest.Mock).mockReturnValue(true);
instance = new Runner(store as AppState);
});
@@ -59,7 +61,7 @@ describe('Runner component', () => {
(spawn as any).mockReturnValueOnce(mockChild);
expect(await instance.run()).toBe(true);
expect(store.binaryManager.getIsDownloaded).toHaveBeenCalled();
expect(getIsDownloaded).toHaveBeenCalled();
expect(window.ElectronFiddle.app.fileManager.saveToTemp).toHaveBeenCalled();
expect(installModules).toHaveBeenCalled();
expect(store.isRunning).toBe(true);
@@ -75,7 +77,7 @@ describe('Runner component', () => {
});
expect(await instance.run()).toBe(true);
expect(store.binaryManager.getIsDownloaded).toHaveBeenCalled();
expect(getIsDownloaded).toHaveBeenCalled();
expect(window.ElectronFiddle.app.fileManager.saveToTemp).toHaveBeenCalled();
expect(installModules).toHaveBeenCalled();
expect(store.isRunning).toBe(true);
@@ -161,7 +163,7 @@ describe('Runner component', () => {
});
it('does not run version not yet downloaded', async () => {
store.binaryManager.getIsDownloaded.mockReturnValueOnce(false);
(getIsDownloaded as jest.Mock).mockReturnValueOnce(false);
expect(await instance.run()).toBe(false);
});
+51 -28
View File
@@ -1,4 +1,5 @@
import { ALL_MOSAICS, EditorId, ElectronVersionSource, ElectronVersionState, GenericDialogType, PanelId } from '../../src/interfaces';
import { ALL_MOSAICS, EditorId, GenericDialogType, PanelId, VersionSource, VersionState } from '../../src/interfaces';
import { getDownloadedVersions, getDownloadingVersions, removeBinary, setupBinary } from '../../src/renderer/binary';
import { Bisector } from '../../src/renderer/bisect';
import { DEFAULT_MOSAIC_ARRANGEMENT } from '../../src/renderer/constants';
import { getContent, isContentUnchanged } from '../../src/renderer/content';
@@ -16,7 +17,10 @@ jest.mock('../../src/renderer/content', () => ({
getContent: jest.fn()
}));
jest.mock('../../src/renderer/binary', () => ({
BinaryManager: require('../mocks/binary').MockBinaryManager
removeBinary: jest.fn(),
setupBinary: jest.fn(),
getDownloadedVersions: jest.fn(),
getDownloadingVersions: jest.fn()
}));
jest.mock('../../src/renderer/fetch-types', () => ({
updateEditorTypeDefinitions: jest.fn()
@@ -55,8 +59,8 @@ describe('AppState', () => {
expect(appState).toBeTruthy();
});
describe('onbeforeunload handler', () => {
it('closes the window', (done) => {
describe('isUnsaved autorun handler', () => {
it('can close the window if user accepts the dialog', (done) => {
window.close = jest.fn();
appState.isUnsaved = true;
expect(window.onbeforeunload).toBeTruthy();
@@ -73,7 +77,7 @@ describe('AppState', () => {
});
});
it('closes the app', (done) => {
it('can close the app after user accepts dialog', (done) => {
const { remote } = require('electron');
window.close = jest.fn();
appState.isUnsaved = true;
@@ -93,7 +97,7 @@ describe('AppState', () => {
});
});
it('does not close the window', (done) => {
it('takes no action if user cancels the dialog', (done) => {
window.close = jest.fn();
appState.isUnsaved = true;
expect(window.onbeforeunload).toBeTruthy();
@@ -109,6 +113,21 @@ describe('AppState', () => {
done();
});
});
it('sets the onDidChangeModelContent handler if saved', () => {
appState.isUnsaved = false;
expect(window.onbeforeunload).toBe(null);
const fn = window.ElectronFiddle.editors!.renderer!
.onDidChangeModelContent;
const call = (fn as jest.Mock<any>).mock.calls[0];
const cb = call[0];
cb();
expect(appState.isUnsaved).toBe(true);
});
});
describe('updateElectronVersions()', () => {
@@ -264,46 +283,50 @@ describe('AppState', () => {
});
it('excludes channels', () => {
const expectedLength = Object.keys(appState.versions).length;
appState.channelsToShow = [ 'Unsupported' as any ];
expect(appState.versionsToShow.length).toEqual(0);
appState.channelsToShow = [ 'Stable' as any ];
expect(appState.versionsToShow.length).toEqual(3);
expect(appState.versionsToShow.length).toEqual(expectedLength);
});
it('excludes states', () => {
appState.statesToShow = [ ElectronVersionState.downloading ];
const expectedLength = Object.keys(appState.versions).length;
appState.statesToShow = [ VersionState.downloading ];
expect(appState.versionsToShow.length).toEqual(0);
appState.statesToShow = [ ElectronVersionState.ready ];
expect(appState.versionsToShow.length).toEqual(3);
appState.statesToShow = [ VersionState.ready ];
expect(appState.versionsToShow.length).toEqual(expectedLength);
});
});
describe('removeVersion()', () => {
it('removes a version', async () => {
appState.versions['2.0.2'].state = ElectronVersionState.ready;
appState.versions['2.0.2'].state = VersionState.ready;
await appState.removeVersion('v2.0.2');
expect(appState.binaryManager.remove).toHaveBeenCalledWith('2.0.2');
expect(removeBinary).toHaveBeenCalledWith('2.0.2');
});
it('does not remove it if not necessary', async () => {
appState.versions['2.0.2'].state = ElectronVersionState.unknown;
appState.versions['2.0.2'].state = VersionState.unknown;
await appState.removeVersion('v2.0.2');
expect(appState.binaryManager.remove).toHaveBeenCalledTimes(0);
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');
expect(appState.binaryManager.remove).toHaveBeenCalledTimes(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: ElectronVersionSource.local,
state: ElectronVersionState.ready,
source: VersionSource.local,
state: VersionState.ready,
version: '4.0.0'
};
@@ -311,28 +334,28 @@ describe('AppState', () => {
expect(saveLocalVersions).toHaveBeenCalledTimes(1);
expect(appState.versions['/local/path']).toBeUndefined();
expect(appState.binaryManager.remove).toHaveBeenCalledTimes(0);
expect(removeBinary).toHaveBeenCalledTimes(0);
});
});
describe('downloadVersion()', () => {
it('downloads a version', async () => {
appState.versions['2.0.2'].state = ElectronVersionState.unknown;
appState.versions['2.0.2'].state = VersionState.unknown;
await appState.downloadVersion('v2.0.2');
expect(appState.binaryManager.setup).toHaveBeenCalledWith('2.0.2');
expect(setupBinary).toHaveBeenCalledWith(appState, '2.0.2');
});
it('downloads an unknown version', async () => {
await appState.downloadVersion('v3.5');
expect(appState.binaryManager.setup).toHaveBeenCalledWith('3.5');
expect(setupBinary).toHaveBeenCalledWith(appState, '3.5');
});
it('does not download a version if already ready', async () => {
appState.versions['2.0.2'].state = ElectronVersionState.ready;
appState.versions['2.0.2'].state = VersionState.ready;
await appState.downloadVersion('v2.0.2');
expect(appState.binaryManager.setup).toHaveBeenCalledTimes(0);
expect(setupBinary).toHaveBeenCalledTimes(0);
});
});
@@ -403,7 +426,7 @@ describe('AppState', () => {
// refreshed - we didn't actually add the local version
// above, since versions.ts is mocked
expect(Object.keys(appState.versions)).toEqual(
['2.0.2', '2.0.1', '1.8.7']
['2.0.2', '2.0.1', '1.8.7', '1.8.6']
);
});
});
@@ -411,17 +434,17 @@ describe('AppState', () => {
describe('updateDownloadedVersionState()', () => {
it('downloads a version if necessary', async () => {
const mockResult = Promise.resolve(['2.0.2']);
(appState.binaryManager.getDownloadedVersions as jest.Mock).mockReturnValueOnce(mockResult);
(getDownloadedVersions as jest.Mock).mockReturnValueOnce(mockResult);
await appState.updateDownloadedVersionState();
expect(appState.versions['2.0.2'].state).toBe(ElectronVersionState.ready);
expect(appState.versions['2.0.2'].state).toBe(VersionState.ready);
});
it('keeps downloading state intact', async () => {
(appState.binaryManager.getDownloadingVersions as jest.Mock).mockReturnValueOnce(['2.0.2']);
(getDownloadingVersions as jest.Mock).mockReturnValueOnce(['2.0.2']);
await appState.updateDownloadedVersionState();
expect(appState.versions['2.0.2'].state).toBe(ElectronVersionState.downloading);
expect(appState.versions['2.0.2'].state).toBe(VersionState.downloading);
});
});
+9 -7
View File
@@ -1,7 +1,7 @@
import { ElectronVersionSource, ElectronVersionState } from '../../src/interfaces';
import { VersionSource, VersionState } from '../../src/interfaces';
import { AppState } from '../../src/renderer/state';
import { TouchBarManager } from '../../src/renderer/touch-bar-manager';
import { mockVersions } from '../mocks/electron-versions';
import { mockVersions, mockVersionsArray } from '../mocks/electron-versions';
import { overridePlatform, resetPlatform } from '../utils';
const { lastElectronVersion } = require('../fixtures/releases-metadata.json');
@@ -21,13 +21,14 @@ describe('TouchBarManager', () => {
appState.versions = mockVersions;
appState.isRunning = false;
appState.versions['2.0.1'].state = ElectronVersionState.unknown;
appState.versions['2.0.1'].state = VersionState.unknown;
});
it('creates a touch bar with versions', () => {
const touchBarMgr = new TouchBarManager(appState);
expect(touchBarMgr.versionSelector.items).toHaveLength(3);
const expectedLength = Object.keys(appState.versions).length;
expect(touchBarMgr.versionSelector.items).toHaveLength(expectedLength);
const [ item ] = touchBarMgr.versionSelector.items;
@@ -38,12 +39,13 @@ describe('TouchBarManager', () => {
it('updates the versions when the versions change', () => {
const touchBarMgr = new TouchBarManager(appState);
appState.versions['3.3.3'] = {
state: ElectronVersionState.downloading,
source: ElectronVersionSource.remote,
state: VersionState.downloading,
source: VersionSource.remote,
version: '3.3.3'
};
expect(touchBarMgr.versionSelector.items).toHaveLength(4);
const expectedLength = Object.keys(appState.versions).length;
expect(touchBarMgr.versionSelector.items).toHaveLength(expectedLength);
const [ item ] = touchBarMgr.versionSelector.items;
+4 -4
View File
@@ -1,7 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
import semver from 'semver';
import { ElectronVersion, ElectronVersionSource } from '../../src/interfaces';
import { RunnableVersion, VersionSource } from '../../src/interfaces';
import {
addLocalVersion,
ElectronReleaseChannel,
@@ -18,7 +18,7 @@ import { mockFetchOnce } from '../utils';
const { expectedVersionCount } = require('../fixtures/releases-metadata.json');
const mockVersions: Array<Partial<ElectronVersion>> = [
const mockVersions: Array<Partial<RunnableVersion>> = [
{ version: 'test-0', localPath: '/test/path/0' },
{ version: 'test-1', localPath: '/test/path/1' },
{ version: 'test-2', localPath: '/test/path/2' },
@@ -114,11 +114,11 @@ describe('versions', () => {
describe('saveLocalVersions()', () => {
it('saves local versions', () => {
const mockLocalVersions = mockVersions.map((v) => {
v.source = ElectronVersionSource.local;
v.source = VersionSource.local;
return v;
});
saveLocalVersions(mockLocalVersions as Array<ElectronVersion>);
saveLocalVersions(mockLocalVersions as Array<RunnableVersion>);
const key = (window.localStorage.setItem as jest.Mock).mock.calls[0][0];
const value = (window.localStorage.setItem as jest.Mock).mock.calls[0][1];
+1 -1
View File
@@ -11,7 +11,7 @@ jest.spyOn(global.console, 'log').mockImplementation(() => jest.fn());
jest.spyOn(global.console, 'warn').mockImplementation(() => jest.fn());
jest.mock('electron', () => require('./mocks/electron'));
jest.mock('fs-extra');
jest.mock('electron-download');
jest.mock('@electron/get');
expect.addSnapshotSerializer(createSerializer({mode: 'deep'}));