├── .gitignore ├── LICENSE ├── README.md ├── bin ├── cli.js └── extension.js ├── dist ├── devpanel.tmpl.html ├── devtools.html ├── img │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ └── 48x48.png ├── js │ └── devtools.js └── manifest.json ├── example ├── .babelrc ├── .eslintrc ├── .gitignore ├── README.md ├── app │ ├── actions │ │ └── counter.js │ ├── api │ │ └── .gitkeep │ ├── app.global.css │ ├── app.html │ ├── app.icns │ ├── components │ │ ├── Counter.css │ │ ├── Counter.js │ │ ├── Home.css │ │ └── Home.js │ ├── containers │ │ ├── App.js │ │ ├── CounterPage.js │ │ └── HomePage.js │ ├── index.js │ ├── reducers │ │ ├── counter.js │ │ └── index.js │ ├── routes.js │ ├── store │ │ ├── configureStore.development.js │ │ ├── configureStore.js │ │ └── configureStore.production.js │ └── utils │ │ └── .gitkeep ├── main.js ├── package.js ├── package.json ├── server.js ├── test │ ├── .eslintrc │ ├── actions │ │ └── counter.spec.js │ ├── components │ │ └── Counter.spec.js │ ├── containers │ │ └── CounterPage.spec.js │ ├── e2e.js │ ├── example.js │ ├── reducers │ │ └── counter.spec.js │ └── setup.js ├── webpack.config.base.js ├── webpack.config.development.js ├── webpack.config.node.js └── webpack.config.production.js ├── package.json ├── src └── devpanel.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | dist/js/*.bundle.js 5 | dist/devpanel.html 6 | *.zip 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 Jhen-Jie Hong 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RemoteDev Extension [![NPM version](http://img.shields.io/npm/v/remotedev-extension.svg?style=flat)](https://www.npmjs.com/package/remotedev-extension) [![Dependency Status](https://david-dm.org/jhen0409/remotedev-extension.svg)](https://david-dm.org/jhen0409/remotedev-extension) [![devDependency Status](https://david-dm.org/jhen0409/remotedev-extension/dev-status.svg)](https://david-dm.org/jhen0409/remotedev-extension#info=devDependencies) 2 | 3 | > Use Redux DevTools in the Browser/Electron DevTools 4 | 5 | ![Demo](https://cloud.githubusercontent.com/assets/3001525/14159740/2b6b89b0-f70a-11e5-8d31-20046b482208.png) 6 | 7 | The demo is used [electron-react-boilerplate](https://github.com/chentsulin/electron-react-boilerplate). 8 | 9 | ## Why? 10 | 11 | [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension) is awesome, but it [cannot running on Electron](https://github.com/zalmoxisus/redux-devtools-extension/issues/13). This project as a major support for the DevTools Extension of [Electron](https://github.com/atom/electron), it means that it doesn't use __background script__. 12 | 13 | This extension is included [remotedev-app](https://github.com/zalmoxisus/remotedev-app), can be used with [remotedev](https://github.com/zalmoxisus/remotedev) / [remote-redux-devtools](https://github.com/zalmoxisus/remote-redux-devtools). 14 | 15 | ## Installation 16 | 17 | ```bash 18 | $ npm i --save-dev remotedev-extension 19 | ``` 20 | 21 | ## Usage (Browser) 22 | 23 | [Download Chrome Extension](https://chrome.google.com/webstore/detail/remotedev-devtools/npmkpkaejamnfodceoimeeioacfcijop) 24 | [Download Opera Extension](https://addons.opera.com/extensions/details/remotedev-devtools) 25 | 26 | ## Usage (Electron) 27 | 28 | ```js 29 | const electron = require('electron'); 30 | const app = electron.app; 31 | const BrowserWindow = electron.BrowserWindow; 32 | 33 | app.on('ready', () => { 34 | // Add DevTools Extension, 35 | // if you want remove it, 36 | // Use: BrowserWindow.removeDevToolsExtension('RemoteDev DevTools'); 37 | BrowserWindow.addDevToolsExtension('node_modules/remotedev-extension/dist'); 38 | 39 | // ... 40 | }); 41 | ``` 42 | 43 | __*NOTE:*__ The Electron v0.37 have a [separate window problem](https://github.com/atom/electron/issues/4958#issuecomment-205121647). 44 | 45 | ## CLI and node 46 | 47 | ```bash 48 | $ remotedev-extension [options] 49 | ``` 50 | 51 | ```js 52 | require('remotedev-extension')(options); 53 | ``` 54 | 55 | #### Options 56 | 57 | * --hostname: the `remotedev-server` hostname, will apply `node_modules/remotedev-extension/dist` settings. 58 | (default: `localhost` if `port` is set) 59 | * --port: the `remotedev-server` port, will apply `node_modules/remotedev-extension/dist` settings. 60 | (default: `8000` if `runserver` or `hostname` is set) 61 | * --runserver: start the `remotedev-server` with options on local. 62 | * --ui-no-buttonbar: Set `noButtonBar` prop for [remotedev-app](https://github.com/zalmoxisus/remotedev-app/blob/master/src/app/index.js#L19). 63 | 64 | ## Example of Electron 65 | 66 | You can refer to [example folder](example). 67 | 68 | ## License 69 | 70 | [MIT](LICENSE) 71 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const argv = require('minimist')(process.argv.slice(2), { 4 | boolean: ['runserver', 'ui-no-buttonbar'] 5 | }); 6 | require('./extension')(argv); 7 | -------------------------------------------------------------------------------- /bin/extension.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const filePath = path.join(__dirname, '../dist/devpanel.tmpl.html'); 4 | const distPath = path.join(__dirname, '../dist/devpanel.html'); 5 | const startRemoteDev = require('remotedev-server'); 6 | 7 | const html = fs.readFileSync(filePath, 'utf-8'); 8 | 9 | module.exports = argv => { 10 | if (argv.hostname || argv.port) { 11 | fs.writeFileSync( 12 | distPath, 13 | html.replace( 14 | '// __remotedevOptionsSet__', 15 | 'window.remotedevOptions = ' + JSON.stringify({ 16 | hostname: argv.hostname || 'localhost', 17 | port: argv.port || 8000, 18 | autoReconnect: true, 19 | noButtonBar: argv['ui-no-buttonbar'] 20 | }) 21 | ) 22 | ); 23 | } else { 24 | fs.writeFileSync(distPath, html); 25 | } 26 | if (argv.runserver) { 27 | argv.port = argv.port || 8000; 28 | return startRemoteDev(argv); 29 | } 30 | return { on: (status, cb) => cb() }; 31 | }; 32 | -------------------------------------------------------------------------------- /dist/devpanel.tmpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | 26 |
27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /dist/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RemoteDev 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /dist/img/logo/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhen0409/remotedev-extension/2f95aef4b8d1276d93b9126d90d1f6540bc0e679/dist/img/logo/128x128.png -------------------------------------------------------------------------------- /dist/img/logo/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhen0409/remotedev-extension/2f95aef4b8d1276d93b9126d90d1f6540bc0e679/dist/img/logo/16x16.png -------------------------------------------------------------------------------- /dist/img/logo/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhen0409/remotedev-extension/2f95aef4b8d1276d93b9126d90d1f6540bc0e679/dist/img/logo/48x48.png -------------------------------------------------------------------------------- /dist/js/devtools.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create( 2 | 'RemoteDev', null, 'devpanel.html', function(panel) {} 3 | ); -------------------------------------------------------------------------------- /dist/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.10", 3 | "name": "RemoteDev DevTools", 4 | "manifest_version": 2, 5 | "description": "Use Redux DevTools in the DevTools", 6 | "icons": { 7 | "16": "img/logo/16x16.png", 8 | "48": "img/logo/48x48.png", 9 | "128": "img/logo/128x128.png" 10 | }, 11 | "devtools_page": "devtools.html", 12 | "permissions": [ "storage" ], 13 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;" 14 | } 15 | -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["add-module-exports"], 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | }, 8 | "test": { 9 | "plugins": [ 10 | ["webpack-loaders", { "config": "webpack.config.node.js", "verbose": false }] 11 | ] 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "browser": true, 6 | "mocha": true, 7 | "node": true 8 | }, 9 | "rules": { 10 | "react/jsx-no-bind": 0, 11 | "react/prefer-stateless-function": 0, 12 | "comma-dangle": 0, 13 | "no-use-before-define": 0, 14 | "consistent-return": 0 15 | }, 16 | "plugins": [ 17 | "react" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # OSX 30 | .DS_Store 31 | 32 | # App packaged 33 | dist 34 | release 35 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # RemoteDev Extension example of Electron 2 | 3 | This example is used [Electron React Boilerplate](https://github.com/chentsulin/electron-react-boilerplate). 4 | 5 | ## Different 6 | 7 | * Changed [package.json](package.json) 8 | * Changed [main.js](main.js) 9 | * Changed [server.js](server.js) 10 | * Changed [app/store/configureStore.development.js](app/store/configureStore.development.js) 11 | * Changed [app/containers/App.js](app/containers/App.js) 12 | * Removed [app/containers/DevTools.js](app/containers/DevTools.js) 13 | 14 | ## Usage 15 | 16 | ```bash 17 | # clone this repo 18 | $ git clone https://github.com/jhen0409/remotedev-extension.git 19 | 20 | # Install dependencies 21 | $ cd example && npm i 22 | 23 | # Run 24 | $ npm run dev 25 | ``` 26 | -------------------------------------------------------------------------------- /example/app/actions/counter.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; 2 | export const DECREMENT_COUNTER = 'DECREMENT_COUNTER'; 3 | 4 | export function increment() { 5 | return { 6 | type: INCREMENT_COUNTER 7 | }; 8 | } 9 | 10 | export function decrement() { 11 | return { 12 | type: DECREMENT_COUNTER 13 | }; 14 | } 15 | 16 | export function incrementIfOdd() { 17 | return (dispatch, getState) => { 18 | const { counter } = getState(); 19 | 20 | if (counter % 2 === 0) { 21 | return; 22 | } 23 | 24 | dispatch(increment()); 25 | }; 26 | } 27 | 28 | export function incrementAsync(delay = 1000) { 29 | return dispatch => { 30 | setTimeout(() => { 31 | dispatch(increment()); 32 | }, delay); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /example/app/api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhen0409/remotedev-extension/2f95aef4b8d1276d93b9126d90d1f6540bc0e679/example/app/api/.gitkeep -------------------------------------------------------------------------------- /example/app/app.global.css: -------------------------------------------------------------------------------- 1 | body { 2 | position: relative; 3 | color: white; 4 | height: 100vh; 5 | background-color: #232C39; 6 | background-image: linear-gradient(45deg, rgba(0, 216, 255, .5) 10%, rgba(0, 1, 127, .7)); 7 | font-family: Arial, Helvetica, Helvetica Neue; 8 | overflow-y: hidden; 9 | } 10 | 11 | h2 { 12 | margin: 0; 13 | font-size: 2.25rem; 14 | font-weight: bold; 15 | letter-spacing: -.025em; 16 | color: #fff; 17 | } 18 | 19 | p { 20 | font-size: 24px; 21 | } 22 | 23 | li { 24 | list-style: none; 25 | } 26 | 27 | a { 28 | color: white; 29 | opacity: .75; 30 | text-decoration: none; 31 | } 32 | 33 | a:hover { 34 | opacity: 1; 35 | text-decoration: none; 36 | cursor: pointer; 37 | } 38 | -------------------------------------------------------------------------------- /example/app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Electron React! 6 | 7 | 17 | 18 | 19 |
20 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /example/app/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhen0409/remotedev-extension/2f95aef4b8d1276d93b9126d90d1f6540bc0e679/example/app/app.icns -------------------------------------------------------------------------------- /example/app/components/Counter.css: -------------------------------------------------------------------------------- 1 | .backButton { 2 | position: absolute; 3 | } 4 | 5 | .counter { 6 | position: absolute; 7 | top: 30%; 8 | left: 45%; 9 | font-size: 10rem; 10 | font-weight: bold; 11 | letter-spacing: -.025em; 12 | } 13 | 14 | .btnGroup { 15 | position: relative; 16 | top: 500px; 17 | width: 480px; 18 | margin: 0 auto; 19 | } 20 | 21 | .btn { 22 | font-size: 1.6rem; 23 | font-weight: bold; 24 | background-color: #fff; 25 | border-radius: 50%; 26 | margin: 10px; 27 | width: 100px; 28 | height: 100px; 29 | opacity: .7; 30 | cursor: pointer; 31 | font-family: Arial, Helvetica, Helvetica Neue; 32 | } 33 | 34 | .btn:hover { 35 | color: white; 36 | background-color: rgba(0, 0, 0, 0.5); 37 | } 38 | -------------------------------------------------------------------------------- /example/app/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Link } from 'react-router'; 3 | import styles from './Counter.css'; 4 | 5 | class Counter extends Component { 6 | static propTypes = { 7 | increment: PropTypes.func.isRequired, 8 | incrementIfOdd: PropTypes.func.isRequired, 9 | incrementAsync: PropTypes.func.isRequired, 10 | decrement: PropTypes.func.isRequired, 11 | counter: PropTypes.number.isRequired 12 | }; 13 | 14 | render() { 15 | const { increment, incrementIfOdd, incrementAsync, decrement, counter } = this.props; 16 | return ( 17 |
18 |
19 | 20 | 21 | 22 |
23 |
24 | {counter} 25 |
26 |
27 | 30 | 33 | 34 | 35 |
36 |
37 | ); 38 | } 39 | } 40 | 41 | export default Counter; 42 | -------------------------------------------------------------------------------- /example/app/components/Home.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: absolute; 3 | top: 30%; 4 | left: 10px; 5 | text-align: center; 6 | } 7 | 8 | .container h2 { 9 | font-size: 5rem; 10 | } 11 | 12 | .container a { 13 | font-size: 1.4rem; 14 | } 15 | -------------------------------------------------------------------------------- /example/app/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import styles from './Home.css'; 4 | 5 | 6 | export default class Home extends Component { 7 | render() { 8 | return ( 9 |
10 |
11 |

Home

12 | to Counter 13 |
14 |
15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/app/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | export default class App extends Component { 4 | static propTypes = { 5 | children: PropTypes.element.isRequired 6 | }; 7 | 8 | render() { 9 | return ( 10 |
{this.props.children}
11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/app/containers/CounterPage.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import Counter from '../components/Counter'; 4 | import * as CounterActions from '../actions/counter'; 5 | 6 | function mapStateToProps(state) { 7 | return { 8 | counter: state.counter 9 | }; 10 | } 11 | 12 | function mapDispatchToProps(dispatch) { 13 | return bindActionCreators(CounterActions, dispatch); 14 | } 15 | 16 | export default connect(mapStateToProps, mapDispatchToProps)(Counter); 17 | -------------------------------------------------------------------------------- /example/app/containers/HomePage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Home from '../components/Home'; 3 | 4 | export default class HomePage extends Component { 5 | render() { 6 | return ( 7 | 8 | ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/app/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { Router, hashHistory } from 'react-router'; 5 | import { syncHistoryWithStore } from 'react-router-redux'; 6 | import routes from './routes'; 7 | import configureStore from './store/configureStore'; 8 | import './app.global.css'; 9 | 10 | const store = configureStore(); 11 | const history = syncHistoryWithStore(hashHistory, store); 12 | 13 | render( 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ); 19 | -------------------------------------------------------------------------------- /example/app/reducers/counter.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions/counter'; 2 | 3 | export default function counter(state = 0, action) { 4 | switch (action.type) { 5 | case INCREMENT_COUNTER: 6 | return state + 1; 7 | case DECREMENT_COUNTER: 8 | return state - 1; 9 | default: 10 | return state; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer as routing } from 'react-router-redux'; 3 | import counter from './counter'; 4 | 5 | const rootReducer = combineReducers({ 6 | counter, 7 | routing 8 | }); 9 | 10 | export default rootReducer; 11 | -------------------------------------------------------------------------------- /example/app/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | import App from './containers/App'; 4 | import HomePage from './containers/HomePage'; 5 | import CounterPage from './containers/CounterPage'; 6 | 7 | 8 | export default ( 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /example/app/store/configureStore.development.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import createLogger from 'redux-logger'; 4 | import { hashHistory } from 'react-router'; 5 | import { routerMiddleware } from 'react-router-redux'; 6 | import rootReducer from '../reducers'; 7 | import devTools from 'remote-redux-devtools'; 8 | 9 | const logger = createLogger({ 10 | level: 'info', 11 | collapsed: true, 12 | }); 13 | 14 | const router = routerMiddleware(hashHistory); 15 | 16 | const enhancer = compose( 17 | applyMiddleware(thunk, router, logger), 18 | devTools({ 19 | name: 'Electron', 20 | hostname: 'localhost', 21 | port: 8000 22 | }) 23 | ); 24 | 25 | export default function configureStore(initialState) { 26 | const store = createStore(rootReducer, initialState, enhancer); 27 | 28 | if (module.hot) { 29 | module.hot.accept('../reducers', () => 30 | store.replaceReducer(require('../reducers')) 31 | ); 32 | } 33 | 34 | return store; 35 | } 36 | -------------------------------------------------------------------------------- /example/app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./configureStore.production'); 3 | } else { 4 | module.exports = require('./configureStore.development'); 5 | } 6 | -------------------------------------------------------------------------------- /example/app/store/configureStore.production.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { hashHistory } from 'react-router'; 4 | import { routerMiddleware } from 'react-router-redux'; 5 | import rootReducer from '../reducers'; 6 | 7 | const router = routerMiddleware(hashHistory); 8 | 9 | const enhancer = applyMiddleware(thunk, router); 10 | 11 | export default function configureStore(initialState) { 12 | return createStore(rootReducer, initialState, enhancer); 13 | } 14 | -------------------------------------------------------------------------------- /example/app/utils/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhen0409/remotedev-extension/2f95aef4b8d1276d93b9126d90d1f6540bc0e679/example/app/utils/.gitkeep -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0 */ 2 | 'use strict'; 3 | 4 | process.env.NODE_ENV = process.env.NODE_ENV || 'production'; 5 | 6 | const electron = require('electron'); 7 | const app = electron.app; 8 | const BrowserWindow = electron.BrowserWindow; 9 | const Menu = electron.Menu; 10 | const crashReporter = electron.crashReporter; 11 | const shell = electron.shell; 12 | let menu; 13 | let template; 14 | let mainWindow = null; 15 | 16 | crashReporter.start(); 17 | 18 | app.on('window-all-closed', () => { 19 | if (process.platform !== 'darwin') app.quit(); 20 | }); 21 | 22 | app.on('ready', () => { 23 | mainWindow = new BrowserWindow({ width: 1024, height: 728 }); 24 | 25 | mainWindow.loadURL(`file://${__dirname}/app/app.html`); 26 | 27 | mainWindow.on('closed', () => { 28 | mainWindow = null; 29 | }); 30 | 31 | if (process.env.NODE_ENV === 'development') { 32 | // Add DevTools Extension 33 | BrowserWindow.removeDevToolsExtension('RemoteDev DevTools'); 34 | BrowserWindow.addDevToolsExtension('node_modules/remotedev-extension/dist'); 35 | mainWindow.openDevTools(); 36 | } 37 | 38 | if (process.platform === 'darwin') { 39 | template = [{ 40 | label: 'Electron', 41 | submenu: [{ 42 | label: 'About ElectronReact', 43 | selector: 'orderFrontStandardAboutPanel:' 44 | }, { 45 | type: 'separator' 46 | }, { 47 | label: 'Services', 48 | submenu: [] 49 | }, { 50 | type: 'separator' 51 | }, { 52 | label: 'Hide ElectronReact', 53 | accelerator: 'Command+H', 54 | selector: 'hide:' 55 | }, { 56 | label: 'Hide Others', 57 | accelerator: 'Command+Shift+H', 58 | selector: 'hideOtherApplications:' 59 | }, { 60 | label: 'Show All', 61 | selector: 'unhideAllApplications:' 62 | }, { 63 | type: 'separator' 64 | }, { 65 | label: 'Quit', 66 | accelerator: 'Command+Q', 67 | click() { 68 | app.quit(); 69 | } 70 | }] 71 | }, { 72 | label: 'Edit', 73 | submenu: [{ 74 | label: 'Undo', 75 | accelerator: 'Command+Z', 76 | selector: 'undo:' 77 | }, { 78 | label: 'Redo', 79 | accelerator: 'Shift+Command+Z', 80 | selector: 'redo:' 81 | }, { 82 | type: 'separator' 83 | }, { 84 | label: 'Cut', 85 | accelerator: 'Command+X', 86 | selector: 'cut:' 87 | }, { 88 | label: 'Copy', 89 | accelerator: 'Command+C', 90 | selector: 'copy:' 91 | }, { 92 | label: 'Paste', 93 | accelerator: 'Command+V', 94 | selector: 'paste:' 95 | }, { 96 | label: 'Select All', 97 | accelerator: 'Command+A', 98 | selector: 'selectAll:' 99 | }] 100 | }, { 101 | label: 'View', 102 | submenu: (process.env.NODE_ENV === 'development') ? [{ 103 | label: 'Reload', 104 | accelerator: 'Command+R', 105 | click() { 106 | mainWindow.restart(); 107 | } 108 | }, { 109 | label: 'Toggle Full Screen', 110 | accelerator: 'Ctrl+Command+F', 111 | click() { 112 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 113 | } 114 | }, { 115 | label: 'Toggle Developer Tools', 116 | accelerator: 'Alt+Command+I', 117 | click() { 118 | mainWindow.toggleDevTools(); 119 | } 120 | }] : [{ 121 | label: 'Toggle Full Screen', 122 | accelerator: 'Ctrl+Command+F', 123 | click() { 124 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 125 | } 126 | }] 127 | }, { 128 | label: 'Window', 129 | submenu: [{ 130 | label: 'Minimize', 131 | accelerator: 'Command+M', 132 | selector: 'performMiniaturize:' 133 | }, { 134 | label: 'Close', 135 | accelerator: 'Command+W', 136 | selector: 'performClose:' 137 | }, { 138 | type: 'separator' 139 | }, { 140 | label: 'Bring All to Front', 141 | selector: 'arrangeInFront:' 142 | }] 143 | }, { 144 | label: 'Help', 145 | submenu: [{ 146 | label: 'Learn More', 147 | click() { 148 | shell.openExternal('http://electron.atom.io'); 149 | } 150 | }, { 151 | label: 'Documentation', 152 | click() { 153 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); 154 | } 155 | }, { 156 | label: 'Community Discussions', 157 | click() { 158 | shell.openExternal('https://discuss.atom.io/c/electron'); 159 | } 160 | }, { 161 | label: 'Search Issues', 162 | click() { 163 | shell.openExternal('https://github.com/atom/electron/issues'); 164 | } 165 | }] 166 | }]; 167 | 168 | menu = Menu.buildFromTemplate(template); 169 | Menu.setApplicationMenu(menu); 170 | } else { 171 | template = [{ 172 | label: '&File', 173 | submenu: [{ 174 | label: '&Open', 175 | accelerator: 'Ctrl+O' 176 | }, { 177 | label: '&Close', 178 | accelerator: 'Ctrl+W', 179 | click() { 180 | mainWindow.close(); 181 | } 182 | }] 183 | }, { 184 | label: '&View', 185 | submenu: (process.env.NODE_ENV === 'development') ? [{ 186 | label: '&Reload', 187 | accelerator: 'Ctrl+R', 188 | click() { 189 | mainWindow.restart(); 190 | } 191 | }, { 192 | label: 'Toggle &Full Screen', 193 | accelerator: 'F11', 194 | click() { 195 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 196 | } 197 | }, { 198 | label: 'Toggle &Developer Tools', 199 | accelerator: 'Alt+Ctrl+I', 200 | click() { 201 | mainWindow.toggleDevTools(); 202 | } 203 | }] : [{ 204 | label: 'Toggle &Full Screen', 205 | accelerator: 'F11', 206 | click() { 207 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 208 | } 209 | }] 210 | }, { 211 | label: 'Help', 212 | submenu: [{ 213 | label: 'Learn More', 214 | click() { 215 | shell.openExternal('http://electron.atom.io'); 216 | } 217 | }, { 218 | label: 'Documentation', 219 | click() { 220 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); 221 | } 222 | }, { 223 | label: 'Community Discussions', 224 | click() { 225 | shell.openExternal('https://discuss.atom.io/c/electron'); 226 | } 227 | }, { 228 | label: 'Search Issues', 229 | click() { 230 | shell.openExternal('https://github.com/atom/electron/issues'); 231 | } 232 | }] 233 | }]; 234 | menu = Menu.buildFromTemplate(template); 235 | mainWindow.setMenu(menu); 236 | } 237 | }); 238 | -------------------------------------------------------------------------------- /example/package.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0, no-shadow: 0, no-unused-vars: 0, no-console: 0 */ 2 | 'use strict'; 3 | 4 | const os = require('os'); 5 | const webpack = require('webpack'); 6 | const cfg = require('./webpack.config.production.js'); 7 | const packager = require('electron-packager'); 8 | const del = require('del'); 9 | const exec = require('child_process').exec; 10 | const argv = require('minimist')(process.argv.slice(2)); 11 | const pkg = require('./package.json'); 12 | const devDeps = Object.keys(pkg.devDependencies); 13 | 14 | const appName = argv.name || argv.n || pkg.productName; 15 | const shouldUseAsar = argv.asar || argv.a || false; 16 | const shouldBuildAll = argv.all || false; 17 | 18 | 19 | const DEFAULT_OPTS = { 20 | dir: './', 21 | name: appName, 22 | asar: shouldUseAsar, 23 | ignore: [ 24 | '/test($|/)', 25 | '/tools($|/)', 26 | '/release($|/)' 27 | ].concat(devDeps.map(name => `/node_modules/${name}($|/)`)) 28 | }; 29 | 30 | const icon = argv.icon || argv.i || 'app/app'; 31 | 32 | if (icon) { 33 | DEFAULT_OPTS.icon = icon; 34 | } 35 | 36 | const version = argv.version || argv.v; 37 | 38 | if (version) { 39 | DEFAULT_OPTS.version = version; 40 | startPack(); 41 | } else { 42 | // use the same version as the currently-installed electron-prebuilt 43 | exec('npm list electron-prebuilt --dev', (err, stdout) => { 44 | if (err) { 45 | DEFAULT_OPTS.version = '0.37.2'; 46 | } else { 47 | DEFAULT_OPTS.version = stdout.split('electron-prebuilt@')[1].replace(/\s/g, ''); 48 | } 49 | 50 | startPack(); 51 | }); 52 | } 53 | 54 | 55 | function startPack() { 56 | console.log('start pack...'); 57 | webpack(cfg, (err, stats) => { 58 | if (err) return console.error(err); 59 | del('release') 60 | .then(paths => { 61 | if (shouldBuildAll) { 62 | // build for all platforms 63 | const archs = ['ia32', 'x64']; 64 | const platforms = ['linux', 'win32', 'darwin']; 65 | 66 | platforms.forEach(plat => { 67 | archs.forEach(arch => { 68 | pack(plat, arch, log(plat, arch)); 69 | }); 70 | }); 71 | } else { 72 | // build for current platform only 73 | pack(os.platform(), os.arch(), log(os.platform(), os.arch())); 74 | } 75 | }) 76 | .catch(err => { 77 | console.error(err); 78 | }); 79 | }); 80 | } 81 | 82 | function pack(plat, arch, cb) { 83 | // there is no darwin ia32 electron 84 | if (plat === 'darwin' && arch === 'ia32') return; 85 | 86 | const iconObj = { 87 | icon: DEFAULT_OPTS.icon + (() => { 88 | let extension = '.png'; 89 | if (plat === 'darwin') { 90 | extension = '.icns'; 91 | } else if (plat === 'win32') { 92 | extension = '.ico'; 93 | } 94 | return extension; 95 | })() 96 | }; 97 | 98 | const opts = Object.assign({}, DEFAULT_OPTS, iconObj, { 99 | platform: plat, 100 | arch, 101 | prune: true, 102 | 'app-version': pkg.version || DEFAULT_OPTS.version, 103 | out: `release/${plat}-${arch}` 104 | }); 105 | 106 | packager(opts, cb); 107 | } 108 | 109 | 110 | function log(plat, arch) { 111 | return (err, filepath) => { 112 | if (err) return console.error(err); 113 | console.log(`${plat}-${arch} finished!`); 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react-boilerplate", 3 | "productName": "ElectronReact", 4 | "version": "0.9.0", 5 | "description": "Electron application boilerplate based on React, React Router, Webpack, React Hot Loader for rapid application development", 6 | "main": "main.js", 7 | "scripts": { 8 | "test": "cross-env NODE_ENV=test mocha --compilers js:babel-core/register --recursive --require ./test/setup.js test/**/*.spec.js", 9 | "test-watch": "npm test -- --watch", 10 | "test-e2e": "cross-env NODE_ENV=test mocha --compilers js:babel-core/register --require ./test/setup.js --require co-mocha ./test/e2e.js", 11 | "lint": "eslint app test *.js", 12 | "hot-server": "node server.js", 13 | "build": "cross-env NODE_ENV=production webpack --config webpack.config.production.js --progress --profile --colors", 14 | "start": "cross-env NODE_ENV=production electron ./", 15 | "start-hot": "cross-env HOT=1 NODE_ENV=development electron ./", 16 | "package": "cross-env NODE_ENV=production node package.js", 17 | "package-all": "npm run package -- --all", 18 | "postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json", 19 | "dev": "concurrently --kill-others \"npm run hot-server\" \"npm run start-hot\"" 20 | }, 21 | "bin": { 22 | "electron": "./node_modules/.bin/electron" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/chentsulin/electron-react-boilerplate.git" 27 | }, 28 | "author": { 29 | "name": "C. T. Lin", 30 | "email": "chentsulin@gmail.com", 31 | "url": "https://github.com/chentsulin" 32 | }, 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/chentsulin/electron-react-boilerplate/issues" 36 | }, 37 | "keywords": [ 38 | "electron", 39 | "boilerplate", 40 | "react", 41 | "react-router", 42 | "flux", 43 | "webpack", 44 | "react-hot" 45 | ], 46 | "homepage": "https://github.com/chentsulin/electron-react-boilerplate#readme", 47 | "devDependencies": { 48 | "asar": "^0.10.0", 49 | "babel-core": "^6.7.4", 50 | "babel-eslint": "^6.0.0", 51 | "babel-loader": "^6.2.4", 52 | "babel-plugin-add-module-exports": "^0.1.2", 53 | "babel-plugin-webpack-loaders": "^0.4.0", 54 | "babel-polyfill": "^6.7.4", 55 | "babel-preset-es2015": "^6.6.0", 56 | "babel-preset-react": "^6.5.0", 57 | "babel-preset-react-hmre": "^1.1.1", 58 | "babel-preset-stage-0": "^6.5.0", 59 | "chai": "^3.5.0", 60 | "chromedriver": "^2.21.2", 61 | "co-mocha": "^1.1.2", 62 | "concurrently": "^2.0.0", 63 | "cross-env": "^1.0.7", 64 | "css-loader": "^0.23.1", 65 | "del": "^2.2.0", 66 | "electron-packager": "^6.0.0", 67 | "electron-prebuilt": "^0.37.2", 68 | "electron-rebuild": "^1.1.3", 69 | "eslint": "^2.5.3", 70 | "eslint-config-airbnb": "^6.2.0", 71 | "eslint-plugin-react": "^4.2.3", 72 | "express": "^4.13.4", 73 | "extract-text-webpack-plugin": "^1.0.1", 74 | "fbjs-scripts": "^0.5.0", 75 | "jsdom": "^8.2.0", 76 | "json-loader": "^0.5.4", 77 | "minimist": "^1.2.0", 78 | "mocha": "^2.4.5", 79 | "node-libs-browser": "^1.0.0", 80 | "react-addons-test-utils": "^0.14.7", 81 | "redux-logger": "^2.6.1", 82 | "remote-redux-devtools": "^0.1.5", 83 | "remotedev-extension": "0.0.9", 84 | "selenium-webdriver": "^2.53.1", 85 | "sinon": "^1.17.3", 86 | "style-loader": "^0.13.1", 87 | "webpack": "^1.12.14", 88 | "webpack-dev-middleware": "^1.6.1", 89 | "webpack-hot-middleware": "^2.10.0", 90 | "webpack-target-electron-renderer": "^0.4.0" 91 | }, 92 | "dependencies": { 93 | "css-modules-require-hook": "^4.0.0", 94 | "electron-debug": "^0.5.2", 95 | "font-awesome": "^4.5.0", 96 | "postcss": "^5.0.19", 97 | "react": "^0.14.7", 98 | "react-dom": "^0.14.7", 99 | "react-redux": "^4.4.1", 100 | "react-router": "^2.0.1", 101 | "react-router-redux": "^4.0.0", 102 | "redux": "^3.3.1", 103 | "redux-thunk": "^2.0.1" 104 | }, 105 | "devEngines": { 106 | "node": "4.x || 5.x", 107 | "npm": "2.x || 3.x" 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0, no-console: 0 */ 2 | 'use strict'; 3 | 4 | const express = require('express'); 5 | const webpack = require('webpack'); 6 | const config = require('./webpack.config.development'); 7 | 8 | const app = express(); 9 | const compiler = webpack(config); 10 | 11 | const PORT = 3000; 12 | 13 | app.use(require('webpack-dev-middleware')(compiler, { 14 | publicPath: config.output.publicPath, 15 | stats: { 16 | colors: true 17 | } 18 | })); 19 | 20 | app.use(require('webpack-hot-middleware')(compiler)); 21 | 22 | app.listen(PORT, 'localhost', err => { 23 | if (err) { 24 | console.log(err); 25 | return; 26 | } 27 | 28 | console.log(`Listening at http://localhost:${PORT}`); 29 | }); 30 | 31 | const remotedev = require('remotedev-extension'); 32 | remotedev({ 33 | hostname: 'localhost', 34 | port: 8000, 35 | runserver: true, 36 | 'ui-no-buttonbar': true 37 | }); 38 | -------------------------------------------------------------------------------- /example/test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /example/test/actions/counter.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-expressions: 0 */ 2 | import { expect } from 'chai'; 3 | import { spy } from 'sinon'; 4 | import * as actions from '../../app/actions/counter'; 5 | 6 | 7 | describe('actions', () => { 8 | it('increment should create increment action', () => { 9 | expect(actions.increment()).to.deep.equal({ type: actions.INCREMENT_COUNTER }); 10 | }); 11 | 12 | it('decrement should create decrement action', () => { 13 | expect(actions.decrement()).to.deep.equal({ type: actions.DECREMENT_COUNTER }); 14 | }); 15 | 16 | it('incrementIfOdd should create increment action', () => { 17 | const fn = actions.incrementIfOdd(); 18 | expect(fn).to.be.a('function'); 19 | const dispatch = spy(); 20 | const getState = () => ({ counter: 1 }); 21 | fn(dispatch, getState); 22 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).to.be.true; 23 | }); 24 | 25 | it('incrementIfOdd shouldnt create increment action if counter is even', () => { 26 | const fn = actions.incrementIfOdd(); 27 | const dispatch = spy(); 28 | const getState = () => ({ counter: 2 }); 29 | fn(dispatch, getState); 30 | expect(dispatch.called).to.be.false; 31 | }); 32 | 33 | // There's no nice way to test this at the moment... 34 | it('incrementAsync', (done) => { 35 | const fn = actions.incrementAsync(1); 36 | expect(fn).to.be.a('function'); 37 | const dispatch = spy(); 38 | fn(dispatch); 39 | setTimeout(() => { 40 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).to.be.true; 41 | done(); 42 | }, 5); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /example/test/components/Counter.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-expressions: 0 */ 2 | import { expect } from 'chai'; 3 | import { spy } from 'sinon'; 4 | import React from 'react'; 5 | import { 6 | renderIntoDocument, 7 | scryRenderedDOMComponentsWithTag, 8 | findRenderedDOMComponentWithClass, 9 | Simulate 10 | } from 'react-addons-test-utils'; 11 | import Counter from '../../app/components/Counter'; 12 | 13 | 14 | function setup() { 15 | const actions = { 16 | increment: spy(), 17 | incrementIfOdd: spy(), 18 | incrementAsync: spy(), 19 | decrement: spy() 20 | }; 21 | const component = renderIntoDocument(); 22 | return { 23 | component, 24 | actions, 25 | buttons: scryRenderedDOMComponentsWithTag(component, 'button').map(button => button), 26 | p: findRenderedDOMComponentWithClass(component, 'counter') 27 | }; 28 | } 29 | 30 | 31 | describe('Counter component', () => { 32 | it('should display count', () => { 33 | const { p } = setup(); 34 | expect(p.textContent).to.match(/^1$/); 35 | }); 36 | 37 | it('first button should call increment', () => { 38 | const { buttons, actions } = setup(); 39 | Simulate.click(buttons[0]); 40 | expect(actions.increment.called).to.be.true; 41 | }); 42 | 43 | it('second button should call decrement', () => { 44 | const { buttons, actions } = setup(); 45 | Simulate.click(buttons[1]); 46 | expect(actions.decrement.called).to.be.true; 47 | }); 48 | 49 | it('third button should call incrementIfOdd', () => { 50 | const { buttons, actions } = setup(); 51 | Simulate.click(buttons[2]); 52 | expect(actions.incrementIfOdd.called).to.be.true; 53 | }); 54 | 55 | it('fourth button should call incrementAsync', () => { 56 | const { buttons, actions } = setup(); 57 | Simulate.click(buttons[3]); 58 | expect(actions.incrementAsync.called).to.be.true; 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /example/test/containers/CounterPage.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import React from 'react'; 3 | import { 4 | renderIntoDocument, 5 | scryRenderedDOMComponentsWithTag, 6 | findRenderedDOMComponentWithClass, 7 | Simulate 8 | } from 'react-addons-test-utils'; 9 | import { Provider } from 'react-redux'; 10 | import CounterPage from '../../app/containers/CounterPage'; 11 | import configureStore from '../../app/store/configureStore'; 12 | 13 | 14 | function setup(initialState) { 15 | const store = configureStore(initialState); 16 | const app = renderIntoDocument( 17 | 18 | 19 | 20 | ); 21 | return { 22 | app, 23 | buttons: scryRenderedDOMComponentsWithTag(app, 'button').map(button => button), 24 | p: findRenderedDOMComponentWithClass(app, 'counter') 25 | }; 26 | } 27 | 28 | 29 | describe('containers', () => { 30 | describe('App', () => { 31 | it('should display initial count', () => { 32 | const { p } = setup(); 33 | expect(p.textContent).to.match(/^0$/); 34 | }); 35 | 36 | it('should display updated count after increment button click', () => { 37 | const { buttons, p } = setup(); 38 | Simulate.click(buttons[0]); 39 | expect(p.textContent).to.match(/^1$/); 40 | }); 41 | 42 | it('should display updated count after descrement button click', () => { 43 | const { buttons, p } = setup(); 44 | Simulate.click(buttons[1]); 45 | expect(p.textContent).to.match(/^-1$/); 46 | }); 47 | 48 | it('shouldnt change if even and if odd button clicked', () => { 49 | const { buttons, p } = setup(); 50 | Simulate.click(buttons[2]); 51 | expect(p.textContent).to.match(/^0$/); 52 | }); 53 | 54 | it('should change if odd and if odd button clicked', () => { 55 | const { buttons, p } = setup({ counter: 1 }); 56 | Simulate.click(buttons[2]); 57 | expect(p.textContent).to.match(/^2$/); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /example/test/e2e.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import chromedriver from 'chromedriver'; 3 | import webdriver from 'selenium-webdriver'; 4 | import { expect } from 'chai'; 5 | import electronPath from 'electron-prebuilt'; 6 | import homeStyles from '../app/components/Home.css'; 7 | import counterStyles from '../app/components/Counter.css'; 8 | 9 | chromedriver.start(); // on port 9515 10 | process.on('exit', chromedriver.stop); 11 | 12 | const delay = time => new Promise(resolve => setTimeout(resolve, time)); 13 | 14 | describe('main window', function spec() { 15 | this.timeout(5000); 16 | 17 | before(async () => { 18 | await delay(1000); // wait chromedriver start time 19 | this.driver = new webdriver.Builder() 20 | .usingServer('http://localhost:9515') 21 | .withCapabilities({ 22 | chromeOptions: { 23 | binary: electronPath, 24 | args: [`app=${path.resolve()}`] 25 | } 26 | }) 27 | .forBrowser('electron') 28 | .build(); 29 | }); 30 | 31 | after(async () => { 32 | await this.driver.quit(); 33 | }); 34 | 35 | const findCounter = () => this.driver.findElement(webdriver.By.className(counterStyles.counter)); 36 | 37 | const findButtons = () => this.driver.findElements(webdriver.By.className(counterStyles.btn)); 38 | 39 | it('should open window', async () => { 40 | const title = await this.driver.getTitle(); 41 | expect(title).to.equal('Hello Electron React!'); 42 | }); 43 | 44 | it('should to Counter with click "to Counter" link', async () => { 45 | const link = await this.driver.findElement(webdriver.By.css(`.${homeStyles.container} > a`)); 46 | link.click(); 47 | 48 | const counter = await findCounter(); 49 | expect(await counter.getText()).to.equal('0'); 50 | }); 51 | 52 | it('should display updated count after increment button click', async () => { 53 | const buttons = await findButtons(); 54 | buttons[0].click(); 55 | 56 | const counter = await findCounter(); 57 | expect(await counter.getText()).to.equal('1'); 58 | }); 59 | 60 | it('should display updated count after descrement button click', async () => { 61 | const buttons = await findButtons(); 62 | const counter = await findCounter(); 63 | 64 | buttons[1].click(); // - 65 | 66 | expect(await counter.getText()).to.equal('0'); 67 | }); 68 | 69 | it('shouldnt change if even and if odd button clicked', async () => { 70 | const buttons = await findButtons(); 71 | const counter = await findCounter(); 72 | buttons[2].click(); // odd 73 | 74 | expect(await counter.getText()).to.equal('0'); 75 | }); 76 | 77 | it('should change if odd and if odd button clicked', async () => { 78 | const buttons = await findButtons(); 79 | const counter = await findCounter(); 80 | 81 | buttons[0].click(); // + 82 | buttons[2].click(); // odd 83 | 84 | expect(await counter.getText()).to.equal('2'); 85 | }); 86 | 87 | it('should change if async button clicked and a second later', async () => { 88 | const buttons = await findButtons(); 89 | const counter = await findCounter(); 90 | buttons[3].click(); // async 91 | 92 | expect(await counter.getText()).to.equal('2'); 93 | 94 | await this.driver.wait(() => 95 | counter.getText().then(text => text === '3') 96 | , 1000, 'count not as expected'); 97 | }); 98 | 99 | it('should back to home if back button clicked', async () => { 100 | const link = await this.driver.findElement( 101 | webdriver.By.css(`.${counterStyles.backButton} > a`) 102 | ); 103 | link.click(); 104 | 105 | await this.driver.findElement(webdriver.By.className(homeStyles.container)); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /example/test/example.js: -------------------------------------------------------------------------------- 1 | /* eslint func-names: 0 */ 2 | import { expect } from 'chai'; 3 | 4 | 5 | describe('description', () => { 6 | it('description', () => { 7 | expect(1 + 2).to.equal(3); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /example/test/reducers/counter.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import counter from '../../app/reducers/counter'; 3 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../../app/actions/counter'; 4 | 5 | 6 | describe('reducers', () => { 7 | describe('counter', () => { 8 | it('should handle initial state', () => { 9 | expect(counter(undefined, {})).to.equal(0); 10 | }); 11 | 12 | it('should handle INCREMENT_COUNTER', () => { 13 | expect(counter(1, { type: INCREMENT_COUNTER })).to.equal(2); 14 | }); 15 | 16 | it('should handle DECREMENT_COUNTER', () => { 17 | expect(counter(1, { type: DECREMENT_COUNTER })).to.equal(0); 18 | }); 19 | 20 | it('should handle unknown action type', () => { 21 | expect(counter(1, { type: 'unknown' })).to.equal(1); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /example/test/setup.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import { jsdom } from 'jsdom'; 3 | 4 | global.document = jsdom(''); 5 | global.window = document.defaultView; 6 | global.navigator = global.window.navigator; 7 | window.localStorage = window.sessionStorage = { 8 | getItem(key) { 9 | return this[key]; 10 | }, 11 | setItem(key, value) { 12 | this[key] = value; 13 | }, 14 | removeItem(key) { 15 | this[key] = undefined; 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /example/webpack.config.base.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0 */ 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | 6 | module.exports = { 7 | module: { 8 | loaders: [{ 9 | test: /\.jsx?$/, 10 | loaders: ['babel-loader'], 11 | exclude: /node_modules/ 12 | }, { 13 | test: /\.json$/, 14 | loader: 'json-loader' 15 | }] 16 | }, 17 | output: { 18 | path: path.join(__dirname, 'dist'), 19 | filename: 'bundle.js', 20 | libraryTarget: 'commonjs2' 21 | }, 22 | resolve: { 23 | extensions: ['', '.js', '.jsx'], 24 | packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'] 25 | }, 26 | plugins: [ 27 | 28 | ], 29 | externals: [ 30 | // put your node 3rd party libraries which can't be built with webpack here 31 | // (mysql, mongodb, and so on..) 32 | ] 33 | }; 34 | -------------------------------------------------------------------------------- /example/webpack.config.development.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0 */ 2 | 'use strict'; 3 | 4 | const webpack = require('webpack'); 5 | const webpackTargetElectronRenderer = require('webpack-target-electron-renderer'); 6 | const baseConfig = require('./webpack.config.base'); 7 | 8 | 9 | const config = Object.create(baseConfig); 10 | 11 | config.debug = true; 12 | 13 | config.devtool = 'cheap-module-eval-source-map'; 14 | 15 | config.entry = [ 16 | 'webpack-hot-middleware/client?path=http://localhost:3000/__webpack_hmr', 17 | './app/index' 18 | ]; 19 | 20 | config.output.publicPath = 'http://localhost:3000/dist/'; 21 | 22 | config.module.loaders.push({ 23 | test: /\.global\.css$/, 24 | loaders: [ 25 | 'style-loader', 26 | 'css-loader?sourceMap' 27 | ] 28 | }, { 29 | test: /^((?!\.global).)*\.css$/, 30 | loaders: [ 31 | 'style-loader', 32 | 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' 33 | ] 34 | }); 35 | 36 | 37 | config.plugins.push( 38 | new webpack.HotModuleReplacementPlugin(), 39 | new webpack.NoErrorsPlugin(), 40 | new webpack.DefinePlugin({ 41 | __DEV__: true, 42 | 'process.env': { 43 | NODE_ENV: JSON.stringify('development') 44 | } 45 | }) 46 | ); 47 | 48 | config.target = webpackTargetElectronRenderer(config); 49 | 50 | module.exports = config; 51 | -------------------------------------------------------------------------------- /example/webpack.config.node.js: -------------------------------------------------------------------------------- 1 | // for babel-plugin-webpack-loaders 2 | const devConfigs = require('./webpack.config.development'); 3 | 4 | module.exports = { 5 | output: { 6 | libraryTarget: 'commonjs2' 7 | }, 8 | module: { 9 | loaders: devConfigs.module.loaders.slice(1) // remove babel-loader 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /example/webpack.config.production.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0 */ 2 | 'use strict'; 3 | 4 | const webpack = require('webpack'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const webpackTargetElectronRenderer = require('webpack-target-electron-renderer'); 7 | const baseConfig = require('./webpack.config.base'); 8 | 9 | 10 | const config = Object.create(baseConfig); 11 | 12 | config.devtool = 'source-map'; 13 | 14 | config.entry = './app/index'; 15 | 16 | config.output.publicPath = '../dist/'; 17 | 18 | config.module.loaders.push({ 19 | test: /\.global\.css$/, 20 | loader: ExtractTextPlugin.extract( 21 | 'style-loader', 22 | 'css-loader' 23 | ) 24 | }, { 25 | test: /^((?!\.global).)*\.css$/, 26 | loader: ExtractTextPlugin.extract( 27 | 'style-loader', 28 | 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' 29 | ) 30 | }); 31 | 32 | config.plugins.push( 33 | new webpack.optimize.OccurenceOrderPlugin(), 34 | new webpack.DefinePlugin({ 35 | __DEV__: false, 36 | 'process.env': { 37 | NODE_ENV: JSON.stringify('production') 38 | } 39 | }), 40 | new webpack.optimize.UglifyJsPlugin({ 41 | compressor: { 42 | screw_ie8: true, 43 | warnings: false 44 | } 45 | }), 46 | new ExtractTextPlugin('style.css', { allChunks: true }) 47 | ); 48 | 49 | config.target = webpackTargetElectronRenderer(config); 50 | 51 | module.exports = config; 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remotedev-extension", 3 | "version": "0.0.10", 4 | "description": "Use Redux DevTools in the Browser/Electron DevTools", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/jhen0409/remotedev-extension.git" 8 | }, 9 | "keywords": [ 10 | "remote", 11 | "redux", 12 | "devtools", 13 | "remotedev", 14 | "react", 15 | "chrome", 16 | "opera", 17 | "electron", 18 | "extension" 19 | ], 20 | "files": [ 21 | "bin", 22 | "dist" 23 | ], 24 | "main": "bin/extension.js", 25 | "bin": { 26 | "remotedev-extension": "bin/cli.js" 27 | }, 28 | "scripts": { 29 | "compress": "node bin/cli.js && nodezip -c dist.zip dist", 30 | "build": "webpack --stats --progress", 31 | "prepublish": "node bin/cli.js && npm run build" 32 | }, 33 | "author": "Jhen ", 34 | "license": "MIT", 35 | "devDependencies": { 36 | "node-zip": "^1.1.1", 37 | "react": "^0.14.7", 38 | "react-dom": "^0.14.7", 39 | "remotedev-app": "^0.3.4", 40 | "webpack": "^1.12.12" 41 | }, 42 | "dependencies": { 43 | "minimist": "^1.2.0", 44 | "remotedev-server": "0.0.7" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/devpanel.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const ReactDOM = require('react-dom'); 3 | const DevTools = require('remotedev-app'); 4 | 5 | const remotedevOptions = window.remotedevOptions; 6 | const options = { 7 | socketOptions: remotedevOptions, 8 | noButtonBar: false 9 | }; 10 | 11 | // prevent setting from previous UI setting 12 | if (remotedevOptions && remotedevOptions.noButtonBar) { 13 | options.noButtonBar = true; 14 | localStorage.removeItem('s:hostname'); 15 | localStorage.removeItem('s:port'); 16 | } 17 | 18 | ReactDOM.render( 19 | React.createElement(DevTools, options), 20 | document.querySelector('#root') 21 | ); 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const port = 3030; 4 | 5 | module.exports = { 6 | entry: './src/devpanel', 7 | output: { 8 | path: path.join(__dirname, 'dist/js'), 9 | filename: 'devpanel.bundle.js', 10 | publicPath: `/js/` 11 | }, 12 | plugins: [ 13 | new webpack.optimize.DedupePlugin(), 14 | new webpack.optimize.UglifyJsPlugin({ 15 | comments: false, 16 | compressor: { 17 | warnings: false 18 | } 19 | }), 20 | new webpack.DefinePlugin({ 21 | 'process.env': { 22 | 'NODE_ENV': JSON.stringify('production') 23 | } 24 | }) 25 | ], 26 | resolve: { 27 | extensions: ['', '.js'] 28 | } 29 | }; 30 | --------------------------------------------------------------------------------