feat: Allow image descriptions to be read automatically by screen readers without needing to expand media. (#2269)

Fixes #2257.

Co-authored-by: Nolan Lawson <nolan@nolanlawson.com>
This commit is contained in:
James Teh 2022-12-03 06:54:03 +10:00 committed by GitHub
parent a775bd9193
commit 8fc9d5c728
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 37 additions and 5 deletions

View File

@ -37,12 +37,15 @@ function cleanupText (text) {
export function getAccessibleLabelForStatus (originalAccount, account, plainTextContent, export function getAccessibleLabelForStatus (originalAccount, account, plainTextContent,
shortInlineFormattedDate, spoilerText, showContent, shortInlineFormattedDate, spoilerText, showContent,
reblog, notification, visibility, omitEmojiInDisplayNames, reblog, notification, visibility, omitEmojiInDisplayNames,
disableLongAriaLabels, showMedia, showPoll) { disableLongAriaLabels, showMedia, sensitive, sensitiveShown, mediaAttachments, showPoll) {
const originalAccountDisplayName = getAccountAccessibleName(originalAccount, omitEmojiInDisplayNames) const originalAccountDisplayName = getAccountAccessibleName(originalAccount, omitEmojiInDisplayNames)
const contentTextToShow = (showContent || !spoilerText) const contentTextToShow = (showContent || !spoilerText)
? cleanupText(plainTextContent) ? cleanupText(plainTextContent)
: formatIntl('intl.contentWarningContent', { spoiler: cleanupText(spoilerText) }) : formatIntl('intl.contentWarningContent', { spoiler: cleanupText(spoilerText) })
const mediaTextToShow = showMedia && 'intl.hasMedia' const mediaTextToShow = showMedia && 'intl.hasMedia'
const mediaDescText = (showMedia && (!sensitive || sensitiveShown))
? mediaAttachments.map(media => media.description)
: []
const pollTextToShow = showPoll && 'intl.hasPoll' const pollTextToShow = showPoll && 'intl.hasPoll'
const privacyText = getPrivacyText(visibility) const privacyText = getPrivacyText(visibility)
@ -57,6 +60,7 @@ export function getAccessibleLabelForStatus (originalAccount, account, plainText
originalAccountDisplayName, originalAccountDisplayName,
contentTextToShow, contentTextToShow,
mediaTextToShow, mediaTextToShow,
...mediaDescText,
pollTextToShow, pollTextToShow,
shortInlineFormattedDate, shortInlineFormattedDate,
`@${originalAccount.acct}`, `@${originalAccount.acct}`,

View File

@ -270,6 +270,13 @@
originalStatus.media_attachments && originalStatus.media_attachments &&
originalStatus.media_attachments.length originalStatus.media_attachments.length
), ),
mediaAttachments: ({ originalStatus }) => (
originalStatus.media_attachments
),
sensitiveShown: ({ $sensitivesShown, uuid }) => !!$sensitivesShown[uuid],
sensitive: ({ originalStatus, $markMediaAsSensitive, $neverMarkMediaAsSensitive }) => (
!$neverMarkMediaAsSensitive && ($markMediaAsSensitive || originalStatus.sensitive)
),
originalAccountEmojis: ({ originalAccount }) => (originalAccount.emojis || []), originalAccountEmojis: ({ originalAccount }) => (originalAccount.emojis || []),
originalStatusEmojis: ({ originalStatus }) => (originalStatus.emojis || []), originalStatusEmojis: ({ originalStatus }) => (originalStatus.emojis || []),
originalAccountDisplayName: ({ originalAccount }) => (originalAccount.display_name || originalAccount.username), originalAccountDisplayName: ({ originalAccount }) => (originalAccount.display_name || originalAccount.username),
@ -288,12 +295,12 @@
ariaLabel: ({ ariaLabel: ({
originalAccount, account, plainTextContent, shortInlineFormattedDate, spoilerText, originalAccount, account, plainTextContent, shortInlineFormattedDate, spoilerText,
showContent, reblog, notification, visibility, $omitEmojiInDisplayNames, $disableLongAriaLabels, showContent, reblog, notification, visibility, $omitEmojiInDisplayNames, $disableLongAriaLabels,
showMedia, showPoll showMedia, sensitive, sensitiveShown, mediaAttachments, showPoll
}) => ( }) => (
getAccessibleLabelForStatus(originalAccount, account, plainTextContent, getAccessibleLabelForStatus(originalAccount, account, plainTextContent,
shortInlineFormattedDate, spoilerText, showContent, shortInlineFormattedDate, spoilerText, showContent,
reblog, notification, visibility, $omitEmojiInDisplayNames, $disableLongAriaLabels, reblog, notification, visibility, $omitEmojiInDisplayNames, $disableLongAriaLabels,
showMedia, showPoll showMedia, sensitive, sensitiveShown, mediaAttachments, showPoll
) )
), ),
showHeader: ({ notification, status, timelineType }) => ( showHeader: ({ notification, status, timelineType }) => (

View File

@ -2,7 +2,7 @@ import { loginAsFoobar } from '../roles'
import { import {
generalSettingsButton, generalSettingsButton,
getNthShowOrHideButton, getNthShowOrHideButton,
getNthStatus, getNthStatusRelativeDateTime, homeNavButton, getNthStatus, getNthStatusAndSensitiveButton, getNthStatusRelativeDateTime, homeNavButton,
notificationsNavButton, notificationsNavButton,
scrollToStatus, scrollToStatus,
settingsNavButton settingsNavButton
@ -39,6 +39,7 @@ test('aria-labels for CWed statuses', async t => {
.expect(getNthStatus(1 + kittenIdx).getAttribute('aria-label')).match( .expect(getNthStatus(1 + kittenIdx).getAttribute('aria-label')).match(
/foobar, Content warning: kitten CW, .* ago, @foobar, Public/i /foobar, Content warning: kitten CW, .* ago, @foobar, Public/i
) )
// toggle the CW button
.click(getNthShowOrHideButton(1 + kittenIdx)) .click(getNthShowOrHideButton(1 + kittenIdx))
.expect(getNthStatus(1 + kittenIdx).getAttribute('aria-label')).match( .expect(getNthStatus(1 + kittenIdx).getAttribute('aria-label')).match(
/foobar, here's a kitten with a CW, .* ago, @foobar, Public/i /foobar, here's a kitten with a CW, .* ago, @foobar, Public/i
@ -47,6 +48,26 @@ test('aria-labels for CWed statuses', async t => {
.expect(getNthStatus(1 + kittenIdx).getAttribute('aria-label')).match( .expect(getNthStatus(1 + kittenIdx).getAttribute('aria-label')).match(
/foobar, Content warning: kitten CW, .* ago, @foobar, Public/i /foobar, Content warning: kitten CW, .* ago, @foobar, Public/i
) )
// toggle the "show sensitive media" button
.click(getNthStatusAndSensitiveButton(1 + kittenIdx, 1))
.expect(getNthStatus(1 + kittenIdx).getAttribute('aria-label')).match(
/foobar, Content warning: kitten CW, has media, kitten, .* ago, @foobar, Public/i
)
.click(getNthStatusAndSensitiveButton(1 + kittenIdx, 1))
.expect(getNthStatus(1 + kittenIdx).getAttribute('aria-label')).match(
/foobar, Content warning: kitten CW, .* ago, @foobar, Public/i
)
})
test('aria-labels for two media attachments', async t => {
await loginAsFoobar(t)
const twoKittensIdx = homeTimeline.findIndex(_ => _.content === 'here\'s 2 kitten photos')
await scrollToStatus(t, 1 + twoKittensIdx)
await t
.hover(getNthStatus(1 + twoKittensIdx))
.expect(getNthStatus(1 + twoKittensIdx).getAttribute('aria-label')).match(
/foobar, here's 2 kitten photos, has media, kitten, kitten, .* ago, @foobar, Public/i
)
}) })
test('aria-labels for notifications', async t => { test('aria-labels for notifications', async t => {

View File

@ -11,7 +11,7 @@ test('aria-labels for statuses with no content text', async t => {
await t await t
.hover(getNthStatus(1)) .hover(getNthStatus(1))
.expect(getNthStatus(1).getAttribute('aria-label')).match( .expect(getNthStatus(1).getAttribute('aria-label')).match(
/foobar, has media, (.+ ago|just now), @foobar, Public/i /foobar, has media, kitteh, (.+ ago|just now), @foobar, Public/i
) )
}) })