gnarails 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +227 -0
- data/.gitignore +5 -0
- data/.pronto.yml +2 -0
- data/.railsrc +2 -0
- data/.rspec +2 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -0
- data/Brewfile +3 -0
- data/CHANGELOG.md +63 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +48 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +213 -0
- data/LICENSE +21 -0
- data/README.md +136 -0
- data/Rakefile +6 -0
- data/bin/ci_pronto +6 -0
- data/bin/console +14 -0
- data/bin/generate-react-test-app.sh +2 -0
- data/bin/generate-test-app.sh +22 -0
- data/bin/setup +8 -0
- data/bin/test-setup.sh +8 -0
- data/exe/gnarails +4 -0
- data/gnarails.gemspec +34 -0
- data/gnarly.rb +395 -0
- data/lib/gnarails/cli/application.rb +192 -0
- data/lib/gnarails/version.rb +3 -0
- data/lib/gnarails.rb +8 -0
- data/templates/.circleci/config.yml +66 -0
- data/templates/.env.development +5 -0
- data/templates/.env.docker/.env.docker-standard +1 -0
- data/templates/.env.docker/.env.docker-webpack +2 -0
- data/templates/.env.docker-assets +1 -0
- data/templates/.env.test +5 -0
- data/templates/.gitignore +20 -0
- data/templates/.pronto.yml +2 -0
- data/templates/.rspec +3 -0
- data/templates/.rubocop.yml +3 -0
- data/templates/.ruby-version +1 -0
- data/templates/.scss-lint.yml +33 -0
- data/templates/Dockerfile +20 -0
- data/templates/Dockerfile-assets +24 -0
- data/templates/README.md +83 -0
- data/templates/database.yml +20 -0
- data/templates/docker-compose.yml/docker-compose-standard.yml +15 -0
- data/templates/docker-compose.yml/docker-compose-webpack.yml +26 -0
- data/templates/react/.babelrc +18 -0
- data/templates/react/.eslintrc.js +45 -0
- data/templates/react/controllers/home_controller.rb +4 -0
- data/templates/react/js/Router/Router.jsx +22 -0
- data/templates/react/js/Router/index.js +3 -0
- data/templates/react/js/api/index.js +17 -0
- data/templates/react/js/api/sessions.js +8 -0
- data/templates/react/js/app_constants/index.js +6 -0
- data/templates/react/js/components/App/App.jsx +15 -0
- data/templates/react/js/components/App/App.tests.jsx +15 -0
- data/templates/react/js/components/App/_styles.scss +3 -0
- data/templates/react/js/components/App/index.js +3 -0
- data/templates/react/js/index.scss +2 -0
- data/templates/react/js/packs/main.jsx +13 -0
- data/templates/react/js/redux/entity_getter.js +7 -0
- data/templates/react/js/redux/history.js +5 -0
- data/templates/react/js/redux/initial_state.js +5 -0
- data/templates/react/js/redux/middlewares/authentication_middleware/authentication_middleware.tests.js +109 -0
- data/templates/react/js/redux/middlewares/authentication_middleware/helpers.js +21 -0
- data/templates/react/js/redux/middlewares/authentication_middleware/helpers.tests.js +84 -0
- data/templates/react/js/redux/middlewares/authentication_middleware/index.js +20 -0
- data/templates/react/js/redux/middlewares/index.js +11 -0
- data/templates/react/js/redux/nodes/app/actions.js +36 -0
- data/templates/react/js/redux/nodes/app/config.js +12 -0
- data/templates/react/js/redux/nodes/app/reducer.js +35 -0
- data/templates/react/js/redux/nodes/app/reducer.tests.js +78 -0
- data/templates/react/js/redux/reducers.js +11 -0
- data/templates/react/js/redux/store.js +14 -0
- data/templates/react/js/storage.js +15 -0
- data/templates/react/js/styles/_variables.scss +1 -0
- data/templates/react/js/test/.setup.js +51 -0
- data/templates/react/js/test/connect_wrapper.jsx +28 -0
- data/templates/react/js/test/create_request_mock.js +29 -0
- data/templates/react/js/test/mock_store.js +12 -0
- data/templates/react/layout.html.erb +16 -0
- data/templates/react/rails_routes.rb +5 -0
- data/templates/react/views/home/default.html.erb +1 -0
- data/templates/script/brakeman +15 -0
- data/templates/script/ci_pronto +6 -0
- data/templates/spec/support/factory_bot.rb +5 -0
- data/templates/spec/support/system_test_configuration.rb +13 -0
- data/test-app/app/controllers/job_postings_controller.rb +5 -0
- data/test-app/app/models/comment.rb +3 -0
- data/test-app/app/models/job_posting.rb +20 -0
- data/test-app/app/views/job_postings/index.html.erb +23 -0
- data/test-app/app/views/layouts/application.html.erb +14 -0
- data/test-app/config/routes.rb +5 -0
- data/test-app/db/migrate/20170918002433_create_job_postings.rb +12 -0
- data/test-app/db/migrate/20170918002455_create_comments.rb +10 -0
- data/test-app/spec/factories/job_postings.rb +5 -0
- data/test-app/spec/models/job_posting_spec.rb +21 -0
- data/test-app/spec/requests/status_spec.rb +11 -0
- data/test-app/spec/system/viewing_all_job_postings_spec.rb +36 -0
- metadata +270 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
import React, { Component } from 'react';
|
2
|
+
import { withRouter } from 'react-router-dom';
|
3
|
+
|
4
|
+
class App extends Component {
|
5
|
+
render () {
|
6
|
+
const { children } = this.props;
|
7
|
+
return (
|
8
|
+
<div className="app">
|
9
|
+
{children}
|
10
|
+
</div>
|
11
|
+
);
|
12
|
+
}
|
13
|
+
};
|
14
|
+
|
15
|
+
export default withRouter(App);
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import expect from 'expect';
|
3
|
+
import { mount } from 'enzyme';
|
4
|
+
|
5
|
+
import App from 'components/App';
|
6
|
+
import connectWrapper from 'test/connect_wrapper';
|
7
|
+
|
8
|
+
describe('App - component', () => {
|
9
|
+
it('renders', () => {
|
10
|
+
const WrappedApp = connectWrapper(App);
|
11
|
+
const Component = mount(<WrappedApp />);
|
12
|
+
|
13
|
+
expect(Component.find('App').length).toEqual(1);
|
14
|
+
});
|
15
|
+
});
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import ReactDOM from 'react-dom';
|
3
|
+
|
4
|
+
import Router from 'Router';
|
5
|
+
import 'index.scss';
|
6
|
+
|
7
|
+
if (typeof window !== 'undefined') {
|
8
|
+
global.document.addEventListener('DOMContentLoaded', () => {
|
9
|
+
const mountNode = global.document.getElementById('react');
|
10
|
+
|
11
|
+
ReactDOM.render(<Router />, mountNode);
|
12
|
+
});
|
13
|
+
};
|
@@ -0,0 +1,109 @@
|
|
1
|
+
import expect from 'expect';
|
2
|
+
|
3
|
+
import appConfig from 'redux/nodes/app/config';
|
4
|
+
import mockStore from 'test/mock_store';
|
5
|
+
import storage from 'storage';
|
6
|
+
|
7
|
+
const authToken = 'abc123';
|
8
|
+
|
9
|
+
describe('Authentication middleware', () => {
|
10
|
+
beforeEach(() => storage.setItem('auth_token', authToken));
|
11
|
+
afterEach(() => storage.removeItem('auth_token'));
|
12
|
+
|
13
|
+
context('when the action type is for the login success action', () => {
|
14
|
+
const action = {
|
15
|
+
type: appConfig.actionTypes.LOGIN_SUCCESS,
|
16
|
+
payload: { jwt: 'jwt' },
|
17
|
+
};
|
18
|
+
const store = mockStore({});
|
19
|
+
|
20
|
+
it('adds the auth_token to local storage', () => {
|
21
|
+
storage.removeItem('auth_token');
|
22
|
+
store.dispatch(action);
|
23
|
+
|
24
|
+
expect(storage.getItem('auth_token')).toEqual('jwt');
|
25
|
+
});
|
26
|
+
});
|
27
|
+
|
28
|
+
context('when the action type is for the logout success action', () => {
|
29
|
+
const action = {
|
30
|
+
type: appConfig.actionTypes.LOGOUT_SUCCESS,
|
31
|
+
payload: {},
|
32
|
+
};
|
33
|
+
const store = mockStore({});
|
34
|
+
|
35
|
+
it('removes the auth_token from local storage', () => {
|
36
|
+
store.dispatch(action);
|
37
|
+
|
38
|
+
expect(storage.getItem('auth_token')).toEqual(undefined);
|
39
|
+
});
|
40
|
+
});
|
41
|
+
|
42
|
+
context('when the action type is not for the logout success action', () => {
|
43
|
+
const action = {
|
44
|
+
type: 'FOO',
|
45
|
+
payload: {},
|
46
|
+
};
|
47
|
+
const store = mockStore({});
|
48
|
+
|
49
|
+
it('does not remove the auth_token from local storage', () => {
|
50
|
+
store.dispatch(action);
|
51
|
+
|
52
|
+
expect(storage.getItem('auth_token')).toEqual(authToken);
|
53
|
+
});
|
54
|
+
});
|
55
|
+
|
56
|
+
context('when the action is for an entity that fails to load', () => {
|
57
|
+
context('when the payload error is an unauthorized error', () => {
|
58
|
+
const action = {
|
59
|
+
type: 'entity_name_SOMETHING_FAILURE',
|
60
|
+
payload: {
|
61
|
+
errors: {
|
62
|
+
http_status: 401,
|
63
|
+
},
|
64
|
+
},
|
65
|
+
};
|
66
|
+
const store = mockStore({});
|
67
|
+
|
68
|
+
it('removes the auth_token from local storage', () => {
|
69
|
+
store.dispatch(action);
|
70
|
+
|
71
|
+
expect(storage.getItem('auth_token')).toEqual(undefined);
|
72
|
+
});
|
73
|
+
});
|
74
|
+
|
75
|
+
context('when the payload error is not an unauthorized error', () => {
|
76
|
+
const action = {
|
77
|
+
type: 'entity_name_SOMETHING_FAILURE',
|
78
|
+
payload: {
|
79
|
+
errors: {},
|
80
|
+
},
|
81
|
+
};
|
82
|
+
const store = mockStore({});
|
83
|
+
|
84
|
+
it('does not remove the auth_token from local storage', () => {
|
85
|
+
store.dispatch(action);
|
86
|
+
|
87
|
+
expect(storage.getItem('auth_token')).toEqual(authToken);
|
88
|
+
});
|
89
|
+
});
|
90
|
+
});
|
91
|
+
|
92
|
+
context('when the action type is not a failure action', () => {
|
93
|
+
const action = {
|
94
|
+
type: 'entity_name_SOMETHING_SUCCESS',
|
95
|
+
payload: {
|
96
|
+
errors: {
|
97
|
+
http_status: 401,
|
98
|
+
},
|
99
|
+
},
|
100
|
+
};
|
101
|
+
const store = mockStore({});
|
102
|
+
|
103
|
+
it('does not remove the auth_token from local storage', () => {
|
104
|
+
store.dispatch(action);
|
105
|
+
|
106
|
+
expect(storage.getItem('auth_token')).toEqual(authToken);
|
107
|
+
});
|
108
|
+
});
|
109
|
+
});
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { endsWith, get } from 'lodash';
|
2
|
+
import appConfig from 'redux/nodes/app/config';
|
3
|
+
|
4
|
+
const { LOGIN_SUCCESS, LOGOUT_SUCCESS } = appConfig.actionTypes;
|
5
|
+
const UNAUTHORIZED_ERROR = 401;
|
6
|
+
|
7
|
+
const isFailureAction = (action) => {
|
8
|
+
return endsWith(action.type, '_FAILURE');
|
9
|
+
};
|
10
|
+
|
11
|
+
const isLoginAction = action => action.type === LOGIN_SUCCESS;
|
12
|
+
const isLogoutAction = action => action.type === LOGOUT_SUCCESS;
|
13
|
+
|
14
|
+
const isUnauthorizedError = action => get(action, 'payload.errors.http_status') === UNAUTHORIZED_ERROR;
|
15
|
+
|
16
|
+
export default {
|
17
|
+
isFailureAction,
|
18
|
+
isLoginAction,
|
19
|
+
isLogoutAction,
|
20
|
+
isUnauthorizedError,
|
21
|
+
};
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import expect from 'expect';
|
2
|
+
|
3
|
+
import helpers from 'redux/middlewares/authentication_middleware/helpers';
|
4
|
+
|
5
|
+
describe('Authentication middleware - helpers', () => {
|
6
|
+
describe('#isFailureAction', () => {
|
7
|
+
it('returns true when the action type ends in _FAILURE', () => {
|
8
|
+
const action = { type: 'SOMETHING_FAILURE' };
|
9
|
+
|
10
|
+
expect(helpers.isFailureAction(action)).toEqual(true);
|
11
|
+
});
|
12
|
+
|
13
|
+
it('returns false when the action type does not end in _FAILURE', () => {
|
14
|
+
const action = { type: 'SOMETHING_FAILUR' };
|
15
|
+
|
16
|
+
expect(helpers.isFailureAction(action)).toEqual(false);
|
17
|
+
});
|
18
|
+
});
|
19
|
+
|
20
|
+
describe('#isLoginAction', () => {
|
21
|
+
it('returns true when the action type is the login success action type', () => {
|
22
|
+
const action = { type: 'LOGIN_SUCCESS' };
|
23
|
+
|
24
|
+
expect(helpers.isLoginAction(action)).toEqual(true);
|
25
|
+
});
|
26
|
+
|
27
|
+
it('returns false when the action type is not the login success action type', () => {
|
28
|
+
const action = { type: 'LOGIN_FAILURE' };
|
29
|
+
|
30
|
+
expect(helpers.isLoginAction(action)).toEqual(false);
|
31
|
+
});
|
32
|
+
});
|
33
|
+
|
34
|
+
describe('#isLogoutAction', () => {
|
35
|
+
it('returns true when the action type is the logout success action type', () => {
|
36
|
+
const action = { type: 'LOGOUT_SUCCESS' };
|
37
|
+
|
38
|
+
expect(helpers.isLogoutAction(action)).toEqual(true);
|
39
|
+
});
|
40
|
+
|
41
|
+
it('returns false when the action type is not the logout success action type', () => {
|
42
|
+
const action = { type: 'LOGOUT_FAILURE' };
|
43
|
+
|
44
|
+
expect(helpers.isLogoutAction(action)).toEqual(false);
|
45
|
+
});
|
46
|
+
});
|
47
|
+
|
48
|
+
describe('#isUnauthorizedError', () => {
|
49
|
+
it('returns true when the action payload has an unauthorized http status', () => {
|
50
|
+
const action = {
|
51
|
+
type: 'FOOBAR',
|
52
|
+
payload: {
|
53
|
+
errors: {
|
54
|
+
http_status: 401,
|
55
|
+
},
|
56
|
+
},
|
57
|
+
};
|
58
|
+
|
59
|
+
expect(helpers.isUnauthorizedError(action)).toEqual(true);
|
60
|
+
});
|
61
|
+
|
62
|
+
it('returns false when the action payload does not have an unauthorized http status', () => {
|
63
|
+
const action = {
|
64
|
+
type: 'FOOBAR',
|
65
|
+
payload: {
|
66
|
+
errors: {
|
67
|
+
http_status: 500,
|
68
|
+
},
|
69
|
+
},
|
70
|
+
};
|
71
|
+
|
72
|
+
expect(helpers.isUnauthorizedError(action)).toEqual(false);
|
73
|
+
});
|
74
|
+
|
75
|
+
it('returns false when there is no action payload http status', () => {
|
76
|
+
const action = {
|
77
|
+
type: 'FOOBAR',
|
78
|
+
payload: {},
|
79
|
+
};
|
80
|
+
|
81
|
+
expect(helpers.isUnauthorizedError(action)).toEqual(false);
|
82
|
+
});
|
83
|
+
});
|
84
|
+
});
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import helpers from 'redux/middlewares/authentication_middleware/helpers';
|
2
|
+
import storage from 'storage';
|
3
|
+
|
4
|
+
const authMiddleware = () => next => (action) => {
|
5
|
+
if (helpers.isLoginAction(action)) {
|
6
|
+
storage.setItem('auth_token', action.payload.jwt);
|
7
|
+
}
|
8
|
+
|
9
|
+
if (helpers.isLogoutAction(action)) {
|
10
|
+
storage.removeItem('auth_token');
|
11
|
+
}
|
12
|
+
|
13
|
+
if (helpers.isFailureAction(action) && helpers.isUnauthorizedError(action)) {
|
14
|
+
storage.removeItem('auth_token');
|
15
|
+
}
|
16
|
+
|
17
|
+
return next(action);
|
18
|
+
};
|
19
|
+
|
20
|
+
export default authMiddleware;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { routerMiddleware } from 'react-router-redux';
|
2
|
+
import thunkMiddleware from 'redux-thunk';
|
3
|
+
|
4
|
+
import authenticationMiddleware from 'redux/middlewares/authentication_middleware';
|
5
|
+
import history from 'redux/history';
|
6
|
+
|
7
|
+
export default [
|
8
|
+
thunkMiddleware,
|
9
|
+
routerMiddleware(history),
|
10
|
+
authenticationMiddleware,
|
11
|
+
];
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import API from 'api';
|
2
|
+
import config from 'redux/nodes/app/config';
|
3
|
+
|
4
|
+
const { actionTypes } = config;
|
5
|
+
|
6
|
+
const loginRequest = { type: actionTypes.LOGIN_REQUEST };
|
7
|
+
const loginFailure = { type: actionTypes.LOGIN_FAILURE };
|
8
|
+
const loginSuccess = (jwt) => {
|
9
|
+
return { type: actionTypes.LOGIN_SUCCESS, payload: { jwt } };
|
10
|
+
};
|
11
|
+
|
12
|
+
const logoutRequest = { type: actionTypes.LOGOUT_REQUEST };
|
13
|
+
const logoutFailure = { type: actionTypes.LOGOUT_FAILURE };
|
14
|
+
const logoutSuccess = { type: actionTypes.LOGOUT_FAILURE };
|
15
|
+
|
16
|
+
const login = ({ email, password }) => {
|
17
|
+
return (dispatch) => {
|
18
|
+
dispatch(loginRequest);
|
19
|
+
|
20
|
+
return API.sessions.create({ email, password })
|
21
|
+
.then(({ jwt }) => dispatch(loginSuccess(jwt)))
|
22
|
+
.catch(() => dispatch(loginFailure));
|
23
|
+
};
|
24
|
+
};
|
25
|
+
|
26
|
+
const logout = () => {
|
27
|
+
return (dispatch) => {
|
28
|
+
dispatch(logoutRequest);
|
29
|
+
|
30
|
+
return API.sessions.destroy()
|
31
|
+
.then(() => dispatch(logoutSuccess))
|
32
|
+
.catch(() => dispatch(logoutFailure));
|
33
|
+
};
|
34
|
+
};
|
35
|
+
|
36
|
+
export default { login, logout };
|
@@ -0,0 +1,12 @@
|
|
1
|
+
const actionTypes = {
|
2
|
+
LOGIN_REQUEST: 'LOGIN_REQUEST',
|
3
|
+
LOGIN_SUCCESS: 'LOGIN_SUCCESS',
|
4
|
+
LOGIN_FAILURE: 'LOGIN_FAILURE',
|
5
|
+
LOGOUT_REQUEST: 'LOGOUT_REQUEST',
|
6
|
+
LOGOUT_SUCCESS: 'LOGOUT_SUCCESS',
|
7
|
+
LOGOUT_FAILURE: 'LOGOUT_FAILURE',
|
8
|
+
};
|
9
|
+
|
10
|
+
export default {
|
11
|
+
actionTypes,
|
12
|
+
};
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import config from 'redux/nodes/app/config';
|
2
|
+
import initialState from 'redux/initial_state';
|
3
|
+
|
4
|
+
const { actionTypes } = config;
|
5
|
+
|
6
|
+
export default (state = initialState.app, action) => {
|
7
|
+
switch (action.type) {
|
8
|
+
case actionTypes.LOGIN_REQUEST:
|
9
|
+
case actionTypes.LOGOUT_REQUEST:
|
10
|
+
return {
|
11
|
+
...state,
|
12
|
+
loading: true,
|
13
|
+
};
|
14
|
+
case actionTypes.LOGIN_SUCCESS:
|
15
|
+
return {
|
16
|
+
...state,
|
17
|
+
loading: false,
|
18
|
+
session: { jwt: action.payload.jwt },
|
19
|
+
};
|
20
|
+
case actionTypes.LOGIN_FAILURE:
|
21
|
+
case actionTypes.LOGOUT_SUCCESS:
|
22
|
+
return {
|
23
|
+
...state,
|
24
|
+
session: {},
|
25
|
+
loading: false,
|
26
|
+
};
|
27
|
+
case actionTypes.LOGOUT_FAILURE:
|
28
|
+
return {
|
29
|
+
...state,
|
30
|
+
loading: false,
|
31
|
+
};
|
32
|
+
default:
|
33
|
+
return state;
|
34
|
+
}
|
35
|
+
};
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import expect from 'expect';
|
2
|
+
|
3
|
+
import reducer from 'redux/nodes/app/reducer';
|
4
|
+
import { app as appState } from 'redux/initial_state';
|
5
|
+
|
6
|
+
describe('App - reducer', () => {
|
7
|
+
describe('dispatching the login actions', () => {
|
8
|
+
describe('LOGIN_REQUEST', () => {
|
9
|
+
it('sets the loading boolean to true', () => {
|
10
|
+
const action = { type: 'LOGIN_REQUEST' };
|
11
|
+
|
12
|
+
expect(reducer(appState, action)).toEqual({
|
13
|
+
...appState,
|
14
|
+
loading: true,
|
15
|
+
});
|
16
|
+
});
|
17
|
+
});
|
18
|
+
|
19
|
+
describe('LOGIN_SUCCESS', () => {
|
20
|
+
it('saves the JWT to state', () => {
|
21
|
+
const action = { type: 'LOGIN_SUCCESS', payload: { jwt: 'jwt' } };
|
22
|
+
expect(reducer(appState, action)).toEqual({
|
23
|
+
...appState,
|
24
|
+
session: { jwt: 'jwt' },
|
25
|
+
});
|
26
|
+
});
|
27
|
+
});
|
28
|
+
|
29
|
+
describe('LOGIN_FAILURE', () => {
|
30
|
+
it('removes the JWT from state', () => {
|
31
|
+
const action = { type: 'LOGIN_FAILURE' };
|
32
|
+
const state = { ...appState, session: { jwt: 'jwt' } };
|
33
|
+
|
34
|
+
expect(reducer(state, action)).toEqual({
|
35
|
+
...appState,
|
36
|
+
session: {},
|
37
|
+
});
|
38
|
+
});
|
39
|
+
});
|
40
|
+
});
|
41
|
+
|
42
|
+
describe('dispatching the logout actions', () => {
|
43
|
+
describe('LOGOUT_REQUEST', () => {
|
44
|
+
it('sets the loading boolean to true', () => {
|
45
|
+
const action = { type: 'LOGOUT_REQUEST' };
|
46
|
+
|
47
|
+
expect(reducer(appState, action)).toEqual({
|
48
|
+
...appState,
|
49
|
+
loading: true,
|
50
|
+
});
|
51
|
+
});
|
52
|
+
});
|
53
|
+
|
54
|
+
describe('LOGOUT_SUCCESS', () => {
|
55
|
+
it('removes the JWT from state', () => {
|
56
|
+
const action = { type: 'LOGOUT_SUCCESS' };
|
57
|
+
const state = { ...appState, session: { jwt: 'jwt' } };
|
58
|
+
|
59
|
+
expect(reducer(state, action)).toEqual({
|
60
|
+
...appState,
|
61
|
+
session: {},
|
62
|
+
});
|
63
|
+
});
|
64
|
+
});
|
65
|
+
|
66
|
+
describe('LOGOUT_FAILURE', () => {
|
67
|
+
it('sets the loading boolean to false', () => {
|
68
|
+
const action = { type: 'LOGOUT_FAILURE' };
|
69
|
+
const state = { ...appState, loading: true };
|
70
|
+
|
71
|
+
expect(reducer(state, action)).toEqual({
|
72
|
+
...appState,
|
73
|
+
loading: false,
|
74
|
+
});
|
75
|
+
});
|
76
|
+
});
|
77
|
+
});
|
78
|
+
});
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { combineReducers } from 'redux';
|
2
|
+
import { reducer as formReducer } from 'redux-form';
|
3
|
+
import { routerReducer } from 'react-router-redux';
|
4
|
+
|
5
|
+
import app from 'redux/nodes/app/reducer';
|
6
|
+
|
7
|
+
export default combineReducers({
|
8
|
+
app,
|
9
|
+
form: formReducer,
|
10
|
+
router: routerReducer,
|
11
|
+
});
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import { applyMiddleware, compose, createStore } from 'redux';
|
2
|
+
import Es6ObjectAssign from 'es6-object-assign';
|
3
|
+
import Es6Promise from 'es6-promise';
|
4
|
+
import middlewares from 'redux/middlewares';
|
5
|
+
import reducers from 'redux/reducers';
|
6
|
+
import InitialState from 'redux/initial_state';
|
7
|
+
|
8
|
+
const appliedMiddleware = applyMiddleware(...middlewares);
|
9
|
+
|
10
|
+
// ie polyfills
|
11
|
+
Es6ObjectAssign.polyfill();
|
12
|
+
Es6Promise.polyfill();
|
13
|
+
|
14
|
+
export default createStore(reducers, InitialState, compose(appliedMiddleware));
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import appConstants from 'app_constants';
|
2
|
+
|
3
|
+
const prefix = appConstants.APP_NAME;
|
4
|
+
|
5
|
+
export default {
|
6
|
+
getItem: (key) => {
|
7
|
+
return global.window.localStorage.getItem(`${prefix}:${key}`);
|
8
|
+
},
|
9
|
+
removeItem: (key) => {
|
10
|
+
return global.window.localStorage.removeItem(`${prefix}:${key}`);
|
11
|
+
},
|
12
|
+
setItem: (key, value) => {
|
13
|
+
return global.window.localStorage.setItem(`${prefix}:${key}`, value);
|
14
|
+
},
|
15
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
$black: #000000;
|
@@ -0,0 +1,51 @@
|
|
1
|
+
var jsdom = require('jsdom').JSDOM;
|
2
|
+
var exposedProperties = ['window', 'navigator', 'document'];
|
3
|
+
var Enzyme = require('enzyme');
|
4
|
+
var Adapter = require('enzyme-adapter-react-16');
|
5
|
+
|
6
|
+
Enzyme.configure({ adapter: new Adapter() });
|
7
|
+
|
8
|
+
jsdom = new jsdom('<!DOCTYPE html><html><body></body></html>');
|
9
|
+
|
10
|
+
global.document = jsdom.window.document;
|
11
|
+
global.window = document.defaultView;
|
12
|
+
|
13
|
+
Object.keys(document.defaultView).forEach((property) => {
|
14
|
+
if (typeof global[property] === 'undefined') {
|
15
|
+
exposedProperties.push(property);
|
16
|
+
global[property] = document.defaultView[property];
|
17
|
+
}
|
18
|
+
});
|
19
|
+
|
20
|
+
function mockStorage() {
|
21
|
+
let storage = {};
|
22
|
+
|
23
|
+
return {
|
24
|
+
setItem(key, value = '') {
|
25
|
+
storage[key] = value;
|
26
|
+
},
|
27
|
+
getItem(key) {
|
28
|
+
return storage[key];
|
29
|
+
},
|
30
|
+
removeItem(key) {
|
31
|
+
delete storage[key];
|
32
|
+
},
|
33
|
+
get length() {
|
34
|
+
return Object.keys(storage).length;
|
35
|
+
},
|
36
|
+
key(i) {
|
37
|
+
return Object.keys(storage)[i] || null;
|
38
|
+
},
|
39
|
+
clear () {
|
40
|
+
storage = {};
|
41
|
+
},
|
42
|
+
};
|
43
|
+
}
|
44
|
+
|
45
|
+
global.navigator = {
|
46
|
+
userAgent: 'node.js'
|
47
|
+
};
|
48
|
+
|
49
|
+
global.window = {
|
50
|
+
localStorage: mockStorage(),
|
51
|
+
};
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import React, { Component } from 'react';
|
2
|
+
import { has } from 'lodash';
|
3
|
+
import { Provider } from 'react-redux';
|
4
|
+
import { MemoryRouter } from 'react-router';
|
5
|
+
|
6
|
+
import createStore from 'test/mock_store';
|
7
|
+
|
8
|
+
const defaultStore = { router: { location: { pathname: '/' } } };
|
9
|
+
|
10
|
+
const connectWrapper = (WrappedComponent, store = defaultStore) => {
|
11
|
+
class Wrapper extends Component {
|
12
|
+
render() {
|
13
|
+
const providerStore = has(store, 'getState') ? store : createStore(store);
|
14
|
+
|
15
|
+
return (
|
16
|
+
<Provider store={providerStore}>
|
17
|
+
<MemoryRouter>
|
18
|
+
<WrappedComponent {...this.props} />
|
19
|
+
</MemoryRouter>
|
20
|
+
</Provider>
|
21
|
+
);
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
return Wrapper;
|
26
|
+
};
|
27
|
+
|
28
|
+
export default connectWrapper;
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import nock from 'nock';
|
2
|
+
|
3
|
+
const createRequestMock = ({
|
4
|
+
bearerToken,
|
5
|
+
host,
|
6
|
+
method,
|
7
|
+
params,
|
8
|
+
path,
|
9
|
+
responseStatus = 200,
|
10
|
+
response,
|
11
|
+
}) => {
|
12
|
+
const reqheaders = { Authorization: `Bearer ${bearerToken}` };
|
13
|
+
const req = bearerToken ? nock(host, { reqheaders }) : nock(host);
|
14
|
+
|
15
|
+
if (params) {
|
16
|
+
return req[method](path, JSON.stringify(params))
|
17
|
+
.reply(responseStatus, response);
|
18
|
+
}
|
19
|
+
|
20
|
+
if (responseStatus >= 300) {
|
21
|
+
return req[method](path)
|
22
|
+
.replyWithError(response);
|
23
|
+
}
|
24
|
+
|
25
|
+
return req[method](path)
|
26
|
+
.reply(responseStatus, response);
|
27
|
+
};
|
28
|
+
|
29
|
+
export default createRequestMock;
|