Created single reducer to handle settings

This commit is contained in:
Alejandro Celaya
2020-04-26 13:00:23 +02:00
parent 3953e98a77
commit bbc47b387e
11 changed files with 40 additions and 32 deletions

View File

@@ -14,7 +14,7 @@ import tagsListReducer from '../tags/reducers/tagsList';
import tagDeleteReducer from '../tags/reducers/tagDelete'; import tagDeleteReducer from '../tags/reducers/tagDelete';
import tagEditReducer from '../tags/reducers/tagEdit'; import tagEditReducer from '../tags/reducers/tagEdit';
import mercureInfoReducer from '../mercure/reducers/mercureInfo'; import mercureInfoReducer from '../mercure/reducers/mercureInfo';
import realTimeUpdatesReducer from '../settings/reducers/realTimeUpdates'; import settingsReducer from '../settings/reducers/settings';
export default combineReducers({ export default combineReducers({
servers: serversReducer, servers: serversReducer,
@@ -32,5 +32,5 @@ export default combineReducers({
tagDelete: tagDeleteReducer, tagDelete: tagDeleteReducer,
tagEdit: tagEditReducer, tagEdit: tagEditReducer,
mercureInfo: mercureInfoReducer, mercureInfo: mercureInfoReducer,
realTimeUpdates: realTimeUpdatesReducer, settings: settingsReducer,
}); });

View File

@@ -4,14 +4,14 @@ import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import Checkbox from '../utils/Checkbox'; import Checkbox from '../utils/Checkbox';
import { RealTimeUpdatesType } from './reducers/realTimeUpdates'; import { SettingsType } from './reducers/settings';
const propTypes = { const propTypes = {
realTimeUpdates: RealTimeUpdatesType, settings: SettingsType,
setRealTimeUpdates: PropTypes.func, setRealTimeUpdates: PropTypes.func,
}; };
const RealTimeUpdates = ({ realTimeUpdates, setRealTimeUpdates }) => ( const RealTimeUpdates = ({ settings: { realTimeUpdates }, setRealTimeUpdates }) => (
<Card> <Card>
<CardHeader>Real-time updates</CardHeader> <CardHeader>Real-time updates</CardHeader>
<CardBody> <CardBody>

View File

@@ -3,16 +3,20 @@ import PropTypes from 'prop-types';
export const LOAD_REAL_TIME_UPDATES = 'shlink/realTimeUpdates/LOAD_REAL_TIME_UPDATES'; export const LOAD_REAL_TIME_UPDATES = 'shlink/realTimeUpdates/LOAD_REAL_TIME_UPDATES';
export const RealTimeUpdatesType = PropTypes.shape({ export const SettingsType = PropTypes.shape({
enabled: PropTypes.bool.isRequired, realTimeUpdates: PropTypes.shape({
enabled: PropTypes.bool.isRequired,
}),
}); });
const initialState = { const initialState = {
enabled: true, realTimeUpdates: {
enabled: true,
},
}; };
export default handleActions({ export default handleActions({
[LOAD_REAL_TIME_UPDATES]: (state, { enabled }) => ({ ...state, enabled }), [LOAD_REAL_TIME_UPDATES]: (state, { realTimeUpdates }) => ({ ...state, realTimeUpdates }),
}, initialState); }, initialState);
export const setRealTimeUpdates = ({ updateSettings }, loadRealTimeUpdatesAction) => (enabled) => { export const setRealTimeUpdates = ({ updateSettings }, loadRealTimeUpdatesAction) => (enabled) => {
@@ -23,10 +27,9 @@ export const setRealTimeUpdates = ({ updateSettings }, loadRealTimeUpdatesAction
export const loadRealTimeUpdates = ({ loadSettings }) => () => { export const loadRealTimeUpdates = ({ loadSettings }) => () => {
const { realTimeUpdates = {} } = loadSettings(); const { realTimeUpdates = {} } = loadSettings();
const { enabled = true } = realTimeUpdates;
return { return {
type: LOAD_REAL_TIME_UPDATES, type: LOAD_REAL_TIME_UPDATES,
enabled, realTimeUpdates,
}; };
}; };

View File

@@ -1,6 +1,6 @@
import RealTimeUpdates from '../RealTimeUpdates'; import RealTimeUpdates from '../RealTimeUpdates';
import Settings from '../Settings'; import Settings from '../Settings';
import { loadRealTimeUpdates, setRealTimeUpdates } from '../reducers/realTimeUpdates'; import { loadRealTimeUpdates, setRealTimeUpdates } from '../reducers/settings';
import SettingsService from './SettingsService'; import SettingsService from './SettingsService';
const provideServices = (bottle, connect) => { const provideServices = (bottle, connect) => {
@@ -8,7 +8,7 @@ const provideServices = (bottle, connect) => {
bottle.serviceFactory('Settings', Settings, 'RealTimeUpdates'); bottle.serviceFactory('Settings', Settings, 'RealTimeUpdates');
bottle.serviceFactory('RealTimeUpdates', () => RealTimeUpdates); bottle.serviceFactory('RealTimeUpdates', () => RealTimeUpdates);
bottle.decorator('RealTimeUpdates', connect([ 'realTimeUpdates' ], [ 'setRealTimeUpdates' ])); bottle.decorator('RealTimeUpdates', connect([ 'settings' ], [ 'setRealTimeUpdates' ]));
// Services // Services
bottle.service('SettingsService', SettingsService, 'Storage'); bottle.service('SettingsService', SettingsService, 'Storage');

View File

@@ -9,7 +9,7 @@ import SortingDropdown from '../utils/SortingDropdown';
import { determineOrderDir } from '../utils/utils'; import { determineOrderDir } from '../utils/utils';
import { MercureInfoType } from '../mercure/reducers/mercureInfo'; import { MercureInfoType } from '../mercure/reducers/mercureInfo';
import { bindToMercureTopic } from '../mercure/helpers'; import { bindToMercureTopic } from '../mercure/helpers';
import { RealTimeUpdatesType } from '../settings/reducers/realTimeUpdates'; import { SettingsType } from '../settings/reducers/settings';
import { shortUrlType } from './reducers/shortUrlsList'; import { shortUrlType } from './reducers/shortUrlsList';
import { shortUrlsListParamsType } from './reducers/shortUrlsListParams'; import { shortUrlsListParamsType } from './reducers/shortUrlsListParams';
import './ShortUrlsList.scss'; import './ShortUrlsList.scss';
@@ -34,7 +34,7 @@ const propTypes = {
createNewVisit: PropTypes.func, createNewVisit: PropTypes.func,
loadMercureInfo: PropTypes.func, loadMercureInfo: PropTypes.func,
mercureInfo: MercureInfoType, mercureInfo: MercureInfoType,
realTimeUpdates: RealTimeUpdatesType, settings: SettingsType,
}; };
// FIXME Replace with typescript: (ShortUrlsRow component) // FIXME Replace with typescript: (ShortUrlsRow component)
@@ -52,7 +52,7 @@ const ShortUrlsList = (ShortUrlsRow) => {
createNewVisit, createNewVisit,
loadMercureInfo, loadMercureInfo,
mercureInfo, mercureInfo,
realTimeUpdates, settings: { realTimeUpdates },
}) => { }) => {
const { orderBy } = shortUrlsListParams; const { orderBy } = shortUrlsListParams;
const [ order, setOrder ] = useState({ const [ order, setOrder ] = useState({

View File

@@ -31,7 +31,7 @@ const provideServices = (bottle, connect) => {
bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsRow'); bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsRow');
bottle.decorator('ShortUrlsList', connect( bottle.decorator('ShortUrlsList', connect(
[ 'selectedServer', 'shortUrlsListParams', 'mercureInfo', 'realTimeUpdates' ], [ 'selectedServer', 'shortUrlsListParams', 'mercureInfo', 'settings' ],
[ 'listShortUrls', 'resetShortUrlParams', 'createNewVisit', 'loadMercureInfo' ] [ 'listShortUrls', 'resetShortUrlParams', 'createNewVisit', 'loadMercureInfo' ]
)); ));

View File

@@ -12,7 +12,7 @@ import { formatDate } from '../utils/helpers/date';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import { MercureInfoType } from '../mercure/reducers/mercureInfo'; import { MercureInfoType } from '../mercure/reducers/mercureInfo';
import { bindToMercureTopic } from '../mercure/helpers'; import { bindToMercureTopic } from '../mercure/helpers';
import { RealTimeUpdatesType } from '../settings/reducers/realTimeUpdates'; import { SettingsType } from '../settings/reducers/settings';
import SortableBarGraph from './SortableBarGraph'; import SortableBarGraph from './SortableBarGraph';
import { shortUrlVisitsType } from './reducers/shortUrlVisits'; import { shortUrlVisitsType } from './reducers/shortUrlVisits';
import VisitsHeader from './VisitsHeader'; import VisitsHeader from './VisitsHeader';
@@ -39,7 +39,7 @@ const propTypes = {
createNewVisit: PropTypes.func, createNewVisit: PropTypes.func,
loadMercureInfo: PropTypes.func, loadMercureInfo: PropTypes.func,
mercureInfo: MercureInfoType, mercureInfo: MercureInfoType,
realTimeUpdates: RealTimeUpdatesType, settings: SettingsType,
}; };
const highlightedVisitsToStats = (highlightedVisits, prop) => highlightedVisits.reduce((acc, highlightedVisit) => { const highlightedVisitsToStats = (highlightedVisits, prop) => highlightedVisits.reduce((acc, highlightedVisit) => {
@@ -68,7 +68,7 @@ const ShortUrlVisits = ({ processStatsFromVisits, normalizeVisits }, OpenMapModa
createNewVisit, createNewVisit,
loadMercureInfo, loadMercureInfo,
mercureInfo, mercureInfo,
realTimeUpdates, settings: { realTimeUpdates },
}) => { }) => {
const [ startDate, setStartDate ] = useState(undefined); const [ startDate, setStartDate ] = useState(undefined);
const [ endDate, setEndDate ] = useState(undefined); const [ endDate, setEndDate ] = useState(undefined);

View File

@@ -11,7 +11,7 @@ const provideServices = (bottle, connect) => {
bottle.serviceFactory('MapModal', () => MapModal); bottle.serviceFactory('MapModal', () => MapModal);
bottle.serviceFactory('ShortUrlVisits', ShortUrlVisits, 'VisitsParser', 'OpenMapModalBtn'); bottle.serviceFactory('ShortUrlVisits', ShortUrlVisits, 'VisitsParser', 'OpenMapModalBtn');
bottle.decorator('ShortUrlVisits', connect( bottle.decorator('ShortUrlVisits', connect(
[ 'shortUrlVisits', 'shortUrlDetail', 'mercureInfo', 'realTimeUpdates' ], [ 'shortUrlVisits', 'shortUrlDetail', 'mercureInfo', 'settings' ],
[ 'getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits', 'createNewVisit', 'loadMercureInfo' ] [ 'getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits', 'createNewVisit', 'loadMercureInfo' ]
)); ));

View File

@@ -2,34 +2,35 @@ import reducer, {
LOAD_REAL_TIME_UPDATES, LOAD_REAL_TIME_UPDATES,
loadRealTimeUpdates, loadRealTimeUpdates,
setRealTimeUpdates, setRealTimeUpdates,
} from '../../../src/settings/reducers/realTimeUpdates'; } from '../../../src/settings/reducers/settings';
describe('realTimeUpdatesReducer', () => { describe('settingsReducer', () => {
const SettingsServiceMock = { const SettingsServiceMock = {
updateSettings: jest.fn(), updateSettings: jest.fn(),
loadSettings: jest.fn(), loadSettings: jest.fn(),
}; };
const realTimeUpdates = { enabled: true };
afterEach(jest.clearAllMocks); afterEach(jest.clearAllMocks);
describe('reducer', () => { describe('reducer', () => {
it('returns realTimeUpdates when action is LOAD_REAL_TIME_UPDATES', () => { it('returns realTimeUpdates when action is LOAD_REAL_TIME_UPDATES', () => {
expect(reducer({}, { type: LOAD_REAL_TIME_UPDATES, enabled: true })).toEqual({ enabled: true }); expect(reducer({}, { type: LOAD_REAL_TIME_UPDATES, realTimeUpdates })).toEqual({ realTimeUpdates });
}); });
}); });
describe('loadRealTimeUpdates', () => { describe('loadRealTimeUpdates', () => {
it.each([ it.each([[ true ], [ false ]])('loads settings and returns LOAD_REAL_TIME_UPDATES action', (enabled) => {
[{}, true ], const realTimeUpdates = { enabled };
[{ realTimeUpdates: {} }, true ],
[{ realTimeUpdates: { enabled: true } }, true ], SettingsServiceMock.loadSettings.mockReturnValue({ realTimeUpdates });
[{ realTimeUpdates: { enabled: false } }, false ],
])('loads settings and returns LOAD_REAL_TIME_UPDATES action', (loadedSettings, expectedEnabled) => {
SettingsServiceMock.loadSettings.mockReturnValue(loadedSettings);
const result = loadRealTimeUpdates(SettingsServiceMock)(); const result = loadRealTimeUpdates(SettingsServiceMock)();
expect(result).toEqual({ type: LOAD_REAL_TIME_UPDATES, enabled: expectedEnabled }); expect(result).toEqual({
type: LOAD_REAL_TIME_UPDATES,
realTimeUpdates,
});
expect(SettingsServiceMock.loadSettings).toHaveBeenCalled(); expect(SettingsServiceMock.loadSettings).toHaveBeenCalled();
}); });
}); });

View File

@@ -9,6 +9,7 @@ describe('<ShortUrlsList />', () => {
const ShortUrlsRow = () => ''; const ShortUrlsRow = () => '';
const listShortUrlsMock = jest.fn(); const listShortUrlsMock = jest.fn();
const resetShortUrlParamsMock = jest.fn(); const resetShortUrlParamsMock = jest.fn();
const realTimeUpdates = { enabled: true };
const ShortUrlsList = shortUrlsListCreator(ShortUrlsRow); const ShortUrlsList = shortUrlsListCreator(ShortUrlsRow);
@@ -37,6 +38,7 @@ describe('<ShortUrlsList />', () => {
] ]
} }
mercureInfo={{ loading: true }} mercureInfo={{ loading: true }}
settings={{ realTimeUpdates }}
/> />
); );
}); });

View File

@@ -21,6 +21,7 @@ describe('<ShortUrlVisits />', () => {
const history = { const history = {
goBack: jest.fn(), goBack: jest.fn(),
}; };
const realTimeUpdates = { enabled: true };
const createComponent = (shortUrlVisits) => { const createComponent = (shortUrlVisits) => {
const ShortUrlVisits = createShortUrlVisits({ processStatsFromVisits, normalizeVisits: identity }, () => ''); const ShortUrlVisits = createShortUrlVisits({ processStatsFromVisits, normalizeVisits: identity }, () => '');
@@ -36,6 +37,7 @@ describe('<ShortUrlVisits />', () => {
shortUrlDetail={{}} shortUrlDetail={{}}
cancelGetShortUrlVisits={identity} cancelGetShortUrlVisits={identity}
matchMedia={() => ({ matches: false })} matchMedia={() => ({ matches: false })}
settings={{ realTimeUpdates }}
/> />
); );