From 68fd0fbe4d893f8007f62eab71e65056693c4972 Mon Sep 17 00:00:00 2001 From: tatomyr Date: Sun, 12 Aug 2018 20:53:10 +0300 Subject: [PATCH 1/7] introduced ProtectedRoute --- src/components/App/App.js | 14 +++---- src/components/Private.js | 33 --------------- .../ProtectedRoute/ProtectedRoute.js | 40 +++++++++++++++++++ src/components/ProtectedRoute/index.js | 9 +++++ src/components/Sidebar/Sidebar.js | 1 - .../__snapshots__/Sidebar.test.js.snap | 4 -- src/redux/reducers/creds.test.js | 2 +- src/redux/reducers/estimates.js | 5 ++- src/redux/reducers/estimates.test.js | 20 ++++++++++ 9 files changed, 80 insertions(+), 48 deletions(-) delete mode 100644 src/components/Private.js create mode 100644 src/components/ProtectedRoute/ProtectedRoute.js create mode 100644 src/components/ProtectedRoute/index.js create mode 100644 src/redux/reducers/estimates.test.js diff --git a/src/components/App/App.js b/src/components/App/App.js index f7e4893..057f84a 100644 --- a/src/components/App/App.js +++ b/src/components/App/App.js @@ -4,10 +4,10 @@ import { Switch, Route } from 'react-router-dom' import Toastr from '../Toastr' import Redirector from '../Redirector' import Spinner from '../Spinner' -import Private from '../Private' import Estimate from '../Estimate' import Dashboard from '../Dashboard' import AuthScreen from '../AuthScreen' +import ProtectedRoute from '../ProtectedRoute' class App extends React.Component { componentDidMount = () => { @@ -18,13 +18,11 @@ class App extends React.Component { render = () => (
- - - - - - - + + + + + {/* eslint-disable-next-line react/destructuring-assignment */} {this.props.showAuthScreen && } diff --git a/src/components/Private.js b/src/components/Private.js deleted file mode 100644 index 5646e24..0000000 --- a/src/components/Private.js +++ /dev/null @@ -1,33 +0,0 @@ -import React, { Fragment } from 'react' -import PropTypes from 'prop-types' -import { withRouter } from 'react-router-dom' -import { connect } from 'react-redux' -import AuthScreen from './AuthScreen' - -const Private = ({ - children, - username, - match: { params: { estimateId = '' } }, -}) => ( - - {username || estimateId === 'new' - ? children - : } - -) - -Private.propTypes = { - children: PropTypes.node.isRequired, - username: PropTypes.string.isRequired, - match: PropTypes.shape({ - params: PropTypes.shape({ - estimateId: PropTypes.string, - }).isRequired, - }).isRequired, -} - -const mapStateToProps = ({ creds: { username } }) => ({ - username, -}) - -export default withRouter(connect(mapStateToProps, null)(Private)) diff --git a/src/components/ProtectedRoute/ProtectedRoute.js b/src/components/ProtectedRoute/ProtectedRoute.js new file mode 100644 index 0000000..9c43d9b --- /dev/null +++ b/src/components/ProtectedRoute/ProtectedRoute.js @@ -0,0 +1,40 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { Route } from 'react-router-dom' +import AuthScreen from '../AuthScreen' + +const ProtectedRoute = ({ + component: Component, + username, + match: { params: { estimateId = '' } }, + ...rest +}) => ( + (username || estimateId === 'new' + ? + : + // TODO: implement redirection + // : ( + // + // ) + )} + /> +) + +ProtectedRoute.propTypes = { + component: PropTypes.func.isRequired, + username: PropTypes.string.isRequired, + match: PropTypes.shape({ + params: PropTypes.shape({ + estimateId: PropTypes.string, + }).isRequired, + }).isRequired, +} + +export default ProtectedRoute diff --git a/src/components/ProtectedRoute/index.js b/src/components/ProtectedRoute/index.js new file mode 100644 index 0000000..c84af54 --- /dev/null +++ b/src/components/ProtectedRoute/index.js @@ -0,0 +1,9 @@ +import { withRouter } from 'react-router-dom' +import { connect } from 'react-redux' +import ProtectedRoute from './ProtectedRoute' + +const mapStateToProps = ({ creds: { username } }) => ({ + username, +}) + +export default withRouter(connect(mapStateToProps, null)(ProtectedRoute)) diff --git a/src/components/Sidebar/Sidebar.js b/src/components/Sidebar/Sidebar.js index 3fcd38d..320993c 100644 --- a/src/components/Sidebar/Sidebar.js +++ b/src/components/Sidebar/Sidebar.js @@ -57,7 +57,6 @@ const Sidebar = ({
diff --git a/src/components/Sidebar/__snapshots__/Sidebar.test.js.snap b/src/components/Sidebar/__snapshots__/Sidebar.test.js.snap index 780a0ae..e2b5995 100644 --- a/src/components/Sidebar/__snapshots__/Sidebar.test.js.snap +++ b/src/components/Sidebar/__snapshots__/Sidebar.test.js.snap @@ -75,7 +75,6 @@ ShallowWrapper { title="Graph" />, , { it('returns initial state', () => { - expect(creds(undefined, {})).toEqual({ ...emptyCreds }) + expect(creds(undefined, {})).toEqual(emptyCreds) }) it('sets checking creds status correctly', () => { diff --git a/src/redux/reducers/estimates.js b/src/redux/reducers/estimates.js index 4786fc7..da1158d 100644 --- a/src/redux/reducers/estimates.js +++ b/src/redux/reducers/estimates.js @@ -48,7 +48,10 @@ export default (state = ({ new: emptyEstimate }), { type, payload }) => { // Put 'new' here to avoid creating an estimate with an undefined `_id` // ...while logging out on different routes // ...(that may not contain an `:estimateId` param) - [payload || 'new']: emptyEstimate, + [payload || 'new']: { + ...emptyEstimate, + _id: payload || 'new', + }, // FIXME: !! }) case RECALCULATE: diff --git a/src/redux/reducers/estimates.test.js b/src/redux/reducers/estimates.test.js new file mode 100644 index 0000000..1330887 --- /dev/null +++ b/src/redux/reducers/estimates.test.js @@ -0,0 +1,20 @@ +/* globals describe, it, expect */ + +// import { +// UPDATE_ESTIMATE, +// RECALCULATE, +// CLEAN_ESTIMATE, +// SET_TITLES, +// MARK_ESTIMATE_SAVED, +// } from '../actions/types' +import estimates, { emptyEstimate } from './estimates' + +// FIXME: avoid duplicating 'new' estimates after logging out + +describe('estimates reducer', () => { + it('returns initial state', () => { + expect(estimates(undefined, {})).toEqual({ new: emptyEstimate }) + }) + + // TODO: implement tests for CLEAN_ESTIMATE +}) From 4db5191bda112d5ca91258257fa4f3534bb39902 Mon Sep 17 00:00:00 2001 From: tatomyr Date: Mon, 13 Aug 2018 00:25:51 +0300 Subject: [PATCH 2/7] moved AuthScreen to a dedicated route; refactored redirector --- .eslintrc.js | 2 +- src/components/App/App.js | 3 -- src/components/AuthScreen/Anonymous.js | 47 ++++++++-------- src/components/AuthScreen/AuthScreen.js | 14 +++-- src/components/AuthScreen/AuthScreen.test.js | 54 +++++++++++-------- src/components/AuthScreen/Authorized.js | 28 +++++----- .../__snapshots__/AuthScreen.test.js.snap | 15 ++++-- src/components/AuthScreen/index.js | 8 +-- .../ProtectedRoute/ProtectedRoute.js | 24 ++++----- src/components/Redirector/Redirector.js | 14 +++-- src/components/Redirector/Redirector.test.js | 4 +- src/components/SideButton/SideButton.js | 3 +- src/components/Sidebar/Sidebar.js | 10 ++-- src/components/Sidebar/Sidebar.test.js | 2 +- .../__snapshots__/Sidebar.test.js.snap | 46 ++++++++++++++-- src/components/Sidebar/index.js | 4 -- src/redux/actions/async.js | 18 ++----- src/redux/actions/index.js | 29 +++++----- src/redux/actions/types.js | 7 ++- src/redux/reducers/estimates.js | 8 ++- src/redux/reducers/estimates.test.js | 1 + src/redux/reducers/redirector.js | 12 ++--- src/redux/reducers/redirector.test.js | 39 ++++++++++---- src/redux/reducers/visualEffects.js | 15 ------ 24 files changed, 237 insertions(+), 170 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 61c66f6..ac090cc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,7 +8,7 @@ module.exports = { semi: ["error", "never"], "arrow-parens": ["error", "as-needed"], "react/jsx-filename-extension": ["warn", { "extensions": [".js"] }], - "react/prop-types": "warn", + "react/prop-types": "error", "jsx-a11y/label-has-for": ["error", { "components": ["Label"], "required": { diff --git a/src/components/App/App.js b/src/components/App/App.js index 057f84a..dc063c2 100644 --- a/src/components/App/App.js +++ b/src/components/App/App.js @@ -23,8 +23,6 @@ class App extends React.Component { - {/* eslint-disable-next-line react/destructuring-assignment */} - {this.props.showAuthScreen && }
@@ -33,7 +31,6 @@ class App extends React.Component { App.propTypes = { checkCreds: PropTypes.func.isRequired, - showAuthScreen: PropTypes.bool.isRequired, username: PropTypes.string.isRequired, } diff --git a/src/components/AuthScreen/Anonymous.js b/src/components/AuthScreen/Anonymous.js index a4befa9..9a061bb 100644 --- a/src/components/AuthScreen/Anonymous.js +++ b/src/components/AuthScreen/Anonymous.js @@ -1,4 +1,5 @@ import React, { Fragment } from 'react' +import { Link } from 'react-router-dom' import PropTypes from 'prop-types' import { Button, @@ -9,18 +10,30 @@ import { } from 'reactstrap' import FA from 'react-fontawesome' import * as api from '../../helpers/api' +import { locationType } from '../Redirector/Redirector' + +const AlertAccessingEstimate = ({ from }) => ((from + && from.pathname + && from.pathname.startsWith('/estimate/') + && from.pathname !== '/estimate/new' +) + ? ( + + Please enter a valid credentials to get access to this estimate + + ) + : null) + +AlertAccessingEstimate.propTypes = { + from: locationType.isRequired, +} const Anonymous = ({ - match: { url, params }, checkCreds, - openGuestSession, + from, }) => ( - {(url.startsWith('/estimate/') && params.estimateId !== 'new') ? ( - - Please enter a valid credentials to get access to this estimate - - ) : null} +
{ e.preventDefault() @@ -67,25 +80,17 @@ const Anonymous = ({
Or
- + + +
) Anonymous.propTypes = { - match: PropTypes.shape({ - url: PropTypes.string.isRequired, - params: PropTypes.shape({ - estimateId: PropTypes.string, - }).isRequired, - }).isRequired, checkCreds: PropTypes.func.isRequired, - openGuestSession: PropTypes.func.isRequired, + from: locationType.isRequired, } export default Anonymous diff --git a/src/components/AuthScreen/AuthScreen.js b/src/components/AuthScreen/AuthScreen.js index 63e6a8b..0cd7d23 100644 --- a/src/components/AuthScreen/AuthScreen.js +++ b/src/components/AuthScreen/AuthScreen.js @@ -3,18 +3,25 @@ import PropTypes from 'prop-types' import Header from '../Header' import Authorized from './Authorized' import Anonymous from './Anonymous' +import { locationType } from '../Redirector/Redirector' const AuthScreen = ({ username, checkingCreds, + location: { state }, ...rest }) => (
- {(checkingCreds && 'Checking permissions. Please wait…') - || (username && ) - || } + {(checkingCreds && 'Checking permissions. Please wait…') || (username && ( + ) + ) || }
) @@ -22,6 +29,7 @@ const AuthScreen = ({ AuthScreen.propTypes = { username: PropTypes.string.isRequired, checkingCreds: PropTypes.bool.isRequired, + location: locationType.isRequired, } export default AuthScreen diff --git a/src/components/AuthScreen/AuthScreen.test.js b/src/components/AuthScreen/AuthScreen.test.js index edb4628..8cbf973 100644 --- a/src/components/AuthScreen/AuthScreen.test.js +++ b/src/components/AuthScreen/AuthScreen.test.js @@ -1,51 +1,61 @@ /* globals describe, it, expect */ import React from 'react' +import { HashRouter as Router } from 'react-router-dom' import renderer from 'react-test-renderer' import AuthScreen from './AuthScreen' -const mockedFunctions = { +const commons = { + location: { pathname: '/', state: { from: '/' } }, + match: { url: '/estimate/new', params: { estimateId: 'new' } }, checkCreds: () => null, resetCreds: () => null, - cleanEstimate: () => null, - closeAuthScreen: () => null, - openGuestSession: () => null, + cleanAllEstimates: () => null, redirect: () => null, } describe('AuthScreen', () => { it('renders correctly for authorized user', () => { const tree = renderer - .create() + .create( + + + // eslint-disable-line comma-dangle + ) .toJSON() expect(tree).toMatchSnapshot() }) it('renders correctly for anonymous user', () => { const tree = renderer - .create() + .create( + + + // eslint-disable-line comma-dangle + ) .toJSON() expect(tree).toMatchSnapshot() }) it('renders correctly when checking credentials', () => { const tree = renderer - .create() + .create( + + + // eslint-disable-line comma-dangle + ) .toJSON() expect(tree).toMatchSnapshot() }) diff --git a/src/components/AuthScreen/Authorized.js b/src/components/AuthScreen/Authorized.js index 2b07fe0..0ff923a 100644 --- a/src/components/AuthScreen/Authorized.js +++ b/src/components/AuthScreen/Authorized.js @@ -1,17 +1,20 @@ import React, { Fragment } from 'react' +import { Redirect } from 'react-router-dom' import PropTypes from 'prop-types' import { Button } from 'reactstrap' import * as api from '../../helpers/api' +import { locationType } from '../Redirector/Redirector' const Authorized = ({ - match: { url, params }, username, resetCreds, - cleanEstimate, - closeAuthScreen, + cleanAllEstimates, redirect, + from, + redirectAutomatically, }) => ( + {redirectAutomatically && }
{/* eslint-disable-next-line react/jsx-one-expression-per-line */} Welcome, {username}! @@ -19,7 +22,7 @@ const Authorized = ({ @@ -29,7 +32,7 @@ const Authorized = ({ onClick={() => { api.removeCreds() resetCreds() - cleanEstimate(params) + cleanAllEstimates() }} > Log Out @@ -38,17 +41,16 @@ const Authorized = ({ ) Authorized.propTypes = { - match: PropTypes.shape({ - url: PropTypes.string.isRequired, - params: PropTypes.shape({ - estimateId: PropTypes.string, - }).isRequired, - }).isRequired, username: PropTypes.string.isRequired, resetCreds: PropTypes.func.isRequired, - cleanEstimate: PropTypes.func.isRequired, - closeAuthScreen: PropTypes.func.isRequired, + cleanAllEstimates: PropTypes.func.isRequired, redirect: PropTypes.func.isRequired, + from: locationType.isRequired, + redirectAutomatically: PropTypes.bool, +} + +Authorized.defaultProps = { + redirectAutomatically: false, } export default Authorized diff --git a/src/components/AuthScreen/__snapshots__/AuthScreen.test.js.snap b/src/components/AuthScreen/__snapshots__/AuthScreen.test.js.snap index cf41e44..c4564d9 100644 --- a/src/components/AuthScreen/__snapshots__/AuthScreen.test.js.snap +++ b/src/components/AuthScreen/__snapshots__/AuthScreen.test.js.snap @@ -102,13 +102,18 @@ exports[`AuthScreen renders correctly for anonymous user 1`] = ` Or
- + +
`; diff --git a/src/components/AuthScreen/index.js b/src/components/AuthScreen/index.js index 5e77f70..9d78ca7 100644 --- a/src/components/AuthScreen/index.js +++ b/src/components/AuthScreen/index.js @@ -3,13 +3,11 @@ import { withRouter } from 'react-router-dom' import AuthScreen from './AuthScreen' import './styles.css' import { - closeAuthScreen, - cleanEstimate, + cleanAllEstimates, resetCreds, } from '../../redux/actions' import { checkCreds, - openGuestSession, redirect, } from '../../redux/actions/async' @@ -25,9 +23,7 @@ const mapStateToProps = ({ const mapDispatchToProps = ({ checkCreds, - closeAuthScreen, - cleanEstimate, - openGuestSession, + cleanAllEstimates, resetCreds, redirect, }) diff --git a/src/components/ProtectedRoute/ProtectedRoute.js b/src/components/ProtectedRoute/ProtectedRoute.js index 9c43d9b..0c1a763 100644 --- a/src/components/ProtectedRoute/ProtectedRoute.js +++ b/src/components/ProtectedRoute/ProtectedRoute.js @@ -1,8 +1,9 @@ import React from 'react' import PropTypes from 'prop-types' -import { Route } from 'react-router-dom' -import AuthScreen from '../AuthScreen' +import { Route, Redirect } from 'react-router-dom' +import { locationType } from '../Redirector/Redirector' +// TODO: implement checkCreds here if !username instead of checkeng if App const ProtectedRoute = ({ component: Component, username, @@ -13,16 +14,14 @@ const ProtectedRoute = ({ {...rest} render={props => (username || estimateId === 'new' ? - : - // TODO: implement redirection - // : ( - // - // ) + : ( + + ) )} /> ) @@ -35,6 +34,7 @@ ProtectedRoute.propTypes = { estimateId: PropTypes.string, }).isRequired, }).isRequired, + location: locationType.isRequired, } export default ProtectedRoute diff --git a/src/components/Redirector/Redirector.js b/src/components/Redirector/Redirector.js index 1fc8acd..576d732 100644 --- a/src/components/Redirector/Redirector.js +++ b/src/components/Redirector/Redirector.js @@ -2,13 +2,21 @@ import React from 'react' import PropTypes from 'prop-types' import { Redirect } from 'react-router-dom' -const Redirector = ({ redirector: { href } }) => href && ( - +const Redirector = ({ redirector: { location } }) => location && ( + ) +export const locationType = PropTypes.oneOfType([ + PropTypes.string, + PropTypes.shape({ + pathname: PropTypes.string.isRequired, + state: PropTypes.any, + }), +]) + Redirector.propTypes = { redirector: PropTypes.shape({ - href: PropTypes.string.isRequired, + location: locationType, }).isRequired, } diff --git a/src/components/Redirector/Redirector.test.js b/src/components/Redirector/Redirector.test.js index d14f0a1..9489eda 100644 --- a/src/components/Redirector/Redirector.test.js +++ b/src/components/Redirector/Redirector.test.js @@ -9,7 +9,7 @@ describe('Redirector', () => { it('renders correctly', () => { const tree = renderer .create() .toJSON() expect(tree).toMatchSnapshot() @@ -20,7 +20,7 @@ describe('Redirector', () => { .create( /* eslint-disable-line comma-dangle */ ) diff --git a/src/components/SideButton/SideButton.js b/src/components/SideButton/SideButton.js index f170ab7..26a6286 100644 --- a/src/components/SideButton/SideButton.js +++ b/src/components/SideButton/SideButton.js @@ -2,6 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import FA from 'react-fontawesome' import { Button } from 'reactstrap' +import { locationType } from '../Redirector/Redirector' const SideButton = ({ title, @@ -33,7 +34,7 @@ SideButton.propTypes = { name: PropTypes.string.isRequired, color: PropTypes.string, onClick: PropTypes.func, - link: PropTypes.string, + link: locationType, disabled: PropTypes.bool, redirect: PropTypes.func.isRequired, } diff --git a/src/components/Sidebar/Sidebar.js b/src/components/Sidebar/Sidebar.js index 320993c..76c73fa 100644 --- a/src/components/Sidebar/Sidebar.js +++ b/src/components/Sidebar/Sidebar.js @@ -2,12 +2,13 @@ import React from 'react' import PropTypes from 'prop-types' import SideButton from '../SideButton' import { estimateType } from '../Estimate/propTypes' +import { locationType } from '../Redirector/Redirector' const Sidebar = ({ match: { params: { estimateId } }, + location, recalc, saveEstimate, - openAuthScreen, estimates, username, graphView, @@ -64,7 +65,10 @@ const Sidebar = ({ title="Auth" name={username ? 'unlock' : 'lock'} color={username ? 'success' : 'warning'} - onClick={openAuthScreen} + link={{ + pathname: '/auth', + state: { from: location }, + }} /> { configure({ adapter: new Adapter() }) const tree = shallow( null} saveEstimate={() => null} - openAuthScreen={() => null} estimates={{ new: emptyEstimate }} username="Test User" graphView="minified" diff --git a/src/components/Sidebar/__snapshots__/Sidebar.test.js.snap b/src/components/Sidebar/__snapshots__/Sidebar.test.js.snap index e2b5995..afe3e10 100644 --- a/src/components/Sidebar/__snapshots__/Sidebar.test.js.snap +++ b/src/components/Sidebar/__snapshots__/Sidebar.test.js.snap @@ -19,6 +19,11 @@ ShallowWrapper { } } graphView="minified" + location={ + Object { + "pathname": "/", + } + } match={ Object { "params": Object { @@ -27,7 +32,6 @@ ShallowWrapper { } } minifyGraph={[Function]} - openAuthScreen={[Function]} recalc={[Function]} saveEstimate={[Function]} username="Test User" @@ -84,8 +88,17 @@ ShallowWrapper { />, , , , dispatch => { +export const redirect = location => dispatch => { dispatch({ type: '__ASYNC__REDIRECT' }) - dispatch(setHref(href)) - setTimeout(() => dispatch(resetHref())) + dispatch(setLocation(location)) + setTimeout(() => dispatch(resetLocation())) } export const saveEstimate = ({ estimateId }) => (dispatch, getState) => { @@ -115,14 +114,7 @@ export const checkCreds = () => dispatch => { console.info(`${titles.length} record(s) found in the DB.`) dispatch(setTitles(titles)) dispatch(setCreds()) - dispatch(closeAuthScreen()) }) .catch(() => { dispatch(resetCreds()) }) .finally(() => { dispatch(delSpinner()) }) } - -export const openGuestSession = () => dispatch => { - dispatch({ type: '__ASYNC__OPEN_GUEST_SESSION' }) - dispatch(closeAuthScreen()) - dispatch(redirect('/estimate/new')) -} diff --git a/src/redux/actions/index.js b/src/redux/actions/index.js index 80e2e1e..1106295 100644 --- a/src/redux/actions/index.js +++ b/src/redux/actions/index.js @@ -1,15 +1,14 @@ import { UPDATE_ESTIMATE, CLEAN_ESTIMATE, + CLEAN_ALL_ESTIMATES, RECALCULATE, - SET_HREF, - RESET_HREF, + SET_LOCATION, + RESET_LOCATION, ADD_SPINNER, DEL_SPINNER, SET_CREDS, RESET_CREDS, - OPEN_AUTH_SCREEN, - CLOSE_AUTH_SCREEN, SET_TITLES, MARK_ESTIMATE_SAVED, ENLARGE_GRAPH, @@ -28,18 +27,22 @@ export const cleanEstimate = ({ estimateId }) => ({ payload: estimateId, }) +export const cleanAllEstimates = () => ({ + type: CLEAN_ALL_ESTIMATES, +}) + export const recalc = _id => ({ type: RECALCULATE, payload: _id, }) -export const setHref = href => ({ - type: SET_HREF, - payload: href, +export const setLocation = location => ({ + type: SET_LOCATION, + payload: location, }) -export const resetHref = () => ({ - type: RESET_HREF, +export const resetLocation = () => ({ + type: RESET_LOCATION, }) export const addSpinner = () => ({ @@ -50,14 +53,6 @@ export const delSpinner = () => ({ type: DEL_SPINNER, }) -export const openAuthScreen = () => ({ - type: OPEN_AUTH_SCREEN, -}) - -export const closeAuthScreen = () => ({ - type: CLOSE_AUTH_SCREEN, -}) - export const setCredsChecking = () => ({ type: SET_CREDS_CHECKING, }) diff --git a/src/redux/actions/types.js b/src/redux/actions/types.js index f8aa38f..3919f9a 100644 --- a/src/redux/actions/types.js +++ b/src/redux/actions/types.js @@ -1,15 +1,14 @@ export const UPDATE_ESTIMATE = 'UPDATE_ESTIMATE' export const CLEAN_ESTIMATE = 'CLEAN_ESTIMATE' +export const CLEAN_ALL_ESTIMATES = 'CLEAN_ALL_ESTIMATES' export const RECALCULATE = 'RECALCULATE' export const ADD_SPINNER = 'ADD_SPINNER' export const DEL_SPINNER = 'DEL_SPINNER' -export const SET_HREF = 'SET_HREF' -export const RESET_HREF = 'RESET_HREF' +export const SET_LOCATION = 'SET_LOCATION' +export const RESET_LOCATION = 'RESET_LOCATION' export const SET_CREDS_CHECKING = 'SET_CREDS_CHECKING' export const SET_CREDS = 'SET_CREDS' export const RESET_CREDS = 'RESET_CREDS' -export const OPEN_AUTH_SCREEN = 'OPEN_AUTH_SCREEN' -export const CLOSE_AUTH_SCREEN = 'CLOSE_AUTH_SCREEN' export const SET_TITLES = 'SET_TITLES' export const MARK_ESTIMATE_SAVED = 'MARK_ESTIMATE_SAVED' export const ENLARGE_GRAPH = 'ENLARGE_GRAPH' diff --git a/src/redux/reducers/estimates.js b/src/redux/reducers/estimates.js index da1158d..2968f36 100644 --- a/src/redux/reducers/estimates.js +++ b/src/redux/reducers/estimates.js @@ -3,6 +3,7 @@ import { UPDATE_ESTIMATE, RECALCULATE, CLEAN_ESTIMATE, + CLEAN_ALL_ESTIMATES, SET_TITLES, MARK_ESTIMATE_SAVED, } from '../actions/types' @@ -51,7 +52,12 @@ export default (state = ({ new: emptyEstimate }), { type, payload }) => { [payload || 'new']: { ...emptyEstimate, _id: payload || 'new', - }, // FIXME: !! + }, // TODO: use it somewhere + }) + + case CLEAN_ALL_ESTIMATES: + return ({ + new: emptyEstimate, }) case RECALCULATE: diff --git a/src/redux/reducers/estimates.test.js b/src/redux/reducers/estimates.test.js index 1330887..27b359a 100644 --- a/src/redux/reducers/estimates.test.js +++ b/src/redux/reducers/estimates.test.js @@ -4,6 +4,7 @@ // UPDATE_ESTIMATE, // RECALCULATE, // CLEAN_ESTIMATE, +// CLEAN_ALL_ESTIMATES, // SET_TITLES, // MARK_ESTIMATE_SAVED, // } from '../actions/types' diff --git a/src/redux/reducers/redirector.js b/src/redux/reducers/redirector.js index e8a0309..fa9df0a 100644 --- a/src/redux/reducers/redirector.js +++ b/src/redux/reducers/redirector.js @@ -1,17 +1,17 @@ -import { SET_HREF, RESET_HREF } from '../actions/types' +import { SET_LOCATION, RESET_LOCATION } from '../actions/types' export default (state = ({ - href: '', + location: '', }), { type, payload }) => { switch (type) { - case SET_HREF: + case SET_LOCATION: return ({ - href: payload, + location: payload, }) - case RESET_HREF: + case RESET_LOCATION: return ({ - href: '', + location: '', }) default: diff --git a/src/redux/reducers/redirector.test.js b/src/redux/reducers/redirector.test.js index 67e3923..b0f51e5 100644 --- a/src/redux/reducers/redirector.test.js +++ b/src/redux/reducers/redirector.test.js @@ -1,29 +1,50 @@ /* globals describe, it, expect */ -import { SET_HREF, RESET_HREF } from '../actions/types' +import { SET_LOCATION, RESET_LOCATION } from '../actions/types' import redirector from './redirector' describe("redirector's behavior", () => { it('returns initial state', () => { - expect(redirector(undefined, {})).toEqual({ href: '' }) + expect(redirector(undefined, {})).toEqual({ location: '' }) }) - it('sets up correct path', () => { + it('sets up correct path (string)', () => { // Given - const beforeState = { href: '' } - const action = { type: SET_HREF, payload: '/estimate/new' } + const beforeState = { location: '' } + const action = { type: SET_LOCATION, payload: '/estimate/new' } // When const afterState = redirector(beforeState, action) // Then - expect(afterState).toEqual({ href: '/estimate/new' }) + expect(afterState).toEqual({ location: '/estimate/new' }) + }) + + it('sets up correct path (object)', () => { + // Given + const beforeState = { location: '' } + const action = { + type: SET_LOCATION, + payload: { + pathname: '/auth', + state: { from: '/estimate/new' }, + }, + } + // When + const afterState = redirector(beforeState, action) + // Then + expect(afterState).toEqual({ + location: { + pathname: '/auth', + state: { from: '/estimate/new' }, + }, + }) }) it('resets path', () => { // Given - const beforeState = { href: '/estimate/new' } - const action = { type: RESET_HREF } + const beforeState = { location: '/estimate/new' } + const action = { type: RESET_LOCATION } // When const afterState = redirector(beforeState, action) - expect(afterState).toEqual({ href: '' }) + expect(afterState).toEqual({ location: '' }) }) }) diff --git a/src/redux/reducers/visualEffects.js b/src/redux/reducers/visualEffects.js index 0bccd68..f4cda99 100644 --- a/src/redux/reducers/visualEffects.js +++ b/src/redux/reducers/visualEffects.js @@ -1,15 +1,12 @@ import { ADD_SPINNER, DEL_SPINNER, - OPEN_AUTH_SCREEN, - CLOSE_AUTH_SCREEN, ENLARGE_GRAPH, MINIFY_GRAPH, } from '../actions/types' export default (state = { spinnersCount: false, - showAuthScreen: false, graphView: 'minified', }, { type }) => { switch (type) { @@ -25,18 +22,6 @@ export default (state = { spinnersCount: state.spinnersCount - 1, }) - case OPEN_AUTH_SCREEN: - return ({ - ...state, - showAuthScreen: true, - }) - - case CLOSE_AUTH_SCREEN: - return ({ - ...state, - showAuthScreen: false, - }) - case ENLARGE_GRAPH: return ({ ...state, From 3953c45280a2d582a2fac088f820453985ad83eb Mon Sep 17 00:00:00 2001 From: tatomyr Date: Mon, 13 Aug 2018 11:47:45 +0300 Subject: [PATCH 3/7] refactored ProtectedRoute; defined generic propTypes --- src/components/App/App.js | 7 ++++--- src/components/AuthScreen/Anonymous.js | 2 +- src/components/AuthScreen/AuthScreen.js | 2 +- src/components/AuthScreen/Authorized.js | 2 +- src/components/Dashboard/Dashboard.js | 2 +- src/components/Dashboard/Project.js | 2 +- src/components/Estimate/Editor.js | 2 +- src/components/Estimate/Estimate.js | 8 ++------ src/components/ProtectedRoute/ProtectedRoute.js | 16 +++++++++------- src/components/Redirector/Redirector.js | 9 +-------- src/components/Root.js | 5 +++-- src/components/SideButton/SideButton.js | 2 +- src/components/Sidebar/Sidebar.js | 9 ++------- .../Estimate => helpers}/propTypes.js | 14 ++++++++++++++ 14 files changed, 42 insertions(+), 40 deletions(-) rename src/{components/Estimate => helpers}/propTypes.js (59%) diff --git a/src/components/App/App.js b/src/components/App/App.js index dc063c2..3784293 100644 --- a/src/components/App/App.js +++ b/src/components/App/App.js @@ -1,14 +1,15 @@ import React from 'react' import PropTypes from 'prop-types' -import { Switch, Route } from 'react-router-dom' +import { Switch } from 'react-router-dom' import Toastr from '../Toastr' import Redirector from '../Redirector' import Spinner from '../Spinner' import Estimate from '../Estimate' import Dashboard from '../Dashboard' import AuthScreen from '../AuthScreen' -import ProtectedRoute from '../ProtectedRoute' +import Route from '../ProtectedRoute' +// TODO: move to Root. Check creds in ProtectedRoute class App extends React.Component { componentDidMount = () => { const { checkCreds, username } = this.props @@ -19,7 +20,7 @@ class App extends React.Component {
- + diff --git a/src/components/AuthScreen/Anonymous.js b/src/components/AuthScreen/Anonymous.js index 9a061bb..d6dc3f0 100644 --- a/src/components/AuthScreen/Anonymous.js +++ b/src/components/AuthScreen/Anonymous.js @@ -10,7 +10,7 @@ import { } from 'reactstrap' import FA from 'react-fontawesome' import * as api from '../../helpers/api' -import { locationType } from '../Redirector/Redirector' +import { locationType } from '../../helpers/propTypes' const AlertAccessingEstimate = ({ from }) => ((from && from.pathname diff --git a/src/components/AuthScreen/AuthScreen.js b/src/components/AuthScreen/AuthScreen.js index 0cd7d23..c5c9f5e 100644 --- a/src/components/AuthScreen/AuthScreen.js +++ b/src/components/AuthScreen/AuthScreen.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import Header from '../Header' import Authorized from './Authorized' import Anonymous from './Anonymous' -import { locationType } from '../Redirector/Redirector' +import { locationType } from '../../helpers/propTypes' const AuthScreen = ({ username, diff --git a/src/components/AuthScreen/Authorized.js b/src/components/AuthScreen/Authorized.js index 0ff923a..a820872 100644 --- a/src/components/AuthScreen/Authorized.js +++ b/src/components/AuthScreen/Authorized.js @@ -3,7 +3,7 @@ import { Redirect } from 'react-router-dom' import PropTypes from 'prop-types' import { Button } from 'reactstrap' import * as api from '../../helpers/api' -import { locationType } from '../Redirector/Redirector' +import { locationType } from '../../helpers/propTypes' const Authorized = ({ username, diff --git a/src/components/Dashboard/Dashboard.js b/src/components/Dashboard/Dashboard.js index bae542c..1df6456 100644 --- a/src/components/Dashboard/Dashboard.js +++ b/src/components/Dashboard/Dashboard.js @@ -8,7 +8,7 @@ import { } from 'reactstrap' import Header from '../Header' import Project from './Project' -import { estimateType } from '../Estimate/propTypes' +import { estimateType } from '../../helpers/propTypes' const Dashboard = ({ estimates }) => (
diff --git a/src/components/Dashboard/Project.js b/src/components/Dashboard/Project.js index 4ebf333..dbf1e95 100644 --- a/src/components/Dashboard/Project.js +++ b/src/components/Dashboard/Project.js @@ -11,7 +11,7 @@ import { Button, } from 'reactstrap' import FA from 'react-fontawesome' -import { estimateType } from '../Estimate/propTypes' +import { estimateType } from '../../helpers/propTypes' const Project = ({ estimate: { diff --git a/src/components/Estimate/Editor.js b/src/components/Estimate/Editor.js index 5210e17..31079e9 100644 --- a/src/components/Estimate/Editor.js +++ b/src/components/Estimate/Editor.js @@ -2,7 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import MonacoEditor from 'react-monaco-editor' import { options, languageDef, configuration } from './editor-config' -import { estimateType } from './propTypes' +import { estimateType } from '../../helpers/propTypes' const editorWillMount = monaco => { this.editor = monaco diff --git a/src/components/Estimate/Estimate.js b/src/components/Estimate/Estimate.js index b7c8ded..d761697 100644 --- a/src/components/Estimate/Estimate.js +++ b/src/components/Estimate/Estimate.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import Editor from './Editor' import Graph from './Graph' import Sidebar from '../Sidebar' -import { estimateType } from './propTypes' +import { matchType, estimateType } from '../../helpers/propTypes' class Estimate extends React.Component { componentDidMount = () => { @@ -68,11 +68,7 @@ class Estimate extends React.Component { } Estimate.propTypes = { - match: PropTypes.shape({ - params: PropTypes.shape({ - estimateId: PropTypes.string, - }).isRequired, - }).isRequired, + match: matchType.isRequired, estimates: PropTypes.objectOf(estimateType).isRequired, getEstimate: PropTypes.func.isRequired, updateEstimate: PropTypes.func.isRequired, diff --git a/src/components/ProtectedRoute/ProtectedRoute.js b/src/components/ProtectedRoute/ProtectedRoute.js index 0c1a763..ea9f4de 100644 --- a/src/components/ProtectedRoute/ProtectedRoute.js +++ b/src/components/ProtectedRoute/ProtectedRoute.js @@ -1,18 +1,19 @@ import React from 'react' import PropTypes from 'prop-types' import { Route, Redirect } from 'react-router-dom' -import { locationType } from '../Redirector/Redirector' +import { locationType, matchType } from '../../helpers/propTypes' // TODO: implement checkCreds here if !username instead of checkeng if App const ProtectedRoute = ({ component: Component, + isProtected, username, match: { params: { estimateId = '' } }, ...rest }) => ( (username || estimateId === 'new' + render={props => (!isProtected || username || estimateId === 'new' ? : ( location && ( ) -export const locationType = PropTypes.oneOfType([ - PropTypes.string, - PropTypes.shape({ - pathname: PropTypes.string.isRequired, - state: PropTypes.any, - }), -]) - Redirector.propTypes = { redirector: PropTypes.shape({ location: locationType, diff --git a/src/components/Root.js b/src/components/Root.js index cc6cc95..87e63ac 100644 --- a/src/components/Root.js +++ b/src/components/Root.js @@ -1,7 +1,8 @@ import React from 'react' import PropTypes from 'prop-types' import { Provider } from 'react-redux' -import { HashRouter as Router, Route, Switch } from 'react-router-dom' +import { HashRouter as Router, Switch } from 'react-router-dom' +import Route from './ProtectedRoute' import App from './App' import Home from './Home' @@ -10,7 +11,7 @@ const Root = ({ store }) => ( - + diff --git a/src/components/SideButton/SideButton.js b/src/components/SideButton/SideButton.js index 26a6286..1e45670 100644 --- a/src/components/SideButton/SideButton.js +++ b/src/components/SideButton/SideButton.js @@ -2,7 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import FA from 'react-fontawesome' import { Button } from 'reactstrap' -import { locationType } from '../Redirector/Redirector' +import { locationType } from '../../helpers/propTypes' const SideButton = ({ title, diff --git a/src/components/Sidebar/Sidebar.js b/src/components/Sidebar/Sidebar.js index 76c73fa..cc52ab6 100644 --- a/src/components/Sidebar/Sidebar.js +++ b/src/components/Sidebar/Sidebar.js @@ -1,8 +1,7 @@ import React from 'react' import PropTypes from 'prop-types' import SideButton from '../SideButton' -import { estimateType } from '../Estimate/propTypes' -import { locationType } from '../Redirector/Redirector' +import { locationType, matchType, estimateType } from '../../helpers/propTypes' const Sidebar = ({ match: { params: { estimateId } }, @@ -80,11 +79,7 @@ const Sidebar = ({ } Sidebar.propTypes = { - match: PropTypes.shape({ - params: PropTypes.shape({ - estimateId: PropTypes.string, - }).isRequired, - }).isRequired, + match: matchType.isRequired, location: locationType.isRequired, recalc: PropTypes.func.isRequired, saveEstimate: PropTypes.func.isRequired, diff --git a/src/components/Estimate/propTypes.js b/src/helpers/propTypes.js similarity index 59% rename from src/components/Estimate/propTypes.js rename to src/helpers/propTypes.js index 536b05b..463f6b3 100644 --- a/src/components/Estimate/propTypes.js +++ b/src/helpers/propTypes.js @@ -11,3 +11,17 @@ export const estimateType = PropTypes.shape({ calculated: PropTypes.bool, saved: PropTypes.bool.isRequired, }) + +export const matchType = PropTypes.shape({ + params: PropTypes.shape({ + estimateId: PropTypes.string, + }).isRequired, +}) + +export const locationType = PropTypes.oneOfType([ + PropTypes.string, + PropTypes.shape({ + pathname: PropTypes.string.isRequired, + state: PropTypes.any, + }), +]) From 6672b7e50876aa3a8e335510586d22a7f7e4966e Mon Sep 17 00:00:00 2001 From: tatomyr Date: Mon, 13 Aug 2018 23:59:30 +0300 Subject: [PATCH 4/7] merged App->Root; refactored AuthScreen, ProtectedRoute; reintroduced Private; changed creds reducer --- package.json | 4 +- src/components/App/App.js | 38 ------------------- src/components/App/index.js | 20 ---------- src/components/App/styles.css | 0 src/components/AuthScreen/AuthScreen.js | 30 ++++++++------- src/components/AuthScreen/AuthScreen.test.js | 18 ++++++--- .../AuthScreen/CredsCheckingScreen.js | 1 + src/components/AuthScreen/index.js | 12 +----- src/components/Estimate/Estimate.js | 1 + src/components/Home/Home.js | 2 +- src/components/Private/Private.js | 30 +++++++++++++++ src/components/Private/index.js | 9 +++++ .../ProtectedRoute/ProtectedRoute.js | 7 +--- src/components/Root.js | 28 ++++++++++---- src/helpers/propTypes.js | 7 ++++ src/redux/actions/async.js | 2 - src/redux/actions/index.js | 5 --- src/redux/actions/types.js | 1 - src/redux/reducers/creds.js | 20 ++++------ src/redux/reducers/creds.test.js | 21 ++-------- 20 files changed, 116 insertions(+), 140 deletions(-) delete mode 100644 src/components/App/App.js delete mode 100644 src/components/App/index.js delete mode 100644 src/components/App/styles.css create mode 100644 src/components/AuthScreen/CredsCheckingScreen.js create mode 100644 src/components/Private/Private.js create mode 100644 src/components/Private/index.js diff --git a/package.json b/package.json index d4acb7c..e6b71aa 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,11 @@ "homepage": "https://tatomyr.github.io/estimate-it/", "scripts": { "start": "react-scripts start", - "build": "react-scripts build && npm run analyze", + "build": "react-scripts build", "lint": "eslint src/", "test": "react-scripts test --env=jsdom --coverage", "precommit": "npm run lint && npm run test", - "predeploy": "npm run build", + "predeploy": "npm run build && npm run analyze", "deploy": "gh-pages -d build", "analyze": "source-map-explorer build/static/js/main.*", "eject": "react-scripts eject" diff --git a/src/components/App/App.js b/src/components/App/App.js deleted file mode 100644 index 3784293..0000000 --- a/src/components/App/App.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { Switch } from 'react-router-dom' -import Toastr from '../Toastr' -import Redirector from '../Redirector' -import Spinner from '../Spinner' -import Estimate from '../Estimate' -import Dashboard from '../Dashboard' -import AuthScreen from '../AuthScreen' -import Route from '../ProtectedRoute' - -// TODO: move to Root. Check creds in ProtectedRoute -class App extends React.Component { - componentDidMount = () => { - const { checkCreds, username } = this.props - if (!username) checkCreds() - } - - render = () => ( -
- - - - - - - - -
- ) -} - -App.propTypes = { - checkCreds: PropTypes.func.isRequired, - username: PropTypes.string.isRequired, -} - -export default App diff --git a/src/components/App/index.js b/src/components/App/index.js deleted file mode 100644 index 03a0fc5..0000000 --- a/src/components/App/index.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from 'react-redux' -import App from './App' -import './styles.css' -import { - checkCreds, -} from '../../redux/actions/async' - -const mapStateToProps = ({ - visualEffects: { showAuthScreen }, - creds: { username }, -}) => ({ - showAuthScreen, - username, -}) - -const mapDispatchToProps = ({ - checkCreds, -}) - -export default connect(mapStateToProps, mapDispatchToProps)(App) diff --git a/src/components/App/styles.css b/src/components/App/styles.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/AuthScreen/AuthScreen.js b/src/components/AuthScreen/AuthScreen.js index c5c9f5e..4e5be38 100644 --- a/src/components/AuthScreen/AuthScreen.js +++ b/src/components/AuthScreen/AuthScreen.js @@ -1,34 +1,36 @@ import React from 'react' -import PropTypes from 'prop-types' import Header from '../Header' import Authorized from './Authorized' import Anonymous from './Anonymous' -import { locationType } from '../../helpers/propTypes' +import CredsCheckingScreen from './CredsCheckingScreen' +import { locationType, credsType } from '../../helpers/propTypes' const AuthScreen = ({ - username, - checkingCreds, + creds: { + haveBeenChecked, + username, + }, location: { state }, ...rest }) => (
- {(checkingCreds && 'Checking permissions. Please wait…') || (username && ( - ) - ) || } + {(!haveBeenChecked && ) + || (username && ( + )) + || }
) AuthScreen.propTypes = { - username: PropTypes.string.isRequired, - checkingCreds: PropTypes.bool.isRequired, + creds: credsType.isRequired, location: locationType.isRequired, } diff --git a/src/components/AuthScreen/AuthScreen.test.js b/src/components/AuthScreen/AuthScreen.test.js index 8cbf973..4123daa 100644 --- a/src/components/AuthScreen/AuthScreen.test.js +++ b/src/components/AuthScreen/AuthScreen.test.js @@ -20,8 +20,10 @@ describe('AuthScreen', () => { .create( // eslint-disable-line comma-dangle @@ -35,8 +37,10 @@ describe('AuthScreen', () => { .create( // eslint-disable-line comma-dangle @@ -50,8 +54,10 @@ describe('AuthScreen', () => { .create( // eslint-disable-line comma-dangle diff --git a/src/components/AuthScreen/CredsCheckingScreen.js b/src/components/AuthScreen/CredsCheckingScreen.js new file mode 100644 index 0000000..a54408d --- /dev/null +++ b/src/components/AuthScreen/CredsCheckingScreen.js @@ -0,0 +1 @@ +export default () => 'Checking permissions. Please wait…' diff --git a/src/components/AuthScreen/index.js b/src/components/AuthScreen/index.js index 9d78ca7..71544e3 100644 --- a/src/components/AuthScreen/index.js +++ b/src/components/AuthScreen/index.js @@ -11,20 +11,12 @@ import { redirect, } from '../../redux/actions/async' -const mapStateToProps = ({ - creds: { - username, - checkingCreds, - }, -}) => ({ - username, - checkingCreds, -}) +const mapStateToProps = ({ creds }) => ({ creds }) const mapDispatchToProps = ({ checkCreds, - cleanAllEstimates, resetCreds, + cleanAllEstimates, redirect, }) diff --git a/src/components/Estimate/Estimate.js b/src/components/Estimate/Estimate.js index d761697..712a710 100644 --- a/src/components/Estimate/Estimate.js +++ b/src/components/Estimate/Estimate.js @@ -18,6 +18,7 @@ class Estimate extends React.Component { .values(estimates) .some(({ saved }) => !saved) // Setting up hook to prevent of accidental window closing/refreshing + // FIXME: after clearing all estimates this doesn't change window.onbeforeunload = thereIsUnsavedEstimate ? () => true : null diff --git a/src/components/Home/Home.js b/src/components/Home/Home.js index d9e9f81..4cc63c0 100644 --- a/src/components/Home/Home.js +++ b/src/components/Home/Home.js @@ -35,7 +35,7 @@ const Home = () => ( diff --git a/src/components/Private/Private.js b/src/components/Private/Private.js new file mode 100644 index 0000000..ace51d4 --- /dev/null +++ b/src/components/Private/Private.js @@ -0,0 +1,30 @@ +import React, { Component, Fragment } from 'react' +import PropTypes from 'prop-types' +import { credsType } from '../../helpers/propTypes' + +class Private extends Component { + componentDidMount = () => { + const { + creds, + checkCreds, + } = this.props + if (!creds.haveBeenChecked) { + checkCreds() + } + } + + render = () => ( + + {/* eslint-disable-next-line react/destructuring-assignment */} + {this.props.children} + + ) +} + +Private.propTypes = { + creds: credsType.isRequired, + checkCreds: PropTypes.func.isRequired, + children: PropTypes.node.isRequired, +} + +export default Private diff --git a/src/components/Private/index.js b/src/components/Private/index.js new file mode 100644 index 0000000..5492122 --- /dev/null +++ b/src/components/Private/index.js @@ -0,0 +1,9 @@ +import { connect } from 'react-redux' +import Private from './Private' +import { checkCreds } from '../../redux/actions/async' + +const mapStateToProps = ({ creds }) => ({ creds }) + +const mapDispatchToProps = ({ checkCreds }) + +export default connect(mapStateToProps, mapDispatchToProps)(Private) diff --git a/src/components/ProtectedRoute/ProtectedRoute.js b/src/components/ProtectedRoute/ProtectedRoute.js index ea9f4de..162e071 100644 --- a/src/components/ProtectedRoute/ProtectedRoute.js +++ b/src/components/ProtectedRoute/ProtectedRoute.js @@ -1,19 +1,17 @@ import React from 'react' import PropTypes from 'prop-types' import { Route, Redirect } from 'react-router-dom' -import { locationType, matchType } from '../../helpers/propTypes' +import { locationType } from '../../helpers/propTypes' -// TODO: implement checkCreds here if !username instead of checkeng if App const ProtectedRoute = ({ component: Component, isProtected, username, - match: { params: { estimateId = '' } }, ...rest }) => ( (!isProtected || username || estimateId === 'new' + render={props => (!isProtected || username || props.location.pathname === '/estimate/new' ? : ( ( - - - - - - +
+ + + + + {/* TODO: */} + + + + + + + +
) diff --git a/src/helpers/propTypes.js b/src/helpers/propTypes.js index 463f6b3..e25f4d6 100644 --- a/src/helpers/propTypes.js +++ b/src/helpers/propTypes.js @@ -25,3 +25,10 @@ export const locationType = PropTypes.oneOfType([ state: PropTypes.any, }), ]) + +export const credsType = PropTypes.shape({ + haveBeenChecked: PropTypes.bool.isRequired, + username: PropTypes.string.isRequired, + dbName: PropTypes.string.isRequired, + apiKey: PropTypes.string.isRequired, +}) diff --git a/src/redux/actions/async.js b/src/redux/actions/async.js index 58dd259..43c5d7a 100644 --- a/src/redux/actions/async.js +++ b/src/redux/actions/async.js @@ -23,7 +23,6 @@ import { resetCreds, setTitles, markEstimateSaved, - setCredsChecking, } from './index' export const redirect = location => dispatch => { @@ -106,7 +105,6 @@ export const getEstimate = ({ estimateId }) => dispatch => { export const checkCreds = () => dispatch => { dispatch({ type: '__ASYNC__CHECK_CREDS' }) - dispatch(setCredsChecking()) dispatch(addSpinner()) return api.fetchTitles() .then(titles => { diff --git a/src/redux/actions/index.js b/src/redux/actions/index.js index 1106295..c8aec0e 100644 --- a/src/redux/actions/index.js +++ b/src/redux/actions/index.js @@ -13,7 +13,6 @@ import { MARK_ESTIMATE_SAVED, ENLARGE_GRAPH, MINIFY_GRAPH, - SET_CREDS_CHECKING, } from './types' import * as api from '../../helpers/api' @@ -53,10 +52,6 @@ export const delSpinner = () => ({ type: DEL_SPINNER, }) -export const setCredsChecking = () => ({ - type: SET_CREDS_CHECKING, -}) - export const setCreds = () => ({ type: SET_CREDS, payload: { ...api.getCreds() }, diff --git a/src/redux/actions/types.js b/src/redux/actions/types.js index 3919f9a..4a1c1fd 100644 --- a/src/redux/actions/types.js +++ b/src/redux/actions/types.js @@ -6,7 +6,6 @@ export const ADD_SPINNER = 'ADD_SPINNER' export const DEL_SPINNER = 'DEL_SPINNER' export const SET_LOCATION = 'SET_LOCATION' export const RESET_LOCATION = 'RESET_LOCATION' -export const SET_CREDS_CHECKING = 'SET_CREDS_CHECKING' export const SET_CREDS = 'SET_CREDS' export const RESET_CREDS = 'RESET_CREDS' export const SET_TITLES = 'SET_TITLES' diff --git a/src/redux/reducers/creds.js b/src/redux/reducers/creds.js index 228f885..8bd3311 100644 --- a/src/redux/reducers/creds.js +++ b/src/redux/reducers/creds.js @@ -1,29 +1,25 @@ -// TODO: Implement login route: .../login/:dbName/:apiKey -import { SET_CREDS_CHECKING, SET_CREDS, RESET_CREDS } from '../actions/types' +import { SET_CREDS, RESET_CREDS } from '../actions/types' export const emptyCreds = ({ + haveBeenChecked: false, + username: '', dbName: '', apiKey: '', - username: '', - checkingCreds: false, }) export default (state = emptyCreds, { type, payload }) => { switch (type) { - case SET_CREDS_CHECKING: - return ({ - ...state, - checkingCreds: true, - }) - case SET_CREDS: return ({ ...payload, - checkingCreds: false, + haveBeenChecked: true, }) case RESET_CREDS: - return emptyCreds + return ({ + ...emptyCreds, + haveBeenChecked: true, + }) default: return state diff --git a/src/redux/reducers/creds.test.js b/src/redux/reducers/creds.test.js index 42df8be..ddbb888 100644 --- a/src/redux/reducers/creds.test.js +++ b/src/redux/reducers/creds.test.js @@ -1,6 +1,6 @@ /* globals describe, it, expect */ -import { SET_CREDS_CHECKING, SET_CREDS, RESET_CREDS } from '../actions/types' +import { SET_CREDS, RESET_CREDS } from '../actions/types' import creds, { emptyCreds } from './creds' describe('authentication', () => { @@ -8,19 +8,6 @@ describe('authentication', () => { expect(creds(undefined, {})).toEqual(emptyCreds) }) - it('sets checking creds status correctly', () => { - // Given - const beforeState = emptyCreds - const action = { type: SET_CREDS_CHECKING } - // When - const afterState = creds(beforeState, action) - // Then - expect(afterState).toEqual({ - ...emptyCreds, - checkingCreds: true, - }) - }) - it('sets creds correctly', () => { // Given const beforeState = emptyCreds @@ -36,25 +23,25 @@ describe('authentication', () => { const afterState = creds(beforeState, action) // Then expect(afterState).toEqual({ + haveBeenChecked: true, user: 'Test User', dbName: 'dbName', apiKey: 'apiKey', - checkingCreds: false, }) }) it('resets creds correctly', () => { // Given const beforeState = { + haveBeenChecked: false, user: 'Test User', dbName: 'dbName', apiKey: 'apiKey', - checkingCreds: true, } const action = { type: RESET_CREDS } // When const afterState = creds(beforeState, action) // Then - expect(afterState).toEqual(emptyCreds) + expect(afterState).toEqual({ ...emptyCreds, haveBeenChecked: true }) }) }) From 0bedad9885bc91fb93e8149b0ed9cb9c04d3e533 Mon Sep 17 00:00:00 2001 From: tatomyr Date: Tue, 14 Aug 2018 23:49:09 +0300 Subject: [PATCH 5/7] moved /estimate/new to the separate route; refactored ProtectedRoute, redirection on Authorized, Sidebar --- src/components/AuthScreen/AuthScreen.js | 2 +- src/components/AuthScreen/AuthScreen.test.js | 7 ++++ src/components/AuthScreen/Authorized.js | 14 +++---- src/components/AuthScreen/index.js | 2 - src/components/Estimate/Estimate.js | 6 +-- .../ProtectedRoute/ProtectedRoute.js | 8 ++-- src/components/Redirector/Redirector.js | 2 +- src/components/Root.js | 10 +++-- src/components/Sidebar/Sidebar.js | 17 ++------ .../__snapshots__/Sidebar.test.js.snap | 40 ++----------------- src/redux/actions/async.js | 4 ++ src/redux/reducers/estimates.test.js | 2 - 12 files changed, 41 insertions(+), 73 deletions(-) diff --git a/src/components/AuthScreen/AuthScreen.js b/src/components/AuthScreen/AuthScreen.js index 4e5be38..cf4ef46 100644 --- a/src/components/AuthScreen/AuthScreen.js +++ b/src/components/AuthScreen/AuthScreen.js @@ -21,7 +21,7 @@ const AuthScreen = ({ )) || } diff --git a/src/components/AuthScreen/AuthScreen.test.js b/src/components/AuthScreen/AuthScreen.test.js index 4123daa..c13e539 100644 --- a/src/components/AuthScreen/AuthScreen.test.js +++ b/src/components/AuthScreen/AuthScreen.test.js @@ -12,6 +12,7 @@ const commons = { resetCreds: () => null, cleanAllEstimates: () => null, redirect: () => null, + history: { goBack: () => null }, } describe('AuthScreen', () => { @@ -23,6 +24,8 @@ describe('AuthScreen', () => { creds={{ haveBeenChecked: true, username: 'Test User', + dbName: 'dbName', + apiKey: 'apiKey', }} {...commons} /> @@ -40,6 +43,8 @@ describe('AuthScreen', () => { creds={{ haveBeenChecked: true, username: '', + dbName: '', + apiKey: '', }} {...commons} /> @@ -57,6 +62,8 @@ describe('AuthScreen', () => { creds={{ haveBeenChecked: false, username: '', + dbName: '', + apiKey: '', }} {...commons} /> diff --git a/src/components/AuthScreen/Authorized.js b/src/components/AuthScreen/Authorized.js index a820872..ddfaed3 100644 --- a/src/components/AuthScreen/Authorized.js +++ b/src/components/AuthScreen/Authorized.js @@ -9,12 +9,12 @@ const Authorized = ({ username, resetCreds, cleanAllEstimates, - redirect, from, - redirectAutomatically, + redirectToReferrer, + history, }) => ( - {redirectAutomatically && } + {redirectToReferrer && }
{/* eslint-disable-next-line react/jsx-one-expression-per-line */} Welcome, {username}! @@ -22,7 +22,7 @@ const Authorized = ({ @@ -44,13 +44,13 @@ Authorized.propTypes = { username: PropTypes.string.isRequired, resetCreds: PropTypes.func.isRequired, cleanAllEstimates: PropTypes.func.isRequired, - redirect: PropTypes.func.isRequired, from: locationType.isRequired, - redirectAutomatically: PropTypes.bool, + redirectToReferrer: PropTypes.bool, + history: PropTypes.objectOf(PropTypes.any).isRequired, } Authorized.defaultProps = { - redirectAutomatically: false, + redirectToReferrer: false, } export default Authorized diff --git a/src/components/AuthScreen/index.js b/src/components/AuthScreen/index.js index 71544e3..7be10e6 100644 --- a/src/components/AuthScreen/index.js +++ b/src/components/AuthScreen/index.js @@ -8,7 +8,6 @@ import { } from '../../redux/actions' import { checkCreds, - redirect, } from '../../redux/actions/async' const mapStateToProps = ({ creds }) => ({ creds }) @@ -17,7 +16,6 @@ const mapDispatchToProps = ({ checkCreds, resetCreds, cleanAllEstimates, - redirect, }) export default withRouter(connect(mapStateToProps, mapDispatchToProps)(AuthScreen)) diff --git a/src/components/Estimate/Estimate.js b/src/components/Estimate/Estimate.js index 712a710..fb91fa1 100644 --- a/src/components/Estimate/Estimate.js +++ b/src/components/Estimate/Estimate.js @@ -27,10 +27,10 @@ class Estimate extends React.Component { fetchHelper = () => { const { getEstimate, - match: { params: { estimateId } }, + match: { params: { estimateId = 'new' } }, estimates, } = this.props - // Fetching an appropriate estimate on route change. + // Fetching an appropriate estimate if such doesn't exist const estimate = estimates[estimateId] const estimateIsntLoaded = !estimate || estimate.text === undefined if (estimateIsntLoaded) { @@ -40,7 +40,7 @@ class Estimate extends React.Component { render = () => { const { - match: { params: { estimateId } }, + match: { params: { estimateId = 'new' } }, estimates, updateEstimate, graphView, diff --git a/src/components/ProtectedRoute/ProtectedRoute.js b/src/components/ProtectedRoute/ProtectedRoute.js index 162e071..44ffe4e 100644 --- a/src/components/ProtectedRoute/ProtectedRoute.js +++ b/src/components/ProtectedRoute/ProtectedRoute.js @@ -11,16 +11,16 @@ const ProtectedRoute = ({ }) => ( (!isProtected || username || props.location.pathname === '/estimate/new' - ? - : ( + render={props => (isProtected && !username + ? ( ) + : )} /> ) diff --git a/src/components/Redirector/Redirector.js b/src/components/Redirector/Redirector.js index 3a3a650..8409c4c 100644 --- a/src/components/Redirector/Redirector.js +++ b/src/components/Redirector/Redirector.js @@ -4,7 +4,7 @@ import { Redirect } from 'react-router-dom' import { locationType } from '../../helpers/propTypes' const Redirector = ({ redirector: { location } }) => location && ( - + ) Redirector.propTypes = { diff --git a/src/components/Root.js b/src/components/Root.js index dc7589b..62a7129 100644 --- a/src/components/Root.js +++ b/src/components/Root.js @@ -20,10 +20,12 @@ const Root = ({ store }) => ( - {/* TODO: */} - - - + + + + + + diff --git a/src/components/Sidebar/Sidebar.js b/src/components/Sidebar/Sidebar.js index cc52ab6..18d29c8 100644 --- a/src/components/Sidebar/Sidebar.js +++ b/src/components/Sidebar/Sidebar.js @@ -1,11 +1,10 @@ import React from 'react' import PropTypes from 'prop-types' import SideButton from '../SideButton' -import { locationType, matchType, estimateType } from '../../helpers/propTypes' +import { matchType, estimateType } from '../../helpers/propTypes' const Sidebar = ({ - match: { params: { estimateId } }, - location, + match: { params: { estimateId = 'new' } }, recalc, saveEstimate, estimates, @@ -64,10 +63,7 @@ const Sidebar = ({ title="Auth" name={username ? 'unlock' : 'lock'} color={username ? 'success' : 'warning'} - link={{ - pathname: '/auth', - state: { from: location }, - }} + link="/auth" /> , , @@ -209,14 +200,7 @@ ShallowWrapper { "nodeType": "class", "props": Object { "color": "success", - "link": Object { - "pathname": "/auth", - "state": Object { - "from": Object { - "pathname": "/", - }, - }, - }, + "link": "/auth", "name": "unlock", "title": "Auth", }, @@ -286,16 +270,7 @@ ShallowWrapper { />, , @@ -407,14 +382,7 @@ ShallowWrapper { "nodeType": "class", "props": Object { "color": "success", - "link": Object { - "pathname": "/auth", - "state": Object { - "from": Object { - "pathname": "/", - }, - }, - }, + "link": "/auth", "name": "unlock", "title": "Auth", }, diff --git a/src/redux/actions/async.js b/src/redux/actions/async.js index 43c5d7a..43859c6 100644 --- a/src/redux/actions/async.js +++ b/src/redux/actions/async.js @@ -23,6 +23,7 @@ import { resetCreds, setTitles, markEstimateSaved, + cleanEstimate, } from './index' export const redirect = location => dispatch => { @@ -67,6 +68,9 @@ export const saveEstimate = ({ estimateId }) => (dispatch, getState) => { .then(savedEstimate => { dispatch(updateEstimate(savedEstimate)) dispatch(markEstimateSaved(savedEstimate._id)) + if (estimateId === 'new') { + dispatch(cleanEstimate('new')) + } dispatch(redirect(`/estimate/${savedEstimate._id}`)) toastr.success(...saved(savedEstimate)) }) diff --git a/src/redux/reducers/estimates.test.js b/src/redux/reducers/estimates.test.js index 27b359a..b133339 100644 --- a/src/redux/reducers/estimates.test.js +++ b/src/redux/reducers/estimates.test.js @@ -10,8 +10,6 @@ // } from '../actions/types' import estimates, { emptyEstimate } from './estimates' -// FIXME: avoid duplicating 'new' estimates after logging out - describe('estimates reducer', () => { it('returns initial state', () => { expect(estimates(undefined, {})).toEqual({ new: emptyEstimate }) From 46d27ee8b3094c9a0b7296d9e2e6039b10e326c9 Mon Sep 17 00:00:00 2001 From: tatomyr Date: Wed, 15 Aug 2018 23:39:36 +0300 Subject: [PATCH 6/7] reintroduced App; refactored estimates actions and reducer and wrote tests; other minor changes --- package.json | 2 +- src/components/App.js | 32 +++ src/components/Dashboard/Project.js | 2 +- src/components/Root.js | 29 +-- src/components/Sidebar/Sidebar.js | 7 +- .../__snapshots__/Sidebar.test.js.snap | 80 ++---- src/redux/actions/async.js | 2 +- src/redux/actions/index.js | 14 +- src/redux/reducers/estimates.js | 38 ++- src/redux/reducers/estimates.test.js | 246 +++++++++++++++++- 10 files changed, 330 insertions(+), 122 deletions(-) create mode 100644 src/components/App.js diff --git a/package.json b/package.json index e6b71aa..cbaa096 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "react-scripts build", "lint": "eslint src/", "test": "react-scripts test --env=jsdom --coverage", - "precommit": "npm run lint && npm run test", + "precommit": "git status && npm run lint && npm run test", "predeploy": "npm run build && npm run analyze", "deploy": "gh-pages -d build", "analyze": "source-map-explorer build/static/js/main.*", diff --git a/src/components/App.js b/src/components/App.js new file mode 100644 index 0000000..842882f --- /dev/null +++ b/src/components/App.js @@ -0,0 +1,32 @@ +import React from 'react' +import { Switch } from 'react-router-dom' +import Route from './ProtectedRoute' +import Home from './Home' +import Private from './Private' +import Redirector from './Redirector' +import Estimate from './Estimate' +import Dashboard from './Dashboard' +import AuthScreen from './AuthScreen' +import Spinner from './Spinner' +import Toastr from './Toastr' + +const App = () => ( +
+ + + + + + + + + + + + + + +
+) + +export default App diff --git a/src/components/Dashboard/Project.js b/src/components/Dashboard/Project.js index dbf1e95..36039b2 100644 --- a/src/components/Dashboard/Project.js +++ b/src/components/Dashboard/Project.js @@ -1,4 +1,4 @@ -// TODO: implement delete project +// TODO: implement deleting project import React from 'react' import { Link } from 'react-router-dom' diff --git a/src/components/Root.js b/src/components/Root.js index 62a7129..7847406 100644 --- a/src/components/Root.js +++ b/src/components/Root.js @@ -1,36 +1,13 @@ import React from 'react' import PropTypes from 'prop-types' import { Provider } from 'react-redux' -import { HashRouter as Router, Switch } from 'react-router-dom' -import Route from './ProtectedRoute' -import Home from './Home' -import Private from './Private' -import Redirector from './Redirector' -import Estimate from './Estimate' -import Dashboard from './Dashboard' -import AuthScreen from './AuthScreen' -import Spinner from './Spinner' -import Toastr from './Toastr' +import { HashRouter as Router } from 'react-router-dom' +import App from './App' const Root = ({ store }) => ( -
- - - - - - - - - - - - - - -
+
) diff --git a/src/components/Sidebar/Sidebar.js b/src/components/Sidebar/Sidebar.js index 18d29c8..4c46e6e 100644 --- a/src/components/Sidebar/Sidebar.js +++ b/src/components/Sidebar/Sidebar.js @@ -22,11 +22,6 @@ const Sidebar = ({ disabled={estimateId === 'new'} link="/estimate/new" /> - )} +
-
, - , , +
, , -
, , - , , +
, , -
, ({ payload: estimate, }) -export const cleanEstimate = ({ estimateId }) => ({ +export const cleanEstimate = _id => ({ type: CLEAN_ESTIMATE, - payload: estimateId, + payload: { _id }, }) export const cleanAllEstimates = () => ({ @@ -32,7 +32,7 @@ export const cleanAllEstimates = () => ({ export const recalc = _id => ({ type: RECALCULATE, - payload: _id, + payload: { _id }, }) export const setLocation = location => ({ @@ -61,14 +61,14 @@ export const resetCreds = () => ({ type: RESET_CREDS, }) -export const setTitles = payload => ({ +export const setTitles = projects => ({ type: SET_TITLES, - payload, + payload: { projects }, }) -export const markEstimateSaved = payload => ({ +export const markEstimateSaved = _id => ({ type: MARK_ESTIMATE_SAVED, - payload, + payload: { _id }, }) export const enlargeGraph = () => ({ diff --git a/src/redux/reducers/estimates.js b/src/redux/reducers/estimates.js index 2968f36..04ab003 100644 --- a/src/redux/reducers/estimates.js +++ b/src/redux/reducers/estimates.js @@ -44,16 +44,19 @@ export default (state = ({ new: emptyEstimate }), { type, payload }) => { } case CLEAN_ESTIMATE: + { + // Putting 'new' here to avoid creating an estimate with an undefined `_id` + // ...while logging out on different routes + // ...(that may not contain an `:estimateId` param) + const { _id = 'new' } = payload return ({ ...state, - // Put 'new' here to avoid creating an estimate with an undefined `_id` - // ...while logging out on different routes - // ...(that may not contain an `:estimateId` param) - [payload || 'new']: { + [_id]: { ...emptyEstimate, - _id: payload || 'new', - }, // TODO: use it somewhere + _id, + }, }) + } case CLEAN_ALL_ESTIMATES: return ({ @@ -61,21 +64,26 @@ export default (state = ({ new: emptyEstimate }), { type, payload }) => { }) case RECALCULATE: + { + const { _id } = payload return ({ ...state, - [payload]: { - ...state[payload], - payload, - ...handleText(state[payload].text), + [_id]: { + ...state[_id], + _id, + ...handleText(state[_id].text), calculated: true, saved: false, }, }) + } case SET_TITLES: + { + const { projects } = payload return ({ ...state, - ...payload.reduce(($, project) => ({ + ...projects.reduce(($, project) => ({ ...$, [project._id]: { ...project, @@ -83,15 +91,19 @@ export default (state = ({ new: emptyEstimate }), { type, payload }) => { }, }), {}), }) + } case MARK_ESTIMATE_SAVED: + { + const { _id } = payload return ({ ...state, - [payload]: { - ...state[payload], + [_id]: { + ...state[_id], saved: true, }, }) + } default: return state diff --git a/src/redux/reducers/estimates.test.js b/src/redux/reducers/estimates.test.js index b133339..3e25a6e 100644 --- a/src/redux/reducers/estimates.test.js +++ b/src/redux/reducers/estimates.test.js @@ -1,13 +1,13 @@ /* globals describe, it, expect */ -// import { -// UPDATE_ESTIMATE, -// RECALCULATE, -// CLEAN_ESTIMATE, -// CLEAN_ALL_ESTIMATES, -// SET_TITLES, -// MARK_ESTIMATE_SAVED, -// } from '../actions/types' +import { + UPDATE_ESTIMATE, + RECALCULATE, + CLEAN_ESTIMATE, + CLEAN_ALL_ESTIMATES, + SET_TITLES, + MARK_ESTIMATE_SAVED, +} from '../actions/types' import estimates, { emptyEstimate } from './estimates' describe('estimates reducer', () => { @@ -15,5 +15,233 @@ describe('estimates reducer', () => { expect(estimates(undefined, {})).toEqual({ new: emptyEstimate }) }) - // TODO: implement tests for CLEAN_ESTIMATE + it('updates estimate', () => { + // Given + const beforeState = { new: emptyEstimate } + const action = { + type: UPDATE_ESTIMATE, + payload: { + _id: 'new', + text: ` + @project Updated estimate + @participants Test User + `, + }, + } + // When + const afterState = estimates(beforeState, action) + // Then + expect(afterState).toEqual({ + new: { + _id: 'new', + text: ` + @project Updated estimate + @participants Test User + `, + graphData: [], + project: 'Updated estimate', + participants: ['Test User'], + calculated: false, + saved: false, + }, + }) + }) + + it('recalculates estimate', () => { + // Mock + console.table = console.log + // Given + const beforeState = { + new: { + _id: 'new', + text: ` + @project Updated estimate + @participants Test User + + Main task + Subtask A = 1 2 + Subtask B = 10 20 + # Commented task = 42 + + @summary + `, + graphData: [], + project: 'Updated estimate', + participants: ['Test User'], + calculated: false, + saved: false, + }, + } + const action = { + type: RECALCULATE, + payload: { _id: 'new' }, + } + // When + const afterState = estimates(beforeState, action) + // Then + expect(afterState).toEqual({ + new: { + _id: 'new', + text: ` + @project Updated estimate + @participants Test User + + Main task = 11 12 21 22 + Subtask A = 1 2 + Subtask B = 10 20 + # Commented task = 42 + +@summary = 11 12 21 22 + `, + graphData: [[11, 25], [12, 50], [21, 75], [22, 100]], + project: 'Updated estimate', + participants: ['Test User'], + calculated: true, + saved: false, + }, + }) + }) + + it('cleans estimate', () => { + // Given + const beforeState = { + new: { + _id: 'new', + text: ` + @project Updated estimate + @participants Test User + `, + graphData: [], + project: 'Updated estimate', + participants: ['Test User'], + calculated: false, + saved: false, + }, + } + const action = { + type: CLEAN_ESTIMATE, + payload: {}, + } + // When + const afterState = estimates(beforeState, action) + // Then + expect(afterState).toEqual({ new: emptyEstimate }) + }) + + + it('cleans all estimates', () => { + // Given + const beforeState = { + new: { + _id: 'new', + text: ` + New Project + `, + graphData: [], + project: '', + participants: [], + calculated: false, + saved: false, + }, + saved_project_id: { + id: 'saved_project_id', + text: ` + @project Updated estimate + @participants Test User + + Main task = 11 12 21 22 + Subtask A = 1 2 + Subtask B = 10 20 + # Commented task = 42 + +@summary = 11 12 21 22 + `, + graphData: [[11, 25], [12, 50], [21, 75], [22, 100]], + project: 'Updated estimate', + participants: ['Test User'], + calculated: true, + saved: true, + }, + } + const action = { type: CLEAN_ALL_ESTIMATES } + // When + const afterState = estimates(beforeState, action) + // Then + expect(afterState).toEqual({ new: emptyEstimate }) + }) + + it('sets titles of projects', () => { + // Given + const beforeState = { new: emptyEstimate } + const action = { + type: SET_TITLES, + payload: { + projects: [ + { _id: 'foo' }, + { _id: 'bar' }, + ], + }, + } + // When + const afterState = estimates(beforeState, action) + // Then + expect(afterState).toEqual({ + new: emptyEstimate, + foo: { _id: 'foo', saved: true }, + bar: { _id: 'bar', saved: true }, + }) + }) + + it('marks estimate as saved', () => { + // Given + const beforeState = { + saved_estimate_id: { + _id: 'saved_estimate_id', + text: ` + @project Updated estimate + @participants Test User + + Main task = 11 12 21 22 + Subtask A = 1 2 + Subtask B = 10 20 + # Commented task = 42 + + @summary = 11 12 21 22 + `, + graphData: [[11, 25], [12, 50], [21, 75], [22, 100]], + project: 'Updated estimate', + participants: ['Test User'], + calculated: true, + saved: false, + }, + } + const action = { + type: MARK_ESTIMATE_SAVED, + payload: { _id: 'saved_estimate_id' }, + } + // When + const afterState = estimates(beforeState, action) + // Then + expect(afterState).toEqual({ + saved_estimate_id: { + _id: 'saved_estimate_id', + text: ` + @project Updated estimate + @participants Test User + + Main task = 11 12 21 22 + Subtask A = 1 2 + Subtask B = 10 20 + # Commented task = 42 + + @summary = 11 12 21 22 + `, + graphData: [[11, 25], [12, 50], [21, 75], [22, 100]], + project: 'Updated estimate', + participants: ['Test User'], + calculated: true, + saved: true, + }, + }) + }) }) From d28d14354e9bb1b99cfc0f8f12f474a6a9d482f8 Mon Sep 17 00:00:00 2001 From: tatomyr Date: Wed, 15 Aug 2018 23:52:11 +0300 Subject: [PATCH 7/7] minor changes --- src/components/Home/Home.js | 44 +++++++++++++------------------- src/components/Home/styles.css | 7 ++++- src/redux/reducers/creds.test.js | 6 ++--- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/components/Home/Home.js b/src/components/Home/Home.js index 4cc63c0..4e0a021 100644 --- a/src/components/Home/Home.js +++ b/src/components/Home/Home.js @@ -2,44 +2,36 @@ import React from 'react' import { Link } from 'react-router-dom' import { Container, - Row, - Col, Button, } from 'reactstrap' import Header from '../Header' import example from '../../helpers/example' const Home = () => ( -
+

Get started!

- - - - - - - - - - - - - - - - - +
+ + + + + + + + + +
diff --git a/src/components/Home/styles.css b/src/components/Home/styles.css index 66bd484..5982d84 100644 --- a/src/components/Home/styles.css +++ b/src/components/Home/styles.css @@ -1,4 +1,8 @@ -article pre { +.home .options button { + margin: 10px; +} + +.home article pre { max-width: 300px; margin: 0 auto; padding: 10px 30px; @@ -6,3 +10,4 @@ article pre { color: wheat; border-radius: 3px; } + diff --git a/src/redux/reducers/creds.test.js b/src/redux/reducers/creds.test.js index ddbb888..41d7ae2 100644 --- a/src/redux/reducers/creds.test.js +++ b/src/redux/reducers/creds.test.js @@ -14,7 +14,7 @@ describe('authentication', () => { const action = { type: SET_CREDS, payload: { - user: 'Test User', + username: 'Test User', dbName: 'dbName', apiKey: 'apiKey', }, @@ -24,7 +24,7 @@ describe('authentication', () => { // Then expect(afterState).toEqual({ haveBeenChecked: true, - user: 'Test User', + username: 'Test User', dbName: 'dbName', apiKey: 'apiKey', }) @@ -34,7 +34,7 @@ describe('authentication', () => { // Given const beforeState = { haveBeenChecked: false, - user: 'Test User', + username: 'Test User', dbName: 'dbName', apiKey: 'apiKey', }