Compare commits
153
Commits
v0.9.0
...
fix-dropdowns
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26e054702a | ||
|
|
16f27132cb | ||
|
|
7613176f69 | ||
|
|
470bea4fca | ||
|
|
363febdb73 | ||
|
|
062ca4e205 | ||
|
|
49053342a5 | ||
|
|
fd582c67a8 | ||
|
|
706b18808b | ||
|
|
a6ae8d5376 | ||
|
|
5034f8c853 | ||
|
|
e916247013 | ||
|
|
c052686de4 | ||
|
|
31aacef131 | ||
|
|
932369b705 | ||
|
|
b835c34786 | ||
|
|
c9e1ba815a | ||
|
|
374a85e2f6 | ||
|
|
4282d0b93d | ||
|
|
549862b3bb | ||
|
|
4691c1b494 | ||
|
|
8125abc657 | ||
|
|
8cba7739f5 | ||
|
|
0b39fe40b1 | ||
|
|
cae078ffe6 | ||
|
|
bfb931435f | ||
|
|
c878f72f2e | ||
|
|
32494c057a | ||
|
|
e732685fff | ||
|
|
d9d5e0cbc9 | ||
|
|
0e1d7b541e | ||
|
|
8a602d94b7 | ||
|
|
155889dbcc | ||
|
|
56ac6662b6 | ||
|
|
adb196549b | ||
|
|
999facf062 | ||
|
|
fa0115f2db | ||
|
|
c0a4c0e48d | ||
|
|
3a5d2205fe | ||
|
|
1083b8f859 | ||
|
|
252d4c1de6 | ||
|
|
0c44d2c9e4 | ||
|
|
5eebc06514 | ||
|
|
eac1423177 | ||
|
|
ae88780281 | ||
|
|
4bf9d48d70 | ||
|
|
df6d4b5a9a | ||
|
|
68202e2b23 | ||
|
|
acd5b35ca1 | ||
|
|
fae1ca3138 | ||
|
|
bd220d2690 | ||
|
|
d31f1016b8 | ||
|
|
3b52be0f79 | ||
|
|
0e11c56f7f | ||
|
|
8019981d1a | ||
|
|
403a58e216 | ||
|
|
3341d0ccaa | ||
|
|
d42e83100a | ||
|
|
50e9883d82 | ||
|
|
f5aedac6a3 | ||
|
|
a3f4aa8ed3 | ||
|
|
9c839aaad8 | ||
|
|
3a7ca8ccd5 | ||
|
|
fef42293bc | ||
|
|
1f9db42c80 | ||
|
|
8e5657ef2d | ||
|
|
a94105dd27 | ||
|
|
cf214dc0d0 | ||
|
|
6dfdf00437 | ||
|
|
b3743a4c73 | ||
|
|
3c0d6c2293 | ||
|
|
89b589fd2e | ||
|
|
42c577988d | ||
|
|
d0e47b6b4d | ||
|
|
f44a127012 | ||
|
|
fa2f5454ce | ||
|
|
1021bb8b2b | ||
|
|
cf4ad36ca2 | ||
|
|
4fe9c627b5 | ||
|
|
262e636e8d | ||
|
|
bbe79ed6e2 | ||
|
|
bc96a5013a | ||
|
|
66ba7b74d5 | ||
|
|
38e5aa98e3 | ||
|
|
a91504b173 | ||
|
|
4685e88c86 | ||
|
|
e0bb82d919 | ||
|
|
7035be3286 | ||
|
|
c961cfadb8 | ||
|
|
7267a2ee88 | ||
|
|
bc03382dc5 | ||
|
|
891c33ba68 | ||
|
|
e7260c74a4 | ||
|
|
ed694a7044 | ||
|
|
14fc77c08c | ||
|
|
f3079648cf | ||
|
|
7272ec7899 | ||
|
|
ac9ac701f7 | ||
|
|
7fce572823 | ||
|
|
6186a80dad | ||
|
|
ade6576285 | ||
|
|
61c58277f3 | ||
|
|
c361d34a6d | ||
|
|
5887a6615a | ||
|
|
7ba4cf5be7 | ||
|
|
0daf872d2b | ||
|
|
75ec8ee9b1 | ||
|
|
183c152e40 | ||
|
|
d6a807ba41 | ||
|
|
24e0abf667 | ||
|
|
eb8261e240 | ||
|
|
84cc07da90 | ||
|
|
b0f3837b47 | ||
|
|
e29664ca45 | ||
|
|
ef36ee39f6 | ||
|
|
73dadb815e | ||
|
|
4945501703 | ||
|
|
77c87fbdeb | ||
|
|
2ef5b42f4a | ||
|
|
9c4bc2ba52 | ||
|
|
80da4e73e3 | ||
|
|
eadd58d149 | ||
|
|
1fc82f8f6d | ||
|
|
9bd7043b2c | ||
|
|
aaeef6fd06 | ||
|
|
1195902bd5 | ||
|
|
c022d0f33d | ||
|
|
996ae775f3 | ||
|
|
ed44dda8fb | ||
|
|
effba13e73 | ||
|
|
65886c3b52 | ||
|
|
8bd4dd366a | ||
|
|
4e19578e34 | ||
|
|
c7fd0bc95e | ||
|
|
29e2f03b1a | ||
|
|
4cd6f330da | ||
|
|
79521eac7c | ||
|
|
2736499d68 | ||
|
|
1cbb781e8d | ||
|
|
e849a49f6f | ||
|
|
0114d2c1eb | ||
|
|
365537012f | ||
|
|
3e435cb64d | ||
|
|
6981382793 | ||
|
|
f293ba84d1 | ||
|
|
55cf507a96 | ||
|
|
e71c2439cd | ||
|
|
9bd253951c | ||
|
|
eda08c9aea | ||
|
|
9414f66893 | ||
|
|
f4725b5dd8 | ||
|
|
fd8be47494 | ||
|
|
2cebe2ca7c |
+1
-1
@@ -4,7 +4,7 @@ os:
|
||||
- linux
|
||||
- osx
|
||||
dist: trusty
|
||||
osx_image: xcode8.3
|
||||
osx_image: xcode10
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "10"
|
||||
- nodejs_version: "12"
|
||||
|
||||
init:
|
||||
- git config --global core.symlinks true
|
||||
|
||||
+36
-3
@@ -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: [
|
||||
@@ -90,8 +95,36 @@ module.exports = {
|
||||
name: 'fiddle'
|
||||
},
|
||||
draft: true,
|
||||
prerelease: true
|
||||
prerelease: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Generated
+5036
-4325
File diff suppressed because it is too large
Load Diff
+44
-44
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "electron-fiddle",
|
||||
"productName": "Electron Fiddle",
|
||||
"version": "0.9.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",
|
||||
@@ -36,9 +36,9 @@
|
||||
"forge": "./forge.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^3.17.1",
|
||||
"@blueprintjs/select": "^3.9.0",
|
||||
"@octokit/rest": "^16.28.2",
|
||||
"@blueprintjs/core": "^3.19.1",
|
||||
"@blueprintjs/select": "^3.11.1",
|
||||
"@octokit/rest": "^16.34.0",
|
||||
"builtin-modules": "^3.1.0",
|
||||
"classnames": "^2.2.6",
|
||||
"electron-default-menu": "^1.0.1",
|
||||
@@ -48,61 +48,61 @@
|
||||
"extract-zip": "^1.6.7",
|
||||
"fix-path": "^2.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"mobx": "^5.11.0",
|
||||
"mobx-react": "^6.1.1",
|
||||
"monaco-editor": "^0.17.1",
|
||||
"mobx": "^5.14.2",
|
||||
"mobx-react": "^6.1.4",
|
||||
"monaco-editor": "^0.18.1",
|
||||
"monaco-loader": "1.0.0",
|
||||
"namor": "^1.1.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"namor": "^1.1.3",
|
||||
"react": "^16.11.0",
|
||||
"react-dom": "^16.11.0",
|
||||
"react-mosaic-component": "^3.2.0",
|
||||
"semver": "^6.2.0",
|
||||
"semver": "^6.3.0",
|
||||
"tmp": "0.1.0",
|
||||
"tslib": "^1.10.0",
|
||||
"update-electron-app": "^1.4.2"
|
||||
"update-electron-app": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.5",
|
||||
"@electron-forge/cli": "^6.0.0-beta.39",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.39",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.39",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.39",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.39",
|
||||
"@electron-forge/publisher-github": "^6.0.0-beta.39",
|
||||
"@babel/core": "^7.6.4",
|
||||
"@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.0",
|
||||
"@types/fs-extra": "^8.0.0",
|
||||
"@types/jest": "^24.0.15",
|
||||
"@types/enzyme": "^3.10.3",
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"@types/jest": "^24.0.19",
|
||||
"@types/log-symbols": "^3.0.0",
|
||||
"@types/node": "^10.11.1",
|
||||
"@types/react": "^16.8.17",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/semver": "^6.0.0",
|
||||
"@types/node": "^12.11.7",
|
||||
"@types/react": "^16.9.11",
|
||||
"@types/react-dom": "^16.9.3",
|
||||
"@types/semver": "^6.0.2",
|
||||
"@types/tmp": "0.1.0",
|
||||
"chokidar": "^2.1.6",
|
||||
"coveralls": "^3.0.4",
|
||||
"electron": "5.0.6",
|
||||
"chokidar": "^3.2.2",
|
||||
"coveralls": "^3.0.7",
|
||||
"electron": "7.1.3",
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-react-16": "^1.14.0",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"jest": "^24.8.0",
|
||||
"enzyme-adapter-react-16": "^1.15.1",
|
||||
"enzyme-to-json": "^3.4.3",
|
||||
"jest": "^24.9.0",
|
||||
"jest-fetch-mock": "^2.1.2",
|
||||
"less": "^3.9.0",
|
||||
"less": "^3.10.3",
|
||||
"log-symbols": "^3.0.0",
|
||||
"node-abi": "^2.9.0",
|
||||
"node-abi": "^2.12.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"parcel-bundler": "^1.12.3",
|
||||
"react-test-renderer": "^16.8.6",
|
||||
"rimraf": "^2.6.3",
|
||||
"standard": "^12.0.1",
|
||||
"stylelint": "^10.1.0",
|
||||
"stylelint-config-standard": "^18.3.0",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.18.0",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"react-test-renderer": "^16.11.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"standard": "^14.3.1",
|
||||
"stylelint": "^11.1.1",
|
||||
"stylelint-config-standard": "^19.0.0",
|
||||
"ts-jest": "^24.1.0",
|
||||
"tslint": "^5.20.0",
|
||||
"tslint-microsoft-contrib": "^6.2.0",
|
||||
"tslint-react": "^4.0.0",
|
||||
"typescript": "^3.5.2"
|
||||
"tslint-react": "^4.1.0",
|
||||
"typescript": "^3.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -24,6 +24,7 @@ export interface EditorValues {
|
||||
main: string;
|
||||
renderer: string;
|
||||
html: string;
|
||||
preload: string;
|
||||
package?: string;
|
||||
}
|
||||
|
||||
@@ -33,9 +34,9 @@ export interface ElectronVersion extends NpmVersion {
|
||||
}
|
||||
|
||||
export interface SetFiddleOptions {
|
||||
values: EditorValues;
|
||||
filePath?: string;
|
||||
templateName?: string;
|
||||
gistId?: string;
|
||||
}
|
||||
|
||||
export interface OutputEntry {
|
||||
@@ -63,7 +64,8 @@ export interface Templates {
|
||||
export const enum EditorId {
|
||||
'main' = 'main',
|
||||
'renderer' = 'renderer',
|
||||
'html' = 'html'
|
||||
'html' = 'html',
|
||||
'preload' = 'preload'
|
||||
}
|
||||
|
||||
// Panels that can show up as a mosaic
|
||||
@@ -73,7 +75,7 @@ export const enum PanelId {
|
||||
|
||||
export type MosaicId = EditorId | PanelId;
|
||||
|
||||
export const ALL_EDITORS = [ EditorId.main, EditorId.renderer, EditorId.html ];
|
||||
export const ALL_EDITORS = [ EditorId.main, EditorId.renderer, EditorId.preload, EditorId.html ];
|
||||
export const ALL_PANELS = [ PanelId.docsDemo ];
|
||||
export const ALL_MOSAICS = [ ...ALL_EDITORS, ...ALL_PANELS ];
|
||||
|
||||
|
||||
+11
-3
@@ -17,14 +17,20 @@ export enum IpcEvents {
|
||||
FS_SAVE_FIDDLE_DIALOG = 'FS_SAVE_FIDDLE_DIALOG',
|
||||
FS_SAVE_FIDDLE_ERROR = 'FS_SAVE_FIDDLE_ERROR',
|
||||
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_WARNING_DIALOG,
|
||||
IpcEvents.SHOW_CONFIRMATION_DIALOG,
|
||||
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG
|
||||
];
|
||||
|
||||
export const ipcRendererEvents = [
|
||||
@@ -44,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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -7,20 +7,15 @@
|
||||
color: var(--text-color-1);
|
||||
background: var(--background-2);
|
||||
box-shadow: inset 0 0 100px 100px rgba(0, 0, 0, 0.15);
|
||||
border-top: @border;
|
||||
top: 10vh;
|
||||
padding: 0;
|
||||
padding-top: 10px;
|
||||
padding-left: 10px;
|
||||
height: 0;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
transition: height 0.2s linear, padding 0.2s linear;
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
&.showing {
|
||||
border-top: @border;
|
||||
padding-top: 10px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
white-space: pre;
|
||||
|
||||
@@ -188,7 +188,6 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
+35
-1
@@ -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';
|
||||
@@ -12,6 +12,15 @@ export function setupDialogs() {
|
||||
ipcMainManager.on(IpcEvents.SHOW_WARNING_DIALOG, (_event, args) => {
|
||||
showWarningDialog(args);
|
||||
});
|
||||
|
||||
ipcMainManager.on(IpcEvents.SHOW_CONFIRMATION_DIALOG, (_event, args) => {
|
||||
showConfirmationDialog(args);
|
||||
});
|
||||
|
||||
ipcMainManager.on(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, async (event) => {
|
||||
await showOpenDialog(event);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,3 +34,28 @@ function showWarningDialog(args: Electron.MessageBoxOptions) {
|
||||
...args
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a confirmation dialog
|
||||
*
|
||||
* @param {Electron.MessageBoxOptions} args
|
||||
*/
|
||||
function showConfirmationDialog(args: Electron.MessageBoxOptions) {
|
||||
dialog.showMessageBox(getOrCreateMainWindow(), {
|
||||
type: 'warning',
|
||||
...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]]);
|
||||
}
|
||||
|
||||
+21
-21
@@ -19,41 +19,41 @@ export function setupFileListeners() {
|
||||
* Shows the "Open Fiddle" dialog and forwards
|
||||
* the path to the renderer
|
||||
*/
|
||||
export function showOpenDialog() {
|
||||
dialog.showOpenDialog({
|
||||
export async function showOpenDialog() {
|
||||
const { filePaths } = await dialog.showOpenDialog({
|
||||
title: 'Open Fiddle',
|
||||
properties: ['openDirectory']
|
||||
}, (filePaths) => {
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ipcMainManager.send(IpcEvents.FS_OPEN_FIDDLE, [ filePaths[0] ]);
|
||||
});
|
||||
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ipcMainManager.send(IpcEvents.FS_OPEN_FIDDLE, [ filePaths[0] ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the "Save Fiddle" dialog and forwards
|
||||
* the path to the renderer
|
||||
*/
|
||||
export function showSaveDialog(event?: IpcEvents, as?: string) {
|
||||
export async function showSaveDialog(event?: IpcEvents, as?: string) {
|
||||
// We want to save to a folder, so we'll use an open dialog here
|
||||
dialog.showOpenDialog({
|
||||
const { filePaths } = await dialog.showOpenDialog({
|
||||
buttonLabel: 'Save here',
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
title: `Save Fiddle${as ? ` as ${as}` : ''}`
|
||||
}, async (filePaths) => {
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Asked to save to ${filePaths[0]}`);
|
||||
|
||||
// Let's confirm real quick if we want this
|
||||
if (await ensureSaveTargetEmpty(filePaths[0])) {
|
||||
ipcMainManager.send(event || IpcEvents.FS_SAVE_FIDDLE, [ filePaths[0] ]);
|
||||
}
|
||||
});
|
||||
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Asked to save to ${filePaths[0]}`);
|
||||
|
||||
// Let's confirm real quick if we want this
|
||||
if (await ensureSaveTargetEmpty(filePaths[0])) {
|
||||
ipcMainManager.send(event || IpcEvents.FS_SAVE_FIDDLE, [ filePaths[0] ]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,9 +7,9 @@ import { isDevMode } from '../utils/devmode';
|
||||
* Is this the first run of Fiddle? If so, perform
|
||||
* tasks that we only want to do in this case.
|
||||
*/
|
||||
export function onFirstRunMaybe() {
|
||||
export async function onFirstRunMaybe() {
|
||||
if (isFirstRun()) {
|
||||
promptMoveToApplicationsFolder();
|
||||
await promptMoveToApplicationsFolder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,18 @@ export function onFirstRunMaybe() {
|
||||
* Ask the user if the app should be moved to the
|
||||
* applications folder.
|
||||
*/
|
||||
function promptMoveToApplicationsFolder(): void {
|
||||
async function promptMoveToApplicationsFolder(): Promise<void> {
|
||||
if (process.platform !== 'darwin') return;
|
||||
if (isDevMode() || app.isInApplicationsFolder()) return;
|
||||
|
||||
dialog.showMessageBox({
|
||||
const { response } = await dialog.showMessageBox({
|
||||
type: 'question',
|
||||
buttons: ['Move to Applications Folder', 'Do Not Move'],
|
||||
defaultId: 0,
|
||||
message: 'Move to Applications Folder?',
|
||||
}, (response) => {
|
||||
if (response === 0) {
|
||||
app.moveToApplicationsFolder();
|
||||
}
|
||||
});
|
||||
|
||||
if (response === 0) {
|
||||
app.moveToApplicationsFolder();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,5 +1,6 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import { IpcEvents, ipcMainEvents, WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
|
||||
import { getOrCreateMainWindow } from './windows';
|
||||
|
||||
@@ -22,8 +23,9 @@ export class IpcMainManager extends EventEmitter {
|
||||
ipcMain.on(name, (...args: Array<any>) => this.emit(name, ...args));
|
||||
});
|
||||
|
||||
ipcMain.on(WEBCONTENTS_READY_FOR_IPC_SIGNAL, (event: Electron.Event) => {
|
||||
ipcMain.on(WEBCONTENTS_READY_FOR_IPC_SIGNAL, (event: Electron.IpcMainEvent) => {
|
||||
this.readyWebContents.add(event.sender);
|
||||
|
||||
const queue = this.messageQueue.get(event.sender);
|
||||
this.messageQueue.delete(event.sender);
|
||||
if (!queue) return;
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ import { getOrCreateMainWindow } from './windows';
|
||||
* the method that takes care of booting the application.
|
||||
*/
|
||||
export async function onReady() {
|
||||
onFirstRunMaybe();
|
||||
await onFirstRunMaybe();
|
||||
if (!isDevMode()) process.env.NODE_ENV = 'production';
|
||||
|
||||
getOrCreateMainWindow();
|
||||
@@ -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();
|
||||
|
||||
+13
-8
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,14 +248,19 @@ export function setupMenu() {
|
||||
// Tweak "View" menu
|
||||
if (label === 'View' && isSubmenu(item.submenu)) {
|
||||
item.submenu = item.submenu.filter((subItem) => subItem.label !== 'Toggle Developer Tools'); // Remove "Toggle Developer Tools"
|
||||
item.submenu.push({ type: 'separator' }, { role: 'resetzoom' }, { role: 'zoomin' }, { role: 'zoomout' }); // Add zooming actions
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+11
-1
@@ -4,6 +4,7 @@ import * as path from 'path';
|
||||
import * as nodeUrl from 'url';
|
||||
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { isDevMode } from '../utils/devmode';
|
||||
import { ipcMainManager } from './ipc';
|
||||
|
||||
const PROTOCOL = 'electron-fiddle';
|
||||
@@ -65,6 +66,12 @@ export const scanArgv = (argv: Array<string>) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const scanNpmArgv = (argv: string) => {
|
||||
const parsedArgv = JSON.parse(argv);
|
||||
const { original: args } = parsedArgv;
|
||||
scanArgv(args);
|
||||
};
|
||||
|
||||
export const listenForProtocolHandler = () => {
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) app.quit();
|
||||
@@ -75,7 +82,10 @@ export const listenForProtocolHandler = () => {
|
||||
}
|
||||
});
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// pass protocol URL via npm start args in dev mode
|
||||
if (isDevMode() && process.env.npm_config_argv) {
|
||||
scanNpmArgv(process.env.npm_config_argv);
|
||||
} else if (process.platform === 'win32') {
|
||||
scanArgv(process.argv);
|
||||
}
|
||||
};
|
||||
|
||||
+75
-38
@@ -2,13 +2,20 @@ import { when } from 'mobx';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { ALL_EDITORS, EditorId, EditorValues } from '../interfaces';
|
||||
import {
|
||||
ALL_EDITORS,
|
||||
EditorId,
|
||||
EditorValues,
|
||||
SetFiddleOptions
|
||||
} from '../interfaces';
|
||||
import { WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
|
||||
import { updateEditorLayout } from '../utils/editor-layout';
|
||||
import { getEditorValue } from '../utils/editor-value';
|
||||
import { getPackageJson, PackageJsonOptions } from '../utils/get-package';
|
||||
import { getTitle } from '../utils/get-title';
|
||||
import { isEditorBackup } from '../utils/type-checks';
|
||||
import { FileManager } from './file-manager';
|
||||
import { RemoteLoader } from './remote-loader';
|
||||
import { Runner } from './runner';
|
||||
import { appState } from './state';
|
||||
import { getTheme } from './themes';
|
||||
@@ -25,32 +32,29 @@ export class App {
|
||||
public monaco: typeof MonacoType | null = null;
|
||||
public state = appState;
|
||||
public fileManager = new FileManager(appState);
|
||||
public remoteLoader = new RemoteLoader(appState);
|
||||
public runner = new Runner(appState);
|
||||
public touchBarManager: TouchBarManager | undefined;
|
||||
|
||||
constructor() {
|
||||
this.getValues = this.getValues.bind(this);
|
||||
this.setValues = this.setValues.bind(this);
|
||||
this.getEditorValues = this.getEditorValues.bind(this);
|
||||
this.setEditorValues = this.setEditorValues.bind(this);
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
this.touchBarManager = new TouchBarManager(appState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the values on all three editors.
|
||||
*
|
||||
* @param {EditorValues} values
|
||||
* @param {warn} warn - Should we warn before overwriting unsaved data?
|
||||
*/
|
||||
public async setValues(values: Partial<EditorValues>, warn: boolean = true): Promise<boolean> {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
if (!fiddle) {
|
||||
throw new Error('Fiddle not ready');
|
||||
}
|
||||
|
||||
if (this.state.isUnsaved && warn) {
|
||||
public async replaceFiddle(
|
||||
editorValues: Partial<EditorValues>,
|
||||
{ filePath, gistId, templateName }: Partial<SetFiddleOptions>
|
||||
) {
|
||||
// if unsaved, prompt user to make sure they're okay with overwriting and changing directory
|
||||
if (this.state.isUnsaved) {
|
||||
this.state.setWarningDialogTexts({
|
||||
label: `Opening this Fiddle will replace your unsaved changes. Do you want to proceed?`,
|
||||
ok: 'Yes'
|
||||
});
|
||||
this.state.isWarningDialogShowing = true;
|
||||
await when(() => !this.state.isWarningDialogShowing);
|
||||
|
||||
@@ -59,31 +63,58 @@ export class App {
|
||||
}
|
||||
}
|
||||
|
||||
for (const name of ALL_EDITORS) {
|
||||
const editor = fiddle.editors[name];
|
||||
const backup = this.state.closedPanels[name];
|
||||
// set values once prompt approves
|
||||
await this.setEditorValues(editorValues);
|
||||
|
||||
if (isEditorBackup(backup) && backup.model && values[name]) {
|
||||
// The editor does not exist, attempt to set it on the backup
|
||||
backup.model.setValue(values[name]!);
|
||||
} else if (editor && editor.setValue && values[name]) {
|
||||
// The editor exists, set the value directly
|
||||
editor.setValue(values[name]!);
|
||||
}
|
||||
}
|
||||
document.title = getTitle(this.state);
|
||||
this.state.gistId = gistId || '';
|
||||
this.state.localPath = filePath;
|
||||
this.state.templateName = templateName;
|
||||
|
||||
// once loaded, we have a "saved" state
|
||||
this.state.isUnsaved = false;
|
||||
this.setupUnsavedOnChangeListener();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the values on all three editors.
|
||||
*
|
||||
* @param {EditorValues} values
|
||||
* @param {warn} warn - Should we warn before overwriting unsaved data?
|
||||
*/
|
||||
public async setEditorValues(values: Partial<EditorValues>): Promise<void> {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
if (!fiddle) {
|
||||
throw new Error('Fiddle not ready');
|
||||
}
|
||||
|
||||
for (const name of ALL_EDITORS) {
|
||||
const editor = fiddle.editors[name];
|
||||
const backup = this.state.closedPanels[name];
|
||||
|
||||
if (typeof values[name] !== 'undefined') {
|
||||
if (isEditorBackup(backup) && backup.model) {
|
||||
// The editor does not exist, attempt to set it on the backup
|
||||
backup.model.setValue(values[name]!);
|
||||
} else if (editor && editor.setValue) {
|
||||
// The editor exists, set the value directly
|
||||
editor.setValue(values[name]!);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the values on all three editors.
|
||||
*
|
||||
* @returns {EditorValues}
|
||||
*/
|
||||
public async getValues(options?: PackageJsonOptions): Promise<EditorValues> {
|
||||
public async getEditorValues(
|
||||
options?: PackageJsonOptions
|
||||
): Promise<EditorValues> {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
if (!fiddle) {
|
||||
@@ -93,10 +124,11 @@ export class App {
|
||||
const values: EditorValues = {
|
||||
html: getEditorValue(EditorId.html),
|
||||
main: getEditorValue(EditorId.main),
|
||||
renderer: getEditorValue(EditorId.renderer),
|
||||
preload: getEditorValue(EditorId.preload),
|
||||
renderer: getEditorValue(EditorId.renderer)
|
||||
};
|
||||
|
||||
if (options && options.include !== false) {
|
||||
if (options && options.include !== false) {
|
||||
values.package = await getPackageJson(this.state, values, options);
|
||||
}
|
||||
|
||||
@@ -112,16 +144,18 @@ export class App {
|
||||
|
||||
const React = await import('react');
|
||||
const { render } = await import('react-dom');
|
||||
const { Header } = await import('./components/header');
|
||||
const { Dialogs } = await import('./components/dialogs');
|
||||
const { Editors } = await import('./components/editors');
|
||||
const { OutputEditorsWrapper } = await import(
|
||||
'./components/output-editors-wrapper'
|
||||
);
|
||||
const { Header } = await import('./components/header');
|
||||
|
||||
const className = `${process.platform} container`;
|
||||
const app = (
|
||||
<div className={className}>
|
||||
<Header appState={this.state} />
|
||||
<Dialogs appState={this.state} />
|
||||
<Editors appState={this.state} />
|
||||
<Header appState={this.state} />
|
||||
<OutputEditorsWrapper appState={this.state} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -131,7 +165,7 @@ export class App {
|
||||
|
||||
ipcRenderer.send(WEBCONTENTS_READY_FOR_IPC_SIGNAL);
|
||||
|
||||
// Todo: A timer here is terrible. Let's fix this
|
||||
// TODO: A timer here is terrible. Let's fix this
|
||||
// and ensure we actually do it once Editors have mounted.
|
||||
setTimeout(() => {
|
||||
this.setupUnsavedOnChangeListener();
|
||||
@@ -161,7 +195,9 @@ export class App {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async setupTheme(): Promise<void> {
|
||||
const tag: HTMLStyleElement | null = document.querySelector('style#fiddle-theme');
|
||||
const tag: HTMLStyleElement | null = document.querySelector(
|
||||
'style#fiddle-theme'
|
||||
);
|
||||
const theme = await getTheme(this.state.theme);
|
||||
|
||||
if (tag && theme.css) {
|
||||
@@ -185,6 +221,7 @@ export class App {
|
||||
}
|
||||
|
||||
window.ElectronFiddle = window.ElectronFiddle || {};
|
||||
window.ElectronFiddle.contentChangeListeners = window.ElectronFiddle.contentChangeListeners || [];
|
||||
window.ElectronFiddle.contentChangeListeners =
|
||||
window.ElectronFiddle.contentChangeListeners || [];
|
||||
window.ElectronFiddle.app = window.ElectronFiddle.app || new App();
|
||||
window.ElectronFiddle.app.setup();
|
||||
|
||||
+45
-12
@@ -4,6 +4,7 @@ import * as path from 'path';
|
||||
import { fancyImport } from '../utils/import';
|
||||
import { normalizeVersion } from '../utils/normalize-version';
|
||||
import { USER_DATA_PATH } from './constants';
|
||||
import { getOfflineTypeDefinitionPath } from './fetch-types';
|
||||
|
||||
/**
|
||||
* The binary manager takes care of downloading Electron versions
|
||||
@@ -19,31 +20,48 @@ export class BinaryManager {
|
||||
* four times before giving up if an error occurs.
|
||||
*
|
||||
* @param {string} iVersion
|
||||
* @param {number} iteration
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async remove(iVersion: string, i: number = 0): Promise<void> {
|
||||
public async remove(iVersion: string): Promise<void> {
|
||||
const version = normalizeVersion(iVersion);
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
let isDeleted = false;
|
||||
|
||||
try {
|
||||
// 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;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Binary Manager: Tried to remove ${version}, but failed`, error);
|
||||
|
||||
if (i < 3) {
|
||||
console.log(`Binary Manager: Trying again`);
|
||||
return this.remove(version, i + 1);
|
||||
isDeleted = true;
|
||||
}
|
||||
};
|
||||
|
||||
const typeDefsCleaner = async () => {
|
||||
await this.removeTypeDefsForVersion(version);
|
||||
};
|
||||
|
||||
await rerunner(binaryCleaner);
|
||||
|
||||
if (isDeleted) {
|
||||
await rerunner(typeDefsCleaner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +107,7 @@ export class BinaryManager {
|
||||
} catch (error) {
|
||||
console.warn(`Failure while unzipping ${version}`, error);
|
||||
|
||||
// Todo: Handle this case
|
||||
// TODO: Handle this case
|
||||
}
|
||||
|
||||
this.state[version] = 'ready';
|
||||
@@ -160,6 +178,20 @@ export class BinaryManager {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expected path for a given Electron version
|
||||
*
|
||||
@@ -170,6 +202,7 @@ export class BinaryManager {
|
||||
return path.join(USER_DATA_PATH, 'electron-bin', version);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unzips an electron package so that we can actually use it.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,11 @@
|
||||
import { Button, InputGroup, Intent } from '@blueprintjs/core';
|
||||
import classnames from 'classnames';
|
||||
import { reaction, when } from 'mobx';
|
||||
import { reaction } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { EditorId, EditorValues } from '../../interfaces';
|
||||
import { IpcEvents } from '../../ipc-events';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../../shared-constants';
|
||||
import { getTitle } from '../../utils/get-title';
|
||||
import { idFromUrl, urlFromId } from '../../utils/gist';
|
||||
import { getOctokit } from '../../utils/octokit';
|
||||
import { getContent } from '../content';
|
||||
import { ipcRendererManager } from '../ipc';
|
||||
import { AppState } from '../state';
|
||||
|
||||
@@ -20,26 +15,32 @@ export interface AddressBarProps {
|
||||
|
||||
export interface AddressBarState {
|
||||
value: string;
|
||||
loaders: {
|
||||
gist: any;
|
||||
example: any;
|
||||
};
|
||||
}
|
||||
|
||||
@observer
|
||||
export class AddressBar extends React.Component<AddressBarProps, AddressBarState> {
|
||||
constructor(props: AddressBarProps) {
|
||||
super(props);
|
||||
|
||||
this.fetchExampleAndLoad = this.fetchExampleAndLoad.bind(this);
|
||||
this.fetchGistAndLoad = this.fetchGistAndLoad.bind(this);
|
||||
this.loadFiddleFromGist = this.loadFiddleFromGist.bind(this);
|
||||
this.loadFiddleFromElectronExample = this.loadFiddleFromElectronExample.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submit = this.submit.bind(this);
|
||||
this.verifyRemoteLoad = this.verifyRemoteLoad.bind(this);
|
||||
|
||||
const { gistId } = this.props.appState;
|
||||
const value = gistId ? urlFromId(gistId) : '';
|
||||
|
||||
this.state = { value };
|
||||
const { remoteLoader } = window.ElectronFiddle.app;
|
||||
|
||||
this.state = {
|
||||
value,
|
||||
loaders: {
|
||||
gist: remoteLoader.loadFiddleFromGist.bind(remoteLoader),
|
||||
example: remoteLoader.loadFiddleFromElectronExample.bind(remoteLoader)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,8 +60,9 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
* @memberof AddressBar
|
||||
*/
|
||||
public submit() {
|
||||
const { remoteLoader } = window.ElectronFiddle.app;
|
||||
if (this.state.value) {
|
||||
this.fetchGistAndLoad(idFromUrl(this.state.value) || this.state.value);
|
||||
remoteLoader.fetchGistAndLoad(idFromUrl(this.state.value) || this.state.value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,48 +70,20 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
* Once the component mounts, we'll subscribe to gistId changes
|
||||
*/
|
||||
public componentDidMount() {
|
||||
const { appState } = this.props;
|
||||
const { loaders } = this.state;
|
||||
reaction(
|
||||
() => this.props.appState.gistId,
|
||||
() => appState.gistId,
|
||||
(gistId: string) => this.setState({ value: urlFromId(gistId) })
|
||||
);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_GIST_REQUEST, this.loadFiddleFromGist);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, this.loadFiddleFromElectronExample);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_GIST_REQUEST, loaders.gist);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, loaders.example);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_GIST_REQUEST, this.loadFiddleFromGist);
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, this.loadFiddleFromElectronExample);
|
||||
}
|
||||
|
||||
public async loadFiddleFromElectronExample(_: any, exampleInfo: { path: string; ref: string }) {
|
||||
console.log(`Loading fiddle from Electron example`, _, exampleInfo);
|
||||
const ok = await this.verifyRemoteLoad('example from the Electron docs');
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchExampleAndLoad(exampleInfo.ref, exampleInfo.path);
|
||||
}
|
||||
|
||||
public async loadFiddleFromGist(_: any, gistInfo: { id: string }) {
|
||||
const ok = await this.verifyRemoteLoad('gist');
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchGistAndLoad(gistInfo.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies from the user that we should be loading this fiddle
|
||||
*
|
||||
* @param what What are we loading from (gist, example, etc.)
|
||||
*/
|
||||
public async verifyRemoteLoad(what: string): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
appState.setWarningDialogTexts({
|
||||
label: `Are you sure you sure you want to load this ${what}? Only load and run it if you trust the source`
|
||||
});
|
||||
appState.isWarningDialogShowing = true;
|
||||
await when(() => !appState.isWarningDialogShowing);
|
||||
|
||||
return !!appState.warningDialogLastResult;
|
||||
const { loaders } = this.state;
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_GIST_REQUEST, loaders.gist);
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, loaders.example);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,85 +95,6 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
this.setState({ value: event.target.value });
|
||||
}
|
||||
|
||||
public async fetchExampleAndLoad(ref: string, path: string): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
|
||||
try {
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
|
||||
// TODO: Maybe we should fetch the package.json for the given ref to use the correct version of Electron?
|
||||
const folder = await octo.repos.getContents({
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
ref,
|
||||
path,
|
||||
});
|
||||
|
||||
const values = {
|
||||
html: await getContent(EditorId.html, appState.version),
|
||||
renderer: await getContent(EditorId.renderer, appState.version),
|
||||
main: await getContent(EditorId.main, appState.version),
|
||||
};
|
||||
|
||||
const loaders: Array<Promise<void>> = [];
|
||||
if (!Array.isArray(folder.data)) {
|
||||
throw new Error('The example Fiddle tried to launch is not a valid Electron example');
|
||||
}
|
||||
|
||||
for (const child of folder.data) {
|
||||
switch (child.name) {
|
||||
case MAIN_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.main = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case INDEX_HTML_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.html = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case RENDERER_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.renderer = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(loaders);
|
||||
|
||||
return this.handleLoadingSuccess(values, '');
|
||||
} catch (error) {
|
||||
return this.handleLoadingFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a fiddle
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
* @memberof AddressBar
|
||||
*/
|
||||
public async fetchGistAndLoad(gistId: string): Promise<boolean> {
|
||||
try {
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
const gist = await octo.gists.get({ gist_id: gistId });
|
||||
|
||||
return this.handleLoadingSuccess({
|
||||
html: gist.data.files[INDEX_HTML_NAME].content,
|
||||
main: gist.data.files[MAIN_JS_NAME].content,
|
||||
renderer: gist.data.files[RENDERER_JS_NAME].content,
|
||||
}, gistId);
|
||||
} catch (error) {
|
||||
return this.handleLoadingFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
public renderLoadButton(isValueCorrect: boolean): JSX.Element {
|
||||
return (
|
||||
<Button
|
||||
@@ -233,49 +128,4 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading a fiddle from GitHub succeeded, let's move on.
|
||||
*
|
||||
* @private
|
||||
* @param {Partial<EditorValues>} values
|
||||
* @param {string} gistId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private async handleLoadingSuccess(values: Partial<EditorValues>, gistId: string): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
|
||||
appState.setWarningDialogTexts({
|
||||
label: 'Loading the fiddle will replace your current unsaved changes. Do you want to discard them?'
|
||||
});
|
||||
|
||||
await window.ElectronFiddle.app.setValues(values);
|
||||
|
||||
document.title = getTitle(appState);
|
||||
appState.gistId = gistId;
|
||||
appState.localPath = undefined;
|
||||
appState.templateName = undefined;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading a fiddle from GitHub failed - this method handles this case
|
||||
* gracefully.
|
||||
*
|
||||
* @private
|
||||
* @param {Error} error
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private handleLoadingFailed(error: Error): false {
|
||||
this.props.appState.setWarningDialogTexts({
|
||||
label: `Loading the fiddle failed: ${error}`,
|
||||
cancel: undefined
|
||||
});
|
||||
|
||||
this.props.appState.toogleWarningDialog();
|
||||
|
||||
console.warn(`Loading Fiddle failed`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export class PublishButton extends React.Component<PublishButtonProps> {
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
const { gitHubPublishAsPublic } = this.props.appState;
|
||||
const options = { includeDependencies: true, includeElectron: true };
|
||||
const values = await window.ElectronFiddle.app.getValues(options);
|
||||
const values = await window.ElectronFiddle.app.getEditorValues(options);
|
||||
|
||||
try {
|
||||
const gist = await octo.gists.create({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
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';
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 { PublishButton } from './commands-publish-button';
|
||||
import { Runner } from './commands-runner';
|
||||
@@ -28,6 +29,7 @@ export class Commands extends React.Component<CommandsProps, {}> {
|
||||
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
const { isBisectCommandShowing: isBisectCommandShowing } = appState;
|
||||
|
||||
return (
|
||||
<div className='commands'>
|
||||
@@ -36,6 +38,15 @@ export class Commands extends React.Component<CommandsProps, {}> {
|
||||
<VersionChooser appState={appState} />
|
||||
<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}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { Button, Dialog, FileInput } from '@blueprintjs/core';
|
||||
import { shell } from 'electron';
|
||||
import * as fsType from 'fs-extra';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
import * as path from 'path';
|
||||
import * as React from 'react';
|
||||
import { getTheme, THEMES_PATH } from '../themes';
|
||||
|
||||
import { AppState } from '../state';
|
||||
import { defaultDark, LoadedFiddleTheme } from '../themes-defaults';
|
||||
|
||||
export interface AddThemeDialogProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface AddThemeDialogState {
|
||||
file?: File;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "add monaco theme" dialog allows users to add custom editor themes.
|
||||
*
|
||||
* @class AddThemeDialog
|
||||
* @extends {React.Component<AddThemeDialogProps, AddThemeDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class AddThemeDialog extends React.Component<AddThemeDialogProps, AddThemeDialogState> {
|
||||
public resetState = { file: undefined };
|
||||
|
||||
constructor(props: AddThemeDialogProps) {
|
||||
super(props);
|
||||
this.state = this.resetState;
|
||||
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onClose = this.onClose.bind(this);
|
||||
this.onChangeFile = this.onChangeFile.bind(this);
|
||||
this.reset = this.reset.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a change of the file input
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public async onChangeFile(event: React.FormEvent<HTMLInputElement>) {
|
||||
const { files } = event.target as any;
|
||||
const file = files && files[0] ? files[0] : undefined;
|
||||
|
||||
this.setState({ file });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the submission of the dialog
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async onSubmit(): Promise<void> {
|
||||
const { file } = this.state;
|
||||
const defaultTheme = !!this.props.appState.theme ? await getTheme(this.props.appState.theme) : defaultDark;
|
||||
if (!file) return;
|
||||
|
||||
try {
|
||||
const editor = fsType.readJSONSync(file.path);
|
||||
if (!editor.base && !editor.rules) throw Error('File does not match specifications'); // has to have these attributes
|
||||
defaultTheme.editor = editor as Partial<MonacoType.editor.IStandaloneThemeData>;
|
||||
const newTheme = defaultTheme;
|
||||
const name = editor.name ? editor.name : file.name;
|
||||
await this.createNewThemeFromMonaco(name, newTheme);
|
||||
} catch (error) {
|
||||
this.props.appState.setWarningDialogTexts({label: `Error: ${error}, please pick a different file.`});
|
||||
this.props.appState.isWarningDialogShowing = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
public async createNewThemeFromMonaco(name: string, newTheme: LoadedFiddleTheme): Promise<boolean> {
|
||||
if (!name) return false;
|
||||
const themePath = path.join(THEMES_PATH, `${name}`);
|
||||
|
||||
await fsType.outputJSON(themePath, {
|
||||
...newTheme,
|
||||
name,
|
||||
}, {spaces: 2});
|
||||
|
||||
this.props.appState.setTheme(themePath);
|
||||
shell.showItemInFolder(themePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
get buttons() {
|
||||
const canSubmit = !!this.state.file;
|
||||
|
||||
return [
|
||||
(
|
||||
<Button
|
||||
icon='add'
|
||||
key='submit'
|
||||
disabled={!canSubmit}
|
||||
onClick={this.onSubmit}
|
||||
text='Add'
|
||||
/>
|
||||
), (
|
||||
<Button
|
||||
icon='cross'
|
||||
key='cancel'
|
||||
onClick={this.onClose}
|
||||
text='Cancel'
|
||||
/>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
public onClose() {
|
||||
this.props.appState.isThemeDialogShowing = false;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isThemeDialogShowing } = this.props.appState;
|
||||
const inputProps = { accept: '.json' };
|
||||
const { file } = this.state;
|
||||
|
||||
const text = file && file.path ? file.path : `Select the Monaco file...`;
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={isThemeDialogShowing}
|
||||
onClose={this.onClose}
|
||||
title='Add theme'
|
||||
className='dialog-add-version'
|
||||
>
|
||||
<div className='bp3-dialog-body'>
|
||||
<FileInput
|
||||
onInputChange={this.onChangeFile}
|
||||
inputProps={inputProps as any}
|
||||
text={text}
|
||||
/>
|
||||
<br />
|
||||
</div>
|
||||
<div className='bp3-dialog-footer'>
|
||||
<div className='bp3-dialog-footer-actions'>
|
||||
{this.buttons}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this component's state
|
||||
*/
|
||||
private reset(): void {
|
||||
this.setState(this.resetState);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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-version-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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
import { Alert, Intent } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface ConfirmDialogProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface ConfirmDialogState {
|
||||
}
|
||||
|
||||
/**
|
||||
* The token dialog prompts the user to either continue or cancel the operation.
|
||||
*
|
||||
* @export
|
||||
* @class ConfirmDialog
|
||||
* @extends {React.Component<ConfirmDialogProps, ConfirmDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class ConfirmDialog extends React.Component<ConfirmDialogProps, ConfirmDialogState> {
|
||||
constructor(props: ConfirmDialogProps) {
|
||||
super(props);
|
||||
|
||||
this.onClose = this.onClose.bind(this);
|
||||
}
|
||||
|
||||
public onClose(result: boolean) {
|
||||
this.props.appState.confirmationPromptLastResult = result;
|
||||
this.props.appState.toggleConfirmationPromptDialog();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isConfirmationPromptShowing, confirmationDialogTexts } = this.props.appState;
|
||||
|
||||
return (
|
||||
<Alert
|
||||
isOpen={isConfirmationPromptShowing}
|
||||
onClose={this.onClose}
|
||||
icon='help'
|
||||
confirmButtonText={confirmationDialogTexts.ok}
|
||||
cancelButtonText={confirmationDialogTexts.cancel}
|
||||
intent={Intent.PRIMARY}
|
||||
>
|
||||
<p>{confirmationDialogTexts.label}</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,7 @@ export interface WarningDialogState {
|
||||
}
|
||||
|
||||
/**
|
||||
* The token dialog asks the user for a GitHub Personal Access Token.
|
||||
* It's also responsible for checking if the token is correct.
|
||||
* The token dialog prompts the user to either continue or cancel the operation.
|
||||
*
|
||||
* @export
|
||||
* @class WarningDialog
|
||||
@@ -30,12 +29,11 @@ export class WarningDialog extends React.Component<WarningDialogProps, WarningDi
|
||||
|
||||
public onClose(result: boolean) {
|
||||
this.props.appState.warningDialogLastResult = result;
|
||||
this.props.appState.toogleWarningDialog();
|
||||
this.props.appState.toggleWarningDialog();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isWarningDialogShowing, warningDialogTexts } = this.props.appState;
|
||||
|
||||
return (
|
||||
<Alert
|
||||
isOpen={isWarningDialogShowing}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { observer } from 'mobx-react';
|
||||
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';
|
||||
import { Settings } from './settings';
|
||||
@@ -21,7 +24,13 @@ export interface DialogsProps {
|
||||
export class Dialogs extends React.Component<DialogsProps, {}> {
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
const { isTokenDialogShowing, isSettingsShowing, isAddVersionDialogShowing } = appState;
|
||||
const {
|
||||
isTokenDialogShowing,
|
||||
isSettingsShowing,
|
||||
isAddVersionDialogShowing,
|
||||
isThemeDialogShowing,
|
||||
isBisectDialogShowing
|
||||
} = appState;
|
||||
const maybeToken = isTokenDialogShowing
|
||||
? <TokenDialog key='dialogs' appState={appState} />
|
||||
: null;
|
||||
@@ -31,13 +40,23 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
|
||||
const maybeAddLocalVersion = isAddVersionDialogShowing
|
||||
? <AddVersionDialog key='add-version-dialog' appState={appState} />
|
||||
: 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} />;
|
||||
|
||||
return (
|
||||
<div key='dialogs' className='dialogs'>
|
||||
{maybeToken}
|
||||
{maybeSettings}
|
||||
{maybeAddLocalVersion}
|
||||
<WarningDialog appState={appState} />
|
||||
{maybeMonaco}
|
||||
{maybeBisect}
|
||||
{eitherWarningOrPrompt}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { AppState } from '../state';
|
||||
export interface EditorProps {
|
||||
appState: AppState;
|
||||
monaco: typeof MonacoType;
|
||||
monoacoOptions: MonacoType.editor.IEditorOptions;
|
||||
monacoOptions: MonacoType.editor.IEditorOptions;
|
||||
id: EditorId;
|
||||
options?: Partial<MonacoType.editor.IEditorConstructionOptions>;
|
||||
editorDidMount?: (editor: MonacoType.editor.IStandaloneCodeEditor) => void;
|
||||
@@ -68,7 +68,7 @@ export class Editor extends React.Component<EditorProps> {
|
||||
* Initialize Monaco.
|
||||
*/
|
||||
public async initMonaco() {
|
||||
const { monaco, monoacoOptions } = this.props;
|
||||
const { monaco, monacoOptions: monacoOptions } = this.props;
|
||||
const ref = this.containerRef.current;
|
||||
|
||||
if (ref) {
|
||||
@@ -77,7 +77,7 @@ export class Editor extends React.Component<EditorProps> {
|
||||
theme: 'main',
|
||||
contextmenu: false,
|
||||
model: null,
|
||||
...monoacoOptions
|
||||
...monacoOptions
|
||||
});
|
||||
|
||||
await this.editorDidMount(this.editor);
|
||||
@@ -121,6 +121,9 @@ export class Editor extends React.Component<EditorProps> {
|
||||
} else {
|
||||
const value = await getContent(id, version);
|
||||
const model = monaco.editor.createModel(value, this.language);
|
||||
model.updateOptions({
|
||||
tabSize: 2
|
||||
});
|
||||
|
||||
this.editor.setModel(model);
|
||||
}
|
||||
|
||||
@@ -30,8 +30,9 @@ const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
|
||||
export const TITLE_MAP: Record<MosaicId, string> = {
|
||||
main: 'Main Process (main.js)',
|
||||
renderer: 'Renderer Process (renderer.js)',
|
||||
preload: 'Preload (preload.js)',
|
||||
html: 'HTML (index.html)',
|
||||
docsDemo: 'Docs & Demos'
|
||||
docsDemo: 'Docs & Demos',
|
||||
};
|
||||
|
||||
export interface EditorsProps {
|
||||
@@ -79,17 +80,11 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
ipcRendererManager.on(IpcEvents.FS_NEW_FIDDLE, async (_event) => {
|
||||
const { version } = this.props.appState;
|
||||
|
||||
this.props.appState.setWarningDialogTexts({
|
||||
label: 'Your current fiddle is unsaved. Do you want to discard it?'
|
||||
});
|
||||
|
||||
window.ElectronFiddle.app.fileManager.setFiddle({
|
||||
values: {
|
||||
html: await getContent(EditorId.html, version),
|
||||
renderer: await getContent(EditorId.renderer, version),
|
||||
main: await getContent(EditorId.main, version),
|
||||
}
|
||||
});
|
||||
await window.ElectronFiddle.app.replaceFiddle({
|
||||
html: await getContent(EditorId.html, version),
|
||||
renderer: await getContent(EditorId.renderer, version),
|
||||
main: await getContent(EditorId.main, version),
|
||||
}, {});
|
||||
});
|
||||
|
||||
ipcRendererManager.on(IpcEvents.MONACO_TOGGLE_OPTION, (_event, cmd: string) => {
|
||||
@@ -241,7 +236,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
id={id}
|
||||
monaco={monaco!}
|
||||
appState={appState}
|
||||
monoacoOptions={defaultMonacoOptions}
|
||||
monacoOptions={defaultMonacoOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as React from 'react';
|
||||
import { AppState } from '../state';
|
||||
import { ChromeMac } from './chrome-mac';
|
||||
import { Commands } from './commands';
|
||||
import { Output } from './output';
|
||||
import { WelcomeTour } from './tour-welcome';
|
||||
|
||||
export interface HeaderProps {
|
||||
@@ -23,7 +22,6 @@ export class Header extends React.Component<HeaderProps, {}> {
|
||||
<ChromeMac appState={this.props.appState} />
|
||||
<header id='header'>
|
||||
<Commands key='commands' appState={this.props.appState} />
|
||||
<Output key='output' appState={this.props.appState} />
|
||||
</header>
|
||||
<WelcomeTour appState={this.props.appState} />
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import * as React from 'react';
|
||||
import { Mosaic, MosaicNode } from 'react-mosaic-component';
|
||||
|
||||
import { AppState } from '../state';
|
||||
import { Editors } from './editors';
|
||||
import { Output } from './output';
|
||||
|
||||
export interface WrapperProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface WrapperState {
|
||||
mosaicArrangement: MosaicNode<WrapperMosaicId>;
|
||||
}
|
||||
|
||||
export type WrapperMosaicId = 'output' | 'editors';
|
||||
|
||||
export class OutputEditorsWrapper extends React.Component<WrapperProps, WrapperState> {
|
||||
|
||||
private MOSAIC_ELEMENTS = {
|
||||
output: <Output appState={this.props.appState} />,
|
||||
editors: <Editors appState={this.props.appState} />
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
mosaicArrangement: {
|
||||
direction: 'column',
|
||||
first: 'output',
|
||||
second: 'editors',
|
||||
splitPercentage: 25,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<Mosaic<WrapperMosaicId>
|
||||
renderTile={(id: string) => this.MOSAIC_ELEMENTS[id]}
|
||||
resize={{minimumPaneSizePercentage: 0}}
|
||||
value={this.state.mosaicArrangement}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private onChange = (currentNode: any) => {
|
||||
const isConsoleShowing = currentNode.splitPercentage !== 0;
|
||||
|
||||
if (isConsoleShowing !== this.props.appState.isConsoleShowing) {
|
||||
this.props.appState.isConsoleShowing = isConsoleShowing;
|
||||
}
|
||||
|
||||
this.setState({mosaicArrangement: currentNode});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
import { autorun } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
import { MosaicContext } from 'react-mosaic-component';
|
||||
|
||||
import { OutputEntry } from '../../interfaces';
|
||||
import { AppState } from '../state';
|
||||
import { WrapperMosaicId } from './output-editors-wrapper';
|
||||
|
||||
|
||||
export interface CommandsProps {
|
||||
appState: AppState;
|
||||
@@ -19,6 +23,8 @@ export interface CommandsProps {
|
||||
*/
|
||||
@observer
|
||||
export class Output extends React.Component<CommandsProps, {}> {
|
||||
public static contextType = MosaicContext;
|
||||
public context: MosaicContext<WrapperMosaicId>;
|
||||
private outputRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
constructor(props: CommandsProps) {
|
||||
@@ -28,6 +34,23 @@ export class Output extends React.Component<CommandsProps, {}> {
|
||||
this.renderEntry = this.renderEntry.bind(this);
|
||||
}
|
||||
|
||||
|
||||
public componentDidMount() {
|
||||
autorun(() => {
|
||||
const { isConsoleShowing } = this.props.appState;
|
||||
|
||||
// this context should always exist, but mocking context in enzyme
|
||||
// is not fully supported, so this condition makes the tests pass
|
||||
if (this.context.mosaicActions && this.context.mosaicActions.expand) {
|
||||
if (!isConsoleShowing) {
|
||||
this.context.mosaicActions.expand(['first'], 0);
|
||||
} else {
|
||||
this.context.mosaicActions.expand(['first'], 25);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
if (this.outputRef.current) {
|
||||
this.outputRef.current.scrollTop = this.outputRef.current.scrollHeight;
|
||||
@@ -71,9 +94,12 @@ export class Output extends React.Component<CommandsProps, {}> {
|
||||
));
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isConsoleShowing, output } = this.props.appState;
|
||||
const className = isConsoleShowing ? 'output showing' : 'output';
|
||||
public render(): JSX.Element | null {
|
||||
const { output, isConsoleShowing } = this.props.appState;
|
||||
|
||||
if (!isConsoleShowing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The last 1000 lines
|
||||
const lines = output
|
||||
@@ -81,7 +107,7 @@ export class Output extends React.Component<CommandsProps, {}> {
|
||||
.map(this.renderEntry);
|
||||
|
||||
return (
|
||||
<div className={className} ref={this.outputRef}>
|
||||
<div className='output' ref={this.outputRef}>
|
||||
{lines}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -63,7 +63,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Exection</h2>
|
||||
<h2>Execution</h2>
|
||||
<Callout>
|
||||
These advanced settings control how Electron Fiddle executes your fiddles.
|
||||
</Callout>
|
||||
|
||||
@@ -76,6 +76,7 @@ export class AppearanceSettings extends React.Component<
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.openThemeFolder = this.openThemeFolder.bind(this);
|
||||
this.handleAddTheme = this.handleAddTheme.bind(this);
|
||||
|
||||
this.state = {
|
||||
themes: []
|
||||
@@ -111,8 +112,9 @@ export class AppearanceSettings extends React.Component<
|
||||
* @memberof AppearanceSettings
|
||||
*/
|
||||
public async createNewThemeFromCurrent(): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const theme = await getTheme(this.props.appState.theme);
|
||||
const theme = await getTheme(appState.theme);
|
||||
|
||||
try {
|
||||
const namor = await fancyImport<any>('namor');
|
||||
@@ -128,6 +130,8 @@ export class AppearanceSettings extends React.Component<
|
||||
|
||||
shell.showItemInFolder(themePath);
|
||||
|
||||
this.setState({themes: await getAvailableThemes()});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn(`Themes: Failed to create new theme from current`, error);
|
||||
@@ -190,13 +194,29 @@ export class AppearanceSettings extends React.Component<
|
||||
</a>. The easiest way to get started is to clone one of the two existing
|
||||
themes and to add your own colors.
|
||||
</p>
|
||||
<p>
|
||||
Additionally, if you wish to import a Monaco Editor theme, pick your JSON file and Fiddle will attempt to import it.
|
||||
</p>
|
||||
<Button
|
||||
onClick={this.createNewThemeFromCurrent}
|
||||
text='Create theme from current selection'
|
||||
icon='duplicate'
|
||||
/>
|
||||
<Button
|
||||
icon='document-open'
|
||||
onClick={this.handleAddTheme}
|
||||
text='Add a Monaco Editor theme'
|
||||
/>
|
||||
</Callout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the "add monaco theme" dialog
|
||||
*/
|
||||
public handleAddTheme(): void {
|
||||
this.props.appState.toggleAddMonacoThemeDialog();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,6 +44,16 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
|
||||
this.state = {
|
||||
section: SettingsSections.General
|
||||
};
|
||||
|
||||
this.closeSettingsPanel = this.closeSettingsPanel.bind(this);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
window.addEventListener('keyup', this.closeSettingsPanel, true);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
window.removeEventListener('keyup', this.closeSettingsPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,4 +148,14 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
|
||||
|
||||
return 'cog';
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger closing of the settings panel upon Esc
|
||||
*/
|
||||
private closeSettingsPanel(event: KeyboardEvent) {
|
||||
const { appState } = this.props;
|
||||
if (event.code === 'Escape') {
|
||||
appState.isSettingsShowing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export function ShowMeApp(_props: any): JSX.Element {
|
||||
|
||||
let result = '';
|
||||
pathsToQuery.forEach((item) => {
|
||||
result += `${item}: ${remote.app.getPath(item)}\n`;
|
||||
result += `${item}: ${remote.app.getPath(item as any)}\n`;
|
||||
});
|
||||
|
||||
setPaths(result);
|
||||
|
||||
@@ -115,7 +115,7 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
a Node.js application is started. The main script runs in the "main
|
||||
process". To display a user interface, the main process creates renderer
|
||||
processes – usually in the form of windows, which Electron calls
|
||||
<code>BrowserWindow</code>.
|
||||
<code>BrowserWindow</code>.
|
||||
</p>
|
||||
<p>
|
||||
To get started, pretend that the main process is just like a Node.js
|
||||
@@ -137,10 +137,10 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
content: (
|
||||
<p>
|
||||
In the default fiddle, this HTML file is loaded in the
|
||||
<code>BrowserWindow</code>. Any HTML, CSS, or JavaScript that works
|
||||
<code>BrowserWindow</code>. Any HTML, CSS, or JavaScript that works
|
||||
in a browser will work here, too. In addition, Electron allows you
|
||||
to execute Node.js code. Take a close look at the
|
||||
<code><script /></code> tag and notice how we can call <code>
|
||||
<code><script /></code> tag and notice how we can call <code>
|
||||
require()</code> like we would in Node.js.
|
||||
</p>
|
||||
)
|
||||
@@ -157,7 +157,7 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
</p>
|
||||
<p>
|
||||
By the way: If you want to use an <code>npm</code> module here, just
|
||||
<code>require</code> it. Electron Fiddle will automatically detect that you
|
||||
<code>require</code> it. Electron Fiddle will automatically detect that you
|
||||
requested a module and install it as soon as you run your fiddle.
|
||||
</p>
|
||||
</>
|
||||
|
||||
@@ -18,3 +18,6 @@ export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
|
||||
second: EditorId.html,
|
||||
}
|
||||
};
|
||||
|
||||
export const ELECTRON_ORG = 'electron';
|
||||
export const ELECTRON_REPO = 'electron';
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function getContent(
|
||||
|
||||
if (name === EditorId.main) {
|
||||
// We currently only distinguish between loadFile
|
||||
// and loadURL. Todo: Properly version the quick-start.
|
||||
// and loadURL. TODO: Properly version the quick-start.
|
||||
if (version && version.startsWith('1.')) {
|
||||
return (await import('../content/main-1-x-x')).main;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export async function getContent(
|
||||
export async function isContentUnchanged(name: EditorId): Promise<boolean> {
|
||||
if (!window.ElectronFiddle || !window.ElectronFiddle.app) return false;
|
||||
|
||||
const values = await window.ElectronFiddle.app.getValues({ include: false });
|
||||
const values = await window.ElectronFiddle.app.getEditorValues({ include: false });
|
||||
|
||||
// Handle main case, which needs to check both possible versions
|
||||
if (name === EditorId.main) {
|
||||
|
||||
+58
-21
@@ -2,28 +2,38 @@ import * as fsType from 'fs-extra';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ElectronVersion, ElectronVersionSource } from '../interfaces';
|
||||
import { callIn } from '../utils/call-in';
|
||||
import { fancyImport } from '../utils/import';
|
||||
import { USER_DATA_PATH } from './constants';
|
||||
|
||||
const definitionPath = path.join(USER_DATA_PATH, 'electron-typedef');
|
||||
|
||||
|
||||
/**
|
||||
* Fetch TypeScript definitions for the current version of Electron (online)
|
||||
*
|
||||
* @param {string} version
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function fetchTypeDefinitions(version: string): Promise<string> {
|
||||
export async function fetchTypeDefinitions(version: string): Promise<string> {
|
||||
const url = `https://unpkg.com/electron@${version}/electron.d.ts`;
|
||||
|
||||
return window.fetch(url)
|
||||
.then((response) => response.text())
|
||||
.catch((error) => {
|
||||
console.warn(`Fetch Types: Could not fetch definitions`, error);
|
||||
return '';
|
||||
});
|
||||
let text: string;
|
||||
try {
|
||||
const response = await window.fetch(url);
|
||||
text = await response.text();
|
||||
} catch (error) {
|
||||
console.warn(`Fetch Types: Could not fetch definitions`, error);
|
||||
return '';
|
||||
}
|
||||
|
||||
// for invalid packa
|
||||
if (text.includes('Cannot find package')) {
|
||||
console.warn(`Fetch Types: ${text}`);
|
||||
return '';
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,27 +58,25 @@ export async function getOfflineTypeDefinitions(version: string): Promise<boolea
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TypeScript defintions for a version of Electron. If none can't be
|
||||
* Get TypeScript definitions for a version of Electron. If none can be
|
||||
* found, returns null.
|
||||
*
|
||||
* @param {string} version
|
||||
* @returns {void}
|
||||
*/
|
||||
export async function getTypeDefinitions(version: string): Promise<string | null> {
|
||||
export async function getDownloadedVersionTypeDefs(version: ElectronVersion): Promise<string | null> {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
await fs.mkdirp(definitionPath);
|
||||
await fs.mkdirp(definitionPath);
|
||||
const offlinePath = getOfflineTypeDefinitionPath(version.version);
|
||||
|
||||
const offlinePath = getOfflineTypeDefinitionPath(version);
|
||||
|
||||
if (await getOfflineTypeDefinitions(version)) {
|
||||
if (await getOfflineTypeDefinitions(version.version)) {
|
||||
try {
|
||||
return await fs.readFile(offlinePath, 'utf-8');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
const typeDefs = await fetchTypeDefinitions(version);
|
||||
|
||||
const typeDefs = await fetchTypeDefinitions(version.version);
|
||||
if (typeDefs && typeDefs.length > 0) {
|
||||
try {
|
||||
await fs.outputFile(offlinePath, typeDefs);
|
||||
@@ -79,17 +87,27 @@ export async function getTypeDefinitions(version: string): Promise<string | null
|
||||
return typeDefs;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLocalVersionTypeDefs(version: ElectronVersion) {
|
||||
if (version.source === ElectronVersionSource.local && !!version.localPath) {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const typesPath = getLocalTypePathForVersion(version);
|
||||
if (!!typesPath && fs.existsSync(typesPath)) {
|
||||
return fs.readFile(typesPath, 'utf-8');
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to update the editor with type definitions.
|
||||
*
|
||||
* @param {string} version
|
||||
*/
|
||||
export async function updateEditorTypeDefinitions(version: string, i: number = 0): Promise<void> {
|
||||
export async function updateEditorTypeDefinitions(version: ElectronVersion, i: number = 0): Promise<void> {
|
||||
const defer = async (): Promise<void> => {
|
||||
if (i > 10) {
|
||||
console.warn(`Fetch Types: Failed, dependencies do not exist`);
|
||||
@@ -106,17 +124,36 @@ export async function updateEditorTypeDefinitions(version: string, i: number = 0
|
||||
const { app } = window.ElectronFiddle;
|
||||
const monaco: typeof MonacoType = app.monaco!;
|
||||
const typeDefDisposable: MonacoType.IDisposable = app.typeDefDisposable!;
|
||||
const typeDefs = await getTypeDefinitions(version);
|
||||
|
||||
const getTypeDefs = (version.source === ElectronVersionSource.local) ?
|
||||
getLocalVersionTypeDefs : getDownloadedVersionTypeDefs;
|
||||
|
||||
const typeDefs = await getTypeDefs(version);
|
||||
|
||||
if (typeDefDisposable) {
|
||||
typeDefDisposable.dispose();
|
||||
}
|
||||
|
||||
if (typeDefs) {
|
||||
console.log(`Fetch Types: Updating Monaco types with electron.d.ts@${version}`);
|
||||
console.log(`Fetch Types: Updating Monaco types with electron.d.ts@${version.version}`);
|
||||
const disposable = monaco.languages.typescript.javascriptDefaults.addExtraLib(typeDefs);
|
||||
window.ElectronFiddle.app.typeDefDisposable = disposable;
|
||||
} else {
|
||||
console.log(`Fetch Types: No type definitions for ${version} 😢`);
|
||||
console.log(`Fetch Types: No type definitions for ${version.version} 😢`);
|
||||
}
|
||||
}
|
||||
|
||||
export function getLocalTypePathForVersion(version: ElectronVersion) {
|
||||
if (version.localPath) {
|
||||
return path.join(
|
||||
version.localPath,
|
||||
'gen',
|
||||
'electron',
|
||||
'tsc',
|
||||
'typings',
|
||||
'electron.d.ts'
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import * as fsType from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
import { EditorValues, Files, FileTransform, SetFiddleOptions } from '../interfaces';
|
||||
import { EditorValues, Files, FileTransform } from '../interfaces';
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { DEFAULT_OPTIONS, PackageJsonOptions } from '../utils/get-package';
|
||||
import { getTitle } from '../utils/get-title';
|
||||
import { fancyImport } from '../utils/import';
|
||||
import { ipcRendererManager } from './ipc';
|
||||
import { AppState } from './state';
|
||||
@@ -38,13 +37,12 @@ export class FileManager {
|
||||
/**
|
||||
* Opens a template.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {string} templateName
|
||||
* @memberof FileManager
|
||||
*/
|
||||
public async openTemplate(name: string) {
|
||||
const values = await getTemplateValues(name);
|
||||
|
||||
this.setFiddle({ values, templateName: name });
|
||||
public async openTemplate(templateName: string) {
|
||||
const editorValues = await getTemplateValues(templateName);
|
||||
await window.ElectronFiddle.app.replaceFiddle(editorValues, {templateName});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,34 +58,15 @@ export class FileManager {
|
||||
|
||||
// We'll do our best and will likely have to
|
||||
// rewrite this once we support multiple files
|
||||
const values: EditorValues = {
|
||||
const editorValues: EditorValues = {
|
||||
html: await this.readFile(path.join(filePath, INDEX_HTML_NAME)),
|
||||
main: await this.readFile(path.join(filePath, MAIN_JS_NAME)),
|
||||
renderer: await this.readFile(path.join(filePath, RENDERER_JS_NAME)),
|
||||
preload: await this.readFile(path.join(filePath, PRELOAD_JS_NAME)),
|
||||
};
|
||||
|
||||
this.setFiddle({ values, filePath });
|
||||
}
|
||||
|
||||
/**
|
||||
* Got values? This method will load them.
|
||||
*
|
||||
* @param {SetFiddleOptions} options
|
||||
* @memberof FileManager
|
||||
*/
|
||||
public async setFiddle({ values, filePath, templateName }: SetFiddleOptions) {
|
||||
this.appState.gistId = '';
|
||||
this.appState.isMyGist = false;
|
||||
this.appState.localPath = filePath;
|
||||
this.appState.templateName = templateName;
|
||||
|
||||
this.appState.setWarningDialogTexts({
|
||||
label: `Opening this fiddle will replace your unsaved changes. Do you want to proceed?`,
|
||||
ok: 'Yes'
|
||||
});
|
||||
|
||||
await window.ElectronFiddle.app.setValues(values);
|
||||
document.title = getTitle(this.appState);
|
||||
window.ElectronFiddle.app.replaceFiddle(editorValues, {filePath});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +88,15 @@ export class FileManager {
|
||||
const files = await this.getFiles(undefined, ...transforms);
|
||||
|
||||
for (const [fileName, content] of files) {
|
||||
await this.saveFile(path.join(pathToSave, fileName), content);
|
||||
const savePath = path.join(pathToSave, fileName);
|
||||
|
||||
// If the file has content, save it to disk. If there's no
|
||||
// content in the file, remove a file that possibly exists.
|
||||
if (content) {
|
||||
await this.saveFile(savePath, content);
|
||||
} else {
|
||||
await this.removeFile(savePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (pathToSave !== localPath) {
|
||||
@@ -132,12 +119,13 @@ export class FileManager {
|
||||
*/
|
||||
public async getFiles(options?: PackageJsonOptions, ...transforms: Array<FileTransform>): Promise<Files> {
|
||||
const pOptions = typeof options === 'object' ? options : DEFAULT_OPTIONS;
|
||||
const values = await window.ElectronFiddle.app.getValues(pOptions);
|
||||
const values = await window.ElectronFiddle.app.getEditorValues(pOptions);
|
||||
let output: Files = new Map();
|
||||
|
||||
output.set(RENDERER_JS_NAME, values.renderer);
|
||||
output.set(MAIN_JS_NAME, values.main);
|
||||
output.set(INDEX_HTML_NAME, values.html);
|
||||
output.set(PRELOAD_JS_NAME, values.preload);
|
||||
output.set(PACKAGE_NAME, values.package!);
|
||||
|
||||
for (const transform of transforms) {
|
||||
@@ -240,4 +228,22 @@ export class FileManager {
|
||||
ipcRendererManager.send(IpcEvents.FS_SAVE_FIDDLE_ERROR, [filePath]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely attempts to remove a file, doesn't crash the app if
|
||||
* it fails.
|
||||
*
|
||||
* @param {string} filePath
|
||||
* @returns {string}
|
||||
* @memberof FileManager
|
||||
*/
|
||||
private async removeFile(filePath: string): Promise<void> {
|
||||
try {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
return await fs.remove(filePath);
|
||||
} catch (error) {
|
||||
console.log(`FileManager: Could not remove ${filePath}`, error);
|
||||
ipcRendererManager.send(IpcEvents.FS_SAVE_FIDDLE_ERROR, [filePath]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -16,7 +16,7 @@ const ignoredModules: Array<string> = [
|
||||
];
|
||||
|
||||
/* regular expression to both match and extract module names */
|
||||
const requiregx = /require\(['"](.*?)['"]\)/gm;
|
||||
const requiregx = /^.*require\(['"](.*?)['"]\)/gm;
|
||||
|
||||
|
||||
/*
|
||||
@@ -91,8 +91,11 @@ export function findModules(input: string): Array<string> {
|
||||
/* grab all global require matches in the text */
|
||||
// tslint:disable-next-line:no-conditional-assignment
|
||||
while (match = (requiregx.exec(input) || null)) {
|
||||
const mod = match[1];
|
||||
modules.push(mod);
|
||||
// ensure commented-out requires aren't downloaded
|
||||
if (!match[0].startsWith('//')) {
|
||||
const mod = match[1];
|
||||
modules.push(mod);
|
||||
}
|
||||
}
|
||||
|
||||
/* map and reduce */
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
import * as Octokit from '@octokit/rest';
|
||||
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 { ELECTRON_ORG, ELECTRON_REPO } from './constants';
|
||||
import { getContent } from './content';
|
||||
import { AppState } from './state';
|
||||
import { ElectronReleaseChannel, getReleaseChannel } from './versions';
|
||||
|
||||
export class RemoteLoader {
|
||||
constructor(private readonly appState: AppState) {
|
||||
this.loadFiddleFromElectronExample.bind(this);
|
||||
this.loadFiddleFromGist.bind(this);
|
||||
this.verifyRemoteLoad.bind(this);
|
||||
this.verifyReleaseChannelEnabled.bind(this);
|
||||
this.fetchExampleAndLoad.bind(this);
|
||||
this.fetchGistAndLoad.bind(this);
|
||||
this.setElectronVersionWithRef.bind(this);
|
||||
this.getPackageVersionFromRef.bind(this);
|
||||
this.handleLoadingSuccess.bind(this);
|
||||
this.handleLoadingFailed.bind(this);
|
||||
}
|
||||
|
||||
public async loadFiddleFromElectronExample(_: any, exampleInfo: { path: string; ref: string }) {
|
||||
console.log(`Loading fiddle from Electron example`, _, exampleInfo);
|
||||
const ok = await this.verifyRemoteLoad('example from the Electron docs', exampleInfo.ref);
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchExampleAndLoad(exampleInfo.ref, exampleInfo.path);
|
||||
}
|
||||
|
||||
public async loadFiddleFromGist(_: any, gistInfo: { id: string }) {
|
||||
const ok = await this.verifyRemoteLoad('gist');
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchGistAndLoad(gistInfo.id);
|
||||
}
|
||||
|
||||
public async fetchExampleAndLoad(ref: string, path: string): Promise<boolean> {
|
||||
try {
|
||||
const octo = await getOctokit(this.appState);
|
||||
|
||||
const folder = await octo.repos.getContents({
|
||||
owner: ELECTRON_REPO,
|
||||
repo: ELECTRON_ORG,
|
||||
ref,
|
||||
path,
|
||||
});
|
||||
|
||||
const ok = await this.setElectronVersionWithRef(ref);
|
||||
if (!ok) return false;
|
||||
|
||||
const values = {
|
||||
html: await getContent(EditorId.html, this.appState.version),
|
||||
renderer: await getContent(EditorId.renderer, this.appState.version),
|
||||
main: await getContent(EditorId.main, this.appState.version),
|
||||
preload: await getContent(EditorId.preload, this.appState.version),
|
||||
};
|
||||
|
||||
const loaders: Array<Promise<void>> = [];
|
||||
if (!Array.isArray(folder.data)) {
|
||||
throw new Error('The example Fiddle tried to launch is not a valid Electron example');
|
||||
}
|
||||
|
||||
for (const child of folder.data) {
|
||||
if (!child.download_url) {
|
||||
console.warn(`Could not find download_url for ${child.name}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (child.name) {
|
||||
case MAIN_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.main = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case INDEX_HTML_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.html = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case RENDERER_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.renderer = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
case PRELOAD_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.preload = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(loaders);
|
||||
|
||||
return this.handleLoadingSuccess(values, '');
|
||||
} catch (error) {
|
||||
return this.handleLoadingFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from a gist. If it doesn't exist, return an empty string.
|
||||
*
|
||||
* @param {Octokit.Response<Octokit.GistsGetResponse>} gist
|
||||
* @param {string} name
|
||||
* @returns {string}
|
||||
* @memberof RemoteLoader
|
||||
*/
|
||||
public getContentOrEmpty(gist: Octokit.Response<Octokit.GistsGetResponse>, name: string): string {
|
||||
try {
|
||||
return gist.data.files[name].content;
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a fiddle
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
* @memberof RemoteLoader
|
||||
*/
|
||||
public async fetchGistAndLoad(gistId: string): Promise<boolean> {
|
||||
try {
|
||||
const octo = await getOctokit(this.appState);
|
||||
const gist = await octo.gists.get({ gist_id: gistId });
|
||||
|
||||
return this.handleLoadingSuccess({
|
||||
html: this.getContentOrEmpty(gist, INDEX_HTML_NAME),
|
||||
main: this.getContentOrEmpty(gist, MAIN_JS_NAME),
|
||||
renderer: this.getContentOrEmpty(gist, RENDERER_JS_NAME),
|
||||
preload: this.getContentOrEmpty(gist, PRELOAD_JS_NAME)
|
||||
}, gistId);
|
||||
} catch (error) {
|
||||
return this.handleLoadingFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async setElectronVersionWithRef(ref: string): Promise<boolean> {
|
||||
const version = await this.getPackageVersionFromRef(ref);
|
||||
|
||||
const supportedVersions = sortedElectronMap(this.appState.versions, (k) => k);
|
||||
if (!supportedVersions.includes(version)) {
|
||||
this.handleLoadingFailed(new Error('Version of Electron in example not supported'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if version is part of release channel
|
||||
const versionReleaseChannel: ElectronReleaseChannel = getReleaseChannel(version);
|
||||
|
||||
if (!this.appState.versionsToShow.includes(versionReleaseChannel)) {
|
||||
const ok = await this.verifyReleaseChannelEnabled(versionReleaseChannel);
|
||||
if (!ok) return false;
|
||||
|
||||
this.appState.versionsToShow.push(versionReleaseChannel);
|
||||
}
|
||||
|
||||
this.appState.setVersion(version);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getPackageVersionFromRef(ref: string): Promise<string> {
|
||||
const octo = await getOctokit(this.appState);
|
||||
const { data: packageJsonData } = await octo.repos.getContents({
|
||||
owner: ELECTRON_ORG,
|
||||
repo: ELECTRON_REPO,
|
||||
ref,
|
||||
path: 'package.json'
|
||||
});
|
||||
|
||||
if (!Array.isArray(packageJsonData) && !!packageJsonData.content) {
|
||||
const packageJsonString = Buffer.from(packageJsonData.content, 'base64').toString('utf8');
|
||||
const { version } = JSON.parse(packageJsonString);
|
||||
return version;
|
||||
} else {
|
||||
console.error(`getPackageVersionFromRef: Received unexpected response from GitHub, could not parse version`, {
|
||||
packageJsonData
|
||||
});
|
||||
|
||||
return '0.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies from the user that we should be loading this fiddle
|
||||
*
|
||||
* @param what What are we loading from (gist, example, etc.)
|
||||
*/
|
||||
public async verifyRemoteLoad(what: string, fiddlePath?: string): Promise<boolean> {
|
||||
this.appState.setConfirmationPromptTexts({
|
||||
label: `Are you sure you sure you want to load this '${what}' from fiddle path '${fiddlePath}'? Only load and run it if you trust the source.`
|
||||
});
|
||||
this.appState.isConfirmationPromptShowing = true;
|
||||
await when(() => !this.appState.isConfirmationPromptShowing);
|
||||
|
||||
return !!this.appState.confirmationPromptLastResult;
|
||||
}
|
||||
|
||||
public async verifyReleaseChannelEnabled(channel: string): Promise<boolean> {
|
||||
this.appState.setWarningDialogTexts({
|
||||
label: `You're loading an example with a version of Electron with an unincluded release
|
||||
channel (${channel}). Do you want to enable the release channel to load the
|
||||
version of Electron from the example?`
|
||||
});
|
||||
this.appState.isWarningDialogShowing = true;
|
||||
await when(() => !this.appState.isWarningDialogShowing);
|
||||
|
||||
return !!this.appState.warningDialogLastResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading a fiddle from GitHub succeeded, let's move on.
|
||||
*
|
||||
* @param {Partial<EditorValues>} values
|
||||
* @param {string} gistId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private async handleLoadingSuccess(values: Partial<EditorValues>, gistId: string): Promise<boolean> {
|
||||
await window.ElectronFiddle.app.replaceFiddle(values, {gistId});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading a fiddle from GitHub failed - this method handles this case
|
||||
* gracefully.
|
||||
*
|
||||
* @param {Error} error
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private handleLoadingFailed(error: Error): false {
|
||||
if (navigator.onLine) {
|
||||
this.appState.setWarningDialogTexts({
|
||||
label: `Loading the fiddle failed: ${error}`,
|
||||
cancel: undefined
|
||||
});
|
||||
} else {
|
||||
this.appState.setWarningDialogTexts({
|
||||
label: `Loading the fiddle failed. Your computer seems to be offline. Error: ${error}`,
|
||||
cancel: undefined
|
||||
});
|
||||
}
|
||||
|
||||
this.appState.toggleWarningDialog();
|
||||
|
||||
console.warn(`Loading Fiddle failed`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ export class Runner {
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async run(): Promise<boolean> {
|
||||
const { fileManager, getValues } = window.ElectronFiddle.app;
|
||||
const { fileManager, getEditorValues } = window.ElectronFiddle.app;
|
||||
const options = { includeDependencies: false, includeElectron: false };
|
||||
const { binaryManager, currentElectronVersion } = this.appState;
|
||||
const { version, localPath } = currentElectronVersion;
|
||||
@@ -47,7 +47,7 @@ export class Runner {
|
||||
}
|
||||
this.appState.isConsoleShowing = true;
|
||||
|
||||
const values = await getValues(options);
|
||||
const values = await getEditorValues(options);
|
||||
const dir = await this.saveToTemp(options);
|
||||
|
||||
if (!dir) return false;
|
||||
@@ -200,8 +200,8 @@ export class Runner {
|
||||
this.appState.isRunning = true;
|
||||
pushOutput(`Electron v${version} started.`);
|
||||
|
||||
this.child.stdout.on('data', (data) => pushOutput(data, { bypassBuffer: false }));
|
||||
this.child.stderr.on('data', (data) => pushOutput(data, { bypassBuffer: false }));
|
||||
this.child.stdout!.on('data', (data) => pushOutput(data, { bypassBuffer: false }));
|
||||
this.child.stderr!.on('data', (data) => pushOutput(data, { bypassBuffer: false }));
|
||||
this.child.on('close', async (code) => {
|
||||
const withCode = typeof code === 'number'
|
||||
? ` with code ${code.toString()}.`
|
||||
|
||||
+103
-40
@@ -1,3 +1,4 @@
|
||||
import * as fsType from 'fs-extra';
|
||||
import { action, autorun, computed, observable, when } from 'mobx';
|
||||
import { MosaicNode } from 'react-mosaic-component';
|
||||
|
||||
@@ -19,14 +20,17 @@ import { arrayToStringMap } from '../utils/array-to-stringmap';
|
||||
import { EditorBackup, getEditorBackup } from '../utils/editor-backup';
|
||||
import { createMosaicArrangement, getVisibleMosaics } from '../utils/editors-mosaic-arrangement';
|
||||
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 { Bisector } from './bisect';
|
||||
import { DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
|
||||
import { getContent, isContentUnchanged } from './content';
|
||||
import { updateEditorTypeDefinitions } from './fetch-types';
|
||||
import { getLocalTypePathForVersion, updateEditorTypeDefinitions } from './fetch-types';
|
||||
import { ipcRendererManager } from './ipc';
|
||||
import { activateTheme } from './themes';
|
||||
|
||||
import {
|
||||
addLocalVersion,
|
||||
ElectronReleaseChannel,
|
||||
@@ -63,7 +67,6 @@ export class AppState {
|
||||
// -- Persisted settings ------------------
|
||||
@observable public version: string = defaultVersion;
|
||||
@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');
|
||||
@@ -71,37 +74,45 @@ 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 ];
|
||||
|| [ElectronReleaseChannel.stable, ElectronReleaseChannel.beta];
|
||||
@observable public statesToShow: Array<ElectronVersionState> =
|
||||
this.retrieve('statesToShow') as Array<ElectronVersionState>
|
||||
|| [ ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.unknown ];
|
||||
this.retrieve('statesToShow') as Array<ElectronVersionState>
|
||||
|| [ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.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 isMyGist: boolean = false;
|
||||
@observable public isPublishing: boolean = false;
|
||||
@observable public versions: Record<string, ElectronVersion> = arrayToStringMap(knownVersions);
|
||||
@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 isRunning = false;
|
||||
@observable public confirmationPromptLastResult: boolean | null = null;
|
||||
@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 isTourShowing: boolean = !localStorage.getItem('hasShownTour');
|
||||
|
||||
// -- Editor Values stored when we close the editor ------------------
|
||||
@@ -121,15 +132,18 @@ export class AppState {
|
||||
this.setVersion = this.setVersion.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.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));
|
||||
@@ -235,15 +249,26 @@ export class AppState {
|
||||
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;
|
||||
}
|
||||
|
||||
@action public toggleAddMonacoThemeDialog() {
|
||||
this.isThemeDialogShowing = !this.isThemeDialogShowing;
|
||||
}
|
||||
|
||||
@action public toggleAuthDialog() {
|
||||
this.isTokenDialogShowing = !this.isTokenDialogShowing;
|
||||
}
|
||||
|
||||
@action public toogleWarningDialog() {
|
||||
@action public toggleWarningDialog() {
|
||||
this.isWarningDialogShowing = !this.isWarningDialogShowing;
|
||||
|
||||
if (this.isWarningDialogShowing) {
|
||||
@@ -251,6 +276,18 @@ export class AppState {
|
||||
}
|
||||
}
|
||||
|
||||
@action public toggleBisectDialog() {
|
||||
this.isBisectDialogShowing = !this.isBisectDialogShowing;
|
||||
}
|
||||
|
||||
@action public toggleConfirmationPromptDialog() {
|
||||
this.isConfirmationPromptShowing = !this.isConfirmationPromptShowing;
|
||||
|
||||
if (this.isConfirmationPromptShowing) {
|
||||
this.confirmationPromptLastResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
@action public toggleSettings() {
|
||||
// We usually don't lose editor focus,
|
||||
// so you can still type. Let's force-blur.
|
||||
@@ -284,6 +321,14 @@ export class AppState {
|
||||
};
|
||||
}
|
||||
|
||||
@action public setConfirmationPromptTexts(input: WarningDialogTexts) {
|
||||
this.confirmationDialogTexts = {
|
||||
ok: 'Okay',
|
||||
cancel: 'Cancel',
|
||||
...input
|
||||
};
|
||||
}
|
||||
|
||||
@action public addLocalVersion(input: NpmVersion) {
|
||||
addLocalVersion(input);
|
||||
|
||||
@@ -291,12 +336,12 @@ export class AppState {
|
||||
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];
|
||||
@@ -330,12 +375,12 @@ export class AppState {
|
||||
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}`);
|
||||
@@ -359,12 +404,12 @@ export class AppState {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a version of Electron (and download it if necessary).
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
/**
|
||||
* 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);
|
||||
|
||||
@@ -382,21 +427,38 @@ export class AppState {
|
||||
// Should we update the editor?
|
||||
if (await isContentUnchanged(EditorId.main)) {
|
||||
const main = await getContent(EditorId.main, version);
|
||||
window.ElectronFiddle.app.setValues({ main }, false);
|
||||
window.ElectronFiddle.app.setEditorValues({ main });
|
||||
}
|
||||
|
||||
// Update TypeScript definitions
|
||||
updateEditorTypeDefinitions(version);
|
||||
const versionObject = this.versions[version];
|
||||
|
||||
if (versionObject.source === ElectronVersionSource.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}`);
|
||||
this.localTypeWatcher = fs.watch(typePath!, async () => {
|
||||
console.info(`TypeDefs: Noticed file change at ${typePath}. Updating editor typedefs.`);
|
||||
await updateEditorTypeDefinitions(versionObject);
|
||||
});
|
||||
} else {
|
||||
if (!!this.localTypeWatcher) {
|
||||
console.info(`TypeDefs: Switched to downloaded version ${version}. Unwatching local typedefs.`);
|
||||
this.localTypeWatcher.close();
|
||||
this.localTypeWatcher = undefined;
|
||||
}
|
||||
}
|
||||
await updateEditorTypeDefinitions(versionObject);
|
||||
|
||||
// Fetch new binaries, maybe?
|
||||
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 };
|
||||
@@ -435,7 +497,7 @@ export class AppState {
|
||||
let strData = data.toString();
|
||||
const { isNotPre, bypassBuffer } = options;
|
||||
|
||||
// Todo: This drops the first part of the buffer... is that fully expected?
|
||||
// TODO: This drops the first part of the buffer... is that fully expected?
|
||||
if (process.platform === 'win32' && bypassBuffer === false) {
|
||||
this.outputBuffer += strData;
|
||||
strData = this.outputBuffer;
|
||||
@@ -533,7 +595,7 @@ export class AppState {
|
||||
*/
|
||||
@action public showMosaic(id: MosaicId) {
|
||||
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
|
||||
this.setVisibleMosaics([ ...currentlyVisible, id ]);
|
||||
this.setVisibleMosaics([...currentlyVisible, id]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -548,6 +610,7 @@ export class AppState {
|
||||
this.isTourShowing = false;
|
||||
this.isConsoleShowing = false;
|
||||
this.isAddVersionDialogShowing = false;
|
||||
this.isThemeDialogShowing = false;
|
||||
|
||||
if (additionalOptions) {
|
||||
for (const key in additionalOptions) {
|
||||
|
||||
@@ -2,12 +2,11 @@ import * as fsExtraType from 'fs-extra';
|
||||
import * as pathType from 'path';
|
||||
|
||||
import { EditorValues } from '../interfaces';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { fancyImport } from '../utils/import';
|
||||
|
||||
/**
|
||||
* Returns expected content for a given name. Currently synchronous,
|
||||
* but probably shouldn't be.
|
||||
* Returns expected content for a given name.
|
||||
*
|
||||
* @param {string} name
|
||||
* @returns {Promise<EditorValues>}
|
||||
@@ -41,6 +40,7 @@ export async function getTemplateValues(name: string): Promise<EditorValues> {
|
||||
return {
|
||||
renderer: await getFile(RENDERER_JS_NAME),
|
||||
main: await getFile(MAIN_JS_NAME),
|
||||
html: await getFile(INDEX_HTML_NAME)
|
||||
html: await getFile(INDEX_HTML_NAME),
|
||||
preload: await getFile(PRELOAD_JS_NAME)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,13 +45,14 @@ export function getDefaultVersion(
|
||||
* Return the release channel for a given input
|
||||
* version.
|
||||
*
|
||||
* @param {NpmVersion} input
|
||||
* @param {NpmVersion | string} input
|
||||
* @returns {ElectronReleaseChannel}
|
||||
*/
|
||||
export function getReleaseChannel(
|
||||
input: NpmVersion
|
||||
input: NpmVersion | string
|
||||
): ElectronReleaseChannel {
|
||||
const tag = input.version || '';
|
||||
|
||||
const tag = (typeof input === 'string') ? input : (input.version || '');
|
||||
|
||||
if (tag.includes('beta')) {
|
||||
return ElectronReleaseChannel.beta;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const INDEX_HTML_NAME = 'index.html';
|
||||
export const MAIN_JS_NAME = 'main.js';
|
||||
export const RENDERER_JS_NAME = 'renderer.js';
|
||||
export const PRELOAD_JS_NAME = 'preload.js';
|
||||
export const PACKAGE_NAME = 'package.json';
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* a debounced version below.
|
||||
*/
|
||||
function _updateEditorLayout() {
|
||||
const { main, renderer, html } = window.ElectronFiddle.editors;
|
||||
|
||||
if (main) main.layout();
|
||||
if (renderer) renderer.layout();
|
||||
if (html) html.layout();
|
||||
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
|
||||
if (window.ElectronFiddle.editors[key]) {
|
||||
window.ElectronFiddle.editors[key].layout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let handle: number;
|
||||
|
||||
@@ -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>
|
||||
@@ -14,7 +14,7 @@ app.on('ready', () => {
|
||||
win = null
|
||||
})
|
||||
|
||||
let view = new BrowserView({
|
||||
const view = new BrowserView({
|
||||
webPreferences: {
|
||||
nodeIntegration: false
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// the result.
|
||||
//
|
||||
// For more info, see:
|
||||
// https://electronjs.org/docs/api/contentTracing
|
||||
// https://electronjs.org/docs/api/content-tracing
|
||||
|
||||
const { app, contentTracing } = require('electron')
|
||||
|
||||
|
||||
@@ -18,6 +18,6 @@ app.on('ready', () => {
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
mainWindow.webContents.on('crashed', () => {
|
||||
console.log(`Window crashed!`)
|
||||
console.log('Window crashed!')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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}`)
|
||||
})
|
||||
|
||||
@@ -22,13 +22,13 @@ app.on('ready', () => {
|
||||
// CommandOrControl which represents Command on macOS and Control on
|
||||
// Linux and Windows to define some accelerators.
|
||||
globalShortcut.register('CommandOrControl+Y', () => {
|
||||
console.log(`The global shortkey was pressed!`)
|
||||
console.log('The global shortkey was pressed!')
|
||||
})
|
||||
|
||||
// It supports "special names". Check out the API documentation for a full
|
||||
// list.
|
||||
globalShortcut.register('VolumeUp', () => console.log(`Turn it up!`))
|
||||
globalShortcut.register('VolumeDown', () => console.log(`Turn it down!`))
|
||||
globalShortcut.register('VolumeUp', () => console.log('Turn it up!'))
|
||||
globalShortcut.register('VolumeDown', () => console.log('Turn it down!'))
|
||||
})
|
||||
|
||||
app.on('will-quit', () => {
|
||||
|
||||
@@ -20,21 +20,21 @@ app.on('ready', () => {
|
||||
if (Notification.isSupported()) {
|
||||
const notification = new Notification({
|
||||
title: 'Hello World!',
|
||||
subtitle: `Nice to see you`,
|
||||
subtitle: 'Nice to see you',
|
||||
body: 'Are you having a good day?',
|
||||
hasReply: true
|
||||
})
|
||||
|
||||
notification.on('show', () => console.log(`Notification shown`))
|
||||
notification.on('click', () => console.log(`Notification clicked`))
|
||||
notification.on('close', () => console.log(`Notification closed`))
|
||||
notification.on('show', () => console.log('Notification shown'))
|
||||
notification.on('click', () => console.log('Notification clicked'))
|
||||
notification.on('close', () => console.log('Notification closed'))
|
||||
notification.on('reply', (event, reply) => {
|
||||
console.log(`Reply: ${reply}`)
|
||||
})
|
||||
|
||||
notification.show()
|
||||
} else {
|
||||
console.log(`Hm, are notifications supported on this system?`)
|
||||
console.log('Hm, are notifications supported on this system?')
|
||||
}
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
@@ -19,10 +19,10 @@ app.on('ready', () => {
|
||||
})
|
||||
|
||||
powerMonitor.on('on-ac', () => {
|
||||
console.log(`We're on AC power`)
|
||||
console.log('We\'re on AC power')
|
||||
})
|
||||
|
||||
powerMonitor.on('on-battery', () => {
|
||||
console.log(`We're on battery power`)
|
||||
console.log('We\'re on battery power')
|
||||
})
|
||||
})
|
||||
|
||||
+19
-12
@@ -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');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,25 +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_WARNING_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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+29
-56
@@ -15,7 +15,22 @@ jest.mock('fs-extra', () => ({
|
||||
existsSync: jest.fn()
|
||||
}));
|
||||
|
||||
const mockTarget = {
|
||||
webContents: {
|
||||
send: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
describe('files', () => {
|
||||
beforeEach(() => {
|
||||
(dialog.showOpenDialog as jest.Mock<any>).mockResolvedValue({
|
||||
filePaths: [ 'my/fake/path' ]
|
||||
});
|
||||
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
|
||||
|
||||
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
|
||||
});
|
||||
|
||||
describe('setupFileListeners()', () => {
|
||||
|
||||
it('sets up the listener', () => {
|
||||
@@ -31,8 +46,8 @@ describe('files', () => {
|
||||
});
|
||||
|
||||
describe('showOpenDialog', () => {
|
||||
it('tries to open an "open" dialog', () => {
|
||||
showOpenDialog();
|
||||
it('tries to open an "open" dialog', async () => {
|
||||
await showOpenDialog();
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
|
||||
@@ -44,22 +59,10 @@ describe('files', () => {
|
||||
});
|
||||
|
||||
it('notifies the main window of the event', async () => {
|
||||
showOpenDialog();
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
const mockTarget = {
|
||||
webContents: {
|
||||
send: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
|
||||
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
|
||||
|
||||
await cb('/my/fake/path');
|
||||
expect(mockTarget.webContents.send).toHaveBeenCalledTimes(1);
|
||||
await cb('');
|
||||
await showOpenDialog();
|
||||
|
||||
expect(mockTarget.webContents.send).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -92,12 +95,11 @@ describe('files', () => {
|
||||
});
|
||||
|
||||
it('handles not getting a path returned', async (done) => {
|
||||
showSaveDialog();
|
||||
(dialog.showOpenDialog as jest.Mock<any>).mockResolvedValueOnce({
|
||||
filePaths: []
|
||||
});
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
|
||||
await cb();
|
||||
await showSaveDialog();
|
||||
|
||||
process.nextTick(() => {
|
||||
expect(fs.existsSync).toHaveBeenCalledTimes(0);
|
||||
@@ -106,68 +108,39 @@ describe('files', () => {
|
||||
});
|
||||
|
||||
it('ensures that the target is empty on save', async () => {
|
||||
showSaveDialog();
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
const mockTarget = {
|
||||
webContents: {
|
||||
send: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
(dialog.showMessageBox as jest.Mock<any>).mockImplementation(async () => true);
|
||||
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
|
||||
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
|
||||
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
|
||||
|
||||
await cb('/my/fake/path');
|
||||
await showSaveDialog();
|
||||
|
||||
expect(dialog.showMessageBox).toHaveBeenCalled();
|
||||
expect(mockTarget.webContents.send).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not overwrite files without consent', async () => {
|
||||
showSaveDialog();
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
const mockTarget = {
|
||||
webContents: {
|
||||
send: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
(dialog.showMessageBox as jest.Mock<any>).mockImplementation(async () => false);
|
||||
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
|
||||
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
|
||||
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
|
||||
|
||||
await cb('/my/fake/path');
|
||||
await showSaveDialog();
|
||||
|
||||
expect(dialog.showMessageBox).toHaveBeenCalled();
|
||||
expect(mockTarget.webContents.send).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('does not overwrite files if an error happens', async () => {
|
||||
showSaveDialog();
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
const mockTarget = {
|
||||
webContents: {
|
||||
send: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
(dialog.showMessageBox as jest.Mock<any>).mockImplementation(async () => {
|
||||
throw new Error('Nope');
|
||||
});
|
||||
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
|
||||
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
|
||||
|
||||
|
||||
let errored = false;
|
||||
|
||||
try {
|
||||
await cb('/fake/path');
|
||||
await showSaveDialog();
|
||||
} catch (error) {
|
||||
errored = error;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ jest.mock('../../src/utils/check-first-run', () => ({
|
||||
isFirstRun: jest.fn()
|
||||
}));
|
||||
|
||||
const mockDialogResponse = {
|
||||
response: 1
|
||||
};
|
||||
|
||||
describe('first-run', () => {
|
||||
const oldDefaultApp = process.defaultApp;
|
||||
const oldPlatform = process.platform;
|
||||
@@ -15,6 +19,8 @@ describe('first-run', () => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'darwin'
|
||||
});
|
||||
|
||||
(dialog.showMessageBox as jest.Mock<any>).mockResolvedValue(mockDialogResponse);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -24,7 +30,7 @@ describe('first-run', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.defaultApp = oldDefaultApp;
|
||||
(process as any).defaultApp = oldDefaultApp;
|
||||
});
|
||||
|
||||
describe('onFirstRunMaybe()', () => {
|
||||
@@ -44,7 +50,7 @@ describe('first-run', () => {
|
||||
|
||||
it(`doesn't run unless required (dev mode)`, () => {
|
||||
(isFirstRun as jest.Mock).mockReturnValueOnce(true);
|
||||
process.defaultApp = true;
|
||||
(process as any).defaultApp = true;
|
||||
(app.isInApplicationsFolder as jest.Mock).mockReturnValue(false);
|
||||
|
||||
onFirstRunMaybe();
|
||||
@@ -65,21 +71,16 @@ describe('first-run', () => {
|
||||
expect(dialog.showMessageBox).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it(`moves the app when requested to do so`, () => {
|
||||
(isFirstRun as jest.Mock).mockReturnValueOnce(true);
|
||||
it(`moves the app when requested to do so`, async () => {
|
||||
(isFirstRun as jest.Mock).mockReturnValue(true);
|
||||
(app.isInApplicationsFolder as jest.Mock).mockReturnValue(false);
|
||||
|
||||
onFirstRunMaybe();
|
||||
|
||||
const call = (dialog.showMessageBox as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
|
||||
cb(1);
|
||||
|
||||
mockDialogResponse.response = 1;
|
||||
await onFirstRunMaybe();
|
||||
expect(app.moveToApplicationsFolder).toHaveBeenCalledTimes(0);
|
||||
|
||||
cb(0);
|
||||
|
||||
mockDialogResponse.response = 0;
|
||||
await onFirstRunMaybe();
|
||||
expect(app.moveToApplicationsFolder).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
+17
-2
@@ -12,6 +12,7 @@ jest.mock('../../src/main/ipc');
|
||||
describe('menu', () => {
|
||||
beforeEach(() => {
|
||||
(electron.app.getName as any).mockReturnValue('Electron Fiddle');
|
||||
(electron.dialog.showOpenDialog as any).mockReturnValue(Promise.resolve({}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -77,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', () => {
|
||||
@@ -173,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']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -181,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];
|
||||
});
|
||||
|
||||
+5
-2
@@ -1,10 +1,12 @@
|
||||
import { FileManager } from './file-manager';
|
||||
import { RemoteLoader } from './remote-loader';
|
||||
import { RunnerMock } from './runner';
|
||||
|
||||
export class AppMock {
|
||||
public setupUnsavedOnChangeListener = jest.fn();
|
||||
public setValues = jest.fn();
|
||||
public getValues = jest.fn(() => ({
|
||||
public replaceFiddle = jest.fn();
|
||||
public setEditorValues = jest.fn();
|
||||
public getEditorValues = jest.fn(() => ({
|
||||
main: 'main-content',
|
||||
renderer: 'renderer-content',
|
||||
html: 'html-content'
|
||||
@@ -18,6 +20,7 @@ export class AppMock {
|
||||
|
||||
public fileManager = new FileManager();
|
||||
public runner = new RunnerMock();
|
||||
public remoteLoader = new RemoteLoader();
|
||||
|
||||
public monaco = {
|
||||
editor: {
|
||||
|
||||
@@ -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()
|
||||
@@ -157,8 +156,8 @@ const electronMock = {
|
||||
writeImage: jest.fn()
|
||||
},
|
||||
dialog: {
|
||||
showOpenDialog: jest.fn(),
|
||||
showMessageBox: jest.fn()
|
||||
showOpenDialog: jest.fn(() => Promise.resolve({})),
|
||||
showMessageBox: jest.fn(() => Promise.resolve({}))
|
||||
},
|
||||
ipcMain: new MockIPC(),
|
||||
ipcRenderer: new MockIPC(),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export class FileManager {
|
||||
public saveToTemp = jest.fn(() => '/mock/temp/dir');
|
||||
public setFiddle = jest.fn();
|
||||
public cleanup = jest.fn();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export class RemoteLoader {
|
||||
public loadFiddleFromGist = jest.fn();
|
||||
public loadFiddleFromElectronExample = jest.fn();
|
||||
public fetchGistAndLoad = jest.fn();
|
||||
}
|
||||
@@ -4,4 +4,6 @@ export class MockState {
|
||||
@observable public isWarningDialogShowing = false;
|
||||
@observable public gistId = '';
|
||||
@observable public closedPanels = {};
|
||||
@observable public isConsoleShowing = true;
|
||||
@observable public output = [];
|
||||
}
|
||||
|
||||
+195
-83
@@ -1,10 +1,13 @@
|
||||
import { App } from '../../src/renderer/app';
|
||||
import { AppState } from '../../src/renderer/state';
|
||||
import { EditorBackup } from '../../src/utils/editor-backup';
|
||||
import { ElectronFiddleMock } from '../mocks/electron-fiddle';
|
||||
import { MockState } from '../mocks/state';
|
||||
import { overridePlatform, resetPlatform } from '../utils';
|
||||
|
||||
jest.mock('../../src/renderer/file-manager', () => require('../mocks/file-manager'));
|
||||
jest.mock('../../src/renderer/file-manager', () =>
|
||||
require('../mocks/file-manager')
|
||||
);
|
||||
jest.mock('../../src/renderer/state', () => ({
|
||||
appState: {
|
||||
theme: 'defaultDark',
|
||||
@@ -18,8 +21,8 @@ jest.mock('../../src/renderer/components/header', () => ({
|
||||
jest.mock('../../src/renderer/components/dialogs', () => ({
|
||||
Dialogs: () => 'Dialogs;'
|
||||
}));
|
||||
jest.mock('../../src/renderer/components/editors', () => ({
|
||||
Editors: () => 'Editors;'
|
||||
jest.mock('../../src/renderer/components/output-editors-wrapper', () => ({
|
||||
OutputEditorsWrapper: () => 'OutputEditorsWrapper;'
|
||||
}));
|
||||
|
||||
describe('Editors component', () => {
|
||||
@@ -33,11 +36,11 @@ describe('Editors component', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const app = new App();
|
||||
const result = await app.setup() as HTMLDivElement;
|
||||
const result = (await app.setup()) as HTMLDivElement;
|
||||
app.setupUnsavedOnChangeListener = jest.fn();
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(result.innerHTML).toBe('Header;Dialogs;Editors;');
|
||||
expect(result.innerHTML).toBe('Dialogs;Header;OutputEditorsWrapper;');
|
||||
expect(app.setupUnsavedOnChangeListener).toHaveBeenCalled();
|
||||
|
||||
jest.useRealTimers();
|
||||
@@ -54,19 +57,19 @@ describe('Editors component', () => {
|
||||
|
||||
it('does not create a touch bar manager on Windows and Linux', () => {
|
||||
overridePlatform('win32');
|
||||
expect((new App()).touchBarManager).toBeFalsy();
|
||||
expect(new App().touchBarManager).toBeFalsy();
|
||||
|
||||
overridePlatform('linux');
|
||||
expect((new App()).touchBarManager).toBeFalsy();
|
||||
expect(new App().touchBarManager).toBeFalsy();
|
||||
|
||||
resetPlatform();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getValues()', () => {
|
||||
describe('getEditorValues()', () => {
|
||||
it('gets values', async () => {
|
||||
const app = new App();
|
||||
const b = await app.getValues({});
|
||||
const b = await app.getEditorValues({});
|
||||
|
||||
expect(b.html).toBe('editor-value');
|
||||
expect(b.main).toBe('editor-value');
|
||||
@@ -81,7 +84,7 @@ describe('Editors component', () => {
|
||||
(window as any).ElectronFiddle.editors.renderer = null;
|
||||
|
||||
const app = new App();
|
||||
const result = await app.getValues({});
|
||||
const result = await app.getEditorValues({});
|
||||
|
||||
expect(result.html).toBe('');
|
||||
expect(result.main).toBe('');
|
||||
@@ -96,7 +99,7 @@ describe('Editors component', () => {
|
||||
const app = new App();
|
||||
let threw = false;
|
||||
try {
|
||||
await app.getValues({});
|
||||
await app.getEditorValues({});
|
||||
} catch (error) {
|
||||
threw = true;
|
||||
}
|
||||
@@ -105,61 +108,57 @@ describe('Editors component', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setValues()', () => {
|
||||
it('attempts to set values', () => {
|
||||
describe('replaceFiddle()', () => {
|
||||
it('sets editor values and source info', (done) => {
|
||||
const app = new App();
|
||||
app.setValues({
|
||||
(app.state as Partial<AppState>) = new MockState();
|
||||
app.state.isUnsaved = false;
|
||||
app.setEditorValues = jest.fn();
|
||||
|
||||
const editorValues = {
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
});
|
||||
renderer: 'renderer-value',
|
||||
preload: ''
|
||||
};
|
||||
|
||||
expect((window as any).ElectronFiddle.editors.html.setValue)
|
||||
.toHaveBeenCalledWith('html-value');
|
||||
expect((window as any).ElectronFiddle.editors.main.setValue)
|
||||
.toHaveBeenCalledWith('main-value');
|
||||
expect((window as any).ElectronFiddle.editors.renderer.setValue)
|
||||
.toHaveBeenCalledWith('renderer-value');
|
||||
app.replaceFiddle(editorValues, {
|
||||
gistId: 'gistId',
|
||||
templateName: 'templateName',
|
||||
filePath: 'localPath'
|
||||
})
|
||||
.then(() => {
|
||||
expect(app.setEditorValues).toHaveBeenCalledWith(editorValues);
|
||||
expect(app.state.gistId).toBe('gistId');
|
||||
expect(app.state.templateName).toBe('templateName');
|
||||
expect(app.state.localPath).toBe('localPath');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('attempts to set values for closed editors', () => {
|
||||
const oldMainEditor = window.ElectronFiddle.editors.main;
|
||||
delete window.ElectronFiddle.editors.main;
|
||||
|
||||
it('unsets state of previous source when called', (done) => {
|
||||
const app = new App();
|
||||
(app.state.closedPanels as any).main = { model: { setValue: jest.fn() } };
|
||||
app.setValues({
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
});
|
||||
|
||||
expect((app.state.closedPanels.main as EditorBackup)!.model!.setValue)
|
||||
.toHaveBeenCalledWith('main-value');
|
||||
|
||||
window.ElectronFiddle.editors.main = oldMainEditor;
|
||||
});
|
||||
|
||||
it('warns when the contents are unsaved, does not proceed if denied', (done) => {
|
||||
const app = new App();
|
||||
(app.state as any) = new MockState();
|
||||
(app.state as Partial<AppState>) = new MockState();
|
||||
app.state.isUnsaved = true;
|
||||
app.state.localPath = '/fake/path';
|
||||
app.state.setWarningDialogTexts = jest.fn();
|
||||
app.setEditorValues = jest.fn();
|
||||
|
||||
app.setValues({
|
||||
const editorValues = {
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
}).then((result) => {
|
||||
expect(result).toBe(true);
|
||||
expect((window as any).ElectronFiddle.editors.html.setValue)
|
||||
.toHaveBeenCalledWith('html-value');
|
||||
expect((window as any).ElectronFiddle.editors.main.setValue)
|
||||
.toHaveBeenCalledWith('main-value');
|
||||
expect((window as any).ElectronFiddle.editors.renderer.setValue)
|
||||
.toHaveBeenCalledWith('renderer-value');
|
||||
};
|
||||
|
||||
done();
|
||||
});
|
||||
expect(app.state.localPath).toBe('/fake/path');
|
||||
|
||||
app.replaceFiddle(editorValues, {
|
||||
gistId: 'gistId',
|
||||
})
|
||||
.then(() => {
|
||||
expect(app.state.localPath).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(app.state.isWarningDialogShowing).toBe(true);
|
||||
@@ -168,32 +167,135 @@ describe('Editors component', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('warns when the contents are unsaved, does proceed if allowed', (done) => {
|
||||
it('marks the new Fiddle as Saved', (done) => {
|
||||
const app = new App();
|
||||
(app.state as any) = new MockState();
|
||||
app.state.isUnsaved = true;
|
||||
(app.state as Partial<AppState>) = new MockState();
|
||||
app.state.isUnsaved = false;
|
||||
app.setupUnsavedOnChangeListener = jest.fn();
|
||||
app.setEditorValues = jest.fn();
|
||||
|
||||
app.setValues({
|
||||
const editorValues = {
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
}).then((result) => {
|
||||
expect(result).toBe(false);
|
||||
expect((window as any).ElectronFiddle.editors.html.setValue)
|
||||
.toHaveBeenCalledTimes(0);
|
||||
expect((window as any).ElectronFiddle.editors.main.setValue)
|
||||
.toHaveBeenCalledTimes(0);
|
||||
expect((window as any).ElectronFiddle.editors.renderer.setValue)
|
||||
.toHaveBeenCalledTimes(0);
|
||||
};
|
||||
|
||||
done();
|
||||
app.replaceFiddle(editorValues, {
|
||||
gistId: 'gistId',
|
||||
templateName: 'templateName',
|
||||
filePath: 'localPath'
|
||||
})
|
||||
.then(() => {
|
||||
expect(app.state.isUnsaved).toBe(false);
|
||||
expect(app.setupUnsavedOnChangeListener).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
describe('when current Fiddle is unsaved and prompt appears', () => {
|
||||
it('takes no action if prompt is rejected', (done) => {
|
||||
const app = new App();
|
||||
(app.state as Partial<AppState>) = new MockState();
|
||||
app.state.isUnsaved = true;
|
||||
app.state.setWarningDialogTexts = jest.fn();
|
||||
app.setEditorValues = jest.fn();
|
||||
|
||||
expect(app.state.localPath).toBeUndefined();
|
||||
expect(app.state.gistId).toBe('');
|
||||
expect(app.state.templateName).toBeUndefined();
|
||||
|
||||
app.replaceFiddle({}, {
|
||||
gistId: 'gistId',
|
||||
templateName: 'templateName',
|
||||
filePath: 'localPath'
|
||||
})
|
||||
.then(() => {
|
||||
expect(app.setEditorValues).not.toHaveBeenCalled();
|
||||
expect(app.state.localPath).toBeUndefined();
|
||||
expect(app.state.gistId).toBe('');
|
||||
expect(app.state.templateName).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(app.state.isWarningDialogShowing).toBe(true);
|
||||
app.state.warningDialogLastResult = false;
|
||||
app.state.isWarningDialogShowing = false;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(app.state.isWarningDialogShowing).toBe(true);
|
||||
app.state.isWarningDialogShowing = false;
|
||||
it('sets editor values and source info if prompt is accepted', (done) => {
|
||||
const app = new App();
|
||||
(app.state as Partial<AppState>) = new MockState();
|
||||
app.state.isUnsaved = true;
|
||||
app.state.setWarningDialogTexts = jest.fn();
|
||||
app.setEditorValues = jest.fn();
|
||||
|
||||
const editorValues = {
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
};
|
||||
|
||||
app.replaceFiddle(editorValues, {
|
||||
gistId: 'gistId',
|
||||
templateName: 'templateName',
|
||||
filePath: 'localPath'
|
||||
})
|
||||
.then(() => {
|
||||
expect(app.setEditorValues).toHaveBeenCalledWith(editorValues);
|
||||
expect(app.state.gistId).toBe('gistId');
|
||||
expect(app.state.templateName).toBe('templateName');
|
||||
expect(app.state.localPath).toBe('localPath');
|
||||
done();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(app.state.isWarningDialogShowing).toBe(true);
|
||||
app.state.warningDialogLastResult = true;
|
||||
app.state.isWarningDialogShowing = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEditorValues()', () => {
|
||||
it('attempts to set values', () => {
|
||||
const app = new App();
|
||||
app.setEditorValues({
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
});
|
||||
|
||||
expect(
|
||||
(window as any).ElectronFiddle.editors.html.setValue
|
||||
).toHaveBeenCalledWith('html-value');
|
||||
expect(
|
||||
(window as any).ElectronFiddle.editors.main.setValue
|
||||
).toHaveBeenCalledWith('main-value');
|
||||
expect(
|
||||
(window as any).ElectronFiddle.editors.renderer.setValue
|
||||
).toHaveBeenCalledWith('renderer-value');
|
||||
});
|
||||
|
||||
it('attempts to set values for closed editors', () => {
|
||||
const oldMainEditor = window.ElectronFiddle.editors.main;
|
||||
delete window.ElectronFiddle.editors.main;
|
||||
|
||||
const app = new App();
|
||||
(app.state.closedPanels as any).main = { model: { setValue: jest.fn() } };
|
||||
app.setEditorValues({
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value',
|
||||
});
|
||||
|
||||
expect(
|
||||
(app.state.closedPanels.main as EditorBackup)!.model!.setValue
|
||||
).toHaveBeenCalledWith('main-value');
|
||||
|
||||
window.ElectronFiddle.editors.main = oldMainEditor;
|
||||
});
|
||||
|
||||
it('throws if the Fiddle object is not present', async () => {
|
||||
(window as any).ElectronFiddle = null;
|
||||
@@ -201,26 +303,35 @@ describe('Editors component', () => {
|
||||
const app = new App();
|
||||
let threw = false;
|
||||
try {
|
||||
await app.setValues({ html: '', main: '', renderer: ''});
|
||||
await app.setEditorValues({ html: '', main: '', renderer: '' });
|
||||
} catch (error) {
|
||||
threw = true;
|
||||
}
|
||||
|
||||
expect(threw).toBe(true);
|
||||
});
|
||||
|
||||
it('does not set a value if none passed in', async () => {
|
||||
const app = new App();
|
||||
await app.setEditorValues({
|
||||
html: 'html-value',
|
||||
main: 'main-value'
|
||||
});
|
||||
|
||||
expect(
|
||||
(window as any).ElectronFiddle.editors.renderer.setValue
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setupUnsavedOnChangeListener()', () => {
|
||||
it('listens for model change events', async () => {
|
||||
const app = new App();
|
||||
|
||||
await app.setValues({
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
});
|
||||
app.setupUnsavedOnChangeListener();
|
||||
|
||||
const fn = window.ElectronFiddle.editors!.renderer!.onDidChangeModelContent;
|
||||
const fn = window.ElectronFiddle.editors!.renderer!
|
||||
.onDidChangeModelContent;
|
||||
const call = (fn as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[0];
|
||||
|
||||
@@ -237,16 +348,16 @@ describe('Editors component', () => {
|
||||
const app = new App();
|
||||
app.setupResizeListener();
|
||||
|
||||
expect(window.addEventListener)
|
||||
.toHaveBeenCalled();
|
||||
expect((window.addEventListener as jest.Mock).mock.calls[0][0])
|
||||
.toBe('resize');
|
||||
expect(window.addEventListener).toHaveBeenCalled();
|
||||
expect((window.addEventListener as jest.Mock).mock.calls[0][0]).toBe(
|
||||
'resize'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setupTheme()', () => {
|
||||
it(`adds the current theme's css to the document`, async () => {
|
||||
document.head!.innerHTML = '<style id="fiddle-theme"></style>';
|
||||
document.head!.innerHTML = "<style id='fiddle-theme'></style>";
|
||||
|
||||
const app = new App();
|
||||
await app.setupTheme();
|
||||
@@ -272,8 +383,9 @@ describe('Editors component', () => {
|
||||
--error-color: #df3434;
|
||||
--fonts-common: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
</style>`.replace(/ /gm, ''));
|
||||
// tslint:enable:max-line-length
|
||||
</style>`.replace(/ /gm, '')
|
||||
);
|
||||
// tslint:enable:max-line-length
|
||||
});
|
||||
|
||||
it('removes the dark theme option if required', async () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BinaryManager } from '../../src/renderer/binary';
|
||||
import { USER_DATA_PATH } from '../../src/renderer/constants';
|
||||
import { overridePlatform, resetPlatform } from '../utils';
|
||||
|
||||
import * as path from 'path';
|
||||
@@ -57,6 +58,55 @@ describe('binary', () => {
|
||||
await binaryManager.remove('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');
|
||||
expect(fs.remove).toHaveBeenCalled();
|
||||
expect(binaryManager.removeTypeDefsForVersion).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('retries typedef cleanup upon failure', async () => {
|
||||
const fs = require('fs-extra');
|
||||
binaryManager.removeTypeDefsForVersion = jest.fn().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);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
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()', () => {
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -27,6 +27,16 @@ exports[`EditorDropdown component renders 1`] = `
|
||||
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={false}
|
||||
icon="eye-open"
|
||||
@@ -98,6 +108,16 @@ exports[`EditorDropdown component renders the extra button if the FIDDLE_DOCS_DE
|
||||
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={false}
|
||||
icon="eye-open"
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -1,23 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Dialogs component renders initially without visible dialogs 1`] = `
|
||||
<div
|
||||
className="dialogs"
|
||||
key="dialogs"
|
||||
>
|
||||
<WarningDialog
|
||||
appState={
|
||||
Object {
|
||||
"isAddVersionDialogShowing": false,
|
||||
"isSettingsShowing": false,
|
||||
"isTokenDialogShowing": false,
|
||||
"warningDialogTexts": Object {
|
||||
"cancel": "",
|
||||
"label": "",
|
||||
"ok": "",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -56,7 +56,7 @@ exports[`Editors component renders 1`] = `
|
||||
maximize
|
||||
</desc>
|
||||
<path
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -84,7 +84,7 @@ exports[`Editors component renders 1`] = `
|
||||
cross
|
||||
</desc>
|
||||
<path
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -229,7 +229,7 @@ exports[`Editors component renders 1`] = `
|
||||
maximize
|
||||
</desc>
|
||||
<path
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -257,7 +257,7 @@ exports[`Editors component renders 1`] = `
|
||||
cross
|
||||
</desc>
|
||||
<path
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -355,7 +355,7 @@ exports[`Editors component renders 1`] = `
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-tile"
|
||||
key="html"
|
||||
key="preload"
|
||||
style={
|
||||
Object {
|
||||
"bottom": "50%",
|
||||
@@ -364,6 +364,179 @@ exports[`Editors component renders 1`] = `
|
||||
"top": "0%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="mosaic-window mosaic-drop-target preload"
|
||||
>
|
||||
<div
|
||||
className="mosaic-window-toolbar draggable"
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<h5>
|
||||
Preload (preload.js)
|
||||
</h5>
|
||||
</div>
|
||||
<div />
|
||||
<div
|
||||
className="mosaic-controls"
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-small"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-maximize"
|
||||
icon="maximize"
|
||||
>
|
||||
<svg
|
||||
data-icon="maximize"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
>
|
||||
<desc>
|
||||
maximize
|
||||
</desc>
|
||||
<path
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="bp3-button bp3-small"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-cross"
|
||||
icon="cross"
|
||||
>
|
||||
<svg
|
||||
data-icon="cross"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
>
|
||||
<desc>
|
||||
cross
|
||||
</desc>
|
||||
<path
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-window-body"
|
||||
>
|
||||
Editor
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-window-body-overlay"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="mosaic-window-additional-actions-bar"
|
||||
/>
|
||||
<div
|
||||
className="mosaic-preview"
|
||||
>
|
||||
<div
|
||||
className="mosaic-window-toolbar"
|
||||
>
|
||||
<div
|
||||
className="mosaic-window-title"
|
||||
>
|
||||
Preload (preload.js)
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-window-body"
|
||||
>
|
||||
<h4>
|
||||
Preload (preload.js)
|
||||
</h4>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-application"
|
||||
icon="application"
|
||||
>
|
||||
<svg
|
||||
data-icon="application"
|
||||
height={72}
|
||||
viewBox="0 0 20 20"
|
||||
width={72}
|
||||
>
|
||||
<desc>
|
||||
application
|
||||
</desc>
|
||||
<path
|
||||
d="M3.5 9h9c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-9c-.28 0-.5.22-.5.5s.22.5.5.5zm0 2h5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-5c-.28 0-.5.22-.5.5s.22.5.5.5zM19 1H1c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm-1 16H2V6h16v11zM3.5 13h7c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-7c-.28 0-.5.22-.5.5s.22.5.5.5z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="drop-target-container"
|
||||
>
|
||||
<div
|
||||
className="drop-target top"
|
||||
/>
|
||||
<div
|
||||
className="drop-target bottom"
|
||||
/>
|
||||
<div
|
||||
className="drop-target left"
|
||||
/>
|
||||
<div
|
||||
className="drop-target right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-split -column"
|
||||
onMouseDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"bottom": "0%",
|
||||
"left": "50%",
|
||||
"right": "0%",
|
||||
"top": "50%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="mosaic-split-line"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-tile"
|
||||
key="html"
|
||||
style={
|
||||
Object {
|
||||
"bottom": "25%",
|
||||
"left": "50%",
|
||||
"right": "0%",
|
||||
"top": "50%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="mosaic-window mosaic-drop-target html"
|
||||
@@ -402,7 +575,7 @@ exports[`Editors component renders 1`] = `
|
||||
maximize
|
||||
</desc>
|
||||
<path
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -430,7 +603,7 @@ exports[`Editors component renders 1`] = `
|
||||
cross
|
||||
</desc>
|
||||
<path
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -518,7 +691,7 @@ exports[`Editors component renders 1`] = `
|
||||
"bottom": "0%",
|
||||
"left": "50%",
|
||||
"right": "0%",
|
||||
"top": "50%",
|
||||
"top": "75%",
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -534,7 +707,7 @@ exports[`Editors component renders 1`] = `
|
||||
"bottom": "0%",
|
||||
"left": "50%",
|
||||
"right": "0%",
|
||||
"top": "50%",
|
||||
"top": "75%",
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -575,7 +748,7 @@ exports[`Editors component renders 1`] = `
|
||||
home
|
||||
</desc>
|
||||
<path
|
||||
d="M2 10v5c0 .55.45 1 1 1h3v-5h4v5h3c.55 0 1-.45 1-1v-5L8 4l-6 6zm13.71-2.71L14 5.59V2c0-.55-.45-1-1-1s-1 .45-1 1v1.59L8.71.29C8.53.11 8.28 0 8 0s-.53.11-.71.29l-7 7a1.003 1.003 0 0 0 1.42 1.42L8 2.41l6.29 6.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71z"
|
||||
d="M2 10v5c0 .55.45 1 1 1h3v-5h4v5h3c.55 0 1-.45 1-1v-5L8 4l-6 6zm13.71-2.71L14 5.59V2c0-.55-.45-1-1-1s-1 .45-1 1v1.59L8.71.29C8.53.11 8.28 0 8 0s-.53.11-.71.29l-7 7a1.003 1.003 0 001.42 1.42L8 2.41l6.29 6.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -609,7 +782,7 @@ exports[`Editors component renders 1`] = `
|
||||
maximize
|
||||
</desc>
|
||||
<path
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -637,7 +810,7 @@ exports[`Editors component renders 1`] = `
|
||||
cross
|
||||
</desc>
|
||||
<path
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -715,7 +888,7 @@ exports[`Editors component renders 1`] = `
|
||||
play
|
||||
</desc>
|
||||
<path
|
||||
d="M12 8c0-.35-.19-.64-.46-.82l.01-.02-6-4-.01.02A.969.969 0 0 0 5 3c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1 .21 0 .39-.08.54-.18l.01.02 6-4-.01-.02c.27-.18.46-.47.46-.82z"
|
||||
d="M12 8c0-.35-.19-.64-.46-.82l.01-.02-6-4-.01.02A.969.969 0 005 3c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1 .21 0 .39-.08.54-.18l.01.02 6-4-.01-.02c.27-.18.46-.47.46-.82z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -869,8 +1042,12 @@ exports[`Editors component renders a toolbar 1`] = `
|
||||
},
|
||||
"second": Object {
|
||||
"direction": "column",
|
||||
"first": "html",
|
||||
"second": "docsDemo",
|
||||
"first": "preload",
|
||||
"second": Object {
|
||||
"direction": "column",
|
||||
"first": "html",
|
||||
"second": "docsDemo",
|
||||
},
|
||||
},
|
||||
},
|
||||
"setWarningDialogTexts": [Function],
|
||||
@@ -893,8 +1070,12 @@ exports[`Editors component renders a toolbar 1`] = `
|
||||
},
|
||||
"second": Object {
|
||||
"direction": "column",
|
||||
"first": "html",
|
||||
"second": "docsDemo",
|
||||
"first": "preload",
|
||||
"second": Object {
|
||||
"direction": "column",
|
||||
"first": "html",
|
||||
"second": "docsDemo",
|
||||
},
|
||||
},
|
||||
},
|
||||
"setWarningDialogTexts": [Function],
|
||||
|
||||
@@ -12,10 +12,6 @@ exports[`Header component renders 1`] = `
|
||||
appState={Object {}}
|
||||
key="commands"
|
||||
/>
|
||||
<output
|
||||
appState={Object {}}
|
||||
key="output"
|
||||
/>
|
||||
</header>
|
||||
<welcome-tour
|
||||
appState={Object {}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`Output component renders with output 1`] = `
|
||||
<div
|
||||
className="output showing"
|
||||
className="output"
|
||||
>
|
||||
<p
|
||||
key="1532704073130--0--0"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`ExecutionSettings component renders 1`] = `
|
||||
<div>
|
||||
<h2>
|
||||
Exection
|
||||
Execution
|
||||
</h2>
|
||||
<Blueprint3.Callout>
|
||||
These advanced settings control how Electron Fiddle executes your fiddles.
|
||||
|
||||
@@ -63,11 +63,19 @@ exports[`AppearanceSettings component renders 1`] = `
|
||||
</a>
|
||||
. The easiest way to get started is to clone one of the two existing themes and to add your own colors.
|
||||
</p>
|
||||
<p>
|
||||
Additionally, if you wish to import a Monaco Editor theme, pick your JSON file and Fiddle will attempt to import it.
|
||||
</p>
|
||||
<Blueprint3.Button
|
||||
icon="duplicate"
|
||||
onClick={[Function]}
|
||||
text="Create theme from current selection"
|
||||
/>
|
||||
<Blueprint3.Button
|
||||
icon="document-open"
|
||||
onClick={[Function]}
|
||||
text="Add a Monaco Editor theme"
|
||||
/>
|
||||
</Blueprint3.Callout>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CreditsSettings component renders only the menu if page unknown 1`] = `
|
||||
exports[`Settings component renders only the menu if page unknown 1`] = `
|
||||
<div
|
||||
className="settings"
|
||||
>
|
||||
@@ -72,7 +72,7 @@ exports[`CreditsSettings component renders only the menu if page unknown 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CreditsSettings component renders the Credits page after a click 1`] = `
|
||||
exports[`Settings component renders the Credits page after a click 1`] = `
|
||||
<div
|
||||
className="settings"
|
||||
>
|
||||
@@ -151,7 +151,7 @@ exports[`CreditsSettings component renders the Credits page after a click 1`] =
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CreditsSettings component renders the Electron page after a click 1`] = `
|
||||
exports[`Settings component renders the Electron page after a click 1`] = `
|
||||
<div
|
||||
className="settings"
|
||||
>
|
||||
@@ -230,7 +230,7 @@ exports[`CreditsSettings component renders the Electron page after a click 1`] =
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CreditsSettings component renders the Electron page by default 1`] = `
|
||||
exports[`Settings component renders the Electron page by default 1`] = `
|
||||
<div
|
||||
className="settings"
|
||||
>
|
||||
@@ -309,7 +309,7 @@ exports[`CreditsSettings component renders the Electron page by default 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CreditsSettings component renders the Execution page after a click 1`] = `
|
||||
exports[`Settings component renders the Execution page after a click 1`] = `
|
||||
<div
|
||||
className="settings"
|
||||
>
|
||||
@@ -388,7 +388,7 @@ exports[`CreditsSettings component renders the Execution page after a click 1`]
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CreditsSettings component renders the General page after a click 1`] = `
|
||||
exports[`Settings component renders the General page after a click 1`] = `
|
||||
<div
|
||||
className="settings"
|
||||
>
|
||||
|
||||
@@ -120,7 +120,7 @@ exports[`Header component renders the tour once started 1`] = `
|
||||
Object {
|
||||
"content": <React.Fragment>
|
||||
<p>
|
||||
Every Electron app starts with a main script, very similar to how a Node.js application is started. The main script runs in the "main process". To display a user interface, the main process creates renderer processes – usually in the form of windows, which Electron calls
|
||||
Every Electron app starts with a main script, very similar to how a Node.js application is started. The main script runs in the "main process". To display a user interface, the main process creates renderer processes – usually in the form of windows, which Electron calls
|
||||
<code>
|
||||
BrowserWindow
|
||||
</code>
|
||||
@@ -147,11 +147,11 @@ exports[`Header component renders the tour once started 1`] = `
|
||||
},
|
||||
Object {
|
||||
"content": <p>
|
||||
In the default fiddle, this HTML file is loaded in the
|
||||
In the default fiddle, this HTML file is loaded in the
|
||||
<code>
|
||||
BrowserWindow
|
||||
</code>
|
||||
. Any HTML, CSS, or JavaScript that works in a browser will work here, too. In addition, Electron allows you to execute Node.js code. Take a close look at the
|
||||
. Any HTML, CSS, or JavaScript that works in a browser will work here, too. In addition, Electron allows you to execute Node.js code. Take a close look at the
|
||||
<code>
|
||||
<script />
|
||||
</code>
|
||||
@@ -179,7 +179,7 @@ exports[`Header component renders the tour once started 1`] = `
|
||||
<code>
|
||||
npm
|
||||
</code>
|
||||
module here, just
|
||||
module here, just
|
||||
<code>
|
||||
require
|
||||
</code>
|
||||
|
||||
@@ -3,58 +3,23 @@ import * as React from 'react';
|
||||
|
||||
import { observable } from 'mobx';
|
||||
import { AddressBar } from '../../../src/renderer/components/commands-address-bar';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../../../src/shared-constants';
|
||||
import { getOctokit } from '../../../src/utils/octokit';
|
||||
import { ElectronFiddleMock } from '../../mocks/electron-fiddle';
|
||||
import { MockState } from '../../mocks/state';
|
||||
|
||||
jest.mock('../../../src/utils/octokit');
|
||||
|
||||
const mockGists = {
|
||||
get: async () => ({
|
||||
data: {
|
||||
files: {
|
||||
'renderer.js': {
|
||||
content: 'renderer-content'
|
||||
},
|
||||
'main.js': {
|
||||
content: 'main-content'
|
||||
},
|
||||
'index.html': {
|
||||
content: 'html'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const mockRepos = {
|
||||
getContents: async () => ({
|
||||
data: [
|
||||
{
|
||||
name: MAIN_JS_NAME,
|
||||
download_url: 'https://main'
|
||||
}, {
|
||||
name: RENDERER_JS_NAME,
|
||||
download_url: 'https://renderer'
|
||||
}, {
|
||||
name: INDEX_HTML_NAME,
|
||||
download_url: 'https://html'
|
||||
}, {
|
||||
name: 'other_stuff',
|
||||
download_url: 'https://google.com'
|
||||
}
|
||||
]
|
||||
})
|
||||
};
|
||||
|
||||
describe('AddressBar component', () => {
|
||||
let store: any;
|
||||
(window as any).ElectronFiddle = new ElectronFiddleMock();
|
||||
|
||||
class MockStore {
|
||||
@observable public gistId: string | null = null;
|
||||
@observable public isWarningDialogShowing: boolean = false;
|
||||
@observable public isConfirmationPromptShowing: boolean = false;
|
||||
public setWarningDialogTexts = jest.fn();
|
||||
public toogleWarningDialog = jest.fn();
|
||||
public setConfirmationDialogTexts = jest.fn();
|
||||
public setConfirmationPromptTexts = jest.fn();
|
||||
public toggleWarningDialog = jest.fn();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -91,8 +56,6 @@ describe('AddressBar component', () => {
|
||||
});
|
||||
|
||||
it('handles submit', () => {
|
||||
const oldLoadFiddle = AddressBar.prototype.fetchGistAndLoad;
|
||||
AddressBar.prototype.fetchGistAndLoad = jest.fn();
|
||||
const preventDefault = jest.fn();
|
||||
const wrapper = shallow(<AddressBar appState={store} />);
|
||||
const instance: AddressBar = wrapper.instance() as any;
|
||||
@@ -102,8 +65,6 @@ describe('AddressBar component', () => {
|
||||
|
||||
expect(wrapper.state('value')).toBe('abcdtestid');
|
||||
expect(preventDefault).toHaveBeenCalled();
|
||||
|
||||
AddressBar.prototype.fetchGistAndLoad = oldLoadFiddle;
|
||||
});
|
||||
|
||||
it('disables during gist publishing', async () => {
|
||||
@@ -117,202 +78,4 @@ describe('AddressBar component', () => {
|
||||
expect(wrapper.find('fieldset').prop('disabled')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchGistAndLoad()', () => {
|
||||
it('loads a fiddle', async () => {
|
||||
(getOctokit as jest.Mock).mockReturnValue({ gists: mockGists });
|
||||
|
||||
const wrapper = shallow(<AddressBar appState={store} />);
|
||||
const instance: AddressBar = wrapper.instance() as any;
|
||||
|
||||
store.gistId = 'abcdtestid';
|
||||
|
||||
instance.handleChange({ target: { value: 'abcdtestid' } } as any);
|
||||
await (wrapper.instance() as AddressBar).fetchGistAndLoad('abcdtestid');
|
||||
|
||||
expect(wrapper.state('value')).toBe('abcdtestid');
|
||||
expect(document.title).toBe('Electron Fiddle - gist.github.com/abcdtestid');
|
||||
});
|
||||
|
||||
it('handles an error', async () => {
|
||||
(getOctokit as jest.Mock).mockReturnValue({
|
||||
gists: {
|
||||
get: async () => {
|
||||
throw new Error('Bwap bwap');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const wrapper = shallow(<AddressBar appState={store} />);
|
||||
const result = await (wrapper.instance() as AddressBar).fetchGistAndLoad('abcdtestid');
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchExampleAndLoad()', () => {
|
||||
it('loads an Electron exmaple', async () => {
|
||||
(getOctokit as jest.Mock).mockReturnValue({ repos: mockRepos });
|
||||
|
||||
const wrapper = shallow(<AddressBar appState={store} />);
|
||||
const instance: AddressBar = wrapper.instance() as any;
|
||||
|
||||
// Setup the mock
|
||||
(fetch as any).mockResponses(
|
||||
[ 'main' ],
|
||||
[ 'renderer' ],
|
||||
[ 'index' ]
|
||||
);
|
||||
|
||||
await instance.fetchExampleAndLoad('4.0.0', 'test/path');
|
||||
|
||||
expect(document.title).toBe('Electron Fiddle - Unsaved');
|
||||
const { calls } = (window.ElectronFiddle.app.setValues as any).mock;
|
||||
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0]).toEqual([{
|
||||
html: 'index',
|
||||
main: 'main',
|
||||
renderer: 'renderer'
|
||||
}]);
|
||||
});
|
||||
|
||||
it('handles an error', async () => {
|
||||
(getOctokit as jest.Mock).mockReturnValue({
|
||||
repos: {
|
||||
getContents: async () => {
|
||||
throw new Error('Bwap bwap');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const wrapper = shallow(<AddressBar appState={store} />);
|
||||
const instance: AddressBar = wrapper.instance() as any;
|
||||
|
||||
// Setup the mock
|
||||
(fetch as any).mockResponses(
|
||||
[ 'main' ],
|
||||
[ 'renderer' ],
|
||||
[ 'index' ]
|
||||
);
|
||||
|
||||
const result = await instance.fetchExampleAndLoad('4.0.0', 'test/path');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('handles incorrect results', async () => {
|
||||
(getOctokit as jest.Mock).mockReturnValue({
|
||||
repos: {
|
||||
getContents: async () => ({
|
||||
not_an_array: true
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const wrapper = shallow(<AddressBar appState={store} />);
|
||||
const instance: AddressBar = wrapper.instance() as any;
|
||||
|
||||
// Setup the mock
|
||||
(fetch as any).mockResponses(
|
||||
[ 'main' ],
|
||||
[ 'renderer' ],
|
||||
[ 'index' ]
|
||||
);
|
||||
|
||||
const result = await instance.fetchExampleAndLoad('4.0.0', 'test/path');
|
||||
expect(result).toBe(false);
|
||||
expect(store.setWarningDialogTexts.mock.calls[0][0].label).toEqual(
|
||||
'Loading the fiddle failed: Error: The example Fiddle tried to launch is not a valid Electron example'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyRemoteLoad()', () => {
|
||||
it('asks the user if they want to load remote content', (done) => {
|
||||
const mockStore: any = new MockStore();
|
||||
const wrapper = shallow(<AddressBar appState={mockStore} />);
|
||||
const instance: AddressBar = wrapper.instance() as any;
|
||||
|
||||
instance.verifyRemoteLoad('test').then(done);
|
||||
|
||||
expect(mockStore.isWarningDialogShowing).toBe(true);
|
||||
mockStore.isWarningDialogShowing = false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyRemoteLoad()', () => {
|
||||
it('asks the user if they want to load remote content', (done) => {
|
||||
const wrapper = shallow(<AddressBar appState={store} />);
|
||||
const instance: AddressBar = wrapper.instance() as any;
|
||||
|
||||
instance.verifyRemoteLoad('test').then(done);
|
||||
|
||||
expect(store.isWarningDialogShowing).toBe(true);
|
||||
store.isWarningDialogShowing = false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadFiddleFromElectronExample()', () => {
|
||||
it('loads the example with confirmation', async () => {
|
||||
const wrapper = shallow(<AddressBar appState={store} />);
|
||||
const instance: AddressBar = wrapper.instance() as any;
|
||||
|
||||
instance.verifyRemoteLoad = jest.fn().mockReturnValue(true);
|
||||
instance.fetchExampleAndLoad = jest.fn();
|
||||
await instance.loadFiddleFromElectronExample(
|
||||
{},
|
||||
{ path: 'test/path', ref: '4.0.0' }
|
||||
);
|
||||
|
||||
expect(instance.verifyRemoteLoad).toHaveBeenCalled();
|
||||
expect(instance.fetchExampleAndLoad).toHaveBeenCalledWith('4.0.0', 'test/path');
|
||||
});
|
||||
|
||||
it('does not load the example without confirmation', async () => {
|
||||
const wrapper = shallow(<AddressBar appState={store} />);
|
||||
const instance: AddressBar = wrapper.instance() as any;
|
||||
|
||||
instance.verifyRemoteLoad = jest.fn().mockReturnValue(false);
|
||||
instance.fetchExampleAndLoad = jest.fn();
|
||||
await instance.loadFiddleFromElectronExample(
|
||||
{},
|
||||
{ path: 'test/path', ref: '4.0.0' }
|
||||
);
|
||||
|
||||
expect(instance.verifyRemoteLoad).toHaveBeenCalled();
|
||||
expect(instance.fetchExampleAndLoad).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadFiddleFromGist()', () => {
|
||||
it('loads the example with confirmation', async () => {
|
||||
const wrapper = shallow(<AddressBar appState={store} />);
|
||||
const instance: AddressBar = wrapper.instance() as any;
|
||||
|
||||
instance.verifyRemoteLoad = jest.fn().mockReturnValue(true);
|
||||
instance.fetchGistAndLoad = jest.fn();
|
||||
await instance.loadFiddleFromGist(
|
||||
{},
|
||||
{ id: 'gist' }
|
||||
);
|
||||
|
||||
expect(instance.verifyRemoteLoad).toHaveBeenCalled();
|
||||
expect(instance.fetchGistAndLoad).toHaveBeenCalledWith('gist');
|
||||
});
|
||||
|
||||
it('does not load the example without confirmation', async () => {
|
||||
const wrapper = shallow(<AddressBar appState={store} />);
|
||||
const instance: AddressBar = wrapper.instance() as any;
|
||||
|
||||
instance.verifyRemoteLoad = jest.fn().mockReturnValue(false);
|
||||
instance.fetchGistAndLoad = jest.fn();
|
||||
await instance.loadFiddleFromGist(
|
||||
{},
|
||||
{ id: 'gist' }
|
||||
);
|
||||
|
||||
expect(instance.verifyRemoteLoad).toHaveBeenCalled();
|
||||
expect(instance.fetchGistAndLoad).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,20 +1,20 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
import { shallow } from "enzyme";
|
||||
import * as React from "react";
|
||||
|
||||
import { PublishButton } from '../../../src/renderer/components/commands-publish-button';
|
||||
import { getOctokit } from '../../../src/utils/octokit';
|
||||
import { PublishButton } from "../../../src/renderer/components/commands-publish-button";
|
||||
import { getOctokit } from "../../../src/utils/octokit";
|
||||
|
||||
jest.mock('../../../src/utils/octokit');
|
||||
jest.mock("../../../src/utils/octokit");
|
||||
|
||||
describe('Publish button component', () => {
|
||||
describe("Publish button component", () => {
|
||||
let store: any;
|
||||
|
||||
const expectedGistCreateOpts = {
|
||||
description: 'Electron Fiddle Gist',
|
||||
description: "Electron Fiddle Gist",
|
||||
files: {
|
||||
'index.html': { content: 'html-content' },
|
||||
'renderer.js': { content: 'renderer-content' },
|
||||
'main.js': { content: 'main-content' },
|
||||
"index.html": { content: "html-content" },
|
||||
"renderer.js": { content: "renderer-content" },
|
||||
"main.js": { content: "main-content" }
|
||||
},
|
||||
public: true
|
||||
};
|
||||
@@ -25,12 +25,12 @@ describe('Publish button component', () => {
|
||||
};
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
it("renders", () => {
|
||||
const wrapper = shallow(<PublishButton appState={store} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('toggles the auth dialog on click if not authed', async () => {
|
||||
it("toggles the auth dialog on click if not authed", async () => {
|
||||
store.toggleAuthDialog = jest.fn();
|
||||
|
||||
const wrapper = shallow(<PublishButton appState={store} />);
|
||||
@@ -40,8 +40,8 @@ describe('Publish button component', () => {
|
||||
expect(store.toggleAuthDialog).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('toggles the publish method on click if authed', async () => {
|
||||
store.gitHubToken = 'github-token';
|
||||
it("toggles the publish method on click if authed", async () => {
|
||||
store.gitHubToken = "github-token";
|
||||
|
||||
const wrapper = shallow(<PublishButton appState={store} />);
|
||||
const instance: PublishButton = wrapper.instance() as any;
|
||||
@@ -51,11 +51,11 @@ describe('Publish button component', () => {
|
||||
expect(instance.publishFiddle).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('attempts to publish to Gist', async () => {
|
||||
it("attempts to publish to Gist", async () => {
|
||||
const mockOctokit = {
|
||||
authenticate: jest.fn(),
|
||||
gists: {
|
||||
create: jest.fn(async () => ({ data: { id: '123' } }))
|
||||
create: jest.fn(async () => ({ data: { id: "123" } }))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -67,21 +67,21 @@ describe('Publish button component', () => {
|
||||
await instance.publishFiddle();
|
||||
|
||||
expect(mockOctokit.gists.create).toHaveBeenCalledWith({
|
||||
description: 'Electron Fiddle Gist',
|
||||
description: "Electron Fiddle Gist",
|
||||
files: {
|
||||
'index.html': { content: 'html-content' },
|
||||
'renderer.js': { content: 'renderer-content' },
|
||||
'main.js': { content: 'main-content' },
|
||||
"index.html": { content: "html-content" },
|
||||
"renderer.js": { content: "renderer-content" },
|
||||
"main.js": { content: "main-content" }
|
||||
},
|
||||
public: true
|
||||
});
|
||||
});
|
||||
|
||||
it('handles missing content', async () => {
|
||||
it("handles missing content", async () => {
|
||||
const mockOctokit = {
|
||||
authenticate: jest.fn(),
|
||||
gists: {
|
||||
create: jest.fn(async () => ({ data: { id: '123' } }))
|
||||
create: jest.fn(async () => ({ data: { id: "123" } }))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -90,27 +90,27 @@ describe('Publish button component', () => {
|
||||
const wrapper = shallow(<PublishButton appState={store} />);
|
||||
const instance: PublishButton = wrapper.instance() as any;
|
||||
|
||||
(window as any).ElectronFiddle.app.getValues.mockReturnValueOnce({});
|
||||
(window as any).ElectronFiddle.app.getEditorValues.mockReturnValueOnce({});
|
||||
|
||||
await instance.publishFiddle();
|
||||
|
||||
expect(mockOctokit.gists.create).toHaveBeenCalledWith({
|
||||
description: 'Electron Fiddle Gist',
|
||||
description: "Electron Fiddle Gist",
|
||||
files: {
|
||||
'index.html': { content: '<!-- Empty -->' },
|
||||
'renderer.js': { content: '// Empty' },
|
||||
'main.js': { content: '// Empty' },
|
||||
"index.html": { content: "<!-- Empty -->" },
|
||||
"renderer.js": { content: "// Empty" },
|
||||
"main.js": { content: "// Empty" }
|
||||
},
|
||||
public: true
|
||||
});
|
||||
});
|
||||
|
||||
it('handles an error in Gist publishing', async () => {
|
||||
it("handles an error in Gist publishing", async () => {
|
||||
const mockOctokit = {
|
||||
authenticate: jest.fn(),
|
||||
gists: {
|
||||
create: jest.fn(() => {
|
||||
throw new Error('bwap bwap');
|
||||
throw new Error("bwap bwap");
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -125,12 +125,12 @@ describe('Publish button component', () => {
|
||||
expect(store.isPublishing).toBe(false);
|
||||
});
|
||||
|
||||
it('uses the privacy setting correctly', async () => {
|
||||
it("uses the privacy setting correctly", async () => {
|
||||
const mockOctokit = {
|
||||
authenticate: jest.fn(),
|
||||
gists: {
|
||||
create: jest.fn(() => {
|
||||
throw new Error('bwap bwap');
|
||||
throw new Error("bwap bwap");
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -145,7 +145,7 @@ describe('Publish button component', () => {
|
||||
|
||||
expect(mockOctokit.gists.create).toHaveBeenCalledWith({
|
||||
...expectedGistCreateOpts,
|
||||
public: false,
|
||||
public: false
|
||||
});
|
||||
|
||||
instance.setPublic();
|
||||
@@ -153,24 +153,24 @@ describe('Publish button component', () => {
|
||||
|
||||
expect(mockOctokit.gists.create).toHaveBeenCalledWith({
|
||||
...expectedGistCreateOpts,
|
||||
public: true,
|
||||
public: true
|
||||
});
|
||||
});
|
||||
|
||||
it('disables during gist publishing', async () => {
|
||||
it("disables during gist publishing", async () => {
|
||||
store.isPublishing = false;
|
||||
const wrapper = shallow(<PublishButton appState={store} />);
|
||||
const instance: PublishButton = wrapper.instance() as any;
|
||||
|
||||
expect(wrapper.find('fieldset').prop('disabled')).toBe(false);
|
||||
expect(wrapper.find("fieldset").prop("disabled")).toBe(false);
|
||||
|
||||
instance.publishFiddle = jest.fn().mockImplementationOnce(() => {
|
||||
return new Promise((resolve) => {
|
||||
wrapper.setProps({appState: {store, isPublishing: true}}, () => {
|
||||
expect(wrapper.find('fieldset').prop('disabled')).toBe(true);
|
||||
return new Promise(resolve => {
|
||||
wrapper.setProps({ appState: { store, isPublishing: true } }, () => {
|
||||
expect(wrapper.find("fieldset").prop("disabled")).toBe(true);
|
||||
});
|
||||
wrapper.setProps({appState: {store, isPublishing: false}}, () => {
|
||||
expect(wrapper.find('fieldset').prop('disabled')).toBe(false);
|
||||
wrapper.setProps({ appState: { store, isPublishing: false } }, () => {
|
||||
expect(wrapper.find("fieldset").prop("disabled")).toBe(false);
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
@@ -179,8 +179,8 @@ describe('Publish button component', () => {
|
||||
await instance.publishFiddle();
|
||||
});
|
||||
|
||||
describe('privacy menu', () => {
|
||||
it('toggles the privacy setting', () => {
|
||||
describe("privacy menu", () => {
|
||||
it("toggles the privacy setting", () => {
|
||||
const wrapper = shallow(<PublishButton appState={store} />);
|
||||
const instance: PublishButton = wrapper.instance() as any;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,7 +17,7 @@ describe('TokenDialog component', () => {
|
||||
store = {
|
||||
isWarningDialogShowing: false,
|
||||
warningDialogTexts: { label: '', ok: '', cancel: '' },
|
||||
toogleWarningDialog: jest.fn()
|
||||
toggleWarningDialog: jest.fn()
|
||||
};
|
||||
});
|
||||
|
||||
@@ -37,6 +37,6 @@ describe('TokenDialog component', () => {
|
||||
const instance: WarningDialog = wrapper.instance() as any;
|
||||
|
||||
instance.onClose(true);
|
||||
expect(store.toogleWarningDialog).toHaveBeenCalledTimes(1);
|
||||
expect(store.toggleWarningDialog).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,27 +26,21 @@ describe('Dialogs component', () => {
|
||||
resetPlatform();
|
||||
});
|
||||
|
||||
it('renders initially without visible dialogs', () => {
|
||||
const wrapper = shallow(<Dialogs appState={store} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find('.dialogs').html()).toBe('<div class="dialogs"></div>');
|
||||
});
|
||||
|
||||
it('renders the token dialog', () => {
|
||||
store.isTokenDialogShowing = true;
|
||||
const wrapper = shallow(<Dialogs appState={store} />);
|
||||
expect(wrapper.text()).toBe('<TokenDialog /><WarningDialog />');
|
||||
expect(wrapper.text()).toBe('<TokenDialog /><ConfirmDialog />');
|
||||
});
|
||||
|
||||
it('renders the settings dialog', () => {
|
||||
store.isSettingsShowing = true;
|
||||
const wrapper = shallow(<Dialogs appState={store} />);
|
||||
expect(wrapper.text()).toBe('<Settings /><WarningDialog />');
|
||||
expect(wrapper.text()).toBe('<Settings /><ConfirmDialog />');
|
||||
});
|
||||
|
||||
it('renders the settings dialog', () => {
|
||||
store.isAddVersionDialogShowing = true;
|
||||
const wrapper = shallow(<Dialogs appState={store} />);
|
||||
expect(wrapper.text()).toBe('<AddVersionDialog /><WarningDialog />');
|
||||
expect(wrapper.text()).toBe('<AddVersionDialog /><ConfirmDialog />');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ describe('Editor component', () => {
|
||||
let store: any;
|
||||
let monaco: any;
|
||||
const editorDispose = jest.fn();
|
||||
const updateOptions = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
store = {
|
||||
@@ -26,7 +27,9 @@ describe('Editor component', () => {
|
||||
setModel: jest.fn(),
|
||||
restoreViewState: jest.fn()
|
||||
})),
|
||||
createModel: jest.fn(),
|
||||
createModel: jest.fn(() => ({
|
||||
updateOptions
|
||||
})),
|
||||
setModel: jest.fn(),
|
||||
dispose: jest.fn()
|
||||
}
|
||||
@@ -35,7 +38,7 @@ describe('Editor component', () => {
|
||||
|
||||
it('renders the editor container', () => {
|
||||
const wrapper = shallow(
|
||||
<Editor appState={store} monaco={monaco} monoacoOptions={{}} id={EditorId.main} />
|
||||
<Editor appState={store} monaco={monaco} monacoOptions={{}} id={EditorId.main} />
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toBe('<div class="editorContainer"></div>');
|
||||
@@ -43,13 +46,13 @@ describe('Editor component', () => {
|
||||
|
||||
it('correctly sets the language', () => {
|
||||
let wrapper = shallow(
|
||||
<Editor appState={store} monaco={monaco} monoacoOptions={{}} id={EditorId.main} />
|
||||
<Editor appState={store} monaco={monaco} monacoOptions={{}} id={EditorId.main} />
|
||||
);
|
||||
|
||||
expect((wrapper.instance() as any).language).toBe('javascript');
|
||||
|
||||
wrapper = shallow(
|
||||
<Editor appState={store} monaco={monaco} monoacoOptions={{}} id={EditorId.html} />
|
||||
<Editor appState={store} monaco={monaco} monacoOptions={{}} id={EditorId.html} />
|
||||
);
|
||||
|
||||
expect((wrapper.instance() as any).language).toBe('html');
|
||||
@@ -57,7 +60,7 @@ describe('Editor component', () => {
|
||||
|
||||
it('denies updates', () => {
|
||||
const wrapper = shallow(
|
||||
<Editor appState={store} monaco={monaco} monoacoOptions={{}} id={EditorId.main} />
|
||||
<Editor appState={store} monaco={monaco} monacoOptions={{}} id={EditorId.main} />
|
||||
);
|
||||
|
||||
expect((wrapper as any)
|
||||
@@ -66,49 +69,72 @@ describe('Editor component', () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('initMonaco() attempts to create an editor', async () => {
|
||||
const didMount = jest.fn();
|
||||
const wrapper = shallow(
|
||||
<Editor
|
||||
appState={store}
|
||||
monaco={monaco}
|
||||
monoacoOptions={{}}
|
||||
id={EditorId.main}
|
||||
editorDidMount={didMount}
|
||||
/>
|
||||
);
|
||||
const instance: any = wrapper.instance();
|
||||
describe('initMonaco()', async () => {
|
||||
it('attempts to create an editor', async () => {
|
||||
const didMount = jest.fn();
|
||||
const wrapper = shallow(
|
||||
<Editor
|
||||
appState={store}
|
||||
monaco={monaco}
|
||||
monacoOptions={{}}
|
||||
id={EditorId.main}
|
||||
editorDidMount={didMount}
|
||||
/>
|
||||
);
|
||||
const instance: any = wrapper.instance();
|
||||
|
||||
instance.containerRef.current = 'ref';
|
||||
await instance.initMonaco();
|
||||
instance.containerRef.current = 'ref';
|
||||
await instance.initMonaco();
|
||||
|
||||
expect(didMount).toHaveBeenCalled();
|
||||
expect(monaco.editor.create).toHaveBeenCalled();
|
||||
expect(monaco.editor.createModel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('initMonaco() attempts to create an editor and restores a backup', async () => {
|
||||
store.getAndRemoveEditorValueBackup.mockReturnValueOnce({
|
||||
model: true,
|
||||
viewState: true
|
||||
expect(didMount).toHaveBeenCalled();
|
||||
expect(monaco.editor.create).toHaveBeenCalled();
|
||||
expect(monaco.editor.createModel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const wrapper = shallow(
|
||||
<Editor
|
||||
appState={store}
|
||||
monaco={monaco}
|
||||
monoacoOptions={{}}
|
||||
id={EditorId.main}
|
||||
editorDidMount={() => undefined}
|
||||
/>
|
||||
);
|
||||
const instance: any = wrapper.instance();
|
||||
it('attempts to restore a backup if available', async () => {
|
||||
store.getAndRemoveEditorValueBackup.mockReturnValueOnce({
|
||||
model: true,
|
||||
viewState: true
|
||||
});
|
||||
|
||||
instance.containerRef.current = 'ref';
|
||||
await instance.initMonaco();
|
||||
const wrapper = shallow(
|
||||
<Editor
|
||||
appState={store}
|
||||
monaco={monaco}
|
||||
monacoOptions={{}}
|
||||
id={EditorId.main}
|
||||
editorDidMount={() => 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();
|
||||
|
||||
expect(instance.editor.restoreViewState).toHaveBeenCalledTimes(1);
|
||||
expect(instance.editor.setModel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('initializes with a fixed tab size', async () => {
|
||||
const didMount = jest.fn();
|
||||
const wrapper = shallow(
|
||||
<Editor
|
||||
appState={store}
|
||||
monaco={monaco}
|
||||
monacoOptions={{}}
|
||||
id={EditorId.main}
|
||||
editorDidMount={didMount}
|
||||
/>
|
||||
);
|
||||
const instance: any = wrapper.instance();
|
||||
|
||||
instance.containerRef.current = 'ref';
|
||||
await instance.initMonaco();
|
||||
|
||||
expect(updateOptions).toHaveBeenCalledWith(expect.objectContaining({
|
||||
tabSize: 2
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('componentWillUnmount() attempts to dispose the editor', async () => {
|
||||
@@ -117,7 +143,7 @@ describe('Editor component', () => {
|
||||
<Editor
|
||||
appState={store}
|
||||
monaco={monaco}
|
||||
monoacoOptions={{}}
|
||||
monacoOptions={{}}
|
||||
id={EditorId.main}
|
||||
editorDidMount={didMount}
|
||||
/>
|
||||
|
||||
@@ -10,9 +10,11 @@ import { updateEditorLayout } from '../../../src/utils/editor-layout';
|
||||
import { createMosaicArrangement } from '../../../src/utils/editors-mosaic-arrangement';
|
||||
import { getFocusedEditor } from '../../../src/utils/focused-editor';
|
||||
|
||||
jest.mock('monaco-loader', () => jest.fn(async () => {
|
||||
return { monaco: true };
|
||||
}));
|
||||
jest.mock('monaco-loader', () =>
|
||||
jest.fn(async () => {
|
||||
return { monaco: true };
|
||||
})
|
||||
);
|
||||
|
||||
jest.mock('../../../src/renderer/components/editor', () => ({
|
||||
Editor: () => 'Editor'
|
||||
@@ -84,11 +86,10 @@ describe('Editors component', () => {
|
||||
});
|
||||
|
||||
it('handles an error', () => {
|
||||
(window.ElectronFiddle.editors.html!.updateOptions as jest.Mock).mockImplementationOnce(
|
||||
() => {
|
||||
throw new Error('Bwap bwap');
|
||||
}
|
||||
);
|
||||
(window.ElectronFiddle.editors.html!
|
||||
.updateOptions as jest.Mock).mockImplementationOnce(() => {
|
||||
throw new Error('Bwap bwap');
|
||||
});
|
||||
|
||||
const wrapper = shallow(<Editors appState={store} />);
|
||||
const instance: Editors = wrapper.instance() as any;
|
||||
@@ -101,19 +102,22 @@ describe('Editors component', () => {
|
||||
const instance: Editors = wrapper.instance() as any;
|
||||
|
||||
expect(instance.toggleEditorOption('wordWrap')).toBe(true);
|
||||
expect(window.ElectronFiddle.editors.html!.updateOptions).toHaveBeenCalledWith(
|
||||
{
|
||||
minimap: { enabled: false },
|
||||
wordWrap: 'off'
|
||||
}
|
||||
);
|
||||
expect(
|
||||
window.ElectronFiddle.editors.html!.updateOptions
|
||||
).toHaveBeenCalledWith({
|
||||
minimap: { enabled: false },
|
||||
wordWrap: 'off'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a toolbar', () => {
|
||||
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);
|
||||
const toolbar = instance.renderToolbar(
|
||||
{ title: TITLE_MAP[EditorId.main] } as any,
|
||||
EditorId.main
|
||||
);
|
||||
|
||||
expect(toolbar).toMatchSnapshot();
|
||||
});
|
||||
@@ -162,7 +166,7 @@ describe('Editors component', () => {
|
||||
|
||||
ipcRendererManager.emit(IpcEvents.FS_NEW_FIDDLE, null);
|
||||
process.nextTick(() => {
|
||||
expect(window.ElectronFiddle.app.fileManager.setFiddle).toHaveBeenCalled();
|
||||
expect(window.ElectronFiddle.app.replaceFiddle).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -172,9 +176,15 @@ describe('Editors component', () => {
|
||||
|
||||
ipcRendererManager.emit(IpcEvents.MONACO_TOGGLE_OPTION, null, 'wordWrap');
|
||||
|
||||
expect(window.ElectronFiddle.editors.html!.updateOptions).toHaveBeenCalled();
|
||||
expect(window.ElectronFiddle.editors.renderer!.updateOptions).toHaveBeenCalled();
|
||||
expect(window.ElectronFiddle.editors.main!.updateOptions).toHaveBeenCalled();
|
||||
expect(
|
||||
window.ElectronFiddle.editors.html!.updateOptions
|
||||
).toHaveBeenCalled();
|
||||
expect(
|
||||
window.ElectronFiddle.editors.renderer!.updateOptions
|
||||
).toHaveBeenCalled();
|
||||
expect(
|
||||
window.ElectronFiddle.editors.main!.updateOptions
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -185,7 +195,7 @@ describe('Editors component', () => {
|
||||
shallow(<Editors appState={store} />);
|
||||
|
||||
process.nextTick(() => {
|
||||
expect(window.ElectronFiddle.app.monaco).toEqual({ monaco: true});
|
||||
expect(window.ElectronFiddle.app.monaco).toEqual({ monaco: true });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,20 +2,43 @@ import { shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
import { Output } from '../../../src/renderer/components/output';
|
||||
import { MockState } from '../../mocks/state';
|
||||
|
||||
let mockContext: any = {};
|
||||
|
||||
jest.mock('react-mosaic-component', () => {
|
||||
const { MosaicContext, MosaicRootActions } = jest.requireActual('react-mosaic-component');
|
||||
|
||||
return {
|
||||
MosaicContext,
|
||||
MosaicRootActions
|
||||
};
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
mockContext = {
|
||||
mosaicActions: {
|
||||
expand: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
hide: jest.fn(),
|
||||
replaceWith: jest.fn(),
|
||||
updateTree: jest.fn(),
|
||||
getRoot: jest.fn()
|
||||
},
|
||||
mosaicId: 'output'
|
||||
};
|
||||
});
|
||||
|
||||
describe('Output component', () => {
|
||||
let store: any;
|
||||
|
||||
beforeEach(() => {
|
||||
store = {
|
||||
isConsoleShowing: true,
|
||||
output: []
|
||||
};
|
||||
store = new MockState();
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<Output appState={store} />);
|
||||
expect(wrapper.html()).toBe('<div class="output showing"></div>');
|
||||
expect(wrapper.html()).toBe('<div class="output"></div>');
|
||||
});
|
||||
|
||||
it('renders with output', () => {
|
||||
@@ -54,14 +77,25 @@ describe('Output component', () => {
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('hides the console via class', () => {
|
||||
it('hides the console with react-mosaic-component', () => {
|
||||
// manually trigger lifecycle methods so that
|
||||
// context can be set before mounting method
|
||||
const wrapper = shallow(<Output appState={store} />, {
|
||||
context: mockContext,
|
||||
disableLifecycleMethods: true
|
||||
});
|
||||
|
||||
wrapper.instance().context = mockContext;
|
||||
wrapper.instance().componentDidMount!();
|
||||
|
||||
expect(mockContext.mosaicActions.expand).toHaveBeenCalledWith(['first'], 25);
|
||||
|
||||
store.isConsoleShowing = false;
|
||||
|
||||
const wrapper = shallow(
|
||||
<Output appState={store} />
|
||||
);
|
||||
|
||||
expect(wrapper.html().includes('showing')).toBe(false);
|
||||
// Todo: There's a scary bug here in Jest / Enzyme. At this point in time,
|
||||
// the context is {}. That's never the case in production.
|
||||
// expect(mockContext.mosaicActions.expand).toHaveBeenCalledWith(['first'], 0);
|
||||
expect(wrapper.html()).toBe(null);
|
||||
});
|
||||
|
||||
it('handles componentDidUpdate', () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user