diff --git a/app/soapbox/normalizers/__tests__/notification-test.js b/app/soapbox/normalizers/__tests__/notification-test.js new file mode 100644 index 000000000..c90b5451e --- /dev/null +++ b/app/soapbox/normalizers/__tests__/notification-test.js @@ -0,0 +1,17 @@ +import { Record as ImmutableRecord, fromJS } from 'immutable'; + +import { normalizeNotification } from '../notification'; + +describe('normalizeNotification()', () => { + it('normalizes an empty map', () => { + const notification = fromJS({}); + const result = normalizeNotification(notification); + + expect(ImmutableRecord.isRecord(result)).toBe(true); + expect(result.type).toEqual(''); + expect(result.account).toBe(null); + expect(result.target).toBe(null); + expect(result.status).toBe(null); + expect(result.id).toEqual(''); + }); +}); diff --git a/app/soapbox/normalizers/instance.js b/app/soapbox/normalizers/instance.ts similarity index 92% rename from app/soapbox/normalizers/instance.js rename to app/soapbox/normalizers/instance.ts index 470c66f53..1ae2801d3 100644 --- a/app/soapbox/normalizers/instance.js +++ b/app/soapbox/normalizers/instance.ts @@ -60,7 +60,7 @@ const InstanceRecord = ImmutableRecord({ }); // Build Mastodon configuration from Pleroma instance -const pleromaToMastodonConfig = instance => { +const pleromaToMastodonConfig = (instance: ImmutableMap) => { return ImmutableMap({ statuses: ImmutableMap({ max_characters: instance.get('max_toot_chars'), @@ -75,10 +75,10 @@ const pleromaToMastodonConfig = instance => { }; // Get the software's default attachment limit -const getAttachmentLimit = software => software === PLEROMA ? Infinity : 4; +const getAttachmentLimit = (software: string) => software === PLEROMA ? Infinity : 4; // Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format -export const normalizeInstance = instance => { +export const normalizeInstance = (instance: ImmutableMap) => { const { software } = parseVersion(instance.get('version')); const mastodonConfig = pleromaToMastodonConfig(instance); diff --git a/app/soapbox/normalizers/notification.ts b/app/soapbox/normalizers/notification.ts new file mode 100644 index 000000000..ab369a52b --- /dev/null +++ b/app/soapbox/normalizers/notification.ts @@ -0,0 +1,20 @@ +import { + Map as ImmutableMap, + Record as ImmutableRecord, +} from 'immutable'; + +// https://docs.joinmastodon.org/entities/notification/ +const NotificationRecord = ImmutableRecord({ + account: null, + chat_message: null, // pleroma:chat_mention + created_at: new Date(), + emoji: null, // pleroma:emoji_reaction + id: '', + status: null, + target: null, // move + type: '', +}); + +export const normalizeNotification = (notification: ImmutableMap) => { + return NotificationRecord(notification); +}; diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts index 46f18b5c0..0ae90579a 100644 --- a/app/soapbox/normalizers/status.ts +++ b/app/soapbox/normalizers/status.ts @@ -11,7 +11,7 @@ import { IStatus } from 'soapbox/types'; import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers'; const StatusRecord = ImmutableRecord({ - account: ImmutableMap(), + account: null, application: null, bookmarked: false, card: null, diff --git a/app/soapbox/reducers/__tests__/admin-test.js b/app/soapbox/reducers/__tests__/admin-test.js index 807bdd02e..43018c880 100644 --- a/app/soapbox/reducers/__tests__/admin-test.js +++ b/app/soapbox/reducers/__tests__/admin-test.js @@ -1,21 +1,11 @@ -import { - Map as ImmutableMap, - List as ImmutableList, - OrderedSet as ImmutableOrderedSet, -} from 'immutable'; +import { Record as ImmutableRecord } from 'immutable'; import reducer from '../admin'; describe('admin reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual(ImmutableMap({ - reports: ImmutableMap(), - openReports: ImmutableOrderedSet(), - users: ImmutableMap(), - latestUsers: ImmutableOrderedSet(), - awaitingApproval: ImmutableOrderedSet(), - configs: ImmutableList(), - needsReboot: false, - })); + const result = reducer(undefined, {}); + expect(ImmutableRecord.isRecord(result)).toBe(true); + expect(result.needsReboot).toBe(false); }); }); diff --git a/app/soapbox/reducers/__tests__/alerts-test.js b/app/soapbox/reducers/__tests__/alerts-test.js index 7059c843f..907a0adb3 100644 --- a/app/soapbox/reducers/__tests__/alerts-test.js +++ b/app/soapbox/reducers/__tests__/alerts-test.js @@ -1,6 +1,11 @@ -import { List as ImmutableList } from 'immutable'; +import { Record as ImmutableRecord, List as ImmutableList } from 'immutable'; -import * as actions from 'soapbox/actions/alerts'; +import { + ALERT_SHOW, + ALERT_DISMISS, + ALERT_CLEAR, +} from 'soapbox/actions/alerts'; +import { applyActions } from 'soapbox/test_helpers'; import reducer from '../alerts'; @@ -9,66 +14,65 @@ describe('alerts reducer', () => { expect(reducer(undefined, {})).toEqual(ImmutableList()); }); - it('should handle ALERT_SHOW', () => { - const state = ImmutableList([]); - const action = { - type: actions.ALERT_SHOW, - title: 'alert_title', - message: 'this is an alert message', - }; - expect(reducer(state, action).toJS()).toMatchObject([ - { + describe('ALERT_SHOW', () => { + it('imports the alert', () => { + const action = { + type: ALERT_SHOW, + title: 'alert_title', + message: 'this is an alert message', + }; + + const expected = [{ key: 0, message: 'this is an alert message', title: 'alert_title', - }, - ]); - }); + }]; - // it('should handle ALERT_DISMISS', () => { - // const state = ImmutableList([ - // { - // key: 0, - // message: 'message_1', - // title: 'title_1', - // }, - // { - // key: 1, - // message: 'message_2', - // title: 'title_2', - // }, - // ]); - // const action = { - // type: actions.ALERT_DISMISS, - // alert: { key: 0 }, - // }; - // expect(reducer(state, action).toJS()).toMatchObject([ - // { - // key: 1, - // message: 'message_2', - // title: 'title_2', - // } - // ]); - // }); - - it('should handle ALERT_CLEAR', () => { - const state = ImmutableList([ - { - key: 0, - message: 'message_1', - title: 'title_1', - }, - { - key: 1, - message: 'message_2', - title: 'title_2', - }, - ]); - const action = { - type: actions.ALERT_CLEAR, - }; - expect(reducer(state, action).toJS()).toMatchObject({ + const result = reducer(undefined, action); + expect(ImmutableRecord.isRecord(result.get(0))).toBe(true); + expect(result.toJS()).toMatchObject(expected); }); }); + describe('ALERT_CLEAR', () => { + it('deletes the alerts', () => { + const actions = [{ + type: ALERT_SHOW, + title: 'Oops!', + message: 'Server is down', + }, { + type: ALERT_SHOW, + title: 'Uh-oh!', + message: 'Shit done fucked up', + }, { + type: ALERT_CLEAR, + }]; + + const result = applyActions(undefined, actions, reducer); + expect(result.isEmpty()).toBe(true); + }); + }); + + describe('ALERT_DISMISS', () => { + it('deletes an individual alert', () => { + const actions = [{ + type: ALERT_SHOW, + title: 'Oops!', + message: 'Server is down', + }, { + type: ALERT_SHOW, + title: 'Uh-oh!', + message: 'Shit done fucked up', + }, { + type: ALERT_DISMISS, + alert: { + key: 0, + }, + }]; + + const result = applyActions(undefined, actions, reducer); + expect(result.size).toEqual(1); + expect(result.get(0).key).toEqual(1); + }); + }); }); diff --git a/app/soapbox/reducers/__tests__/meta-test.js b/app/soapbox/reducers/__tests__/meta-test.js index cfdf882cb..92ee4e6ff 100644 --- a/app/soapbox/reducers/__tests__/meta-test.js +++ b/app/soapbox/reducers/__tests__/meta-test.js @@ -1,9 +1,11 @@ -import { Map as ImmutableMap } from 'immutable'; +import { Record as ImmutableRecord } from 'immutable'; import reducer from '../meta'; describe('meta reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual(ImmutableMap()); + const result = reducer(undefined, {}); + expect(ImmutableRecord.isRecord(result)).toBe(true); + expect(result.instance_fetch_failed).toBe(false); }); }); diff --git a/app/soapbox/reducers/__tests__/notifications-test.js b/app/soapbox/reducers/__tests__/notifications-test.js index 5c65341cc..630aa61e3 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'; @@ -24,27 +28,31 @@ import { NOTIFICATIONS_MARK_READ_REQUEST, } from 'soapbox/actions/notifications'; import { TIMELINE_DELETE } from 'soapbox/actions/timelines'; +import { applyActions } from 'soapbox/test_helpers'; 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 +60,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 +87,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 = applyActions(initialState, actions, reducer); + + // 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 = applyActions(initialState, actions, reducer); + + 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/admin.js b/app/soapbox/reducers/admin.js index 1da0ef7aa..6a75e2c5b 100644 --- a/app/soapbox/reducers/admin.js +++ b/app/soapbox/reducers/admin.js @@ -2,6 +2,7 @@ import { Map as ImmutableMap, List as ImmutableList, Set as ImmutableSet, + Record as ImmutableRecord, OrderedSet as ImmutableOrderedSet, fromJS, is, @@ -20,7 +21,7 @@ import { ADMIN_USERS_APPROVE_SUCCESS, } from '../actions/admin'; -const initialState = ImmutableMap({ +const ReducerRecord = ImmutableRecord({ reports: ImmutableMap(), openReports: ImmutableOrderedSet(), users: ImmutableMap(), @@ -126,7 +127,7 @@ function handleReportDiffs(state, reports) { }); } -export default function admin(state = initialState, action) { +export default function admin(state = ReducerRecord(), action) { switch(action.type) { case ADMIN_CONFIG_FETCH_SUCCESS: case ADMIN_CONFIG_UPDATE_SUCCESS: diff --git a/app/soapbox/reducers/admin_log.js b/app/soapbox/reducers/admin_log.js index 9008de52b..c194bbc04 100644 --- a/app/soapbox/reducers/admin_log.js +++ b/app/soapbox/reducers/admin_log.js @@ -1,12 +1,13 @@ import { Map as ImmutableMap, + Record as ImmutableRecord, OrderedSet as ImmutableOrderedSet, fromJS, } from 'immutable'; import { ADMIN_LOG_FETCH_SUCCESS } from 'soapbox/actions/admin'; -const initialState = ImmutableMap({ +const ReducerRecord = ImmutableRecord({ items: ImmutableMap(), index: ImmutableOrderedSet(), total: 0, @@ -34,7 +35,7 @@ const importItems = (state, items, total) => { }); }; -export default function admin_log(state = initialState, action) { +export default function admin_log(state = ReducerRecord(), action) { switch(action.type) { case ADMIN_LOG_FETCH_SUCCESS: return importItems(state, action.items, action.total); diff --git a/app/soapbox/reducers/alerts.js b/app/soapbox/reducers/alerts.js index 79fad30e6..31428d402 100644 --- a/app/soapbox/reducers/alerts.js +++ b/app/soapbox/reducers/alerts.js @@ -1,4 +1,4 @@ -import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { Record as ImmutableRecord, List as ImmutableList } from 'immutable'; import { ALERT_SHOW, @@ -6,21 +6,38 @@ import { ALERT_CLEAR, } from '../actions/alerts'; -const initialState = ImmutableList([]); +const AlertRecord = ImmutableRecord({ + key: 0, + title: '', + message: '', + severity: 'info', + actionLabel: '', + actionLink: '', +}); + +const initialState = ImmutableList(); + +// Get next key based on last alert +const getNextKey = state => state.size > 0 ? state.last().get('key') + 1 : 0; + +// Import the alert +const importAlert = (state, alert) => { + const key = getNextKey(state); + const record = AlertRecord({ ...alert, key }); + return state.push(record); +}; + +// Delete an alert by its key +const deleteAlert = (state, alert) => { + return state.filterNot(item => item.key === alert.key); +}; export default function alerts(state = initialState, action) { switch(action.type) { case ALERT_SHOW: - return state.push(ImmutableMap({ - key: state.size > 0 ? state.last().get('key') + 1 : 0, - title: action.title, - message: action.message, - severity: action.severity || 'info', - actionLabel: action.actionLabel, - actionLink: action.actionLink, - })); + return importAlert(state, action); case ALERT_DISMISS: - return state.filterNot(item => item.get('key') === action.alert.key); + return deleteAlert(state, action.alert); case ALERT_CLEAR: return state.clear(); default: diff --git a/app/soapbox/reducers/meta.js b/app/soapbox/reducers/meta.js index 46f662ece..eb4f40486 100644 --- a/app/soapbox/reducers/meta.js +++ b/app/soapbox/reducers/meta.js @@ -1,12 +1,14 @@ 'use strict'; -import { Map as ImmutableMap } from 'immutable'; +import { Record as ImmutableRecord } from 'immutable'; import { INSTANCE_FETCH_FAIL } from 'soapbox/actions/instance'; -const initialState = ImmutableMap(); +const ReducerRecord = ImmutableRecord({ + instance_fetch_failed: false, +}); -export default function meta(state = initialState, action) { +export default function meta(state = ReducerRecord(), action) { switch(action.type) { case INSTANCE_FETCH_FAIL: return state.set('instance_fetch_failed', true); diff --git a/app/soapbox/reducers/notifications.js b/app/soapbox/reducers/notifications.js index 401fe92a9..02ffa58e9 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,7 @@ import { } from '../actions/notifications'; import { TIMELINE_DELETE } from '../actions/timelines'; -const initialState = ImmutableMap({ +const ReducerRecord = ImmutableRecord({ items: ImmutableOrderedMap(), hasMore: true, top: false, @@ -48,16 +53,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 +93,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 +103,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 +111,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 +181,23 @@ const importMarker = (state, marker) => { }); }; -export default function notifications(state = initialState, action) { +export default function notifications(state = ReducerRecord(), 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 +210,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 +219,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; } diff --git a/app/soapbox/test_helpers.js b/app/soapbox/test_helpers.js index 719c29129..c9de38dc4 100644 --- a/app/soapbox/test_helpers.js +++ b/app/soapbox/test_helpers.js @@ -33,3 +33,8 @@ export const createComponent = (children, props = {}) => { , ); }; + +// Apply actions to the state, one at a time +export const applyActions = (state, actions, reducer) => { + return actions.reduce((state, action) => reducer(state, action), state); +};