gnarails 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +227 -0
  3. data/.gitignore +5 -0
  4. data/.pronto.yml +2 -0
  5. data/.railsrc +2 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +3 -0
  8. data/.ruby-version +1 -0
  9. data/Brewfile +3 -0
  10. data/CHANGELOG.md +63 -0
  11. data/CODE_OF_CONDUCT.md +74 -0
  12. data/CONTRIBUTING.md +48 -0
  13. data/Gemfile +11 -0
  14. data/Gemfile.lock +213 -0
  15. data/LICENSE +21 -0
  16. data/README.md +136 -0
  17. data/Rakefile +6 -0
  18. data/bin/ci_pronto +6 -0
  19. data/bin/console +14 -0
  20. data/bin/generate-react-test-app.sh +2 -0
  21. data/bin/generate-test-app.sh +22 -0
  22. data/bin/setup +8 -0
  23. data/bin/test-setup.sh +8 -0
  24. data/exe/gnarails +4 -0
  25. data/gnarails.gemspec +34 -0
  26. data/gnarly.rb +395 -0
  27. data/lib/gnarails/cli/application.rb +192 -0
  28. data/lib/gnarails/version.rb +3 -0
  29. data/lib/gnarails.rb +8 -0
  30. data/templates/.circleci/config.yml +66 -0
  31. data/templates/.env.development +5 -0
  32. data/templates/.env.docker/.env.docker-standard +1 -0
  33. data/templates/.env.docker/.env.docker-webpack +2 -0
  34. data/templates/.env.docker-assets +1 -0
  35. data/templates/.env.test +5 -0
  36. data/templates/.gitignore +20 -0
  37. data/templates/.pronto.yml +2 -0
  38. data/templates/.rspec +3 -0
  39. data/templates/.rubocop.yml +3 -0
  40. data/templates/.ruby-version +1 -0
  41. data/templates/.scss-lint.yml +33 -0
  42. data/templates/Dockerfile +20 -0
  43. data/templates/Dockerfile-assets +24 -0
  44. data/templates/README.md +83 -0
  45. data/templates/database.yml +20 -0
  46. data/templates/docker-compose.yml/docker-compose-standard.yml +15 -0
  47. data/templates/docker-compose.yml/docker-compose-webpack.yml +26 -0
  48. data/templates/react/.babelrc +18 -0
  49. data/templates/react/.eslintrc.js +45 -0
  50. data/templates/react/controllers/home_controller.rb +4 -0
  51. data/templates/react/js/Router/Router.jsx +22 -0
  52. data/templates/react/js/Router/index.js +3 -0
  53. data/templates/react/js/api/index.js +17 -0
  54. data/templates/react/js/api/sessions.js +8 -0
  55. data/templates/react/js/app_constants/index.js +6 -0
  56. data/templates/react/js/components/App/App.jsx +15 -0
  57. data/templates/react/js/components/App/App.tests.jsx +15 -0
  58. data/templates/react/js/components/App/_styles.scss +3 -0
  59. data/templates/react/js/components/App/index.js +3 -0
  60. data/templates/react/js/index.scss +2 -0
  61. data/templates/react/js/packs/main.jsx +13 -0
  62. data/templates/react/js/redux/entity_getter.js +7 -0
  63. data/templates/react/js/redux/history.js +5 -0
  64. data/templates/react/js/redux/initial_state.js +5 -0
  65. data/templates/react/js/redux/middlewares/authentication_middleware/authentication_middleware.tests.js +109 -0
  66. data/templates/react/js/redux/middlewares/authentication_middleware/helpers.js +21 -0
  67. data/templates/react/js/redux/middlewares/authentication_middleware/helpers.tests.js +84 -0
  68. data/templates/react/js/redux/middlewares/authentication_middleware/index.js +20 -0
  69. data/templates/react/js/redux/middlewares/index.js +11 -0
  70. data/templates/react/js/redux/nodes/app/actions.js +36 -0
  71. data/templates/react/js/redux/nodes/app/config.js +12 -0
  72. data/templates/react/js/redux/nodes/app/reducer.js +35 -0
  73. data/templates/react/js/redux/nodes/app/reducer.tests.js +78 -0
  74. data/templates/react/js/redux/reducers.js +11 -0
  75. data/templates/react/js/redux/store.js +14 -0
  76. data/templates/react/js/storage.js +15 -0
  77. data/templates/react/js/styles/_variables.scss +1 -0
  78. data/templates/react/js/test/.setup.js +51 -0
  79. data/templates/react/js/test/connect_wrapper.jsx +28 -0
  80. data/templates/react/js/test/create_request_mock.js +29 -0
  81. data/templates/react/js/test/mock_store.js +12 -0
  82. data/templates/react/layout.html.erb +16 -0
  83. data/templates/react/rails_routes.rb +5 -0
  84. data/templates/react/views/home/default.html.erb +1 -0
  85. data/templates/script/brakeman +15 -0
  86. data/templates/script/ci_pronto +6 -0
  87. data/templates/spec/support/factory_bot.rb +5 -0
  88. data/templates/spec/support/system_test_configuration.rb +13 -0
  89. data/test-app/app/controllers/job_postings_controller.rb +5 -0
  90. data/test-app/app/models/comment.rb +3 -0
  91. data/test-app/app/models/job_posting.rb +20 -0
  92. data/test-app/app/views/job_postings/index.html.erb +23 -0
  93. data/test-app/app/views/layouts/application.html.erb +14 -0
  94. data/test-app/config/routes.rb +5 -0
  95. data/test-app/db/migrate/20170918002433_create_job_postings.rb +12 -0
  96. data/test-app/db/migrate/20170918002455_create_comments.rb +10 -0
  97. data/test-app/spec/factories/job_postings.rb +5 -0
  98. data/test-app/spec/models/job_posting_spec.rb +21 -0
  99. data/test-app/spec/requests/status_spec.rb +11 -0
  100. data/test-app/spec/system/viewing_all_job_postings_spec.rb +36 -0
  101. metadata +270 -0
@@ -0,0 +1,8 @@
1
+ export default () => {
2
+ return {
3
+ create: ({ email, password }) => {
4
+ return Promise.resolve({ email, password, jwt: 'jwt' });
5
+ },
6
+ destroy: () => Promise.resolve({}),
7
+ };
8
+ };
@@ -0,0 +1,6 @@
1
+ const { origin } = global.window;
2
+
3
+ export default {
4
+ API_HOST: `${origin}/api`,
5
+ APP_NAME: 'CHANGE_ME',
6
+ };
@@ -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,3 @@
1
+ .app {
2
+ color: $black;
3
+ }
@@ -0,0 +1,3 @@
1
+ import App from './App';
2
+
3
+ export default App;
@@ -0,0 +1,2 @@
1
+ @import 'styles/variables';
2
+ @import 'components/App/_styles.scss';
@@ -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,7 @@
1
+ import entityGetter from 'react-entity-getter';
2
+
3
+ const pathToEntities = (entityName) => {
4
+ return `entities.${entityName}.data`;
5
+ };
6
+
7
+ export default entityGetter(pathToEntities);
@@ -0,0 +1,5 @@
1
+ import createHistory from 'history/createBrowserHistory';
2
+
3
+ global.window.gnarHistory = global.window.gnarHistory || createHistory();
4
+
5
+ export default global.window.gnarHistory;
@@ -0,0 +1,5 @@
1
+ export const app = { session: {}, loading: false };
2
+
3
+ export default {
4
+ app,
5
+ };
@@ -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;