fix(a11y): improved aria-label for status and notifications (#690)
* fix(a11y): improved aria-label for status and notifications fixes #689 * only calculate formatted date once * fixup tests * fixup tests more * fixup * fixup tests again
This commit is contained in:
parent
2db06ea472
commit
cc81a7bec6
|
@ -143,7 +143,8 @@
|
||||||
"Element",
|
"Element",
|
||||||
"Image",
|
"Image",
|
||||||
"NotificationEvent",
|
"NotificationEvent",
|
||||||
"NodeList"
|
"NodeList",
|
||||||
|
"DOMParser"
|
||||||
],
|
],
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|
49
routes/_a11y/getAccessibleLabelForStatus.js
Normal file
49
routes/_a11y/getAccessibleLabelForStatus.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { getAccountAccessibleName } from './getAccountAccessibleName'
|
||||||
|
import { htmlToPlainText } from '../_utils/htmlToPlainText'
|
||||||
|
import { POST_PRIVACY_OPTIONS } from '../_static/statuses'
|
||||||
|
|
||||||
|
function notificationText (notification, omitEmojiInDisplayNames) {
|
||||||
|
if (!notification) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let notificationAccountDisplayName = getAccountAccessibleName(notification.account, omitEmojiInDisplayNames)
|
||||||
|
if (notification.type === 'reblog') {
|
||||||
|
return `${notificationAccountDisplayName} boosted your status`
|
||||||
|
} else if (notification.type === 'favourite') {
|
||||||
|
return `${notificationAccountDisplayName} favorited your status`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function privacyText (visibility) {
|
||||||
|
for (let option of POST_PRIVACY_OPTIONS) {
|
||||||
|
if (option.key === visibility) {
|
||||||
|
return option.label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reblogText (reblog, account, omitEmojiInDisplayNames) {
|
||||||
|
if (!reblog) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let accountDisplayName = getAccountAccessibleName(account, omitEmojiInDisplayNames)
|
||||||
|
return `Boosted by ${accountDisplayName}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAccessibleLabelForStatus (originalAccount, account, content,
|
||||||
|
timeagoFormattedDate, spoilerText, showContent,
|
||||||
|
reblog, notification, visibility, omitEmojiInDisplayNames) {
|
||||||
|
let originalAccountDisplayName = getAccountAccessibleName(originalAccount, omitEmojiInDisplayNames)
|
||||||
|
|
||||||
|
let values = [
|
||||||
|
notificationText(notification, omitEmojiInDisplayNames),
|
||||||
|
originalAccountDisplayName,
|
||||||
|
(showContent || !spoilerText) ? htmlToPlainText(content) : `Content warning: ${spoilerText}`,
|
||||||
|
timeagoFormattedDate,
|
||||||
|
`@${originalAccount.acct}`,
|
||||||
|
privacyText(visibility),
|
||||||
|
reblogText(reblog, account, omitEmojiInDisplayNames)
|
||||||
|
].filter(Boolean)
|
||||||
|
|
||||||
|
return values.join(', ')
|
||||||
|
}
|
10
routes/_a11y/getAccountAccessibleName.js
Normal file
10
routes/_a11y/getAccountAccessibleName.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { removeEmoji } from '../_utils/removeEmoji'
|
||||||
|
|
||||||
|
export function getAccountAccessibleName (account, omitEmojiInDisplayNames) {
|
||||||
|
let emojis = account.emojis
|
||||||
|
let displayName = account.display_name || account.username
|
||||||
|
if (omitEmojiInDisplayNames) {
|
||||||
|
displayName = removeEmoji(displayName, emojis) || displayName
|
||||||
|
}
|
||||||
|
return displayName
|
||||||
|
}
|
|
@ -6,7 +6,9 @@
|
||||||
<article class="notification-article"
|
<article class="notification-article"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
aria-posinset={index}
|
aria-posinset={index}
|
||||||
aria-setsize={length} >
|
aria-setsize={length}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
>
|
||||||
<StatusHeader {notification} {notificationId} {status} {statusId} {timelineType}
|
<StatusHeader {notification} {notificationId} {status} {statusId} {timelineType}
|
||||||
{account} {accountId} {uuid} isStatusInNotification="true" />
|
{account} {accountId} {uuid} isStatusInNotification="true" />
|
||||||
</article>
|
</article>
|
||||||
|
@ -30,6 +32,7 @@
|
||||||
import Status from './Status.html'
|
import Status from './Status.html'
|
||||||
import StatusHeader from './StatusHeader.html'
|
import StatusHeader from './StatusHeader.html'
|
||||||
import { store } from '../../_store/store'
|
import { store } from '../../_store/store'
|
||||||
|
import { getAccountAccessibleName } from '../../_a11y/getAccountAccessibleName'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -45,7 +48,10 @@
|
||||||
statusId: ({ status }) => status && status.id,
|
statusId: ({ status }) => status && status.id,
|
||||||
uuid: ({ $currentInstance, timelineType, timelineValue, notificationId, statusId }) => {
|
uuid: ({ $currentInstance, timelineType, timelineValue, notificationId, statusId }) => {
|
||||||
return `${$currentInstance}/${timelineType}/${timelineValue}/${notificationId}/${statusId || ''}`
|
return `${$currentInstance}/${timelineType}/${timelineValue}/${notificationId}/${statusId || ''}`
|
||||||
}
|
},
|
||||||
|
ariaLabel: ({ status, account, $omitEmojiInDisplayNames }) => (
|
||||||
|
!status && `${getAccountAccessibleName(account, $omitEmojiInDisplayNames)} followed you, @${account.acct}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -110,7 +110,9 @@
|
||||||
import { classname } from '../../_utils/classname'
|
import { classname } from '../../_utils/classname'
|
||||||
import { checkDomAncestors } from '../../_utils/checkDomAncestors'
|
import { checkDomAncestors } from '../../_utils/checkDomAncestors'
|
||||||
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
|
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
|
||||||
import { removeEmoji } from '../../_utils/removeEmoji'
|
import { getAccountAccessibleName } from '../../_a11y/getAccountAccessibleName'
|
||||||
|
import { getAccessibleLabelForStatus } from '../../_a11y/getAccessibleLabelForStatus'
|
||||||
|
import { formatTimeagoDate } from '../../_intl/formatTimeagoDate'
|
||||||
|
|
||||||
const INPUT_TAGS = new Set(['a', 'button', 'input', 'textarea'])
|
const INPUT_TAGS = new Set(['a', 'button', 'input', 'textarea'])
|
||||||
const isUserInputElement = node => INPUT_TAGS.has(node.localName)
|
const isUserInputElement = node => INPUT_TAGS.has(node.localName)
|
||||||
|
@ -211,16 +213,17 @@
|
||||||
),
|
),
|
||||||
originalAccountEmojis: ({ originalAccount }) => (originalAccount.emojis || []),
|
originalAccountEmojis: ({ originalAccount }) => (originalAccount.emojis || []),
|
||||||
originalAccountDisplayName: ({ originalAccount }) => (originalAccount.display_name || originalAccount.username),
|
originalAccountDisplayName: ({ originalAccount }) => (originalAccount.display_name || originalAccount.username),
|
||||||
originalAccountAccessibleName: ({ originalAccountDisplayName, originalAccountEmojis, $omitEmojiInDisplayNames }) => {
|
originalAccountAccessibleName: ({ originalAccount, $omitEmojiInDisplayNames }) => {
|
||||||
if ($omitEmojiInDisplayNames) {
|
return getAccountAccessibleName(originalAccount, $omitEmojiInDisplayNames)
|
||||||
return removeEmoji(originalAccountDisplayName, originalAccountEmojis) || originalAccountDisplayName
|
|
||||||
}
|
|
||||||
return originalAccountDisplayName
|
|
||||||
},
|
},
|
||||||
ariaLabel: ({ originalAccountAccessibleName, originalStatus, visibility, isStatusInOwnThread }) => (
|
createdAtDate: ({ originalStatus }) => originalStatus.created_at,
|
||||||
(visibility === 'direct' ? 'Direct message' : 'Status') +
|
timeagoFormattedDate: ({ createdAtDate }) => formatTimeagoDate(createdAtDate),
|
||||||
` by ${originalAccountAccessibleName}` +
|
reblog: ({ status }) => status.reblog,
|
||||||
(isStatusInOwnThread ? ' (focused)' : '')
|
ariaLabel: ({ originalAccount, account, content, timeagoFormattedDate, spoilerText,
|
||||||
|
showContent, reblog, notification, visibility, $omitEmojiInDisplayNames }) => (
|
||||||
|
getAccessibleLabelForStatus(originalAccount, account, content,
|
||||||
|
timeagoFormattedDate, spoilerText, showContent,
|
||||||
|
reblog, notification, visibility, $omitEmojiInDisplayNames)
|
||||||
),
|
),
|
||||||
showHeader: ({ notification, status, timelineType }) => (
|
showHeader: ({ notification, status, timelineType }) => (
|
||||||
(notification && (notification.type === 'reblog' || notification.type === 'favourite')) ||
|
(notification && (notification.type === 'reblog' || notification.type === 'favourite')) ||
|
||||||
|
@ -238,7 +241,8 @@
|
||||||
params: ({ notification, notificationId, status, statusId, timelineType,
|
params: ({ notification, notificationId, status, statusId, timelineType,
|
||||||
account, accountId, uuid, isStatusInNotification, isStatusInOwnThread,
|
account, accountId, uuid, isStatusInNotification, isStatusInOwnThread,
|
||||||
originalAccount, originalAccountId, spoilerShown, visibility, replyShown,
|
originalAccount, originalAccountId, spoilerShown, visibility, replyShown,
|
||||||
replyVisibility, spoilerText, originalStatus, originalStatusId, inReplyToId }) => ({
|
replyVisibility, spoilerText, originalStatus, originalStatusId, inReplyToId,
|
||||||
|
createdAtDate, timeagoFormattedDate }) => ({
|
||||||
notification,
|
notification,
|
||||||
notificationId,
|
notificationId,
|
||||||
status,
|
status,
|
||||||
|
@ -258,7 +262,9 @@
|
||||||
spoilerText,
|
spoilerText,
|
||||||
originalStatus,
|
originalStatus,
|
||||||
originalStatusId,
|
originalStatusId,
|
||||||
inReplyToId
|
inReplyToId,
|
||||||
|
createdAtDate,
|
||||||
|
timeagoFormattedDate
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
href="/statuses/{originalStatusId}"
|
href="/statuses/{originalStatusId}"
|
||||||
focus-key={focusKey}
|
focus-key={focusKey}
|
||||||
>
|
>
|
||||||
<time datetime={createdAtDate} title={relativeDate}
|
<time datetime={createdAtDate} title={timeagoFormattedDate}
|
||||||
aria-label="{relativeDate} – click to show thread">
|
aria-label="{timeagoFormattedDate} – click to show thread">
|
||||||
{relativeDate}
|
{timeagoFormattedDate}
|
||||||
</time>
|
</time>
|
||||||
</a>
|
</a>
|
||||||
<style>
|
<style>
|
||||||
|
@ -29,19 +29,8 @@
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import { mark, stop } from '../../_utils/marks'
|
|
||||||
import timeago from 'timeago.js'
|
|
||||||
const timeagoInstance = timeago()
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
createdAtDate: ({ originalStatus }) => originalStatus.created_at,
|
|
||||||
relativeDate: ({ createdAtDate }) => {
|
|
||||||
mark('compute relativeDate')
|
|
||||||
let res = timeagoInstance.format(createdAtDate)
|
|
||||||
stop('compute relativeDate')
|
|
||||||
return res
|
|
||||||
},
|
|
||||||
focusKey: ({ uuid }) => `status-relative-date-${uuid}`
|
focusKey: ({ uuid }) => `status-relative-date-${uuid}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
routes/_intl/formatTimeagoDate.js
Normal file
11
routes/_intl/formatTimeagoDate.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import timeago from 'timeago.js'
|
||||||
|
import { mark, stop } from '../_utils/marks'
|
||||||
|
|
||||||
|
const timeagoInstance = timeago()
|
||||||
|
|
||||||
|
export function formatTimeagoDate (date) {
|
||||||
|
mark('formatTimeagoDate')
|
||||||
|
let res = timeagoInstance.format(date)
|
||||||
|
stop('formatTimeagoDate')
|
||||||
|
return res
|
||||||
|
}
|
13
routes/_utils/htmlToPlainText.js
Normal file
13
routes/_utils/htmlToPlainText.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { mark, stop } from './marks'
|
||||||
|
|
||||||
|
let domParser = process.browser && new DOMParser()
|
||||||
|
|
||||||
|
export function htmlToPlainText (html) {
|
||||||
|
if (!html) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
mark('htmlToPlainText')
|
||||||
|
let res = domParser.parseFromString(html, 'text/html').documentElement.textContent
|
||||||
|
stop('htmlToPlainText')
|
||||||
|
return res
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { getNthStatus, getNthStatusSelector } from '../utils'
|
import { getNthStatusSelector } from '../utils'
|
||||||
import { loginAsFoobar } from '../roles'
|
import { loginAsFoobar } from '../roles'
|
||||||
import { Selector as $ } from 'testcafe'
|
import { Selector as $ } from 'testcafe'
|
||||||
|
|
||||||
|
@ -8,12 +8,10 @@ fixture`005-status-types.js`
|
||||||
test('shows followers-only vs regular in home timeline', async t => {
|
test('shows followers-only vs regular in home timeline', async t => {
|
||||||
await loginAsFoobar(t)
|
await loginAsFoobar(t)
|
||||||
await t
|
await t
|
||||||
.expect(getNthStatus(1).getAttribute('aria-label')).eql('Status by admin')
|
|
||||||
.expect($(`${getNthStatusSelector(1)} .status-content`).innerText).contains('notification of unlisted message')
|
.expect($(`${getNthStatusSelector(1)} .status-content`).innerText).contains('notification of unlisted message')
|
||||||
.expect($(`${getNthStatusSelector(1)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label'))
|
.expect($(`${getNthStatusSelector(1)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label'))
|
||||||
.eql('Boost')
|
.eql('Boost')
|
||||||
.expect($(`${getNthStatusSelector(1)} .status-toolbar button:nth-child(2)`).hasAttribute('disabled')).notOk()
|
.expect($(`${getNthStatusSelector(1)} .status-toolbar button:nth-child(2)`).hasAttribute('disabled')).notOk()
|
||||||
.expect(getNthStatus(2).getAttribute('aria-label')).eql('Status by admin')
|
|
||||||
.expect($(`${getNthStatusSelector(2)} .status-content`).innerText).contains('notification of followers-only message')
|
.expect($(`${getNthStatusSelector(2)} .status-content`).innerText).contains('notification of followers-only message')
|
||||||
.expect($(`${getNthStatusSelector(2)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label'))
|
.expect($(`${getNthStatusSelector(2)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label'))
|
||||||
.eql('Cannot be boosted because this is followers-only')
|
.eql('Cannot be boosted because this is followers-only')
|
||||||
|
@ -24,17 +22,14 @@ test('shows direct vs followers-only vs regular in notifications', async t => {
|
||||||
await loginAsFoobar(t)
|
await loginAsFoobar(t)
|
||||||
await t
|
await t
|
||||||
.navigateTo('/notifications')
|
.navigateTo('/notifications')
|
||||||
.expect(getNthStatus(2).getAttribute('aria-label')).eql('Status by admin')
|
|
||||||
.expect($(`${getNthStatusSelector(2)} .status-content`).innerText).contains('notification of unlisted message')
|
.expect($(`${getNthStatusSelector(2)} .status-content`).innerText).contains('notification of unlisted message')
|
||||||
.expect($(`${getNthStatusSelector(2)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label'))
|
.expect($(`${getNthStatusSelector(2)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label'))
|
||||||
.eql('Boost')
|
.eql('Boost')
|
||||||
.expect($(`${getNthStatusSelector(2)} .status-toolbar button:nth-child(2)`).hasAttribute('disabled')).notOk()
|
.expect($(`${getNthStatusSelector(2)} .status-toolbar button:nth-child(2)`).hasAttribute('disabled')).notOk()
|
||||||
.expect(getNthStatus(3).getAttribute('aria-label')).eql('Status by admin')
|
|
||||||
.expect($(`${getNthStatusSelector(3)} .status-content`).innerText).contains('notification of followers-only message')
|
.expect($(`${getNthStatusSelector(3)} .status-content`).innerText).contains('notification of followers-only message')
|
||||||
.expect($(`${getNthStatusSelector(3)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label'))
|
.expect($(`${getNthStatusSelector(3)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label'))
|
||||||
.eql('Cannot be boosted because this is followers-only')
|
.eql('Cannot be boosted because this is followers-only')
|
||||||
.expect($(`${getNthStatusSelector(3)} .status-toolbar button:nth-child(2)`).hasAttribute('disabled')).ok()
|
.expect($(`${getNthStatusSelector(3)} .status-toolbar button:nth-child(2)`).hasAttribute('disabled')).ok()
|
||||||
.expect(getNthStatus(4).getAttribute('aria-label')).eql('Direct message by admin')
|
|
||||||
.expect($(`${getNthStatusSelector(4)} .status-content`).innerText).contains('notification of direct message')
|
.expect($(`${getNthStatusSelector(4)} .status-content`).innerText).contains('notification of direct message')
|
||||||
.expect($(`${getNthStatusSelector(4)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label'))
|
.expect($(`${getNthStatusSelector(4)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label'))
|
||||||
.eql('Cannot be boosted because this is a direct message')
|
.eql('Cannot be boosted because this is a direct message')
|
||||||
|
|
69
tests/spec/022-status-aria-label.js
Normal file
69
tests/spec/022-status-aria-label.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { loginAsFoobar } from '../roles'
|
||||||
|
import { getNthShowOrHideButton, getNthStatus, notificationsNavButton, scrollToStatus } from '../utils'
|
||||||
|
import { indexWhere } from '../../routes/_utils/arrays'
|
||||||
|
import { homeTimeline } from '../fixtures'
|
||||||
|
|
||||||
|
fixture`022-status-aria-label.js`
|
||||||
|
.page`http://localhost:4002`
|
||||||
|
|
||||||
|
test('basic aria-labels for statuses', async t => {
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await t
|
||||||
|
.hover(getNthStatus(0))
|
||||||
|
.expect(getNthStatus(0).getAttribute('aria-label')).match(
|
||||||
|
/quux, pinned toot 1, .+ ago, @quux, Unlisted, Boosted by admin/i
|
||||||
|
)
|
||||||
|
.hover(getNthStatus(0))
|
||||||
|
.expect(getNthStatus(1).getAttribute('aria-label')).match(
|
||||||
|
/admin, @foobar notification of unlisted message, .* ago, @admin, Unlisted/i
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('aria-labels for CWed statuses', async t => {
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
let kittenIdx = indexWhere(homeTimeline, _ => _.spoiler === 'kitten CW')
|
||||||
|
await scrollToStatus(t, kittenIdx)
|
||||||
|
await t
|
||||||
|
.hover(getNthStatus(kittenIdx))
|
||||||
|
.expect(getNthStatus(kittenIdx).getAttribute('aria-label')).match(
|
||||||
|
/foobar, Content warning: kitten CW, .* ago, @foobar, Public/i
|
||||||
|
)
|
||||||
|
.click(getNthShowOrHideButton(kittenIdx))
|
||||||
|
.expect(getNthStatus(kittenIdx).getAttribute('aria-label')).match(
|
||||||
|
/foobar, here's a kitten with a CW, .* ago, @foobar, Public/i
|
||||||
|
)
|
||||||
|
.click(getNthShowOrHideButton(kittenIdx))
|
||||||
|
.expect(getNthStatus(kittenIdx).getAttribute('aria-label')).match(
|
||||||
|
/foobar, Content warning: kitten CW, .* ago, @foobar, Public/i
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('aria-labels for notifications', async t => {
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await t
|
||||||
|
.click(notificationsNavButton)
|
||||||
|
.hover(getNthStatus(0))
|
||||||
|
.expect(getNthStatus(0).getAttribute('aria-label')).match(
|
||||||
|
/admin favorited your status, foobar, this is unlisted, .* ago, @foobar, Unlisted/i
|
||||||
|
)
|
||||||
|
.hover(getNthStatus(1))
|
||||||
|
.expect(getNthStatus(1).getAttribute('aria-label')).match(
|
||||||
|
/admin boosted your status, foobar, this is unlisted, .* ago, @foobar, Unlisted/i
|
||||||
|
)
|
||||||
|
.hover(getNthStatus(2))
|
||||||
|
.expect(getNthStatus(2).getAttribute('aria-label')).match(
|
||||||
|
/admin, @foobar notification of unlisted message, .* ago, @admin, Unlisted/i
|
||||||
|
)
|
||||||
|
await scrollToStatus(t, 4)
|
||||||
|
await t
|
||||||
|
.hover(getNthStatus(4))
|
||||||
|
.expect(getNthStatus(4).getAttribute('aria-label')).match(
|
||||||
|
/admin, @foobar notification of direct message, .* ago, @admin, Direct/i
|
||||||
|
)
|
||||||
|
await scrollToStatus(t, 5)
|
||||||
|
await t
|
||||||
|
.hover(getNthStatus(5))
|
||||||
|
.expect(getNthStatus(5).getAttribute('aria-label')).match(
|
||||||
|
/quux followed you, @quux/i
|
||||||
|
)
|
||||||
|
})
|
|
@ -1,12 +1,17 @@
|
||||||
import { loginAsFoobar } from '../roles'
|
import { loginAsFoobar } from '../roles'
|
||||||
import {
|
import {
|
||||||
avatarInComposeBox,
|
avatarInComposeBox,
|
||||||
displayNameInComposeBox, generalSettingsButton, getNthStatus, getNthStatusSelector, getUrl, homeNavButton,
|
displayNameInComposeBox,
|
||||||
|
generalSettingsButton,
|
||||||
|
getNthStatus,
|
||||||
|
getNthStatusSelector,
|
||||||
|
getUrl,
|
||||||
|
homeNavButton,
|
||||||
removeEmojiFromDisplayNamesInput,
|
removeEmojiFromDisplayNamesInput,
|
||||||
settingsNavButton,
|
settingsNavButton,
|
||||||
sleep
|
sleep
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { updateUserDisplayNameAs } from '../serverActions'
|
import { postAs, updateUserDisplayNameAs } from '../serverActions'
|
||||||
import { Selector as $ } from 'testcafe'
|
import { Selector as $ } from 'testcafe'
|
||||||
|
|
||||||
fixture`118-display-name-custom-emoji.js`
|
fixture`118-display-name-custom-emoji.js`
|
||||||
|
@ -85,26 +90,34 @@ test('Cannot remove emoji from user display names if result would be empty', asy
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Check status aria labels for de-emojified text', async t => {
|
test('Check status aria labels for de-emojified text', async t => {
|
||||||
await updateUserDisplayNameAs('foobar', '🌈 foo :blobpats: 🌈')
|
let rainbow = String.fromCodePoint(0x1F308)
|
||||||
|
await updateUserDisplayNameAs('foobar', `${rainbow} foo :blobpats: ${rainbow}`)
|
||||||
|
await postAs('foobar', 'hey ho lotsa emojos')
|
||||||
await sleep(1000)
|
await sleep(1000)
|
||||||
await loginAsFoobar(t)
|
await loginAsFoobar(t)
|
||||||
await t
|
await t
|
||||||
.click(displayNameInComposeBox)
|
.click(displayNameInComposeBox)
|
||||||
.expect(getNthStatus(0).getAttribute('aria-label')).eql('Status by 🌈 foo :blobpats: 🌈')
|
.expect(getNthStatus(0).getAttribute('aria-label')).match(
|
||||||
|
new RegExp(`${rainbow} foo :blobpats: ${rainbow}, hey ho lotsa emojos, (.* ago|just now), @foobar, Public`, 'i')
|
||||||
|
)
|
||||||
.click(settingsNavButton)
|
.click(settingsNavButton)
|
||||||
.click(generalSettingsButton)
|
.click(generalSettingsButton)
|
||||||
.click(removeEmojiFromDisplayNamesInput)
|
.click(removeEmojiFromDisplayNamesInput)
|
||||||
.expect(removeEmojiFromDisplayNamesInput.checked).ok()
|
.expect(removeEmojiFromDisplayNamesInput.checked).ok()
|
||||||
.click(homeNavButton)
|
.click(homeNavButton)
|
||||||
.click(displayNameInComposeBox)
|
.click(displayNameInComposeBox)
|
||||||
.expect(getNthStatus(0).getAttribute('aria-label')).eql('Status by foo')
|
.expect(getNthStatus(0).getAttribute('aria-label')).match(
|
||||||
|
new RegExp(`foo, hey ho lotsa emojos, (.* ago|just now), @foobar, Public`, 'i')
|
||||||
|
)
|
||||||
.click(settingsNavButton)
|
.click(settingsNavButton)
|
||||||
.click(generalSettingsButton)
|
.click(generalSettingsButton)
|
||||||
.click(removeEmojiFromDisplayNamesInput)
|
.click(removeEmojiFromDisplayNamesInput)
|
||||||
.expect(removeEmojiFromDisplayNamesInput.checked).notOk()
|
.expect(removeEmojiFromDisplayNamesInput.checked).notOk()
|
||||||
.click(homeNavButton)
|
.click(homeNavButton)
|
||||||
.click(displayNameInComposeBox)
|
.click(displayNameInComposeBox)
|
||||||
.expect(getNthStatus(0).getAttribute('aria-label')).eql('Status by 🌈 foo :blobpats: 🌈')
|
.expect(getNthStatus(0).getAttribute('aria-label')).match(
|
||||||
|
new RegExp(`${rainbow} foo :blobpats: ${rainbow}, hey ho lotsa emojos, (.* ago|just now), @foobar, Public`, 'i')
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Check some odd emoji', async t => {
|
test('Check some odd emoji', async t => {
|
||||||
|
|
Loading…
Reference in a new issue