fix: fix bell notifications, add tests

This commit is contained in:
Nolan Lawson 2022-04-28 08:18:50 -07:00
parent 2e9afd711f
commit c67be9acc2
21 changed files with 177 additions and 152 deletions

View File

@ -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' },

View File

@ -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',

View File

@ -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 || '') })
)

View File

@ -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 })
}

View File

@ -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: {

View File

@ -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)
}
}
}

View File

@ -8,7 +8,6 @@
<div class="account-profile-grid-wrapper">
<div class="account-profile-grid">
<AccountProfileHeader {account} {relationship} {verifyCredentials} />
<AccountProfileNotify {account} {relationship} {verifyCredentials} />
<AccountProfileFollow {account} {relationship} {verifyCredentials} />
<AccountProfileNote {account} />
<AccountProfileMeta {account} />
@ -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 @@
</style>
<script>
import AccountProfileHeader from './AccountProfileHeader.html'
import AccountProfileNotify from './AccountProfileNotify.html'
import AccountProfileFollow from './AccountProfileFollow.html'
import AccountProfileNote from './AccountProfileNote.html'
import AccountProfileMeta from './AccountProfileMeta.html'
@ -147,7 +144,6 @@
AccountProfileHeader,
AccountProfileFollow,
AccountProfileNote,
AccountProfileNotify,
AccountProfileMeta,
AccountProfileDetails,
AccountProfileMovedBanner,

View File

@ -1,92 +0,0 @@
<div class="account-profile-notify {shown ? 'shown' : ''}">
<!--
The bell notification button (Mastodon 3.3+)
Shows if we're getting notifications or not.
It is not possible to turn on notifications for accounts you don't follow.
Also the instance can just have no support for this feature.
-->
<IconButton
className="account-profile-notify-icon-button"
{label}
pressedLabel="{intl.denotifyLabel}"
{href}
big={!$isVeryTinyMobileSize}
on:click="onNotifyButtonClick(event)"
ref:icon
/>
</div>
<style>
.account-profile-notify {
grid-area: notify;
align-self: flex-start;
display: none;
}
.account-profile-notify.shown {
display: block;
}
@media (max-width: 240px) {
.account-profile-notify {
justify-self: flex-end;
}
}
</style>
<script>
import IconButton from '../IconButton.html'
import { FOLLOW_BUTTON_ANIMATION } from '../../_static/animations.js'
import { store } from '../../_store/store.js'
import { setAccountNotify } from '../../_actions/notify.js'
import { formatIntl } from '../../_utils/formatIntl.js'
export default {
methods: {
oncreate () {
if (process.browser) {
window.__button = this
}
},
async onNotifyButtonClick (e) {
e.preventDefault()
e.stopPropagation()
const {
account,
accountId,
notifying
} = this.get()
if (notifying) { // unblock
await setAccountNotify(accountId, false)
} else { // follow/unfollow
this.refs.icon.animate(FOLLOW_BUTTON_ANIMATION)
await setAccountNotify(accountId, true)
}
}
},
store: () => store,
data: () => ({
}),
computed: {
accountId: ({ account }) => account.id,
notifying: ({ relationship }) => {
return relationship && relationship.notifying
},
href: ({ notifying }) => {
if (notifying) {
return '#fa-bell-ringing'
}
return '#fa-bell-o'
},
label: ({ notifying }) => {
if (notifying) {
return formatIntl('intl.notifyLabel')
}
return formatIntl('intl.denotifyLabel')
},
shown: ({ verifyCredentials, relationship }) => (
verifyCredentials && relationship && verifyCredentials.id !== relationship.id && relationship.following
),
},
components: {
IconButton
}
}
</script>

View File

@ -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
}
]
}),

View File

@ -76,6 +76,10 @@
{
key: 'poll',
label: 'intl.pollResults'
},
{
key: 'status',
label: 'intl.subscriptions'
}
]
}),

View File

@ -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'
),

View File

@ -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'

View File

@ -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'

View File

@ -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)
)
)

View File

@ -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

View File

@ -214,6 +214,7 @@ async function showRichNotification (data, notification) {
}
case 'reblog':
case 'favourite':
case 'status':
case 'poll': {
await self.registration.showNotification(data.title, {
badge,

View File

@ -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

View File

@ -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')

View File

@ -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)

View File

@ -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()
})

View File

@ -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
}