Skip to content
Open
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
123 changes: 123 additions & 0 deletions src/helpers/resource/components/ResourceRequester.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {
resourceCreateRequest,
// resourceUpdateRequest,
// resourceDeleteRequest,
} from '../../../modules/resource/actions'

class ResourceRequester extends React.Component {
state = {
requestResult: null,
error: null,
loading: false,
}

getRequesResultValuesObj() {
const {requestResult} = this.state
const entities = requestResult && requestResult.entities
const result = requestResult && requestResult.data

return {entities, requestResult, result}
}

getStatusObj() {
const error = !!this.state.error
const loading = this.state.loading
const success = !!this.state.requestResult && !loading
const initial = !error && !loading && !success

return {error, initial, loading, success}
}

createResource = data => {
this.setState({loading: true})
this.requestResourceCreate(data).then(
this.requestResourceSuccess,
this.requestResourceError,
)
}

requestResourceError = error => {
this.setState({loading: false, error})
}

requestResourceSuccess = payload => {
const requestResult = {
api: payload.api,
data: payload.data,
entities: payload.entities,
entityType: payload.entityType,
resource: payload.resource,
}

this.setState({
requestResult,
error: null,
loading: false,
})
}

requestResourceCreate = data => {
const {requestCreate, resource, entityType} = this.props
return requestCreate(resource, data, entityType)
}

// requestResourceDelete = data => {
// const {requestDelete, resource, entityType} = this.props
// return requestDelete(resource, data, entityType)
// }

// requestResourceUpdate = data => {
// const {requestUpdate, resource, entityType} = this.props
// return requestUpdate(resource, data, entityType)
// }

resetState = () => {
this.setState({
requestResult: null,
error: null,
loading: false,
})
}

render() {
if (typeof this.props.children !== 'function') {
throw new Error('Children should be a Function!')
}

const {error} = this.state
const {entityType} = this.props
const {result, ...requestResultValues} = this.getRequesResultValuesObj()

return this.props.children({
createResource: this.createResource,
status: this.getStatusObj(),
error,
entityType,
result,
...requestResultValues,
})
}
}

ResourceRequester.propTypes = {
children: PropTypes.func.isRequired,
entityType: PropTypes.string,
// list: PropTypes.bool.isRequired,
requestCreate: PropTypes.func,
// requestDelete: PropTypes.func,
// requestUpdate: PropTypes.func,
// requestParams: PropTypes.object,
resource: PropTypes.string.isRequired,
// resourceId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}

const mapDispatchToProps = {
requestCreate: resourceCreateRequest,
// requestDelete: resourceDeleteRequest,
// requestUpdate: resourceUpdateRequest,
}

export default connect(null, mapDispatchToProps)(ResourceRequester)
156 changes: 156 additions & 0 deletions src/helpers/resource/components/__tests__/ResourceRequester.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import React from 'react'
import MockAdapter from 'axios-mock-adapter'
import createSagaMiddleware from 'redux-saga'
import {Provider} from 'react-redux'
import {createStore, applyMiddleware, combineReducers} from 'redux'
import {render, Simulate, wait} from 'react-testing-library'
import {schema} from 'normalizr'
import ReduxThunk from 'redux-thunk'
// eslint-disable-next-line
import 'dom-testing-library/extend-expect'
import {middleware as ReduxSagaThunk} from 'redux-saga-thunk'
import ResourceRequester from '../ResourceRequester'
import {createResource, createEntities} from '../../../../'
import API from '../../../../utils/test/api'

const {api, axiosInstance} = API.create()
const mockApi = new MockAdapter(axiosInstance, {delayResponse: 300})

function createStoreForTests(initialState) {
const resource = createResource()
const entities = createEntities({
isDevEnv: false,
schemas: {
user: new schema.Entity('user', {}, {idAttribute: 'uuid'}),
team: new schema.Entity('team', {idAttribute: 'id'}),
},
})
const reducer = combineReducers({
entities: entities.reducer,
resource: resource.reducer,
})
const sagas = resource.sagas
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
initialState,
applyMiddleware(ReduxSagaThunk, ReduxThunk, sagaMiddleware),
)
sagaMiddleware.run(sagas, {api})
return store
}

function renderWithRedux(
ui,
{initialState, store = createStoreForTests(initialState)} = {},
) {
return {
...render(<Provider store={store}>{ui}</Provider>),
// adding `store` to the returned utilities to allow us
// to reference it in our tests (just try to avoid using
// this to test implementation details).
store,
}
}

// eslint-disable-next-line react/prop-types
const Status = ({children, initial, loading, error, success}) => {
if (initial) return <div data-testid="render-initial">Initial</div>
if (loading) return <div data-testid="render-loading">Loading</div>
if (error) return <div data-testid="render-error">{children}</div>
if (success) return <div data-testid="render-success">{children}</div>
throw new Error('Status component not passed status prop')
}

test('ResourceRequester component receives props and renders initial status', () => {
// Renders ResourceRequester component with statusView from renderInitial prop.
const {getByTestId} = renderWithRedux(
<ResourceRequester resource={'example'}>
{({status}) => <div>{status.initial && <Status initial />}</div>}
</ResourceRequester>,
)

// Expects ResourceRequester component to render statusView from renderInitial.
expect(getByTestId('render-initial')).toHaveTextContent('Initial')
})

test('ResourceRequester POSTs when createResource called and renders results', async () => {
const newUserData = {id: 1, name: 'Ben'}
mockApi.onPost('/user').reply(201, {...newUserData})

// Renders ResourceRequester component with statusView from renderInitial prop.
const {getByTestId, getByText} = renderWithRedux(
<ResourceRequester resource={'user'}>
{({status, createResource: createUser, result: user}) => (
<div>
<div>
<button
onClick={e => {
e.preventDefault()
createUser({...newUserData})
}}
data-testid="create-user"
>
Create User
</button>
</div>
{status.initial && <Status initial />}
{status.success && (
<Status success>
<div key={user.id}>
{user.id}:{user.name}
</div>
</Status>
)}
</div>
)}
</ResourceRequester>,
)

// Expects ResourceRequester component to render statusView from renderInitial.
expect(getByTestId('render-initial')).toHaveTextContent('Initial')

// Simulating a button click in the browser
Simulate.click(getByText('Create User'))

// Expects ResourceRequester component to render statusView from renderLoading.
await wait(() => expect(getByTestId('render-success')).toBeInTheDOM())
})

// test('ResourceRequester loads with params passed in', async () => {
// mockApi
// .onGet('/dude', {params: {best: 'dude'}})
// .reply(200, [{id: 1, name: 'Ben'}, {id: 2, name: 'Sam'}])

// // Renders ResourceRequester component with statusView from renderInitial prop.
// const {getByTestId} = renderWithRedux(
// <ResourceRequester
// resource={'dude'}
// renderInitial={() => <Status initial />}
// renderError={error => <Status error>{error}</Status>}
// renderLoading={() => <Status loading />}
// renderSuccess={userList => (
// <Status success>
// {userList.map(user => (
// <div key={user.id}>
// {user.id}:{user.name}
// </div>
// ))}
// </Status>
// )}
// requestParams={{best: 'dude'}}
// loadOnMount
// list
// >
// {({statusView}) => <div>{statusView}</div>}
// </ResourceRequester>,
// )

// // Expects ResourceRequester component to render statusView from renderLoading.
// expect(getByTestId('render-loading')).toHaveTextContent('Loading')

// // expect(api.get).toHaveBeenCalled()
// // console.log(mockApi.history.get)
// // // Expects ResourceRequester component to render statusView from renderSuccess.
// await wait(() => expect(getByTestId('render-success')).toBeInTheDOM())
// })