From 3972e16e439f03460b2a6fc68013a940ae69328a Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 27 May 2022 15:22:20 -0400 Subject: [PATCH] Add tests for alerts action --- app/soapbox/actions/__tests__/alerts.test.ts | 149 +++++++++++++++++++ app/soapbox/actions/alerts.js | Bin 1669 -> 0 bytes app/soapbox/actions/alerts.ts | 74 +++++++++ app/soapbox/actions/snackbar.ts | 2 +- 4 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 app/soapbox/actions/__tests__/alerts.test.ts delete mode 100644 app/soapbox/actions/alerts.js create mode 100644 app/soapbox/actions/alerts.ts diff --git a/app/soapbox/actions/__tests__/alerts.test.ts b/app/soapbox/actions/__tests__/alerts.test.ts new file mode 100644 index 0000000000..f2419893a6 --- /dev/null +++ b/app/soapbox/actions/__tests__/alerts.test.ts @@ -0,0 +1,149 @@ +import { AxiosError } from 'axios'; + +import { mockStore } from 'soapbox/jest/test-helpers'; +import rootReducer from 'soapbox/reducers'; + +import { dismissAlert, showAlert, showAlertForError } from '../alerts'; + +const buildError = (message: string, status: number) => new AxiosError(message, String(status), null, null, { + data: { + error: message, + }, + statusText: String(status), + status, + headers: {}, + config: {}, +}); + +let store; + +beforeEach(() => { + const state = rootReducer(undefined, {}); + store = mockStore(state); +}); + +describe('dismissAlert()', () => { + it('dispatches the proper actions', async() => { + const alert = 'hello world'; + const expectedActions = [ + { type: 'ALERT_DISMISS', alert }, + ]; + await store.dispatch(dismissAlert(alert)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); +}); + +describe('showAlert()', () => { + it('dispatches the proper actions', async() => { + const title = 'title'; + const message = 'msg'; + const severity = 'info'; + const expectedActions = [ + { type: 'ALERT_SHOW', title, message, severity }, + ]; + await store.dispatch(showAlert(title, message, severity)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); +}); + +describe('showAlert()', () => { + describe('with a 502 status code', () => { + it('dispatches the proper actions', async() => { + const message = 'The server is down'; + const error = buildError(message, 502); + + const expectedActions = [ + { type: 'ALERT_SHOW', title: '', message, severity: 'error' }, + ]; + await store.dispatch(showAlertForError(error)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('with a 404 status code', () => { + it('dispatches the proper actions', async() => { + const error = buildError('', 404); + + const expectedActions = []; + await store.dispatch(showAlertForError(error)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('with a 410 status code', () => { + it('dispatches the proper actions', async() => { + const error = buildError('', 410); + + const expectedActions = []; + await store.dispatch(showAlertForError(error)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('with an accepted status code', () => { + describe('with a message from the server', () => { + it('dispatches the proper actions', async() => { + const message = 'custom message'; + const error = buildError(message, 200); + + const expectedActions = [ + { type: 'ALERT_SHOW', title: '', message, severity: 'error' }, + ]; + await store.dispatch(showAlertForError(error)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('without a message from the server', () => { + it('dispatches the proper actions', async() => { + const message = 'The request has been accepted for processing'; + const error = buildError(message, 202); + + const expectedActions = [ + { type: 'ALERT_SHOW', title: '', message, severity: 'error' }, + ]; + await store.dispatch(showAlertForError(error)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + }); + + describe('without a response', () => { + it('dispatches the proper actions', async() => { + const error = new AxiosError(); + + const expectedActions = [ + { + type: 'ALERT_SHOW', + title: { + defaultMessage: 'Oops!', + id: 'alert.unexpected.title', + }, + message: { + defaultMessage: 'An unexpected error occurred.', + id: 'alert.unexpected.message', + }, + severity: 'error', + }, + ]; + await store.dispatch(showAlertForError(error)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); +}); diff --git a/app/soapbox/actions/alerts.js b/app/soapbox/actions/alerts.js deleted file mode 100644 index c71ce3e874fbdcfb89cfd1d57751e9b09f09b3c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1669 zcma)7O^@0z5WVv&W={!H8fZl?Tcs6hsfx6#l{Qp8uF_<(SPgb$J6#Z)|K72kFCb`} z3yJM{^XAPP!*->R8jg@*&N$wqQpqzas3DhP2cEM4~+#Ep)w%H`ea zaq|@ac=!eMMBw!cB*nY`>|b);Q-&iI+8{{blci<0Ss5t{K_cyACiEp#X;XYc# z|ES`-kalEzYoe3PJ~gCFbh-`T=^1sb6OFEWR&bVs3koHwk_L%3O}otF97pq;=!6QS z$V#=p8{BJ}XN#dh)YTZb{7(9p9?x?9>_6qq2?m!Rg diff --git a/app/soapbox/actions/alerts.ts b/app/soapbox/actions/alerts.ts new file mode 100644 index 0000000000..b0af2af352 --- /dev/null +++ b/app/soapbox/actions/alerts.ts @@ -0,0 +1,74 @@ +import { AnyAction } from '@reduxjs/toolkit'; +import { AxiosError } from 'axios'; +import { defineMessages, MessageDescriptor } from 'react-intl'; + +import { httpErrorMessages } from 'soapbox/utils/errors'; + +import { SnackbarActionSeverity } from './snackbar'; + +const messages = defineMessages({ + unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' }, + unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' }, +}); + +export const ALERT_SHOW = 'ALERT_SHOW'; +export const ALERT_DISMISS = 'ALERT_DISMISS'; +export const ALERT_CLEAR = 'ALERT_CLEAR'; + +const noOp = () => { }; + +function dismissAlert(alert: any) { + return { + type: ALERT_DISMISS, + alert, + }; +} + +function showAlert( + title: MessageDescriptor | string = messages.unexpectedTitle, + message: MessageDescriptor | string = messages.unexpectedMessage, + severity: SnackbarActionSeverity = 'info', +) { + return { + type: ALERT_SHOW, + title, + message, + severity, + }; +} + +const showAlertForError = (error: AxiosError) => (dispatch: React.Dispatch, _getState: any) => { + if (error.response) { + const { data, status, statusText } = error.response; + + if (status === 502) { + return dispatch(showAlert('', 'The server is down', 'error')); + } + + if (status === 404 || status === 410) { + // Skip these errors as they are reflected in the UI + return dispatch(noOp as any); + } + + let message: string | undefined = statusText; + + if (data.error) { + message = data.error; + } + + if (!message) { + message = httpErrorMessages.find((httpError) => httpError.code === status)?.description; + } + + return dispatch(showAlert('', message, 'error')); + } else { + console.error(error); + return dispatch(showAlert(undefined, undefined, 'error')); + } +}; + +export { + dismissAlert, + showAlert, + showAlertForError, +}; diff --git a/app/soapbox/actions/snackbar.ts b/app/soapbox/actions/snackbar.ts index d1cda0d943..d4238cf339 100644 --- a/app/soapbox/actions/snackbar.ts +++ b/app/soapbox/actions/snackbar.ts @@ -2,7 +2,7 @@ import { ALERT_SHOW } from './alerts'; import type { MessageDescriptor } from 'react-intl'; -type SnackbarActionSeverity = 'info' | 'success' | 'error' +export type SnackbarActionSeverity = 'info' | 'success' | 'error' type SnackbarMessage = string | MessageDescriptor