From f78eb4405b0ebb945e50d90f7c44294298d97739 Mon Sep 17 00:00:00 2001 From: Tamlyn Rhodes <tamlyn@tamlyn.org> Date: Mon, 27 Nov 2017 22:42:17 +0000 Subject: [PATCH] Convert to GraphQL with Apollo Client Remove redundant actions and reducers Add withLoader helper to handle component loading state --- config/default.js | 5 - config/test.js | 5 +- package.json | 5 + src/actions/collections.js | 244 ---------------------- src/actions/currentUser.js | 33 --- src/actions/fileUpload.js | 48 ----- src/actions/fragments.js | 211 ------------------- src/actions/index.js | 36 +--- src/actions/teams.js | 159 -------------- src/actions/users.js | 112 ---------- src/components/AuthenticatedComponent.jsx | 49 ++--- src/components/Root.js | 28 ++- src/components/UpdateSubscriber.jsx | 2 +- src/helpers/Authorize.jsx | 27 +-- src/helpers/Utils.js | 5 - src/helpers/api.js | 14 -- src/helpers/endpoint.js | 3 - src/helpers/token.js | 7 - src/helpers/withAuthsome.js | 63 +++--- src/helpers/withLoader.js | 21 ++ src/index.js | 1 - src/reducers/collections.js | 130 ------------ src/reducers/currentUser.js | 48 ----- src/reducers/error.js | 9 - src/reducers/fileUpload.js | 24 --- src/reducers/fragments.js | 80 ------- src/reducers/index.js | 17 -- src/reducers/teams.js | 44 ---- src/reducers/users.js | 72 ------- src/store/configureStore.js | 14 -- src/validations.js | 4 +- test/actions/collections.test.js | 120 ----------- test/actions/currentUser.test.js | 20 -- test/actions/fileUpload.test.js | 32 --- test/actions/fragments.test.js | 105 ---------- test/actions/index.test.js | 14 -- test/actions/teams.test.js | 85 -------- test/actions/users.test.js | 54 ----- test/helpers/api.test.js | 118 ----------- test/helpers/describeAction.js | 128 ------------ test/reducers/collections.test.js | 116 ---------- test/reducers/currentUser.test.js | 73 ------- test/reducers/error.test.js | 22 -- test/reducers/fileUpload.test.js | 36 ---- test/reducers/fragments.test.js | 97 --------- test/reducers/teams.test.js | 70 ------- test/reducers/users.test.js | 109 ---------- yarn.lock | 122 ++++++++++- 48 files changed, 248 insertions(+), 2593 deletions(-) delete mode 100644 config/default.js delete mode 100644 src/actions/collections.js delete mode 100644 src/actions/currentUser.js delete mode 100644 src/actions/fileUpload.js delete mode 100644 src/actions/fragments.js delete mode 100644 src/actions/teams.js delete mode 100644 src/actions/users.js delete mode 100644 src/helpers/Utils.js delete mode 100644 src/helpers/endpoint.js delete mode 100644 src/helpers/token.js create mode 100644 src/helpers/withLoader.js delete mode 100644 src/reducers/collections.js delete mode 100644 src/reducers/currentUser.js delete mode 100644 src/reducers/error.js delete mode 100644 src/reducers/fileUpload.js delete mode 100644 src/reducers/fragments.js delete mode 100644 src/reducers/index.js delete mode 100644 src/reducers/teams.js delete mode 100644 src/reducers/users.js delete mode 100644 test/actions/collections.test.js delete mode 100644 test/actions/currentUser.test.js delete mode 100644 test/actions/fileUpload.test.js delete mode 100644 test/actions/fragments.test.js delete mode 100644 test/actions/index.test.js delete mode 100644 test/actions/teams.test.js delete mode 100644 test/actions/users.test.js delete mode 100644 test/helpers/api.test.js delete mode 100644 test/helpers/describeAction.js delete mode 100644 test/reducers/collections.test.js delete mode 100644 test/reducers/currentUser.test.js delete mode 100644 test/reducers/error.test.js delete mode 100644 test/reducers/fileUpload.test.js delete mode 100644 test/reducers/fragments.test.js delete mode 100644 test/reducers/teams.test.js delete mode 100644 test/reducers/users.test.js diff --git a/config/default.js b/config/default.js deleted file mode 100644 index 3e1cd0b..0000000 --- a/config/default.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - 'pubsweet-client': { - API_ENDPOINT: 'http://localhost:3000/api', - }, -} diff --git a/config/test.js b/config/test.js index bf905ac..b25a093 100644 --- a/config/test.js +++ b/config/test.js @@ -1,11 +1,12 @@ module.exports = { 'pubsweet-client': { - API_ENDPOINT: 'http://example.com', + // this should be a relative URL but tests use node-fetch which requires absolute URL + API_ENDPOINT: '/graphql', 'update-subscriber': { visible: true, }, }, authsome: { - mode: 'fake-mode' + mode: 'fake-mode', }, } diff --git a/package.json b/package.json index bdcff00..fa3b17c 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,15 @@ "author": "Collaborative Knowledge Foundation", "license": "MIT", "dependencies": { + "apollo-client-preset": "^1.0.3", + "apollo-link": "^1.0.3", "authsome": "0.0.9", "config": "^1.21.0", "eslint-config-prettier": "^2.6.0", "event-source-polyfill": "^0.0.10", "global": "^4.3.1", + "graphql": "^0.11.7", + "graphql-tag": "^2.5.0", "husky": "^0.14.3", "isomorphic-fetch": "^2.1.1", "lint-staged": "^4.2.3", @@ -29,6 +33,7 @@ "prop-types": "^15.5.8", "pubsweet-component-login": "^0.5.2", "react": "^15.4.4", + "react-apollo": "^2.0.1", "react-css-themr": "^2.1.2", "react-redux": "^5.0.2", "react-router-dom": "^4.2.2", diff --git a/src/actions/collections.js b/src/actions/collections.js deleted file mode 100644 index 2a06e40..0000000 --- a/src/actions/collections.js +++ /dev/null @@ -1,244 +0,0 @@ -import * as api from '../helpers/api' -import * as T from './types' - -const collectionUrl = (collection, suffix) => { - let url = '/collections' - - if (collection) url += `/${collection.id}` - - if (suffix) url += `/${suffix}` - - return url -} - -function getCollectionsRequest() { - return { - type: T.GET_COLLECTIONS_REQUEST, - } -} - -function getCollectionsFailure(error) { - return { - type: T.GET_COLLECTIONS_FAILURE, - error: error, - } -} - -function getCollectionsSuccess(collections) { - return { - type: T.GET_COLLECTIONS_SUCCESS, - collections: collections, - receivedAt: Date.now(), - } -} - -export function getCollections(options) { - return dispatch => { - dispatch(getCollectionsRequest()) - - let url = collectionUrl() - - if (options && options.fields) { - url += '?fields=' + encodeURIComponent(options.fields.join(',')) - } - - return api - .get(url) - .then( - collections => dispatch(getCollectionsSuccess(collections)), - err => dispatch(getCollectionsFailure(err)), - ) - } -} - -function getCollectionTeamsRequest() { - return { - type: T.GET_COLLECTION_TEAMS_REQUEST, - } -} - -function getCollectionTeamsFailure(error) { - return { - type: T.GET_COLLECTION_TEAMS_FAILURE, - error: error, - } -} - -function getCollectionTeamsSuccess(teams) { - return { - type: T.GET_COLLECTION_TEAMS_SUCCESS, - teams, - receivedAt: Date.now(), - } -} - -export function getCollectionTeams(collection) { - return dispatch => { - dispatch(getCollectionTeamsRequest()) - - let url = collectionUrl(collection, 'teams') - - return api - .get(url) - .then( - teams => dispatch(getCollectionTeamsSuccess(teams)), - err => dispatch(getCollectionTeamsFailure(err)), - ) - } -} - -function createCollectionRequest(collection) { - return { - type: T.CREATE_COLLECTION_REQUEST, - collection: collection, - } -} - -function createCollectionSuccess(collection) { - return { - type: T.CREATE_COLLECTION_SUCCESS, - collection: collection, - } -} - -function createCollectionFailure(collection, error) { - return { - type: T.CREATE_COLLECTION_FAILURE, - isFetching: false, - collection: collection, - error: error, - } -} - -export function createCollection(collection) { - return dispatch => { - dispatch(createCollectionRequest(collection)) - - const url = collectionUrl() - - return api - .create(url, collection) - .then( - collection => dispatch(createCollectionSuccess(collection)), - err => dispatch(createCollectionFailure(collection, err)), - ) - } -} - -function getCollectionRequest(collection) { - return { - type: T.GET_COLLECTION_REQUEST, - collection: collection, - } -} - -function getCollectionSuccess(collection) { - return { - type: T.GET_COLLECTION_SUCCESS, - collection: collection, - receivedAt: Date.now(), - } -} - -function getCollectionFailure(collection, error) { - return { - type: T.GET_COLLECTION_FAILURE, - isFetching: false, - collection: collection, - error: error, - } -} - -export function getCollection(collection) { - return dispatch => { - dispatch(getCollectionRequest(collection)) - - const url = collectionUrl(collection) - - return api - .get(url) - .then( - collection => dispatch(getCollectionSuccess(collection)), - err => dispatch(getCollectionFailure(collection, err)), - ) - } -} - -function updateCollectionRequest(collection) { - return { - type: T.UPDATE_COLLECTION_REQUEST, - collection: collection, - } -} - -function updateCollectionSuccess(collection, update) { - return { - type: T.UPDATE_COLLECTION_SUCCESS, - collection: collection, - update: update, - receivedAt: Date.now(), - } -} - -function updateCollectionFailure(collection, error) { - return { - type: T.UPDATE_COLLECTION_FAILURE, - isFetching: false, - collection: collection, - error: error, - } -} - -export function updateCollection(collection) { - return dispatch => { - dispatch(updateCollectionRequest(collection)) - - const url = collectionUrl(collection) - - return api - .update(url, collection) - .then( - update => dispatch(updateCollectionSuccess(collection, update)), - err => dispatch(updateCollectionFailure(collection, err)), - ) - } -} - -function deleteCollectionRequest(collection) { - return { - type: T.DELETE_COLLECTION_REQUEST, - collection: collection, - update: { deleted: true }, - } -} - -function deleteCollectionSuccess(collection) { - return { - type: T.DELETE_COLLECTION_SUCCESS, - collection: collection, - } -} - -function deleteCollectionFailure(collection, error) { - return { - type: T.DELETE_COLLECTION_FAILURE, - collection: collection, - update: { deleted: undefined }, - error: error, - } -} - -export function deleteCollection(collection) { - return dispatch => { - dispatch(deleteCollectionRequest(collection)) - - const url = collectionUrl(collection) - - return api - .remove(url) - .then( - () => dispatch(deleteCollectionSuccess(collection)), - err => dispatch(deleteCollectionFailure(collection, err)), - ) - } -} diff --git a/src/actions/currentUser.js b/src/actions/currentUser.js deleted file mode 100644 index a3a08b0..0000000 --- a/src/actions/currentUser.js +++ /dev/null @@ -1,33 +0,0 @@ -import * as api from '../helpers/api' -import * as T from './types' - -function getCurrentUserRequest() { - return { - type: T.GET_CURRENT_USER_REQUEST, - } -} - -function getCurrentUserSuccess(user) { - return { - type: T.GET_CURRENT_USER_SUCCESS, - user, - } -} - -function getCurrentUserFailure(error) { - return { - type: T.GET_CURRENT_USER_FAILURE, - error, - } -} - -export function getCurrentUser() { - return dispatch => { - dispatch(getCurrentUserRequest()) - - return api - .get('/users/authenticate') - .then(user => dispatch(getCurrentUserSuccess(user))) - .catch(err => dispatch(getCurrentUserFailure(err))) - } -} diff --git a/src/actions/fileUpload.js b/src/actions/fileUpload.js deleted file mode 100644 index ee67814..0000000 --- a/src/actions/fileUpload.js +++ /dev/null @@ -1,48 +0,0 @@ -import request from '../helpers/api' -import * as T from './types' - -function fileUploadRequest() { - return { - type: T.FILE_UPLOAD_REQUEST, - isFetching: true, - } -} - -function fileUploadSuccess(file) { - return { - type: T.FILE_UPLOAD_SUCCESS, - isFetching: false, - file: file, - } -} - -function fileUploadFailure(message) { - return { - type: T.FILE_UPLOAD_FAILURE, - isFetching: false, - error: message, - } -} - -export function fileUpload(file) { - return dispatch => { - dispatch(fileUploadRequest()) - - const data = new FormData() - data.append('file', file) - - let opts = { - method: 'POST', - headers: { - Accept: 'text/plain', // the response is a URL - // TODO: set the Location header of the response instead - }, - body: data, - } - - return request('/upload', opts).then( - file => dispatch(fileUploadSuccess(file)), - err => dispatch(fileUploadFailure(err)), - ) - } -} diff --git a/src/actions/fragments.js b/src/actions/fragments.js deleted file mode 100644 index 0f9197b..0000000 --- a/src/actions/fragments.js +++ /dev/null @@ -1,211 +0,0 @@ -import * as api from '../helpers/api' -import * as T from './types' - -export const fragmentUrl = (collection, fragment) => { - let url = '' - if (collection) url += `/collections/${collection.id}` - url += '/fragments' - if (fragment && fragment.id) url += `/${fragment.id}` - - return url -} - -function getFragmentsRequest(collection) { - return { - type: T.GET_FRAGMENTS_REQUEST, - collection: collection, - } -} - -function getFragmentsSuccess(collection, fragments) { - return { - type: T.GET_FRAGMENTS_SUCCESS, - collection: collection, - fragments: fragments, - receivedAt: Date.now(), - } -} - -function getFragmentsFailure(error) { - return { - type: T.GET_FRAGMENTS_FAILURE, - error: error, - } -} - -export function getFragments(collection, options) { - return dispatch => { - dispatch(getFragmentsRequest(collection)) - - let url = fragmentUrl(collection) - - if (options && options.fields) { - url += '?fields=' + encodeURIComponent(options.fields.join(',')) - } - - return api - .get(url) - .then( - fragments => dispatch(getFragmentsSuccess(collection, fragments)), - err => dispatch(getFragmentsFailure(err)), - ) - } -} - -function createFragmentRequest(fragment) { - return { - type: T.CREATE_FRAGMENT_REQUEST, - fragment: fragment, - } -} - -function createFragmentSuccess(collection, fragment) { - return { - type: T.CREATE_FRAGMENT_SUCCESS, - collection: collection, - fragment: fragment, - } -} - -function createFragmentFailure(fragment, error) { - return { - type: T.CREATE_FRAGMENT_FAILURE, - isFetching: false, - fragment: fragment, - error: error, - } -} - -export function createFragment(collection, fragment) { - return dispatch => { - dispatch(createFragmentRequest(fragment)) - - const url = fragmentUrl(collection, fragment) - - return api - .create(url, fragment) - .then( - fragment => dispatch(createFragmentSuccess(collection, fragment)), - err => dispatch(createFragmentFailure(fragment, err)), - ) - } -} - -function getFragmentRequest(fragment) { - return { - type: T.GET_FRAGMENT_REQUEST, - fragment: fragment, - } -} - -function getFragmentSuccess(fragment) { - return { - type: T.GET_FRAGMENT_SUCCESS, - fragment: fragment, - receivedAt: Date.now(), - } -} - -function getFragmentFailure(fragment, error) { - return { - type: T.GET_FRAGMENT_FAILURE, - isFetching: false, - fragment: fragment, - error: error, - } -} - -export function getFragment(collection, fragment) { - return dispatch => { - dispatch(getFragmentRequest(fragment)) - - const url = fragmentUrl(collection, fragment) - - return api - .get(url) - .then( - fragment => dispatch(getFragmentSuccess(fragment)), - err => dispatch(getFragmentFailure(fragment, err)), - ) - } -} - -function updateFragmentRequest(fragment) { - return { - type: T.UPDATE_FRAGMENT_REQUEST, - fragment: fragment, - } -} - -function updateFragmentSuccess(fragment, update) { - return { - type: T.UPDATE_FRAGMENT_SUCCESS, - fragment: fragment, - update: update, - receivedAt: Date.now(), - } -} - -function updateFragmentFailure(fragment, error) { - return { - type: T.UPDATE_FRAGMENT_FAILURE, - isFetching: false, - fragment: fragment, - error: error, - } -} - -export function updateFragment(collection, fragment) { - return dispatch => { - dispatch(updateFragmentRequest(fragment)) - - const url = fragmentUrl(collection, fragment) - - return api - .update(url, fragment) - .then( - update => dispatch(updateFragmentSuccess(fragment, update)), - err => dispatch(updateFragmentFailure(fragment, err)), - ) - } -} - -function deleteFragmentRequest(fragment) { - return { - type: T.DELETE_FRAGMENT_REQUEST, - fragment: fragment, - update: { deleted: true }, - } -} - -function deleteFragmentSuccess(collection, fragment) { - return { - type: T.DELETE_FRAGMENT_SUCCESS, - collection: collection, - fragment: fragment, - } -} - -function deleteFragmentFailure(fragment, error) { - return { - type: T.DELETE_FRAGMENT_FAILURE, - fragment: fragment, - update: { deleted: undefined }, - error: error, - } -} - -export function deleteFragment(collection, fragment) { - return dispatch => { - dispatch(deleteFragmentRequest(fragment)) - - const url = fragmentUrl(collection, fragment) - - return api - .remove(url) - .then( - json => dispatch(deleteFragmentSuccess(collection, fragment)), - err => dispatch(deleteFragmentFailure(fragment, err)), - ) - } -} diff --git a/src/actions/index.js b/src/actions/index.js index 0ad2f8c..b1c6ea4 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -1,35 +1 @@ -import { RESET_ERROR_MESSAGE } from './types' - -import * as collections from './collections' -import * as currentUser from './currentUser' -import * as fileUpload from './fileUpload' -import * as fragments from './fragments' -import * as teams from './teams' -import * as users from './users' - -import componentActions from '../components/actions' - -// Resets the currently visible error message. -const resetErrorMessage = () => ({ - type: RESET_ERROR_MESSAGE, -}) - -// Hydrate hydrates the store from a persistent store, the backend. -// It gets collections, fragments and user data (via token). -const hydrate = () => dispatch => - Promise.all([ - dispatch(currentUser.getCurrentUser()), - dispatch(collections.getCollections()), - ]) - -export default { - ...collections, - ...currentUser, - ...fileUpload, - ...fragments, - ...teams, - ...users, - ...componentActions, - hydrate, - resetErrorMessage, -} +export default {} diff --git a/src/actions/teams.js b/src/actions/teams.js deleted file mode 100644 index 741bf17..0000000 --- a/src/actions/teams.js +++ /dev/null @@ -1,159 +0,0 @@ -import * as api from '../helpers/api' -import * as T from './types' - -const teamUrl = team => { - let url = '/teams' - - if (team) url += `/${team.id}` - - return url -} - -function getTeamsRequest() { - return { - type: T.GET_TEAMS_REQUEST, - isFetching: true, - } -} - -function getTeamsSuccess(teams) { - return { - type: T.GET_TEAMS_SUCCESS, - isFetching: false, - teams: teams, - } -} - -function getTeamsFailure(message) { - return { - type: T.GET_TEAMS_FAILURE, - isFetching: false, - message, - } -} - -export function getTeams() { - return dispatch => { - dispatch(getTeamsRequest()) - - return api - .get(teamUrl()) - .then( - teams => dispatch(getTeamsSuccess(teams)), - err => dispatch(getTeamsFailure(err)), - ) - } -} - -function createTeamRequest(team) { - return { - type: T.CREATE_TEAM_REQUEST, - team: team, - } -} - -function createTeamSuccess(team) { - return { - type: T.CREATE_TEAM_SUCCESS, - team: team, - } -} - -function createTeamFailure(team, error) { - return { - type: T.CREATE_TEAM_FAILURE, - isFetching: false, - team: team, - error: error, - } -} - -export function createTeam(team) { - return dispatch => { - dispatch(createTeamRequest(team)) - - const url = teamUrl() - - return api - .create(url, team) - .then( - team => dispatch(createTeamSuccess(team)), - err => dispatch(createTeamFailure(team, err)), - ) - } -} - -function updateTeamRequest(team) { - return { - type: T.UPDATE_TEAM_REQUEST, - team: team, - } -} - -function updateTeamSuccess(team) { - return { - type: T.UPDATE_TEAM_SUCCESS, - team: team, - } -} - -function updateTeamFailure(team, error) { - return { - type: T.UPDATE_TEAM_FAILURE, - isFetching: false, - team: team, - error: error, - } -} - -export function updateTeam(team) { - return dispatch => { - dispatch(updateTeamRequest(team)) - const url = teamUrl(team) - - return api - .update(url, team) - .then( - team => dispatch(updateTeamSuccess(team)), - err => dispatch(updateTeamFailure(team, err)), - ) - } -} - -function deleteTeamRequest(team) { - return { - type: T.DELETE_TEAM_REQUEST, - team: team, - } -} - -function deleteTeamSuccess(team) { - return { - type: T.DELETE_TEAM_SUCCESS, - team: team, - } -} - -function deleteTeamFailure(team, error) { - return { - type: T.DELETE_TEAM_FAILURE, - isFetching: false, - team: team, - error: error, - } -} - -export function deleteTeam(team) { - return dispatch => { - dispatch(deleteTeamRequest(team)) - - const url = teamUrl(team) - - return api - .remove(url) - .then( - team => dispatch(deleteTeamSuccess(team)), - err => dispatch(deleteTeamFailure(team, err)), - ) - } -} diff --git a/src/actions/users.js b/src/actions/users.js deleted file mode 100644 index 1c76c04..0000000 --- a/src/actions/users.js +++ /dev/null @@ -1,112 +0,0 @@ -import * as api from '../helpers/api' -import * as T from './types' - -function getUsersRequest() { - return { - type: T.GET_USERS_REQUEST, - isFetching: true, - } -} - -function getUsersSuccess(users) { - return { - type: T.GET_USERS_SUCCESS, - isFetching: false, - users: users, - } -} - -function getUsersFailure(message) { - return { - type: T.GET_USERS_FAILURE, - isFetching: false, - message, - } -} - -export function getUsers() { - return dispatch => { - dispatch(getUsersRequest()) - - return api - .get('/users') - .then( - users => dispatch(getUsersSuccess(users.users)), - err => dispatch(getUsersFailure(err)), - ) - } -} - -function getUserRequest(user) { - return { - type: T.GET_USER_REQUEST, - user, - } -} - -function getUserSuccess(user) { - return { - type: T.GET_USER_SUCCESS, - user, - } -} - -function getUserFailure(user, error) { - return { - type: T.GET_USER_FAILURE, - user, - error, - } -} - -export function getUser(user) { - return dispatch => { - dispatch(getUserRequest(user)) - - return api - .get('/users/' + user.id) - .then( - user => dispatch(getUserSuccess(user)), - err => dispatch(getUserFailure(err)), - ) - } -} - -function updateUserRequest(user) { - return { - type: T.UPDATE_USER_REQUEST, - user: user, - isFetching: true, - } -} - -function updateUserSuccess(users) { - return { - type: T.UPDATE_USER_SUCCESS, - isFetching: false, - users: users, - } -} - -function updateUserFailure(message) { - return { - type: T.UPDATE_USER_FAILURE, - isFetching: false, - error: message, - } -} - -export function updateUser(user) { - return dispatch => { - dispatch(updateUserRequest(user)) - - const url = '/users/' + user.id - - return api - .update(url, user) - .then( - user => dispatch(updateUserSuccess(user)), - err => dispatch(updateUserFailure(err)), - ) - } -} diff --git a/src/components/AuthenticatedComponent.jsx b/src/components/AuthenticatedComponent.jsx index 9960140..507e31e 100644 --- a/src/components/AuthenticatedComponent.jsx +++ b/src/components/AuthenticatedComponent.jsx @@ -1,53 +1,46 @@ import React from 'react' -import { connect } from 'react-redux' import { withRouter } from 'react-router' -import { push } from 'react-router-redux' import PropTypes from 'prop-types' +import { graphql, compose } from 'react-apollo' -import actions from '../actions' +import gql from 'graphql-tag' -export class AuthenticatedComponent extends React.Component { - componentWillMount() { - this.props.getCurrentUser().then(() => this.checkAuth(this.props)) - } +const query = gql` + query CurrentUser { + currentUser: loggedInUser { + id + username + email + admin + } + }` + +export class AuthenticatedComponent extends React.Component { componentWillReceiveProps(nextProps) { this.checkAuth(nextProps) } - checkAuth({ isFetching, isAuthenticated }) { - if (!isFetching && !isAuthenticated) { + checkAuth({ data: {loading, currentUser} }) { + if (!loading && !currentUser) { const redirectAfterLogin = this.props.location.pathname this.props.pushState(`/login?next=${redirectAfterLogin}`) } } render() { - return this.props.isAuthenticated ? this.props.children : null + return this.props.data.currentUser ? this.props.children : null } } AuthenticatedComponent.propTypes = { + data: PropTypes.shape({ + loading: PropTypes.bool, + currentUser: PropTypes.object, + }), children: PropTypes.node, location: PropTypes.object, - getCurrentUser: PropTypes.func.isRequired, - isFetching: PropTypes.bool, - isAuthenticated: PropTypes.bool, pushState: PropTypes.func.isRequired, } -function mapState(state) { - return { - isFetching: state.currentUser.isFetching, - isAuthenticated: state.currentUser.isAuthenticated, - } -} - -const ConnectedAuthenticatedComponent = withRouter( - connect(mapState, { - getCurrentUser: actions.getCurrentUser, - pushState: push, - })(AuthenticatedComponent), -) - -export default ConnectedAuthenticatedComponent +export default compose(withRouter, graphql(query))(AuthenticatedComponent) diff --git a/src/components/Root.js b/src/components/Root.js index f40aafc..bba6750 100644 --- a/src/components/Root.js +++ b/src/components/Root.js @@ -3,12 +3,34 @@ import { ConnectedRouter } from 'react-router-redux' import { Provider } from 'react-redux' import PropTypes from 'prop-types' import { ThemeProvider } from 'react-css-themr' +import { ApolloProvider } from 'react-apollo' +import { ApolloClient } from 'apollo-client' +import { HttpLink } from 'apollo-link-http' +import { ApolloLink } from 'apollo-link' +import { InMemoryCache } from 'apollo-cache-inmemory' +import config from 'config' + +const httpLink = new HttpLink({ uri: config['pubsweet-client'].API_ENDPOINT }) +const authLink = new ApolloLink((operation, forward) => { + operation.setContext({ + headers: { + authorization: `Bearer ${localStorage.getItem('graphcoolToken')}`, + }, + }) + return forward(operation) +}) +const client = new ApolloClient({ + link: authLink.concat(httpLink), + cache: new InMemoryCache(), +}) const Root = ({ store, history, routes, theme }) => ( <Provider store={store}> - <ConnectedRouter history={history}> - <ThemeProvider theme={theme}>{routes}</ThemeProvider> - </ConnectedRouter> + <ApolloProvider client={client}> + <ConnectedRouter history={history}> + <ThemeProvider theme={theme}>{routes}</ThemeProvider> + </ConnectedRouter> + </ApolloProvider> </Provider> ) diff --git a/src/components/UpdateSubscriber.jsx b/src/components/UpdateSubscriber.jsx index 9f483e2..da8586a 100644 --- a/src/components/UpdateSubscriber.jsx +++ b/src/components/UpdateSubscriber.jsx @@ -6,7 +6,7 @@ import _ from 'lodash/fp' import * as T from '../actions/types' import 'event-source-polyfill' -import token from '../helpers/token' +const token = 'TODO' const actionMap = { 'collection:create': T.CREATE_COLLECTION_SUCCESS, diff --git a/src/helpers/Authorize.jsx b/src/helpers/Authorize.jsx index 2d23985..4774a74 100644 --- a/src/helpers/Authorize.jsx +++ b/src/helpers/Authorize.jsx @@ -2,10 +2,10 @@ import React from 'react' import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { compose } from 'redux' import withAuthsome from './withAuthsome' +import { graphql, compose } from 'react-apollo' +import gql from 'graphql-tag' export class Authorize extends React.Component { constructor(props) { @@ -24,7 +24,7 @@ export class Authorize extends React.Component { this.checkAuth(nextProps) } - async checkAuth({ authsome, currentUser, operation, object }) { + async checkAuth({ authsome, data: {currentUser}, operation, object }) { try { const authorized = await authsome.can( currentUser && currentUser.id, @@ -47,7 +47,7 @@ export class Authorize extends React.Component { } Authorize.propTypes = { - currentUser: PropTypes.object, + data: PropTypes.shape({currentUser: PropTypes.object}), operation: PropTypes.string, object: PropTypes.object, children: PropTypes.element, @@ -55,13 +55,14 @@ Authorize.propTypes = { authsome: PropTypes.object.isRequired, } -function mapState(state) { - return { - teams: state.teams, - collections: state.collections, - fragments: state.fragments, - currentUser: state.currentUser.user, - } -} +const query = gql` + query CurrentUser { + currentUser: loggedInUser { + id + username + email + admin + } + }` -export default compose(withAuthsome(), connect(mapState))(Authorize) +export default compose(withAuthsome(), graphql(query))(Authorize) diff --git a/src/helpers/Utils.js b/src/helpers/Utils.js deleted file mode 100644 index bb9a20f..0000000 --- a/src/helpers/Utils.js +++ /dev/null @@ -1,5 +0,0 @@ -// NOTE: deprecated - only retained for backwards compatibility - -import { deprecatedFetch } from './api' - -export const fetch = deprecatedFetch diff --git a/src/helpers/api.js b/src/helpers/api.js index b3e29c4..0e8c879 100644 --- a/src/helpers/api.js +++ b/src/helpers/api.js @@ -1,8 +1,4 @@ import fetch from 'isomorphic-fetch' -import endpoint from './endpoint' - -// read the authentication token from LocalStorage -import getToken from './token' const parse = response => { if (response.headers.get('content-type').includes('application/json')) { @@ -16,16 +12,6 @@ const request = (url, options = {}) => { options.headers = options.headers || {} options.headers['Accept'] = options.headers['Accept'] || 'application/json' - const token = getToken() - - if (token) { - options.headers['Authorization'] = 'Bearer ' + token - } - - if (!url.match(/^https?:/)) { - url = endpoint + url - } - return fetch(url, options).then(response => { if (!response.ok) { return response.text().then(errorText => { diff --git a/src/helpers/endpoint.js b/src/helpers/endpoint.js deleted file mode 100644 index 84a7b26..0000000 --- a/src/helpers/endpoint.js +++ /dev/null @@ -1,3 +0,0 @@ -import config from 'config' - -module.exports = config['pubsweet-client'].API_ENDPOINT diff --git a/src/helpers/token.js b/src/helpers/token.js deleted file mode 100644 index 0fb7d80..0000000 --- a/src/helpers/token.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = () => { - const localStorage = window.localStorage || global.window.localStorage - - if (!localStorage) throw new Error('localstorage is not available') - - return localStorage.getItem('token') -} diff --git a/src/helpers/withAuthsome.js b/src/helpers/withAuthsome.js index 01ab39e..e997de2 100644 --- a/src/helpers/withAuthsome.js +++ b/src/helpers/withAuthsome.js @@ -1,36 +1,47 @@ +import React from 'react' import Authsome from 'authsome' -import { connect } from 'react-redux' +import { withApollo } from 'react-apollo' import config from 'config' +import { gql } from 'apollo-client-preset' const mode = require(config.authsome.mode) +const fragment = gql` + fragment theUser on User { + id + username + email + admin + } +` + // higher order component to inject authsome into a component -export default function withAuthsome() { - const authsome = new Authsome({...config.authsome, mode}, {}) +function withAuthsome() { + const authsome = new Authsome({ ...config.authsome, mode }, {}) - function mapState(state) { - authsome.context = { - // fetch entities from store instead of database - models: { - Collection: { - find: id => - state.collections.find(collection => collection.id === id), - }, - Fragment: { - find: id => state.fragments[id], - }, - Team: { - find: id => state.teams.find(team => team.id === id), - }, - User: { - find: id => { - return state.users.users.find(user => user.id === id) + return Component => + withApollo(({ client, ...props }) => { + authsome.context = { + // fetch entities from store instead of database + models: { + Collection: { + find: id => + client.readFragment({ id: `Collection:${id}`, fragment }), + }, + Fragment: { + find: id => client.readFragment({ id: `Fragment:${id}`, fragment }), + }, + Team: { + find: id => client.readFragment({ id: `Team:${id}`, fragment }), + }, + User: { + find: id => + client.readFragment({ id: `UserPayload:${id}`, fragment }), }, }, - }, - } + } - return { authsome } - } - - return connect(mapState) + return <Component authsome={authsome} {...props} /> + }) } + +export default withAuthsome diff --git a/src/helpers/withLoader.js b/src/helpers/withLoader.js new file mode 100644 index 0000000..cb9dbfb --- /dev/null +++ b/src/helpers/withLoader.js @@ -0,0 +1,21 @@ +import React from 'react' +import PropTypes from 'prop-types' + +export default () => { + return WrappedComponent => { + const Wrapper = ({ + data: { loading, error, ...apolloProps }, + ...parentProps + }) => { + if (loading) return <div>Loading...</div> + if (error) return <div>{error.message}</div> + return <WrappedComponent {...parentProps} {...apolloProps} /> + } + + Wrapper.propTypes = { + data: PropTypes.object, + } + + return Wrapper + } +} diff --git a/src/index.js b/src/index.js index 0296627..b7e85b8 100644 --- a/src/index.js +++ b/src/index.js @@ -2,5 +2,4 @@ export { default as configureStore } from './store/configureStore' export { default as Root } from './components/Root' export { requireAuthentication } from './components/AuthenticatedComponent' export { default as actions } from './actions' -export { default as reducers } from './reducers' export { default as validations } from './validations' diff --git a/src/reducers/collections.js b/src/reducers/collections.js deleted file mode 100644 index 25d4fbb..0000000 --- a/src/reducers/collections.js +++ /dev/null @@ -1,130 +0,0 @@ -import { - GET_COLLECTIONS_SUCCESS, - GET_COLLECTIONS_FAILURE, - CREATE_COLLECTION_SUCCESS, - GET_COLLECTION_REQUEST, - GET_COLLECTION_SUCCESS, - UPDATE_COLLECTION_SUCCESS, - PATCH_COLLECTION_SUCCESS, - DELETE_COLLECTION_SUCCESS, - GET_FRAGMENTS_SUCCESS, - CREATE_FRAGMENT_SUCCESS, - DELETE_FRAGMENT_SUCCESS, - LOGOUT_SUCCESS, -} from '../actions/types' - -import find from 'lodash/find' -import union from 'lodash/union' -import difference from 'lodash/difference' -import clone from 'lodash/clone' -import findIndex from 'lodash/findIndex' -import without from 'lodash/without' - -export default function(state = [], action) { - const collections = clone(state) - - // TODO: store entities as an object or immutable Map, with the id as the key - function getCollection() { - return find(collections, { id: action.collection.id }) - } - - function getCollectionIndex() { - return findIndex(collections, { id: action.collection.id }) - } - - function addCollection() { - // only add the collection if it hasn't already been added - if (!getCollection()) { - collections.push(action.collection) - } - - return collections - } - - function setCollection() { - const index = getCollectionIndex() - - // NOTE: this is necessary because the collections state is an array - if (index === -1) { - collections.push(action.collection) - } else { - collections[index] = action.collection - } - - return collections - } - - function updateCollection() { - const index = getCollectionIndex() - - collections[index] = { ...collections[index], ...action.update } - - return collections - } - - function deleteCollection() { - const collection = getCollection() - - return without(collections, collection) - } - - function addFragments() { - const collection = getCollection() - - if (collection) { - collection.fragments = union( - collection.fragments, - (action.fragments || [action.fragment]).map(fragment => fragment.id), - ) - } - - return collections - } - - function removeFragments() { - const collection = getCollection() - - if (collection) { - collection.fragments = difference( - collection.fragments, - (action.fragments || [action.fragment]).map(fragment => fragment.id), - ) - } - - return collections - } - - switch (action.type) { - case GET_COLLECTIONS_SUCCESS: - return clone(action.collections) - - case GET_COLLECTIONS_FAILURE: - return [] - - case CREATE_COLLECTION_SUCCESS: - return addCollection() - - case GET_COLLECTION_SUCCESS: - return setCollection() - - case UPDATE_COLLECTION_SUCCESS: - case PATCH_COLLECTION_SUCCESS: - return updateCollection() - - case GET_COLLECTION_REQUEST: - case DELETE_COLLECTION_SUCCESS: - return deleteCollection() - - case DELETE_FRAGMENT_SUCCESS: - return removeFragments() - - case GET_FRAGMENTS_SUCCESS: - case CREATE_FRAGMENT_SUCCESS: - return addFragments() - - case LOGOUT_SUCCESS: - return [] - } - - return state -} diff --git a/src/reducers/currentUser.js b/src/reducers/currentUser.js deleted file mode 100644 index a14eb84..0000000 --- a/src/reducers/currentUser.js +++ /dev/null @@ -1,48 +0,0 @@ -import { - GET_CURRENT_USER_REQUEST, - GET_CURRENT_USER_SUCCESS, - GET_CURRENT_USER_FAILURE, - LOGOUT_SUCCESS, -} from '../actions/types' - -export default function( - state = { - isFetching: false, - isAuthenticated: false, - }, - action, -) { - switch (action.type) { - case GET_CURRENT_USER_REQUEST: - return { - ...state, - isFetching: true, - isAuthenticated: false, - } - - case GET_CURRENT_USER_SUCCESS: - return { - ...state, - isFetching: false, - isAuthenticated: true, - user: action.user, - } - - case GET_CURRENT_USER_FAILURE: - return { - ...state, - isFetching: false, - isAuthenticated: false, - } - - case LOGOUT_SUCCESS: - return { - isFetching: false, - isAuthenticated: false, - user: null, - } - - default: - return state - } -} diff --git a/src/reducers/error.js b/src/reducers/error.js deleted file mode 100644 index df299d1..0000000 --- a/src/reducers/error.js +++ /dev/null @@ -1,9 +0,0 @@ -// Updates error message to notify about the failed fetches. -export default function(state = null, { error }) { - if (error && error.message) { - console.error(error) - return error.message - } - - return null -} diff --git a/src/reducers/fileUpload.js b/src/reducers/fileUpload.js deleted file mode 100644 index d5db71d..0000000 --- a/src/reducers/fileUpload.js +++ /dev/null @@ -1,24 +0,0 @@ -import { FILE_UPLOAD_REQUEST, FILE_UPLOAD_SUCCESS } from '../actions/types' - -export default function( - state = { - isFetching: false, - }, - action, -) { - switch (action.type) { - case FILE_UPLOAD_SUCCESS: - return { - ...state, - isFetching: false, - file: action.file, - } - case FILE_UPLOAD_REQUEST: - return { - ...state, - isFetching: true, - } - default: - return state - } -} diff --git a/src/reducers/fragments.js b/src/reducers/fragments.js deleted file mode 100644 index 4d5cafe..0000000 --- a/src/reducers/fragments.js +++ /dev/null @@ -1,80 +0,0 @@ -import { - GET_FRAGMENTS_SUCCESS, - CREATE_FRAGMENT_REQUEST, - CREATE_FRAGMENT_SUCCESS, - CREATE_FRAGMENT_FAILURE, - GET_FRAGMENT_REQUEST, - GET_FRAGMENT_SUCCESS, - UPDATE_FRAGMENT_REQUEST, - UPDATE_FRAGMENT_SUCCESS, - // UPDATE_FRAGMENT_FAILURE, - DELETE_FRAGMENT_REQUEST, - DELETE_FRAGMENT_FAILURE, - DELETE_FRAGMENT_SUCCESS, - LOGOUT_SUCCESS, -} from '../actions/types' - -import clone from 'lodash/clone' -import unset from 'lodash/unset' - -export default function(state = {}, action) { - const fragments = clone(state) - - function replaceAll() { - unset(fragments, action.collection.fragments) - action.fragments.forEach(fragment => { - fragments[fragment.id] = fragment - }) - return fragments - } - - function setOne() { - fragments[action.fragment.id] = action.fragment - - return fragments - } - - function updateOne() { - const oldfragment = fragments[action.fragment.id] || {} - const update = action.update || action.fragment - - fragments[action.fragment.id] = { ...oldfragment, ...update } - - return fragments - } - - function removeOne() { - unset(fragments, action.fragment.id) - return fragments - } - - // choose the sword, and you will join me - // choose the ball, and you join your mother... in death - // you don't understand my words, but you must choose - // æ‹ ä¸€åˆ€ | Ogami IttÅ - switch (action.type) { - case UPDATE_FRAGMENT_REQUEST: - case UPDATE_FRAGMENT_SUCCESS: - case DELETE_FRAGMENT_REQUEST: - case DELETE_FRAGMENT_FAILURE: - case CREATE_FRAGMENT_SUCCESS: - case CREATE_FRAGMENT_REQUEST: - return updateOne() - - case GET_FRAGMENT_SUCCESS: - return setOne() - - case GET_FRAGMENT_REQUEST: - case CREATE_FRAGMENT_FAILURE: - case DELETE_FRAGMENT_SUCCESS: - return removeOne() - - case GET_FRAGMENTS_SUCCESS: - return replaceAll() - - case LOGOUT_SUCCESS: - return {} - } - - return state -} diff --git a/src/reducers/index.js b/src/reducers/index.js deleted file mode 100644 index 25a2890..0000000 --- a/src/reducers/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import collections from './collections' -import currentUser from './currentUser' -import error from './error' -import fileUpload from './fileUpload' -import fragments from './fragments' -import users from './users' -import teams from './teams' - -export default { - collections, - currentUser, - error, - fileUpload, - fragments, - teams, - users, -} diff --git a/src/reducers/teams.js b/src/reducers/teams.js deleted file mode 100644 index 4055822..0000000 --- a/src/reducers/teams.js +++ /dev/null @@ -1,44 +0,0 @@ -import { - GET_TEAMS_SUCCESS, - CREATE_TEAM_SUCCESS, - UPDATE_TEAM_SUCCESS, - DELETE_TEAM_SUCCESS, - GET_COLLECTION_TEAMS_SUCCESS, - LOGOUT_SUCCESS, -} from '../actions/types' - -import clone from 'lodash/clone' -import findIndex from 'lodash/findIndex' -import differenceBy from 'lodash/differenceBy' -import unionBy from 'lodash/unionBy' - -export default function(state = [], action) { - const teams = clone(state) - - function updateOne() { - const index = findIndex(teams, { id: action.team.id }) - if (index !== -1) { - teams[index] = { ...teams[index], ...action.team } - } else { - teams.push(action.team) - } - - return teams - } - - switch (action.type) { - case CREATE_TEAM_SUCCESS: - case UPDATE_TEAM_SUCCESS: - return updateOne() - case GET_TEAMS_SUCCESS: - return clone(action.teams) - case DELETE_TEAM_SUCCESS: - return differenceBy(state, [action.team], 'id') - case LOGOUT_SUCCESS: - return [] - case GET_COLLECTION_TEAMS_SUCCESS: - return unionBy(state, action.teams, 'id') - } - - return state -} diff --git a/src/reducers/users.js b/src/reducers/users.js deleted file mode 100644 index 80d00fb..0000000 --- a/src/reducers/users.js +++ /dev/null @@ -1,72 +0,0 @@ -import { unionBy } from 'lodash' - -import { - GET_USERS_REQUEST, - GET_USERS_SUCCESS, - GET_USER_SUCCESS, - UPDATE_USER_REQUEST, - UPDATE_USER_SUCCESS, - GET_CURRENT_USER_SUCCESS, - LOGOUT_SUCCESS, -} from '../actions/types' - -// TODO: store users as an object/map instead of an array - -// The users reducer. -export default ( - state = { - isFetching: false, - users: [], - }, - action, -) => { - switch (action.type) { - case GET_USERS_REQUEST: - return { - ...state, - isFetching: true, - } - - case GET_USERS_SUCCESS: - return { - ...state, - isFetching: false, - users: action.users, - } - - case GET_USER_SUCCESS: - return { - ...state, - isFetching: false, - users: unionBy([action.user], state.users, 'id'), - } - - case UPDATE_USER_REQUEST: - return { - ...state, - isFetching: true, - } - - case UPDATE_USER_SUCCESS: - return { - ...state, - isFetching: false, - users: unionBy([action.user], state.users, 'id'), - } - - case LOGOUT_SUCCESS: - return { - isFetching: false, - users: [], - } - - case GET_CURRENT_USER_SUCCESS: - return { - ...state, - users: unionBy([action.user], state.users, 'id'), - } - - default: - return state - } -} diff --git a/src/store/configureStore.js b/src/store/configureStore.js index 9e65bef..e1314e4 100644 --- a/src/store/configureStore.js +++ b/src/store/configureStore.js @@ -5,11 +5,6 @@ import thunk from 'redux-thunk' import { createLogger } from 'redux-logger' import { reducer as formReducer } from 'redux-form' import config from 'config' -import reducers from '../reducers' - -require('../components/reducers').forEach(componentReducers => - Object.assign(reducers, componentReducers), -) function createConfigureStore(env) { return function configureStore( @@ -19,7 +14,6 @@ function createConfigureStore(env) { customMiddlewares = [], ) { const reducer = combineReducers({ - ...reducers, form: formReducer, routing: routerReducer, ...customReducers, @@ -47,14 +41,6 @@ function createConfigureStore(env) { composeEnhancers(...middleware), ) - if (module.hot) { - // Enable Webpack hot module replacement for reducers - module.hot.accept('../reducers', () => { - const nextRootReducer = require('../reducers') - store.replaceReducer(nextRootReducer) - }) - } - return store } } diff --git a/src/validations.js b/src/validations.js index 3aaca1f..4ba52ba 100644 --- a/src/validations.js +++ b/src/validations.js @@ -1,3 +1 @@ -import config from 'config' - -module.exports = require('pubsweet-server/src/models/validations')(config) +module.exports = {} diff --git a/test/actions/collections.test.js b/test/actions/collections.test.js deleted file mode 100644 index 65ed309..0000000 --- a/test/actions/collections.test.js +++ /dev/null @@ -1,120 +0,0 @@ -global.PUBSWEET_COMPONENTS = [] - -const actions = require('../../src/actions/collections') -const describeAction = require('../helpers/describeAction')(actions) -const T = require('../../src/actions/types') - -describe('Collection actions', () => { - describeAction('getCollections', { - types: { - request: T.GET_COLLECTIONS_REQUEST, - success: T.GET_COLLECTIONS_SUCCESS, - failure: T.GET_COLLECTIONS_FAILURE, - }, - properties: { - request: ['type'], - success: ['type', 'collections', 'receivedAt'], - failure: ['type', 'error'], - }, - }) - - describeAction('getCollectionTeams', { - firstarg: { id: 123 }, - types: { - request: T.GET_COLLECTION_TEAMS_REQUEST, - success: T.GET_COLLECTION_TEAMS_SUCCESS, - failure: T.GET_COLLECTION_TEAMS_FAILURE, - }, - properties: { - request: ['type'], - success: ['type', 'teams', 'receivedAt'], - failure: ['type', 'error'], - }, - }) - - describeAction('createCollection', { - firstarg: { - type: 'testing', - title: 'this is a test collection', - }, - types: { - request: T.CREATE_COLLECTION_REQUEST, - success: T.CREATE_COLLECTION_SUCCESS, - failure: T.CREATE_COLLECTION_FAILURE, - }, - properties: { - request: ['type', 'collection'], - success: ['type', 'collection'], - failure: ['type', 'isFetching', 'collection', 'error'], - }, - }) - - describeAction('getCollection', { - firstarg: { id: 123 }, - types: { - request: T.GET_COLLECTION_REQUEST, - success: T.GET_COLLECTION_SUCCESS, - failure: T.GET_COLLECTION_FAILURE, - }, - properties: { - request: ['type', 'collection'], - success: ['type', 'collection', 'receivedAt'], - failure: ['type', 'isFetching', 'collection', 'error'], - }, - }) - - describeAction('updateCollection', { - firstarg: { id: 123 }, - secondarg: { - type: 'testing', - title: 'this is an updated collection', - }, - types: { - request: T.UPDATE_COLLECTION_REQUEST, - success: T.UPDATE_COLLECTION_SUCCESS, - failure: T.UPDATE_COLLECTION_FAILURE, - }, - properties: { - request: ['type', 'collection'], - success: ['type', 'collection'], - failure: ['type', 'isFetching', 'collection', 'error'], - }, - }) - - // NOTE: enable this once PATCH method is implemented on the server - // describeAction('patchCollection', { - // firstarg: newcol, - // secondarg: { - // type: 'testing', - // title: 'this is a patched collection' - // }, - // types: { - // request: T.PATCH_COLLECTION_REQUEST, - // success: T.PATCH_COLLECTION_SUCCESS, - // failure: T.PATCH_COLLECTION_FAILURE - // }, - // properties: { - // request: ['type', 'collection'], - // success: ['type', 'collection'], - // failure: ['type', 'isFetching', 'collection', 'error'] - // } - // }, (action, data) => { - // expect( - // data.PATCH_COLLECTION_SUCCESS.collection.title - // ).toBe('this is a patched collection') - // }) - - describeAction('deleteCollection', { - firstarg: { id: 123 }, - types: { - request: T.DELETE_COLLECTION_REQUEST, - success: T.DELETE_COLLECTION_SUCCESS, - failure: T.DELETE_COLLECTION_FAILURE, - }, - properties: { - request: ['type', 'collection', 'update'], - success: ['type', 'collection'], - failure: ['type', 'collection', 'update', 'error'], - }, - }) -}) diff --git a/test/actions/currentUser.test.js b/test/actions/currentUser.test.js deleted file mode 100644 index 4bfd727..0000000 --- a/test/actions/currentUser.test.js +++ /dev/null @@ -1,20 +0,0 @@ -global.PUBSWEET_COMPONENTS = [] - -const actions = require('../../src/actions/currentUser') -const describeAction = require('../helpers/describeAction')(actions) -const T = require('../../src/actions/types') - -describe('currentUser actions', () => { - describeAction('getCurrentUser', { - types: { - request: T.GET_CURRENT_USER_REQUEST, - success: T.GET_CURRENT_USER_SUCCESS, - failure: T.GET_CURRENT_USER_FAILURE, - }, - properties: { - request: [], - success: ['user'], - failure: ['error'], - }, - }) -}) diff --git a/test/actions/fileUpload.test.js b/test/actions/fileUpload.test.js deleted file mode 100644 index 33b3eaa..0000000 --- a/test/actions/fileUpload.test.js +++ /dev/null @@ -1,32 +0,0 @@ -global.PUBSWEET_COMPONENTS = [] - -require('isomorphic-form-data') -global.FormData.prototype.oldAppend = global.FormData.prototype.append -global.FormData.prototype.append = function(field, value, options) { - // File upload testing hack - if (typeof value === 'string') { - value = fs.createReadStream(value) - } - this.oldAppend(field, value, options) -} - -const fs = require('fs') -const actions = require('../../src/actions/fileUpload') -const describeAction = require('../helpers/describeAction')(actions) -const T = require('../../src/actions/types') - -describe('fileUpload actions', () => { - describeAction('fileUpload', { - firstarg: './test/helpers/mockapp.js', - types: { - request: T.FILE_UPLOAD_REQUEST, - success: T.FILE_UPLOAD_SUCCESS, - failure: T.FILE_UPLOAD_FAILURE, - }, - properties: { - request: ['isFetching'], - success: ['isFetching', 'file'], - failure: ['isFetching', 'error'], - }, - }) -}) diff --git a/test/actions/fragments.test.js b/test/actions/fragments.test.js deleted file mode 100644 index 97c50fb..0000000 --- a/test/actions/fragments.test.js +++ /dev/null @@ -1,105 +0,0 @@ -global.PUBSWEET_COMPONENTS = [] - -const actions = require('../../src/actions/fragments') -const describeAction = require('../helpers/describeAction')(actions) -const T = require('../../src/actions/types') - -describe('fragments actions', () => { - const mockcol = { id: '123' } - const mockfragment = { id: '1234' } - - describeAction('getFragments', { - firstarg: mockcol, - types: { - request: T.GET_FRAGMENTS_REQUEST, - success: T.GET_FRAGMENTS_SUCCESS, - }, - properties: { - success: ['fragments'], - }, - }) - - // get a list of collections, with the specified fields - describeAction('getFragments', { - firstarg: mockcol, - secondarg: { - fields: ['type', 'presentation'], - }, - types: { - request: T.GET_FRAGMENTS_REQUEST, - success: T.GET_FRAGMENTS_SUCCESS, - failure: T.GET_FRAGMENTS_FAILURE, - }, - properties: { - success: ['fragments'], - }, - }) - - describeAction('createFragment', { - // no collection routes to top level fragment endpoint - firstarg: null, - secondarg: { - title: 'mock fragment', - type: 'some_fragment', - owners: [], - }, - types: { - request: T.CREATE_FRAGMENT_REQUEST, - success: T.CREATE_FRAGMENT_SUCCESS, - failure: T.CREATE_FRAGMENT_FAILURE, - }, - properties: { - success: ['collection', 'fragment'], - failure: ['fragment', 'error'], - }, - }) - - describeAction('getFragment', { - firstarg: mockcol, - secondarg: mockfragment, - types: { - request: T.GET_FRAGMENT_REQUEST, - success: T.GET_FRAGMENT_SUCCESS, - failure: T.GET_FRAGMENT_FAILURE, - }, - properties: { - request: ['fragment'], - success: ['fragment', 'receivedAt'], - failure: ['isFetching', 'fragment', 'error'], - }, - }) - - describeAction('updateFragment', { - firstarg: mockcol, - secondarg: { - id: '1234', - title: 'modded fragment', - type: 'some_fragment', - owners: [], - }, - types: { - request: T.UPDATE_FRAGMENT_REQUEST, - success: T.UPDATE_FRAGMENT_SUCCESS, - failure: T.UPDATE_FRAGMENT_FAILURE, - }, - properties: { - success: ['fragment', 'receivedAt'], - failure: ['fragment', 'error'], - }, - }) - - describeAction('deleteFragment', { - firstarg: mockcol, - secondarg: mockfragment, - types: { - request: T.DELETE_FRAGMENT_REQUEST, - success: T.DELETE_FRAGMENT_SUCCESS, - failure: T.DELETE_FRAGMENT_FAILURE, - }, - properties: { - request: ['fragment', 'update'], - success: ['fragment', 'collection'], - failure: ['fragment', 'error', 'update'], - }, - }) -}) diff --git a/test/actions/index.test.js b/test/actions/index.test.js deleted file mode 100644 index 7f00128..0000000 --- a/test/actions/index.test.js +++ /dev/null @@ -1,14 +0,0 @@ -global.PUBSWEET_COMPONENTS = [] - -const { hydrate } = require('../../src/actions').default - -describe('actions index', () => { - describe('hydrate', () => { - it('dispatches two actions', async () => { - const mockDispatch = jest.fn() - await hydrate()(mockDispatch) - - expect(mockDispatch.mock.calls).toHaveLength(2) - }) - }) -}) diff --git a/test/actions/teams.test.js b/test/actions/teams.test.js deleted file mode 100644 index e0206a2..0000000 --- a/test/actions/teams.test.js +++ /dev/null @@ -1,85 +0,0 @@ -global.PUBSWEET_COMPONENTS = [] - -const actions = require('../../src/actions/teams') -const describeAction = require('../helpers/describeAction')(actions) -const T = require('../../src/actions/types') - -describe('teams actions', () => { - describeAction('getTeams', { - types: { - request: T.GET_TEAMS_REQUEST, - success: T.GET_TEAMS_SUCCESS, - failure: T.GET_TEAMS_FAILURE, - }, - properties: { - request: ['isFetching'], - success: ['isFetching', 'teams'], - failure: ['isFetching', 'message'], - }, - }) - - describeAction('createTeam', { - firstarg: { - name: 'My readers', - teamType: { - name: 'Readers', - permissions: 'read', - }, - object: { - kind: 'blogpost', - source: '<blog></blog>', - presentation: '<p></p>', - }, - }, - types: { - request: T.CREATE_TEAM_REQUEST, - success: T.CREATE_TEAM_SUCCESS, - failure: T.CREATE_TEAM_FAILURE, - }, - properties: { - request: ['team'], - success: ['team'], - failure: ['isFetching', 'team', 'error'], - }, - }) - - describeAction('updateTeam', { - firstarg: { id: 234 }, - secondard: { - name: 'My readers', - teamType: { - name: 'Readers', - permissions: 'read', - }, - object: { - kind: 'blogpost', - source: '<blog></blog>', - presentation: '<p></p>', - }, - }, - types: { - request: T.UPDATE_TEAM_REQUEST, - success: T.UPDATE_TEAM_SUCCESS, - failure: T.UPDATE_TEAM_FAILURE, - }, - properties: { - request: ['team'], - success: ['team'], - failure: ['isFetching', 'team', 'error'], - }, - }) - - describeAction('deleteTeam', { - firstarg: { id: 234 }, - types: { - request: T.DELETE_TEAM_REQUEST, - success: T.DELETE_TEAM_SUCCESS, - failure: T.DELETE_TEAM_FAILURE, - }, - properties: { - request: ['team'], - success: ['team'], - failure: ['isFetching', 'team', 'error'], - }, - }) -}) diff --git a/test/actions/users.test.js b/test/actions/users.test.js deleted file mode 100644 index e65136f..0000000 --- a/test/actions/users.test.js +++ /dev/null @@ -1,54 +0,0 @@ -global.PUBSWEET_COMPONENTS = [] - -const actions = require('../../src/actions/users') -const describeAction = require('../helpers/describeAction')(actions) -const T = require('../../src/actions/types') - -describe('users actions', () => { - const user = { - username: 'fakeymcfake', - password: 'correct battery horse staple', - email: 'fakey_mcfake@pseudonymous.com', - id: '57d0fc8e-ece9-47bf-87d3-7935326b0128', - } - - describeAction('getUsers', { - types: { - request: T.GET_USERS_REQUEST, - success: T.GET_USERS_SUCCESS, - failure: T.GET_USERS_FAILURE, - }, - properties: { - success: ['users'], - failure: ['isFetching', 'message'], - }, - }) - - describeAction('getUser', { - firstarg: { id: user.id }, - types: { - request: T.GET_USER_REQUEST, - success: T.GET_USER_SUCCESS, - failure: T.GET_USER_FAILURE, - }, - properties: { - request: ['user'], - success: ['user'], - failure: ['user', 'error'], - }, - }) - - describeAction('updateUser', { - firstarg: user, - secondarg: user, - types: { - request: T.UPDATE_USER_REQUEST, - success: T.UPDATE_USER_SUCCESS, - failure: T.UPDATE_USER_FAILURE, - }, - properties: { - success: ['users'], - failure: ['isFetching', 'error'], - }, - }) -}) diff --git a/test/helpers/api.test.js b/test/helpers/api.test.js deleted file mode 100644 index dd1d4ed..0000000 --- a/test/helpers/api.test.js +++ /dev/null @@ -1,118 +0,0 @@ -import nock from 'nock' -import endpoint from '../../src/helpers/endpoint' - -// must be require, not import, due to mocking global above -const api = require('../../src/helpers/api') - -describe('API helper', () => { - beforeAll(() => { - global.window.localStorage = { - getItem: jest.fn(() => 'tok123'), - } - }) - - it('makes a GET request', async () => { - nock(endpoint) - .get('/thing') - .reply(200, 'A thing', { 'content-type': 'text/plain' }) - - const actual = await api.get('/thing') - expect(actual).toBe('A thing') - }) - - it('makes a POST request', async () => { - nock(endpoint) - .post('/thing', { - some: 'data', - }) - .reply(200, 'A new thing', { 'content-type': 'text/plain' }) - - const actual = await api.create('/thing', { some: 'data' }) - expect(actual).toBe('A new thing') - }) - - it('makes a PATCH request', async () => { - nock(endpoint) - .patch('/thing/1', { - some: 'data', - }) - .reply(200, 'A partially updated thing', { 'content-type': 'text/plain' }) - - const actual = await api.update('/thing/1', { some: 'data' }) - expect(actual).toBe('A partially updated thing') - }) - - it('makes a PUT request', async () => { - nock(endpoint) - .put('/thing/1', { - some: 'data', - }) - .reply(200, 'An updated thing', { 'content-type': 'text/plain' }) - - const actual = await api.update('/thing/1', { some: 'data' }, true) - expect(actual).toBe('An updated thing') - }) - - it('makes a DELETE request', async () => { - nock(endpoint) - .delete('/thing/1') - .reply(200, 'No thing', { 'content-type': 'text/plain' }) - - const actual = await api.remove('/thing/1') - expect(actual).toBe('No thing') - }) - - it('automatically parses JSON response', async () => { - const expected = { oh: 'yeah' } - nock(endpoint) - .get('/thing') - .reply(200, expected, { 'content-type': 'application/json' }) - - const actual = await api.get('/thing') - expect(actual).toEqual(expected) - }) - - it('includes token in header', async () => { - nock(endpoint, { - reqheaders: { - authorization: 'Bearer tok123', - }, - }) - .get('/thing') - .reply(200, 'OK', { 'content-type': '' }) - - const actual = await api.get('/thing') - expect(actual).toEqual('OK') - }) - - it('wraps HTTP errors', async () => { - nock(endpoint) - .get('/thing') - .reply(500, 'Yikes!', { 'content-type': '' }) - - try { - await api.get('/thing') - } catch (e) { - expect(e.message).toBe('Internal Server Error') - expect(e.response).toBe('Yikes!') - expect(e.statusCode).toBe(500) - } - expect.hasAssertions() - }) - - it('optionally returns raw response', async () => { - nock(endpoint) - .get('/thing') - .reply(204) - - const response = await api.default('/thing', { - method: 'GET', - parse: false, - }) - - expect(response).toMatchObject({ - ok: true, - statusText: 'No Content', - }) - }) -}) diff --git a/test/helpers/describeAction.js b/test/helpers/describeAction.js deleted file mode 100644 index 4d6222d..0000000 --- a/test/helpers/describeAction.js +++ /dev/null @@ -1,128 +0,0 @@ -const allactions = require('../../src/actions').default -const api = require('../../src/helpers/api') - -const mockGetState = () => { - return { - currentUser: {}, - } -} - -function mockApi(succeed = true) { - const implementation = () => - succeed ? Promise.resolve({}) : Promise.reject(new Error({})) - Object.keys(api) - .filter(method => typeof api[method] === 'function') - .forEach(method => - jest.spyOn(api, method).mockImplementation(implementation), - ) -} - -// custom Jest matcher -expect.extend({ - toHaveProperties(object, expectedKeys) { - const actualKeys = Object.keys(object) - const pass = expectedKeys.every(key => actualKeys.includes(key)) - return { - message: `Expected object ${pass - ? 'not to' - : 'to'} have properties: ${expectedKeys.join(', ')}`, - pass, - } - }, -}) - -const describeAction = actions => (key, opts) => { - describe(key, () => { - const actionCreator = actions[key] - - beforeEach(mockApi) - - afterEach(() => jest.restoreAllMocks()) - - it('is exported from the file', () => { - expect(actions).toHaveProperty(key) - }) - - it('is exported in the all actions object', () => { - expect(allactions).toHaveProperty(key) - }) - - it('returns a fetcher function', () => { - const thunk = actionCreator(() => {}, mockGetState) - expect(typeof thunk).toBe('function') - }) - - it('returns a promise from the fetcher function', () => { - const thunk = actionCreator(opts.firstarg, opts.secondarg) - const returned = thunk(() => {}, mockGetState) - expect(typeof returned.then).toBe('function') - }) - - it('dispatches an action object with a type property', () => { - const actions = [] - const thunk = actionCreator(opts.firstarg, opts.secondarg) - thunk(action => actions.push(action), mockGetState) - expect(actions).toHaveLength(1) - expect(actions[0]).toHaveProperty('type') - }) - - if (opts.types.request) { - const properties = opts.properties.request - const propmsg = properties ? `with [${properties.join(', ')}] ` : '' - - it(`dispatches ${key}Request ${propmsg}immediately`, () => { - const actions = [] - const thunk = actionCreator(opts.firstarg, opts.secondarg) - thunk(action => actions.push(action), mockGetState) - - const firstAction = actions[0] - expect(firstAction).toBeTruthy() - expect(firstAction.type).toBe(opts.types.request) - if (properties) { - expect(firstAction).toHaveProperties(properties) - } - }) - } - - if (opts.types.success) { - const properties = opts.properties.success - const propmsg = properties ? `with [${properties.join(', ')}] ` : '' - - it(`dispatches ${key}Success ${propmsg}on successful response`, async () => { - const actions = [] - const thunk = actionCreator(opts.firstarg, opts.secondarg) - await thunk(action => actions.push(action), mockGetState) - - const secondAction = actions[1] - expect(secondAction).toBeTruthy() - expect(secondAction.type).toBe(opts.types.success) - if (properties) { - expect(secondAction).toHaveProperties(properties) - } - }) - } - - if (opts.types.failure) { - const properties = opts.properties.failure - const propmsg = properties ? `with [${properties.join(', ')}] ` : '' - - it(`dispatches ${key}Failure ${propmsg}on failed response`, async () => { - // make API reject every request - mockApi(false) - - const actions = [] - const thunk = actionCreator(opts.firstarg, opts.secondarg) - await thunk(action => actions.push(action), mockGetState) - - const secondAction = actions[1] - expect(secondAction).toBeTruthy() - expect(secondAction.type).toBe(opts.types.failure) - if (properties) { - expect(secondAction).toHaveProperties(properties) - } - }) - } - }) -} - -module.exports = describeAction diff --git a/test/reducers/collections.test.js b/test/reducers/collections.test.js deleted file mode 100644 index 0ff025c..0000000 --- a/test/reducers/collections.test.js +++ /dev/null @@ -1,116 +0,0 @@ -const allReducers = require('../../src/reducers').default -const reducer = require('../../src/reducers/collections').default - -const T = require('../../src/actions/types') - -describe('collections reducers', () => { - it('is exported in the all reducers object', () => { - expect(allReducers.collections).toBe(reducer) - }) - - const mockCollection = { id: '123' } - const mockFragment = { name: 'mock fragment', id: '1234' } - const mockCollectionWithFragment = { - ...mockCollection, - fragments: [mockFragment.id], - } - - it('getCollections success', () => { - const actual = reducer([mockCollection], { - type: T.GET_COLLECTIONS_SUCCESS, - collections: [mockCollection], - }) - expect(actual).toEqual([mockCollection]) - }) - - it('getCollections failure', () => { - const actual = reducer(undefined, { - type: T.GET_COLLECTIONS_FAILURE, - }) - expect(actual).toEqual([]) - }) - - it('getCollection request', () => { - const actual = reducer([mockCollection], { - type: T.GET_COLLECTION_REQUEST, - collection: mockCollection, - }) - expect(actual).toEqual([]) - }) - - it('getCollection success adds collection to store', () => { - const actual = reducer([], { - type: T.GET_COLLECTION_SUCCESS, - collection: mockCollection, - }) - expect(actual).toEqual([mockCollection]) - }) - - it('getCollection success updates collection in store', () => { - const actual = reducer([mockCollection], { - type: T.GET_COLLECTION_SUCCESS, - collection: mockCollectionWithFragment, - }) - expect(actual).toEqual([mockCollectionWithFragment]) - }) - - it('createCollection success', () => { - const actual = reducer(['dummy'], { - type: T.CREATE_COLLECTION_SUCCESS, - collection: mockCollection, - }) - expect(actual).toEqual(['dummy', mockCollection]) - }) - - it('createCollection success ignores duplicate', () => { - const actual = reducer([mockCollection], { - type: T.CREATE_COLLECTION_SUCCESS, - collection: { ...mockCollection, same: 'but different' }, - }) - expect(actual).toEqual([mockCollection]) - }) - - it('updateCollection success', () => { - const actual = reducer(['dummy', mockCollection], { - type: T.UPDATE_COLLECTION_SUCCESS, - collection: mockCollection, - update: { - some: 'value', - }, - }) - expect(actual).toEqual(['dummy', { ...mockCollection, some: 'value' }]) - }) - - it('addFragments success', () => { - const actual = reducer([mockCollection], { - type: T.CREATE_FRAGMENT_SUCCESS, - collection: mockCollection, - fragment: mockFragment, - }) - expect(actual).toEqual([mockCollectionWithFragment]) - }) - - it('removeFragments success', () => { - const actual = reducer([mockCollectionWithFragment], { - type: T.DELETE_FRAGMENT_SUCCESS, - collection: mockCollectionWithFragment, - fragment: mockFragment, - }) - expect(actual).toEqual([mockCollectionWithFragment]) - }) - - it('logout success', () => { - const actual = reducer([mockCollectionWithFragment], { - type: T.LOGOUT_SUCCESS, - }) - expect(actual).toEqual([]) - }) - - it('returns same state for unrecognised action', () => { - const state = [] - const actual = reducer(state, { - type: 'something else', - }) - expect(actual).toBe(state) - }) -}) diff --git a/test/reducers/currentUser.test.js b/test/reducers/currentUser.test.js deleted file mode 100644 index de55c02..0000000 --- a/test/reducers/currentUser.test.js +++ /dev/null @@ -1,73 +0,0 @@ -const allReducers = require('../../src/reducers').default -const reducer = require('../../src/reducers/currentUser').default - -const T = require('../../src/actions/types') - -describe('currentUser reducers', () => { - it('is exported in the all reducers object', () => { - expect(allReducers.currentUser).toBe(reducer) - }) - - const mockuser = { - name: 'jo johnson', - } - - it('currentUser success', () => { - const actual = reducer( - {}, - { - type: T.GET_CURRENT_USER_SUCCESS, - user: mockuser, - }, - ) - expect(actual).toEqual({ - isFetching: false, - isAuthenticated: true, - user: mockuser, - }) - }) - - it('currentUser failure', () => { - const actual = reducer( - {}, - { - type: T.GET_CURRENT_USER_FAILURE, - }, - ) - expect(actual).toEqual({ isFetching: false, isAuthenticated: false }) - }) - - it('currentUser request', () => { - const actual = reducer( - {}, - { - type: T.GET_CURRENT_USER_REQUEST, - }, - ) - expect(actual).toEqual({ isFetching: true, isAuthenticated: false }) - }) - - it('logout success', () => { - const actual = reducer( - { - user: mockuser, - }, - { - type: T.LOGOUT_SUCCESS, - }, - ) - expect(actual).toEqual({ - isFetching: false, - isAuthenticated: false, - user: null, - }) - }) - - it('returns same state for unrecognised action', () => { - const state = {} - const actual = reducer(state, { - type: 'something else', - }) - expect(actual).toBe(state) - }) -}) diff --git a/test/reducers/error.test.js b/test/reducers/error.test.js deleted file mode 100644 index ad2fc75..0000000 --- a/test/reducers/error.test.js +++ /dev/null @@ -1,22 +0,0 @@ -const allReducers = require('../../src/reducers').default -const reducer = require('../../src/reducers/error').default - -describe('error reducers', () => { - it('is exported in the all reducers object', () => { - expect(allReducers.error).toBe(reducer) - }) - - describe('reducer error handler', () => { - it("doesn't do anything if there's no error", () => { - expect(reducer(null, { error: null })).toBeNull() - }) - - it("returns the error message if there's an error", () => { - jest.spyOn(console, 'error').mockImplementation(jest.fn()) - const error = new Error('this is a fake error') - const action = { error } - expect(reducer(null, action)).toBe(error.message) - expect(console.error.mock.calls[0][0]).toBe(error) - }) - }) -}) diff --git a/test/reducers/fileUpload.test.js b/test/reducers/fileUpload.test.js deleted file mode 100644 index 4e0a014..0000000 --- a/test/reducers/fileUpload.test.js +++ /dev/null @@ -1,36 +0,0 @@ -const allReducers = require('../../src/reducers').default -const reducer = require('../../src/reducers/fileUpload').default - -const T = require('../../src/actions/types') - -describe('fileUpload reducers', () => { - it('is exported in the all reducers object', () => { - expect(allReducers.fileUpload).toBe(reducer) - }) - - it('fileUpload success', () => { - const actual = reducer(undefined, { - type: T.FILE_UPLOAD_SUCCESS, - file: 'somefile', - }) - expect(actual).toEqual({ - isFetching: false, - file: 'somefile', - }) - }) - - it('fileUpload request', () => { - const actual = reducer(undefined, { - type: T.FILE_UPLOAD_REQUEST, - }) - expect(actual).toEqual({ isFetching: true }) - }) - - it('returns same state for unrecognised action', () => { - const state = {} - const actual = reducer(state, { - type: 'something else', - }) - expect(actual).toBe(state) - }) -}) diff --git a/test/reducers/fragments.test.js b/test/reducers/fragments.test.js deleted file mode 100644 index 6a51433..0000000 --- a/test/reducers/fragments.test.js +++ /dev/null @@ -1,97 +0,0 @@ -const allReducers = require('../../src/reducers').default -const reducer = require('../../src/reducers/fragments').default - -const T = require('../../src/actions/types') - -const clone = require('lodash/clone') - -describe('fragments reducers', () => { - it('is exported in the all reducers object', () => { - expect(allReducers.fragments).toBe(reducer) - }) - - const mockcol = { id: '123' } - mockcol.fragments = [] - - const mockfrag = { - title: 'mock fragment', - type: 'some_fragment', - owners: [], - } - const mockstate = {} - mockstate[mockfrag.id] = mockfrag - - const mockfragmod = { - title: 'modded fragment', - type: 'some_fragment', - owners: [], - } - const mockstatemod = {} - mockstatemod[mockfrag.id] = mockfragmod - - const colwithfrag = clone(mockcol) - colwithfrag.fragments = [mockfrag] - - it('getOne request', () => { - const actual = reducer(mockstate, { - type: T.GET_FRAGMENT_REQUEST, - collection: colwithfrag, - fragment: mockfragmod, - }) - expect(actual).toEqual({}) - }) - - it('getOne success', () => { - const actual = reducer( - {}, - { - type: T.GET_FRAGMENT_SUCCESS, - collection: colwithfrag, - fragment: mockfragmod, - }, - ) - expect(actual).toEqual(mockstatemod) - }) - - it('updateOne success', () => { - const actual = reducer(mockstate, { - type: T.UPDATE_FRAGMENT_SUCCESS, - collection: colwithfrag, - fragment: mockfragmod, - }) - expect(actual).toEqual(mockstatemod) - }) - - it('removeOne success', () => { - const actual = reducer(mockstate, { - type: T.DELETE_FRAGMENT_SUCCESS, - collection: colwithfrag, - fragment: mockfrag, - }) - expect(actual).toEqual({}) - }) - - it('replaceAll success', () => { - const actual = reducer(mockstate, { - type: T.GET_FRAGMENTS_SUCCESS, - collection: colwithfrag, - fragments: [mockfragmod], - }) - expect(actual).toEqual(mockstatemod) - }) - - it('logout success', () => { - const actual = reducer(mockstate, { - type: T.LOGOUT_SUCCESS, - }) - expect(actual).toEqual({}) - }) - - it('returns same state for unrecognised action', () => { - const state = {} - const actual = reducer(state, { - type: 'something else', - }) - expect(actual).toBe(state) - }) -}) diff --git a/test/reducers/teams.test.js b/test/reducers/teams.test.js deleted file mode 100644 index 49160d6..0000000 --- a/test/reducers/teams.test.js +++ /dev/null @@ -1,70 +0,0 @@ -const allReducers = require('../../src/reducers').default -const reducer = require('../../src/reducers/teams').default - -const T = require('../../src/actions/types') - -describe('teams reducers', () => { - it('is exported in the all reducers object', () => { - expect(allReducers.teams).toBe(reducer) - }) - - const mockteam = { name: 'someteam', id: '1234' } - const mockstate = [mockteam] - - it('createTeam success', () => { - const actual = reducer([], { - type: T.CREATE_TEAM_SUCCESS, - team: mockteam, - }) - expect(actual).toEqual(mockstate) - }) - - it('updateTeam success', () => { - const updatedTeam = { ...mockteam, foo: 'bar' } - const actual = reducer(mockstate, { - type: T.CREATE_TEAM_SUCCESS, - team: updatedTeam, - }) - expect(actual).toEqual([updatedTeam]) - }) - - it('getTeams success', () => { - const actual = reducer(mockstate, { - type: T.GET_TEAMS_SUCCESS, - teams: mockstate, - }) - expect(actual).toEqual(mockstate) - }) - - it('deleteTeam success', () => { - const actual = reducer(mockstate, { - type: T.DELETE_TEAM_SUCCESS, - team: { ...mockteam }, - }) - expect(actual).toEqual([]) - }) - - it('logout success', () => { - const actual = reducer(mockstate, { - type: T.LOGOUT_SUCCESS, - }) - expect(actual).toEqual([]) - }) - - it('getCollectionTeam success', () => { - const extraTeam = { id: '4321', name: 'Another team' } - const actual = reducer(mockstate, { - type: T.GET_COLLECTION_TEAMS_SUCCESS, - teams: [extraTeam, mockteam], - }) - expect(actual).toEqual([mockteam, extraTeam]) - }) - - it('returns same state for unrecognised action', () => { - const state = [] - const actual = reducer(state, { - type: 'something else', - }) - expect(actual).toBe(state) - }) -}) diff --git a/test/reducers/users.test.js b/test/reducers/users.test.js deleted file mode 100644 index 33e2c17..0000000 --- a/test/reducers/users.test.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GET_CURRENT_USER_SUCCESS } from '../../src/actions/types' - -const allReducers = require('../../src/reducers').default -const reducer = require('../../src/reducers/users').default - -const T = require('../../src/actions/types') - -describe('users reducers', () => { - it('is exported in the all reducers object', () => { - expect(allReducers.users).toBe(reducer) - }) - - const user = { - username: 'fakeymcfake', - password: 'correct battery horse staple', - email: 'fakey_mcfake@pseudonymous.com', - id: '57d0fc8e-ece9-47bf-87d3-7935326b0128', - } - - const usermod = { ...user, email: 'new@email.com' } - - const mockstate = { users: [user] } - - it('getUsers success', () => { - const actual = reducer(undefined, { - type: T.GET_USERS_SUCCESS, - users: [user], - }) - expect(actual).toEqual({ - isFetching: false, - users: [user], - }) - }) - - it('getUsers request', () => { - const actual = reducer(undefined, { - type: T.GET_USERS_REQUEST, - }) - expect(actual).toEqual({ - isFetching: true, - users: [], - }) - }) - - it('getUser success', () => { - const actual = reducer( - { users: [] }, - { - type: T.GET_USER_SUCCESS, - user: user, - }, - ) - expect(actual).toEqual({ - users: [user], - isFetching: false, - }) - }) - - it('updateUser request', () => { - const actual = reducer(mockstate, { - type: T.UPDATE_USER_REQUEST, - user: usermod, - }) - expect(actual).toEqual({ - users: [user], - isFetching: true, - }) - }) - - it('updateUser success', () => { - const actual = reducer(mockstate, { - type: T.UPDATE_USER_SUCCESS, - user: usermod, - }) - expect(actual).toEqual({ - users: [usermod], - isFetching: false, - }) - }) - - it('logout success', () => { - const actual = reducer(mockstate, { - type: T.LOGOUT_SUCCESS, - user: usermod, - }) - expect(actual).toEqual({ - users: [], - isFetching: false, - }) - }) - - it('getCurrentUser success adds user to users array', () => { - const actual = reducer(mockstate, { - type: GET_CURRENT_USER_SUCCESS, - user, - }) - expect(actual).toEqual({ - users: [user], - }) - }) - - it('returns same state for unrecognised action', () => { - const state = {} - const actual = reducer(state, { - type: 'something else', - }) - expect(actual).toBe(state) - }) -}) diff --git a/yarn.lock b/yarn.lock index 2860c32..4b38b55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,14 @@ # yarn lockfile v1 +"@types/async@2.0.45": + version "2.0.45" + resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.45.tgz#0cfe971d7ed5542695740338e0455c91078a0e83" + +"@types/zen-observable@0.5.3", "@types/zen-observable@^0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.5.3.tgz#91b728599544efbb7386d8b6633693a3c2e7ade5" + abab@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -99,6 +107,75 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" +apollo-cache-inmemory@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.1.1.tgz#1511f00eb845da88504abf867f408c3026a909ba" + dependencies: + apollo-cache "^1.0.1" + apollo-utilities "^1.0.2" + graphql-anywhere "^4.0.1" + +apollo-cache@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.0.1.tgz#66c16141173bc752d3ad3dce990310c10dfc4076" + dependencies: + apollo-utilities "^1.0.2" + +apollo-client-preset@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/apollo-client-preset/-/apollo-client-preset-1.0.3.tgz#e1f5d2d34115806c5021ae17cd09a88f61f623f5" + dependencies: + apollo-cache-inmemory "^1.1.1" + apollo-client "^2.0.3" + apollo-link "1.0.0" + apollo-link-http "1.1.0" + graphql-tag "^2.4.2" + +apollo-client@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.0.3.tgz#f99f32e2c851bbd52da1e1b113ce8f6a0cf94945" + dependencies: + "@types/zen-observable" "^0.5.3" + apollo-cache "^1.0.1" + apollo-link "^1.0.0" + apollo-link-dedup "^1.0.0" + apollo-utilities "^1.0.2" + symbol-observable "^1.0.2" + zen-observable "^0.6.0" + optionalDependencies: + "@types/async" "2.0.45" + +apollo-link-dedup@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/apollo-link-dedup/-/apollo-link-dedup-1.0.2.tgz#bab659dde41f8dd627839142d4dad90e55251110" + +apollo-link-http@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.1.0.tgz#a85cc43d9a5286bb54ac32213f7a16bef3554ae4" + +apollo-link@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.0.0.tgz#3d334285789c217f95712ebce434d56ce7f3e991" + dependencies: + apollo-utilities "^0.2.0-beta.0" + zen-observable "^0.6.0" + +apollo-link@^1.0.0, apollo-link@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.0.3.tgz#759c36abeeb99e227eca45f919ee07fb8fee911e" + dependencies: + "@types/zen-observable" "0.5.3" + apollo-utilities "^1.0.0" + zen-observable "^0.6.0" + +apollo-utilities@^0.2.0-beta.0: + version "0.2.0-rc.3" + resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-0.2.0-rc.3.tgz#7bd93be0f587f20c5b46e21880272e305759fdc2" + +apollo-utilities@^1.0.0, apollo-utilities@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.2.tgz#bcf348a7e613e82e2624ddb5be2b9f6bf1259c6d" + app-root-path@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46" @@ -2004,6 +2081,22 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" +graphql-anywhere@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.0.1.tgz#eb53ed5c56ef42e21d34dc22951e3da38f88a342" + dependencies: + apollo-utilities "^1.0.2" + +graphql-tag@^2.4.2, graphql-tag@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.5.0.tgz#b43bfd8b5babcd2c205ad680c03e98b238934e0f" + +graphql@^0.11.7: + version "0.11.7" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.11.7.tgz#e5abaa9cb7b7cccb84e9f0836bf4370d268750c6" + dependencies: + iterall "1.1.3" + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -2104,7 +2197,7 @@ hoist-non-react-statics@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" -hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.3.1: +hoist-non-react-statics@^2.2.0, hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" @@ -2492,6 +2585,10 @@ istanbul-reports@^1.1.2: dependencies: handlebars "^4.0.3" +iterall@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.3.tgz#1cbbff96204056dde6656e2ed2e2226d0e6d72c9" + jest-changed-files@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-21.2.0.tgz#5dbeecad42f5d88b482334902ce1cba6d9798d29" @@ -2970,6 +3067,10 @@ lodash.flatten@^4.2.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" +lodash.flowright@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/lodash.flowright/-/lodash.flowright-3.5.0.tgz#2b5fff399716d7e7dc5724fe9349f67065184d67" + lodash.foreach@^4.3.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" @@ -2982,7 +3083,7 @@ lodash.merge@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" -lodash.pick@^4.2.1: +lodash.pick@^4.2.1, lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" @@ -3602,6 +3703,17 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-apollo@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-2.0.1.tgz#43997db9f294d81bd229eb705944fb36d5164607" + dependencies: + apollo-link "^1.0.0" + hoist-non-react-statics "^2.2.0" + invariant "^2.2.1" + lodash.flowright "^3.5.0" + lodash.pick "^4.4.0" + prop-types "^15.5.8" + react-bootstrap@^0.31.3: version "0.31.3" resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-0.31.3.tgz#db2b7d45b00b5dac1ab8b6de3dd97feb3091b849" @@ -4234,7 +4346,7 @@ supports-color@^4.0.0: dependencies: has-flag "^2.0.0" -symbol-observable@^1.0.1, symbol-observable@^1.0.3: +symbol-observable@^1.0.1, symbol-observable@^1.0.2, symbol-observable@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" @@ -4576,3 +4688,7 @@ yargs@~3.10.0: cliui "^2.1.0" decamelize "^1.0.0" window-size "0.1.0" + +zen-observable@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.6.0.tgz#8a6157ed15348d185d948cfc4a59d90a2c0f70ee" -- GitLab