mirror of
https://github.com/Telecominfraproject/wlan-cloud-ui.git
synced 2025-11-01 19:27:51 +00:00
switched to use Apollo
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* AppReducer
|
||||
*
|
||||
* The reducer takes care of our data. Using actions, we can
|
||||
* update our application state. To add a new action,
|
||||
* add it to the switch statement in the reducer function
|
||||
*
|
||||
*/
|
||||
|
||||
import produce from 'immer';
|
||||
|
||||
// The initial state of the App
|
||||
export const initialState = {
|
||||
loading: false,
|
||||
error: false,
|
||||
currentUser: false,
|
||||
};
|
||||
|
||||
/* eslint-disable default-case, no-param-reassign */
|
||||
const appReducer = (state = initialState, action) =>
|
||||
produce(state, () => {
|
||||
switch (action.type) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
export default appReducer;
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* The global state selectors
|
||||
*/
|
||||
|
||||
import { createSelector } from 'reselect';
|
||||
import { initialState } from './reducer';
|
||||
|
||||
const selectGlobal = state => state.global || initialState;
|
||||
|
||||
const selectRouter = state => state.router;
|
||||
|
||||
const makeSelectCurrentUser = () =>
|
||||
createSelector(
|
||||
selectGlobal,
|
||||
globalState => globalState.currentUser,
|
||||
);
|
||||
|
||||
const makeSelectLoading = () =>
|
||||
createSelector(
|
||||
selectGlobal,
|
||||
globalState => globalState.loading,
|
||||
);
|
||||
|
||||
const makeSelectError = () =>
|
||||
createSelector(
|
||||
selectGlobal,
|
||||
globalState => globalState.error,
|
||||
);
|
||||
|
||||
const makeSelectLocation = () =>
|
||||
createSelector(
|
||||
selectRouter,
|
||||
routerState => routerState.location,
|
||||
);
|
||||
|
||||
export {
|
||||
selectGlobal,
|
||||
makeSelectCurrentUser,
|
||||
makeSelectLoading,
|
||||
makeSelectError,
|
||||
makeSelectLocation,
|
||||
};
|
||||
@@ -1,31 +1,23 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose } from 'redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { AppLayout as Layout } from 'cu-ui';
|
||||
|
||||
import { makeSelectLocation } from 'containers/App/selectors';
|
||||
|
||||
import logo from 'images/logo-light.png';
|
||||
import logoMobile from 'images/logoxmobile.jpg';
|
||||
|
||||
const MasterLayout = ({ children, locationState }) => (
|
||||
<Layout logo={logo} logoMobile={logoMobile} onLogout={() => {}} locationState={locationState}>
|
||||
const MasterLayout = ({ children }) => {
|
||||
const location = useLocation();
|
||||
return (
|
||||
<Layout company="ConnectUs" logo={logo} logoMobile={logoMobile} onLogout={() => {}} locationState={location}>
|
||||
{children}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
MasterLayout.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
locationState: PropTypes.instanceOf(Object).isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = createStructuredSelector({
|
||||
locationState: makeSelectLocation(),
|
||||
});
|
||||
|
||||
const withConnect = connect(mapStateToProps);
|
||||
|
||||
export default compose(withConnect)(MasterLayout);
|
||||
export default MasterLayout;
|
||||
|
||||
22
app/index.js
22
app/index.js
@@ -1,27 +1,27 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ConnectedRouter } from 'connected-react-router';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import ApolloClient from 'apollo-boost';
|
||||
import { ApolloProvider } from '@apollo/react-hooks';
|
||||
|
||||
import 'styles/antd.less';
|
||||
import 'styles/index.scss';
|
||||
|
||||
import App from 'containers/App';
|
||||
import configureStore from 'store';
|
||||
import history from 'utils/history';
|
||||
|
||||
// Create redux store with history
|
||||
const initialState = {};
|
||||
const store = configureStore(initialState, history);
|
||||
const MOUNT_NODE = document.getElementById('root');
|
||||
|
||||
const client = new ApolloClient({
|
||||
// uri: "https://48p1r2roz4.sse.codesandbox.io"
|
||||
});
|
||||
|
||||
const render = () => {
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<ApolloProvider client={client}>
|
||||
<Router>
|
||||
<App />
|
||||
</ConnectedRouter>
|
||||
</Provider>,
|
||||
</Router>
|
||||
</ApolloProvider>,
|
||||
MOUNT_NODE
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Combine all reducers in this file and export the combined reducers.
|
||||
*/
|
||||
|
||||
import { combineReducers } from 'redux';
|
||||
import { connectRouter } from 'connected-react-router';
|
||||
|
||||
import globalReducer from 'containers/App/reducer';
|
||||
import history from 'utils/history';
|
||||
|
||||
/**
|
||||
* Merges the main reducer with the router state and dynamically injected reducers
|
||||
*/
|
||||
|
||||
export default function createReducer(injectedReducers = {}) {
|
||||
const rootReducer = combineReducers({
|
||||
global: globalReducer,
|
||||
router: connectRouter(history),
|
||||
...injectedReducers,
|
||||
});
|
||||
|
||||
return rootReducer;
|
||||
}
|
||||
55
app/store.js
55
app/store.js
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Create the store with dynamic reducers
|
||||
*/
|
||||
|
||||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import { routerMiddleware } from 'connected-react-router';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import createReducer from './reducers';
|
||||
|
||||
export default function configureStore(initialState = {}, history) {
|
||||
let composeEnhancers = compose;
|
||||
const reduxSagaMonitorOptions = {};
|
||||
|
||||
// If Redux Dev Tools and Saga Dev Tools Extensions are installed, enable them
|
||||
/* istanbul ignore next */
|
||||
if (process.env.NODE_ENV !== 'production' && typeof window === 'object') {
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__)
|
||||
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
|
||||
|
||||
// NOTE: Uncomment the code below to restore support for Redux Saga
|
||||
// Dev Tools once it supports redux-saga version 1.x.x
|
||||
// if (window.__SAGA_MONITOR_EXTENSION__)
|
||||
// reduxSagaMonitorOptions = {
|
||||
// sagaMonitor: window.__SAGA_MONITOR_EXTENSION__,
|
||||
// };
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
|
||||
|
||||
// Create the store with two middlewares
|
||||
// 1. sagaMiddleware: Makes redux-sagas work
|
||||
// 2. routerMiddleware: Syncs the location/URL path to the state
|
||||
const middlewares = [sagaMiddleware, routerMiddleware(history)];
|
||||
|
||||
const enhancers = [applyMiddleware(...middlewares)];
|
||||
|
||||
const store = createStore(createReducer(), initialState, composeEnhancers(...enhancers));
|
||||
|
||||
// Extensions
|
||||
store.runSaga = sagaMiddleware.run;
|
||||
store.injectedReducers = {}; // Reducer registry
|
||||
store.injectedSagas = {}; // Saga registry
|
||||
|
||||
// Make reducers hot reloadable, see http://mxs.is/googmo
|
||||
/* istanbul ignore next */
|
||||
if (module.hot) {
|
||||
module.hot.accept('./reducers', () => {
|
||||
store.replaceReducer(createReducer(store.injectedReducers));
|
||||
});
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Test store addons
|
||||
*/
|
||||
|
||||
import { browserHistory } from 'react-router-dom';
|
||||
import configureStore from '../configureStore';
|
||||
|
||||
describe('configureStore', () => {
|
||||
let store;
|
||||
|
||||
beforeAll(() => {
|
||||
store = configureStore({}, browserHistory);
|
||||
});
|
||||
|
||||
describe('injectedReducers', () => {
|
||||
it('should contain an object for reducers', () => {
|
||||
expect(typeof store.injectedReducers).toBe('object');
|
||||
});
|
||||
});
|
||||
|
||||
describe('injectedSagas', () => {
|
||||
it('should contain an object for sagas', () => {
|
||||
expect(typeof store.injectedSagas).toBe('object');
|
||||
});
|
||||
});
|
||||
|
||||
describe('runSaga', () => {
|
||||
it('should contain a hook for `sagaMiddleware.run`', () => {
|
||||
expect(typeof store.runSaga).toBe('function');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('configureStore params', () => {
|
||||
it('should call window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__', () => {
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const compose = jest.fn();
|
||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = () => compose;
|
||||
configureStore(undefined, browserHistory);
|
||||
expect(compose).toHaveBeenCalled();
|
||||
/* eslint-enable */
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import { conformsTo, isFunction, isObject } from 'lodash';
|
||||
import invariant from 'invariant';
|
||||
|
||||
/**
|
||||
* Validate the shape of redux store
|
||||
*/
|
||||
export default function checkStore(store) {
|
||||
const shape = {
|
||||
dispatch: isFunction,
|
||||
subscribe: isFunction,
|
||||
getState: isFunction,
|
||||
replaceReducer: isFunction,
|
||||
runSaga: isFunction,
|
||||
injectedReducers: isObject,
|
||||
injectedSagas: isObject,
|
||||
};
|
||||
invariant(
|
||||
conformsTo(store, shape),
|
||||
'(app/utils...) injectors: Expected a valid redux store',
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export const RESTART_ON_REMOUNT = '@@saga-injector/restart-on-remount';
|
||||
export const DAEMON = '@@saga-injector/daemon';
|
||||
export const ONCE_TILL_UNMOUNT = '@@saga-injector/once-till-unmount';
|
||||
@@ -1,4 +0,0 @@
|
||||
import { createBrowserHistory } from 'history';
|
||||
|
||||
const history = createBrowserHistory();
|
||||
export default history;
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from 'react';
|
||||
import hoistNonReactStatics from 'hoist-non-react-statics';
|
||||
import { ReactReduxContext } from 'react-redux';
|
||||
|
||||
import getInjectors from './reducerInjectors';
|
||||
|
||||
/**
|
||||
* Dynamically injects a reducer
|
||||
*
|
||||
* @param {string} key A key of the reducer
|
||||
* @param {function} reducer A reducer that will be injected
|
||||
*
|
||||
*/
|
||||
export default ({ key, reducer }) => WrappedComponent => {
|
||||
class ReducerInjector extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
getInjectors(context.store).injectReducer(key, reducer);
|
||||
}
|
||||
|
||||
static WrappedComponent = WrappedComponent;
|
||||
|
||||
render() {
|
||||
return <WrappedComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
ReducerInjector.displayName = `withReducer(${WrappedComponent.displayName ||
|
||||
WrappedComponent.name ||
|
||||
'Component'})`;
|
||||
ReducerInjector.contextType = ReactReduxContext;
|
||||
|
||||
return hoistNonReactStatics(ReducerInjector, WrappedComponent);
|
||||
};
|
||||
|
||||
const useInjectReducer = ({ key, reducer }) => {
|
||||
const context = React.useContext(ReactReduxContext);
|
||||
React.useEffect(() => {
|
||||
getInjectors(context.store).injectReducer(key, reducer);
|
||||
}, []);
|
||||
};
|
||||
|
||||
export { useInjectReducer };
|
||||
@@ -1,61 +0,0 @@
|
||||
import React from 'react';
|
||||
import hoistNonReactStatics from 'hoist-non-react-statics';
|
||||
import { ReactReduxContext } from 'react-redux';
|
||||
|
||||
import getInjectors from './sagaInjectors';
|
||||
|
||||
/**
|
||||
* Dynamically injects a saga, passes component's props as saga arguments
|
||||
*
|
||||
* @param {string} key A key of the saga
|
||||
* @param {function} saga A root saga that will be injected
|
||||
* @param {string} [mode] By default (constants.DAEMON) the saga will be started
|
||||
* on component mount and never canceled or started again. Another two options:
|
||||
* - constants.RESTART_ON_REMOUNT — the saga will be started on component mount and
|
||||
* cancelled with `task.cancel()` on component unmount for improved performance,
|
||||
* - constants.ONCE_TILL_UNMOUNT — behaves like 'RESTART_ON_REMOUNT' but never runs it again.
|
||||
*
|
||||
*/
|
||||
export default ({ key, saga, mode }) => WrappedComponent => {
|
||||
class InjectSaga extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.injectors = getInjectors(context.store);
|
||||
|
||||
this.injectors.injectSaga(key, { saga, mode }, this.props);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.injectors.ejectSaga(key);
|
||||
}
|
||||
|
||||
static WrappedComponent = WrappedComponent;
|
||||
|
||||
render() {
|
||||
return <WrappedComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
InjectSaga.contextType = ReactReduxContext;
|
||||
|
||||
InjectSaga.displayName = `withSaga(${WrappedComponent.displayName ||
|
||||
WrappedComponent.name ||
|
||||
'Component'})`;
|
||||
|
||||
return hoistNonReactStatics(InjectSaga, WrappedComponent);
|
||||
};
|
||||
|
||||
const useInjectSaga = ({ key, saga, mode }) => {
|
||||
const context = React.useContext(ReactReduxContext);
|
||||
React.useEffect(() => {
|
||||
const injectors = getInjectors(context.store);
|
||||
injectors.injectSaga(key, { saga, mode });
|
||||
|
||||
return () => {
|
||||
injectors.ejectSaga(key);
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
export { useInjectSaga };
|
||||
@@ -1,34 +0,0 @@
|
||||
import invariant from 'invariant';
|
||||
import { isEmpty, isFunction, isString } from 'lodash';
|
||||
|
||||
import checkStore from './checkStore';
|
||||
import createReducer from '../reducers';
|
||||
|
||||
export function injectReducerFactory(store, isValid) {
|
||||
return function injectReducer(key, reducer) {
|
||||
if (!isValid) checkStore(store);
|
||||
|
||||
invariant(
|
||||
isString(key) && !isEmpty(key) && isFunction(reducer),
|
||||
'(app/utils...) injectReducer: Expected `reducer` to be a reducer function',
|
||||
);
|
||||
|
||||
// Check `store.injectedReducers[key] === reducer` for hot reloading when a key is the same but a reducer is different
|
||||
if (
|
||||
Reflect.has(store.injectedReducers, key) &&
|
||||
store.injectedReducers[key] === reducer
|
||||
)
|
||||
return;
|
||||
|
||||
store.injectedReducers[key] = reducer; // eslint-disable-line no-param-reassign
|
||||
store.replaceReducer(createReducer(store.injectedReducers));
|
||||
};
|
||||
}
|
||||
|
||||
export default function getInjectors(store) {
|
||||
checkStore(store);
|
||||
|
||||
return {
|
||||
injectReducer: injectReducerFactory(store, true),
|
||||
};
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import invariant from 'invariant';
|
||||
import { isEmpty, isFunction, isString, conformsTo } from 'lodash';
|
||||
|
||||
import checkStore from './checkStore';
|
||||
import { DAEMON, ONCE_TILL_UNMOUNT, RESTART_ON_REMOUNT } from './constants';
|
||||
|
||||
const allowedModes = [RESTART_ON_REMOUNT, DAEMON, ONCE_TILL_UNMOUNT];
|
||||
|
||||
const checkKey = key =>
|
||||
invariant(
|
||||
isString(key) && !isEmpty(key),
|
||||
'(app/utils...) injectSaga: Expected `key` to be a non empty string',
|
||||
);
|
||||
|
||||
const checkDescriptor = descriptor => {
|
||||
const shape = {
|
||||
saga: isFunction,
|
||||
mode: mode => isString(mode) && allowedModes.includes(mode),
|
||||
};
|
||||
invariant(
|
||||
conformsTo(descriptor, shape),
|
||||
'(app/utils...) injectSaga: Expected a valid saga descriptor',
|
||||
);
|
||||
};
|
||||
|
||||
export function injectSagaFactory(store, isValid) {
|
||||
return function injectSaga(key, descriptor = {}, args) {
|
||||
if (!isValid) checkStore(store);
|
||||
|
||||
const newDescriptor = {
|
||||
...descriptor,
|
||||
mode: descriptor.mode || DAEMON,
|
||||
};
|
||||
const { saga, mode } = newDescriptor;
|
||||
|
||||
checkKey(key);
|
||||
checkDescriptor(newDescriptor);
|
||||
|
||||
let hasSaga = Reflect.has(store.injectedSagas, key);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const oldDescriptor = store.injectedSagas[key];
|
||||
// enable hot reloading of daemon and once-till-unmount sagas
|
||||
if (hasSaga && oldDescriptor.saga !== saga) {
|
||||
oldDescriptor.task.cancel();
|
||||
hasSaga = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!hasSaga ||
|
||||
(hasSaga && mode !== DAEMON && mode !== ONCE_TILL_UNMOUNT)
|
||||
) {
|
||||
/* eslint-disable no-param-reassign */
|
||||
store.injectedSagas[key] = {
|
||||
...newDescriptor,
|
||||
task: store.runSaga(saga, args),
|
||||
};
|
||||
/* eslint-enable no-param-reassign */
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function ejectSagaFactory(store, isValid) {
|
||||
return function ejectSaga(key) {
|
||||
if (!isValid) checkStore(store);
|
||||
|
||||
checkKey(key);
|
||||
|
||||
if (Reflect.has(store.injectedSagas, key)) {
|
||||
const descriptor = store.injectedSagas[key];
|
||||
if (descriptor.mode && descriptor.mode !== DAEMON) {
|
||||
descriptor.task.cancel();
|
||||
// Clean up in production; in development we need `descriptor.saga` for hot reloading
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// Need some value to be able to detect `ONCE_TILL_UNMOUNT` sagas in `injectSaga`
|
||||
store.injectedSagas[key] = 'done'; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default function getInjectors(store) {
|
||||
checkStore(store);
|
||||
|
||||
return {
|
||||
injectSaga: injectSagaFactory(store, true),
|
||||
ejectSaga: ejectSagaFactory(store, true),
|
||||
};
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Test injectors
|
||||
*/
|
||||
|
||||
import checkStore from '../checkStore';
|
||||
|
||||
describe('checkStore', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = {
|
||||
dispatch: () => {},
|
||||
subscribe: () => {},
|
||||
getState: () => {},
|
||||
replaceReducer: () => {},
|
||||
runSaga: () => {},
|
||||
injectedReducers: {},
|
||||
injectedSagas: {},
|
||||
};
|
||||
});
|
||||
|
||||
it('should not throw if passed valid store shape', () => {
|
||||
expect(() => checkStore(store)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw if passed invalid store shape', () => {
|
||||
expect(() => checkStore({})).toThrow();
|
||||
expect(() => checkStore({ ...store, injectedSagas: null })).toThrow();
|
||||
expect(() => checkStore({ ...store, injectedReducers: null })).toThrow();
|
||||
expect(() => checkStore({ ...store, runSaga: null })).toThrow();
|
||||
expect(() => checkStore({ ...store, replaceReducer: null })).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -1,98 +0,0 @@
|
||||
/**
|
||||
* Test injectors
|
||||
*/
|
||||
|
||||
import { memoryHistory } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { render } from 'react-testing-library';
|
||||
|
||||
import configureStore from '../../configureStore';
|
||||
import injectReducer, { useInjectReducer } from '../injectReducer';
|
||||
import * as reducerInjectors from '../reducerInjectors';
|
||||
|
||||
// Fixtures
|
||||
const Component = () => null;
|
||||
|
||||
const reducer = s => s;
|
||||
|
||||
describe('injectReducer decorator', () => {
|
||||
let store;
|
||||
let injectors;
|
||||
let ComponentWithReducer;
|
||||
|
||||
beforeAll(() => {
|
||||
reducerInjectors.default = jest.fn().mockImplementation(() => injectors);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
store = configureStore({}, memoryHistory);
|
||||
injectors = {
|
||||
injectReducer: jest.fn(),
|
||||
};
|
||||
ComponentWithReducer = injectReducer({ key: 'test', reducer })(Component);
|
||||
reducerInjectors.default.mockClear();
|
||||
});
|
||||
|
||||
it('should inject a given reducer', () => {
|
||||
renderer.create(
|
||||
<Provider store={store}>
|
||||
<ComponentWithReducer />
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
expect(injectors.injectReducer).toHaveBeenCalledTimes(1);
|
||||
expect(injectors.injectReducer).toHaveBeenCalledWith('test', reducer);
|
||||
});
|
||||
|
||||
it('should set a correct display name', () => {
|
||||
expect(ComponentWithReducer.displayName).toBe('withReducer(Component)');
|
||||
expect(
|
||||
injectReducer({ key: 'test', reducer })(() => null).displayName,
|
||||
).toBe('withReducer(Component)');
|
||||
});
|
||||
|
||||
it('should propagate props', () => {
|
||||
const props = { testProp: 'test' };
|
||||
const renderedComponent = renderer.create(
|
||||
<Provider store={store}>
|
||||
<ComponentWithReducer {...props} />
|
||||
</Provider>,
|
||||
);
|
||||
const {
|
||||
props: { children },
|
||||
} = renderedComponent.getInstance();
|
||||
|
||||
expect(children.props).toEqual(props);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useInjectReducer hook', () => {
|
||||
let store;
|
||||
let injectors;
|
||||
let ComponentWithReducer;
|
||||
|
||||
beforeAll(() => {
|
||||
injectors = {
|
||||
injectReducer: jest.fn(),
|
||||
};
|
||||
reducerInjectors.default = jest.fn().mockImplementation(() => injectors);
|
||||
store = configureStore({}, memoryHistory);
|
||||
ComponentWithReducer = () => {
|
||||
useInjectReducer({ key: 'test', reducer });
|
||||
return null;
|
||||
};
|
||||
});
|
||||
|
||||
it('should inject a given reducer', () => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<ComponentWithReducer />
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
expect(injectors.injectReducer).toHaveBeenCalledTimes(1);
|
||||
expect(injectors.injectReducer).toHaveBeenCalledWith('test', reducer);
|
||||
});
|
||||
});
|
||||
@@ -1,149 +0,0 @@
|
||||
/**
|
||||
* Test injectors
|
||||
*/
|
||||
|
||||
import { memoryHistory } from 'react-router-dom';
|
||||
import { put } from 'redux-saga/effects';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { render } from 'react-testing-library';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import configureStore from '../../configureStore';
|
||||
import injectSaga, { useInjectSaga } from '../injectSaga';
|
||||
import * as sagaInjectors from '../sagaInjectors';
|
||||
|
||||
// Fixtures
|
||||
const Component = () => null;
|
||||
|
||||
function* testSaga() {
|
||||
yield put({ type: 'TEST', payload: 'yup' });
|
||||
}
|
||||
|
||||
describe('injectSaga decorator', () => {
|
||||
let store;
|
||||
let injectors;
|
||||
let ComponentWithSaga;
|
||||
|
||||
beforeAll(() => {
|
||||
sagaInjectors.default = jest.fn().mockImplementation(() => injectors);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
store = configureStore({}, memoryHistory);
|
||||
injectors = {
|
||||
injectSaga: jest.fn(),
|
||||
ejectSaga: jest.fn(),
|
||||
};
|
||||
ComponentWithSaga = injectSaga({
|
||||
key: 'test',
|
||||
saga: testSaga,
|
||||
mode: 'testMode',
|
||||
})(Component);
|
||||
sagaInjectors.default.mockClear();
|
||||
});
|
||||
|
||||
it('should inject given saga, mode, and props', () => {
|
||||
const props = { test: 'test' };
|
||||
renderer.create(
|
||||
<Provider store={store}>
|
||||
<ComponentWithSaga {...props} />
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
expect(injectors.injectSaga).toHaveBeenCalledTimes(1);
|
||||
expect(injectors.injectSaga).toHaveBeenCalledWith(
|
||||
'test',
|
||||
{ saga: testSaga, mode: 'testMode' },
|
||||
props,
|
||||
);
|
||||
});
|
||||
|
||||
it('should eject on unmount with a correct saga key', () => {
|
||||
const props = { test: 'test' };
|
||||
const renderedComponent = renderer.create(
|
||||
<Provider store={store}>
|
||||
<ComponentWithSaga {...props} />
|
||||
</Provider>,
|
||||
);
|
||||
renderedComponent.unmount();
|
||||
|
||||
expect(injectors.ejectSaga).toHaveBeenCalledTimes(1);
|
||||
expect(injectors.ejectSaga).toHaveBeenCalledWith('test');
|
||||
});
|
||||
|
||||
it('should set a correct display name', () => {
|
||||
expect(ComponentWithSaga.displayName).toBe('withSaga(Component)');
|
||||
expect(
|
||||
injectSaga({ key: 'test', saga: testSaga })(() => null).displayName,
|
||||
).toBe('withSaga(Component)');
|
||||
});
|
||||
|
||||
it('should propagate props', () => {
|
||||
const props = { testProp: 'test' };
|
||||
const renderedComponent = renderer.create(
|
||||
<Provider store={store}>
|
||||
<ComponentWithSaga {...props} />
|
||||
</Provider>,
|
||||
);
|
||||
const {
|
||||
props: { children },
|
||||
} = renderedComponent.getInstance();
|
||||
expect(children.props).toEqual(props);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useInjectSaga hook', () => {
|
||||
let store;
|
||||
let injectors;
|
||||
let ComponentWithSaga;
|
||||
|
||||
beforeAll(() => {
|
||||
sagaInjectors.default = jest.fn().mockImplementation(() => injectors);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
store = configureStore({}, memoryHistory);
|
||||
injectors = {
|
||||
injectSaga: jest.fn(),
|
||||
ejectSaga: jest.fn(),
|
||||
};
|
||||
ComponentWithSaga = () => {
|
||||
useInjectSaga({
|
||||
key: 'test',
|
||||
saga: testSaga,
|
||||
mode: 'testMode',
|
||||
});
|
||||
return null;
|
||||
};
|
||||
sagaInjectors.default.mockClear();
|
||||
});
|
||||
|
||||
it('should inject given saga and mode', () => {
|
||||
const props = { test: 'test' };
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<ComponentWithSaga {...props} />
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
expect(injectors.injectSaga).toHaveBeenCalledTimes(1);
|
||||
expect(injectors.injectSaga).toHaveBeenCalledWith('test', {
|
||||
saga: testSaga,
|
||||
mode: 'testMode',
|
||||
});
|
||||
});
|
||||
|
||||
it('should eject on unmount with a correct saga key', () => {
|
||||
const props = { test: 'test' };
|
||||
const { unmount } = render(
|
||||
<Provider store={store}>
|
||||
<ComponentWithSaga {...props} />
|
||||
</Provider>,
|
||||
);
|
||||
unmount();
|
||||
|
||||
expect(injectors.ejectSaga).toHaveBeenCalledTimes(1);
|
||||
expect(injectors.ejectSaga).toHaveBeenCalledWith('test');
|
||||
});
|
||||
});
|
||||
@@ -1,100 +0,0 @@
|
||||
/**
|
||||
* Test injectors
|
||||
*/
|
||||
|
||||
import produce from 'immer';
|
||||
import { memoryHistory } from 'react-router-dom';
|
||||
import identity from 'lodash/identity';
|
||||
|
||||
import configureStore from '../../configureStore';
|
||||
|
||||
import getInjectors, { injectReducerFactory } from '../reducerInjectors';
|
||||
|
||||
// Fixtures
|
||||
|
||||
const initialState = { reduced: 'soon' };
|
||||
|
||||
/* eslint-disable default-case, no-param-reassign */
|
||||
const reducer = (state = initialState, action) =>
|
||||
produce(state, draft => {
|
||||
switch (action.type) {
|
||||
case 'TEST':
|
||||
draft.reduced = action.payload;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
describe('reducer injectors', () => {
|
||||
let store;
|
||||
let injectReducer;
|
||||
|
||||
describe('getInjectors', () => {
|
||||
beforeEach(() => {
|
||||
store = configureStore({}, memoryHistory);
|
||||
});
|
||||
|
||||
it('should return injectors', () => {
|
||||
expect(getInjectors(store)).toEqual(
|
||||
expect.objectContaining({
|
||||
injectReducer: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if passed invalid store shape', () => {
|
||||
Reflect.deleteProperty(store, 'dispatch');
|
||||
|
||||
expect(() => getInjectors(store)).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('injectReducer helper', () => {
|
||||
beforeEach(() => {
|
||||
store = configureStore({}, memoryHistory);
|
||||
injectReducer = injectReducerFactory(store, true);
|
||||
});
|
||||
|
||||
it('should check a store if the second argument is falsy', () => {
|
||||
const inject = injectReducerFactory({});
|
||||
|
||||
expect(() => inject('test', reducer)).toThrow();
|
||||
});
|
||||
|
||||
it('it should not check a store if the second argument is true', () => {
|
||||
Reflect.deleteProperty(store, 'dispatch');
|
||||
|
||||
expect(() => injectReducer('test', reducer)).not.toThrow();
|
||||
});
|
||||
|
||||
it("should validate a reducer and reducer's key", () => {
|
||||
expect(() => injectReducer('', reducer)).toThrow();
|
||||
expect(() => injectReducer(1, reducer)).toThrow();
|
||||
expect(() => injectReducer(1, 1)).toThrow();
|
||||
});
|
||||
|
||||
it('given a store, it should provide a function to inject a reducer', () => {
|
||||
injectReducer('test', reducer);
|
||||
|
||||
const actual = store.getState().test;
|
||||
const expected = initialState;
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should not assign reducer if already existing', () => {
|
||||
store.replaceReducer = jest.fn();
|
||||
injectReducer('test', reducer);
|
||||
injectReducer('test', reducer);
|
||||
|
||||
expect(store.replaceReducer).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should assign reducer if different implementation for hot reloading', () => {
|
||||
store.replaceReducer = jest.fn();
|
||||
injectReducer('test', reducer);
|
||||
injectReducer('test', identity);
|
||||
|
||||
expect(store.replaceReducer).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* Test the request function
|
||||
*/
|
||||
|
||||
import 'whatwg-fetch';
|
||||
import request from '../request';
|
||||
|
||||
describe('request', () => {
|
||||
// Before each test, stub the fetch function
|
||||
beforeEach(() => {
|
||||
window.fetch = jest.fn();
|
||||
});
|
||||
|
||||
describe('stubbing successful response', () => {
|
||||
// Before each test, pretend we got a successful response
|
||||
beforeEach(() => {
|
||||
const res = new Response('{"hello":"world"}', {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
window.fetch.mockReturnValue(Promise.resolve(res));
|
||||
});
|
||||
|
||||
it('should format the response correctly', done => {
|
||||
request('/thisurliscorrect')
|
||||
.catch(done)
|
||||
.then(json => {
|
||||
expect(json.hello).toBe('world');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('stubbing 204 response', () => {
|
||||
// Before each test, pretend we got a successful response
|
||||
beforeEach(() => {
|
||||
const res = new Response('', {
|
||||
status: 204,
|
||||
statusText: 'No Content',
|
||||
});
|
||||
|
||||
window.fetch.mockReturnValue(Promise.resolve(res));
|
||||
});
|
||||
|
||||
it('should return null on 204 response', done => {
|
||||
request('/thisurliscorrect')
|
||||
.catch(done)
|
||||
.then(json => {
|
||||
expect(json).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('stubbing error response', () => {
|
||||
// Before each test, pretend we got an unsuccessful response
|
||||
beforeEach(() => {
|
||||
const res = new Response('', {
|
||||
status: 404,
|
||||
statusText: 'Not Found',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
window.fetch.mockReturnValue(Promise.resolve(res));
|
||||
});
|
||||
|
||||
it('should catch errors', done => {
|
||||
request('/thisdoesntexist').catch(err => {
|
||||
expect(err.response.status).toBe(404);
|
||||
expect(err.response.statusText).toBe('Not Found');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,231 +0,0 @@
|
||||
/**
|
||||
* Test injectors
|
||||
*/
|
||||
|
||||
import { memoryHistory } from 'react-router-dom';
|
||||
import { put } from 'redux-saga/effects';
|
||||
|
||||
import configureStore from '../../configureStore';
|
||||
import getInjectors, {
|
||||
injectSagaFactory,
|
||||
ejectSagaFactory,
|
||||
} from '../sagaInjectors';
|
||||
import { DAEMON, ONCE_TILL_UNMOUNT, RESTART_ON_REMOUNT } from '../constants';
|
||||
|
||||
function* testSaga() {
|
||||
yield put({ type: 'TEST', payload: 'yup' });
|
||||
}
|
||||
|
||||
describe('injectors', () => {
|
||||
const originalNodeEnv = process.env.NODE_ENV;
|
||||
let store;
|
||||
let injectSaga;
|
||||
let ejectSaga;
|
||||
|
||||
describe('getInjectors', () => {
|
||||
beforeEach(() => {
|
||||
store = configureStore({}, memoryHistory);
|
||||
});
|
||||
|
||||
it('should return injectors', () => {
|
||||
expect(getInjectors(store)).toEqual(
|
||||
expect.objectContaining({
|
||||
injectSaga: expect.any(Function),
|
||||
ejectSaga: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if passed invalid store shape', () => {
|
||||
Reflect.deleteProperty(store, 'dispatch');
|
||||
|
||||
expect(() => getInjectors(store)).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ejectSaga helper', () => {
|
||||
beforeEach(() => {
|
||||
store = configureStore({}, memoryHistory);
|
||||
injectSaga = injectSagaFactory(store, true);
|
||||
ejectSaga = ejectSagaFactory(store, true);
|
||||
});
|
||||
|
||||
it('should check a store if the second argument is falsy', () => {
|
||||
const eject = ejectSagaFactory({});
|
||||
|
||||
expect(() => eject('test')).toThrow();
|
||||
});
|
||||
|
||||
it('should not check a store if the second argument is true', () => {
|
||||
Reflect.deleteProperty(store, 'dispatch');
|
||||
injectSaga('test', { saga: testSaga });
|
||||
|
||||
expect(() => ejectSaga('test')).not.toThrow();
|
||||
});
|
||||
|
||||
it("should validate saga's key", () => {
|
||||
expect(() => ejectSaga('')).toThrow();
|
||||
expect(() => ejectSaga(1)).toThrow();
|
||||
});
|
||||
|
||||
it('should cancel a saga in RESTART_ON_REMOUNT mode', () => {
|
||||
const cancel = jest.fn();
|
||||
store.injectedSagas.test = { task: { cancel }, mode: RESTART_ON_REMOUNT };
|
||||
ejectSaga('test');
|
||||
|
||||
expect(cancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not cancel a daemon saga', () => {
|
||||
const cancel = jest.fn();
|
||||
store.injectedSagas.test = { task: { cancel }, mode: DAEMON };
|
||||
ejectSaga('test');
|
||||
|
||||
expect(cancel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ignore saga that was not previously injected', () => {
|
||||
expect(() => ejectSaga('test')).not.toThrow();
|
||||
});
|
||||
|
||||
it("should remove non daemon saga's descriptor in production", () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
injectSaga('test', { saga: testSaga, mode: RESTART_ON_REMOUNT });
|
||||
injectSaga('test1', { saga: testSaga, mode: ONCE_TILL_UNMOUNT });
|
||||
|
||||
ejectSaga('test');
|
||||
ejectSaga('test1');
|
||||
|
||||
expect(store.injectedSagas.test).toBe('done');
|
||||
expect(store.injectedSagas.test1).toBe('done');
|
||||
process.env.NODE_ENV = originalNodeEnv;
|
||||
});
|
||||
|
||||
it("should not remove daemon saga's descriptor in production", () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
injectSaga('test', { saga: testSaga, mode: DAEMON });
|
||||
ejectSaga('test');
|
||||
|
||||
expect(store.injectedSagas.test.saga).toBe(testSaga);
|
||||
process.env.NODE_ENV = originalNodeEnv;
|
||||
});
|
||||
|
||||
it("should not remove daemon saga's descriptor in development", () => {
|
||||
injectSaga('test', { saga: testSaga, mode: DAEMON });
|
||||
ejectSaga('test');
|
||||
|
||||
expect(store.injectedSagas.test.saga).toBe(testSaga);
|
||||
});
|
||||
});
|
||||
|
||||
describe('injectSaga helper', () => {
|
||||
beforeEach(() => {
|
||||
store = configureStore({}, memoryHistory);
|
||||
injectSaga = injectSagaFactory(store, true);
|
||||
ejectSaga = ejectSagaFactory(store, true);
|
||||
});
|
||||
|
||||
it('should check a store if the second argument is falsy', () => {
|
||||
const inject = injectSagaFactory({});
|
||||
|
||||
expect(() => inject('test', testSaga)).toThrow();
|
||||
});
|
||||
|
||||
it('it should not check a store if the second argument is true', () => {
|
||||
Reflect.deleteProperty(store, 'dispatch');
|
||||
|
||||
expect(() => injectSaga('test', { saga: testSaga })).not.toThrow();
|
||||
});
|
||||
|
||||
it("should validate saga's key", () => {
|
||||
expect(() => injectSaga('', { saga: testSaga })).toThrow();
|
||||
expect(() => injectSaga(1, { saga: testSaga })).toThrow();
|
||||
});
|
||||
|
||||
it("should validate saga's descriptor", () => {
|
||||
expect(() => injectSaga('test')).toThrow();
|
||||
expect(() => injectSaga('test', { saga: 1 })).toThrow();
|
||||
expect(() =>
|
||||
injectSaga('test', { saga: testSaga, mode: 'testMode' }),
|
||||
).toThrow();
|
||||
expect(() => injectSaga('test', { saga: testSaga, mode: 1 })).toThrow();
|
||||
expect(() =>
|
||||
injectSaga('test', { saga: testSaga, mode: RESTART_ON_REMOUNT }),
|
||||
).not.toThrow();
|
||||
expect(() =>
|
||||
injectSaga('test', { saga: testSaga, mode: DAEMON }),
|
||||
).not.toThrow();
|
||||
expect(() =>
|
||||
injectSaga('test', { saga: testSaga, mode: ONCE_TILL_UNMOUNT }),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should pass args to saga.run', () => {
|
||||
const args = {};
|
||||
store.runSaga = jest.fn();
|
||||
injectSaga('test', { saga: testSaga }, args);
|
||||
|
||||
expect(store.runSaga).toHaveBeenCalledWith(testSaga, args);
|
||||
});
|
||||
|
||||
it('should not start daemon and once-till-unmount sagas if were started before', () => {
|
||||
store.runSaga = jest.fn();
|
||||
|
||||
injectSaga('test1', { saga: testSaga, mode: DAEMON });
|
||||
injectSaga('test1', { saga: testSaga, mode: DAEMON });
|
||||
injectSaga('test2', { saga: testSaga, mode: ONCE_TILL_UNMOUNT });
|
||||
injectSaga('test2', { saga: testSaga, mode: ONCE_TILL_UNMOUNT });
|
||||
|
||||
expect(store.runSaga).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should start any saga that was not started before', () => {
|
||||
store.runSaga = jest.fn();
|
||||
|
||||
injectSaga('test1', { saga: testSaga });
|
||||
injectSaga('test2', { saga: testSaga, mode: DAEMON });
|
||||
injectSaga('test3', { saga: testSaga, mode: ONCE_TILL_UNMOUNT });
|
||||
|
||||
expect(store.runSaga).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should restart a saga if different implementation for hot reloading', () => {
|
||||
const cancel = jest.fn();
|
||||
store.injectedSagas.test = { saga: testSaga, task: { cancel } };
|
||||
store.runSaga = jest.fn();
|
||||
|
||||
function* testSaga1() {
|
||||
yield put({ type: 'TEST', payload: 'yup' });
|
||||
}
|
||||
|
||||
injectSaga('test', { saga: testSaga1 });
|
||||
|
||||
expect(cancel).toHaveBeenCalledTimes(1);
|
||||
expect(store.runSaga).toHaveBeenCalledWith(testSaga1, undefined);
|
||||
});
|
||||
|
||||
it('should not cancel saga if different implementation in production', () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
const cancel = jest.fn();
|
||||
store.injectedSagas.test = {
|
||||
saga: testSaga,
|
||||
task: { cancel },
|
||||
mode: RESTART_ON_REMOUNT,
|
||||
};
|
||||
|
||||
function* testSaga1() {
|
||||
yield put({ type: 'TEST', payload: 'yup' });
|
||||
}
|
||||
|
||||
injectSaga('test', { saga: testSaga1, mode: DAEMON });
|
||||
|
||||
expect(cancel).toHaveBeenCalledTimes(0);
|
||||
process.env.NODE_ENV = originalNodeEnv;
|
||||
});
|
||||
|
||||
it('should save an entire descriptor in the saga registry', () => {
|
||||
injectSaga('test', { saga: testSaga, foo: 'bar' });
|
||||
expect(store.injectedSagas.test.foo).toBe('bar');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -23,7 +23,6 @@
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"cu-ui": "git+https://sean_macfarlane@bitbucket.org/connectustechnologies/connectus-wlan-ui-workspace.git",
|
||||
"graphql": "^14.6.0",
|
||||
"history": "^4.10.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0",
|
||||
|
||||
Reference in New Issue
Block a user