mute and unmute accounts

This commit is contained in:
Nolan Lawson 2018-04-14 21:58:06 -07:00
parent 732d1fded4
commit c98c0be315
12 changed files with 240 additions and 37 deletions

View File

@ -24,7 +24,6 @@ module.exports = [
{id: 'fa-paperclip', src: 'node_modules/font-awesome-svg-png/white/svg/paperclip.svg', title: 'Paperclip'},
{id: 'fa-thumb-tack', src: 'node_modules/font-awesome-svg-png/white/svg/thumb-tack.svg', title: 'Thumbtack'},
{id: 'fa-bars', src: 'node_modules/font-awesome-svg-png/white/svg/bars.svg', title: 'List'},
{id: 'fa-volume-off', src: 'node_modules/font-awesome-svg-png/white/svg/volume-off.svg', title: 'Volume off'},
{id: 'fa-ban', src: 'node_modules/font-awesome-svg-png/white/svg/ban.svg', title: 'Ban'},
{id: 'fa-camera', src: 'node_modules/font-awesome-svg-png/white/svg/camera.svg', title: 'Add media'},
{id: 'fa-smile', src: 'node_modules/font-awesome-svg-png/white/svg/smile-o.svg', title: 'Custom emoji'},
@ -33,5 +32,7 @@ module.exports = [
{id: 'fa-trash', src: 'node_modules/font-awesome-svg-png/white/svg/trash-o.svg', title: 'Delete'},
{id: 'fa-hourglass', src: 'node_modules/font-awesome-svg-png/white/svg/hourglass.svg', title: 'Follow requested'},
{id: 'fa-pencil', src: 'node_modules/font-awesome-svg-png/white/svg/pencil.svg', title: 'Compose'},
{id: 'fa-times', src: 'node_modules/font-awesome-svg-png/white/svg/times.svg', title: 'Close'}
{id: 'fa-times', src: 'node_modules/font-awesome-svg-png/white/svg/times.svg', title: 'Close'},
{id: 'fa-volume-off', src: 'node_modules/font-awesome-svg-png/white/svg/volume-off.svg', title: 'Mute'},
{id: 'fa-volume-up', src: 'node_modules/font-awesome-svg-png/white/svg/volume-up.svg', title: 'Unmute'}
]

27
routes/_actions/mute.js Normal file
View File

@ -0,0 +1,27 @@
import { store } from '../_store/store'
import { muteAccount, unmuteAccount } from '../_api/mute'
import { toast } from '../_utils/toast'
import { updateProfileAndRelationship } from './accounts'
export async function setAccountMuted (accountId, mute, toastOnSuccess) {
let instanceName = store.get('currentInstance')
let accessToken = store.get('accessToken')
try {
if (mute) {
await muteAccount(instanceName, accessToken, accountId)
} else {
await unmuteAccount(instanceName, accessToken, accountId)
}
await updateProfileAndRelationship(accountId)
if (toastOnSuccess) {
if (mute) {
toast.say('Muted account')
} else {
toast.say('Unmuted account')
}
}
} catch (e) {
console.error(e)
toast.say(`Unable to ${mute ? 'mute' : 'unmute'} account: ` + (e.message || ''))
}
}

12
routes/_api/mute.js Normal file
View File

@ -0,0 +1,12 @@
import { auth, basename } from './utils'
import { postWithTimeout } from '../_utils/ajax'
export async function muteAccount (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/mute`
return postWithTimeout(url, null, auth(accessToken))
}
export async function unmuteAccount (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/unmute`
return postWithTimeout(url, null, auth(accessToken))
}

View File

@ -16,6 +16,8 @@ import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog'
import { oncreate } from '../helpers/onCreateDialog'
import { setAccountBlocked } from '../../../_actions/block'
import { setAccountMuted } from '../../../_actions/mute'
import { setAccountFollowed } from '../../../_actions/follow'
export default {
oncreate,
@ -24,21 +26,62 @@ export default {
id: createDialogId()
}),
computed: {
blocking: (relationship) => relationship && relationship.blocking,
items: (account, blocking) => (
[
{
// begin account data copypasta
verifyCredentialsId: (verifyCredentials) => verifyCredentials.id,
following: (relationship) => relationship && relationship.following,
followRequested: (relationship) => relationship && relationship.requested,
accountId: (account) => account && account.id,
acct: (account) => account.acct,
muting: (relationship) => relationship.muting,
blocking: (relationship) => relationship.blocking,
followLabel: (following, followRequested, account, acct) => {
if (typeof following === 'undefined' || !account) {
return ''
}
return (following || followRequested)
? `Unfollow @${acct}`
: `Follow @${acct}`
},
followIcon: (following, followRequested) => {
return following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
},
blockLabel: (blocking, acct) => {
return blocking ? `Unblock @${acct}` : `Block @${acct}`
},
blockIcon: (blocking) => blocking ? '#fa-unlock' : '#fa-ban',
muteLabel: (muting, acct) => {
return muting ? `Unmute @${acct}` : `Mute @${acct}`
},
muteIcon: (muting) => muting ? '#fa-volume-up' : '#fa-volume-off',
// end account data copypasta
items: (blockLabel, blocking, blockIcon, muteLabel, muteIcon,
followLabel, followIcon, following, followRequested,
accountId, verifyCredentialsId, acct) => {
let isUser = accountId === verifyCredentialsId
return [
!isUser && {
key: 'mention',
label: `Mention @${account.acct}`,
label: `Mention @${acct}`,
icon: '#fa-comments'
},
{
!isUser && !blocking && {
key: 'follow',
label: followLabel,
icon: followIcon
},
!isUser && {
key: 'block',
label: blocking ? `Unblock @${account.acct}` : `Block @${account.acct}`,
icon: blocking ? '#fa-unlock' : '#fa-ban'
label: blockLabel,
icon: blockIcon
},
!isUser && !blocking && {
key: 'mute',
label: muteLabel,
icon: muteIcon
}
]
)
].filter(Boolean)
}
},
methods: {
show,
@ -47,25 +90,41 @@ export default {
switch (item.key) {
case 'mention':
return this.onMentionClicked()
case 'follow':
return this.onFollowClicked()
case 'block':
return this.onBlockClicked()
case 'mute':
return this.onMuteClicked()
}
},
async onMentionClicked() {
let account = this.get('account')
let acct = this.get('acct')
this.store.setComposeData('dialog', {
text: `@${account.acct} `
text: `@${acct} `
})
let dialogs = await importDialogs()
dialogs.showComposeDialog()
this.close()
},
async onFollowClicked() {
let accountId = this.get('accountId')
let following = this.get('following')
this.close()
await setAccountFollowed(accountId, !following, true)
},
async onBlockClicked() {
let account = this.get('account')
let blocking = this.get('blocking')
let accountId = account.id
this.close()
await setAccountBlocked(accountId, !blocking, true)
},
async onMuteClicked() {
let accountId = this.get('accountId')
let muting = this.get('muting')
this.close()
await setAccountMuted(accountId, !muting, true)
}
},
components: {

View File

@ -16,6 +16,7 @@ import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog'
import { oncreate } from '../helpers/onCreateDialog'
import { setAccountBlocked } from '../../../_actions/block'
import { setAccountMuted } from '../../../_actions/mute'
export default {
oncreate,
@ -23,44 +24,62 @@ export default {
relationship: ($currentAccountRelationship) => $currentAccountRelationship,
account: ($currentAccountProfile) => $currentAccountProfile,
verifyCredentials: ($currentVerifyCredentials) => $currentVerifyCredentials,
// begin account data copypasta
verifyCredentialsId: (verifyCredentials) => verifyCredentials.id,
following: (relationship) => relationship && relationship.following,
followRequested: (relationship) => relationship && relationship.requested,
accountId: (account) => account && account.id,
acct: (account) => account.acct,
muting: (relationship) => relationship.muting,
blocking: (relationship) => relationship.blocking,
followLabel: (following, followRequested, account) => {
followLabel: (following, followRequested, account, acct) => {
if (typeof following === 'undefined' || !account) {
return ''
}
return (following || followRequested)
? `Unfollow @${account.acct}`
: `Follow @${account.acct}`
? `Unfollow @${acct}`
: `Follow @${acct}`
},
blockLabel: (blocking, account) => {
return blocking ? `Unblock @${account.acct}` : `Block @${account.acct}`
followIcon: (following, followRequested) => {
return following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
},
items: (blockLabel, blocking, followLabel, following, followRequested, accountId, verifyCredentialsId) => (
[
accountId === verifyCredentialsId &&
{
blockLabel: (blocking, acct) => {
return blocking ? `Unblock @${acct}` : `Block @${acct}`
},
blockIcon: (blocking) => blocking ? '#fa-unlock' : '#fa-ban',
muteLabel: (muting, acct) => {
return muting ? `Unmute @${acct}` : `Mute @${acct}`
},
muteIcon: (muting) => muting ? '#fa-volume-up' : '#fa-volume-off',
// end account data copypasta
items: (blockLabel, blocking, blockIcon, muteLabel, muteIcon,
followLabel, followIcon, following, followRequested,
accountId, verifyCredentialsId) => {
let isUser = accountId === verifyCredentialsId
return [
isUser && {
key: 'delete',
label: 'Delete',
icon: '#fa-trash'
},
accountId !== verifyCredentialsId && !blocking &&
{
!isUser && !blocking && {
key: 'follow',
label: followLabel,
icon: following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
icon: followIcon
},
accountId !== verifyCredentialsId &&
{
!isUser && {
key: 'block',
label: blockLabel,
icon: blocking ? '#fa-unlock' : '#fa-ban'
icon: blockIcon
},
!isUser && !blocking && {
key: 'mute',
label: muteLabel,
icon: muteIcon
}
].filter(Boolean)
)
}
},
components: {
ModalDialog,
@ -78,6 +97,8 @@ export default {
return this.onFollowClicked()
case 'block':
return this.onBlockClicked()
case 'mute':
return this.onMuteClicked()
}
},
async onDeleteClicked() {
@ -96,6 +117,12 @@ export default {
let blocking = this.get('blocking')
this.close()
await setAccountBlocked(accountId, !blocking, true)
},
async onMuteClicked() {
let accountId = this.get('accountId')
let muting = this.get('muting')
this.close()
await setAccountMuted(accountId, !muting, true)
}
}
}

View File

@ -2,7 +2,7 @@ import AccountProfileOptionsDialog from '../components/AccountProfileOptionsDial
import { createDialogElement } from '../helpers/createDialogElement'
import { createDialogId } from '../helpers/createDialogId'
export function showAccountProfileOptionsDialog (account, relationship) {
export function showAccountProfileOptionsDialog (account, relationship, verifyCredentials) {
let dialog = new AccountProfileOptionsDialog({
target: createDialogElement(),
data: {
@ -10,7 +10,8 @@ export function showAccountProfileOptionsDialog (account, relationship) {
label: 'Profile options dialog',
title: '',
account: account,
relationship: relationship
relationship: relationship,
verifyCredentials: verifyCredentials
}
})
dialog.show()

View File

@ -5,7 +5,7 @@
<AccountProfileHeader :account :relationship :verifyCredentials />
<AccountProfileFollow :account :relationship :verifyCredentials />
<AccountProfileNote :account />
<AccountProfileDetails :account :relationship />
<AccountProfileDetails :account :relationship :verifyCredentials />
</div>
</div>
</div>

View File

@ -120,8 +120,9 @@
async onMoreOptionsClick() {
let account = this.get('account')
let relationship = this.get('relationship')
let verifyCredentials = this.get('verifyCredentials')
let dialogs = await importDialogs()
dialogs.showAccountProfileOptionsDialog(account, relationship)
dialogs.showAccountProfileOptionsDialog(account, relationship, verifyCredentials)
}
},
components: {

View File

@ -106,7 +106,6 @@ if (!localStorage.store_currentInstance) {
<symbol id="fa-paperclip" viewBox="0 0 1792 1792"><title>Paperclip</title><path d="M1596 1385q0 117-79 196t-196 79q-135 0-235-100L309 784Q196 669 196 513q0-159 110-270t269-111q158 0 273 113l605 606q10 10 10 22 0 16-30.5 46.5T1386 950q-13 0-23-10L757 333q-79-77-181-77-106 0-179 75t-73 181q0 105 76 181l776 777q63 63 145 63 64 0 106-42t42-106q0-82-63-145L825 659q-26-24-60-24-29 0-48 19t-19 48q0 32 25 59l410 410q10 10 10 22 0 16-31 47t-47 31q-12 0-22-10L633 851q-63-61-63-149 0-82 57-139t139-57q88 0 149 63l581 581q100 98 100 235z"></path></symbol>
<symbol id="fa-thumb-tack" viewBox="0 0 1792 1792"><title>Thumbtack</title><path d="M800 864V416q0-14-9-23t-23-9-23 9-9 23v448q0 14 9 23t23 9 23-9 9-23zm672 352q0 26-19 45t-45 19H979l-51 483q-2 12-10.5 20.5T897 1792h-1q-27 0-32-27l-76-485H384q-26 0-45-19t-19-45q0-123 78.5-221.5T576 896V384q-52 0-90-38t-38-90 38-90 90-38h640q52 0 90 38t38 90-38 90-90 38v512q99 0 177.5 98.5T1472 1216z"></path></symbol>
<symbol id="fa-bars" viewBox="0 0 1792 1792"><title>List</title><path d="M1664 1344v128q0 26-19 45t-45 19H192q-26 0-45-19t-19-45v-128q0-26 19-45t45-19h1408q26 0 45 19t19 45zm0-512v128q0 26-19 45t-45 19H192q-26 0-45-19t-19-45V832q0-26 19-45t45-19h1408q26 0 45 19t19 45zm0-512v128q0 26-19 45t-45 19H192q-26 0-45-19t-19-45V320q0-26 19-45t45-19h1408q26 0 45 19t19 45z"></path></symbol>
<symbol id="fa-volume-off" viewBox="0 0 1792 1792"><title>Volume off</title><path d="M1280 352v1088q0 26-19 45t-45 19-45-19l-333-333H576q-26 0-45-19t-19-45V704q0-26 19-45t45-19h262l333-333q19-19 45-19t45 19 19 45z"></path></symbol>
<symbol id="fa-ban" viewBox="0 0 1792 1792"><title>Ban</title><path d="M1440 893q0-161-87-295l-754 753q137 89 297 89 111 0 211.5-43.5T1281 1280t116-174.5 43-212.5zm-999 299l755-754q-135-91-300-91-148 0-273 73T425 619t-73 274q0 162 89 299zm1223-299q0 157-61 300t-163.5 246-245 164-298.5 61-298.5-61-245-164T189 1193t-61-300 61-299.5T352.5 348t245-164T896 123t298.5 61 245 164T1603 593.5t61 299.5z"></path></symbol>
<symbol id="fa-camera" viewBox="0 0 2048 1792"><title>Add media</title><path d="M1024 672q119 0 203.5 84.5T1312 960t-84.5 203.5T1024 1248t-203.5-84.5T736 960t84.5-203.5T1024 672zm704-416q106 0 181 75t75 181v896q0 106-75 181t-181 75H320q-106 0-181-75t-75-181V512q0-106 75-181t181-75h224l51-136q19-49 69.5-84.5T768 0h512q53 0 103.5 35.5T1453 120l51 136h224zm-704 1152q185 0 316.5-131.5T1472 960t-131.5-316.5T1024 512 707.5 643.5 576 960t131.5 316.5T1024 1408z"></path></symbol>
<symbol id="fa-smile" viewBox="0 0 1792 1792"><title>Custom emoji</title><path d="M1262 1075q-37 121-138 195t-228 74-228-74-138-195q-8-25 4-48.5t38-31.5q25-8 48.5 4t31.5 38q25 80 92.5 129.5T896 1216t151.5-49.5T1140 1037q8-26 32-38t49-4 37 31.5 4 48.5zM768 640q0 53-37.5 90.5T640 768t-90.5-37.5T512 640t37.5-90.5T640 512t90.5 37.5T768 640zm512 0q0 53-37.5 90.5T1152 768t-90.5-37.5T1024 640t37.5-90.5T1152 512t90.5 37.5T1280 640zm256 256q0-130-51-248.5t-136.5-204-204-136.5T896 256t-248.5 51-204 136.5-136.5 204T256 896t51 248.5 136.5 204 204 136.5 248.5 51 248.5-51 204-136.5 136.5-204 51-248.5zm128 0q0 209-103 385.5T1281.5 1561 896 1664t-385.5-103T231 1281.5 128 896t103-385.5T510.5 231 896 128t385.5 103T1561 510.5 1664 896z"></path></symbol>
@ -116,6 +115,8 @@ if (!localStorage.store_currentInstance) {
<symbol id="fa-hourglass" viewBox="0 0 1792 1792"><title>Follow requested</title><path d="M1632 1600q14 0 23 9t9 23v128q0 14-9 23t-23 9H160q-14 0-23-9t-9-23v-128q0-14 9-23t23-9h1472zm-1374-64q3-55 16-107t30-95 46-87 53.5-76 64.5-69.5 66-60 70.5-55T671 939t65-43q-43-28-65-43t-66.5-47.5-70.5-55-66-60-64.5-69.5-53.5-76-46-87-30-95-16-107h1276q-3 55-16 107t-30 95-46 87-53.5 76-64.5 69.5-66 60-70.5 55T1121 853t-65 43q43 28 65 43t66.5 47.5 70.5 55 66 60 64.5 69.5 53.5 76 46 87 30 95 16 107H258zM1632 0q14 0 23 9t9 23v128q0 14-9 23t-23 9H160q-14 0-23-9t-9-23V32q0-14 9-23t23-9h1472z"></path></symbol>
<symbol id="fa-pencil" viewBox="0 0 1792 1792"><title>Compose</title><path d="M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832H128v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z"></path></symbol>
<symbol id="fa-times" viewBox="0 0 1792 1792"><title>Close</title><path d="M1490 1322q0 40-28 68l-136 136q-28 28-68 28t-68-28l-294-294-294 294q-28 28-68 28t-68-28l-136-136q-28-28-28-68t28-68l294-294-294-294q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 294 294-294q28-28 68-28t68 28l136 136q28 28 28 68t-28 68l-294 294 294 294q28 28 28 68z"></path></symbol>
<symbol id="fa-volume-off" viewBox="0 0 1792 1792"><title>Mute</title><path d="M1280 352v1088q0 26-19 45t-45 19-45-19l-333-333H576q-26 0-45-19t-19-45V704q0-26 19-45t45-19h262l333-333q19-19 45-19t45 19 19 45z"></path></symbol>
<symbol id="fa-volume-up" viewBox="0 0 1792 1792"><title>Unmute</title><path d="M832 352v1088q0 26-19 45t-45 19-45-19l-333-333H128q-26 0-45-19t-19-45V704q0-26 19-45t45-19h262l333-333q19-19 45-19t45 19 19 45zm384 544q0 76-42.5 141.5T1061 1131q-10 5-25 5-26 0-45-18.5t-19-45.5q0-21 12-35.5t29-25 34-23 29-36 12-56.5-12-56.5-29-36-34-23-29-25-12-35.5q0-27 19-45.5t45-18.5q15 0 25 5 70 27 112.5 93t42.5 142zm256 0q0 153-85 282.5T1162 1367q-13 5-25 5-27 0-46-19t-19-45q0-39 39-59 56-29 76-44 74-54 115.5-135.5T1344 896t-41.5-173.5T1187 587q-20-15-76-44-39-20-39-59 0-26 19-45t45-19q13 0 26 5 140 59 225 188.5t85 282.5zm256 0q0 230-127 422.5T1263 1602q-13 5-26 5-26 0-45-19t-19-45q0-36 39-59 7-4 22.5-10.5t22.5-10.5q46-25 82-51 123-91 192-227t69-289-69-289-192-227q-36-26-82-51-7-4-22.5-10.5T1212 308q-39-23-39-59 0-26 19-45t45-19q13 0 26 5 211 91 338 283.5T1728 896z"></path></symbol>
</svg><!-- end insert svg here -->
</svg>
<!-- The application will be rendered inside this element,

View File

@ -31,6 +31,8 @@ test('Can block and unblock an account from a status', async t => {
.click(accountProfileFollowButton)
.expect(accountProfileFollowedBy.innerText).contains('')
.expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Follow')
.click(accountProfileFollowButton)
.expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Unfollow')
})
test('Can block and unblock an account from the account profile page', async t => {
@ -39,8 +41,9 @@ test('Can block and unblock an account from the account profile page', async t =
.expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Follow')
.click(accountProfileMoreOptionsButton)
.expect(getNthDialogOptionsOption(1).innerText).contains('Mention @baz')
.expect(getNthDialogOptionsOption(2).innerText).contains('Block @baz')
.click(getNthDialogOptionsOption(2))
.expect(getNthDialogOptionsOption(2).innerText).contains('Follow @baz')
.expect(getNthDialogOptionsOption(3).innerText).contains('Block @baz')
.click(getNthDialogOptionsOption(3))
.expect(accountProfileFollowedBy.innerText).match(/blocked/i)
.expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Unblock')
.click(accountProfileFollowButton)

View File

@ -0,0 +1,43 @@
import {
accountProfileFollowButton,
accountProfileMoreOptionsButton, communityNavButton, getNthSearchResult,
getNthStatus, getNthStatusOptionsButton, getNthDialogOptionsOption, getUrl, modalDialog
} from '../utils'
import { Selector as $ } from 'testcafe'
import { foobarRole } from '../roles'
import { postAs } from '../serverActions'
fixture`114-mute-unmute.js`
.page`http://localhost:4002`
test('Can mute and unmute an account', async t => {
await t.useRole(foobarRole)
let post = 'blah blah blah'
await postAs('admin', post)
await t.expect(getNthStatus(0).innerText).contains(post, {timeout: 20000})
.click(getNthStatusOptionsButton(0))
.expect(getNthDialogOptionsOption(1).innerText).contains('Unfollow @admin')
.expect(getNthDialogOptionsOption(2).innerText).contains('Block @admin')
.expect(getNthDialogOptionsOption(3).innerText).contains('Mute @admin')
.click(getNthDialogOptionsOption(3))
.expect(modalDialog.exists).notOk()
.click(communityNavButton)
.click($('a[href="/muted"]'))
.expect(getNthSearchResult(1).innerText).contains('@admin')
.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')
.click(getNthDialogOptionsOption(4))
.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')
.click(getNthDialogOptionsOption(2))
.expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Unfollow')
})

View File

@ -0,0 +1,28 @@
import {
accountProfileFollowButton,
accountProfileMoreOptionsButton, closeDialogButton,
getNthDialogOptionsOption
} from '../utils'
import { foobarRole } from '../roles'
fixture`115-follow-unfollow.js`
.page`http://localhost:4002`
test('Can follow and unfollow an account from the profile page', async t => {
await t.useRole(foobarRole)
.navigateTo('/accounts/5')
.expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Follow')
.click(accountProfileMoreOptionsButton)
.expect(getNthDialogOptionsOption(1).innerText).contains('Mention @baz')
.expect(getNthDialogOptionsOption(2).innerText).contains('Follow @baz')
.click(getNthDialogOptionsOption(2))
.expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Unfollow')
.click(accountProfileMoreOptionsButton)
.expect(getNthDialogOptionsOption(2).innerText).contains('Unfollow @baz')
.click(getNthDialogOptionsOption(2))
.expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Follow')
.click(accountProfileMoreOptionsButton)
.expect(getNthDialogOptionsOption(2).innerText).contains('Follow @baz')
.click(closeDialogButton)
.expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Follow')
})