From 7d91bb7ff99d7d53cbde4ce0673efba60316562e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 11 Mar 2022 13:51:34 -0600 Subject: [PATCH] Refactor Notifications reducer with Immutable.Record, start rewriting tests --- .../reducers/__tests__/notifications-test.js | 1009 +++++++---------- app/soapbox/reducers/notifications.js | 66 +- 2 files changed, 446 insertions(+), 629 deletions(-) diff --git a/app/soapbox/reducers/__tests__/notifications-test.js b/app/soapbox/reducers/__tests__/notifications-test.js index 5c65341cc..21410a0f9 100644 --- a/app/soapbox/reducers/__tests__/notifications-test.js +++ b/app/soapbox/reducers/__tests__/notifications-test.js @@ -1,4 +1,8 @@ -import { Map as ImmutableMap, OrderedMap as ImmutableOrderedMap, fromJS } from 'immutable'; +import { + Map as ImmutableMap, + OrderedMap as ImmutableOrderedMap, + Record as ImmutableRecord, +} from 'immutable'; import { take } from 'lodash'; import intlMessages from 'soapbox/__fixtures__/intlMessages.json'; @@ -27,24 +31,27 @@ import { TIMELINE_DELETE } from 'soapbox/actions/timelines'; import reducer from '../notifications'; +const initialState = reducer(undefined, {}); + describe('notifications reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual(ImmutableMap({ - items: ImmutableOrderedMap(), + const expected = { + items: {}, hasMore: true, top: false, unread: 0, isLoading: false, - queuedNotifications: ImmutableOrderedMap(), + queuedNotifications: {}, totalQueuedNotificationsCount: 0, lastRead: -1, - })); + }; + + expect(ImmutableRecord.isRecord(initialState)).toBe(true); + expect(initialState.toJS()).toMatchObject(expected); }); describe('NOTIFICATIONS_EXPAND_SUCCESS', () => { it('imports the notifications', () => { - const state = undefined; - const action = { type: NOTIFICATIONS_EXPAND_SUCCESS, notifications: take(notifications, 3), @@ -52,47 +59,17 @@ describe('notifications reducer', () => { skipLoading: true, }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap([ - ['10744', ImmutableMap({ - id: '10744', - type: 'pleroma:emoji_reaction', - account: '9vMAje101ngtjlMj7w', - target: null, - created_at: '2020-06-10T02:54:39.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: '😢', - chat_message: undefined, - })], - ['10743', ImmutableMap({ - id: '10743', - type: 'favourite', - account: '9v5c6xSEgAi3Zu1Lv6', - target: null, - created_at: '2020-06-10T02:51:05.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ['10741', ImmutableMap({ - id: '10741', - type: 'favourite', - account: '9v5cKMOPGqPcgfcWp6', - target: null, - created_at: '2020-06-10T02:05:06.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ]), - hasMore: false, - top: false, - unread: 0, - isLoading: false, - queuedNotifications: ImmutableOrderedMap(), - totalQueuedNotificationsCount: 0, - lastRead: -1, - })); + const result = reducer(undefined, action); + + // The items are parsed as records + expect(ImmutableOrderedMap.isOrderedMap(result.items)).toBe(true); + expect(ImmutableRecord.isRecord(result.items.get('10743'))).toBe(true); + + // We can get an item + expect(result.items.get('10744').emoji).toEqual('😢'); + + // hasMore is set to false because `next` is null + expect(result.hasMore).toBe(false); }); it('drops invalid notifications', () => { @@ -109,596 +86,440 @@ describe('notifications reducer', () => { skipLoading: true, }; - const expected = ImmutableOrderedMap([ - ['4', fromJS({ - id: '4', - type: 'mention', - account: '7', - target: null, - created_at: undefined, - status: 'a', - emoji: undefined, - chat_message: undefined, - })], - ]); + const result = reducer(undefined, action); - expect(reducer(undefined, action).get('items')).toEqual(expected); + // Only '4' is valid + expect(result.items.size).toEqual(1); + expect(result.items.get('4').id).toEqual('4'); }); }); - it('should handle NOTIFICATIONS_EXPAND_REQUEST', () => { - const state = ImmutableMap({ - isLoading: false, + describe('NOTIFICATIONS_EXPAND_REQUEST', () => { + it('sets isLoading to true', () => { + const state = initialState.set('isLoading', false); + const action = { type: NOTIFICATIONS_EXPAND_REQUEST }; + + expect(reducer(state, action).isLoading).toBe(true); }); - const action = { - type: NOTIFICATIONS_EXPAND_REQUEST, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - isLoading: true, - })); }); - it('should handle NOTIFICATIONS_EXPAND_FAIL', () => { - const state = ImmutableMap({ - isLoading: true, + describe('NOTIFICATIONS_EXPAND_FAIL', () => { + it('sets isLoading to false', () => { + const state = initialState.set('isLoading', true); + const action = { type: NOTIFICATIONS_EXPAND_FAIL }; + + expect(reducer(state, action).isLoading).toBe(false); }); - const action = { - type: NOTIFICATIONS_EXPAND_FAIL, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - isLoading: false, - })); }); - it('should handle NOTIFICATIONS_FILTER_SET', () => { - const state = ImmutableMap({ - items: ImmutableOrderedMap([ - ['10744', ImmutableMap({ - id: '10744', - type: 'pleroma:emoji_reaction', - account: '9vMAje101ngtjlMj7w', - target: null, - created_at: '2020-06-10T02:54:39.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: '😢', - chat_message: undefined, - })], - ['10743', ImmutableMap({ - id: '10743', - type: 'favourite', - account: '9v5c6xSEgAi3Zu1Lv6', - target: null, - created_at: '2020-06-10T02:51:05.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ['10741', ImmutableMap({ - id: '10741', - type: 'favourite', - account: '9v5cKMOPGqPcgfcWp6', - target: null, - created_at: '2020-06-10T02:05:06.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ]), - hasMore: false, - top: false, - unread: 1, - isLoading: false, - queuedNotifications: ImmutableOrderedMap(), - totalQueuedNotificationsCount: 0, - lastRead: -1, + describe('NOTIFICATIONS_FILTER_SET', () => { + it('clears the items', () => { + const actions = [{ + type: NOTIFICATIONS_EXPAND_SUCCESS, + notifications: [ + { id: '1', type: 'mention', status: { id: '4' }, account: { id: '7' } }, + { id: '2', type: 'mention', status: { id: '5' }, account: { id: '8' } }, + { id: '3', type: 'mention', status: { id: '6' }, account: { id: '9' } }, + ], + next: null, + skipLoading: true, + }, { + type: NOTIFICATIONS_FILTER_SET, + }]; + + // Setup by expanding, then calling `NOTIFICATIONS_FILTER_SET` + const result = actions.reduce((state, action) => reducer(state, action), initialState); + + // Setting the filter wipes notifications + expect(result.items.isEmpty()).toBe(true); + }); + + it('sets hasMore to true', () => { + const state = initialState.set('hasMore', false); + const action = { type: NOTIFICATIONS_FILTER_SET }; + const result = reducer(state, action); + + expect(result.hasMore).toBe(true); }); - const action = { - type: NOTIFICATIONS_FILTER_SET, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap(), - hasMore: true, - top: false, - unread: 1, - isLoading: false, - queuedNotifications: ImmutableOrderedMap(), - totalQueuedNotificationsCount: 0, - lastRead: -1, - })); }); - it('should handle NOTIFICATIONS_SCROLL_TOP by changing unread to 0 when top = true', () => { - const state = ImmutableMap({ - unread: 1, + describe('NOTIFICATIONS_SCROLL_TOP', () => { + it('resets `unread` counter to 0 when top is true (ie, scrolled to the top)', () => { + const state = initialState.set('unread', 1); + const action = { type: NOTIFICATIONS_SCROLL_TOP, top: true }; + const result = reducer(state, action); + + expect(result.unread).toEqual(0); + expect(result.top).toBe(true); + }); + + it('leaves `unread` alone when top is false (ie, not scrolled to top)', () => { + const state = initialState.set('unread', 3); + const action = { type: NOTIFICATIONS_SCROLL_TOP, top: false }; + const result = reducer(state, action); + + expect(result.unread).toEqual(3); + expect(result.top).toBe(false); }); - const action = { - type: NOTIFICATIONS_SCROLL_TOP, - top: true, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - unread: 0, - top: true, - })); }); - it('should handle NOTIFICATIONS_SCROLL_TOP by not changing unread val when top = false', () => { - const state = ImmutableMap({ - unread: 3, + describe('NOTIFICATIONS_UPDATE', () => { + it('imports the notification', () => { + const action = { type: NOTIFICATIONS_UPDATE, notification }; + const result = reducer(initialState, action); + + expect(result.items.get('10743').type).toEqual('favourite'); + }); + + it('increments `unread` counter when top is false', () => { + const action = { type: NOTIFICATIONS_UPDATE, notification }; + const result = reducer(initialState, action); + + expect(result.unread).toEqual(1); }); - const action = { - type: NOTIFICATIONS_SCROLL_TOP, - top: false, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - unread: 3, - top: false, - })); }); - it('should handle NOTIFICATIONS_UPDATE, when top = false, increment unread', () => { - const state = ImmutableMap({ - items: ImmutableOrderedMap(), - top: false, - unread: 1, - }); - const action = { - type: NOTIFICATIONS_UPDATE, - notification: notification, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap([ - ['10743', ImmutableMap({ - id: '10743', - type: 'favourite', - account: '9v5c6xSEgAi3Zu1Lv6', - target: null, - created_at: '2020-06-10T02:51:05.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ]), - top: false, - unread: 2, - })); - }); - - it('should handle NOTIFICATIONS_UPDATE_QUEUE', () => { - const state = ImmutableMap({ - items: ImmutableOrderedMap(), - queuedNotifications: ImmutableOrderedMap(), - totalQueuedNotificationsCount: 0, - }); - const action = { - type: NOTIFICATIONS_UPDATE_QUEUE, - notification: notification, - intlMessages: intlMessages, - intlLocale: 'en', - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap(), - queuedNotifications: ImmutableOrderedMap([[notification.id, { - notification: notification, - intlMessages: intlMessages, + describe('NOTIFICATIONS_UPDATE_QUEUE', () => { + it('adds the notification to the queue (and increases the counter)', () => { + const action = { + type: NOTIFICATIONS_UPDATE_QUEUE, + notification, + intlMessages, intlLocale: 'en', - }]]), - totalQueuedNotificationsCount: 1, - })); - }); + }; - it('should handle NOTIFICATIONS_DEQUEUE', () => { - const state = ImmutableMap({ - items: ImmutableOrderedMap(), - queuedNotifications: take(notifications, 1), - totalQueuedNotificationsCount: 1, + const result = reducer(initialState, action); + + // Doesn't add it as a regular item + expect(result.items.isEmpty()).toBe(true); + + // Adds it to the queued items + expect(result.queuedNotifications.size).toEqual(1); + expect(result.totalQueuedNotificationsCount).toEqual(1); + expect(result.queuedNotifications.getIn(['10743', 'notification', 'type'])).toEqual('favourite'); }); - const action = { - type: NOTIFICATIONS_DEQUEUE, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap(), - queuedNotifications: ImmutableOrderedMap(), - totalQueuedNotificationsCount: 0, - })); }); - it('should handle NOTIFICATIONS_EXPAND_SUCCESS with non-empty items and next set true', () => { - const state = ImmutableMap({ - items: ImmutableOrderedMap([ - ['10734', ImmutableMap({ - id: '10734', - type: 'pleroma:emoji_reaction', - account: '9vMAje101ngtjlMj7w', - target: null, - created_at: '2020-06-10T02:54:39.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: '😢', - chat_message: undefined, - })], - ]), - unread: 1, - hasMore: true, - isLoading: false, + describe('NOTIFICATIONS_DEQUEUE', () => { + it('resets the queued counter to 0', () => { + const state = initialState.set('totalQueuedNotificationsCount', 1); + const action = { type: NOTIFICATIONS_DEQUEUE }; + const result = reducer(state, action); + + expect(result.totalQueuedNotificationsCount).toEqual(0); }); - const action = { - type: NOTIFICATIONS_EXPAND_SUCCESS, - notifications: take(notifications, 3), - next: true, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap([ - ['10744', ImmutableMap({ - id: '10744', - type: 'pleroma:emoji_reaction', - account: '9vMAje101ngtjlMj7w', - target: null, - created_at: '2020-06-10T02:54:39.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: '😢', - chat_message: undefined, - })], - ['10743', ImmutableMap({ - id: '10743', - type: 'favourite', - account: '9v5c6xSEgAi3Zu1Lv6', - target: null, - created_at: '2020-06-10T02:51:05.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ['10741', ImmutableMap({ - id: '10741', - type: 'favourite', - account: '9v5cKMOPGqPcgfcWp6', - target: null, - created_at: '2020-06-10T02:05:06.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ['10734', ImmutableMap({ - id: '10734', - type: 'pleroma:emoji_reaction', - account: '9vMAje101ngtjlMj7w', - target: null, - created_at: '2020-06-10T02:54:39.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: '😢', - chat_message: undefined, - })], - ]), - unread: 1, - hasMore: true, - isLoading: false, - })); }); - it('should handle NOTIFICATIONS_EXPAND_SUCCESS with empty items and next set true', () => { - const state = ImmutableMap({ - items: ImmutableOrderedMap(), - unread: 1, - hasMore: true, - isLoading: false, + describe('NOTIFICATIONS_EXPAND_SUCCESS', () => { + it('with non-empty items and next set true', () => { + const state = ImmutableMap({ + items: ImmutableOrderedMap([ + ['10734', ImmutableMap({ + id: '10734', + type: 'pleroma:emoji_reaction', + account: '9vMAje101ngtjlMj7w', + target: null, + created_at: '2020-06-10T02:54:39.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: '😢', + chat_message: null, + })], + ]), + unread: 1, + hasMore: true, + isLoading: false, + }); + + const action = { + type: NOTIFICATIONS_EXPAND_SUCCESS, + notifications: take(notifications, 3), + next: true, + }; + + const expected = ImmutableMap({ + items: ImmutableOrderedMap([ + ['10744', ImmutableMap({ + id: '10744', + type: 'pleroma:emoji_reaction', + account: '9vMAje101ngtjlMj7w', + target: null, + created_at: '2020-06-10T02:54:39.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: '😢', + chat_message: null, + })], + ['10743', ImmutableMap({ + id: '10743', + type: 'favourite', + account: '9v5c6xSEgAi3Zu1Lv6', + target: null, + created_at: '2020-06-10T02:51:05.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ['10741', ImmutableMap({ + id: '10741', + type: 'favourite', + account: '9v5cKMOPGqPcgfcWp6', + target: null, + created_at: '2020-06-10T02:05:06.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ['10734', ImmutableMap({ + id: '10734', + type: 'pleroma:emoji_reaction', + account: '9vMAje101ngtjlMj7w', + target: null, + created_at: '2020-06-10T02:54:39.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: '😢', + chat_message: null, + })], + ]), + unread: 1, + hasMore: true, + isLoading: false, + }); + + expect(reducer(state, action).toJS()).toEqual(expected.toJS()); }); - const action = { - type: NOTIFICATIONS_EXPAND_SUCCESS, - notifications: take(notifications, 3), - next: true, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap([ - ['10744', ImmutableMap({ - id: '10744', - type: 'pleroma:emoji_reaction', - account: '9vMAje101ngtjlMj7w', - target: null, - created_at: '2020-06-10T02:54:39.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: '😢', - chat_message: undefined, - })], - ['10743', ImmutableMap({ - id: '10743', - type: 'favourite', - account: '9v5c6xSEgAi3Zu1Lv6', - target: null, - created_at: '2020-06-10T02:51:05.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ['10741', ImmutableMap({ - id: '10741', - type: 'favourite', - account: '9v5cKMOPGqPcgfcWp6', - target: null, - created_at: '2020-06-10T02:05:06.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ]), - unread: 1, - hasMore: true, - isLoading: false, - })); - }); - it('should handle ACCOUNT_BLOCK_SUCCESS', () => { - const state = ImmutableMap({ - items: ImmutableOrderedMap([ - ['10744', ImmutableMap({ - id: '10744', - type: 'pleroma:emoji_reaction', - account: '9vMAje101ngtjlMj7w', - target: null, - created_at: '2020-06-10T02:54:39.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: '😢', - chat_message: undefined, - })], - ['10743', ImmutableMap({ - id: '10743', - type: 'favourite', - account: '9v5c6xSEgAi3Zu1Lv6', - target: null, - created_at: '2020-06-10T02:51:05.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ['10741', ImmutableMap({ - id: '10741', - type: 'favourite', - account: '9v5cKMOPGqPcgfcWp6', - target: null, - created_at: '2020-06-10T02:05:06.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ]), + it('with empty items and next set true', () => { + const state = ImmutableMap({ + items: ImmutableOrderedMap(), + unread: 1, + hasMore: true, + isLoading: false, + }); + + const action = { + type: NOTIFICATIONS_EXPAND_SUCCESS, + notifications: take(notifications, 3), + next: true, + }; + + const expected = ImmutableMap({ + items: ImmutableOrderedMap([ + ['10744', ImmutableMap({ + id: '10744', + type: 'pleroma:emoji_reaction', + account: '9vMAje101ngtjlMj7w', + target: null, + created_at: '2020-06-10T02:54:39.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: '😢', + chat_message: null, + })], + ['10743', ImmutableMap({ + id: '10743', + type: 'favourite', + account: '9v5c6xSEgAi3Zu1Lv6', + target: null, + created_at: '2020-06-10T02:51:05.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ['10741', ImmutableMap({ + id: '10741', + type: 'favourite', + account: '9v5cKMOPGqPcgfcWp6', + target: null, + created_at: '2020-06-10T02:05:06.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ]), + unread: 1, + hasMore: true, + isLoading: false, + }); + + expect(reducer(state, action).toJS()).toEqual(expected.toJS()); }); - const action = { - type: ACCOUNT_BLOCK_SUCCESS, - relationship: relationship, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap([ - ['10743', ImmutableMap({ - id: '10743', - type: 'favourite', - account: '9v5c6xSEgAi3Zu1Lv6', - target: null, - created_at: '2020-06-10T02:51:05.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ['10741', ImmutableMap({ - id: '10741', - type: 'favourite', - account: '9v5cKMOPGqPcgfcWp6', - target: null, - created_at: '2020-06-10T02:05:06.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ]), - })); }); - it('should handle ACCOUNT_MUTE_SUCCESS', () => { - const state = ImmutableMap({ - items: ImmutableOrderedMap([ - ['10744', ImmutableMap({ - id: '10744', - type: 'pleroma:emoji_reaction', - account: '9vMAje101ngtjlMj7w', - target: null, - created_at: '2020-06-10T02:54:39.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: '😢', - chat_message: undefined, - })], - ['10743', ImmutableMap({ - id: '10743', - type: 'favourite', - account: '9v5c6xSEgAi3Zu1Lv6', - target: null, - created_at: '2020-06-10T02:51:05.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ['10741', ImmutableMap({ - id: '10741', - type: 'favourite', - account: '9v5cKMOPGqPcgfcWp6', - target: null, - created_at: '2020-06-10T02:05:06.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ]), + describe('ACCOUNT_BLOCK_SUCCESS', () => { + it('should handle', () => { + const state = ImmutableMap({ + items: ImmutableOrderedMap([ + ['10744', ImmutableMap({ + id: '10744', + type: 'pleroma:emoji_reaction', + account: '9vMAje101ngtjlMj7w', + target: null, + created_at: '2020-06-10T02:54:39.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: '😢', + chat_message: null, + })], + ['10743', ImmutableMap({ + id: '10743', + type: 'favourite', + account: '9v5c6xSEgAi3Zu1Lv6', + target: null, + created_at: '2020-06-10T02:51:05.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ['10741', ImmutableMap({ + id: '10741', + type: 'favourite', + account: '9v5cKMOPGqPcgfcWp6', + target: null, + created_at: '2020-06-10T02:05:06.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ]), + }); + const action = { + type: ACCOUNT_BLOCK_SUCCESS, + relationship, + }; + expect(reducer(state, action)).toEqual(ImmutableMap({ + items: ImmutableOrderedMap([ + ['10743', ImmutableMap({ + id: '10743', + type: 'favourite', + account: '9v5c6xSEgAi3Zu1Lv6', + target: null, + created_at: '2020-06-10T02:51:05.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ['10741', ImmutableMap({ + id: '10741', + type: 'favourite', + account: '9v5cKMOPGqPcgfcWp6', + target: null, + created_at: '2020-06-10T02:05:06.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ]), + })); }); - const action = { - type: ACCOUNT_MUTE_SUCCESS, - relationship: relationship, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap([ - ['10743', ImmutableMap({ - id: '10743', - type: 'favourite', - account: '9v5c6xSEgAi3Zu1Lv6', - target: null, - created_at: '2020-06-10T02:51:05.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ['10741', ImmutableMap({ - id: '10741', - type: 'favourite', - account: '9v5cKMOPGqPcgfcWp6', - target: null, - created_at: '2020-06-10T02:05:06.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ]), - })); }); - it('should handle NOTIFICATIONS_CLEAR', () => { - const state = ImmutableMap({ - items: ImmutableOrderedMap(), - hasMore: true, + describe('ACCOUNT_MUTE_SUCCESS', () => { + it('should handle', () => { + const state = ImmutableMap({ + items: ImmutableOrderedMap([ + ['10744', ImmutableMap({ + id: '10744', + type: 'pleroma:emoji_reaction', + account: '9vMAje101ngtjlMj7w', + target: null, + created_at: '2020-06-10T02:54:39.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: '😢', + chat_message: null, + })], + ['10743', ImmutableMap({ + id: '10743', + type: 'favourite', + account: '9v5c6xSEgAi3Zu1Lv6', + target: null, + created_at: '2020-06-10T02:51:05.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ['10741', ImmutableMap({ + id: '10741', + type: 'favourite', + account: '9v5cKMOPGqPcgfcWp6', + target: null, + created_at: '2020-06-10T02:05:06.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ]), + }); + const action = { + type: ACCOUNT_MUTE_SUCCESS, + relationship: relationship, + }; + expect(reducer(state, action)).toEqual(ImmutableMap({ + items: ImmutableOrderedMap([ + ['10743', ImmutableMap({ + id: '10743', + type: 'favourite', + account: '9v5c6xSEgAi3Zu1Lv6', + target: null, + created_at: '2020-06-10T02:51:05.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ['10741', ImmutableMap({ + id: '10741', + type: 'favourite', + account: '9v5cKMOPGqPcgfcWp6', + target: null, + created_at: '2020-06-10T02:05:06.000Z', + status: '9vvNxoo5EFbbnfdXQu', + emoji: null, + chat_message: null, + })], + ]), + })); }); - const action = { - type: NOTIFICATIONS_CLEAR, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap(), - hasMore: false, - })); }); - it('should handle NOTIFICATIONS_MARK_READ_REQUEST', () => { - const state = ImmutableMap({ - items: ImmutableOrderedMap(), + describe('NOTIFICATIONS_CLEAR', () => { + it('clears the items', () => { + const state = initialState.set('items', ImmutableOrderedMap([['1', {}], ['2', {}]])); + const action = { type: NOTIFICATIONS_CLEAR }; + const result = reducer(state, action); + + expect(result.items.isEmpty()).toBe(true); }); - const action = { - type: NOTIFICATIONS_MARK_READ_REQUEST, - lastRead: 35098814, - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap(), - lastRead: 35098814, - })); }); - it('should handle TIMELINE_DELETE', () => { - const state = ImmutableMap({ - items: ImmutableOrderedMap([ - ['10744', ImmutableMap({ - id: '10744', - type: 'pleroma:emoji_reaction', - account: '9vMAje101ngtjlMj7w', - target: null, - created_at: '2020-06-10T02:54:39.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: '😢', - chat_message: undefined, - })], - ['10743', ImmutableMap({ - id: '10743', - type: 'favourite', - account: '9v5c6xSEgAi3Zu1Lv6', - target: null, - created_at: '2020-06-10T02:51:05.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ['10741', ImmutableMap({ - id: '10741', - type: 'favourite', - account: '9v5cKMOPGqPcgfcWp6', - target: null, - created_at: '2020-06-10T02:05:06.000Z', - status: '9vvNxoo5EFbbnfdXQu', - emoji: undefined, - chat_message: undefined, - })], - ]), + describe('NOTIFICATIONS_MARK_READ_REQUEST', () => { + it('sets lastRead to the one in the action', () => { + const action = { type: NOTIFICATIONS_MARK_READ_REQUEST, lastRead: '1234' }; + const result = reducer(undefined, action); + + expect(result.lastRead).toEqual('1234'); }); - const action = { - type: TIMELINE_DELETE, - id: '9vvNxoo5EFbbnfdXQu', - }; - expect(reducer(state, action)).toEqual(ImmutableMap({ - items: ImmutableOrderedMap(), - })); }); - // Disable for now - // https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/432 - // - // it('should handle TIMELINE_DISCONNECT', () => { - // const state = ImmutableMap({ - // items: ImmutableOrderedSet([ - // ImmutableMap({ - // id: '10744', - // type: 'pleroma:emoji_reaction', - // account: '9vMAje101ngtjlMj7w', - // created_at: '2020-06-10T02:54:39.000Z', - // status: '9vvNxoo5EFbbnfdXQu', - // emoji: '😢', - // chat_message: undefined, - // }), - // ImmutableMap({ - // id: '10743', - // type: 'favourite', - // account: '9v5c6xSEgAi3Zu1Lv6', - // created_at: '2020-06-10T02:51:05.000Z', - // status: '9vvNxoo5EFbbnfdXQu', - // emoji: undefined, - // chat_message: undefined, - // }), - // ImmutableMap({ - // id: '10741', - // type: 'favourite', - // account: '9v5cKMOPGqPcgfcWp6', - // created_at: '2020-06-10T02:05:06.000Z', - // status: '9vvNxoo5EFbbnfdXQu', - // emoji: undefined, - // chat_message: undefined, - // }), - // ]), - // }); - // const action = { - // type: TIMELINE_DISCONNECT, - // timeline: 'home', - // }; - // expect(reducer(state, action)).toEqual(ImmutableMap({ - // items: ImmutableOrderedSet([ - // null, - // ImmutableMap({ - // id: '10744', - // type: 'pleroma:emoji_reaction', - // account: '9vMAje101ngtjlMj7w', - // created_at: '2020-06-10T02:54:39.000Z', - // status: '9vvNxoo5EFbbnfdXQu', - // emoji: '😢', - // chat_message: undefined, - // }), - // ImmutableMap({ - // id: '10743', - // type: 'favourite', - // account: '9v5c6xSEgAi3Zu1Lv6', - // created_at: '2020-06-10T02:51:05.000Z', - // status: '9vvNxoo5EFbbnfdXQu', - // emoji: undefined, - // chat_message: undefined, - // }), - // ImmutableMap({ - // id: '10741', - // type: 'favourite', - // account: '9v5cKMOPGqPcgfcWp6', - // created_at: '2020-06-10T02:05:06.000Z', - // status: '9vvNxoo5EFbbnfdXQu', - // emoji: undefined, - // chat_message: undefined, - // }), - // ]), - // })); - // }); + describe('TIMELINE_DELETE', () => { + it('deletes notifications corresponding to the status ID', () => { + const actions = [{ + type: NOTIFICATIONS_EXPAND_SUCCESS, + notifications: [ + { id: '1', type: 'mention', status: { id: '4' }, account: { id: '7' } }, + { id: '2', type: 'mention', status: { id: '5' }, account: { id: '8' } }, + { id: '3', type: 'mention', status: { id: '6' }, account: { id: '9' } }, + { id: '4', type: 'mention', status: { id: '5' }, account: { id: '7' } }, + ], + next: null, + skipLoading: true, + }, { + type: TIMELINE_DELETE, + id: '5', + }]; + + // Setup by expanding, then calling `NOTIFICATIONS_FILTER_SET` + const result = actions.reduce((state, action) => reducer(state, action), initialState); + + expect(result.items.size).toEqual(2); + expect(result.items.get('5')).toBe(undefined); + }); + }); describe('MARKER_FETCH_SUCCESS', () => { it('sets lastRead', () => { diff --git a/app/soapbox/reducers/notifications.js b/app/soapbox/reducers/notifications.js index 401fe92a9..61600674b 100644 --- a/app/soapbox/reducers/notifications.js +++ b/app/soapbox/reducers/notifications.js @@ -1,10 +1,10 @@ -import { Map as ImmutableMap, OrderedMap as ImmutableOrderedMap, fromJS } from 'immutable'; - import { - MARKER_FETCH_SUCCESS, - MARKER_SAVE_REQUEST, - MARKER_SAVE_SUCCESS, -} from 'soapbox/actions/markers'; + Record as ImmutableRecord, + OrderedMap as ImmutableOrderedMap, + fromJS, +} from 'immutable'; + +import { normalizeNotification } from 'soapbox/normalizers/notification'; import { ACCOUNT_BLOCK_SUCCESS, @@ -12,6 +12,11 @@ import { FOLLOW_REQUEST_AUTHORIZE_SUCCESS, FOLLOW_REQUEST_REJECT_SUCCESS, } from '../actions/accounts'; +import { + MARKER_FETCH_SUCCESS, + MARKER_SAVE_REQUEST, + MARKER_SAVE_SUCCESS, +} from '../actions/markers'; import { NOTIFICATIONS_UPDATE, NOTIFICATIONS_EXPAND_SUCCESS, @@ -27,7 +32,8 @@ import { } from '../actions/notifications'; import { TIMELINE_DELETE } from '../actions/timelines'; -const initialState = ImmutableMap({ +// Record for the whole reducer +const NotificationsRecord = ImmutableRecord({ items: ImmutableOrderedMap(), hasMore: true, top: false, @@ -48,16 +54,16 @@ const comparator = (a, b) => { return 0; }; -const notificationToMap = notification => ImmutableMap({ - id: notification.id, - type: notification.type, - account: notification.account.id, - target: notification.target ? notification.target.id : null, - created_at: notification.created_at, - status: notification.status ? notification.status.id : null, - emoji: notification.emoji, - chat_message: notification.chat_message, -}); +const minifyNotification = notification => { + return notification.mergeWith((o, n) => n || o, { + account: notification.getIn(['account', 'id']), + status: notification.getIn(['status', 'id']), + }); +}; + +const fixNotification = notification => { + return minifyNotification(normalizeNotification(notification)); +}; const isValid = notification => { try { @@ -88,7 +94,7 @@ const countFuture = (notifications, lastId) => { }, 0); }; -const normalizeNotification = (state, notification) => { +const importNotification = (state, notification) => { const top = state.get('top'); if (!top) state = state.update('unread', unread => unread + 1); @@ -98,7 +104,7 @@ const normalizeNotification = (state, notification) => { map = map.take(20); } - return map.set(notification.id, notificationToMap(notification)).sort(comparator); + return map.set(notification.id, fixNotification(notification)).sort(comparator); }); }; @@ -106,7 +112,7 @@ const processRawNotifications = notifications => ( ImmutableOrderedMap( notifications .filter(isValid) - .map(n => [n.id, notificationToMap(n)]), + .map(n => [n.id, fixNotification(n)]), )); const expandNormalizedNotifications = (state, notifications, next) => { @@ -176,23 +182,23 @@ const importMarker = (state, marker) => { }); }; -export default function notifications(state = initialState, action) { +export default function notifications(state = NotificationsRecord(), action) { switch(action.type) { case NOTIFICATIONS_EXPAND_REQUEST: return state.set('isLoading', true); case NOTIFICATIONS_EXPAND_FAIL: return state.set('isLoading', false); case NOTIFICATIONS_FILTER_SET: - return state.set('items', ImmutableOrderedMap()).set('hasMore', true); + return state.delete('items').set('hasMore', true); case NOTIFICATIONS_SCROLL_TOP: return updateTop(state, action.top); case NOTIFICATIONS_UPDATE: - return normalizeNotification(state, action.notification); + return importNotification(state, action.notification); case NOTIFICATIONS_UPDATE_QUEUE: return updateNotificationsQueue(state, action.notification, action.intlMessages, action.intlLocale); case NOTIFICATIONS_DEQUEUE: return state.withMutations(mutable => { - mutable.set('queuedNotifications', ImmutableOrderedMap()); + mutable.delete('queuedNotifications'); mutable.set('totalQueuedNotificationsCount', 0); }); case NOTIFICATIONS_EXPAND_SUCCESS: @@ -205,7 +211,7 @@ export default function notifications(state = initialState, action) { case FOLLOW_REQUEST_REJECT_SUCCESS: return filterNotificationIds(state, [action.id], 'follow_request'); case NOTIFICATIONS_CLEAR: - return state.set('items', ImmutableOrderedMap()).set('hasMore', false); + return state.delete('items').set('hasMore', false); case NOTIFICATIONS_MARK_READ_REQUEST: return state.set('lastRead', action.lastRead); case MARKER_FETCH_SUCCESS: @@ -214,16 +220,6 @@ export default function notifications(state = initialState, action) { return importMarker(state, fromJS(action.marker)); case TIMELINE_DELETE: return deleteByStatus(state, action.id); - - // Disable for now - // https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/432 - // - // case TIMELINE_DISCONNECT: - // // This is kind of a hack - `null` renders a LoadGap in the component - // // https://github.com/tootsuite/mastodon/pull/6886 - // return action.timeline === 'home' ? - // state.update('items', items => items.first() ? ImmutableOrderedSet([null]).union(items) : items) : - // state; default: return state; }