feat: aria-labels and buttons contain more media info (#1743)

* feat: aria-labels and buttons contain more media info

fixes #1733

* fix lint
This commit is contained in:
Nolan Lawson 2020-04-25 19:03:39 -07:00 committed by GitHub
parent bfb1da6bd0
commit 1f0d67fcc4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 53 additions and 17 deletions

View file

@ -14,7 +14,7 @@ To run a dev server with hot reloading:
Now it's running at `localhost:4002`. Now it's running at `localhost:4002`.
**Linux users:** for file changes to work, **Linux users:** for file changes to work,
you'll probably want to run `export CHOKIDAR_USEPOLLING=1` you'll probably want to run `export CHOKIDAR_USEPOLLING=1`
because of [this issue](https://github.com/paulmillr/chokidar/issues/237). because of [this issue](https://github.com/paulmillr/chokidar/issues/237).
@ -39,7 +39,7 @@ running on `localhost:3000`.
The integration tests require running Mastodon itself, The integration tests require running Mastodon itself,
meaning the [Mastodon development guide](https://docs.joinmastodon.org/development/overview/) meaning the [Mastodon development guide](https://docs.joinmastodon.org/development/overview/)
is relevant here. In particular, you'll need a recent is relevant here. In particular, you'll need a recent
version of Ruby, Redis, and Postgres running. For a full list of deps, see `bin/setup-mastodon-in-travis.sh`. version of Ruby, Redis, and Postgres running. For a full list of deps, see `bin/setup-mastodon-in-travis.sh`.
Run integration tests, using headless Chrome by default: Run integration tests, using headless Chrome by default:
@ -96,7 +96,7 @@ There are two parts to the Mastodon data used for testing:
1. A Postgres dump and a tgz containing the media files, located in `fixtures` 1. A Postgres dump and a tgz containing the media files, located in `fixtures`
2. A script that populates the Mastodon backend with test data (`restore-mastodon-data.js`). 2. A script that populates the Mastodon backend with test data (`restore-mastodon-data.js`).
The reason we don't use a Postgres dump for everything The reason we don't use a Postgres dump for everything
is that Mastodon will ignore changes made after a certain period of time, and we is that Mastodon will ignore changes made after a certain period of time, and we
don't want our tests to randomly start breaking one day. Running the script ensures that statuses, don't want our tests to randomly start breaking one day. Running the script ensures that statuses,
favorites, boosts, etc. are all "fresh". favorites, boosts, etc. are all "fresh".

View file

@ -36,11 +36,13 @@ function cleanupText (text) {
export function getAccessibleLabelForStatus (originalAccount, account, plainTextContent, export function getAccessibleLabelForStatus (originalAccount, account, plainTextContent,
timeagoFormattedDate, spoilerText, showContent, timeagoFormattedDate, spoilerText, showContent,
reblog, notification, visibility, omitEmojiInDisplayNames, reblog, notification, visibility, omitEmojiInDisplayNames,
disableLongAriaLabels) { disableLongAriaLabels, showMedia, showPoll) {
const originalAccountDisplayName = getAccountAccessibleName(originalAccount, omitEmojiInDisplayNames) const originalAccountDisplayName = getAccountAccessibleName(originalAccount, omitEmojiInDisplayNames)
const contentTextToShow = (showContent || !spoilerText) const contentTextToShow = (showContent || !spoilerText)
? cleanupText(plainTextContent) ? cleanupText(plainTextContent)
: `Content warning: ${cleanupText(spoilerText)}` : `Content warning: ${cleanupText(spoilerText)}`
const mediaTextToShow = showMedia && 'has media'
const pollTextToShow = showPoll && 'has poll'
const privacyText = getPrivacyText(visibility) const privacyText = getPrivacyText(visibility)
if (disableLongAriaLabels) { if (disableLongAriaLabels) {
@ -53,6 +55,8 @@ export function getAccessibleLabelForStatus (originalAccount, account, plainText
getNotificationText(notification, omitEmojiInDisplayNames), getNotificationText(notification, omitEmojiInDisplayNames),
originalAccountDisplayName, originalAccountDisplayName,
contentTextToShow, contentTextToShow,
mediaTextToShow,
pollTextToShow,
timeagoFormattedDate, timeagoFormattedDate,
`@${originalAccount.acct}`, `@${originalAccount.acct}`,
privacyText, privacyText,

View file

@ -2,7 +2,7 @@
<button id={elementId} <button id={elementId}
type="button" type="button"
class="inline-media play-video-button focus-after {$largeInlineMedia ? '' : 'fixed-size'} {type === 'audio' ? 'play-audio-button' : ''}" class="inline-media play-video-button focus-after {$largeInlineMedia ? '' : 'fixed-size'} {type === 'audio' ? 'play-audio-button' : ''}"
aria-label="Play {type === 'video' ? 'video' : 'audio'}: {description}" aria-label={videoOrAudioButtonLabel}
{tabindex} {tabindex}
aria-hidden={ariaHidden} aria-hidden={ariaHidden}
style={inlineMediaStyle}> style={inlineMediaStyle}>
@ -25,7 +25,7 @@
<button id={elementId} <button id={elementId}
type="button" type="button"
class="inline-media show-image-button focus-after {$largeInlineMedia ? '' : 'fixed-size'}" class="inline-media show-image-button focus-after {$largeInlineMedia ? '' : 'fixed-size'}"
aria-label="Show image: {description}" aria-label={imageButtonAriaLabel}
title={description} title={description}
on:mouseover="set({mouseover: event})" on:mouseover="set({mouseover: event})"
style={inlineMediaStyle} style={inlineMediaStyle}
@ -34,7 +34,7 @@
> >
{#if type === 'gifv' && $autoplayGifs && !blurhash} {#if type === 'gifv' && $autoplayGifs && !blurhash}
<AutoplayVideo <AutoplayVideo
ariaLabel="Animated GIF: {description}" ariaLabel="Animated image: {description}"
poster={previewUrl} poster={previewUrl}
src={url} src={url}
width={inlineWidth} width={inlineWidth}
@ -44,7 +44,7 @@
{:elseif type === 'gifv'} {:elseif type === 'gifv'}
<NonAutoplayGifv <NonAutoplayGifv
class={noNativeWidthHeight ? 'no-native-width-height' : ''} class={noNativeWidthHeight ? 'no-native-width-height' : ''}
label="Animated GIF: {description}" label="Animated image: {description}"
poster={previewUrl} poster={previewUrl}
{blurhash} {blurhash}
src={url} src={url}
@ -166,7 +166,13 @@ export default {
} }
}, },
tabindex: ({ showAsSensitive }) => showAsSensitive ? '-1' : '0', tabindex: ({ showAsSensitive }) => showAsSensitive ? '-1' : '0',
ariaHidden: ({ showAsSensitive }) => showAsSensitive ariaHidden: ({ showAsSensitive }) => showAsSensitive,
imageButtonAriaLabel: ({ type, description }) => (
`Show ${type === 'gifv' ? 'animated image' : 'image'}: ${description}`
),
videoOrAudioButtonLabel: ({ type, description }) => (
`Play ${type === 'video' ? 'video' : 'audio'}: ${description}`
)
}, },
methods: { methods: {
onClick () { onClick () {

View file

@ -280,11 +280,14 @@
reblog: ({ status }) => status.reblog, reblog: ({ status }) => status.reblog,
ariaLabel: ({ ariaLabel: ({
originalAccount, account, plainTextContent, timeagoFormattedDate, spoilerText, originalAccount, account, plainTextContent, timeagoFormattedDate, spoilerText,
showContent, reblog, notification, visibility, $omitEmojiInDisplayNames, $disableLongAriaLabels showContent, reblog, notification, visibility, $omitEmojiInDisplayNames, $disableLongAriaLabels,
showMedia, showPoll
}) => ( }) => (
getAccessibleLabelForStatus(originalAccount, account, plainTextContent, getAccessibleLabelForStatus(originalAccount, account, plainTextContent,
timeagoFormattedDate, spoilerText, showContent, timeagoFormattedDate, spoilerText, showContent,
reblog, notification, visibility, $omitEmojiInDisplayNames, $disableLongAriaLabels) reblog, notification, visibility, $omitEmojiInDisplayNames, $disableLongAriaLabels,
showMedia, showPoll
)
), ),
showHeader: ({ notification, status, timelineType }) => ( showHeader: ({ notification, status, timelineType }) => (
(notification && ['reblog', 'favourite', 'poll'].includes(notification.type)) || (notification && ['reblog', 'favourite', 'poll'].includes(notification.type)) ||

View file

@ -1,4 +1,11 @@
import { closeDialogButton, getNthStatus, getNthStatusSelector, modalDialogContents, scrollToStatus } from '../utils' import {
closeDialogButton,
getNthStatus,
getNthStatusMediaButton,
getNthStatusSelector,
modalDialogContents,
scrollToStatus
} from '../utils'
import { loginAsFoobar } from '../roles' import { loginAsFoobar } from '../roles'
import { Selector as $ } from 'testcafe' import { Selector as $ } from 'testcafe'
import { homeTimeline } from '../fixtures' import { homeTimeline } from '../fixtures'
@ -13,8 +20,10 @@ test('shows sensitive images and videos', async t => {
const videoIdx = homeTimeline.findIndex(_ => _.content === 'secret video') const videoIdx = homeTimeline.findIndex(_ => _.content === 'secret video')
await scrollToStatus(t, 1 + kittenIdx) await scrollToStatus(t, 1 + kittenIdx)
await t.expect($(`${getNthStatusSelector(1 + kittenIdx)} .status-media img`).getAttribute('src')).match(/^blob:http:\/\/localhost/) await t.expect($(`${getNthStatusSelector(1 + kittenIdx)} .status-media img`).getAttribute('src'))
.match(/^blob:http:\/\/localhost/)
.click($(`${getNthStatusSelector(1 + kittenIdx)} .status-sensitive-media-button`)) .click($(`${getNthStatusSelector(1 + kittenIdx)} .status-sensitive-media-button`))
.expect($(getNthStatusMediaButton(1 + kittenIdx)).getAttribute('aria-label')).eql('Show image: kitten')
.expect($(`${getNthStatusSelector(1 + kittenIdx)} .status-media img`).getAttribute('alt')).eql('kitten') .expect($(`${getNthStatusSelector(1 + kittenIdx)} .status-media img`).getAttribute('alt')).eql('kitten')
.expect($(`${getNthStatusSelector(1 + kittenIdx)} .status-media img`).getAttribute('src')).match(/^http:\/\//) .expect($(`${getNthStatusSelector(1 + kittenIdx)} .status-media img`).getAttribute('src')).match(/^http:\/\//)
.hover(getNthStatus(1 + videoIdx)) .hover(getNthStatus(1 + videoIdx))
@ -31,7 +40,9 @@ test('click and close image and video modals', async t => {
await scrollToStatus(t, 1 + videoIdx) await scrollToStatus(t, 1 + videoIdx)
await t.expect(modalDialogContents.exists).notOk() await t.expect(modalDialogContents.exists).notOk()
.click($(`${getNthStatusSelector(1 + videoIdx)} .play-video-button`)) .expect($(getNthStatusMediaButton(1 + videoIdx)).getAttribute('aria-label'))
.eql('Play video: kitten')
.click($(getNthStatusMediaButton(1 + videoIdx)))
.expect(modalDialogContents.exists).ok() .expect(modalDialogContents.exists).ok()
.expect($('.modal-dialog video').getAttribute('src')).contains('mp4') .expect($('.modal-dialog video').getAttribute('src')).contains('mp4')
.expect($('.modal-dialog video').getAttribute('poster')).contains('png') .expect($('.modal-dialog video').getAttribute('poster')).contains('png')
@ -39,7 +50,9 @@ test('click and close image and video modals', async t => {
.expect(modalDialogContents.exists).notOk() .expect(modalDialogContents.exists).notOk()
.hover(getNthStatus(1 + kittenIdx - 1)) .hover(getNthStatus(1 + kittenIdx - 1))
.hover(getNthStatus(1 + kittenIdx)) .hover(getNthStatus(1 + kittenIdx))
.click($(`${getNthStatusSelector(1 + kittenIdx)} .show-image-button`)) .expect($(getNthStatusMediaButton(1 + kittenIdx)).getAttribute('aria-label'))
.eql('Show animated image: kitten')
.click($(getNthStatusMediaButton(1 + kittenIdx)))
.expect(modalDialogContents.exists).ok() .expect(modalDialogContents.exists).ok()
.expect($('.modal-dialog video').getAttribute('src')).contains('mp4') .expect($('.modal-dialog video').getAttribute('src')).contains('mp4')
.expect($('.modal-dialog video').getAttribute('poster')).contains('png') .expect($('.modal-dialog video').getAttribute('poster')).contains('png')

View file

@ -1,6 +1,6 @@
import { loginAsFoobar } from '../roles' import { loginAsFoobar } from '../roles'
import { getNthStatus } from '../utils' import { getNthStatus } from '../utils'
import { postAs, postEmptyStatusWithMediaAs } from '../serverActions' import { createPollAs, postAs, postEmptyStatusWithMediaAs } from '../serverActions'
fixture`120-status-aria-label.js` fixture`120-status-aria-label.js`
.page`http://localhost:4002` .page`http://localhost:4002`
@ -11,7 +11,17 @@ 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, (.+ ago|just now), @foobar, Public/i /foobar, has media, (.+ ago|just now), @foobar, Public/i
)
})
test('aria-labels for statuses with polls', async t => {
await createPollAs('foobar', 'here is my poll', ['yolo', 'whatever'])
await loginAsFoobar(t)
await t
.hover(getNthStatus(1))
.expect(getNthStatus(1).getAttribute('aria-label')).match(
/foobar, here is my poll, has poll, (.+ ago|just now), @foobar, Public/i
) )
}) })