Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Jake Gibbons
committed
Feb 27, 2017
1 parent
5f12aca
commit ba605d9
Showing
14 changed files
with
434 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import React from 'react' | ||
|
||
export const Conter = (props) => ( | ||
<div style={{ margin: '0 auto' }} > | ||
<h2>Conter: {props.conter}</h2> | ||
<button className='btn btn-default' onClick={props.reset}> | ||
Reset | ||
</button> | ||
{' '} | ||
<button className='btn btn-default' onClick={props.increment}> | ||
Increment | ||
</button> | ||
{' '} | ||
<button className='btn btn-default' onClick={props.doubleAsync}> | ||
Double (Async) | ||
</button> | ||
{' '} | ||
<button className='btn btn-default' onClick={props.exponentAsync}> | ||
Exponent (Async) | ||
</button> | ||
</div> | ||
) | ||
|
||
Conter.propTypes = { | ||
conter: React.PropTypes.number.isRequired, | ||
exponentAsync: React.PropTypes.func.isRequired, | ||
doubleAsync: React.PropTypes.func.isRequired, | ||
reset: React.PropTypes.func.isRequired, | ||
increment: React.PropTypes.func.isRequired, | ||
} | ||
|
||
export default Conter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { connect } from 'react-redux' | ||
import { | ||
increment, | ||
reset, | ||
doubleAsync, | ||
exponentAsync, | ||
} from '../modules/conter' | ||
|
||
/* This is a container component. Notice it does not contain any JSX, | ||
nor does it import React. This component is **only** responsible for | ||
wiring in the actions and state necessary to render a presentational | ||
component - in this case, the conter: */ | ||
|
||
import Conter from '../components/Conter' | ||
|
||
/* Object of action creators (can also be function that returns object). | ||
Keys will be passed as props to presentational components. Here we are | ||
implementing our wrapper around increment; the component doesn't care */ | ||
|
||
const mapDispatchToProps = { | ||
increment : () => increment(1), | ||
reset, | ||
doubleAsync, | ||
exponentAsync, | ||
} | ||
|
||
const mapStateToProps = (state) => ({ | ||
conter : state.conter | ||
}) | ||
|
||
/* Note: mapStateToProps is where you should use `reselect` to create selectors, ie: | ||
import { createSelector } from 'reselect' | ||
const conter = (state) => state.conter | ||
const tripleCount = createSelector(conter, (count) => count * 3) | ||
const mapStateToProps = (state) => ({ | ||
conter: tripleCount(state) | ||
}) | ||
Selectors can compute derived data, allowing Redux to store the minimal possible state. | ||
Selectors are efficient. A selector is not recomputed unless one of its arguments change. | ||
Selectors are composable. They can be used as input to other selectors. | ||
https://github.com/reactjs/reselect */ | ||
|
||
export default connect(mapStateToProps, mapDispatchToProps)(Conter) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { injectReducer } from '../../store/reducers' | ||
|
||
export default (store) => ({ | ||
path : 'conter', | ||
/* Async getComponent is only invoked when route matches */ | ||
getComponent (nextState, cb) { | ||
/* Webpack - use 'require.ensure' to create a split point | ||
and embed an async module loader (jsonp) when bundling */ | ||
require.ensure([], (require) => { | ||
/* Webpack - use require callback to define | ||
dependencies for bundling */ | ||
const Conter = require('./containers/ConterContainer').default | ||
const reducer = require('./modules/conter').default | ||
|
||
/* Add the reducer to the store on key 'conter' */ | ||
injectReducer(store, { key: 'conter', reducer }) | ||
|
||
/* Return getComponent */ | ||
cb(null, Conter) | ||
|
||
/* Webpack named bundle */ | ||
}, 'conter') | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// ------------------------------------ | ||
// Constants | ||
// ------------------------------------ | ||
export const CONTER_INCREMENT = 'CONTER_INCREMENT' | ||
export const CONTER_RESET = 'CONTER_RESET' | ||
export const CONTER_DOUBLE_ASYNC = 'CONTER_DOUBLE_ASYNC' | ||
export const CONTER_EXPONENT_ASYNC = 'CONTER_EXPONENT_ASYNC' | ||
|
||
// ------------------------------------ | ||
// Actions | ||
// ------------------------------------ | ||
export function increment (value = 1) { | ||
return { | ||
type : CONTER_INCREMENT, | ||
payload : value | ||
} | ||
} | ||
|
||
export function reset () { | ||
return { type: CONTER_RESET } | ||
} | ||
|
||
/* This is a thunk, meaning it is a function that immediately | ||
returns a function for lazy evaluation. It is incredibly useful for | ||
creating async actions, especially when combined with redux-thunk! */ | ||
|
||
export const doubleAsync = () => { | ||
return (dispatch, getState) => { | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
dispatch({ | ||
type : CONTER_DOUBLE_ASYNC, | ||
payload : getState().conter | ||
}) | ||
resolve() | ||
}, 333) | ||
}) | ||
} | ||
} | ||
|
||
export const exponentAsync = () => { | ||
return (dispatch, getState) => { | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
dispatch({ | ||
type : CONTER_EXPONENT_ASYNC, | ||
payload : getState().conter | ||
}) | ||
resolve() | ||
}, 666) | ||
}) | ||
} | ||
} | ||
|
||
export const actions = { | ||
increment, | ||
reset, | ||
doubleAsync, | ||
exponentAsync, | ||
} | ||
|
||
// ------------------------------------ | ||
// Action Handlers | ||
// ------------------------------------ | ||
const ACTION_HANDLERS = { | ||
[CONTER_INCREMENT]: (state, action) => state + action.payload, | ||
[CONTER_RESET]: () => 0, | ||
[CONTER_DOUBLE_ASYNC]: (state, action) => state * 2, | ||
[CONTER_EXPONENT_ASYNC]: (state, action) => state * state, | ||
} | ||
|
||
// ------------------------------------ | ||
// Reducer | ||
// ------------------------------------ | ||
const initialState = 0 | ||
export default function conterReducer (state = initialState, action) { | ||
const handler = ACTION_HANDLERS[action.type] | ||
|
||
return handler ? handler(state, action) : state | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import React from 'react' | ||
import { bindActionCreators } from 'redux' | ||
import { Conter } from 'routes/Conter/components/Conter' | ||
import { shallow } from 'enzyme' | ||
|
||
describe('(Component) Conter', () => { | ||
let _props, _spies, _wrapper | ||
|
||
beforeEach(() => { | ||
_spies = {} | ||
_props = { | ||
conter : 5, | ||
...bindActionCreators({ | ||
doubleAsync : (_spies.doubleAsync = sinon.spy()), | ||
increment : (_spies.increment = sinon.spy()) | ||
}, _spies.dispatch = sinon.spy()) | ||
} | ||
_wrapper = shallow(<Conter {..._props} />) | ||
}) | ||
|
||
it('Should render as a <div>.', () => { | ||
expect(_wrapper.is('div')).to.equal(true) | ||
}) | ||
|
||
it('Should render with an <h2> that includes Sample Conter text.', () => { | ||
expect(_wrapper.find('h2').text()).to.match(/Conter:/) | ||
}) | ||
|
||
it('Should render props.conter at the end of the sample conter <h2>.', () => { | ||
expect(_wrapper.find('h2').text()).to.match(/5$/) | ||
_wrapper.setProps({ conter: 8 }) | ||
expect(_wrapper.find('h2').text()).to.match(/8$/) | ||
}) | ||
|
||
it('Should render exactly two buttons.', () => { | ||
expect(_wrapper.find('button')).to.have.length(2) | ||
}) | ||
|
||
describe('An increment button...', () => { | ||
let _button | ||
|
||
beforeEach(() => { | ||
_button = _wrapper.find('button').filterWhere(a => a.text() === 'Increment') | ||
}) | ||
|
||
it('has bootstrap classes', () => { | ||
expect(_button.hasClass('btn btn-default')).to.be.true | ||
}) | ||
|
||
it('Should dispatch a `increment` action when clicked', () => { | ||
_spies.dispatch.should.have.not.been.called | ||
|
||
_button.simulate('click') | ||
|
||
_spies.dispatch.should.have.been.called | ||
_spies.increment.should.have.been.called | ||
}) | ||
}) | ||
|
||
describe('A Double (Async) button...', () => { | ||
let _button | ||
|
||
beforeEach(() => { | ||
_button = _wrapper.find('button').filterWhere(a => a.text() === 'Double (Async)') | ||
}) | ||
|
||
it('has bootstrap classes', () => { | ||
expect(_button.hasClass('btn btn-default')).to.be.true | ||
}) | ||
|
||
it('Should dispatch a `doubleAsync` action when clicked', () => { | ||
_spies.dispatch.should.have.not.been.called | ||
|
||
_button.simulate('click') | ||
|
||
_spies.dispatch.should.have.been.called | ||
_spies.doubleAsync.should.have.been.called | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import ConterRoute from 'routes/Conter' | ||
|
||
describe('(Route) Conter', () => { | ||
let _route | ||
|
||
beforeEach(() => { | ||
_route = ConterRoute({}) | ||
}) | ||
|
||
it('Should return a route configuration object', () => { | ||
expect(typeof _route).to.equal('object') | ||
}) | ||
|
||
it('Configuration should contain path `conter`', () => { | ||
expect(_route.path).to.equal('conter') | ||
}) | ||
}) |
Oops, something went wrong.