diff --git a/app/soapbox/__tests__/compare_id.test.ts b/app/soapbox/__tests__/compare_id.test.ts new file mode 100644 index 000000000..583b4a1eb --- /dev/null +++ b/app/soapbox/__tests__/compare_id.test.ts @@ -0,0 +1,7 @@ +import compareId from '../compare_id'; + +test('compareId', () => { + expect(compareId('3', '3')).toBe(0); + expect(compareId('10', '1')).toBe(1); + expect(compareId('99', '100')).toBe(-1); +}); diff --git a/app/soapbox/actions/__tests__/notifications.test.ts b/app/soapbox/actions/__tests__/notifications.test.ts new file mode 100644 index 000000000..2d0dd9356 --- /dev/null +++ b/app/soapbox/actions/__tests__/notifications.test.ts @@ -0,0 +1,38 @@ +import { OrderedMap as ImmutableOrderedMap } from 'immutable'; + +import { __stub } from 'soapbox/api'; +import { mockStore, rootState } from 'soapbox/jest/test-helpers'; +import { normalizeNotification } from 'soapbox/normalizers'; + +import { markReadNotifications } from '../notifications'; + +describe('markReadNotifications()', () => { + it('fires off marker when top notification is newer than lastRead', async() => { + __stub((mock) => mock.onPost('/api/v1/markers').reply(200, {})); + + const items = ImmutableOrderedMap({ + '10': normalizeNotification({ id: '10' }), + }); + + const state = rootState + .set('me', '123') + .setIn(['notifications', 'lastRead'], '9') + .setIn(['notifications', 'items'], items); + + const store = mockStore(state); + + const expectedActions = [{ + type: 'MARKER_SAVE_REQUEST', + marker: { + notifications: { + last_read_id: '10', + }, + }, + }]; + + store.dispatch(markReadNotifications()); + const actions = store.getActions(); + + expect(actions).toEqual(expectedActions); + }); +}); diff --git a/app/soapbox/actions/notifications.ts b/app/soapbox/actions/notifications.ts index 4c825ef8b..79994bde5 100644 --- a/app/soapbox/actions/notifications.ts +++ b/app/soapbox/actions/notifications.ts @@ -7,6 +7,7 @@ import 'intl-pluralrules'; import { defineMessages } from 'react-intl'; import api, { getLinks } from 'soapbox/api'; +import compareId from 'soapbox/compare_id'; import { getFilters, regexFromFilters } from 'soapbox/selectors'; import { isLoggedIn } from 'soapbox/utils/auth'; import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features'; @@ -304,23 +305,22 @@ const markReadNotifications = () => if (!isLoggedIn(getState)) return; const state = getState(); - const instance = state.instance; - const topNotificationId = state.notifications.get('items').first(ImmutableMap()).get('id'); - const lastReadId = state.notifications.get('lastRead'); - const v = parseVersion(instance.version); + const topNotificationId: string | undefined = state.notifications.get('items').first(ImmutableMap()).get('id'); + const lastReadId: string | -1 = state.notifications.get('lastRead'); + const v = parseVersion(state.instance.version); - if (!(topNotificationId && topNotificationId > lastReadId)) return; + if (topNotificationId && (lastReadId === -1 || compareId(topNotificationId, lastReadId) > 0)) { + const marker = { + notifications: { + last_read_id: topNotificationId, + }, + }; - const marker = { - notifications: { - last_read_id: topNotificationId, - }, - }; + dispatch(saveMarker(marker)); - dispatch(saveMarker(marker)); - - if (v.software === PLEROMA) { - dispatch(markReadPleroma(topNotificationId)); + if (v.software === PLEROMA) { + dispatch(markReadPleroma(topNotificationId)); + } } }; diff --git a/app/soapbox/compare_id.ts b/app/soapbox/compare_id.ts index e92d13ef5..b92a44cf1 100644 --- a/app/soapbox/compare_id.ts +++ b/app/soapbox/compare_id.ts @@ -1,5 +1,14 @@ 'use strict'; +/** + * Compare numerical primary keys represented as strings. + * For example, '10' (as a string) is considered less than '9' + * when sorted alphabetically. So compare string length first. + * + * - `0`: id1 == id2 + * - `1`: id1 > id2 + * - `-1`: id1 < id2 + */ export default function compareId(id1: string, id2: string) { if (id1 === id2) { return 0; diff --git a/dangerfile.ts b/dangerfile.ts index e55c808b8..aa18c0ad9 100644 --- a/dangerfile.ts +++ b/dangerfile.ts @@ -11,7 +11,7 @@ if (docs.edited) { const uiCode = danger.git.fileMatch('app/soapbox/components/ui/**'); const uiTests = danger.git.fileMatch('app/soapbox/components/ui/**/__tests__/**'); -if (uiCode.modified && !uiTests.modified) { +if (uiCode.edited && !uiTests.edited) { warn('You have UI changes (`soapbox/components/ui`) without tests.'); } @@ -19,7 +19,7 @@ if (uiCode.modified && !uiTests.modified) { const actionsCode = danger.git.fileMatch('app/soapbox/actions/**'); const actionsTests = danger.git.fileMatch('app/soapbox/actions/**__tests__/**'); -if (actionsCode.modified && !actionsTests.modified) { +if (actionsCode.edited && !actionsTests.edited) { warn('You have actions changes (`soapbox/actions`) without tests.'); } @@ -27,6 +27,6 @@ if (actionsCode.modified && !actionsTests.modified) { const reducersCode = danger.git.fileMatch('app/soapbox/reducers/**'); const reducersTests = danger.git.fileMatch('app/soapbox/reducers/**__tests__/**'); -if (reducersCode.modified && !reducersTests.modified) { +if (reducersCode.edited && !reducersTests.edited) { warn('You have reducer changes (`soapbox/reducers`) without tests.'); }