Compare commits

...
20 Commits
Author SHA1 Message Date
Felix Rieseberg ebf421ebed wip: Properly select Node version 2019-12-12 15:20:13 -05:00
Felix Rieseberg 41409f578e wip: Even more stuff 2019-12-12 14:58:34 -05:00
Felix Rieseberg f32fce2f80 Merge pull request #281 from electron/fix-dropdowns
fix: Incorrect dropdowns
2019-12-12 14:56:25 -05:00
Felix Rieseberg 26e054702a fix: Incorrect dropdowns 2019-12-12 14:23:11 -05:00
Felix Rieseberg 16f27132cb Revert "First basis for having Node in there, too"
This reverts commit 7613176f69.
2019-12-12 14:06:34 -05:00
Felix RiesebergandTierney Cyren 7613176f69 First basis for having Node in there, too
Co-authored-by: Tierney Cyren <ticyren@microsoft.com>
2019-12-11 12:19:16 -05:00
dependabot[bot] 470bea4fca build(deps): bump serialize-to-js from 3.0.0 to 3.0.1 (#280)
Bumps [serialize-to-js](https://github.com/commenthol/serialize-to-js) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/commenthol/serialize-to-js/releases)
- [Commits](https://github.com/commenthol/serialize-to-js/compare/v3.0.0...v3.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-10 00:30:59 -08:00
Felix Rieseberg 363febdb73 Merge pull request #279 from electron/notarize
build: Notarize the build with Apple
2019-12-04 13:43:38 -08:00
Felix Rieseberg 062ca4e205 build: Update to electron-forge b46 2019-12-04 10:44:13 -08:00
Felix Rieseberg 49053342a5 Merge pull request #278 from electron/electron-713
chore: Update Electron to 7.1.3
2019-12-04 10:09:10 -08:00
Felix Rieseberg fd582c67a8 build: Use a later macOS image 2019-12-03 18:08:51 -08:00
Felix Rieseberg 706b18808b build: Notarize the build with Apple 2019-12-03 17:52:52 -08:00
Felix Rieseberg a6ae8d5376 chore: Update Electron to 7.1.3 2019-12-03 17:45:02 -08:00
Erick Zhao 5034f8c853 feat: add bisect helper (#257) 2019-11-22 18:46:58 -05:00
Letra e916247013 fix: ensure dev tools only open in dev mode (#277) 2019-11-15 09:22:14 -05:00
Felix Rieseberg c052686de4 0.11.1 2019-11-12 14:39:38 +01:00
Erick Zhao 31aacef131 fix: update folder selection dialog for Electron 7 (#271) 2019-11-05 16:51:04 -05:00
Erick Zhao 932369b705 fix: update deprecated app.setName API (#273) 2019-11-05 16:24:43 -05:00
Shelley Vohr b835c34786 fix: update dialog example for promisification (#272) 2019-11-03 11:36:00 -05:00
Felix Rieseberg c9e1ba815a 0.11.0 2019-10-31 11:49:23 -04:00
46 changed files with 2797 additions and 616 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ os:
- linux
- osx
dist: trusty
osx_image: xcode8.3
osx_image: xcode10
sudo: false
cache:
+35 -2
View File
@@ -7,7 +7,7 @@ const packageJson = require('./package.json')
const { version } = packageJson
const iconDir = path.resolve(__dirname, 'assets', 'icons')
module.exports = {
const config = {
hooks: {
generateAssets: require('./tools/generateAssets')
},
@@ -32,7 +32,12 @@ module.exports = {
OriginalFilename: 'Electron Fiddle',
},
osxSign: {
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)'
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)',
'hardened-runtime': true,
'gatekeeper-assess': false,
'entitlements': 'static/entitlements.plist',
'entitlements-inherit': 'static/entitlements.plist',
'signature-flags': 'library'
}
},
makers: [
@@ -95,3 +100,31 @@ module.exports = {
}
]
}
function notarizeMaybe() {
if (process.platform !== 'darwin') {
return;
}
if (!process.env.CI) {
console.log(`Not in CI, skipping notarization`);
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;
}
config.packagerConfig.osxNotarize = {
appBundleId: 'com.electron.fiddle',
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_PASSWORD,
ascProvider: 'LT94ZKYDCJ'
}
}
notarizeMaybe()
// Finally, export it
module.exports = config
+900 -282
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -1,7 +1,7 @@
{
"name": "electron-fiddle",
"productName": "Electron Fiddle",
"version": "0.10.0",
"version": "0.11.1",
"description": "The easiest way to get started with Electron",
"repository": "https://github.com/electron/fiddle",
"main": "./dist/src/main/main",
@@ -63,12 +63,12 @@
},
"devDependencies": {
"@babel/core": "^7.6.4",
"@electron-forge/cli": "^6.0.0-beta.45",
"@electron-forge/maker-deb": "^6.0.0-beta.45",
"@electron-forge/maker-rpm": "^6.0.0-beta.45",
"@electron-forge/maker-squirrel": "^6.0.0-beta.45",
"@electron-forge/maker-zip": "^6.0.0-beta.45",
"@electron-forge/publisher-github": "^6.0.0-beta.45",
"@electron-forge/cli": "^6.0.0-beta.46",
"@electron-forge/maker-deb": "^6.0.0-beta.46",
"@electron-forge/maker-rpm": "^6.0.0-beta.46",
"@electron-forge/maker-squirrel": "^6.0.0-beta.46",
"@electron-forge/maker-zip": "^6.0.0-beta.46",
"@electron-forge/publisher-github": "^6.0.0-beta.46",
"@types/builtin-modules": "^3.1.1",
"@types/classnames": "^2.2.9",
"@types/enzyme": "^3.10.3",
@@ -82,7 +82,7 @@
"@types/tmp": "0.1.0",
"chokidar": "^3.2.2",
"coveralls": "^3.0.7",
"electron": "7.0.0",
"electron": "7.1.3",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"enzyme-to-json": "^3.4.3",
+11 -3
View File
@@ -4,7 +4,7 @@ export type Files = Map<string, string>;
export type FileTransform = (files: Files) => Promise<Files>;
export enum ElectronVersionState {
export enum VersionState {
ready = 'ready',
downloading = 'downloading',
unknown = 'unknown'
@@ -14,12 +14,20 @@ export enum ElectronVersionSource {
remote = 'remote',
local = 'local'
}
export interface NpmVersion {
export interface Version {
version: string;
}
export interface NpmVersion extends Version {
name?: string;
localPath?: string;
}
export interface NodeVersion extends Version {
state: VersionState;
}
export interface EditorValues {
main: string;
renderer: string;
@@ -29,7 +37,7 @@ export interface EditorValues {
}
export interface ElectronVersion extends NpmVersion {
state: ElectronVersionState;
state: VersionState;
source: ElectronVersionSource;
}
+9 -3
View File
@@ -19,14 +19,18 @@ export enum IpcEvents {
SHOW_WARNING_DIALOG = 'SHOW_WARNING_DIALOG',
SHOW_CONFIRMATION_DIALOG = 'SHOW_CONFIRMATION_DIALOG',
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR',
CLEAR_CONSOLE = 'CLEAR_CONSOLE'
CLEAR_CONSOLE = 'CLEAR_CONSOLE',
LOAD_LOCAL_VERSION_FOLDER = 'LOAD_LOCAL_VERSION_FOLDER',
SHOW_LOCAL_VERSION_FOLDER_DIALOG = 'SHOW_LOCAL_VERSION_FOLDER_DIALOG',
BISECT_COMMANDS_TOGGLE = 'BISECT_COMMANDS_TOGGLE',
}
export const ipcMainEvents = [
IpcEvents.FS_SAVE_FIDDLE_DIALOG,
IpcEvents.FS_SAVE_FIDDLE,
IpcEvents.SHOW_WARNING_DIALOG,
IpcEvents.SHOW_CONFIRMATION_DIALOG
IpcEvents.SHOW_CONFIRMATION_DIALOG,
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG
];
export const ipcRendererEvents = [
@@ -46,7 +50,9 @@ export const ipcRendererEvents = [
IpcEvents.FS_SAVE_FIDDLE_FORGE,
IpcEvents.FS_SAVE_FIDDLE_GIST,
IpcEvents.SHOW_WELCOME_TOUR,
IpcEvents.CLEAR_CONSOLE
IpcEvents.CLEAR_CONSOLE,
IpcEvents.LOAD_LOCAL_VERSION_FOLDER,
IpcEvents.BISECT_COMMANDS_TOGGLE
];
export const WEBCONTENTS_READY_FOR_IPC_SIGNAL = 'WEBCONTENTS_READY_FOR_IPC_SIGNAL';
@@ -1,3 +0,0 @@
button.version-chooser.bp3-button {
border-radius: 3px 0 0 3px;
}
+1 -1
View File
@@ -2,7 +2,6 @@
header {
box-shadow: 0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);
//border-bottom: 1px solid rgba(255, 255, 255, 0.1);
background-color: @background-3;
-webkit-app-region: drag;
}
@@ -20,6 +19,7 @@ header {
select {
-webkit-app-region: no-drag;
margin-bottom: 0;
white-space: nowrap;
}
select {
-1
View File
@@ -16,5 +16,4 @@
@import "components/chrome-mac.less";
@import "components/editors.less";
@import "components/tour.less";
@import "components/commands-version-chooser.less";
@import "components/show-me.less";
+1 -1
View File
@@ -7,7 +7,7 @@ import { isDevMode } from '../utils/devmode';
* @returns {Promise<void>}
*/
export async function setupDevTools(): Promise<void> {
if (!isDevMode) return;
if (!isDevMode()) return;
const {
default: installExtension,
+18 -1
View File
@@ -1,4 +1,4 @@
import { dialog } from 'electron';
import { dialog, IpcMainEvent } from 'electron';
import { IpcEvents } from '../ipc-events';
import { ipcMainManager } from './ipc';
import { getOrCreateMainWindow } from './windows';
@@ -17,6 +17,10 @@ export function setupDialogs() {
showConfirmationDialog(args);
});
ipcMainManager.on(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, async (event) => {
await showOpenDialog(event);
});
}
/**
@@ -42,3 +46,16 @@ function showConfirmationDialog(args: Electron.MessageBoxOptions) {
...args
});
}
async function showOpenDialog(event: IpcMainEvent) {
const { filePaths } = await dialog.showOpenDialog({
title: 'Open Folder',
properties: ['openDirectory']
});
if (!filePaths || filePaths.length < 1) {
return;
}
event.reply(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, [filePaths[0]]);
}
+1 -1
View File
@@ -68,7 +68,7 @@ export function main() {
}
// Set the app's name
app.setName('Electron Fiddle');
app.name = 'Electron Fiddle';
// Ensure that there's only ever one Fiddle running
listenForProtocolHandler();
+12 -7
View File
@@ -144,7 +144,7 @@ function getShowMeMenuItem(key: string, item: string | Templates): MenuItemConst
if (typeof item === 'string') {
return {
label: key,
click: () => ipcMainManager.send(IpcEvents.FS_OPEN_TEMPLATE, [ key ])
click: () => ipcMainManager.send(IpcEvents.FS_OPEN_TEMPLATE, [key])
};
}
@@ -160,10 +160,10 @@ function getShowMeMenu(): MenuItemConstructorOptions {
const showMeMenu: Array<MenuItemConstructorOptions> = Object.keys(SHOW_ME_TEMPLATES)
.map((key) => getShowMeMenuItem(key, SHOW_ME_TEMPLATES[key]));
return {
label: 'Show Me',
submenu: showMeMenu
};
return {
label: 'Show Me',
submenu: showMeMenu
};
}
/**
@@ -251,11 +251,16 @@ export function setupMenu() {
item.submenu.push({ type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' }); // Add zooming actions
item.submenu.push({ type: 'separator' }, {
label: 'Toggle Soft Wrap',
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, [ 'wordWrap' ]),
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, ['wordWrap']),
});
item.submenu.push({ type: 'separator' }, {
label: 'Toggle Mini Map',
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, [ 'minimap.enabled' ]),
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, ['minimap.enabled']),
});
item.submenu.push({ type: 'separator' }, {
label: 'Toggle Bisect Helper',
click: () => ipcMainManager.send(IpcEvents.BISECT_COMMANDS_TOGGLE),
accelerator: 'CommandorControl+Shift+B',
});
}
+58
View File
@@ -0,0 +1,58 @@
import { ElectronVersion } from '../interfaces';
export class Bisector {
public revList: Array<ElectronVersion>;
public minRev: number;
public maxRev: number;
private pivot: number;
constructor(revList: Array<ElectronVersion>) {
this.getCurrentVersion = this.getCurrentVersion.bind(this);
this.continue = this.continue.bind(this);
this.calculatePivot = this.calculatePivot.bind(this);
this.revList = revList;
this.minRev = 0;
this.maxRev = revList.length - 1;
this.calculatePivot();
}
public getCurrentVersion() {
return this.revList[this.pivot];
}
public continue(isGoodVersion: boolean) {
let isBisectOver = false;
if (this.maxRev - this.minRev <= 1) {
isBisectOver = true;
}
if (isGoodVersion) {
const upPivot = Math.floor((this.maxRev - this.pivot) / 2) + this.pivot;
this.minRev = this.pivot;
if (upPivot !== this.maxRev && upPivot !== this.pivot) {
this.pivot = upPivot;
} else {
isBisectOver = true;
}
} else {
const downPivot = Math.floor((this.pivot - this.minRev) / 2) + this.minRev;
this.maxRev = this.pivot;
if (downPivot !== this.minRev && downPivot !== this.pivot) {
this.pivot = downPivot;
} else {
isBisectOver = true;
}
}
if (isBisectOver) {
return [this.revList[this.minRev], this.revList[this.maxRev]];
} else {
return this.revList[this.pivot];
}
}
private calculatePivot() {
this.pivot = Math.floor((this.maxRev - this.minRev) / 2);
}
}
@@ -0,0 +1,68 @@
import { Button } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { AppState } from '../state';
interface BisectHandlerProps {
appState: AppState;
}
@observer
export class BisectHandler extends React.Component<BisectHandlerProps> {
constructor(props: BisectHandlerProps) {
super(props);
this.continueBisect = this.continueBisect.bind(this);
this.terminateBisect = this.terminateBisect.bind(this);
}
public continueBisect(isGood: boolean) {
const { appState } = this.props;
const response = appState.Bisector!.continue(isGood);
if (Array.isArray(response)) {
this.terminateBisect();
const [minRev, maxRev] = response;
appState.pushOutput(`[BISECT] Complete: Check between versions ${minRev.version} and ${maxRev.version}.`);
} else {
appState.setVersion(response.version);
}
}
public terminateBisect() {
const { appState } = this.props;
appState.Bisector = undefined;
}
public render() {
const { appState } = this.props;
if (!!appState.Bisector) {
return (
<>
<Button
icon={'thumbs-up'}
onClick={() => this.continueBisect(true)}
/>
<Button
icon={'thumbs-down'}
onClick={() => this.continueBisect(false)}
/>
<Button
icon={'cross'}
onClick={this.terminateBisect}
>
Cancel Bisect
</Button>
</>
);
} else {
return (
<Button
icon='git-branch'
text='Bisect'
onClick={appState.toggleBisectDialog}
/>
);
}
}
}
@@ -1,11 +1,11 @@
import { Button, MenuItem } from '@blueprintjs/core';
import { Button, ButtonGroup, MenuItem } from '@blueprintjs/core';
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../interfaces';
import { ElectronVersion, ElectronVersionSource, VersionState } from '../../interfaces';
import { highlightText } from '../../utils/highlight-text';
import { sortedElectronMap } from '../../utils/sorted-electron-map';
import { sortedElectronMap } from '../../utils/sorted-map';
import { AppState } from '../state';
import { getReleaseChannel } from '../versions';
@@ -24,11 +24,11 @@ export function getItemLabel({ source, state, name }: ElectronVersion): string {
if (source === ElectronVersionSource.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`;
}
}
@@ -71,7 +71,7 @@ export const filterItem: ItemPredicate<ElectronVersion> = (query, { version }) =
*/
export const renderItem: ItemRenderer<ElectronVersion> = (item, { handleClick, modifiers, query }) => {
if (!modifiers.matchesPredicate) {
return null;
return null;
}
return (
@@ -95,6 +95,30 @@ export interface VersionChooserProps {
appState: AppState;
}
export const getVersionsFromAppState = (appState: AppState) => {
const { versions, versionsToShow, statesToShow } = appState;
return sortedElectronMap<ElectronVersion>(versions, (_key, item) => item)
.filter((item) => {
if (!item) {
return false;
}
// Check if we want to show the version
if (!versionsToShow.includes(getReleaseChannel(item))) {
return false;
}
// Check if we want to show the state
if (!statesToShow.includes(item.state)) {
return false;
}
return true;
});
};
/**
* A dropdown allowing the selection of Electron versions. The actual
* download is managed in the state.
@@ -103,7 +127,7 @@ export interface VersionChooserProps {
* @extends {React.Component<VersionChooserProps, VersionChooserState>}
*/
@observer
export class VersionChooser extends React.Component<VersionChooserProps, VersionChooserState> {
export class ElectronVersionChooser extends React.Component<VersionChooserProps, VersionChooserState> {
constructor(props: VersionChooserProps) {
super(props);
@@ -120,48 +144,29 @@ export class VersionChooser extends React.Component<VersionChooserProps, Version
this.props.appState.setVersion(version);
}
public getItems(): Array<ElectronVersion> {
const { versions, versionsToShow, statesToShow } = this.props.appState;
return sortedElectronMap<ElectronVersion>(versions, (_key, item) => item)
.filter((item) => {
if (!item) {
return false;
}
// Check if we want to show the version
if (!versionsToShow.includes(getReleaseChannel(item))) {
return false;
}
// Check if we want to show the state
if (!statesToShow.includes(item.state)) {
return false;
}
return true;
});
}
public render() {
const { currentElectronVersion } = this.props.appState;
const { currentElectronVersion, Bisector } = this.props.appState;
const { version } = currentElectronVersion;
return (
<ElectronVersionSelect
filterable={true}
items={this.getItems()}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onItemSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
>
<Button
className='version-chooser'
text={`Electron v${version}`}
icon={getItemIcon(currentElectronVersion)}
/>
</ElectronVersionSelect>
<ButtonGroup>
<ElectronVersionSelect
filterable={true}
items={getVersionsFromAppState(this.props.appState)}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onItemSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
disabled={!!Bisector}
>
<Button
className='version-chooser'
text={`Electron v${version}`}
icon={getItemIcon(currentElectronVersion)}
disabled={!!Bisector}
/>
</ElectronVersionSelect>
</ButtonGroup>
);
}
}
@@ -0,0 +1,151 @@
import { Button, ButtonGroup, MenuItem } from '@blueprintjs/core';
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion, NodeVersion, VersionState } from '../../interfaces';
import { highlightText } from '../../utils/highlight-text';
import { sortedNodeMap } from '../../utils/sorted-map';
import { AppState } from '../state';
const ElectronVersionSelect = Select.ofType<NodeVersion>();
/**
* Helper method: Returns the <Select /> label for an Electron
* version.
*
* @param {ElectronVersion} { source, state }
* @returns {string}
*/
export function getItemLabel({ state }: NodeVersion): string {
let label = '';
if (state === VersionState.unknown) {
label = `Not downloaded`;
} else if (state === VersionState.ready) {
label = `Downloaded`;
} else if (state === VersionState.downloading) {
label = `Downloading`;
}
return label;
}
/**
* Helper method: Returns the <Select /> icon for an Electron
* version.
*
* @param {ElectronVersion} { state }
* @returns
*/
export function getItemIcon({ state }: NodeVersion) {
return state === 'ready'
? 'saved'
: state === 'downloading' ? 'cloud-download' : 'cloud';
}
/**
* Helper method: Returns the <Select /> predicate for an Electron
* version.
*
* @param {string} query
* @param {ElectronVersion} { version }
* @returns
*/
export const filterItem: ItemPredicate<NodeVersion> = (query, { version }) => {
return version.toLowerCase().includes(query.toLowerCase());
};
/**
* Helper method: Returns the <Select /> <MenuItem /> for Electron
* versions.
*
* @param {ElectronVersion} item
* @param {IItemRendererProps} { handleClick, modifiers, query }
* @returns
*/
export const renderItem: ItemRenderer<NodeVersion> = (item, { handleClick, modifiers, query }) => {
if (!modifiers.matchesPredicate) {
return null;
}
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
text={highlightText(item.version, query)}
key={item.version}
onClick={handleClick}
label={getItemLabel(item)}
icon={getItemIcon(item)}
/>
);
};
export interface VersionChooserState {
value: string;
}
export interface VersionChooserProps {
appState: AppState;
}
export const getVersionsFromAppState = (appState: AppState) => {
const { nodeVersions } = appState;
return sortedNodeMap<NodeVersion>(nodeVersions, (_key, item) => item)
.filter((item) => !!item);
};
/**
* A dropdown allowing the selection of Electron versions. The actual
* download is managed in the state.
*
* @class VersionChooser
* @extends {React.Component<VersionChooserProps, VersionChooserState>}
*/
@observer
export class NodeVersionChooser extends React.Component<VersionChooserProps, VersionChooserState> {
constructor(props: VersionChooserProps) {
super(props);
this.onItemSelect = this.onItemSelect.bind(this);
}
/**
* Handle change, which usually means that we'd like update
* the selection version.
*
* @param {React.ChangeEvent<HTMLSelectElement>} event
*/
public onItemSelect({ version }: ElectronVersion) {
this.props.appState.setNodeVersion(version);
}
public render() {
const { currentNodeVersion, Bisector } = this.props.appState;
const { version } = currentNodeVersion;
return (
<ButtonGroup>
<ElectronVersionSelect
filterable={true}
items={getVersionsFromAppState(this.props.appState)}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onItemSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
disabled={!!Bisector}
>
<Button
className='version-chooser'
text={`Node v${version}`}
icon={getItemIcon(currentNodeVersion)}
disabled={!!Bisector}
/>
</ElectronVersionSelect>
</ButtonGroup>
);
}
}
+3 -3
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 {
@@ -27,11 +27,11 @@ export class Runner extends React.Component<RunnerProps, RunnerState> {
const state = currentElectronVersion && currentElectronVersion.state;
const props: IButtonProps = { className: 'button-run' };
if (state === ElectronVersionState.downloading) {
if (state === VersionState.downloading) {
props.text = 'Downloading';
props.disabled = true;
props.icon = <Spinner size={16} />;
} else if (state === ElectronVersionState.ready) {
} else if (state === VersionState.ready) {
if (isRunning) {
props.active = true;
props.text = 'Stop';
+30 -2
View File
@@ -4,10 +4,12 @@ import * as React from 'react';
import { AppState } from '../state';
import { AddressBar } from './commands-address-bar';
import { BisectHandler } from './commands-bisect';
import { EditorDropdown } from './commands-editors';
import { ElectronVersionChooser } from './commands-electron-chooser';
import { NodeVersionChooser } from './commands-node-chooser';
import { PublishButton } from './commands-publish-button';
import { Runner } from './commands-runner';
import { VersionChooser } from './commands-version-chooser';
export interface CommandsProps {
appState: AppState;
@@ -28,14 +30,24 @@ export class Commands extends React.Component<CommandsProps, {}> {
public render() {
const { appState } = this.props;
const { isBisectCommandShowing: isBisectCommandShowing } = appState;
return (
<div className='commands'>
<div>
<ControlGroup fill={true} vertical={false}>
<VersionChooser appState={appState} />
{this.getVersionChooser()}
<Runner appState={appState} />
</ControlGroup>
{
// tslint:disable-next-line jsx-no-multiline-js
isBisectCommandShowing &&
(
<ControlGroup fill={true} vertical={false}>
<BisectHandler appState={appState} />
</ControlGroup>
)
}
<ControlGroup fill={true} vertical={false}>
<Button
active={appState.isConsoleShowing}
@@ -43,6 +55,12 @@ export class Commands extends React.Component<CommandsProps, {}> {
text='Console'
onClick={appState.toggleConsole}
/>
<Button
active={appState.isNodeMode}
icon='ninja'
text='Node.js Mode'
onClick={appState.toggleNodeMode}
/>
<EditorDropdown appState={appState} />
</ControlGroup>
</div>
@@ -53,4 +71,14 @@ export class Commands extends React.Component<CommandsProps, {}> {
</div>
);
}
public getVersionChooser() {
const { appState } = this.props;
if (appState.isNodeMode) {
return <NodeVersionChooser appState={appState} />;
} else {
return <ElectronVersionChooser appState={appState} />;
}
}
}
+31 -24
View File
@@ -6,7 +6,9 @@ import * as React from 'react';
import * as semver from 'semver';
import { NpmVersion } from '../../interfaces';
import { IpcEvents } from '../../ipc-events';
import { getElectronNameForPlatform } from '../../utils/electron-name';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
export interface AddVersionDialogProps {
@@ -16,7 +18,7 @@ export interface AddVersionDialogProps {
export interface AddVersionDialogState {
isValidElectron: boolean;
isValidVersion: boolean;
file?: File;
folderPath?: string;
version: string;
}
@@ -39,8 +41,15 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
this.onSubmit = this.onSubmit.bind(this);
this.onClose = this.onClose.bind(this);
this.onChangeFile = this.onChangeFile.bind(this);
this.onChangeVersion = this.onChangeVersion.bind(this);
ipcRendererManager.on(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, (_event, [file]) => {
this.setFolderPath(file);
});
}
public componentWillUnmount() {
ipcRendererManager.removeAllListeners(IpcEvents.LOAD_LOCAL_VERSION_FOLDER);
}
/**
@@ -48,16 +57,11 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public async onChangeFile(event: React.FormEvent<HTMLInputElement>) {
const { files } = event.target as any;
public async setFolderPath(folderPath: string) {
const { binaryManager } = this.props.appState;
const file = files && files[0] ? files[0] : undefined;
const isValidElectron = !!await binaryManager.getIsDownloaded('custom', folderPath);
const isValidElectron = !!(file
&& file.path
&& await binaryManager.getIsDownloaded('custom', file.path));
this.setState({ file, isValidElectron });
this.setState({ folderPath, isValidElectron });
}
public onChangeVersion(event: React.ChangeEvent<HTMLInputElement>) {
@@ -67,7 +71,7 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
this.setState({
version,
isValidVersion,
});
});
}
/**
@@ -76,18 +80,18 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
* @returns {Promise<void>}
*/
public async onSubmit(): Promise<void> {
const { file, version } = this.state;
const { folderPath, version } = this.state;
if (!file) return;
if (!folderPath) return;
const name = file.path
const name = folderPath
.slice(-20)
.split(path.sep)
.slice(1)
.join(path.sep);
const toAdd: NpmVersion = {
localPath: file.path,
localPath: folderPath,
version,
name
};
@@ -129,12 +133,16 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
public render() {
const { isAddVersionDialogShowing } = this.props.appState;
const inputProps = { webkitdirectory: 'true' };
const { file } = this.state;
const inputProps = {
onClick: (e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
e.preventDefault();
ipcRendererManager.send(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG);
}
};
const { folderPath } = this.state;
const text = file && file.path
? file.path
: `Select the folder containing ${getElectronNameForPlatform()}...`;
const text = folderPath ||
`Select the folder containing ${getElectronNameForPlatform()}...`;
return (
<Dialog
@@ -145,7 +153,6 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
>
<div className='bp3-dialog-body'>
<FileInput
onInputChange={this.onChangeFile}
id='custom-electron-version'
inputProps={inputProps as any}
text={text}
@@ -163,9 +170,9 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
}
private renderPath(): JSX.Element | null {
const { file, isValidElectron } = this.state;
const { folderPath, isValidElectron } = this.state;
if (!file || !file.path) return null;
if (!folderPath) return null;
const info = isValidElectron
? `We found an ${getElectronNameForPlatform()} in this folder.`
@@ -208,7 +215,7 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
isValidElectron: false,
isValidVersion: false,
version: '',
file: undefined
folderPath: undefined
});
}
}
+164
View File
@@ -0,0 +1,164 @@
import { Button, Dialog, Label, MenuItem } from '@blueprintjs/core';
import { Select } from '@blueprintjs/select';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion } from '../../interfaces';
import { Bisector } from '../bisect';
import { AppState } from '../state';
import { filterItem, getItemIcon, getVersionsFromAppState, renderItem } from './commands-electron-chooser';
const ElectronVersionSelect = Select.ofType<ElectronVersion>();
export interface BisectDialogProps {
appState: AppState;
}
export interface BisectDialogState {
startIndex?: number;
endIndex?: number;
allVersions: Array<ElectronVersion>;
}
/**
* The "add version" dialog allows users to add custom builds of Electron.
*
* @class AddVersionDialog
* @extends {React.Component<BisectDialogProps, BisectDialogState>}
*/
@observer
export class BisectDialog extends React.Component<BisectDialogProps, BisectDialogState> {
constructor(props: BisectDialogProps) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
this.onClose = this.onClose.bind(this);
this.onBeginSelect = this.onBeginSelect.bind(this);
this.onEndSelect = this.onEndSelect.bind(this);
const allVersions = getVersionsFromAppState(this.props.appState);
this.state = { allVersions };
}
public onBeginSelect(version: ElectronVersion) {
this.setState({ startIndex: this.state.allVersions.indexOf(version) });
}
public onEndSelect(version: ElectronVersion) {
this.setState({ endIndex: this.state.allVersions.indexOf(version) });
}
/**
* Handles the submission of the dialog
*
* @returns {Promise<void>}
*/
public async onSubmit(): Promise<void> {
const { endIndex, startIndex, allVersions } = this.state;
const { appState } = this.props;
if (endIndex === undefined || startIndex === undefined) {
return;
}
const bisectRange = allVersions
.slice(endIndex, startIndex + 1)
.reverse();
appState.Bisector = new Bisector(bisectRange);
const initialBisectPivot = appState.Bisector.getCurrentVersion().version;
appState.setVersion(initialBisectPivot);
this.onClose();
}
/**
* Closes the dialog
*/
public onClose() {
this.props.appState.isBisectDialogShowing = false;
}
get buttons() {
const canSubmit =
!!this.state.startIndex &&
!!this.state.endIndex &&
this.state.startIndex > this.state.endIndex;
return [
(
<Button
icon='play'
key='submit'
disabled={!canSubmit}
onClick={this.onSubmit}
text='Begin'
/>
), (
<Button
icon='cross'
key='cancel'
onClick={this.onClose}
text='Cancel'
/>
)
];
}
public render() {
const { isBisectDialogShowing } = this.props.appState;
const { startIndex, endIndex, allVersions } = this.state;
return (
<Dialog
isOpen={isBisectDialogShowing}
onClose={this.onClose}
title='Start a bisect session'
className='dialog-add-version'
>
<div className='bp3-dialog-body'>
<Label>
Earliest Version
<ElectronVersionSelect
filterable={true}
items={allVersions}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onBeginSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
>
<Button
text={startIndex ? `v${allVersions[startIndex].version}` : ``}
icon={startIndex ? getItemIcon(allVersions[startIndex]) : 'small-minus'}
fill={true}
/>
</ElectronVersionSelect>
</Label>
<Label>
Latest Version
<ElectronVersionSelect
filterable={true}
items={allVersions.slice(0, startIndex!)}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onEndSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
disabled={!startIndex}
>
<Button
text={endIndex ? `v${allVersions[endIndex].version}` : ``}
icon={endIndex ? getItemIcon(allVersions[endIndex]) : 'small-minus'}
fill={true}
disabled={!startIndex}
/>
</ElectronVersionSelect>
</Label>
</div>
<div className='bp3-dialog-footer'>
<div className='bp3-dialog-footer-actions'>
{this.buttons}
</div>
</div>
</Dialog>
);
}
}
+13 -2
View File
@@ -4,6 +4,7 @@ import * as React from 'react';
import { AppState } from '../state';
import { AddThemeDialog } from './dialog-add-theme';
import { AddVersionDialog } from './dialog-add-version';
import { BisectDialog } from './dialog-bisect';
import { ConfirmDialog } from './dialog-confirm';
import { TokenDialog } from './dialog-token';
import { WarningDialog } from './dialog-warning';
@@ -23,7 +24,13 @@ export interface DialogsProps {
export class Dialogs extends React.Component<DialogsProps, {}> {
public render() {
const { appState } = this.props;
const { isTokenDialogShowing, isSettingsShowing, isAddVersionDialogShowing, isThemeDialogShowing } = appState;
const {
isTokenDialogShowing,
isSettingsShowing,
isAddVersionDialogShowing,
isThemeDialogShowing,
isBisectDialogShowing
} = appState;
const maybeToken = isTokenDialogShowing
? <TokenDialog key='dialogs' appState={appState} />
: null;
@@ -35,9 +42,12 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
: null;
const maybeMonaco = isThemeDialogShowing ? <AddThemeDialog appState={appState} />
: null;
const maybeBisect = isBisectDialogShowing
? <BisectDialog key='bisect-dialog' appState={appState} />
: null;
const eitherWarningOrPrompt = appState.isWarningDialogShowing
? <WarningDialog appState={appState} />
: <ConfirmDialog appState={appState}/>;
: <ConfirmDialog appState={appState} />;
return (
<div key='dialogs' className='dialogs'>
@@ -45,6 +55,7 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
{maybeSettings}
{maybeAddLocalVersion}
{maybeMonaco}
{maybeBisect}
{eitherWarningOrPrompt}
</div>
);
@@ -12,9 +12,9 @@ import {
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../interfaces';
import { ElectronVersion, ElectronVersionSource, VersionState } from '../../interfaces';
import { normalizeVersion } from '../../utils/normalize-version';
import { sortedElectronMap } from '../../utils/sorted-electron-map';
import { sortedElectronMap } from '../../utils/sorted-map';
import { AppState } from '../state';
import { ElectronReleaseChannel, getReleaseChannel } from '../versions';
@@ -69,7 +69,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 +202,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);
};
@@ -211,21 +211,21 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
label='Include Electron versions that are:'
>
<Checkbox
checked={getIsChecked(ElectronVersionState.downloading)}
checked={getIsChecked(VersionState.downloading)}
label='Downloading'
id='downloading'
onChange={this.handleStateChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronVersionState.ready)}
checked={getIsChecked(VersionState.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}
@@ -350,10 +350,10 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
let icon: IconName = 'box';
let humanState = 'Downloaded';
if (state === ElectronVersionState.downloading) {
if (state === VersionState.downloading) {
icon = 'cloud-download';
humanState = 'Downloading';
} else if (state === ElectronVersionState.unknown) {
} else if (state === VersionState.unknown) {
icon = 'cloud';
humanState = 'Not downloaded';
}
+1 -1
View File
@@ -3,7 +3,7 @@ import { when } from 'mobx';
import { EditorId, EditorValues } from '../interfaces';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
import { getOctokit } from '../utils/octokit';
import { sortedElectronMap } from '../utils/sorted-electron-map';
import { sortedElectronMap } from '../utils/sorted-map';
import { ELECTRON_ORG, ELECTRON_REPO } from './constants';
import { getContent } from './content';
import { AppState } from './state';
+121 -42
View File
@@ -8,11 +8,12 @@ import {
EditorId,
ElectronVersion,
ElectronVersionSource,
ElectronVersionState,
MosaicId,
NodeVersion,
NpmVersion,
OutputEntry,
OutputOptions,
VersionState,
WarningDialogTexts
} from '../interfaces';
import { IpcEvents } from '../ipc-events';
@@ -24,6 +25,7 @@ import { fancyImport } from '../utils/import';
import { normalizeVersion } from '../utils/normalize-version';
import { isEditorBackup, isEditorId, isPanelId } from '../utils/type-checks';
import { BinaryManager } from './binary';
import { Bisector } from './bisect';
import { DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
import { getContent, isContentUnchanged } from './content';
import { getLocalTypePathForVersion, updateEditorTypeDefinitions } from './fetch-types';
@@ -33,14 +35,16 @@ import { activateTheme } from './themes';
import {
addLocalVersion,
ElectronReleaseChannel,
getAllElectronVersions,
getDefaultVersion,
getElectronVersions,
getUpdatedElectronVersions,
getUpdatedNodeVersions,
saveLocalVersions
} from './versions';
const knownVersions = getElectronVersions();
const knownVersions = getAllElectronVersions();
const defaultVersion = getDefaultVersion(knownVersions);
const defaultNodeVersion = '12.0.0';
/**
* Editors exist outside of React's world. To make things *a lot*
@@ -65,8 +69,8 @@ window.ElectronFiddle = window.ElectronFiddle || {
export class AppState {
// -- Persisted settings ------------------
@observable public version: string = defaultVersion;
@observable public nodeVersion: string = defaultNodeVersion;
@observable public theme: string | null = localStorage.getItem('theme');
@observable public isClearingConsoleOnRun: boolean = !!this.retrieve('isClearingConsoleOnRun');
@observable public gitHubAvatarUrl: string | null = localStorage.getItem('gitHubAvatarUrl');
@observable public gitHubName: string | null = localStorage.getItem('gitHubName');
@observable public gitHubLogin: string | null = localStorage.getItem('gitHubLogin');
@@ -74,41 +78,47 @@ export class AppState {
@observable public gitHubPublishAsPublic: boolean = !!this.retrieve('gitHubPublishAsPublic');
@observable public versionsToShow: Array<ElectronReleaseChannel> =
this.retrieve('versionsToShow') as Array<ElectronReleaseChannel>
|| [ ElectronReleaseChannel.stable, ElectronReleaseChannel.beta ];
@observable public statesToShow: Array<ElectronVersionState> =
this.retrieve('statesToShow') as Array<ElectronVersionState>
|| [ ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.unknown ];
|| [ElectronReleaseChannel.stable, ElectronReleaseChannel.beta];
@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 binaryManager: BinaryManager = new BinaryManager();
@observable public isClearingConsoleOnRun: boolean = !!this.retrieve('isClearingConsoleOnRun');
// -- Various session-only state ------------------
@observable public gistId: string = '';
@observable public isPublishing: boolean = false;
@observable public versions: Record<string, ElectronVersion> = arrayToStringMap(knownVersions);
@observable public nodeVersions: Record<string, NodeVersion> = arrayToStringMap([] as Array<NodeVersion>);
@observable public output: Array<OutputEntry> = [];
@observable public localPath: string | undefined;
@observable public isUpdatingElectronVersions = false;
@observable public warningDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
@observable public confirmationDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
@observable public warningDialogLastResult: boolean | null = null;
@observable public confirmationPromptLastResult: boolean | null = null;
@observable public isRunning = false;
@observable public mosaicArrangement: MosaicNode<MosaicId> | null = DEFAULT_MOSAIC_ARRANGEMENT;
@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 isUpdatingElectronVersions: boolean = false;
// -- Various "isShowing" settings ------------------
@observable public isBisectCommandShowing: boolean;
@observable public isConsoleShowing: boolean = false;
@observable public isTokenDialogShowing: boolean = false;
@observable public isWarningDialogShowing: boolean = false;
@observable public isConfirmationPromptShowing: boolean = false;
@observable public isSettingsShowing: boolean = false;
@observable public isUnsaved: boolean = false;
@observable public isBisectDialogShowing: boolean = false;
@observable public isAddVersionDialogShowing: boolean = false;
@observable public isThemeDialogShowing: boolean = false;
@observable public isNodeMode: boolean = false;
@observable public isTourShowing: boolean = !localStorage.getItem('hasShownTour');
// -- Editor Values stored when we close the editor ------------------
@@ -126,17 +136,22 @@ export class AppState {
this.pushOutput = this.pushOutput.bind(this);
this.removeVersion = this.removeVersion.bind(this);
this.setVersion = this.setVersion.bind(this);
this.setNodeVersion = this.setNodeVersion.bind(this);
this.showTour = this.showTour.bind(this);
this.signOutGitHub = this.signOutGitHub.bind(this);
this.toggleBisectCommands = this.toggleBisectCommands.bind(this);
this.toggleAuthDialog = this.toggleAuthDialog.bind(this);
this.toggleConsole = this.toggleConsole.bind(this);
this.toggleNodeMode = this.toggleNodeMode.bind(this);
this.clearConsole = this.clearConsole.bind(this);
this.toggleSettings = this.toggleSettings.bind(this);
this.toggleBisectDialog = this.toggleBisectDialog.bind(this);
this.updateElectronVersions = this.updateElectronVersions.bind(this);
ipcRendererManager.on(IpcEvents.OPEN_SETTINGS, this.toggleSettings);
ipcRendererManager.on(IpcEvents.SHOW_WELCOME_TOUR, this.showTour);
ipcRendererManager.on(IpcEvents.CLEAR_CONSOLE, this.clearConsole);
ipcRendererManager.on(IpcEvents.BISECT_COMMANDS_TOGGLE, this.toggleBisectCommands);
// Setup auto-runs
autorun(() => this.save('theme', this.theme));
@@ -189,6 +204,7 @@ export class AppState {
// Update our known versions
this.updateElectronVersions();
this.updateNodeVersions();
// Make sure the console isn't all empty and sad
this.pushOutput('Console ready 🔬');
@@ -206,6 +222,18 @@ export class AppState {
}
}
/**
* Returns the current NodeVersion or the first
* one that can be found.
*/
@computed get currentNodeVersion(): NodeVersion {
if (this.nodeVersions[this.nodeVersion]) {
return this.nodeVersions[this.nodeVersion];
} else {
return this.nodeVersions[defaultNodeVersion];
}
}
/**
* Update the Electron versions: First, fetch them from GitHub,
* then update their respective downloaded state.
@@ -226,6 +254,21 @@ export class AppState {
this.isUpdatingElectronVersions = false;
}
/**
* Update the Node versions: First, fetch them from Node.js,
* then update their respective downloaded state.
*
* Fails silently.
*/
@action public async updateNodeVersions() {
try {
const nodeVersions = await getUpdatedNodeVersions();
this.nodeVersions = arrayToStringMap(nodeVersions);
} catch (error) {
console.warn(`State: Could not update Node.js versions`, error);
}
}
@action public async getName() {
if (!this.name) {
this.name = await getName(this);
@@ -238,10 +281,21 @@ export class AppState {
this.isConsoleShowing = !this.isConsoleShowing;
}
@action public toggleNodeMode() {
this.isNodeMode = !this.isNodeMode;
}
@action public clearConsole() {
this.output = [];
}
@action public toggleBisectCommands() {
// guard against hiding the commands when executing a bisect
if (!this.Bisector && !this.isBisectDialogShowing) {
this.isBisectCommandShowing = !this.isBisectCommandShowing;
}
}
@action public toggleAddVersionDialog() {
this.isAddVersionDialogShowing = !this.isAddVersionDialogShowing;
}
@@ -262,6 +316,10 @@ export class AppState {
}
}
@action public toggleBisectDialog() {
this.isBisectDialogShowing = !this.isBisectDialogShowing;
}
@action public toggleConfirmationPromptDialog() {
this.isConfirmationPromptShowing = !this.isConfirmationPromptShowing;
@@ -314,16 +372,16 @@ export class AppState {
@action public addLocalVersion(input: NpmVersion) {
addLocalVersion(input);
this.versions = arrayToStringMap(getElectronVersions());
this.versions = arrayToStringMap(getAllElectronVersions());
this.updateDownloadedVersionState();
}
/**
* Remove a version of Electron
*
* @param {string} input
* @returns {Promise<void>}
*/
/**
* Remove a version of Electron
*
* @param {string} input
* @returns {Promise<void>}
*/
@action public async removeVersion(input: string) {
const version = normalizeVersion(input);
const release = this.versions[version];
@@ -350,19 +408,19 @@ export class AppState {
saveLocalVersions(versionsAsArray);
} else {
await this.binaryManager.remove(version);
updatedVersions[version].state = ElectronVersionState.unknown;
updatedVersions[version].state = VersionState.unknown;
}
this.versions = updatedVersions;
this.updateDownloadedVersionState();
}
/**
* Download a version of Electron.
*
* @param {string} input
* @returns {Promise<void>}
*/
/**
* Download a version of Electron.
*
* @param {string} input
* @returns {Promise<void>}
*/
@action public async downloadVersion(input: string) {
const version = normalizeVersion(input);
console.log(`State: Downloading Electron ${version}`);
@@ -376,7 +434,7 @@ export class AppState {
console.log(`State: Instructing BinaryManager to fetch v${version}`);
const updatedVersions = { ...this.versions };
updatedVersions[version] = updatedVersions[version] || {};
updatedVersions[version].state = ElectronVersionState.downloading;
updatedVersions[version].state = VersionState.downloading;
this.versions = updatedVersions;
await this.binaryManager.setup(version);
@@ -386,12 +444,33 @@ export class AppState {
}
}
/**
* Select a version of Electron (and download it if necessary).
*
* @param {string} input
* @returns {Promise<void>}
*/
/**
* Select a version of Node.js (and download fi necessary)
*
* @param {string} input
* @returns {Promise<void>}
*/
@action public async setNodeVersion(input: string) {
const version = normalizeVersion(input);
if (!this.nodeVersions[version]) {
console.warn(`State: Called setNodeVersion() with ${version}, which does not exist.`);
this.setVersion(this.nodeVersions[0].version);
return;
}
console.log(`State: Switching to Node ${version}`);
this.nodeVersion = version;
}
/**
* Select a version of Electron (and download it if necessary).
*
* @param {string} input
* @returns {Promise<void>}
*/
@action public async setVersion(input: string) {
const version = normalizeVersion(input);
@@ -436,18 +515,18 @@ export class AppState {
await this.downloadVersion(version);
}
/**
* Go and check which versions have already been downloaded.
*
* @returns {Promise<void>}
*/
/**
* Go and check which versions have already been downloaded.
*
* @returns {Promise<void>}
*/
@action public async updateDownloadedVersionState(): Promise<void> {
const downloadedVersions = await this.binaryManager.getDownloadedVersions();
const updatedVersions = { ...this.versions };
(downloadedVersions || []).forEach((version) => {
if (updatedVersions[version]) {
updatedVersions[version].state = ElectronVersionState.ready;
updatedVersions[version].state = VersionState.ready;
}
});
console.log(`State: Updated version state`, updatedVersions);
@@ -577,7 +656,7 @@ export class AppState {
*/
@action public showMosaic(id: MosaicId) {
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
this.setVisibleMosaics([ ...currentlyVisible, id ]);
this.setVisibleMosaics([...currentlyVisible, id]);
}
/**
+3 -3
View File
@@ -5,9 +5,9 @@ import {
} from 'electron';
import { autorun } from 'mobx';
import { ElectronVersion, ElectronVersionState } from '../interfaces';
import { ElectronVersion, VersionState } from '../interfaces';
import { getNiceGreeting } from '../utils/nice-greeting';
import { sortedElectronMap } from '../utils/sorted-electron-map';
import { sortedElectronMap } from '../utils/sorted-map';
import { AppState } from './state';
const { TouchBar } = remote;
@@ -26,7 +26,7 @@ const {
* @returns {string}
*/
export function getItemIcon(
{ state }: Partial<ElectronVersion> = { state: ElectronVersionState.unknown }
{ state }: Partial<ElectronVersion> = { state: VersionState.unknown }
) {
return state === 'ready'
? '💾'
+62 -16
View File
@@ -1,4 +1,4 @@
import { ElectronVersion, ElectronVersionSource, ElectronVersionState, NpmVersion } from '../interfaces';
import { ElectronVersion, ElectronVersionSource, NodeVersion, NpmVersion, VersionState } from '../interfaces';
import { normalizeVersion } from '../utils/normalize-version';
export const enum ElectronReleaseChannel {
@@ -82,7 +82,7 @@ export const enum VersionKeys {
* @param {() => Array<NpmVersion>} fallbackMethod
* @returns {Array<NpmVersion>}
*/
function getVersions(
function getElectronVersions(
key: VersionKeys, fallbackMethod: () => Array<NpmVersion>
): Array<NpmVersion> {
const fromLs = window.localStorage.getItem(key);
@@ -117,7 +117,7 @@ function getVersions(
* @param {VersionKeys} key
* @param {Array<NpmVersion} versions
*/
function saveVersions(key: VersionKeys, versions: Array<NpmVersion>) {
function saveElectronVersions(key: VersionKeys, versions: Array<NpmVersion>) {
const stringified = JSON.stringify(versions);
window.localStorage.setItem(key, stringified);
}
@@ -127,12 +127,12 @@ function saveVersions(key: VersionKeys, versions: Array<NpmVersion>) {
*
* @returns {Array<NpmVersion>}
*/
export function getElectronVersions(): Array<ElectronVersion> {
export function getAllElectronVersions(): Array<ElectronVersion> {
const known: Array<ElectronVersion> = getKnownVersions().map((version) => {
return {
...version,
source: ElectronVersionSource.remote,
state: ElectronVersionState.unknown
state: VersionState.unknown
};
});
@@ -140,11 +140,11 @@ export function getElectronVersions(): Array<ElectronVersion> {
return {
...version,
source: ElectronVersionSource.local,
state: ElectronVersionState.ready
state: VersionState.ready
};
});
return [ ...known, ...local ];
return [...known, ...local];
}
/**
@@ -171,7 +171,7 @@ export function addLocalVersion(input: NpmVersion): Array<NpmVersion> {
* @returns {Array<NpmVersion>}
*/
export function getLocalVersions(): Array<NpmVersion> {
const versions = getVersions(VersionKeys.local, () => []);
const versions = getElectronVersions(VersionKeys.local, () => []);
return versions;
}
@@ -190,7 +190,7 @@ export function saveLocalVersions(versions: Array<NpmVersion | ElectronVersion>)
return true;
});
return saveVersions(VersionKeys.local, filteredVersions);
return saveElectronVersions(VersionKeys.local, filteredVersions);
}
/**
@@ -200,7 +200,7 @@ export function saveLocalVersions(versions: Array<NpmVersion | ElectronVersion>)
* @returns {Array<NpmVersion>}
*/
export function getKnownVersions(): Array<NpmVersion> {
return getVersions(VersionKeys.known, () => require('../../static/releases.json'));
return getElectronVersions(VersionKeys.known, () => require('../../static/releases.json'));
}
/**
@@ -209,7 +209,7 @@ export function getKnownVersions(): Array<NpmVersion> {
* @param {Array<NpmVersion>} versions
*/
export function saveKnownVersions(versions: Array<NpmVersion>) {
return saveVersions(VersionKeys.known, versions);
return saveElectronVersions(VersionKeys.known, versions);
}
/**
@@ -222,20 +222,66 @@ export function saveKnownVersions(versions: Array<NpmVersion>) {
export async function getUpdatedElectronVersions(
): Promise<Array<ElectronVersion>> {
try {
await fetchVersions();
await fetchElectronVersions();
} catch (error) {
console.warn(`Versions: Failed to fetch versions`, { error });
console.warn(`Versions: Failed to fetch Electron versions`, { error });
}
return getElectronVersions();
return getAllElectronVersions();
}
export async function getUpdatedNodeVersions(): Promise<Array<NodeVersion>> {
try {
return await fetchNodeVersions();
} catch (error) {
console.warn(`Versions: Failed to fetch Node.js versions`, { error });
}
return [];
}
/**
* Fetch the latest known versions directly from npm.
* Fetch the latest known Node.js versions directly from Node.js.
*
* @returns {Promise<Array<NpmVersion>>}
*
* let entry = {
* "headers": 1,
* "linux-arm64": 2,
* "linux-x64": 6,
* "osx-x64-tar": 8,
* "win-x64-zip": 14,
* "win-x86-zip": 18
* }
*
* nodejs.org/dist/${version}/node-${version}-${files[entry]}.file-ext
* ^ for windows, take the plat and extend with the file extension in the identifier above
*/
export async function fetchNodeVersions() {
const channels = [
`https://nodejs.org/dist/index.json`
];
let output: Array<NodeVersion> = [];
for (const channelUrl of channels) {
const response = await window.fetch(channelUrl);
const versions = await response.json();
output = versions.map((input: any) => ({
version: normalizeVersion(input.version)
}));
}
return output;
}
/**
* Fetch the latest known Electron versions directly from npm.
*
* @returns {Promise<Array<NpmVersion>>}
*/
export async function fetchVersions() {
export async function fetchElectronVersions() {
const channels = [
`https://registry.npmjs.org/electron`, // stable, beta
`https://registry.npmjs.org/electron-nightly` // nightly
+4 -4
View File
@@ -1,4 +1,4 @@
import { ElectronVersion } from '../interfaces';
import { Version } from '../interfaces';
import { normalizeVersion } from './normalize-version';
/**
@@ -8,9 +8,9 @@ import { normalizeVersion } from './normalize-version';
* @param {Array<ElectronVersion>} input
* @returns {Record<string, ElectronVersion>}
*/
export function arrayToStringMap(
input: Array<ElectronVersion>
): Record<string, ElectronVersion> {
export function arrayToStringMap<T extends Version>(
input: Array<T>
): Record<string, T> {
const output = {};
input.forEach((version) => {
@@ -1,6 +1,6 @@
import * as semver from 'semver';
import { ElectronVersion } from '../interfaces';
import { ElectronVersion, NodeVersion } from '../interfaces';
/**
* Sorts Electron versions and returns the result of a
@@ -13,6 +13,28 @@ import { ElectronVersion } from '../interfaces';
export function sortedElectronMap<T>(
versions: Record<string, ElectronVersion>,
mapFn: (key: string, version: ElectronVersion) => T
) {
return sortedMap<ElectronVersion, T>(versions, mapFn);
}
/**
* Sorts Node versions and returns the result of a
* map function.
*
* @param {Record<string, NodeVersion>} versions
* @param {(key: string, version: NodeVersion) => void} mapFn
* @returns {Array<T>}
*/
export function sortedNodeMap<T>(
versions: Record<string, NodeVersion>,
mapFn: (key: string, version: NodeVersion) => T
) {
return sortedMap<NodeVersion, T>(versions, mapFn);
}
function sortedMap<T1, T2>(
versions: Record<string, T1>,
mapFn: (key: string, version: T1) => T2
) {
return Object.keys(versions)
.sort((a, b) => {
@@ -30,5 +52,5 @@ export function sortedElectronMap<T>(
return semver.gt(a, b, true) ? -1 : 1;
})
.map((key) => mapFn(key, versions[key])) as Array<T>;
.map((key) => mapFn(key, versions[key])) as Array<T2>;
}
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>
+10 -7
View File
@@ -12,13 +12,16 @@ app.on('ready', () => {
// Show an "Open File" dialog and attempt to open
// the chosen file in our window.
//
// If you provide a callback, the method will be asynchronous,
// but in thise case, we'll just use the synchronous variant.
const file = dialog.showOpenDialog({
title: 'Hello!',
dialog.showOpenDialog(mainWindow, {
properties: ['openFile']
}).then(result => {
if (result.canceled) {
console.log('Dialog was canceled')
} else {
const file = result.filePaths[0]
mainWindow.loadURL(`file://${file}`)
}
}).catch(err => {
console.log(err)
})
mainWindow.loadURL(`file://${file}`)
})
+19 -12
View File
@@ -1,12 +1,6 @@
import { setupDevTools } from '../../src/main/devtools';
let mockIsDevMode = false;
jest.mock('../../src/utils/devmode', () => ({
get isDevMode() {
return mockIsDevMode;
}
}));
import { isDevMode } from '../../src/utils/devmode';
jest.mock('../../src/utils/devmode');
jest.mock('electron-devtools-installer', () => ({
default: jest.fn(),
@@ -17,18 +11,31 @@ jest.mock('electron-devtools-installer', () => ({
describe('devtools', () => {
it('does not set up developer tools if not in dev mode', () => {
const devtools = require('electron-devtools-installer');
(isDevMode as jest.Mock).mockReturnValue(false);
setupDevTools();
expect(devtools.default).toHaveBeenCalledTimes(0);
});
it('sets up developer tools if not in dev mode', () => {
it('sets up developer tools if in dev mode', () => {
const devtools = require('electron-devtools-installer');
mockIsDevMode = true;
(isDevMode as jest.Mock).mockReturnValue(true);
setupDevTools();
expect(devtools.default).toHaveBeenCalledTimes(1);
});
it('catch error in setting up developer tools', async (done) => {
const devtools = require('electron-devtools-installer');
// throw devtool error
devtools.default.mockRejectedValue(new Error('devtool error'));
(isDevMode as jest.Mock).mockReturnValue(true);
try {
await setupDevTools();
done();
} catch (e) {
expect(e).toMatch('error');
}
});
});
+79 -9
View File
@@ -3,26 +3,96 @@ import { setupDialogs } from '../../src/main/dialogs';
import { ipcMainManager } from '../../src/main/ipc';
import { dialog } from 'electron';
import { flushPromises } from '../utils';
jest.mock('../../src/main/windows');
describe('dialogs', () => {
it('sets up dialogs', () => {
setupDialogs();
beforeEach(() => {
setupDialogs();
});
it('sets up dialogs', () => {
expect(ipcMainManager.eventNames()).toEqual([
IpcEvents.SHOW_WARNING_DIALOG,
IpcEvents.SHOW_CONFIRMATION_DIALOG
IpcEvents.SHOW_CONFIRMATION_DIALOG,
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG
]);
});
it('tries to show a dialog if triggered', () => {
setupDialogs();
describe('warning dialog', () => {
it('shows dialog when triggering IPC event', () => {
ipcMainManager.emit(IpcEvents.SHOW_WARNING_DIALOG, {}, { hi: 'hello' });
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
type: 'warning',
hi: 'hello'
});
});
});
ipcMainManager.emit(IpcEvents.SHOW_WARNING_DIALOG, {}, { hi: 'hello' });
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
type: 'warning',
hi: 'hello'
describe('confirmation dialog', () => {
it('shows dialog when triggering IPC event', () => {
ipcMainManager.emit(IpcEvents.SHOW_CONFIRMATION_DIALOG, {}, { hi: 'hello' });
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
type: 'warning',
hi: 'hello'
});
});
});
describe('local version folder dialog', () => {
it('shows dialog when triggering IPC event', () => {
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
filePaths: []
});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: jest.fn()
});
expect(dialog.showOpenDialog).toHaveBeenCalledWith(expect.objectContaining({
properties: ['openDirectory']
}));
});
it('triggers IPC load local version event', async () => {
const replyFn = jest.fn();
const paths = ['/test/path/'];
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
filePaths: paths
});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: replyFn
});
await flushPromises();
expect(replyFn).toHaveBeenCalledWith(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, paths);
});
it('does nothing if not given a path', async () => {
const replyFn = jest.fn();
// empty array
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
filePaths: []
});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: replyFn
});
await flushPromises();
expect(replyFn).not.toHaveBeenCalled();
// nothing in response
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: replyFn
});
await flushPromises();
expect(replyFn).not.toHaveBeenCalled();
});
});
});
+16 -2
View File
@@ -78,6 +78,20 @@ describe('menu', () => {
(toggleMap as any).click();
expect(ipcMainManager.send).toHaveBeenCalledTimes(2);
});
it('adds Bisect toggle', () => {
overridePlatform('linux');
setupMenu();
const result = (electron.Menu.buildFromTemplate as any).mock.calls[0][0];
const submenu = result[2].submenu as Array<Electron.MenuItemConstructorOptions>;
const toggleSoftWrap = submenu.find(({ label }) => label === 'Toggle Bisect Helper');
(toggleSoftWrap as any).click();
expect(ipcMainManager.send).toHaveBeenCalledWith(IpcEvents.BISECT_COMMANDS_TOGGLE);
});
});
describe('menu groups', () => {
@@ -174,7 +188,7 @@ describe('menu', () => {
it('attempts to open a template on click', () => {
showMe.submenu[0].submenu[0].click();
expect(ipcMainManager.send)
.toHaveBeenCalledWith(IpcEvents.FS_OPEN_TEMPLATE, [ 'App' ]);
.toHaveBeenCalledWith(IpcEvents.FS_OPEN_TEMPLATE, ['App']);
});
});
@@ -182,7 +196,7 @@ describe('menu', () => {
let tasks: any;
beforeEach(() => {
const mock = (electron.Menu.buildFromTemplate as any).mock;
const mock = (electron.Menu.buildFromTemplate as any).mock;
const menu = mock.calls[0][0];
tasks = menu[menu.length - 3];
});
-1
View File
@@ -116,7 +116,6 @@ const app = {
quit: jest.fn(),
relaunch: jest.fn(),
setJumpList: jest.fn(),
setName: jest.fn(),
requestSingleInstanceLock: jest.fn(),
on: jest.fn(),
once: jest.fn()
+70
View File
@@ -0,0 +1,70 @@
import { ElectronVersionSource, ElectronVersionState } from '../../src/interfaces';
import { Bisector } from '../../src/renderer/bisect';
const generateVersionRange = (rangeLength: number) =>
(new Array(rangeLength)).fill(0).map((_, i) => ({
state: ElectronVersionState.ready,
version: `${i + 1}.0.0`,
source: ElectronVersionSource.local
}));
describe('bisect', () => {
let bisector: Bisector;
beforeEach(() => {
const versions = generateVersionRange(9);
bisector = new Bisector(versions);
});
it('selects a pivot in the middle of the array', () => {
const pivot = bisector.getCurrentVersion();
const middleIndex = Math.floor(bisector.revList.length / 2);
expect(pivot).toBe(bisector.revList[middleIndex]);
});
describe('continue()', () => {
it('returns the current version', () => {
const result = bisector.continue(true);
const version = bisector.getCurrentVersion();
expect(result).toBe(version);
});
it('discards lower half of the range if pivot is good version', () => {
const pivotVersion = bisector.getCurrentVersion();
bisector.continue(true);
expect(bisector.revList[bisector.minRev]).toBe(pivotVersion);
});
it('discards upper half of the range if pivot is bad version', () => {
const pivotVersion = bisector.getCurrentVersion();
bisector.continue(false);
expect(bisector.revList[bisector.maxRev]).toBe(pivotVersion);
});
it('terminates if fewer than 2 items are left', () => {
const versions = generateVersionRange(2);
bisector = new Bisector(versions);
expect(bisector.revList.length).toBe(2);
const responseGood = bisector.continue(true);
expect(responseGood).toHaveLength(2);
expect(versions).toContain(responseGood[0]);
expect(versions).toContain(responseGood[1]);
bisector = new Bisector(versions);
expect(bisector.revList.length).toBe(2);
const responseBad = bisector.continue(false);
expect(responseBad).toHaveLength(2);
expect(versions).toContain(responseBad[0]);
expect(versions).toContain(responseBad[1]);
});
});
describe('getCurrentVersion()', () => {
it('returns a version within the range', () => {
const version = bisector.getCurrentVersion();
expect(bisector.revList).toContain(version);
});
});
});
@@ -0,0 +1,27 @@
// 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`] = `
<Fragment>
<Blueprint3.Button
icon="thumbs-up"
onClick={[Function]}
/>
<Blueprint3.Button
icon="thumbs-down"
onClick={[Function]}
/>
<Blueprint3.Button
icon="cross"
onClick={[Function]}
>
Cancel Bisect
</Blueprint3.Button>
</Fragment>
`;
@@ -1,46 +1,50 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VersionChooser component handles corrupt data 1`] = `
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
<Blueprint3.ButtonGroup>
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
disabled={false}
icon="saved"
text="Electron v2.0.2"
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
icon="saved"
text="Electron v2.0.2"
/>
</Blueprint3.Select>
</Blueprint3.Select>
</Blueprint3.ButtonGroup>
`;
exports[`VersionChooser component renderItem() renders an item 1`] = `
@@ -62,44 +66,48 @@ exports[`VersionChooser component renderItem() renders an item 1`] = `
`;
exports[`VersionChooser component renders 1`] = `
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
<Blueprint3.ButtonGroup>
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
disabled={false}
icon="saved"
text="Electron v2.0.2"
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
icon="saved"
text="Electron v2.0.2"
/>
</Blueprint3.Select>
</Blueprint3.Select>
</Blueprint3.ButtonGroup>
`;
@@ -16,10 +16,9 @@ exports[`AddVersionDialog component renders 1`] = `
id="custom-electron-version"
inputProps={
Object {
"webkitdirectory": "true",
"onClick": [Function],
}
}
onInputChange={[Function]}
text="/test/file"
/>
<br />
@@ -79,10 +78,9 @@ exports[`AddVersionDialog component renders 2`] = `
id="custom-electron-version"
inputProps={
Object {
"webkitdirectory": "true",
"onClick": [Function],
}
}
onInputChange={[Function]}
text="/test/file"
/>
<br />
@@ -0,0 +1,413 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BisectDialog component renders 1`] = `
<Blueprint3.Dialog
canOutsideClickClose={true}
className="dialog-add-version"
isOpen={false}
onClose={[Function]}
title="Start a bisect session"
>
<div
className="bp3-dialog-body"
>
<Component>
Earliest Version
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
fill={true}
icon="saved"
text="v4.0.0"
/>
</Blueprint3.Select>
</Component>
<Component>
Latest Version
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
disabled={false}
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
</div>
<div
className="bp3-dialog-footer"
>
<div
className="bp3-dialog-footer-actions"
>
<Blueprint3.Button
disabled={true}
icon="play"
key="submit"
onClick={[Function]}
text="Begin"
/>
<Blueprint3.Button
icon="cross"
key="cancel"
onClick={[Function]}
text="Cancel"
/>
</div>
</div>
</Blueprint3.Dialog>
`;
exports[`BisectDialog component renders 2`] = `
<Blueprint3.Dialog
canOutsideClickClose={true}
className="dialog-add-version"
isOpen={false}
onClose={[Function]}
title="Start a bisect session"
>
<div
className="bp3-dialog-body"
>
<Component>
Earliest Version
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
<Component>
Latest Version
<Blueprint3.Select
disabled={true}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
disabled={true}
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
</div>
<div
className="bp3-dialog-footer"
>
<div
className="bp3-dialog-footer-actions"
>
<Blueprint3.Button
disabled={true}
icon="play"
key="submit"
onClick={[Function]}
text="Begin"
/>
<Blueprint3.Button
icon="cross"
key="cancel"
onClick={[Function]}
text="Cancel"
/>
</div>
</div>
</Blueprint3.Dialog>
`;
exports[`BisectDialog component renders 3`] = `
<Blueprint3.Dialog
canOutsideClickClose={true}
className="dialog-add-version"
isOpen={false}
onClose={[Function]}
title="Start a bisect session"
>
<div
className="bp3-dialog-body"
>
<Component>
Earliest Version
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
fill={true}
icon="saved"
text="v4.0.0"
/>
</Blueprint3.Select>
</Component>
<Component>
Latest Version
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
disabled={false}
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
</div>
<div
className="bp3-dialog-footer"
>
<div
className="bp3-dialog-footer-actions"
>
<Blueprint3.Button
disabled={true}
icon="play"
key="submit"
onClick={[Function]}
text="Begin"
/>
<Blueprint3.Button
icon="cross"
key="cancel"
onClick={[Function]}
text="Cancel"
/>
</div>
</div>
</Blueprint3.Dialog>
`;
@@ -0,0 +1,86 @@
import { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react';
import { BisectHandler } from '../../../src/renderer/components/commands-bisect';
describe('Bisect commands component', () => {
let store: any;
beforeEach(() => {
store = {
Bisector: {
continue: jest.fn(),
getCurrentVersion: jest.fn()
},
setVersion: jest.fn(),
version: '1.0.0',
pushOutput: jest.fn()
};
});
it('renders helper buttons if bisect instance is active', () => {
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} />);
expect(wrapper).toMatchSnapshot();
});
describe('buttons', () => {
let wrapper: ShallowWrapper;
let instance: BisectHandler;
beforeEach(() => {
wrapper = shallow(<BisectHandler appState={store} />);
instance = wrapper.instance() as any;
instance.continueBisect = jest.fn();
});
it('passes in Version=Good when thumbs up button is pressed', () => {
wrapper.find('[icon="thumbs-up"]').simulate('click');
expect(instance.continueBisect).toHaveBeenCalledWith(true);
});
it('passes in Version=Bad when thumbs down button is pressed', () => {
wrapper.find('[icon="thumbs-down"]').simulate('click');
expect(instance.continueBisect).toHaveBeenCalledWith(false);
});
});
describe('continueBisect()', () => {
it('sets version assigned by bisect algorithm', () => {
const wrapper = shallow(<BisectHandler appState={store} />);
const instance: BisectHandler = wrapper.instance() as any;
store.Bisector.continue.mockReturnValue({
version: '2.0.0'
});
instance.continueBisect(true);
expect(store.setVersion).toHaveBeenCalledWith('2.0.0');
});
it('terminates bisect if algorithm returns array', () => {
const wrapper = shallow(<BisectHandler appState={store} />);
const instance: BisectHandler = wrapper.instance() as any;
instance.terminateBisect = jest.fn();
// same value is only returned when there is only 1 version left
store.Bisector.continue.mockReturnValue(['minRev', 'maxRev']);
instance.continueBisect(true);
expect(store.setVersion).not.toHaveBeenCalled();
expect(instance.terminateBisect).toHaveBeenCalled();
expect(store.pushOutput).toHaveBeenCalled();
});
});
describe('terminateBisect()', () => {
it('removes the bisect instance from the app state', () => {
const wrapper = shallow(<BisectHandler appState={store} />);
const instance: BisectHandler = wrapper.instance() as any;
instance.terminateBisect();
expect(store.Bisector).toBeUndefined();
});
});
});
@@ -1,15 +1,16 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { IpcEvents } from '../../../src/ipc-events';
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');
describe('AddVersionDialog component', () => {
let store: any;
const mockFile = {
path: '/test/file'
};
const mockFile = '/test/file';
beforeAll(() => {
// We render the buttons different depending on the
@@ -37,7 +38,7 @@ describe('AddVersionDialog component', () => {
wrapper.setState({
isValidVersion: true,
isValidElectron: true,
file: mockFile
folderPath: mockFile
});
expect(wrapper).toMatchSnapshot();
@@ -45,35 +46,32 @@ describe('AddVersionDialog component', () => {
wrapper.setState({
isValidVersion: false,
isValidElectron: true,
file: mockFile
folderPath: mockFile
});
expect(wrapper).toMatchSnapshot();
});
describe('onChangeFile()', () => {
it('handles the change event', async () => {
it('overrides default input with Electron dialog', () => {
const preventDefault = jest.fn();
const wrapper = shallow(<AddVersionDialog appState={store} />);
const inp = wrapper.find('#custom-electron-version');
inp.dive().find('input[type="file"]').simulate('click', { preventDefault });
expect(ipcRendererManager.send as jest.Mock)
.toHaveBeenCalledWith(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG);
expect(preventDefault).toHaveBeenCalled();
});
describe('setFolderPath()', () => {
it('does something', async () => {
store.binaryManager.getIsDownloaded.mockResolvedValue(true);
const wrapper = shallow(<AddVersionDialog appState={store} />);
await (wrapper.instance() as any).setFolderPath('/test/');
await (wrapper.instance() as any).onChangeFile({ target: { files: [ mockFile ] } });
expect(wrapper.state('file')).toBe(mockFile);
});
it('handles invalid input', async () => {
const wrapper = shallow(<AddVersionDialog appState={store} />);
await (wrapper.instance() as any).onChangeFile({ target: { files: [] } });
expect(wrapper.state('file')).toBe(undefined);
});
it('handles the change event and checks for Electron', async () => {
const wrapper = shallow(<AddVersionDialog appState={store} />);
store.binaryManager.getIsDownloaded.mockReturnValueOnce(false);
await (wrapper.instance() as any).onChangeFile({ target: { files: [ mockFile ] } });
expect(wrapper.state('file')).toBe(mockFile);
expect(wrapper.state('isValidElectron')).toBe(false);
expect(wrapper.state('isValidElectron')).toBe(true);
expect(wrapper.state('folderPath')).toBe('/test/');
});
});
@@ -93,7 +91,7 @@ describe('AddVersionDialog component', () => {
expect(wrapper.state('isValidVersion')).toBe(false);
expect(wrapper.state('version')).toBe('foo');
(wrapper.instance() as any).onChangeVersion({ target: { } });
(wrapper.instance() as any).onChangeVersion({ target: {} });
expect(wrapper.state('isValidVersion')).toBe(false);
expect(wrapper.state('version')).toBe('');
});
@@ -113,9 +111,7 @@ describe('AddVersionDialog component', () => {
wrapper.setState({
version: '3.3.3',
file: {
path: '/test/path'
}
folderPath: '/test/path'
});
await (wrapper.instance() as any).onSubmit();
@@ -0,0 +1,124 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { ElectronVersionSource, ElectronVersionState } from '../../../src/interfaces';
import { Bisector } from '../../../src/renderer/bisect';
import { BisectDialog } from '../../../src/renderer/components/dialog-bisect';
import { ElectronReleaseChannel } from '../../../src/renderer/versions';
jest.mock('../../../src/renderer/bisect');
describe('BisectDialog component', () => {
let store: any;
const generateVersionRange = (rangeLength: number) =>
(new Array(rangeLength)).fill(0).map((_, i) => ({
state: ElectronVersionState.ready,
version: `${i + 1}.0.0`,
source: ElectronVersionSource.local
}));
beforeEach(() => {
store = {
versions: generateVersionRange(5),
versionsToShow: [ElectronReleaseChannel.stable],
statesToShow: [ElectronVersionState.ready],
setVersion: jest.fn()
};
});
it('renders', () => {
const wrapper = shallow(<BisectDialog appState={store} />);
// start and end selected
wrapper.setState({
startIndex: 3,
endIndex: 0,
allVersions: generateVersionRange(5)
});
expect(wrapper).toMatchSnapshot();
// no selection
wrapper.setState({
startIndex: undefined,
endIndex: undefined,
allVersions: generateVersionRange(5)
});
expect(wrapper).toMatchSnapshot();
// only start selected
wrapper.setState({
startIndex: 3,
endIndex: undefined,
allVersions: generateVersionRange(5)
});
expect(wrapper).toMatchSnapshot();
});
describe('onBeginSelect()', () => {
it('sets the begin version', () => {
const wrapper = shallow(<BisectDialog appState={store} />);
const instance: BisectDialog = wrapper.instance() as any;
expect(instance.state.startIndex).toBeUndefined();
instance.onBeginSelect(store.versions[2]);
expect(instance.state.startIndex).toBe(2);
});
});
describe('onEndSelect()', () => {
it('sets the end version', () => {
const wrapper = shallow(<BisectDialog appState={store} />);
const instance: BisectDialog = wrapper.instance() as any;
expect(instance.state.endIndex).toBeUndefined();
instance.onEndSelect(store.versions[2]);
expect(instance.state.endIndex).toBe(2);
});
});
describe('onSubmit()', () => {
it('initiates a bisect instance and sets a version', async () => {
const version = '1.0.0';
(Bisector as jest.Mock).mockImplementation(() => {
return {
getCurrentVersion: () => ({ version })
};
});
const versions = generateVersionRange(5);
const wrapper = shallow(<BisectDialog appState={store} />);
wrapper.setState({
startIndex: 4,
endIndex: 0,
allVersions: versions
});
const instance: BisectDialog = wrapper.instance() as any;
await instance.onSubmit();
expect(Bisector).toHaveBeenCalledWith(versions.slice(0, 5).reverse());
expect(store.Bisector).toBeDefined();
expect(store.setVersion).toHaveBeenCalledWith(version);
});
it('does nothing if endIndex or startIndex are falsy', async () => {
const wrapper = shallow(<BisectDialog appState={store} />);
wrapper.setState({
startIndex: undefined,
endIndex: 0
});
const instance1: BisectDialog = wrapper.instance() as any;
await instance1.onSubmit();
expect(Bisector).not.toHaveBeenCalled();
wrapper.setState({
startIndex: 4,
endIndex: undefined
});
const instance2: BisectDialog = wrapper.instance() as any;
await instance2.onSubmit();
expect(Bisector).not.toHaveBeenCalled();
});
});
});
+32 -4
View File
@@ -1,4 +1,5 @@
import { ALL_MOSAICS, EditorId, ElectronVersionSource, ElectronVersionState, PanelId } from '../../src/interfaces';
import { Bisector } from '../../src/renderer/bisect';
import { DEFAULT_MOSAIC_ARRANGEMENT } from '../../src/renderer/constants';
import { getContent, isContentUnchanged } from '../../src/renderer/content';
import { ipcRendererManager } from '../../src/renderer/ipc';
@@ -142,7 +143,7 @@ describe('AppState', () => {
expect(appState.currentElectronVersion).toEqual(mockVersions['2.0.2']);
});
});
});
describe('toggleConsole()', () => {
it('toggles the console', () => {
@@ -179,6 +180,33 @@ describe('AppState', () => {
});
});
describe('toggleBisectCommands()', () => {
it('toggles visibility of the bisect commands', () => {
const isVisible = appState.isBisectCommandShowing;
appState.toggleBisectCommands();
expect(isVisible).not.toBe(appState.isBisectCommandShowing);
});
it('takes no action if bisect dialog is active', () => {
const isVisible = appState.isBisectCommandShowing;
expect(appState.isBisectDialogShowing).toBe(false);
appState.toggleBisectDialog();
appState.toggleBisectCommands();
expect(isVisible).toBe(appState.isBisectCommandShowing);
});
it('takes no action if bisect instance is active', () => {
const isVisible = appState.isBisectCommandShowing;
appState.Bisector = new Bisector([]);
appState.toggleBisectCommands();
expect(isVisible).toBe(appState.isBisectCommandShowing);
});
});
describe('toggleAddVersionDialog()', () => {
it('toggles the add version dialog', () => {
appState.toggleAddVersionDialog();
@@ -296,7 +324,7 @@ describe('AppState', () => {
await appState.setVersion('v1.0.0');
expect(getContent).toHaveBeenCalledTimes(1);
expect(window.ElectronFiddle.app.setEditorValues).toHaveBeenCalledTimes(1);
expect(window.ElectronFiddle.app.setEditorValues).toHaveBeenCalledTimes(1);
});
});
@@ -341,7 +369,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']
);
});
});
@@ -426,7 +454,7 @@ describe('AppState', () => {
it('updates the visible editors and creates a backup', () => {
appState.mosaicArrangement = createMosaicArrangement(ALL_MOSAICS);
appState.closedPanels = {};
appState.setVisibleMosaics([ EditorId.main ]);
appState.setVisibleMosaics([EditorId.main]);
expect(appState.mosaicArrangement).toEqual(EditorId.main);
expect(appState.closedPanels[EditorId.renderer]).toBeTruthy();
+4
View File
@@ -13,3 +13,7 @@ export function resetPlatform() {
writable: true
});
}
export function flushPromises() {
return new Promise((resolve) => setImmediate(resolve));
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { isDevMode } from '../../src/utils/devmode';
describe('devMode', () => {
const old = process.defaultApp;
const old = (process as any).defaultApp; // for tsconfig error
afterEach(() => {
Object.defineProperty(process, 'defaultApp', { value: old });