diff --git a/bin/svgs.js b/bin/svgs.js index 0409f0fb..16d90adc 100644 --- a/bin/svgs.js +++ b/bin/svgs.js @@ -1,6 +1,7 @@ export default [ { id: 'pinafore-logo', src: 'src/static/sailboat.svg', inline: true }, { id: 'fa-bell', src: 'src/thirdparty/font-awesome-svg-png/white/svg/bell.svg', inline: true }, + { id: 'fa-bell-o', src: 'src/thirdparty/font-awesome-svg-png/white/svg/bell-o.svg' }, { id: 'fa-users', src: 'src/thirdparty/font-awesome-svg-png/white/svg/users.svg', inline: true }, { id: 'fa-globe', src: 'src/thirdparty/font-awesome-svg-png/white/svg/globe.svg' }, { id: 'fa-gear', src: 'src/thirdparty/font-awesome-svg-png/white/svg/gear.svg', inline: true }, @@ -22,6 +23,7 @@ export default [ { id: 'fa-user-plus', src: 'src/thirdparty/font-awesome-svg-png/white/svg/user-plus.svg' }, { id: 'fa-external-link', src: 'src/thirdparty/font-awesome-svg-png/white/svg/external-link.svg' }, { id: 'fa-search', src: 'src/thirdparty/font-awesome-svg-png/white/svg/search.svg', inline: true }, + { id: 'fa-comment', src: 'src/thirdparty/font-awesome-svg-png/white/svg/comment.svg' }, { id: 'fa-comments', src: 'src/thirdparty/font-awesome-svg-png/white/svg/comments.svg', inline: true }, { id: 'fa-paperclip', src: 'src/thirdparty/font-awesome-svg-png/white/svg/paperclip.svg' }, { id: 'fa-thumb-tack', src: 'src/thirdparty/font-awesome-svg-png/white/svg/thumb-tack.svg' }, diff --git a/src/intl/en-US.js b/src/intl/en-US.js index 297d1530..e0cb576d 100644 --- a/src/intl/en-US.js +++ b/src/intl/en-US.js @@ -309,10 +309,10 @@ export default { true {(follow requested)} other {} }`, - notifyLabel: `Subscribe`, - denotifyLabel: `Unsubscribe`, + notify: 'Subscribe to {account}', + denotify: 'Unsubscribe from {account}', subscribedAccount: 'Subscribed to account', - unsubscribedAccount: 'Unsubscribed to account', + unsubscribedAccount: 'Unsubscribed from account', unblock: 'Unblock', nameAndFollowing: 'Name and following', clickToSeeAvatar: 'Click to see avatar', @@ -474,6 +474,7 @@ export default { newFollowers: 'New followers', reblogs: 'Boosts', pollResults: 'Poll results', + subscriptions: 'Subscribed toots', needToReauthenticate: 'You need to reauthenticate in order to enable push notification. Log out of {instance}?', failedToUpdatePush: 'Failed to update push notification settings: {error}', // Themes @@ -508,6 +509,7 @@ export default { rebloggedYou: 'boosted your toot', favoritedYou: 'favorited your toot', followedYou: 'followed you', + posted: 'posted', pollYouCreatedEnded: 'A poll you created has ended', pollYouVotedEnded: 'A poll you voted on has ended', reblogged: 'boosted', diff --git a/src/routes/_actions/notify.js b/src/routes/_actions/setAccountNotified.js similarity index 83% rename from src/routes/_actions/notify.js rename to src/routes/_actions/setAccountNotified.js index 383182b0..f962825d 100644 --- a/src/routes/_actions/notify.js +++ b/src/routes/_actions/setAccountNotified.js @@ -4,7 +4,7 @@ import { toast } from '../_components/toast/toast.js' import { updateLocalRelationship } from './accounts.js' import { formatIntl } from '../_utils/formatIntl.js' -export async function setAccountNotify (accountId, notify, toastOnSuccess) { +export async function setAccountNotified (accountId, notify, toastOnSuccess) { const { currentInstance, accessToken } = store.get() try { let relationship @@ -15,11 +15,11 @@ export async function setAccountNotify (accountId, notify, toastOnSuccess) { } await updateLocalRelationship(currentInstance, accountId, relationship) if (toastOnSuccess) { - /* no await */ toast.say(follow ? 'intl.subscribedAccount' : 'intl.unsubscribedAccount') + /* no await */ toast.say(notify ? 'intl.subscribedAccount' : 'intl.unsubscribedAccount') } } catch (e) { console.error(e) - /* no await */ toast.say(follow + /* no await */ toast.say(notify ? formatIntl('intl.unableToSubscribe', { error: (e.message || '') }) : formatIntl('intl.unableToUnsubscribe', { error: (e.message || '') }) ) diff --git a/src/routes/_api/notify.js b/src/routes/_api/notify.js index e06fbb8b..8871120b 100644 --- a/src/routes/_api/notify.js +++ b/src/routes/_api/notify.js @@ -4,13 +4,13 @@ import { auth, basename } from './utils.js' export async function notifyAccount (instanceName, accessToken, accountId) { const url = `${basename(instanceName)}/api/v1/accounts/${accountId}/follow` return post(url, { - "notify": true, + notify: true }, auth(accessToken), { timeout: WRITE_TIMEOUT }) } export async function denotifyAccount (instanceName, accessToken, accountId) { const url = `${basename(instanceName)}/api/v1/accounts/${accountId}/follow` return post(url, { - "notify": false + notify: false }, auth(accessToken), { timeout: WRITE_TIMEOUT }) } diff --git a/src/routes/_components/dialog/components/AccountProfileOptionsDialog.html b/src/routes/_components/dialog/components/AccountProfileOptionsDialog.html index c369083d..5b44f64f 100644 --- a/src/routes/_components/dialog/components/AccountProfileOptionsDialog.html +++ b/src/routes/_components/dialog/components/AccountProfileOptionsDialog.html @@ -23,6 +23,7 @@ import { composeNewStatusMentioning } from '../../../_actions/mention.js' import { toggleMute } from '../../../_actions/toggleMute.js' import { reportStatusOrAccount } from '../../../_actions/report.js' import { formatIntl } from '../../../_utils/formatIntl.js' +import { setAccountNotified } from '../../../_actions/setAccountNotified.js' export default { oncreate, @@ -39,6 +40,7 @@ export default { username: ({ account }) => account.username, muting: ({ relationship }) => relationship && relationship.muting, blocking: ({ relationship }) => relationship && relationship.blocking, + notifying: ({ relationship }) => relationship && relationship.notifying, followLabel: ({ following, followRequested, account, username }) => { if (typeof following === 'undefined' || !account) { return '' @@ -86,7 +88,7 @@ export default { blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon, following, followRequested, accountId, verifyCredentialsId, username, isUser, showReblogsLabel, - domain, blockDomainLabel, reportLabel + domain, blockDomainLabel, reportLabel, notifying }) => ([ !isUser && { key: 'mention', @@ -98,6 +100,16 @@ export default { label: followLabel, icon: followIcon }, + !isUser && following && notifying === false && { // notifying could be undefined for old servers + key: 'notify', + label: formatIntl('intl.notify', { account: `@${username}` }), + icon: '#fa-bell' + }, + !isUser && following && notifying === true && { // notifying could be undefined for old servers + key: 'denotify', + label: formatIntl('intl.denotify', { account: `@${username}` }), + icon: '#fa-bell-o' + }, !isUser && { key: 'block', label: blockLabel, @@ -151,6 +163,10 @@ export default { return this.onCopyClicked() case 'report': return this.onReport() + case 'notify': + return this.onNotifyClicked() + case 'denotify': + return this.onDenotifyClicked() } }, async onMentionClicked () { @@ -193,6 +209,16 @@ export default { const { account } = this.get() this.close() await reportStatusOrAccount({ account }) + }, + async onNotifyClicked () { + const { accountId } = this.get() + this.close() + await setAccountNotified(accountId, /* notify */ true, /* toastOnSuccess */ true) + }, + async onDenotifyClicked () { + const { accountId } = this.get() + this.close() + await setAccountNotified(accountId, /* notify */ false, /* toastOnSuccess */ true) } }, components: { diff --git a/src/routes/_components/dialog/components/StatusOptionsDialog.html b/src/routes/_components/dialog/components/StatusOptionsDialog.html index d5b05088..d187652d 100644 --- a/src/routes/_components/dialog/components/StatusOptionsDialog.html +++ b/src/routes/_components/dialog/components/StatusOptionsDialog.html @@ -26,6 +26,7 @@ import { shareStatus } from '../../../_actions/share.js' import { toggleMute } from '../../../_actions/toggleMute.js' import { reportStatusOrAccount } from '../../../_actions/report.js' import { formatIntl } from '../../../_utils/formatIntl.js' +import { setAccountNotified } from '../../../_actions/setAccountNotified.js' export default { oncreate, @@ -53,6 +54,7 @@ export default { username: ({ account }) => account.username, muting: ({ relationship }) => relationship.muting, blocking: ({ relationship }) => relationship.blocking, + notifying: ({ relationship }) => relationship && relationship.notifying, followLabel: ({ following, followRequested, account, username }) => { if (typeof following === 'undefined' || !account) { return '' @@ -96,7 +98,8 @@ export default { items: ({ blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon, following, followRequested, pinLabel, isUser, visibility, mentionsUser, mutingConversation, - muteConversationLabel, muteConversationIcon, supportsWebShare, isPublicOrUnlisted, bookmarkLabel + muteConversationLabel, muteConversationIcon, supportsWebShare, isPublicOrUnlisted, bookmarkLabel, + username, notifying }) => ([ isUser && { key: 'delete', @@ -113,6 +116,16 @@ export default { label: followLabel, icon: followIcon }, + !isUser && following && notifying === false && { // notifying could be undefined for old servers + key: 'notify', + label: formatIntl('intl.notify', { account: `@${username}` }), + icon: '#fa-bell' + }, + !isUser && following && notifying === true && { // notifying could be undefined for old servers + key: 'denotify', + label: formatIntl('intl.denotify', { account: `@${username}` }), + icon: '#fa-bell-o' + }, !isUser && { key: 'block', label: blockLabel, @@ -187,6 +200,10 @@ export default { return this.onReport() case 'bookmark': return this.onBookmark() + case 'notify': + return this.onNotifyClicked() + case 'denotify': + return this.onDenotifyClicked() } }, async onDeleteClicked () { @@ -244,6 +261,16 @@ export default { const { status } = this.get() this.close() await setStatusBookmarkedOrUnbookmarked(status.id, !status.bookmarked) + }, + async onNotifyClicked () { + const { accountId } = this.get() + this.close() + await setAccountNotified(accountId, /* notify */ true, /* toastOnSuccess */ true) + }, + async onDenotifyClicked () { + const { accountId } = this.get() + this.close() + await setAccountNotified(accountId, /* notify */ false, /* toastOnSuccess */ true) } } } diff --git a/src/routes/_components/profile/AccountProfile.html b/src/routes/_components/profile/AccountProfile.html index 0a2f5c12..19323172 100644 --- a/src/routes/_components/profile/AccountProfile.html +++ b/src/routes/_components/profile/AccountProfile.html @@ -8,7 +8,6 @@
- @@ -38,7 +37,7 @@ display: grid; grid-template-areas: "avatar name label followed-by follow" "avatar username username username follow" - "avatar note note note notify" + "avatar note note note follow" "meta meta meta meta meta" "details details details details details"; grid-template-columns: min-content auto 1fr 1fr min-content; @@ -72,7 +71,7 @@ grid-template-areas: "avatar name follow" "avatar label follow" "avatar username follow" - "avatar followed-by notify" + "avatar followed-by follow" "note note note" "meta meta meta" "details details details"; @@ -98,7 +97,6 @@ "username username" "followed-by followed-by" "follow follow" - "notify notify" "note note" "meta meta" "details details"; @@ -110,7 +108,6 @@ diff --git a/src/routes/_components/settings/instance/NotificationFilterSettings.html b/src/routes/_components/settings/instance/NotificationFilterSettings.html index 708b4080..3d2f9cef 100644 --- a/src/routes/_components/settings/instance/NotificationFilterSettings.html +++ b/src/routes/_components/settings/instance/NotificationFilterSettings.html @@ -10,7 +10,8 @@ NOTIFICATION_FAVORITES, NOTIFICATION_FOLLOWS, NOTIFICATION_MENTIONS, - NOTIFICATION_POLLS + NOTIFICATION_POLLS, + NOTIFICATION_SUBSCRIPTIONS } from '../../../_static/instanceSettings.js' export default { @@ -40,6 +41,11 @@ key: NOTIFICATION_POLLS, label: 'intl.pollResults', defaultValue: true + }, + { + key: NOTIFICATION_SUBSCRIPTIONS, + label: 'intl.subscriptions', + defaultValue: true } ] }), diff --git a/src/routes/_components/settings/instance/PushNotificationSettings.html b/src/routes/_components/settings/instance/PushNotificationSettings.html index 9caa2bd3..02309fc2 100644 --- a/src/routes/_components/settings/instance/PushNotificationSettings.html +++ b/src/routes/_components/settings/instance/PushNotificationSettings.html @@ -76,6 +76,10 @@ { key: 'poll', label: 'intl.pollResults' + }, + { + key: 'status', + label: 'intl.subscriptions' } ] }), diff --git a/src/routes/_components/status/Status.html b/src/routes/_components/status/Status.html index 870703f5..15a46e2a 100644 --- a/src/routes/_components/status/Status.html +++ b/src/routes/_components/status/Status.html @@ -295,7 +295,7 @@ ) ), showHeader: ({ notification, status, timelineType }) => ( - (notification && ['reblog', 'favourite', 'poll'].includes(notification.type)) || + (notification && ['reblog', 'favourite', 'poll', 'status'].includes(notification.type)) || status.reblog || timelineType === 'pinned' ), diff --git a/src/routes/_components/status/StatusHeader.html b/src/routes/_components/status/StatusHeader.html index 908dc5f8..25273ee1 100644 --- a/src/routes/_components/status/StatusHeader.html +++ b/src/routes/_components/status/StatusHeader.html @@ -125,6 +125,8 @@ return '#fa-user-plus' } else if (notificationType === 'poll') { return '#fa-bar-chart' + } else if (notificationType === 'status') { + return '#fa-comment' } return '#fa-star' }, @@ -135,6 +137,8 @@ return 'intl.favoritedYou' } else if (notificationType === 'follow') { return 'intl.followedYou' + } else if (notificationType === 'status') { + return 'intl.posted' } else if (notificationType === 'poll') { if ($currentVerifyCredentials && status && $currentVerifyCredentials.id === status.account.id) { return 'intl.pollYouCreatedEnded' diff --git a/src/routes/_static/instanceSettings.js b/src/routes/_static/instanceSettings.js index 83787f8f..407caaa4 100644 --- a/src/routes/_static/instanceSettings.js +++ b/src/routes/_static/instanceSettings.js @@ -6,3 +6,4 @@ export const NOTIFICATION_FAVORITES = 'notificationFavs' export const NOTIFICATION_FOLLOWS = 'notificationFollows' export const NOTIFICATION_MENTIONS = 'notificationMentions' export const NOTIFICATION_POLLS = 'notificationPolls' +export const NOTIFICATION_SUBSCRIPTIONS = 'notificationSubscriptions' diff --git a/src/routes/_store/computations/timelineFilterComputations.js b/src/routes/_store/computations/timelineFilterComputations.js index 0690543b..6e32ed6a 100644 --- a/src/routes/_store/computations/timelineFilterComputations.js +++ b/src/routes/_store/computations/timelineFilterComputations.js @@ -3,7 +3,7 @@ import { HOME_REPLIES, NOTIFICATION_FAVORITES, NOTIFICATION_FOLLOWS, NOTIFICATION_MENTIONS, NOTIFICATION_POLLS, - NOTIFICATION_REBLOGS + NOTIFICATION_REBLOGS, NOTIFICATION_SUBSCRIPTIONS } from '../../_static/instanceSettings.js' import { WORD_FILTER_CONTEXT_ACCOUNT, @@ -46,12 +46,14 @@ export function timelineFilterComputations (store) { computeTimelineFilter(store, 'timelineShowFavs', { notifications: NOTIFICATION_FAVORITES }) computeTimelineFilter(store, 'timelineShowMentions', { notifications: NOTIFICATION_MENTIONS }) computeTimelineFilter(store, 'timelineShowPolls', { notifications: NOTIFICATION_POLLS }) + computeTimelineFilter(store, 'timelineShowSubscriptions', { notifications: NOTIFICATION_SUBSCRIPTIONS }) 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) + computeNotificationFilter(store, 'timelineNotificationShowSubscriptions', NOTIFICATION_SUBSCRIPTIONS) store.compute( 'timelineWordFilterContext', @@ -85,10 +87,10 @@ export function timelineFilterComputations (store) { [ 'timelineShowReblogs', 'timelineShowReplies', 'timelineShowFollows', 'timelineShowFavs', 'timelineShowMentions', 'timelineShowPolls', - 'timelineWordFilterContext' + 'timelineShowSubscriptions', 'timelineWordFilterContext' ], - (showReblogs, showReplies, showFollows, showFavs, showMentions, showPolls, wordFilterContext) => ( - createFilterFunction(showReblogs, showReplies, showFollows, showFavs, showMentions, showPolls, wordFilterContext) + (showReblogs, showReplies, showFollows, showFavs, showMentions, showPolls, showSubscriptions, wordFilterContext) => ( + createFilterFunction(showReblogs, showReplies, showFollows, showFavs, showMentions, showPolls, showSubscriptions, wordFilterContext) ) ) @@ -99,10 +101,10 @@ export function timelineFilterComputations (store) { [ 'timelineNotificationShowReblogs', 'timelineNotificationShowFollows', 'timelineNotificationShowFavs', 'timelineNotificationShowMentions', - 'timelineNotificationShowPolls' + 'timelineNotificationShowPolls', 'timelineNotificationShowSubscriptions' ], - (showReblogs, showFollows, showFavs, showMentions, showPolls) => ( - createFilterFunction(showReblogs, true, showFollows, showFavs, showMentions, showPolls, WORD_FILTER_CONTEXT_NOTIFICATIONS) + (showReblogs, showFollows, showFavs, showMentions, showPolls, showSubscriptions) => ( + createFilterFunction(showReblogs, true, showFollows, showFavs, showMentions, showPolls, showSubscriptions, WORD_FILTER_CONTEXT_NOTIFICATIONS) ) ) diff --git a/src/routes/_utils/createFilterFunction.js b/src/routes/_utils/createFilterFunction.js index 6846a6f7..23fc5097 100644 --- a/src/routes/_utils/createFilterFunction.js +++ b/src/routes/_utils/createFilterFunction.js @@ -1,7 +1,8 @@ // create a function for filtering timeline item summaries export const createFilterFunction = ( - showReblogs, showReplies, showFollows, showFavs, showMentions, showPolls, wordFilterContext + showReblogs, showReplies, showFollows, showFavs, showMentions, showPolls, + showSubscriptions, wordFilterContext ) => { return item => { if (item.filterContexts && item.filterContexts.includes(wordFilterContext)) { @@ -19,6 +20,8 @@ export const createFilterFunction = ( return showMentions case 'follow': return showFollows + case 'status': + return showSubscriptions } if (item.reblogId) { return showReblogs diff --git a/src/service-worker.js b/src/service-worker.js index 8aa7e520..03f247c5 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -214,6 +214,7 @@ async function showRichNotification (data, notification) { } case 'reblog': case 'favourite': + case 'status': case 'poll': { await self.registration.showNotification(data.title, { badge, diff --git a/tests/serverActions.js b/tests/serverActions.js index 873352df..990a6e25 100644 --- a/tests/serverActions.js +++ b/tests/serverActions.js @@ -1,18 +1,18 @@ -import { favoriteStatus } from '../src/routes/_api/favorite' +import { favoriteStatus } from '../src/routes/_api/favorite.js' import fetch from 'node-fetch' import FileApi from 'file-api' -import { users } from './users' -import { postStatus } from '../src/routes/_api/statuses' -import { deleteStatus } from '../src/routes/_api/delete' -import { authorizeFollowRequest, getFollowRequests } from '../src/routes/_api/followRequests' -import { followAccount, unfollowAccount } from '../src/routes/_api/follow' -import { updateCredentials } from '../src/routes/_api/updateCredentials' -import { reblogStatus } from '../src/routes/_api/reblog' +import { users } from './users.js' +import { postStatus } from '../src/routes/_api/statuses.js' +import { deleteStatus } from '../src/routes/_api/delete.js' +import { authorizeFollowRequest, getFollowRequests } from '../src/routes/_api/followRequests.js' +import { followAccount, unfollowAccount } from '../src/routes/_api/follow.js' +import { updateCredentials } from '../src/routes/_api/updateCredentials.js' +import { reblogStatus } from '../src/routes/_api/reblog.js' import { submitMedia } from './submitMedia.js' -import { voteOnPoll } from '../src/routes/_api/polls' -import { POLL_EXPIRY_DEFAULT } from '../src/routes/_static/polls' -import { createList, getLists } from '../src/routes/_api/lists' -import { createFilter, deleteFilter, getFilters } from '../src/routes/_api/filters' +import { voteOnPoll } from '../src/routes/_api/polls.js' +import { POLL_EXPIRY_DEFAULT } from '../src/routes/_static/polls.js' +import { createList, getLists } from '../src/routes/_api/lists.js' +import { createFilter, deleteFilter, getFilters } from '../src/routes/_api/filters.js' global.fetch = fetch global.File = FileApi.File diff --git a/tests/spec/113-block-unblock.js b/tests/spec/113-block-unblock.js index 4ddd2173..ff3b08ed 100644 --- a/tests/spec/113-block-unblock.js +++ b/tests/spec/113-block-unblock.js @@ -2,7 +2,7 @@ import { accountProfileFollowButton, accountProfileFollowedBy, accountProfileMoreOptionsButton, communityNavButton, getNthSearchResult, getNthStatus, getNthStatusOptionsButton, getNthDialogOptionsOption, getUrl, modalDialog, - sleep + sleep, getDialogOptionWithText } from '../utils' import { Selector as $ } from 'testcafe' import { loginAsFoobar } from '../roles' @@ -21,10 +21,10 @@ test('Can block and unblock an account from a status', async t => { await t .click(getNthStatusOptionsButton(1)) .expect(getNthDialogOptionsOption(1).innerText).contains('Unfollow @admin') - .expect(getNthDialogOptionsOption(2).innerText).contains('Block @admin') + .expect(getNthDialogOptionsOption(3).innerText).contains('Block @admin') await sleep(500) await t - .click(getNthDialogOptionsOption(2)) + .click(getDialogOptionWithText('Block @admin')) .expect(modalDialog.exists).notOk() await sleep(500) await t @@ -60,12 +60,9 @@ test('Can block and unblock an account from the account profile page', async t = await sleep(500) await t .click(accountProfileMoreOptionsButton) - .expect(getNthDialogOptionsOption(1).innerText).contains('Mention @baz') - .expect(getNthDialogOptionsOption(2).innerText).contains('Follow @baz') - .expect(getNthDialogOptionsOption(3).innerText).contains('Block @baz') await sleep(500) await t - .click(getNthDialogOptionsOption(3)) + .click(getDialogOptionWithText('Block @baz')) .expect(accountProfileFollowedBy.innerText).match(/blocked/i) .expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Unblock') .expect(accountProfileFollowButton.getAttribute('title')).eql('Unblock') diff --git a/tests/spec/114-mute-unmute.js b/tests/spec/114-mute-unmute.js index 8df6fbf2..935b7950 100644 --- a/tests/spec/114-mute-unmute.js +++ b/tests/spec/114-mute-unmute.js @@ -5,11 +5,10 @@ import { getNthSearchResult, getNthStatus, getNthStatusOptionsButton, - getNthDialogOptionsOption, getUrl, modalDialog, closeDialogButton, - confirmationDialogOKButton, sleep + confirmationDialogOKButton, sleep, getDialogOptionWithText } from '../utils' import { Selector as $ } from 'testcafe' import { loginAsFoobar } from '../roles' @@ -25,12 +24,9 @@ test('Can mute and unmute an account', async t => { await t.expect(getNthStatus(1).innerText).contains(post, { timeout: 20000 }) .click(getNthStatusOptionsButton(1)) - .expect(getNthDialogOptionsOption(1).innerText).contains('Unfollow @admin') - .expect(getNthDialogOptionsOption(2).innerText).contains('Block @admin') - .expect(getNthDialogOptionsOption(3).innerText).contains('Mute @admin') await sleep(1000) await t - .click(getNthDialogOptionsOption(3)) + .click(getDialogOptionWithText('Mute @admin')) await sleep(1000) await t .click(confirmationDialogOKButton) @@ -43,20 +39,13 @@ test('Can mute and unmute an account', async t => { .click(getNthSearchResult(1)) .expect(getUrl()).contains('/accounts/1') .click(accountProfileMoreOptionsButton) - .expect(getNthDialogOptionsOption(1).innerText).contains('Mention @admin') - .expect(getNthDialogOptionsOption(2).innerText).contains('Unfollow @admin') - .expect(getNthDialogOptionsOption(3).innerText).contains('Block @admin') - .expect(getNthDialogOptionsOption(4).innerText).contains('Unmute @admin') await sleep(1000) await t - .click(getNthDialogOptionsOption(4)) + .click(getDialogOptionWithText('Unmute @admin')) await sleep(1000) await t .click(accountProfileMoreOptionsButton) - .expect(getNthDialogOptionsOption(1).innerText).contains('Mention @admin') - .expect(getNthDialogOptionsOption(2).innerText).contains('Unfollow @admin') - .expect(getNthDialogOptionsOption(3).innerText).contains('Block @admin') - .expect(getNthDialogOptionsOption(4).innerText).contains('Mute @admin') + .expect(getDialogOptionWithText('Mute @admin').exists).ok() await sleep(1000) await t .click(closeDialogButton) diff --git a/tests/spec/139-notify-denotify.js b/tests/spec/139-notify-denotify.js new file mode 100644 index 00000000..3c626431 --- /dev/null +++ b/tests/spec/139-notify-denotify.js @@ -0,0 +1,49 @@ +import { + accountProfileMoreOptionsButton, + getNthStatus, + getNthStatusOptionsButton, + getUrl, + modalDialog, + sleep, getDialogOptionWithText, getNthStatusAccountLink, notificationsNavButton, getNthStatusHeader +} from '../utils' +import { loginAsFoobar } from '../roles' +import { postAs } from '../serverActions' + +fixture`139-notify-denotify.js` + .page`http://localhost:4002` + +test('Can notify and denotify an account', async t => { + await loginAsFoobar(t) + const post = 'ha ha ha' + await postAs('admin', post) + + await t.expect(getNthStatus(1).innerText).contains(post, { timeout: 20000 }) + .click(getNthStatusOptionsButton(1)) + await sleep(1000) + await t.click(getDialogOptionWithText('Subscribe to @admin')) + await sleep(1000) + await t + .expect(modalDialog.exists).notOk() + await sleep(1000) + const notificationPost = 'get a notification for this' + await postAs('admin', notificationPost) + await sleep(1000) + await t + .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (1 notification)', { + timeout: 20000 + }) + .click(notificationsNavButton) + .expect(getUrl()).contains('/notifications') + await t + .expect(getNthStatus(1).innerText).contains(notificationPost, { timeout: 20000 }) + .expect(getNthStatusHeader(1).innerText).contains('posted') + .click(getNthStatusAccountLink(1)) + .expect(getUrl()).contains('/accounts/1') + .click(accountProfileMoreOptionsButton) + await sleep(1000) + await t.click(getDialogOptionWithText('Unsubscribe from @admin')) + await sleep(1000) + await t.click(accountProfileMoreOptionsButton) + await t + .expect(getDialogOptionWithText('Subscribe to @admin').exists).ok() +}) diff --git a/tests/utils.js b/tests/utils.js index 56cbe8e7..593ba305 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -522,6 +522,10 @@ export function getNthStatusOptionsButton (n) { return $(`${getNthStatusSelector(n)} .status-toolbar button:nth-child(4)`) } +export function getNthStatusAccountLink (n) { + return $(`${getNthStatusSelector(n)} .status-author-name`) +} + export function getNthFavoritedLabel (n) { return getNthFavoriteButton(n).getAttribute('aria-label') } @@ -546,6 +550,10 @@ export function getNthDialogOptionsOption (n) { return $(`.modal-dialog li:nth-child(${n}) button`) } +export function getDialogOptionWithText (text) { + return $('.modal-dialog li button').withText(text) +} + export function getReblogsCount () { return reblogsCountElement.innerCount }