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 1 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Binary file not shown.
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';
|
import type { MessageDescriptor } from 'react-intl';
|
||||||
|
|
||||||
type SnackbarActionSeverity = 'info' | 'success' | 'error'
|
export type SnackbarActionSeverity = 'info' | 'success' | 'error'
|
||||||
|
|
||||||
type SnackbarMessage = string | MessageDescriptor
|
type SnackbarMessage = string | MessageDescriptor
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue