diff --git a/frontend/app/store/user/actions.test.ts b/frontend/app/store/user/actions.test.ts index 72b32542..068c47d2 100644 --- a/frontend/app/store/user/actions.test.ts +++ b/frontend/app/store/user/actions.test.ts @@ -4,8 +4,8 @@ import { LS_HIDDEN_USERS_KEY } from '@app/common/constants'; import { COMMENTS_PATCH } from '@app/store/comments/types'; import INITIAL_STORE from './__mocks__/comments-store.json'; -import { setVerifiedStatus, hideUser, unhideUser, unblockUser } from './actions'; -import { USER_UNHIDE, USER_HIDE, USER_UNBAN } from './types'; +import { setVerifiedStatus, hideUser, unhideUser, unblockUser, fetchBlockedUsers, fetchHiddenUsers } from './actions'; +import { USER_UNHIDE, USER_HIDE, USER_UNBAN, USER_BANLIST_SET, USER_HIDELIST_SET } from './types'; describe('store user actions', () => { beforeAll(() => { @@ -15,6 +15,38 @@ describe('store user actions', () => { require('jest-fetch-mock').resetMocks(); }); + test('fetchBlockedUsers', async () => { + const store = mockStore(INITIAL_STORE); + + await store.dispatch(fetchBlockedUsers()); + + const actions = store.getActions(); + + expect(actions[0]).toEqual({ type: USER_BANLIST_SET, list: [] }); + }); + + test('fetchHiddenUsers', async () => { + const store = mockStore(INITIAL_STORE); + + await store.dispatch(fetchHiddenUsers()); + + const actions = store.getActions(); + + expect(actions[0]).toEqual({ type: USER_HIDELIST_SET, payload: {} }); + }); + + test('fetchHiddenUsers with data', async () => { + const data = { '1': { id: '1' }, '2': { id: '2' } }; + const store = mockStore(INITIAL_STORE); + + localStorage.setItem(LS_HIDDEN_USERS_KEY, JSON.stringify(data)); + await store.dispatch(fetchHiddenUsers()); + + const actions = store.getActions(); + + expect(actions[0]).toEqual({ type: USER_HIDELIST_SET, payload: data }); + }); + test('setVerifiedStatus', async () => { const store = mockStore(INITIAL_STORE); @@ -45,8 +77,9 @@ describe('store user actions', () => { localStorage.clear(); }); - const store = mockStore(INITIAL_STORE); test('hideUser', async () => { + const store = mockStore(INITIAL_STORE); + await store.dispatch(hideUser({ id: '1' } as User)); const actions = store.getActions(); @@ -58,12 +91,14 @@ describe('store user actions', () => { }); test('unhideUser', async () => { + const store = mockStore(INITIAL_STORE); + localStorage.setItem(LS_HIDDEN_USERS_KEY, JSON.stringify({ '1': { id: '1' } })); await store.dispatch(unhideUser('1')); const actions = store.getActions(); - expect(actions[2]).toEqual({ type: USER_UNHIDE, id: '1' }); + expect(actions[0]).toEqual({ type: USER_UNHIDE, id: '1' }); expect(localStorage.getItem).toHaveBeenCalledWith(LS_HIDDEN_USERS_KEY); expect(localStorage.setItem).toHaveBeenCalledWith(LS_HIDDEN_USERS_KEY, JSON.stringify({})); }); diff --git a/frontend/app/store/user/actions.ts b/frontend/app/store/user/actions.ts index 608d6a58..fab12283 100644 --- a/frontend/app/store/user/actions.ts +++ b/frontend/app/store/user/actions.ts @@ -1,6 +1,9 @@ import * as api from '@app/common/api'; import { User, BlockedUser, AuthProvider, BlockTTL } from '@app/common/types'; import { ttlToTime } from '@app/utils/ttl-to-time'; +import getHiddenUsers from '@app/utils/get-hidden-users'; +import { LS_HIDDEN_USERS_KEY } from '@app/common/constants'; +import { setItem } from '@app/common/local-storage'; import { StoreAction } from '../index'; import { @@ -15,8 +18,6 @@ import { USER_SET_ACTION, } from './types'; import { unsetCommentMode, fetchComments } from '../comments/actions'; -import { IS_STORAGE_AVAILABLE, LS_HIDDEN_USERS_KEY } from '@app/common/constants'; -import { setItem, getItem } from '@app/common/local-storage'; import { updateProvider } from '../provider/actions'; import { COMMENTS_PATCH } from '../comments/types'; @@ -51,10 +52,9 @@ export const logout = (): StoreAction> => async dispatch => { export const fetchBlockedUsers = (): StoreAction> => async dispatch => { const list = (await api.getBlocked()) || []; - dispatch({ - type: USER_BANLIST_SET, - list, - }); + + dispatch({ type: USER_BANLIST_SET, list }); + return list; }; @@ -91,14 +91,13 @@ export const unblockUser = (id: User['id']): StoreAction> => async }; export const fetchHiddenUsers = (): StoreAction => dispatch => { - if (!IS_STORAGE_AVAILABLE) return; + const hiddenUsers = getHiddenUsers(); - const hiddenUsers = JSON.parse(getItem(LS_HIDDEN_USERS_KEY) || '{}'); dispatch({ type: USER_HIDELIST_SET, payload: hiddenUsers }); }; export const hideUser = (user: User): StoreAction => (dispatch, getState) => { - const hiddenUsers = JSON.parse(getItem(LS_HIDDEN_USERS_KEY) || '{}'); + const hiddenUsers = getHiddenUsers(); hiddenUsers[user.id] = user; setItem(LS_HIDDEN_USERS_KEY, JSON.stringify(hiddenUsers)); @@ -112,13 +111,13 @@ export const hideUser = (user: User): StoreAction => (dispatch, getState) }; export const unhideUser = (userId: string): StoreAction => (dispatch, _getState) => { - const hiddenUsers = JSON.parse(getItem(LS_HIDDEN_USERS_KEY) || '{}'); + const hiddenUsers = getHiddenUsers(); if (Object.prototype.hasOwnProperty.call(hiddenUsers, userId)) { delete hiddenUsers[userId]; } - setItem(LS_HIDDEN_USERS_KEY, JSON.stringify(hiddenUsers)); + setItem(LS_HIDDEN_USERS_KEY, JSON.stringify(hiddenUsers)); dispatch({ type: USER_UNHIDE, id: userId }); // no need for comments patch as comments will be refetched after action diff --git a/frontend/app/utils/get-hidden-users.test.ts b/frontend/app/utils/get-hidden-users.test.ts new file mode 100644 index 00000000..cac621b6 --- /dev/null +++ b/frontend/app/utils/get-hidden-users.test.ts @@ -0,0 +1,33 @@ +import { LS_HIDDEN_USERS_KEY } from '@app/common/constants'; + +import getHiddenUsers from './get-hidden-users'; + +describe('getHiddenUsers', () => { + it('should get hidden users from local storage', async () => { + localStorage.setItem(LS_HIDDEN_USERS_KEY, JSON.stringify([])); + expect(getHiddenUsers()).toEqual({}); + }); + + it('should return empty object with array in local storage', async () => { + localStorage.setItem(LS_HIDDEN_USERS_KEY, JSON.stringify([])); + expect(getHiddenUsers()).toEqual({}); + }); + + it('should return empty object with null in local storage', async () => { + localStorage.setItem(LS_HIDDEN_USERS_KEY, JSON.stringify(null)); + expect(getHiddenUsers()).toEqual({}); + }); + + it('should return empty object with string in local storage', async () => { + localStorage.setItem(LS_HIDDEN_USERS_KEY, JSON.stringify('string')); + expect(getHiddenUsers()).toEqual({}); + }); + + test('should return empty object and log error with invalid JSON in localStorage', async () => { + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + + localStorage.setItem(LS_HIDDEN_USERS_KEY, '"{:"""'); + expect(getHiddenUsers()).toEqual({}); + expect(consoleSpy).toHaveBeenCalled(); + }); +}); diff --git a/frontend/app/utils/get-hidden-users.ts b/frontend/app/utils/get-hidden-users.ts new file mode 100644 index 00000000..4eae0431 --- /dev/null +++ b/frontend/app/utils/get-hidden-users.ts @@ -0,0 +1,17 @@ +import { User } from '@app/common/types'; +import { getItem } from '@app/common/local-storage'; +import { LS_HIDDEN_USERS_KEY } from '@app/common/constants'; + +export default function getHiddenUsers() { + try { + const hiddenUsers: Record = JSON.parse(getItem(LS_HIDDEN_USERS_KEY) || '{}'); + + if (typeof hiddenUsers === 'object' && hiddenUsers !== null && !Array.isArray(hiddenUsers)) { + return hiddenUsers; + } + } catch (e) { + console.error('incorrect hidden user data in local storage', e); // eslint-disable-line no-console + } + + return {}; +}