Migrated first reducer to typescript, adding also type for the shared app state

This commit is contained in:
Alejandro Celaya 2020-08-23 09:52:09 +02:00
parent e193a692e8
commit 87e64e5899
8 changed files with 117 additions and 59 deletions

6
package-lock.json generated
View file

@ -3441,6 +3441,12 @@
"popper.js": "^1.14.1"
}
},
"@types/redux-actions": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@types/redux-actions/-/redux-actions-2.6.1.tgz",
"integrity": "sha512-zKgK+ATp3sswXs6sOYo1tk8xdXTy4CTaeeYrVQlClCjeOpag5vzPo0ASWiiBJ7vsiQRAdb3VkuFLnDoBimF67g==",
"dev": true
},
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",

View file

@ -86,6 +86,7 @@
"@types/react-redux": "^7.1.9",
"@types/react-router-dom": "^5.1.5",
"@types/reactstrap": "^8.5.1",
"@types/redux-actions": "^2.6.1",
"@types/uuid": "^8.3.0",
"adm-zip": "^0.4.13",
"autoprefixer": "^9.6.3",

View file

@ -1 +1,25 @@
import { MercureInfo } from '../mercure/reducers/mercureInfo';
export type ConnectDecorator = (props: string[], actions?: string[]) => any;
export interface ShlinkState {
servers: any;
selectedServer: any;
shortUrlsList: any;
shortUrlsListParams: any;
shortUrlCreationResult: any;
shortUrlDeletion: any;
shortUrlTags: any;
shortUrlMeta: any;
shortUrlEdition: any;
shortUrlVisits: any;
tagVisits: any;
shortUrlDetail: any;
tagsList: any;
tagDelete: any;
tagEdit: any;
mercureInfo: MercureInfo;
settings: any;
}
export type GetState = () => ShlinkState;

View file

@ -1,49 +0,0 @@
import { handleActions } from 'redux-actions';
import PropTypes from 'prop-types';
/* eslint-disable padding-line-between-statements */
export const GET_MERCURE_INFO_START = 'shlink/mercure/GET_MERCURE_INFO_START';
export const GET_MERCURE_INFO_ERROR = 'shlink/mercure/GET_MERCURE_INFO_ERROR';
export const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO';
/* eslint-enable padding-line-between-statements */
export const MercureInfoType = PropTypes.shape({
token: PropTypes.string,
mercureHubUrl: PropTypes.string,
loading: PropTypes.bool,
error: PropTypes.bool,
});
const initialState = {
token: undefined,
mercureHubUrl: undefined,
loading: true,
error: false,
};
export default handleActions({
[GET_MERCURE_INFO_START]: (state) => ({ ...state, loading: true, error: false }),
[GET_MERCURE_INFO_ERROR]: (state) => ({ ...state, loading: false, error: true }),
[GET_MERCURE_INFO]: (state, { token, mercureHubUrl }) => ({ token, mercureHubUrl, loading: false, error: false }),
}, initialState);
export const loadMercureInfo = (buildShlinkApiClient) => () => async (dispatch, getState) => {
dispatch({ type: GET_MERCURE_INFO_START });
const { settings } = getState();
const { mercureInfo } = buildShlinkApiClient(getState);
if (!settings.realTimeUpdates.enabled) {
dispatch({ type: GET_MERCURE_INFO_ERROR });
return;
}
try {
const result = await mercureInfo();
dispatch({ type: GET_MERCURE_INFO, ...result });
} catch (e) {
dispatch({ type: GET_MERCURE_INFO_ERROR });
}
};

View file

@ -0,0 +1,59 @@
import { handleActions } from 'redux-actions';
import PropTypes from 'prop-types';
import { Dispatch } from 'redux';
import { ShlinkApiClientBuilder, ShlinkMercureInfo } from '../../utils/services/types';
import { GetState } from '../../container/types';
/* eslint-disable padding-line-between-statements */
export const GET_MERCURE_INFO_START = 'shlink/mercure/GET_MERCURE_INFO_START';
export const GET_MERCURE_INFO_ERROR = 'shlink/mercure/GET_MERCURE_INFO_ERROR';
export const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO';
/* eslint-enable padding-line-between-statements */
/** @deprecated Use MercureInfo interface */
export const MercureInfoType = PropTypes.shape({
token: PropTypes.string,
mercureHubUrl: PropTypes.string,
loading: PropTypes.bool,
error: PropTypes.bool,
});
export interface MercureInfo {
token?: string;
mercureHubUrl?: string;
loading: boolean;
error: boolean;
}
const initialState: MercureInfo = {
loading: true,
error: false,
};
export default handleActions<MercureInfo, ShlinkMercureInfo>({
[GET_MERCURE_INFO_START]: (state) => ({ ...state, loading: true, error: false }),
[GET_MERCURE_INFO_ERROR]: (state) => ({ ...state, loading: false, error: true }),
[GET_MERCURE_INFO]: (_, { payload }) => ({ ...payload, loading: false, error: false }),
}, initialState);
export const loadMercureInfo = (buildShlinkApiClient: ShlinkApiClientBuilder) =>
() => async (dispatch: Dispatch, getState: GetState) => {
dispatch({ type: GET_MERCURE_INFO_START });
const { settings } = getState();
const { mercureInfo } = buildShlinkApiClient(getState);
if (!settings.realTimeUpdates.enabled) {
dispatch({ type: GET_MERCURE_INFO_ERROR });
return;
}
try {
const payload = await mercureInfo();
dispatch({ type: GET_MERCURE_INFO, payload });
} catch (e) {
dispatch({ type: GET_MERCURE_INFO_ERROR });
}
};

View file

@ -16,8 +16,9 @@ import tagDeleteReducer from '../tags/reducers/tagDelete';
import tagEditReducer from '../tags/reducers/tagEdit';
import mercureInfoReducer from '../mercure/reducers/mercureInfo';
import settingsReducer from '../settings/reducers/settings';
import { ShlinkState } from '../container/types';
export default combineReducers({
export default combineReducers<ShlinkState>({
servers: serversReducer,
selectedServer: selectedServerReducer,
shortUrlsList: shortUrlsListReducer,

View file

@ -0,0 +1,11 @@
import { RegularServer } from '../../servers/data';
import { GetState } from '../../container/types';
import ShlinkApiClient from './ShlinkApiClient';
// FIXME Move to ShlinkApiClientBuilder
export type ShlinkApiClientBuilder = (getStateOrSelectedServer: RegularServer | GetState) => ShlinkApiClient;
export interface ShlinkMercureInfo {
token: string;
mercureHubUrl: string;
}

View file

@ -1,9 +1,14 @@
import { Mock } from 'ts-mockery';
import { Action } from 'redux-actions';
import reducer, {
GET_MERCURE_INFO_START,
GET_MERCURE_INFO_ERROR,
GET_MERCURE_INFO,
loadMercureInfo,
} from '../../../src/mercure/reducers/mercureInfo.js';
} from '../../../src/mercure/reducers/mercureInfo';
import { ShlinkMercureInfo } from '../../../src/utils/services/types';
import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient';
import { GetState } from '../../../src/container/types';
describe('mercureInfoReducer', () => {
const mercureInfo = {
@ -13,21 +18,21 @@ describe('mercureInfoReducer', () => {
describe('reducer', () => {
it('returns loading on GET_MERCURE_INFO_START', () => {
expect(reducer({}, { type: GET_MERCURE_INFO_START })).toEqual({
expect(reducer(undefined, { type: GET_MERCURE_INFO_START } as Action<ShlinkMercureInfo>)).toEqual({
loading: true,
error: false,
});
});
it('returns error on GET_MERCURE_INFO_ERROR', () => {
expect(reducer({}, { type: GET_MERCURE_INFO_ERROR })).toEqual({
expect(reducer(undefined, { type: GET_MERCURE_INFO_ERROR } as Action<ShlinkMercureInfo>)).toEqual({
loading: false,
error: true,
});
});
it('returns mercure info on GET_MERCURE_INFO', () => {
expect(reducer({}, { type: GET_MERCURE_INFO, ...mercureInfo })).toEqual({
expect(reducer(undefined, { type: GET_MERCURE_INFO, payload: mercureInfo })).toEqual({
...mercureInfo,
loading: false,
error: false,
@ -36,15 +41,15 @@ describe('mercureInfoReducer', () => {
});
describe('loadMercureInfo', () => {
const createApiClientMock = (result) => ({
mercureInfo: jest.fn(() => result),
const createApiClientMock = (result: Promise<ShlinkMercureInfo>) => Mock.of<ShlinkApiClient>({
mercureInfo: jest.fn().mockReturnValue(result),
});
const dispatch = jest.fn();
const createGetStateMock = (enabled) => jest.fn(() => ({
const createGetStateMock = (enabled: boolean): GetState => jest.fn().mockReturnValue({
settings: {
realTimeUpdates: { enabled },
},
}));
});
afterEach(jest.resetAllMocks);
@ -69,7 +74,7 @@ describe('mercureInfoReducer', () => {
expect(apiClientMock.mercureInfo).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch).toHaveBeenNthCalledWith(1, { type: GET_MERCURE_INFO_START });
expect(dispatch).toHaveBeenNthCalledWith(2, { type: GET_MERCURE_INFO, ...mercureInfo });
expect(dispatch).toHaveBeenNthCalledWith(2, { type: GET_MERCURE_INFO, payload: mercureInfo });
});
it('throws error on failure', async () => {