From bf640b9b0f7331de1b3114358be7d404607674d5 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 26 May 2019 16:01:06 -0700 Subject: [PATCH] fix: fix unread notifications badge for filters (#1231) fixes #1230 --- src/routes/_components/timeline/Timeline.html | 69 +--------- src/routes/_static/instanceSettings.js | 7 - .../computations/timelineComputations.js | 124 +++++++++++++++++- .../spec/125-notification-timeline-filters.js | 93 +++++++++++++ tests/utils.js | 2 + 5 files changed, 217 insertions(+), 78 deletions(-) create mode 100644 tests/spec/125-notification-timeline-filters.js diff --git a/src/routes/_components/timeline/Timeline.html b/src/routes/_components/timeline/Timeline.html index 7efd9cb9..c33a4ffa 100644 --- a/src/routes/_components/timeline/Timeline.html +++ b/src/routes/_components/timeline/Timeline.html @@ -59,22 +59,6 @@ import { observe } from 'svelte-extras' import { createMakeProps } from '../../_actions/createMakeProps' import { showMoreAndScrollToTop } from '../../_actions/showMoreAndScrollToTop' - import { get } from '../../_utils/lodash-lite' - import { - HOME_REBLOGS, - HOME_REPLIES, - NOTIFICATION_REBLOGS, - NOTIFICATION_FOLLOWS, - NOTIFICATION_FAVORITES, - NOTIFICATION_POLLS, - NOTIFICATION_MENTIONS, - FILTER_FAVORITE, - FILTER_FOLLOW, - FILTER_MENTION, - FILTER_POLL, - FILTER_REBLOG, - FILTER_REPLY - } from '../../_static/instanceSettings' export default { oncreate () { @@ -143,59 +127,10 @@ timelineValue !== $firstTimelineItemId && timelineValue ), - currentInstanceSettings: ({ $currentInstance, $instanceSettings }) => ( - $instanceSettings[$currentInstance] || {} - ), - timelineFilters: ({ currentInstanceSettings, timeline }) => { - if (timeline === 'home') { - return { - [FILTER_REBLOG]: get(currentInstanceSettings, [HOME_REBLOGS], true), - [FILTER_REPLY]: get(currentInstanceSettings, [HOME_REPLIES], true) - } - } else if (timeline === 'notifications') { - return { - [FILTER_REBLOG]: get(currentInstanceSettings, [NOTIFICATION_REBLOGS], true), - [FILTER_FOLLOW]: get(currentInstanceSettings, [NOTIFICATION_FOLLOWS], true), - [FILTER_FAVORITE]: get(currentInstanceSettings, [NOTIFICATION_FAVORITES], true), - [FILTER_MENTION]: get(currentInstanceSettings, [NOTIFICATION_MENTIONS], true), - [FILTER_POLL]: get(currentInstanceSettings, [NOTIFICATION_POLLS], true) - } - } - }, - showReblogs: ({ timelineFilters }) => get(timelineFilters, [FILTER_REBLOG], true), - showReplies: ({ timelineFilters }) => get(timelineFilters, [FILTER_REPLY], true), - showFollows: ({ timelineFilters }) => get(timelineFilters, [FILTER_FOLLOW], true), - showMentions: ({ timelineFilters }) => get(timelineFilters, [FILTER_MENTION], true), - showPolls: ({ timelineFilters }) => get(timelineFilters, [FILTER_POLL], true), - showFavs: ({ timelineFilters }) => get(timelineFilters, [FILTER_FAVORITE], true), - itemIds: ({ - $timelineItemSummaries, showReblogs, showReplies, showFollows, showMentions, - showPolls, showFavs - }) => ( - $timelineItemSummaries && $timelineItemSummaries.filter(item => { - switch (item.type) { - case 'poll': - return showPolls - case 'favourite': - return showFavs - case 'reblog': - return showReblogs - case 'mention': - return showMentions - case 'follow': - return showFollows - } - if (item.reblogId) { - return showReblogs - } else if (item.replyId) { - return showReplies - } else { - return true - } - }).map(_ => _.id) + itemIds: ({ $filteredTimelineItemSummaries }) => ( + $filteredTimelineItemSummaries && $filteredTimelineItemSummaries.map(_ => _.id) ), itemIdsToAdd: ({ $timelineItemSummariesToAdd }) => ( - // TODO: filter $timelineItemSummariesToAdd && $timelineItemSummariesToAdd.map(_ => _.id) ), headerProps: ({ itemIdsToAdd }) => { diff --git a/src/routes/_static/instanceSettings.js b/src/routes/_static/instanceSettings.js index db45398b..83787f8f 100644 --- a/src/routes/_static/instanceSettings.js +++ b/src/routes/_static/instanceSettings.js @@ -6,10 +6,3 @@ export const NOTIFICATION_FAVORITES = 'notificationFavs' export const NOTIFICATION_FOLLOWS = 'notificationFollows' export const NOTIFICATION_MENTIONS = 'notificationMentions' export const NOTIFICATION_POLLS = 'notificationPolls' - -export const FILTER_REBLOG = 'reblog' -export const FILTER_REPLY = 'reply' -export const FILTER_MENTION = 'mention' -export const FILTER_FOLLOW = 'follow' -export const FILTER_FAVORITE = 'fav' -export const FILTER_POLL = 'poll' diff --git a/src/routes/_store/computations/timelineComputations.js b/src/routes/_store/computations/timelineComputations.js index 35afbe32..26785190 100644 --- a/src/routes/_store/computations/timelineComputations.js +++ b/src/routes/_store/computations/timelineComputations.js @@ -1,5 +1,14 @@ import { get } from '../../_utils/lodash-lite' import { getFirstIdFromItemSummaries, getLastIdFromItemSummaries } from '../../_utils/getIdFromItemSummaries' +import { + HOME_REBLOGS, + HOME_REPLIES, + NOTIFICATION_REBLOGS, + NOTIFICATION_FOLLOWS, + NOTIFICATION_FAVORITES, + NOTIFICATION_POLLS, + NOTIFICATION_MENTIONS +} from '../../_static/instanceSettings' function computeForTimeline (store, key, defaultValue) { store.compute(key, @@ -10,6 +19,31 @@ function computeForTimeline (store, key, defaultValue) { ) } +// Compute just the boolean, e.g. 'showPolls', so that we can use that boolean as +// the input to the timelineFilterFunction computations. This should reduce the need to +// re-compute the timelineFilterFunction over and over. +function computeTimelineFilter (store, computationName, timelinesToSettingsKeys) { + store.compute( + computationName, + ['currentInstance', 'instanceSettings', 'currentTimeline'], + (currentInstance, instanceSettings, currentTimeline) => { + let settingsKey = timelinesToSettingsKeys[currentTimeline] + return settingsKey ? get(instanceSettings, [currentInstance, settingsKey], true) : true + } + ) +} + +// Ditto for notifications, which we always have to keep track of due to the notification count. +function computeNotificationFilter (store, computationName, key) { + store.compute( + computationName, + ['currentInstance', 'instanceSettings'], + (currentInstance, instanceSettings) => { + return get(instanceSettings, [currentInstance, key], true) + } + ) +} + export function timelineComputations (store) { computeForTimeline(store, 'timelineItemSummaries', null) computeForTimeline(store, 'timelineItemSummariesToAdd', null) @@ -41,11 +75,93 @@ export function timelineComputations (store) { getLastIdFromItemSummaries(timelineItemSummaries) )) + computeTimelineFilter(store, 'timelineShowReblogs', { home: HOME_REBLOGS, notifications: NOTIFICATION_REBLOGS }) + computeTimelineFilter(store, 'timelineShowReplies', { home: HOME_REPLIES }) + computeTimelineFilter(store, 'timelineShowFollows', { notifications: NOTIFICATION_FOLLOWS }) + computeTimelineFilter(store, 'timelineShowFavs', { notifications: NOTIFICATION_FAVORITES }) + computeTimelineFilter(store, 'timelineShowMentions', { notifications: NOTIFICATION_MENTIONS }) + computeTimelineFilter(store, 'timelineShowPolls', { notifications: NOTIFICATION_POLLS }) + + computeNotificationFilter(store, 'timelineNotificationShowReblogs', NOTIFICATION_REBLOGS) + computeNotificationFilter(store, 'timelineNotificationShowFollows', NOTIFICATION_FOLLOWS) + computeNotificationFilter(store, 'timelineNotificationShowFavs', NOTIFICATION_FAVORITES) + computeNotificationFilter(store, 'timelineNotificationShowMentions', NOTIFICATION_MENTIONS) + computeNotificationFilter(store, 'timelineNotificationShowPolls', NOTIFICATION_POLLS) + + function createFilterFunction (showReblogs, showReplies, showFollows, showFavs, showMentions, showPolls) { + return item => { + switch (item.type) { + case 'poll': + return showPolls + case 'favourite': + return showFavs + case 'reblog': + return showReblogs + case 'mention': + return showMentions + case 'follow': + return showFollows + } + if (item.reblogId) { + return showReblogs + } else if (item.replyId) { + return showReplies + } else { + return true + } + } + } + + store.compute( + 'timelineFilterFunction', + [ + 'timelineShowReblogs', 'timelineShowReplies', 'timelineShowFollows', + 'timelineShowFavs', 'timelineShowMentions', 'timelineShowPolls' + ], + (showReblogs, showReplies, showFollows, showFavs, showMentions, showPolls) => ( + createFilterFunction(showReblogs, showReplies, showFollows, showFavs, showMentions, showPolls) + ) + ) + + store.compute( + 'timelineNotificationFilterFunction', + [ + 'timelineNotificationShowReblogs', 'timelineNotificationShowFollows', + 'timelineNotificationShowFavs', 'timelineNotificationShowMentions', + 'timelineNotificationShowPolls' + ], + (showReblogs, showFollows, showFavs, showMentions, showPolls) => ( + createFilterFunction(showReblogs, true, showFollows, showFavs, showMentions, showPolls) + ) + ) + + store.compute( + 'filteredTimelineItemSummaries', + ['timelineItemSummaries', 'timelineFilterFunction'], + (timelineItemSummaries, timelineFilterFunction) => { + return timelineItemSummaries && timelineItemSummaries.filter(timelineFilterFunction) + } + ) + + store.compute('timelineNotificationItemSummaries', + [`timelineData_timelineItemSummariesToAdd`, 'timelineFilterFunction', 'currentInstance'], + (root, timelineFilterFunction, currentInstance) => ( + get(root, [currentInstance, 'notifications']) + ) + ) + + store.compute( + 'filteredTimelineNotificationItemSummaries', + ['timelineNotificationItemSummaries', 'timelineNotificationFilterFunction'], + (timelineNotificationItemSummaries, timelineNotificationFilterFunction) => ( + timelineNotificationItemSummaries && timelineNotificationItemSummaries.filter(timelineNotificationFilterFunction) + ) + ) + store.compute('numberOfNotifications', - [`timelineData_timelineItemSummariesToAdd`, 'currentInstance'], - (root, currentInstance) => ( - (root && root[currentInstance] && root[currentInstance].notifications && - root[currentInstance].notifications.length) || 0 + ['filteredTimelineNotificationItemSummaries'], + (filteredTimelineNotificationItemSummaries) => ( + filteredTimelineNotificationItemSummaries ? filteredTimelineNotificationItemSummaries.length : 0 ) ) diff --git a/tests/spec/125-notification-timeline-filters.js b/tests/spec/125-notification-timeline-filters.js new file mode 100644 index 00000000..a43e5027 --- /dev/null +++ b/tests/spec/125-notification-timeline-filters.js @@ -0,0 +1,93 @@ +import { + settingsNavButton, + getNthStatusContent, + instanceSettingNotificationReblogs, + notificationBadge, + instanceSettingNotificationFavs, + instanceSettingNotificationMentions, instanceSettingNotificationFollows +} from '../utils' +import { loginAsFoobar } from '../roles' +import { Selector as $ } from 'testcafe' +import { favoriteStatusAs, followAs, postAs, reblogStatusAs, unfollowAs } from '../serverActions' + +fixture`125-notification-timeline-filters.js` + .page`http://localhost:4002` + +test('Notification timeline filters correctly affect counts - boosts', async t => { + let timeout = 20000 + let { id: statusId } = await postAs('foobar', 'I do not care if you boost this') + await loginAsFoobar(t) + await t + .expect(getNthStatusContent(1).innerText).contains('I do not care if you boost this') + await reblogStatusAs('admin', statusId) + await t + .expect(notificationBadge.innerText).eql('1', { timeout }) + .click(settingsNavButton) + .click($('a').withText('Instances')) + .click($('a').withText('localhost:3000')) + .click(instanceSettingNotificationReblogs) + .expect(instanceSettingNotificationReblogs.checked).notOk() + .expect(notificationBadge.exists).notOk({ timeout }) + .click(instanceSettingNotificationReblogs) + .expect(instanceSettingNotificationReblogs.checked).ok() + .expect(notificationBadge.innerText).eql('1', { timeout }) +}) + +test('Notification timeline filters correctly affect counts - favs', async t => { + let timeout = 20000 + let { id: statusId } = await postAs('foobar', 'I do not care if you fav this') + await loginAsFoobar(t) + await t + .expect(getNthStatusContent(1).innerText).contains('I do not care if you fav this') + await favoriteStatusAs('admin', statusId) + await t + .expect(notificationBadge.innerText).eql('1', { timeout }) + .click(settingsNavButton) + .click($('a').withText('Instances')) + .click($('a').withText('localhost:3000')) + .click(instanceSettingNotificationFavs) + .expect(instanceSettingNotificationFavs.checked).notOk() + .expect(notificationBadge.exists).notOk({ timeout }) + .click(instanceSettingNotificationFavs) + .expect(instanceSettingNotificationFavs.checked).ok() + .expect(notificationBadge.innerText).eql('1', { timeout }) +}) + +test('Notification timeline filters correctly affect counts - favs', async t => { + let timeout = 20000 + await loginAsFoobar(t) + await t + .expect(getNthStatusContent(1).exists).ok() + await postAs('admin', 'hey yo @foobar') + await t + .expect(notificationBadge.innerText).eql('1', { timeout }) + .click(settingsNavButton) + .click($('a').withText('Instances')) + .click($('a').withText('localhost:3000')) + .click(instanceSettingNotificationMentions) + .expect(instanceSettingNotificationMentions.checked).notOk() + .expect(notificationBadge.exists).notOk({ timeout }) + .click(instanceSettingNotificationMentions) + .expect(instanceSettingNotificationMentions.checked).ok() + .expect(notificationBadge.innerText).eql('1', { timeout }) +}) + +test('Notification timeline filters correctly affect counts - follows', async t => { + let timeout = 20000 + await loginAsFoobar(t) + await t + .expect(getNthStatusContent(1).exists).ok() + await followAs('ExternalLinks', 'foobar') + await t + .expect(notificationBadge.innerText).eql('1', { timeout }) + .click(settingsNavButton) + .click($('a').withText('Instances')) + .click($('a').withText('localhost:3000')) + .click(instanceSettingNotificationFollows) + .expect(instanceSettingNotificationFollows.checked).notOk() + .expect(notificationBadge.exists).notOk({ timeout }) + .click(instanceSettingNotificationFollows) + .expect(instanceSettingNotificationMentions.checked).ok() + .expect(notificationBadge.innerText).eql('1', { timeout }) + await unfollowAs('ExternalLinks', 'foobar') +}) diff --git a/tests/utils.js b/tests/utils.js index 601c90e4..19f61542 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -73,6 +73,8 @@ export const instanceSettingNotificationFavs = $('#instance-option-notificationF export const instanceSettingNotificationReblogs = $('#instance-option-notificationReblogs') export const instanceSettingNotificationMentions = $('#instance-option-notificationMentions') +export const notificationBadge = $('#main-nav li:nth-child(2) .nav-link-badge') + export function getComposeModalNthMediaAltInput (n) { return $(`.modal-dialog .compose-media:nth-child(${n}) .compose-media-alt input`) }