Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/async/actions/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import fetch from 'isomorphic-fetch'
import 'isomorphic-fetch'

export const REQUEST_POSTS = 'REQUEST_POSTS'
export const RECEIVE_POSTS = 'RECEIVE_POSTS'
Expand Down
28 changes: 15 additions & 13 deletions examples/async/containers/App.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { selectReddit, fetchPostsIfNeeded, invalidateReddit } from '../actions'
import * as actions from '../actions'
import Picker from '../components/Picker'
import Posts from '../components/Posts'

class App extends Component {
export class App extends Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.handleRefreshClick = this.handleRefreshClick.bind(this)
}

componentDidMount() {
const { dispatch, selectedReddit } = this.props
dispatch(fetchPostsIfNeeded(selectedReddit))
componentWillMount() {
const { selectedReddit, fetchPostsIfNeeded } = this.props
fetchPostsIfNeeded(selectedReddit)
}

componentWillReceiveProps(nextProps) {
if (nextProps.selectedReddit !== this.props.selectedReddit) {
const { dispatch, selectedReddit } = nextProps
dispatch(fetchPostsIfNeeded(selectedReddit))
const { fetchPostsIfNeeded, selectedReddit } = nextProps
fetchPostsIfNeeded(selectedReddit)
}
}

handleChange(nextReddit) {
this.props.dispatch(selectReddit(nextReddit))
this.props.selectReddit(nextReddit)
}

handleRefreshClick(e) {
e.preventDefault()

const { dispatch, selectedReddit } = this.props
dispatch(invalidateReddit(selectedReddit))
dispatch(fetchPostsIfNeeded(selectedReddit))
const { invalidateReddit, fetchPostsIfNeeded, selectedReddit } = this.props
invalidateReddit(selectedReddit)
fetchPostsIfNeeded(selectedReddit)
}

render() {
Expand Down Expand Up @@ -73,7 +73,9 @@ App.propTypes = {
posts: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
lastUpdated: PropTypes.number,
dispatch: PropTypes.func.isRequired
fetchPostsIfNeeded: PropTypes.func.isRequired,
selectReddit: PropTypes.func.isRequired,
invalidateReddit: PropTypes.func.isRequired
}

function mapStateToProps(state) {
Expand All @@ -95,4 +97,4 @@ function mapStateToProps(state) {
}
}

export default connect(mapStateToProps)(App)
export default connect(mapStateToProps, actions)(App)
9 changes: 8 additions & 1 deletion examples/async/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"version": "0.0.0",
"description": "Redux async example",
"scripts": {
"start": "node server.js"
"start": "node server.js",
"test": "cross-env NODE_ENV=test mocha --recursive --compilers js:babel-register",
"test:watch": "npm test -- --watch"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -41,9 +43,14 @@
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-react-hmre": "^1.1.1",
"cross-env": "^1.0.7",
"enzyme": "^2.0.0",
"expect": "^1.6.0",
"express": "^4.13.3",
"fetch-mock": "^4.1.1",
"mocha": "^2.2.5",
"node-libs-browser": "^0.5.2",
"react-addons-test-utils": "^0.14.7",
"webpack": "^1.9.11",
"webpack-dev-middleware": "^1.2.0",
"webpack-hot-middleware": "^2.9.1"
Expand Down
5 changes: 5 additions & 0 deletions examples/async/test/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"env": {
"mocha": true
}
}
167 changes: 167 additions & 0 deletions examples/async/test/actions/actions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import expect from 'expect'
import fetchMock from 'fetch-mock'
import { fetchPostsIfNeeded } from '../../actions'

function setup(reddit = 'reactjs') {
const dispatch = expect.createSpy()
const getState = expect.createSpy()

const actionCreator = fetchPostsIfNeeded(reddit)

const response = {
body: JSON.stringify({
data: {
children: [
{ data: 'post 1' },
{ data: 'post 2' }
]
}
})
}

fetchMock.mock(`https://www.reddit.com/r/${reddit}.json`, response)

return {
actionCreator: actionCreator,
dispatch: dispatch,
getState: getState
}
}

function callThroughWithDispatch(dispatch) {
return dispatch.calls[0].arguments[0](dispatch)
}

describe('fetchPostsIfNeeded action', () => {
afterEach(() => {
fetchMock.restore()
})

it('should dispatch REQUEST_POSTS action', () => {
const { actionCreator, dispatch, getState } = setup()

getState.andReturn({
postsByReddit: {}
})

actionCreator(dispatch, getState)

callThroughWithDispatch(dispatch)

expect(dispatch).toHaveBeenCalledWith({
type: 'REQUEST_POSTS',
reddit: 'reactjs'
})
})

it('should dispatch RECEIVE_POSTS action', () => {
const { actionCreator, dispatch, getState } = setup()

getState.andReturn({
postsByReddit: {}
})

actionCreator(dispatch, getState)

return callThroughWithDispatch(dispatch)
.then(() => {
const { type, reddit, posts } = dispatch.calls[2].arguments[0]

const action = { type, reddit, posts }

expect(action).toEqual({
type: 'RECEIVE_POSTS',
reddit: 'reactjs',
posts: [ 'post 1', 'post 2' ]
})
})
})

describe('when fetching', () => {
it('should not dispatch REQUEST_POSTS action', () => {
const { actionCreator, dispatch, getState } = setup()

getState.andReturn({
postsByReddit: {
reactjs: {
isFetching: true
}
}
})

actionCreator(dispatch, getState)

expect(dispatch).toNotHaveBeenCalled()
})
})

describe('when existing posts', () => {
it('should not dispatch REQUEST_POSTS action', () => {
const { actionCreator, dispatch, getState } = setup()

getState.andReturn({
postsByReddit: {
reactjs: {
posts: [ 'post 3', 'post 4' ]
}
}
})

actionCreator(dispatch, getState)

expect(dispatch).toNotHaveBeenCalled()
})

describe('when invalidated', () => {
it('should dispatch REQUEST_POSTS action', () => {
const { actionCreator, dispatch, getState } = setup()

getState.andReturn({
postsByReddit: {
reactjs: {
posts: [ 'post 3', 'post 4' ],
didInvalidate: true
}
}
})

actionCreator(dispatch, getState)

callThroughWithDispatch(dispatch)

expect(dispatch).toHaveBeenCalledWith({
type: 'REQUEST_POSTS',
reddit: 'reactjs'
})
})

it('should dispatch RECEIVE_POSTS action', () => {
const { actionCreator, dispatch, getState } = setup()

getState.andReturn({
postsByReddit: {
reactjs: {
posts: [ 'post 3', 'post 4' ],
didInvalidate: true
}
}
})

actionCreator(dispatch, getState)

return callThroughWithDispatch(dispatch)
.then(() => {
const { type, reddit, posts } = dispatch.calls[2].arguments[0]

const action = { type, reddit, posts }

expect(action).toEqual({
type: 'RECEIVE_POSTS',
reddit: 'reactjs',
posts: [ 'post 1', 'post 2' ]
})
})
})
})
})
})
123 changes: 123 additions & 0 deletions examples/async/test/components/App.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import expect from 'expect'
import React from 'react'
import { shallow } from 'enzyme'
import { App } from '../../containers/App'
import Picker from '../../components/Picker'
import Posts from '../../components/Posts'

function setup(selectedReddit, isFetching = false, posts = []) {
const actions = {
fetchPostsIfNeeded: expect.createSpy(),
selectReddit: expect.createSpy(),
invalidateReddit: expect.createSpy()
}

const eventArgs = {
preventDefault: expect.createSpy()
}

const component = shallow(
<App
selectedReddit={selectedReddit}
posts={posts}
isFetching={isFetching}
lastUpdated={ new Date().getTime() }
{...actions} />
)

return {
component: component,
actions: actions,
picker: component.find(Picker),
p: component.find('p'),
refreshLink: component.findWhere(n => n.type() === 'a' && n.contains('Refresh')),
status: component.find('h2'),
postList: component.find(Posts),
postListWrap: component.find('div').last(),
eventArgs: eventArgs
}
}

describe('App component', () => {
it('should render Picker component', () => {
const { picker } = setup('reactjs')
expect(picker.length).toEqual(1)
})

it('should render last updated', () => {
const { p } = setup('reactjs')
expect(p.text()).toMatch(/Last updated at /)
})

it('should render refresh link', () => {
const { refreshLink } = setup('reactjs')
expect(refreshLink.length).toEqual(1)
})

it('should render empty status', () => {
const { status } = setup('reactjs')
expect(status.text()).toEqual('Empty.')
})

describe('when fetching', () => {
it('should not render refresh link', () => {
const { refreshLink } = setup('reactjs', true)
expect(refreshLink.length).toEqual(0)
})

it('should render loading status', () => {
const { status } = setup('reactjs', true)
expect(status.text()).toEqual('Loading...')
})
})

describe('when given posts', () => {
const posts = [
{ title: 'Post 1' },
{ title: 'Post 2' }
]

it('should render Posts component', () => {
const { postList } = setup('reactjs', false, posts)
expect(postList.prop('posts')).toEqual(posts)
})

describe('when fetching', () => {
it('should render Posts at half opacity', () => {
const { postListWrap } = setup('reactjs', true, posts)
expect(postListWrap.prop('style')).toEqual({ opacity: '0.50' })
})
})
})

it('should call selectReddit action on Picker change', () => {
const { picker, actions } = setup('reactjs')
picker.simulate('change')
expect(actions.selectReddit).toHaveBeenCalled()
})

it('should call invalidateReddit action on refresh click', () => {
const { refreshLink, actions, eventArgs } = setup('reactjs')
refreshLink.simulate('click', eventArgs)
expect(actions.invalidateReddit).toHaveBeenCalled()
})

it('should call fetchPostsIfNeeded action on mount', () => {
const { actions } = setup('reactjs')
expect(actions.fetchPostsIfNeeded).toHaveBeenCalled()
})

it('should call fetchPostsIfNeeded action on refresh click', () => {
const { refreshLink, actions, eventArgs } = setup('reactjs')
refreshLink.simulate('click', eventArgs)

expect(actions.fetchPostsIfNeeded.calls.length).toEqual(2)
})

it('should call fetchPostsIfNeeded action when given new selectedReddit', () => {
const { component, actions } = setup('reactjs')
component.setProps(Object.assign({}, actions, { selectedReddit: 'frontend', posts: [], isFetching: false, lastUpdated: new Date().getTime() }))

expect(actions.fetchPostsIfNeeded.calls.length).toEqual(2)
})
})
Loading