Merge branch 'alerts-test' into 'develop'
Add tests for alerts action See merge request soapbox-pub/soapbox-fe!1464
This commit is contained in:
commit
3082064453
4 changed files with 224 additions and 69 deletions
149
app/soapbox/actions/__tests__/alerts.test.ts
Normal file
149
app/soapbox/actions/__tests__/alerts.test.ts
Normal file
|
@ -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<any>(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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,68 +0,0 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import { httpErrorMessages } from 'soapbox/utils/errors';
|
||||
|
||||
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 = () => {};
|
||||
|
||||
export function dismissAlert(alert) {
|
||||
return {
|
||||
type: ALERT_DISMISS,
|
||||
alert,
|
||||
};
|
||||
}
|
||||
|
||||
export function clearAlert() {
|
||||
return {
|
||||
type: ALERT_CLEAR,
|
||||
};
|
||||
}
|
||||
|
||||
export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage, severity = 'info') {
|
||||
return {
|
||||
type: ALERT_SHOW,
|
||||
title,
|
||||
message,
|
||||
severity,
|
||||
};
|
||||
}
|
||||
|
||||
export function showAlertForError(error) {
|
||||
return (dispatch, _getState) => {
|
||||
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);
|
||||
}
|
||||
|
||||
let message = 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'));
|
||||
}
|
||||
};
|
||||
}
|
74
app/soapbox/actions/alerts.ts
Normal file
74
app/soapbox/actions/alerts.ts
Normal file
|
@ -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<any>) => (dispatch: React.Dispatch<AnyAction>, _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,
|
||||
};
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue