diff --git a/lib/reducers/calendar-filters-reducer.js b/lib/reducers/calendar-filters-reducer.js index f8ccfe378..3cb82cd7f 100644 --- a/lib/reducers/calendar-filters-reducer.js +++ b/lib/reducers/calendar-filters-reducer.js @@ -1,177 +1,205 @@ // @flow import { updateCalendarCommunityFilter, clearCalendarCommunityFilter, } from '../actions/community-actions.js'; import { siweAuthActionTypes } from '../actions/siwe-actions.js'; import { newThreadActionTypes, joinThreadActionTypes, leaveThreadActionTypes, deleteThreadActionTypes, } from '../actions/thread-actions.js'; import { logOutActionTypes, deleteKeyserverAccountActionTypes, logInActionTypes, registerActionTypes, } from '../actions/user-actions.js'; import { filteredThreadIDs, nonThreadCalendarFilters, nonExcludeDeletedCalendarFilters, } from '../selectors/calendar-filter-selectors.js'; import { threadInFilterList } from '../shared/thread-utils.js'; import { updateSpecs } from '../shared/updates/update-specs.js'; import { type CalendarFilter, defaultCalendarFilters, updateCalendarThreadFilter, clearCalendarThreadFilter, setCalendarDeletedFilter, calendarThreadFilterTypes, } from '../types/filter-types.js'; import type { BaseAction } from '../types/redux-types.js'; import { fullStateSyncActionType, incrementalStateSyncActionType, } from '../types/socket-types.js'; import type { RawThreadInfos, ThreadStore } from '../types/thread-types.js'; import { type ClientUpdateInfo, processUpdatesActionType, } from '../types/update-types.js'; -import { setNewSessionActionType } from '../utils/action-utils.js'; +import { + extractKeyserverIDFromID, + setNewSessionActionType, +} from '../utils/action-utils.js'; import { filterThreadIDsBelongingToCommunity } from '../utils/drawer-utils.react.js'; export default function reduceCalendarFilters( state: $ReadOnlyArray, action: BaseAction, threadStore: ThreadStore, ): $ReadOnlyArray { if ( action.type === logOutActionTypes.success || action.type === deleteKeyserverAccountActionTypes.success || action.type === logInActionTypes.success || action.type === siweAuthActionTypes.success || action.type === registerActionTypes.success || (action.type === setNewSessionActionType && action.payload.sessionChange.cookieInvalidated) ) { return defaultCalendarFilters; } else if (action.type === updateCalendarThreadFilter) { const nonThreadFilters = nonThreadCalendarFilters(state); return [ ...nonThreadFilters, { type: calendarThreadFilterTypes.THREAD_LIST, threadIDs: action.payload.threadIDs, }, ]; } else if (action.type === clearCalendarThreadFilter) { return nonThreadCalendarFilters(state); } else if (action.type === setCalendarDeletedFilter) { const otherFilters = nonExcludeDeletedCalendarFilters(state); if (action.payload.includeDeleted && otherFilters.length === state.length) { // Attempting to remove NOT_DELETED filter, but it doesn't exist return state; } else if (action.payload.includeDeleted) { // Removing NOT_DELETED filter return otherFilters; } else if (otherFilters.length < state.length) { // Attempting to add NOT_DELETED filter, but it already exists return state; } else { // Adding NOT_DELETED filter return [...state, { type: calendarThreadFilterTypes.NOT_DELETED }]; } } else if ( action.type === newThreadActionTypes.success || action.type === joinThreadActionTypes.success || action.type === leaveThreadActionTypes.success || action.type === deleteThreadActionTypes.success || action.type === processUpdatesActionType ) { return updateFilterListFromUpdateInfos( state, action.payload.updatesResult.newUpdates, ); } else if (action.type === incrementalStateSyncActionType) { return updateFilterListFromUpdateInfos( state, action.payload.updatesResult.newUpdates, ); } else if (action.type === fullStateSyncActionType) { return removeDeletedThreadIDsFromFilterList( state, action.payload.threadInfos, ); } else if (action.type === updateCalendarCommunityFilter) { const nonThreadFilters = nonThreadCalendarFilters(state); const threadIDs = Array.from( filterThreadIDsBelongingToCommunity( action.payload, threadStore.threadInfos, ), ); return [ ...nonThreadFilters, { type: calendarThreadFilterTypes.THREAD_LIST, threadIDs, }, ]; } else if (action.type === clearCalendarCommunityFilter) { const nonThreadFilters = nonThreadCalendarFilters(state); return nonThreadFilters; } return state; } function updateFilterListFromUpdateInfos( state: $ReadOnlyArray, updateInfos: $ReadOnlyArray, ): $ReadOnlyArray { const currentlyFilteredIDs: ?$ReadOnlySet = filteredThreadIDs(state); if (!currentlyFilteredIDs) { return state; } const newFilteredThreadIDs = updateInfos.reduce( (reducedFilteredThreadIDs, update) => { const { reduceCalendarThreadFilters } = updateSpecs[update.type]; return reduceCalendarThreadFilters ? reduceCalendarThreadFilters(reducedFilteredThreadIDs, update) : reducedFilteredThreadIDs; }, currentlyFilteredIDs, ); if (currentlyFilteredIDs !== newFilteredThreadIDs) { return [ ...nonThreadCalendarFilters(state), { type: 'threads', threadIDs: [...newFilteredThreadIDs] }, ]; } return state; } -function removeDeletedThreadIDsFromFilterList( +function filterThreadIDsInFilterList( state: $ReadOnlyArray, - threadInfos: RawThreadInfos, + filterCondition: (threadID: string) => boolean, ): $ReadOnlyArray { const currentlyFilteredIDs = filteredThreadIDs(state); if (!currentlyFilteredIDs) { return state; } - const filtered = [...currentlyFilteredIDs].filter(threadID => - threadInFilterList(threadInfos[threadID]), - ); + const filtered = [...currentlyFilteredIDs].filter(filterCondition); + if (filtered.length < currentlyFilteredIDs.size) { return [ ...nonThreadCalendarFilters(state), { type: 'threads', threadIDs: filtered }, ]; } return state; } + +function removeDeletedThreadIDsFromFilterList( + state: $ReadOnlyArray, + threadInfos: RawThreadInfos, +): $ReadOnlyArray { + const filterCondition = (threadID: string) => + threadInFilterList(threadInfos[threadID]); + + return filterThreadIDsInFilterList(state, filterCondition); +} + +function removeKeyserverThreadIDsFromFilterList( + state: $ReadOnlyArray, + keyserverIDs: $ReadOnlyArray, +): $ReadOnlyArray { + const keyserverIDsSet = new Set(keyserverIDs); + const filterCondition = (threadID: string) => + !keyserverIDsSet.has(extractKeyserverIDFromID(threadID)); + + return filterThreadIDsInFilterList(state, filterCondition); +} + +export { + removeDeletedThreadIDsFromFilterList, + removeKeyserverThreadIDsFromFilterList, +}; diff --git a/lib/reducers/calendar-filters-reducer.test.js b/lib/reducers/calendar-filters-reducer.test.js new file mode 100644 index 000000000..f3db9178f --- /dev/null +++ b/lib/reducers/calendar-filters-reducer.test.js @@ -0,0 +1,173 @@ +// @flow + +import { + removeDeletedThreadIDsFromFilterList, + removeKeyserverThreadIDsFromFilterList, +} from './calendar-filters-reducer.js'; +import type { ThreadStore } from '../types/thread-types'; + +const calendarFilters = [ + { type: 'threads', threadIDs: ['256|1', '256|83815'] }, +]; + +const threadStore: ThreadStore = { + threadInfos: { + '256|1': { + id: '256|1', + type: 12, + name: 'GENESIS', + description: '', + color: '648caa', + creationTime: 1689091732528, + parentThreadID: null, + repliesCount: 0, + containingThreadID: null, + community: null, + pinnedCount: 0, + minimallyEncoded: true, + members: [ + { + id: '256', + role: '256|83796', + permissions: '3f73ff', + isSender: true, + minimallyEncoded: true, + }, + { + id: '83810', + role: '256|83795', + permissions: '3', + isSender: false, + minimallyEncoded: true, + }, + ], + roles: { + '256|83795': { + id: '256|83795', + name: 'Members', + permissions: ['000', '010', '005', '015', '0a7'], + isDefault: true, + minimallyEncoded: true, + }, + '256|83796': { + id: '256|83796', + name: 'Admins', + permissions: ['000', '010', '005', '015', '0a7'], + isDefault: false, + minimallyEncoded: true, + }, + }, + currentUser: { + role: '256|83795', + permissions: '3', + subscription: { + home: true, + pushNotifs: true, + }, + unread: false, + minimallyEncoded: true, + }, + }, + '256|83815': { + id: '256|83815', + type: 7, + name: '', + description: + 'This is your private chat, where you can set reminders and jot notes in private!', + color: '57697f', + creationTime: 1689248242797, + parentThreadID: '256|1', + repliesCount: 0, + containingThreadID: '256|1', + community: '256|1', + pinnedCount: 0, + minimallyEncoded: true, + members: [ + { + id: '256', + role: null, + permissions: '2c7fff', + isSender: false, + minimallyEncoded: true, + }, + { + id: '83810', + role: '256|83816', + permissions: '3026f', + isSender: true, + minimallyEncoded: true, + }, + ], + roles: { + '256|83816': { + id: '256|83816', + name: 'Members', + permissions: ['000', '010', '005', '015', '0a7'], + isDefault: true, + minimallyEncoded: true, + }, + }, + currentUser: { + role: null, + permissions: '3026f', + subscription: { + home: true, + pushNotifs: true, + }, + unread: false, + minimallyEncoded: true, + }, + }, + }, +}; + +describe('removeDeletedThreadIDsFromFilterList', () => { + it('Removes threads the user is not a member of anymore', () => { + expect( + removeDeletedThreadIDsFromFilterList( + calendarFilters, + threadStore.threadInfos, + ), + ).toEqual([{ type: 'threads', threadIDs: ['256|1'] }]); + }); +}); + +const threadIDsToStay = [ + '256|1', + '256|2', + '200|4', + '300|5', + '300|6', + '256|100', +]; +const keyserverToRemove1 = '100'; +const keyserverToRemove2 = '400'; +const threadIDsToRemove = [ + keyserverToRemove1 + '|3', + keyserverToRemove1 + '|7', + keyserverToRemove2 + '|8', +]; +const calendarFiltersState = [ + { type: 'not_deleted' }, + { + type: 'threads', + threadIDs: [...threadIDsToStay, ...threadIDsToRemove], + }, +]; + +describe('removeKeyserverThreadIDsFromFilterList', () => { + it('Removes threads from the given keyserver', () => { + expect( + removeKeyserverThreadIDsFromFilterList(calendarFiltersState, [ + keyserverToRemove1, + keyserverToRemove2, + ]), + ).toEqual([ + { type: 'not_deleted' }, + { + type: 'threads', + threadIDs: threadIDsToStay, + }, + ]); + }); +});