diff --git a/app/soapbox/actions/__tests__/accounts.test.ts b/app/soapbox/actions/__tests__/accounts.test.ts index 23cea16b0..310e42a1b 100644 --- a/app/soapbox/actions/__tests__/accounts.test.ts +++ b/app/soapbox/actions/__tests__/accounts.test.ts @@ -5,7 +5,19 @@ import { mockStore } from 'soapbox/jest/test-helpers'; import rootReducer from 'soapbox/reducers'; import { normalizeAccount } from '../../normalizers'; -import { createAccount, fetchAccount, fetchAccountByUsername } from '../accounts'; +import { + blockAccount, + createAccount, + fetchAccount, + fetchAccountByUsername, + followAccount, + muteAccount, + subscribeAccount, + unblockAccount, + unfollowAccount, + unmuteAccount, + unsubscribeAccount, +} from '../accounts'; let store; @@ -357,3 +369,560 @@ describe('fetchAccountByUsername()', () => { }); }); }); + +describe('followAccount()', () => { + describe('when logged out', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', null); + store = mockStore(state); + }); + + it('should do nothing', async() => { + await store.dispatch(followAccount(1)); + const actions = store.getActions(); + + expect(actions).toEqual([]); + }); + }); + + describe('when logged in', () => { + const id = 1; + + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', '123'); + store = mockStore(state); + }); + + describe('with a successful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/follow`).reply(200, { success: true }); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { + type: 'ACCOUNT_FOLLOW_REQUEST', + id, + locked: false, + skipLoading: true, + }, + { + type: 'ACCOUNT_FOLLOW_SUCCESS', + relationship: { success: true }, + alreadyFollowing: undefined, + skipLoading: true, + }, + ]; + await store.dispatch(followAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('with an unsuccessful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/follow`).networkError(); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { + type: 'ACCOUNT_FOLLOW_REQUEST', + id, + locked: false, + skipLoading: true, + }, + { + type: 'ACCOUNT_FOLLOW_FAIL', + error: new Error('Network Error'), + locked: false, + skipLoading: true, + }, + ]; + await store.dispatch(followAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + }); +}); + +describe('unfollowAccount()', () => { + describe('when logged out', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', null); + store = mockStore(state); + }); + + it('should do nothing', async() => { + await store.dispatch(unfollowAccount(1)); + const actions = store.getActions(); + + expect(actions).toEqual([]); + }); + }); + + describe('when logged in', () => { + const id = 1; + + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', '123'); + store = mockStore(state); + }); + + describe('with a successful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/unfollow`).reply(200, { success: true }); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_UNFOLLOW_REQUEST', id: 1, skipLoading: true }, + { + type: 'ACCOUNT_UNFOLLOW_SUCCESS', + relationship: { success: true }, + statuses: ImmutableMap({}), + skipLoading: true, + }, + ]; + await store.dispatch(unfollowAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('with an unsuccessful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/unfollow`).networkError(); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { + type: 'ACCOUNT_UNFOLLOW_REQUEST', + id, + skipLoading: true, + }, + { + type: 'ACCOUNT_UNFOLLOW_FAIL', + error: new Error('Network Error'), + skipLoading: true, + }, + ]; + await store.dispatch(unfollowAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + }); +}); + +describe('blockAccount()', () => { + const id = 1; + + describe('when logged out', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', null); + store = mockStore(state); + }); + + it('should do nothing', async() => { + await store.dispatch(blockAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual([]); + }); + }); + + describe('when logged in', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', '123'); + store = mockStore(state); + }); + + describe('with a successful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/block`).reply(200, {}); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_BLOCK_REQUEST', id }, + { + type: 'ACCOUNT_BLOCK_SUCCESS', + relationship: {}, + statuses: ImmutableMap({}), + }, + ]; + await store.dispatch(blockAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('with an unsuccessful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/block`).networkError(); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_BLOCK_REQUEST', id }, + { type: 'ACCOUNT_BLOCK_FAIL', error: new Error('Network Error') }, + ]; + await store.dispatch(blockAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + }); +}); + +describe('unblockAccount()', () => { + const id = 1; + + describe('when logged out', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', null); + store = mockStore(state); + }); + + it('should do nothing', async() => { + await store.dispatch(unblockAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual([]); + }); + }); + + describe('when logged in', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', '123'); + store = mockStore(state); + }); + + describe('with a successful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/unblock`).reply(200, {}); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_UNBLOCK_REQUEST', id }, + { + type: 'ACCOUNT_UNBLOCK_SUCCESS', + relationship: {}, + }, + ]; + await store.dispatch(unblockAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('with an unsuccessful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/unblock`).networkError(); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_UNBLOCK_REQUEST', id }, + { type: 'ACCOUNT_UNBLOCK_FAIL', error: new Error('Network Error') }, + ]; + await store.dispatch(unblockAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + }); +}); + +describe('muteAccount()', () => { + const id = 1; + + describe('when logged out', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', null); + store = mockStore(state); + }); + + it('should do nothing', async() => { + await store.dispatch(unblockAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual([]); + }); + }); + + describe('when logged in', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', '123'); + store = mockStore(state); + }); + + describe('with a successful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/mute`).reply(200, {}); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_MUTE_REQUEST', id }, + { + type: 'ACCOUNT_MUTE_SUCCESS', + relationship: {}, + statuses: ImmutableMap({}), + }, + ]; + await store.dispatch(muteAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('with an unsuccessful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/mute`).networkError(); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_MUTE_REQUEST', id }, + { type: 'ACCOUNT_MUTE_FAIL', error: new Error('Network Error') }, + ]; + await store.dispatch(muteAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + }); +}); + +describe('unmuteAccount()', () => { + const id = 1; + + describe('when logged out', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', null); + store = mockStore(state); + }); + + it('should do nothing', async() => { + await store.dispatch(unblockAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual([]); + }); + }); + + describe('when logged in', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', '123'); + store = mockStore(state); + }); + + describe('with a successful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/unmute`).reply(200, {}); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_UNMUTE_REQUEST', id }, + { + type: 'ACCOUNT_UNMUTE_SUCCESS', + relationship: {}, + }, + ]; + await store.dispatch(unmuteAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('with an unsuccessful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/accounts/${id}/unmute`).networkError(); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_UNMUTE_REQUEST', id }, + { type: 'ACCOUNT_UNMUTE_FAIL', error: new Error('Network Error') }, + ]; + await store.dispatch(unmuteAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + }); +}); + +describe('subscribeAccount()', () => { + const id = 1; + + describe('when logged out', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', null); + store = mockStore(state); + }); + + it('should do nothing', async() => { + await store.dispatch(subscribeAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual([]); + }); + }); + + describe('when logged in', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', '123'); + store = mockStore(state); + }); + + describe('with a successful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/pleroma/accounts/${id}/subscribe`).reply(200, {}); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_SUBSCRIBE_REQUEST', id }, + { + type: 'ACCOUNT_SUBSCRIBE_SUCCESS', + relationship: {}, + }, + ]; + await store.dispatch(subscribeAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('with an unsuccessful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/pleroma/accounts/${id}/subscribe`).networkError(); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_SUBSCRIBE_REQUEST', id }, + { type: 'ACCOUNT_SUBSCRIBE_FAIL', error: new Error('Network Error') }, + ]; + await store.dispatch(subscribeAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + }); +}); + +describe('unsubscribeAccount()', () => { + const id = 1; + + describe('when logged out', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', null); + store = mockStore(state); + }); + + it('should do nothing', async() => { + await store.dispatch(subscribeAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual([]); + }); + }); + + describe('when logged in', () => { + beforeEach(() => { + const state = rootReducer(undefined, {}).set('me', '123'); + store = mockStore(state); + }); + + describe('with a successful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/pleroma/accounts/${id}/unsubscribe`).reply(200, {}); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_UNSUBSCRIBE_REQUEST', id }, + { + type: 'ACCOUNT_UNSUBSCRIBE_SUCCESS', + relationship: {}, + }, + ]; + await store.dispatch(unsubscribeAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + + describe('with an unsuccessful API request', () => { + beforeEach(() => { + __stub((mock) => { + mock.onPost(`/api/v1/pleroma/accounts/${id}/unsubscribe`).networkError(); + }); + }); + + it('should dispatch the correct actions', async() => { + const expectedActions = [ + { type: 'ACCOUNT_UNSUBSCRIBE_REQUEST', id }, + { type: 'ACCOUNT_UNSUBSCRIBE_FAIL', error: new Error('Network Error') }, + ]; + await store.dispatch(unsubscribeAccount(id)); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); + }); + }); +}); diff --git a/app/soapbox/actions/accounts.js b/app/soapbox/actions/accounts.js index b93f0e599..1fa430d71 100644 --- a/app/soapbox/actions/accounts.js +++ b/app/soapbox/actions/accounts.js @@ -230,32 +230,30 @@ export function fetchAccountFail(id, error) { export function followAccount(id, options = { reblogs: true }) { return (dispatch, getState) => { - if (!isLoggedIn(getState)) return; + if (!isLoggedIn(getState)) return null; const alreadyFollowing = getState().getIn(['relationships', id, 'following']); const locked = getState().getIn(['accounts', id, 'locked'], false); dispatch(followAccountRequest(id, locked)); - api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => { - dispatch(followAccountSuccess(response.data, alreadyFollowing)); - }).catch(error => { - dispatch(followAccountFail(error, locked)); - }); + return api(getState) + .post(`/api/v1/accounts/${id}/follow`, options) + .then(response => dispatch(followAccountSuccess(response.data, alreadyFollowing))) + .catch(error => dispatch(followAccountFail(error, locked))); }; } export function unfollowAccount(id) { return (dispatch, getState) => { - if (!isLoggedIn(getState)) return; + if (!isLoggedIn(getState)) return null; dispatch(unfollowAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => { - dispatch(unfollowAccountSuccess(response.data, getState().get('statuses'))); - }).catch(error => { - dispatch(unfollowAccountFail(error)); - }); + return api(getState) + .post(`/api/v1/accounts/${id}/unfollow`) + .then(response => dispatch(unfollowAccountSuccess(response.data, getState().get('statuses')))) + .catch(error => dispatch(unfollowAccountFail(error))); }; } @@ -313,30 +311,29 @@ export function unfollowAccountFail(error) { export function blockAccount(id) { return (dispatch, getState) => { - if (!isLoggedIn(getState)) return; + if (!isLoggedIn(getState)) return null; dispatch(blockAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/block`).then(response => { - // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers - dispatch(blockAccountSuccess(response.data, getState().get('statuses'))); - }).catch(error => { - dispatch(blockAccountFail(id, error)); - }); + return api(getState) + .post(`/api/v1/accounts/${id}/block`) + .then(response => { + // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers + return dispatch(blockAccountSuccess(response.data, getState().get('statuses'))); + }).catch(error => dispatch(blockAccountFail(error))); }; } export function unblockAccount(id) { return (dispatch, getState) => { - if (!isLoggedIn(getState)) return; + if (!isLoggedIn(getState)) return null; dispatch(unblockAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => { - dispatch(unblockAccountSuccess(response.data)); - }).catch(error => { - dispatch(unblockAccountFail(id, error)); - }); + return api(getState) + .post(`/api/v1/accounts/${id}/unblock`) + .then(response => dispatch(unblockAccountSuccess(response.data))) + .catch(error => dispatch(unblockAccountFail(error))); }; } @@ -383,33 +380,32 @@ export function unblockAccountFail(error) { }; } - export function muteAccount(id, notifications) { return (dispatch, getState) => { - if (!isLoggedIn(getState)) return; + if (!isLoggedIn(getState)) return null; dispatch(muteAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications }).then(response => { - // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers - dispatch(muteAccountSuccess(response.data, getState().get('statuses'))); - }).catch(error => { - dispatch(muteAccountFail(id, error)); - }); + return api(getState) + .post(`/api/v1/accounts/${id}/mute`, { notifications }) + .then(response => { + // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers + return dispatch(muteAccountSuccess(response.data, getState().get('statuses'))); + }) + .catch(error => dispatch(muteAccountFail(error))); }; } export function unmuteAccount(id) { return (dispatch, getState) => { - if (!isLoggedIn(getState)) return; + if (!isLoggedIn(getState)) return null; dispatch(unmuteAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => { - dispatch(unmuteAccountSuccess(response.data)); - }).catch(error => { - dispatch(unmuteAccountFail(id, error)); - }); + return api(getState) + .post(`/api/v1/accounts/${id}/unmute`) + .then(response => dispatch(unmuteAccountSuccess(response.data))) + .catch(error => dispatch(unmuteAccountFail(error))); }; } @@ -459,29 +455,27 @@ export function unmuteAccountFail(error) { export function subscribeAccount(id, notifications) { return (dispatch, getState) => { - if (!isLoggedIn(getState)) return; + if (!isLoggedIn(getState)) return null; dispatch(subscribeAccountRequest(id)); - api(getState).post(`/api/v1/pleroma/accounts/${id}/subscribe`, { notifications }).then(response => { - dispatch(subscribeAccountSuccess(response.data)); - }).catch(error => { - dispatch(subscribeAccountFail(id, error)); - }); + return api(getState) + .post(`/api/v1/pleroma/accounts/${id}/subscribe`, { notifications }) + .then(response => dispatch(subscribeAccountSuccess(response.data))) + .catch(error => dispatch(subscribeAccountFail(error))); }; } export function unsubscribeAccount(id) { return (dispatch, getState) => { - if (!isLoggedIn(getState)) return; + if (!isLoggedIn(getState)) return null; dispatch(unsubscribeAccountRequest(id)); - api(getState).post(`/api/v1/pleroma/accounts/${id}/unsubscribe`).then(response => { - dispatch(unsubscribeAccountSuccess(response.data)); - }).catch(error => { - dispatch(unsubscribeAccountFail(id, error)); - }); + return api(getState) + .post(`/api/v1/pleroma/accounts/${id}/unsubscribe`) + .then(response => dispatch(unsubscribeAccountSuccess(response.data))) + .catch(error => dispatch(unsubscribeAccountFail(error))); }; }