diff --git a/package-lock.json b/package-lock.json index e67e9e4e..a644d0e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 8454ae72..e82a37e8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/container/types.ts b/src/container/types.ts index 27759ea8..85486e2e 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -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; diff --git a/src/mercure/reducers/mercureInfo.js b/src/mercure/reducers/mercureInfo.js deleted file mode 100644 index 62a9b9db..00000000 --- a/src/mercure/reducers/mercureInfo.js +++ /dev/null @@ -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 }); - } -}; diff --git a/src/mercure/reducers/mercureInfo.ts b/src/mercure/reducers/mercureInfo.ts new file mode 100644 index 00000000..a5e251fc --- /dev/null +++ b/src/mercure/reducers/mercureInfo.ts @@ -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({ + [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 }); + } + }; diff --git a/src/reducers/index.ts b/src/reducers/index.ts index 1cdf34fd..58bf18ad 100644 --- a/src/reducers/index.ts +++ b/src/reducers/index.ts @@ -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({ servers: serversReducer, selectedServer: selectedServerReducer, shortUrlsList: shortUrlsListReducer, diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts new file mode 100644 index 00000000..c5f5a474 --- /dev/null +++ b/src/utils/services/types.ts @@ -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; +} diff --git a/test/mercure/reducers/mercureInfo.test.js b/test/mercure/reducers/mercureInfo.test.ts similarity index 73% rename from test/mercure/reducers/mercureInfo.test.js rename to test/mercure/reducers/mercureInfo.test.ts index c5219548..eb48e1f9 100644 --- a/test/mercure/reducers/mercureInfo.test.js +++ b/test/mercure/reducers/mercureInfo.test.ts @@ -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)).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)).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) => Mock.of({ + 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 () => {