diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..86c445f54 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015", "react"] +} diff --git a/app/__tests__/main.test.js b/app/__tests__/main.test.js new file mode 100644 index 000000000..013366dec --- /dev/null +++ b/app/__tests__/main.test.js @@ -0,0 +1,22 @@ +// import {expect, unmock, it, describe} from 'jest'; + +jest.unmock('../components/Sidebar'); + +import React from 'react'; +import ReactDOM from 'react-dom'; +import TestUtils from 'react-addons-test-utils'; +import Sidebar from '../components/Sidebar'; + +describe('Sidebar', () => { + it('changes the text after click', () => { + // Render a checkbox with label in the document + const sidebar = TestUtils.renderIntoDocument( +
+ ); + + const sidebarNode = ReactDOM.findDOMNode(sidebar); + + // Verify that it's Off by default + expect(sidebarNode.textContent).toEqual('Off'); + }); +}); diff --git a/app/actions/global.js b/app/actions/global.js new file mode 100644 index 000000000..37b512467 --- /dev/null +++ b/app/actions/global.js @@ -0,0 +1,23 @@ +import * as types from '../constants/actionTypes'; +import {LOCALSTORAGE_KEY} from "../constants/global"; + +export function restoreState () { + return (dispatch) => { + setTimeout(() => { + let state = undefined; + try { + state = JSON.parse(localStorage[LOCALSTORAGE_KEY]); + } catch (e) { } + + dispatch({type: types.GLOBAL_STATE_RESTORED, state}); + }, 300); + } +} + +export function loadStart () { + return {type: types.GLOBAL_LOAD_START}; +} + +export function loadStop () { + return {type: types.GLOBAL_LOAD_STOP}; +} diff --git a/app/actions/index.js b/app/actions/index.js deleted file mode 100644 index f05e9a8b5..000000000 --- a/app/actions/index.js +++ /dev/null @@ -1,69 +0,0 @@ -import * as types from '../constants/ActionTypes' - -export function addTodo (text) { - return (dispatch, getState) => { - dispatch(loadStart()); - - setTimeout(() => { - let id = Date.now(); - let completed = false; - localStorage['todos'] = JSON.stringify( - [...JSON.parse(localStorage['todos'] || '[]'), { - text: text, - id: id, - completed: completed - }] - ); - dispatch({type: types.ADD_TODO, text, id, completed}); - dispatch(loadStop()); - }, 300); - }; -} - -export function loadTodos () { - return (dispatch) => { - dispatch(loadStart()); - setTimeout(() => { - let todos = JSON.parse(localStorage['todos'] || '[]'); - dispatch({type: types.LOAD_TODOS, todos}) - }, 300); - } -} - -export function loadStart () { - return {type: types.LOAD_START}; -} - -export function loadStop () { - return {type: types.LOAD_STOP}; -} - -export function deleteTodo (id) { - return (dispatch) => { - dispatch(loadStart()); - setTimeout(() => { - - localStorage['todos'] = JSON.stringify( - JSON.parse(localStorage['todos'] || '[]').filter(t => t.id !== id) - ); - dispatch({type: types.DELETE_TODO, id}); - dispatch(loadStop()); - }, 300); - } -} - -export function editTodo (id, text) { - return {type: types.EDIT_TODO, id, text}; -} - -export function completeTodo (id) { - return {type: types.COMPLETE_TODO, id}; -} - -export function completeAll () { - return {type: types.COMPLETE_ALL}; -} - -export function clearCompleted () { - return {type: types.CLEAR_COMPLETED}; -} \ No newline at end of file diff --git a/app/actions/requests.js b/app/actions/requests.js new file mode 100644 index 000000000..54869b9d1 --- /dev/null +++ b/app/actions/requests.js @@ -0,0 +1,62 @@ +import * as types from '../constants/actionTypes' +import * as methods from '../constants/global' +import {loadStart} from "./global"; +import {loadStop} from "./global"; + +function defaultRequest () { + return { + id: null, + _mode: 'json', + created: 0, + modified: 0, + name: '', + method: methods.METHOD_GET, + body: '', + params: [], + headers: [], + authentication: {} + } +} + +/** + * Build a new request from a subset of fields + * @param request values to override defaults with + * @returns {*} + */ +function buildRequest (request) { + // Build the required fields + const id = request.id || `req_${Date.now()}`; + const created = request.created || Date.now(); + const modified = request.modified || Date.now(); + + // Create the request + return Object.assign({}, defaultRequest(), request, { + id, created, modified + }); +} + +export function addRequest (name = 'My Request') { + return (dispatch) => { + dispatch(loadStart()); + const request = buildRequest({name}); + + setTimeout(() => { + dispatch({type: types.REQUEST_ADD, request}); + dispatch(loadStop()); + }, 500); + }; +} + +export function updateRequest (requestPatch) { + return (dispatch) => { + dispatch(loadStart()); + + const newRequest = Object.assign({}, requestPatch, {modified: Date.now()}); + const request = buildRequest(newRequest); + + setTimeout(() => { + dispatch({type: types.REQUEST_UPDATE, request}); + dispatch(loadStop()); + }, 500); + }; +} diff --git a/app/components/App.js b/app/components/App.js new file mode 100644 index 000000000..549647d57 --- /dev/null +++ b/app/components/App.js @@ -0,0 +1,40 @@ +import React, {PropTypes, Component} from 'react' +import Sidebar from './Sidebar' +import RequestPane from './RequestPane' +import ResponsePane from './ResponsePane' + +class App extends Component { + renderEditor () { + const {updateRequest, requests} = this.props; + return ( +
+ + +
+ ) + } + + render () { + const {addRequest, loading, requests} = this.props; + return ( +
+ + {requests.active ? this.renderEditor() :
} +
+ ) + } +} + +App.propTypes = { + addRequest: PropTypes.func.isRequired, + updateRequest: PropTypes.func.isRequired, + requests: PropTypes.object.isRequired, + loading: PropTypes.bool.isRequired +}; + +export default App; diff --git a/app/components/Editor.js b/app/components/Editor.js index a5dafa66d..5aa8c325d 100644 --- a/app/components/Editor.js +++ b/app/components/Editor.js @@ -50,32 +50,20 @@ class Editor extends Component { componentDidMount () { var textareaNode = this.refs.textarea; - let options = Object.assign({ - theme: 'monokai', - scrollPastEnd: true, - foldGutter: true, - gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - cursorScrollMargin: 60, - extraKeys: { - "Ctrl-Q": function (cm) { - cm.foldCode(cm.getCursor()); - } - }, - scrollbarStyle: 'overlay' - }, this.props.options || {}); - if (options.mode === 'application/json') { - options.mode = 'application/ld+json'; - } - - this.codeMirror = CodeMirror.fromTextArea(textareaNode, options); + this.codeMirror = CodeMirror.fromTextArea(textareaNode); this.codeMirror.on('change', this.codemirrorValueChanged.bind(this)); this.codeMirror.on('focus', this.focusChanged.bind(this, true)); this.codeMirror.on('blur', this.focusChanged.bind(this, false)); this._currentCodemirrorValue = this.props.defaultValue || this.props.value || ''; + this.codemirrorSetOptions(this.props.options); this.codeMirror.setValue(this._currentCodemirrorValue); } + componentDidUpdate () { + this.codemirrorSetOptions(this.props.options); + } + componentWillUnmount () { // todo: is there a lighter-weight way to remove the cm instance? if (this.codeMirror) { @@ -96,10 +84,6 @@ class Editor extends Component { } } - getCodeMirror () { - return this.codeMirror; - } - focus () { if (this.codeMirror) { this.codeMirror.focus(); @@ -113,18 +97,51 @@ class Editor extends Component { this.props.onFocusChange && this.props.onFocusChange(focused); } - codemirrorValueChanged (doc, change) { - var newValue = doc.getValue(); - this._currentCodemirrorValue = newValue; - this.props.onChange && this.props.onChange(newValue); + codemirrorSetOptions (options) { + options = Object.assign({ + theme: 'monokai', + scrollPastEnd: true, + foldGutter: true, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + cursorScrollMargin: 60, + extraKeys: { + "Ctrl-Q": function (cm) { + cm.foldCode(cm.getCursor()); + } + }, + scrollbarStyle: 'overlay' + }, options || {}); + + if (options.mode === 'json') { + options.mode = 'application/json'; + } + + if (options.mode === 'application/json') { + options.mode = 'application/ld+json'; + } + + Object.keys(options).map(key => { + this.codeMirror.setOption(key, options[key]); + }); + } + + codemirrorValueChanged (doc) { + clearTimeout(this._timeout); + this._timeout = setTimeout(() => { + var newValue = doc.getValue(); + this._currentCodemirrorValue = newValue; + this.props.onChange && this.props.onChange(newValue); + }, (this.props.debounceMillis || 0)); } render () { return ( - +
+ +
); } } @@ -135,7 +152,8 @@ Editor.propTypes = { options: PropTypes.object, path: PropTypes.string, value: PropTypes.string, - className: PropTypes.any + className: PropTypes.any, + debounceMillis: PropTypes.number }; export default Editor; diff --git a/app/components/RequestPane.js b/app/components/RequestPane.js index 4fa039ec3..bf7173deb 100644 --- a/app/components/RequestPane.js +++ b/app/components/RequestPane.js @@ -1,36 +1,68 @@ -import React from 'react' +import React, {Component, PropTypes} from 'react' import Editor from '../components/Editor' +import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; -const RequestPane = (props) => ( -
-
-
-
- - - +// Don't inject component styles (use our own) +Tabs.setUseDefaultStyles(false); + +class RequestPane extends Component { + shouldComponentUpdate (nextProps, nextState) { + return nextProps.request !== this.props.request + } + + render () { + const {request, updateRequest} = this.props; + + return ( +
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + Params + + updateRequest(Object.assign({}, request, {body}) )} + debounceMillis={500} + options={{mode: request._mode, lineNumbers: true}}/> + + Basic Auth + Headers +
-
-
-
-
- {['Query Params', 'Body', 'Headers', 'Basic Auth'].map((name => - - ))} -
- {localStorage['json'] = v}} - options={{mode: 'application/json', lineNumbers: true}}/> -
-
-); + + ); + } +} -RequestPane.propTypes = {}; +RequestPane.propTypes = { + updateRequest: PropTypes.func.isRequired, + request: PropTypes.object.isRequired +}; export default RequestPane; diff --git a/app/components/ResponsePane.js b/app/components/ResponsePane.js index 71a89066e..eaa9c9fea 100644 --- a/app/components/ResponsePane.js +++ b/app/components/ResponsePane.js @@ -1,20 +1,22 @@ -import React from 'react' +import React, {PropTypes} from 'react' import Editor from '../components/Editor' const ResponsePane = (props) => (
-
-
+
+
200 SUCCESS
GET https://google.com
-
- +
+
); -ResponsePane.propTypes = {}; +ResponsePane.propTypes = { + request: PropTypes.object.isRequired +}; export default ResponsePane; diff --git a/app/components/Sidebar.js b/app/components/Sidebar.js index 8f9253076..0a9e40292 100644 --- a/app/components/Sidebar.js +++ b/app/components/Sidebar.js @@ -1,22 +1,43 @@ -import React from 'react' +import React, {PropTypes} from 'react' const Sidebar = (props) => ( ); -Sidebar.propTypes = {}; +Sidebar.propTypes = { + requests: PropTypes.object.isRequired, + loading: PropTypes.bool.isRequired +}; export default Sidebar; diff --git a/app/constants/actionTypes.js b/app/constants/actionTypes.js index 62d2b046f..c7f1cbbc6 100644 --- a/app/constants/actionTypes.js +++ b/app/constants/actionTypes.js @@ -1 +1,10 @@ -export const ADD_REQUEST = 'ADD_REQUEST'; +// Global +export const GLOBAL_LOAD_START = 'GLOBAL.LOAD_START'; +export const GLOBAL_LOAD_STOP = 'GLOBAL.LOAD_STOP'; +export const GLOBAL_STATE_SAVED = 'GLOBAL.STATE_SAVED'; +export const GLOBAL_STATE_RESTORED = 'GLOBAL.STATE_RESTORED'; + +// Requests +export const REQUEST_ADD = 'REQUEST.ADD'; +export const REQUEST_UPDATE = 'REQUEST.UPDATE'; +export const REQUESTS_LOADED = 'REQUESTS.LOADED'; diff --git a/app/constants/global.js b/app/constants/global.js new file mode 100644 index 000000000..3b3cab175 --- /dev/null +++ b/app/constants/global.js @@ -0,0 +1,11 @@ +// Global +export const LOCALSTORAGE_DEBOUNCE_MILLIS = 1000; +export const LOCALSTORAGE_KEY = 'insomnia'; + +// HTTP Methods +export const METHOD_GET = 'GET'; +export const METHOD_PUT = 'PUT'; +export const METHOD_POST = 'POST'; +export const METHOD_DELETE = 'DELETE'; +export const METHOD_OPTIONS = 'OPTIONS'; +export const METHOD_HEAD = 'HEAD'; diff --git a/app/containers/App.js b/app/containers/App.js deleted file mode 100644 index abe85121d..000000000 --- a/app/containers/App.js +++ /dev/null @@ -1,42 +0,0 @@ -import React, { Component, PropTypes } from 'react' -import { connect } from 'react-redux' -import Sidebar from '../components/Sidebar' -import RequestPane from '../components/RequestPane' -import ResponsePane from '../components/ResponsePane' - -class App extends Component { - render () { - //const { global, todos, actions } = this.props; - return ( -
- - - -
- ) - }; -} - -App.propTypes = { - //todos: PropTypes.array.isRequired, - //global: PropTypes.object.isRequired, - //actions: PropTypes.object.isRequired -}; - -function mapStateToProps (state) { - return { - //todos: state.todos, - //global: state.global - } -} - -function mapDispatchToProps (dispatch) { - return { - //actions: bindActionCreators(TodoActions, dispatch) - } -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(App) diff --git a/app/containers/AppWrapper.js b/app/containers/AppWrapper.js new file mode 100644 index 000000000..54e745054 --- /dev/null +++ b/app/containers/AppWrapper.js @@ -0,0 +1,45 @@ +import React, {Component, PropTypes} from 'react' +import {connect} from 'react-redux' +import {bindActionCreators} from 'redux' +import App from '../components/App.js'; + +import * as RequestActions from '../actions/requests' +import * as GlobalActions from '../actions/global' + +class AppWrapper extends Component { + componentDidMount() { + this.props.actions.restoreState(); + } + render() { + const {actions, requests, loading, initialized} = this.props; + if (!initialized) { + return
Loading...
+ } else { + return + } + } +} + +function mapStateToProps (state) { + return state; +} + +function mapDispatchToProps (dispatch) { + return { + actions: Object.assign( + {}, + bindActionCreators(GlobalActions, dispatch), + bindActionCreators(RequestActions, dispatch) + ) + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(AppWrapper); + diff --git a/app/css/base.scss b/app/css/base.scss index 100142b38..3714b3519 100644 --- a/app/css/base.scss +++ b/app/css/base.scss @@ -25,6 +25,10 @@ h2 { text-align: center; } +.pull-right { + float: right; +} + strong { font-weight: 600; } diff --git a/app/css/components/editor.scss b/app/css/components/editor.scss index 5b4d2bc0e..8e67ffdea 100644 --- a/app/css/components/editor.scss +++ b/app/css/components/editor.scss @@ -1,26 +1,31 @@ @import '../constants/colors'; -.CodeMirror { - height: 100% !important; - width: 100%; - font-size: 13px !important; - font-family: "Source Code Pro", monospace; - padding: 5px 0; - box-sizing: border-box; +.editor { + height: 100%; + + .CodeMirror { + height: 100% !important; + width: 100%; + font-size: 12px !important; + font-family: "Source Code Pro", monospace; + padding: 5px 0; + box-sizing: border-box; + } + + .CodeMirror, + .cm-s-seti.CodeMirror, // Hack because seti theme is dumb + .CodeMirror-gutters, + .CodeMirror-scrollbar-filler, + .CodeMirror-gutter-filler { + background-color: $bg-dark !important; + } + + .CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div { + background: lighten($bg-dark, 10%); + } + + .CodeMirror-linenumber, .CodeMirror-guttermarker-subtle { + color: #555555 !important; + } } -.CodeMirror, -.cm-s-seti.CodeMirror, // Hack because seti theme is dumb -.CodeMirror-gutters, -.CodeMirror-scrollbar-filler, -.CodeMirror-gutter-filler { - background-color: $bg-dark !important; -} - -.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div { - background: lighten($bg-dark, 10%); -} - -.CodeMirror-linenumber, .CodeMirror-guttermarker-subtle { - color: #555555 !important; -} diff --git a/app/css/components/response.scss b/app/css/components/response.scss index 8af019135..eed29445f 100644 --- a/app/css/components/response.scss +++ b/app/css/components/response.scss @@ -1,7 +1,7 @@ @import '../constants/colors'; #response { - .pane-header { + .pane__header { .tag { // HACK: Find some way to center this vertically margin-top: -4px; diff --git a/app/css/components/tabs.scss b/app/css/components/tabs.scss new file mode 100644 index 000000000..8a53d9ca8 --- /dev/null +++ b/app/css/components/tabs.scss @@ -0,0 +1,9 @@ +.ReactTabs { + .ReactTabs__Tab { + opacity: 0.5; + } + + .ReactTabs__Tab--selected { + opacity: 1; + } +} diff --git a/app/css/constants/colors.scss b/app/css/constants/colors.scss index 9b6832f62..7bafebfc2 100644 --- a/app/css/constants/colors.scss +++ b/app/css/constants/colors.scss @@ -28,6 +28,14 @@ $font-dark-bg: #dddddd; &, a, textarea, input, .btn { color: $font-dark-bg; } + + .btn:hover { + background: lighten($bg-dark, 4%); + } + + .btn:active { + background: lighten($bg-dark, 6%); + } } .bg-light { diff --git a/app/css/grid.scss b/app/css/grid.scss index 87950f907..4724271ba 100644 --- a/app/css/grid.scss +++ b/app/css/grid.scss @@ -13,6 +13,7 @@ } .col { + display: block; flex: 1 1 auto; width: 100%; height: 100%; @@ -33,7 +34,14 @@ } .row { - height: 100%; + display: block; flex: 1 1 auto; + height: 100%; box-sizing: border-box; } + +.un-grid { + display: initial; + height: auto; + width: auto; +} diff --git a/app/css/index.scss b/app/css/index.scss index 9591001e4..b81085103 100644 --- a/app/css/index.scss +++ b/app/css/index.scss @@ -19,3 +19,4 @@ @import 'components/request'; @import 'components/response'; @import 'components/tag'; +@import 'components/tabs'; diff --git a/app/css/layout/forms.scss b/app/css/layout/forms.scss index f391e2a90..9f79b559c 100644 --- a/app/css/layout/forms.scss +++ b/app/css/layout/forms.scss @@ -2,7 +2,6 @@ @import '../constants/colors'; .form-control { - width: 100%; outline: none; border: 0; height: $form-height; diff --git a/app/css/layout/pane.scss b/app/css/layout/pane.scss index 1286643cc..df652a149 100644 --- a/app/css/layout/pane.scss +++ b/app/css/layout/pane.scss @@ -2,29 +2,22 @@ @import '../constants/dimensions'; .pane { - .pane-header { + .pane__header { z-index: 1; position: relative; - border-right: 2px solid rgba(0,0,0,0.05); + border-right: 2px solid rgba(0, 0, 0, 0.04); + height: $header-height; - &.pane-header-clickable a, &:not(.pane-header-clickable) > *:first-child { + .pane__header__content { display: block; height: $header-height; box-sizing: border-box; padding: $default-padding; } - - &:not(.pane-header_clickable).pane-header-no-padding > *:first-child { - padding: 0; - } - - &.pane-header-clickable.header-no-padding a { - padding: 0; - } } - .pane-body { + .pane__body { height: 100%; - border-right: 2px solid rgba(0,0,0,0.1); + border-right: 1px solid rgba(0, 0, 0, 0.1); } } diff --git a/app/index.js b/app/index.js index 1922b62b2..a925e6865 100644 --- a/app/index.js +++ b/app/index.js @@ -1,8 +1,9 @@ import React from 'react' -import { render } from 'react-dom' -import { Provider } from 'react-redux' -import App from './containers/App' +import {render} from 'react-dom' +import {Provider} from 'react-redux' import configureStore from './stores/configureStore' +import AppWrapper from './containers/AppWrapper' + // Global CSS import './css/index.scss' @@ -12,6 +13,6 @@ import './css/lib/fontawesome/css/font-awesome.css' const store = configureStore(); render( - , - document.getElementById('root') + , + document.getElementById('root') ); diff --git a/app/middleware/localstorage.js b/app/middleware/localstorage.js new file mode 100644 index 000000000..1d56b3236 --- /dev/null +++ b/app/middleware/localstorage.js @@ -0,0 +1,13 @@ +import {LOCALSTORAGE_DEBOUNCE_MILLIS} from "../constants/global"; + +let timeout = null; +export default (store) => next => action => { + let result = next(action); + + clearTimeout(timeout); + timeout = setTimeout(() => { + localStorage['insomnia'] = JSON.stringify(store.getState(), null, 2); + }, LOCALSTORAGE_DEBOUNCE_MILLIS); + + return result; +}; diff --git a/app/reducers/global.js b/app/reducers/global.js new file mode 100644 index 000000000..aa33b333d --- /dev/null +++ b/app/reducers/global.js @@ -0,0 +1,29 @@ +import * as types from '../constants/actionTypes'; +import requestsReducer from './requests' +import settingsReducer from './settings' + +const initialState = { + initialized: false, + loading: false +}; + +export default function (state = initialState, action) { + switch (action.type) { + case types.GLOBAL_STATE_SAVED: + return state; + case types.GLOBAL_STATE_RESTORED: + return Object.assign({}, state, action.state, {initialized: true}); + case types.GLOBAL_LOAD_START: + return Object.assign({}, state, {loading: true}); + case types.GLOBAL_LOAD_STOP: + return Object.assign({}, state, {loading: false}); + default: + // Send everything else to the child reducers + const requests = requestsReducer(state.requests, action); + const settings = settingsReducer(state.settings, action); + return Object.assign({}, state, { + settings, + requests + }); + } +}; diff --git a/app/reducers/index.js b/app/reducers/index.js deleted file mode 100644 index e8deb0709..000000000 --- a/app/reducers/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import { combineReducers } from 'redux' -import requests from './requests' - -const rootReducer = combineReducers({ - requests -}); - -export default rootReducer; diff --git a/app/reducers/requests.js b/app/reducers/requests.js index b6178bf71..fa2ea62ae 100644 --- a/app/reducers/requests.js +++ b/app/reducers/requests.js @@ -1,22 +1,33 @@ -import * as types from '../constants/ActionTypes'; +import * as types from "../constants/actionTypes"; -function request (state, action) { - switch (action.type) { - case types.ADD_REQUEST: - return { - foo: 'bar' - }; +const initialState = { + all: [], + active: null +}; - default: - return state; - } +function requestsReducer (state = [], action) { + switch (action.type) { + case types.REQUEST_ADD: + return [...state, action.request]; + case types.REQUEST_UPDATE: + return state.map(request => request.id === action.request.id ? action.request : request); + default: + return state; + } } -export default function requests (state = [], action) { - switch (action.type) { - case types.ADD_REQUEST: - return [...state, request(undefined, action)]; - default: - return state; - } +export default function (state = initialState, action) { + let all, active; + switch (action.type) { + case types.REQUEST_ADD: + all = requestsReducer(state.all, action); + active = state.active || action.request; + return Object.assign({}, state, {all, active}); + case types.REQUEST_UPDATE: + all = requestsReducer(state.all, action); + active = state.active.id === action.request.id ? action.request : state.active; + return Object.assign({}, state, {all, active}); + default: + return state + } } diff --git a/app/reducers/settings.js b/app/reducers/settings.js new file mode 100644 index 000000000..18ab86b79 --- /dev/null +++ b/app/reducers/settings.js @@ -0,0 +1,11 @@ +import * as types from '../constants/actionTypes'; + +const initialState = { +}; + +export default function (state = initialState, action) { + switch (action.type) { + default: + return state + } +} diff --git a/app/stores/configureStore.js b/app/stores/configureStore.js index a0b2daa38..24c5dc714 100644 --- a/app/stores/configureStore.js +++ b/app/stores/configureStore.js @@ -1,27 +1,31 @@ -import { createStore, applyMiddleware } from 'redux' +import {createStore, applyMiddleware} from 'redux' import thunkMiddleware from 'redux-thunk' import createLogger from 'redux-logger' -import rootReducer from '../reducers' +import rootReducer from '../reducers/global' +import localStorageMiddleware from '../middleware/localstorage' -const loggerMiddleware = createLogger(); +const loggerMiddleware = createLogger({ + collapsed: true +}); export default function configureStore (initialState) { - const store = createStore( - rootReducer, - initialState, - applyMiddleware( - thunkMiddleware, - loggerMiddleware - ) - ); + const store = createStore( + rootReducer, + initialState, + applyMiddleware( + thunkMiddleware, + localStorageMiddleware, + loggerMiddleware + ) + ); - if (module.hot) { - // Enable Webpack hot module replacement for reducers - module.hot.accept('../reducers', () => { - const nextReducer = require('../reducers').default; - store.replaceReducer(nextReducer); - }) - } + if (module.hot) { + // Enable Webpack hot module replacement for reducers + module.hot.accept('../reducers/global', () => { + const nextReducer = require('../reducers/global').default; + store.replaceReducer(nextReducer); + }) + } - return store -} \ No newline at end of file + return store +} diff --git a/package.json b/package.json index 078a6982c..b5019eeb9 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,12 @@ "url": "git@bitbucket.org:gschier/insomnia.git" }, "dependencies": { - "babel-polyfill": "^6.7.2", "classnames": "^2.2.3", "codemirror": "^5.12.0", "react": "^0.14.7", "react-dom": "^0.14.7", "react-redux": "^4.4.1", + "react-tabs": "^0.5.3", "redux": "^3.3.1", "redux-logger": "^2.6.1", "redux-thunk": "^2.0.1", @@ -20,12 +20,17 @@ }, "devDependencies": { "babel-core": "^6.7.2", + "babel-jest": "^9.0.3", "babel-loader": "^6.2.4", + "babel-polyfill": "^6.7.2", "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.5.0", "css-loader": "^0.23.1", "file-loader": "^0.8.5", + "jest": "^0.1.40", + "jest-cli": "^0.9.2", "node-sass": "^3.4.2", + "react-addons-test-utils": "^0.14.7", "react-hot-loader": "^1.3.0", "sass-loader": "^3.2.0", "style-loader": "^0.13.0", @@ -34,6 +39,14 @@ }, "scripts": { "start": "node server.js", + "test": "jest", "build": "webpack --config webpack/prod.config.js # --optimize-minimize" + }, + "jest": { + "unmockedModulePathPatterns": [ + "/node_modules/react", + "/node_modules/react-dom", + "/node_modules/react-addons-test-utils" + ] } }