forked from cybrespace/pinafore
add ability to pin and unpin statuses (#235)
* add ability to pin and unpin statuses * add another test
This commit is contained in:
parent
d079b6d9e1
commit
8089202977
|
@ -1,6 +1,6 @@
|
||||||
import { actions } from './mastodon-data'
|
import { actions } from './mastodon-data'
|
||||||
import { users } from '../tests/users'
|
import { users } from '../tests/users'
|
||||||
import { pinStatus, postStatus } from '../routes/_api/statuses'
|
import { postStatus } from '../routes/_api/statuses'
|
||||||
import { followAccount } from '../routes/_api/follow'
|
import { followAccount } from '../routes/_api/follow'
|
||||||
import { favoriteStatus } from '../routes/_api/favorite'
|
import { favoriteStatus } from '../routes/_api/favorite'
|
||||||
import { reblogStatus } from '../routes/_api/reblog'
|
import { reblogStatus } from '../routes/_api/reblog'
|
||||||
|
@ -10,6 +10,7 @@ import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import FormData from 'form-data'
|
import FormData from 'form-data'
|
||||||
import { auth } from '../routes/_api/utils'
|
import { auth } from '../routes/_api/utils'
|
||||||
|
import { pinStatus } from '../routes/_api/pin'
|
||||||
|
|
||||||
global.File = FileApi.File
|
global.File = FileApi.File
|
||||||
global.FormData = FileApi.FormData
|
global.FormData = FileApi.FormData
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { store } from '../_store/store'
|
||||||
|
import { toast } from '../_utils/toast'
|
||||||
|
import { pinStatus, unpinStatus } from '../_api/pin'
|
||||||
|
import { setStatusPinned as setStatusPinnedInDatabase } from '../_database/timelines/updateStatus'
|
||||||
|
import { emit } from '../_utils/eventBus'
|
||||||
|
|
||||||
|
export async function setStatusPinnedOrUnpinned (statusId, pinned, toastOnSuccess) {
|
||||||
|
let { currentInstance, accessToken } = store.get()
|
||||||
|
try {
|
||||||
|
if (pinned) {
|
||||||
|
await pinStatus(currentInstance, accessToken, statusId)
|
||||||
|
} else {
|
||||||
|
await unpinStatus(currentInstance, accessToken, statusId)
|
||||||
|
}
|
||||||
|
if (toastOnSuccess) {
|
||||||
|
if (pinned) {
|
||||||
|
toast.say('Pinned status')
|
||||||
|
} else {
|
||||||
|
toast.say('Unpinned status')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await setStatusPinnedInDatabase(currentInstance, statusId, pinned)
|
||||||
|
emit('updatePinnedStatuses')
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
toast.say(`Unable to ${pinned ? 'pin' : 'unpin'} status: ` + (e.message || ''))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { postWithTimeout } from '../_utils/ajax'
|
||||||
|
import { auth, basename } from './utils'
|
||||||
|
|
||||||
|
export async function pinStatus (instanceName, accessToken, statusId) {
|
||||||
|
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/pin`
|
||||||
|
return postWithTimeout(url, null, auth(accessToken))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unpinStatus (instanceName, accessToken, statusId) {
|
||||||
|
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/unpin`
|
||||||
|
return postWithTimeout(url, null, auth(accessToken))
|
||||||
|
}
|
|
@ -23,13 +23,3 @@ export async function postStatus (instanceName, accessToken, text, inReplyToId,
|
||||||
|
|
||||||
return postWithTimeout(url, body, auth(accessToken))
|
return postWithTimeout(url, body, auth(accessToken))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function pinStatus (instanceName, accessToken, statusId) {
|
|
||||||
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/pin`
|
|
||||||
return postWithTimeout(url, null, auth(accessToken))
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function unpinStatus (instanceName, accessToken, statusId) {
|
|
||||||
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/unpin`
|
|
||||||
return postWithTimeout(url, null, auth(accessToken))
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { close } from '../helpers/closeDialog'
|
||||||
import { oncreate } from '../helpers/onCreateDialog'
|
import { oncreate } from '../helpers/onCreateDialog'
|
||||||
import { setAccountBlocked } from '../../../_actions/block'
|
import { setAccountBlocked } from '../../../_actions/block'
|
||||||
import { setAccountMuted } from '../../../_actions/mute'
|
import { setAccountMuted } from '../../../_actions/mute'
|
||||||
|
import { setStatusPinnedOrUnpinned } from '../../../_actions/pin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate,
|
oncreate,
|
||||||
|
@ -24,6 +25,8 @@ export default {
|
||||||
relationship: ($currentAccountRelationship) => $currentAccountRelationship,
|
relationship: ($currentAccountRelationship) => $currentAccountRelationship,
|
||||||
account: ($currentAccountProfile) => $currentAccountProfile,
|
account: ($currentAccountProfile) => $currentAccountProfile,
|
||||||
verifyCredentials: ($currentVerifyCredentials) => $currentVerifyCredentials,
|
verifyCredentials: ($currentVerifyCredentials) => $currentVerifyCredentials,
|
||||||
|
statusId: (status) => status.id,
|
||||||
|
pinned: (status) => status.pinned,
|
||||||
// begin account data copypasta
|
// begin account data copypasta
|
||||||
verifyCredentialsId: (verifyCredentials) => verifyCredentials.id,
|
verifyCredentialsId: (verifyCredentials) => verifyCredentials.id,
|
||||||
following: (relationship) => relationship && relationship.following,
|
following: (relationship) => relationship && relationship.following,
|
||||||
|
@ -52,16 +55,21 @@ export default {
|
||||||
},
|
},
|
||||||
muteIcon: (muting) => muting ? '#fa-volume-up' : '#fa-volume-off',
|
muteIcon: (muting) => muting ? '#fa-volume-up' : '#fa-volume-off',
|
||||||
// end account data copypasta
|
// end account data copypasta
|
||||||
items: (blockLabel, blocking, blockIcon, muteLabel, muteIcon,
|
isUser: (accountId, verifyCredentialsId) => accountId === verifyCredentialsId,
|
||||||
followLabel, followIcon, following, followRequested,
|
pinLabel: (pinned, isUser) => isUser ? (pinned ? 'Unpin from profile' : 'Pin to profile') : '',
|
||||||
accountId, verifyCredentialsId) => {
|
items: (blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon,
|
||||||
let isUser = accountId === verifyCredentialsId
|
following, followRequested, pinLabel, isUser) => {
|
||||||
return [
|
return [
|
||||||
isUser && {
|
isUser && {
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
icon: '#fa-trash'
|
icon: '#fa-trash'
|
||||||
},
|
},
|
||||||
|
isUser && {
|
||||||
|
key: 'pin',
|
||||||
|
label: pinLabel,
|
||||||
|
icon: '#fa-thumb-tack'
|
||||||
|
},
|
||||||
!isUser && !blocking && {
|
!isUser && !blocking && {
|
||||||
key: 'follow',
|
key: 'follow',
|
||||||
label: followLabel,
|
label: followLabel,
|
||||||
|
@ -93,6 +101,8 @@ export default {
|
||||||
switch (item.key) {
|
switch (item.key) {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
return this.onDeleteClicked()
|
return this.onDeleteClicked()
|
||||||
|
case 'pin':
|
||||||
|
return this.onPinClicked()
|
||||||
case 'follow':
|
case 'follow':
|
||||||
return this.onFollowClicked()
|
return this.onFollowClicked()
|
||||||
case 'block':
|
case 'block':
|
||||||
|
@ -106,6 +116,11 @@ export default {
|
||||||
this.close()
|
this.close()
|
||||||
await doDeleteStatus(statusId)
|
await doDeleteStatus(statusId)
|
||||||
},
|
},
|
||||||
|
async onPinClicked () {
|
||||||
|
let { statusId, pinned } = this.get()
|
||||||
|
this.close()
|
||||||
|
await setStatusPinnedOrUnpinned(statusId, !pinned, true)
|
||||||
|
},
|
||||||
async onFollowClicked () {
|
async onFollowClicked () {
|
||||||
let { accountId, following } = this.get()
|
let { accountId, following } = this.get()
|
||||||
this.close()
|
this.close()
|
||||||
|
|
|
@ -2,14 +2,14 @@ import StatusOptionsDialog from '../components/StatusOptionsDialog.html'
|
||||||
import { createDialogElement } from '../helpers/createDialogElement'
|
import { createDialogElement } from '../helpers/createDialogElement'
|
||||||
import { createDialogId } from '../helpers/createDialogId'
|
import { createDialogId } from '../helpers/createDialogId'
|
||||||
|
|
||||||
export default function showStatusOptionsDialog (statusId) {
|
export default function showStatusOptionsDialog (status) {
|
||||||
let dialog = new StatusOptionsDialog({
|
let dialog = new StatusOptionsDialog({
|
||||||
target: createDialogElement(),
|
target: createDialogElement(),
|
||||||
data: {
|
data: {
|
||||||
id: createDialogId(),
|
id: createDialogId(),
|
||||||
label: 'Status options dialog',
|
label: 'Status options dialog',
|
||||||
title: '',
|
title: '',
|
||||||
statusId: statusId
|
status: status
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
|
@ -107,11 +107,11 @@
|
||||||
async onOptionsClick (e) {
|
async onOptionsClick (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
let { originalStatusId, originalAccountId } = this.get()
|
let { originalStatus, originalAccountId } = this.get()
|
||||||
let updateRelationshipPromise = updateProfileAndRelationship(originalAccountId)
|
let updateRelationshipPromise = updateProfileAndRelationship(originalAccountId)
|
||||||
let showStatusOptionsDialog = await importShowStatusOptionsDialog()
|
let showStatusOptionsDialog = await importShowStatusOptionsDialog()
|
||||||
await updateRelationshipPromise
|
await updateRelationshipPromise
|
||||||
showStatusOptionsDialog(originalStatusId)
|
showStatusOptionsDialog(originalStatus)
|
||||||
},
|
},
|
||||||
onPostedStatus (realm, inReplyToUuid) {
|
onPostedStatus (realm, inReplyToUuid) {
|
||||||
let {
|
let {
|
||||||
|
|
|
@ -1,33 +1,38 @@
|
||||||
<div role="feed" aria-label="Pinned toots" class="pinned-statuses">
|
<div role="feed" aria-label="Pinned toots" class="pinned-statuses">
|
||||||
{{#if pinnedStatuses}}
|
{{#each pinnedStatuses as status, index @id}}
|
||||||
{{#each pinnedStatuses as status, index}}
|
<Status :status
|
||||||
<Status :status
|
timelineType="pinned"
|
||||||
timelineType="pinned"
|
timelineValue="{{accountId}}"
|
||||||
timelineValue="{{accountId}}"
|
:index
|
||||||
:index
|
length="{{pinnedStatuses.length}}"
|
||||||
length="{{pinnedStatuses.length}}"
|
/>
|
||||||
/>
|
{{/each}}
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
import { store } from '../../_store/store'
|
import { store } from '../../_store/store'
|
||||||
import Status from '../status/Status.html'
|
import Status from '../status/Status.html'
|
||||||
import { updatePinnedStatusesForAccount } from '../../_actions/pinnedStatuses'
|
import { updatePinnedStatusesForAccount } from '../../_actions/pinnedStatuses'
|
||||||
|
import { on } from '../../_utils/eventBus'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async oncreate () {
|
async oncreate () {
|
||||||
let { accountId } = this.get()
|
on('updatePinnedStatuses', this, () => this.updatePinnedStatuses())
|
||||||
await updatePinnedStatusesForAccount(accountId)
|
await this.updatePinnedStatuses()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
pinnedStatuses: ($pinnedStatuses, $currentInstance, accountId) => {
|
pinnedStatuses: ($pinnedStatuses, $currentInstance, accountId) => {
|
||||||
return $pinnedStatuses[$currentInstance] && $pinnedStatuses[$currentInstance][accountId]
|
return ($pinnedStatuses[$currentInstance] && $pinnedStatuses[$currentInstance][accountId]) || []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
store: () => store,
|
store: () => store,
|
||||||
components: {
|
components: {
|
||||||
Status
|
Status
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async updatePinnedStatuses () {
|
||||||
|
let { accountId } = this.get()
|
||||||
|
await updatePinnedStatusesForAccount(accountId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -13,10 +13,19 @@ export async function insertPinnedStatuses (instanceName, accountId, statuses) {
|
||||||
let storeNames = [PINNED_STATUSES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
|
let storeNames = [PINNED_STATUSES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
|
||||||
await dbPromise(db, storeNames, 'readwrite', (stores) => {
|
await dbPromise(db, storeNames, 'readwrite', (stores) => {
|
||||||
let [ pinnedStatusesStore, statusesStore, accountsStore ] = stores
|
let [ pinnedStatusesStore, statusesStore, accountsStore ] = stores
|
||||||
statuses.forEach((status, i) => {
|
|
||||||
storeStatus(statusesStore, accountsStore, status)
|
let keyRange = createPinnedStatusKeyRange(accountId)
|
||||||
pinnedStatusesStore.put(status.id, createPinnedStatusId(accountId, i))
|
pinnedStatusesStore.getAll(keyRange).onsuccess = e => {
|
||||||
})
|
// if there was e.g. 1 pinned status before and 2 now, then we need to delete the old one
|
||||||
|
let existingPinnedStatuses = e.target.result
|
||||||
|
for (let i = statuses.length; i < existingPinnedStatuses.length; i++) {
|
||||||
|
pinnedStatusesStore.delete(createPinnedStatusKeyRange(accountId, i))
|
||||||
|
}
|
||||||
|
statuses.forEach((status, i) => {
|
||||||
|
storeStatus(statusesStore, accountsStore, status)
|
||||||
|
pinnedStatusesStore.put(status.id, createPinnedStatusId(accountId, i))
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,3 +39,9 @@ export async function setStatusReblogged (instanceName, statusId, reblogged) {
|
||||||
status.reblogs_count = (status.reblogs_count || 0) + delta
|
status.reblogs_count = (status.reblogs_count || 0) + delta
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setStatusPinned (instanceName, statusId, pinned) {
|
||||||
|
return updateStatus(instanceName, statusId, status => {
|
||||||
|
status.pinned = pinned
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Selector as $ } from 'testcafe'
|
import { Selector as $ } from 'testcafe'
|
||||||
import { getUrl } from '../utils'
|
import { communityNavButton, getNthPinnedStatus, getUrl } from '../utils'
|
||||||
import { foobarRole } from '../roles'
|
import { foobarRole } from '../roles'
|
||||||
|
|
||||||
fixture`004-pinned-statuses.js`
|
fixture`004-pinned-statuses.js`
|
||||||
|
@ -7,9 +7,9 @@ fixture`004-pinned-statuses.js`
|
||||||
|
|
||||||
test("shows a user's pinned statuses", async t => {
|
test("shows a user's pinned statuses", async t => {
|
||||||
await t.useRole(foobarRole)
|
await t.useRole(foobarRole)
|
||||||
.click($('nav a[aria-label=Community]'))
|
.click(communityNavButton)
|
||||||
.expect(getUrl()).contains('/community')
|
.expect(getUrl()).contains('/community')
|
||||||
.click($('a').withText(('Pinned')))
|
.click($('a[href="/pinned"]'))
|
||||||
.expect(getUrl()).contains('/pinned')
|
.expect(getUrl()).contains('/pinned')
|
||||||
.expect($('.status-article').getAttribute('aria-posinset')).eql('0')
|
.expect($('.status-article').getAttribute('aria-posinset')).eql('0')
|
||||||
.expect($('.status-article').getAttribute('aria-setsize')).eql('1')
|
.expect($('.status-article').getAttribute('aria-setsize')).eql('1')
|
||||||
|
@ -19,17 +19,17 @@ test("shows a user's pinned statuses", async t => {
|
||||||
test("shows pinned statuses on a user's account page", async t => {
|
test("shows pinned statuses on a user's account page", async t => {
|
||||||
await t.useRole(foobarRole)
|
await t.useRole(foobarRole)
|
||||||
.navigateTo('/accounts/2')
|
.navigateTo('/accounts/2')
|
||||||
.expect($('.pinned-statuses .status-article').getAttribute('aria-posinset')).eql('0')
|
.expect(getNthPinnedStatus(0).getAttribute('aria-posinset')).eql('0')
|
||||||
.expect($('.pinned-statuses .status-article').getAttribute('aria-setsize')).eql('1')
|
.expect(getNthPinnedStatus(0).getAttribute('aria-setsize')).eql('1')
|
||||||
.expect($('.pinned-statuses .status-article').innerText).contains('this is unlisted')
|
.expect(getNthPinnedStatus(0).innerText).contains('this is unlisted')
|
||||||
})
|
})
|
||||||
|
|
||||||
test("shows pinned statuses on a user's account page 2", async t => {
|
test("shows pinned statuses on a user's account page 2", async t => {
|
||||||
await t.useRole(foobarRole)
|
await t.useRole(foobarRole)
|
||||||
.navigateTo('/accounts/3')
|
.navigateTo('/accounts/3')
|
||||||
.expect($('.pinned-statuses .status-article').getAttribute('aria-posinset')).eql('0')
|
.expect(getNthPinnedStatus(0).getAttribute('aria-posinset')).eql('0')
|
||||||
.expect($('.pinned-statuses .status-article').getAttribute('aria-setsize')).eql('2')
|
.expect(getNthPinnedStatus(0).getAttribute('aria-setsize')).eql('2')
|
||||||
.expect($('.pinned-statuses .status-article').innerText).contains('pinned toot 1')
|
.expect(getNthPinnedStatus(0).innerText).contains('pinned toot 1')
|
||||||
.expect($('.pinned-statuses .status-article[aria-posinset="1"]').getAttribute('aria-setsize')).eql('2')
|
.expect(getNthPinnedStatus(1).getAttribute('aria-setsize')).eql('2')
|
||||||
.expect($('.pinned-statuses .status-article[aria-posinset="1"]').innerText).contains('pinned toot 2')
|
.expect(getNthPinnedStatus(1).innerText).contains('pinned toot 2')
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { lockedAccountRole } from '../roles'
|
import { lockedAccountRole } from '../roles'
|
||||||
import { followAs, unfollowAs } from '../serverActions'
|
import { followAs, unfollowAs } from '../serverActions'
|
||||||
import {
|
import {
|
||||||
|
avatarInComposeBox,
|
||||||
communityNavButton, followersButton, getNthSearchResult, getSearchResultByHref, getUrl, goBack,
|
communityNavButton, followersButton, getNthSearchResult, getSearchResultByHref, getUrl, goBack,
|
||||||
homeNavButton, sleep
|
homeNavButton, sleep
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
|
@ -67,7 +68,7 @@ test('Can approve and reject follow requests', async t => {
|
||||||
.expect(getNthSearchResult(1).exists).notOk({timeout})
|
.expect(getNthSearchResult(1).exists).notOk({timeout})
|
||||||
// check our follow list to make sure they follow us
|
// check our follow list to make sure they follow us
|
||||||
.click(homeNavButton)
|
.click(homeNavButton)
|
||||||
.click($('.compose-box-avatar'))
|
.click(avatarInComposeBox)
|
||||||
.expect(getUrl()).contains(`/accounts/${users.LockedAccount.id}`)
|
.expect(getUrl()).contains(`/accounts/${users.LockedAccount.id}`)
|
||||||
.click(followersButton)
|
.click(followersButton)
|
||||||
.expect(getNthSearchResult(1).innerText).match(/(@admin|@quux)/)
|
.expect(getNthSearchResult(1).innerText).match(/(@admin|@quux)/)
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { foobarRole } from '../roles'
|
||||||
|
import { postAs } from '../serverActions'
|
||||||
|
import {
|
||||||
|
avatarInComposeBox, getNthDialogOptionsOption, getNthPinnedStatus, getNthPinnedStatusFavoriteButton, getNthStatus,
|
||||||
|
getNthStatusOptionsButton, getUrl, sleep
|
||||||
|
} from '../utils'
|
||||||
|
import { users } from '../users'
|
||||||
|
|
||||||
|
fixture`117-pin-unpin.js`
|
||||||
|
.page`http://localhost:4002`
|
||||||
|
|
||||||
|
test('Can pin statuses', async t => {
|
||||||
|
await t.useRole(foobarRole)
|
||||||
|
|
||||||
|
await postAs('foobar', 'I am going to pin this')
|
||||||
|
|
||||||
|
await sleep(2000)
|
||||||
|
|
||||||
|
await t.click(avatarInComposeBox)
|
||||||
|
.expect(getUrl()).contains(`/accounts/${users.foobar.id}`)
|
||||||
|
.expect(getNthPinnedStatus(0).getAttribute('aria-setsize')).eql('1')
|
||||||
|
.expect(getNthPinnedStatus(0).innerText).contains('this is unlisted')
|
||||||
|
.expect(getNthStatus(0).innerText).contains('I am going to pin this')
|
||||||
|
.click(getNthStatusOptionsButton(0))
|
||||||
|
.expect(getNthDialogOptionsOption(1).innerText).contains('Delete')
|
||||||
|
.expect(getNthDialogOptionsOption(2).innerText).contains('Pin to profile')
|
||||||
|
.click(getNthDialogOptionsOption(2))
|
||||||
|
.expect(getNthPinnedStatus(0).getAttribute('aria-setsize')).eql('2')
|
||||||
|
.expect(getNthPinnedStatus(0).innerText).contains('I am going to pin this')
|
||||||
|
.expect(getNthPinnedStatus(1).innerText).contains('this is unlisted')
|
||||||
|
.expect(getNthStatus(0).innerText).contains('I am going to pin this')
|
||||||
|
.click(getNthStatusOptionsButton(0))
|
||||||
|
.expect(getNthDialogOptionsOption(1).innerText).contains('Delete')
|
||||||
|
.expect(getNthDialogOptionsOption(2).innerText).contains('Unpin from profile')
|
||||||
|
.click(getNthDialogOptionsOption(2))
|
||||||
|
.expect(getUrl()).contains(`/accounts/${users.foobar.id}`)
|
||||||
|
.expect(getNthPinnedStatus(0).getAttribute('aria-setsize')).eql('1')
|
||||||
|
.expect(getNthPinnedStatus(0).innerText).contains('this is unlisted')
|
||||||
|
.expect(getNthStatus(0).innerText).contains('I am going to pin this')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Can favorite a pinned status', async t => {
|
||||||
|
await t.useRole(foobarRole)
|
||||||
|
.click(avatarInComposeBox)
|
||||||
|
.expect(getNthPinnedStatus(0).getAttribute('aria-setsize')).eql('1')
|
||||||
|
.expect(getNthPinnedStatusFavoriteButton(0).getAttribute('aria-pressed')).eql('false')
|
||||||
|
.click(getNthPinnedStatusFavoriteButton(0))
|
||||||
|
.expect(getNthPinnedStatusFavoriteButton(0).getAttribute('aria-pressed')).eql('true')
|
||||||
|
.click(getNthPinnedStatusFavoriteButton(0))
|
||||||
|
.expect(getNthPinnedStatusFavoriteButton(0).getAttribute('aria-pressed')).eql('false')
|
||||||
|
})
|
|
@ -41,6 +41,7 @@ export const addInstanceButton = $('#submitButton')
|
||||||
export const mastodonLogInButton = $('button[type="submit"]')
|
export const mastodonLogInButton = $('button[type="submit"]')
|
||||||
export const followsButton = $('.account-profile-details > *:nth-child(2)')
|
export const followsButton = $('.account-profile-details > *:nth-child(2)')
|
||||||
export const followersButton = $('.account-profile-details > *:nth-child(3)')
|
export const followersButton = $('.account-profile-details > *:nth-child(3)')
|
||||||
|
export const avatarInComposeBox = $('.compose-box-avatar')
|
||||||
|
|
||||||
export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({
|
export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({
|
||||||
innerCount: el => parseInt(el.innerText, 10)
|
innerCount: el => parseInt(el.innerText, 10)
|
||||||
|
@ -224,6 +225,14 @@ export function getReblogsCount () {
|
||||||
return reblogsCountElement.innerCount
|
return reblogsCountElement.innerCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNthPinnedStatus (n) {
|
||||||
|
return $(`.pinned-statuses article[aria-posinset="${n}"]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNthPinnedStatusFavoriteButton (n) {
|
||||||
|
return getNthPinnedStatus(n).find('.status-toolbar button:nth-child(3)')
|
||||||
|
}
|
||||||
|
|
||||||
export async function validateTimeline (t, timeline) {
|
export async function validateTimeline (t, timeline) {
|
||||||
for (let i = 0; i < timeline.length; i++) {
|
for (let i = 0; i < timeline.length; i++) {
|
||||||
let status = timeline[i]
|
let status = timeline[i]
|
||||||
|
|
Loading…
Reference in New Issue