fix(a11y): fix NVDA crash on long aria-label (#702)
* fix(a11y): fix NVDA crash on long aria-label fixes #694 * use the word truncated instead of ellipsis * fix test * really fix tests
This commit is contained in:
parent
12892d0032
commit
0515133ece
|
@ -6,41 +6,13 @@ import { favoriteStatus } from '../routes/_api/favorite'
|
|||
import { reblogStatus } from '../routes/_api/reblog'
|
||||
import fetch from 'node-fetch'
|
||||
import FileApi from 'file-api'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import FormData from 'form-data'
|
||||
import { auth } from '../routes/_api/utils'
|
||||
import { pinStatus } from '../routes/_api/pin'
|
||||
import { submitMedia } from '../tests/submitMedia'
|
||||
|
||||
global.File = FileApi.File
|
||||
global.FormData = FileApi.FormData
|
||||
global.fetch = fetch
|
||||
|
||||
async function submitMedia (accessToken, filename, alt) {
|
||||
let form = new FormData()
|
||||
form.append('file', fs.createReadStream(path.join(__dirname, '../tests/images/' + filename)))
|
||||
form.append('description', alt)
|
||||
return new Promise((resolve, reject) => {
|
||||
form.submit({
|
||||
host: 'localhost',
|
||||
port: 3000,
|
||||
path: '/api/v1/media',
|
||||
headers: auth(accessToken)
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
let data = ''
|
||||
|
||||
res.on('data', chunk => {
|
||||
data += chunk
|
||||
})
|
||||
|
||||
res.on('end', () => resolve(JSON.parse(data)))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function restoreMastodonData () {
|
||||
console.log('Restoring mastodon data...')
|
||||
let internalIdsToIds = {}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { getAccountAccessibleName } from './getAccountAccessibleName'
|
||||
import { htmlToPlainText } from '../_utils/htmlToPlainText'
|
||||
import { POST_PRIVACY_OPTIONS } from '../_static/statuses'
|
||||
import { htmlToPlainText } from '../_utils/htmlToPlainText'
|
||||
|
||||
const MAX_TEXT_LENGTH = 150
|
||||
|
||||
function notificationText (notification, omitEmojiInDisplayNames) {
|
||||
if (!notification) {
|
||||
|
@ -30,15 +32,28 @@ function reblogText (reblog, account, omitEmojiInDisplayNames) {
|
|||
return `Boosted by ${accountDisplayName}`
|
||||
}
|
||||
|
||||
// Works around a bug in NVDA where it may crash if the string is too long
|
||||
// https://github.com/nolanlawson/pinafore/issues/694
|
||||
function truncateTextForSRs (text) {
|
||||
if (text.length > MAX_TEXT_LENGTH) {
|
||||
text = text.substring(0, MAX_TEXT_LENGTH)
|
||||
text = text.replace(/\S+$/, '') + ' (truncated)'
|
||||
}
|
||||
return text.replace(/\s+/g, ' ').trim()
|
||||
}
|
||||
|
||||
export function getAccessibleLabelForStatus (originalAccount, account, content,
|
||||
timeagoFormattedDate, spoilerText, showContent,
|
||||
reblog, notification, visibility, omitEmojiInDisplayNames) {
|
||||
let originalAccountDisplayName = getAccountAccessibleName(originalAccount, omitEmojiInDisplayNames)
|
||||
let contentTextToShow = (showContent || !spoilerText)
|
||||
? truncateTextForSRs(htmlToPlainText(content))
|
||||
: `Content warning: ${truncateTextForSRs(spoilerText)}`
|
||||
|
||||
let values = [
|
||||
notificationText(notification, omitEmojiInDisplayNames),
|
||||
originalAccountDisplayName,
|
||||
(showContent || !spoilerText) ? htmlToPlainText(content) : `Content warning: ${spoilerText}`,
|
||||
contentTextToShow,
|
||||
timeagoFormattedDate,
|
||||
`@${originalAccount.acct}`,
|
||||
privacyText(visibility),
|
||||
|
|
|
@ -77,9 +77,6 @@
|
|||
},
|
||||
methods: {
|
||||
hydrateContent () {
|
||||
if (!this.refs.node) {
|
||||
return
|
||||
}
|
||||
mark('hydrateContent')
|
||||
let node = this.refs.node
|
||||
let { originalStatus, uuid } = this.get()
|
||||
|
|
|
@ -8,6 +8,7 @@ import { authorizeFollowRequest, getFollowRequests } from '../routes/_actions/fo
|
|||
import { followAccount, unfollowAccount } from '../routes/_api/follow'
|
||||
import { updateCredentials } from '../routes/_api/updateCredentials'
|
||||
import { reblogStatus } from '../routes/_api/reblog'
|
||||
import { submitMedia } from './submitMedia'
|
||||
|
||||
global.fetch = fetch
|
||||
global.File = FileApi.File
|
||||
|
@ -28,6 +29,12 @@ export async function postAs (username, text) {
|
|||
null, null, false, null, 'public')
|
||||
}
|
||||
|
||||
export async function postEmptyStatusWithMediaAs (username, filename, alt) {
|
||||
let mediaResponse = await submitMedia(users[username].accessToken, filename, alt)
|
||||
return postStatus(instanceName, users[username].accessToken, '',
|
||||
null, [mediaResponse.id], false, null, 'public')
|
||||
}
|
||||
|
||||
export async function postReplyAs (username, text, inReplyTo) {
|
||||
return postStatus(instanceName, users[username].accessToken, text,
|
||||
inReplyTo, null, false, null, 'public')
|
||||
|
|
|
@ -138,4 +138,7 @@ test('Check some odd emoji', async t => {
|
|||
.expect(removeEmojiFromDisplayNamesInput.checked).notOk()
|
||||
.click(homeNavButton)
|
||||
.expect(displayNameInComposeBox.innerText).eql('foo 🕹📺')
|
||||
|
||||
// clean up after all these tests are done
|
||||
await updateUserDisplayNameAs('foobar', 'foobar')
|
||||
})
|
||||
|
|
16
tests/spec/120-status-aria-label.js
Normal file
16
tests/spec/120-status-aria-label.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { loginAsFoobar } from '../roles'
|
||||
import { getNthStatus } from '../utils'
|
||||
import { postEmptyStatusWithMediaAs } from '../serverActions'
|
||||
|
||||
fixture`120-status-aria-label.js`
|
||||
.page`http://localhost:4002`
|
||||
|
||||
test('aria-labels for statuses with no content text', async t => {
|
||||
await postEmptyStatusWithMediaAs('foobar', 'kitten1.jpg', 'kitteh')
|
||||
await loginAsFoobar(t)
|
||||
await t
|
||||
.hover(getNthStatus(0))
|
||||
.expect(getNthStatus(0).getAttribute('aria-label')).match(
|
||||
/foobar, (.+ ago|just now), @foobar, Public/i
|
||||
)
|
||||
})
|
29
tests/submitMedia.js
Normal file
29
tests/submitMedia.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import FormData from 'form-data'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { auth } from '../routes/_api/utils'
|
||||
|
||||
export async function submitMedia (accessToken, filename, alt) {
|
||||
let form = new FormData()
|
||||
form.append('file', fs.createReadStream(path.join(__dirname, 'images', filename)))
|
||||
form.append('description', alt)
|
||||
return new Promise((resolve, reject) => {
|
||||
form.submit({
|
||||
host: 'localhost',
|
||||
port: 3000,
|
||||
path: '/api/v1/media',
|
||||
headers: auth(accessToken)
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
let data = ''
|
||||
|
||||
res.on('data', chunk => {
|
||||
data += chunk
|
||||
})
|
||||
|
||||
res.on('end', () => resolve(JSON.parse(data)))
|
||||
})
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue