Fix alts for image uploads (#54)
* Fix alts for image uploads Fixes #41 * fix alts mixed with no-alts
This commit is contained in:
parent
bca959d1a3
commit
7ae3212c55
|
@ -4,6 +4,7 @@ import { postStatus as postStatusToServer } from '../_api/statuses'
|
||||||
import { addStatusOrNotification } from './addStatusOrNotification'
|
import { addStatusOrNotification } from './addStatusOrNotification'
|
||||||
import { database } from '../_database/database'
|
import { database } from '../_database/database'
|
||||||
import { emit } from '../_utils/eventBus'
|
import { emit } from '../_utils/eventBus'
|
||||||
|
import { putMediaDescription } from '../_api/media'
|
||||||
|
|
||||||
export async function insertHandleForReply (statusId) {
|
export async function insertHandleForReply (statusId) {
|
||||||
let instanceName = store.get('currentInstance')
|
let instanceName = store.get('currentInstance')
|
||||||
|
@ -20,7 +21,8 @@ export async function insertHandleForReply (statusId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postStatus (realm, text, inReplyToId, mediaIds,
|
export async function postStatus (realm, text, inReplyToId, mediaIds,
|
||||||
sensitive, spoilerText, visibility) {
|
sensitive, spoilerText, visibility,
|
||||||
|
mediaDescriptions = []) {
|
||||||
let instanceName = store.get('currentInstance')
|
let instanceName = store.get('currentInstance')
|
||||||
let accessToken = store.get('accessToken')
|
let accessToken = store.get('accessToken')
|
||||||
let online = store.get('online')
|
let online = store.get('online')
|
||||||
|
@ -34,6 +36,9 @@ export async function postStatus (realm, text, inReplyToId, mediaIds,
|
||||||
postingStatus: true
|
postingStatus: true
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
|
await Promise.all(mediaDescriptions.map(async (description, i) => {
|
||||||
|
return description && putMediaDescription(instanceName, accessToken, mediaIds[i], description)
|
||||||
|
}))
|
||||||
let status = await postStatusToServer(instanceName, accessToken, text,
|
let status = await postStatusToServer(instanceName, accessToken, text,
|
||||||
inReplyToId, mediaIds, sensitive, spoilerText, visibility)
|
inReplyToId, mediaIds, sensitive, spoilerText, visibility)
|
||||||
addStatusOrNotification(instanceName, 'home', status)
|
addStatusOrNotification(instanceName, 'home', status)
|
||||||
|
|
|
@ -36,9 +36,15 @@ export function deleteMedia (realm, i) {
|
||||||
let composeText = store.getComposeData(realm, 'text') || ''
|
let composeText = store.getComposeData(realm, 'text') || ''
|
||||||
composeText = composeText.replace(' ' + deletedMedia.data.text_url, '')
|
composeText = composeText.replace(' ' + deletedMedia.data.text_url, '')
|
||||||
|
|
||||||
|
let mediaDescriptions = store.getComposeData(realm, 'mediaDescriptions') || []
|
||||||
|
if (mediaDescriptions[i]) {
|
||||||
|
mediaDescriptions[i] = null
|
||||||
|
}
|
||||||
|
|
||||||
store.setComposeData(realm, {
|
store.setComposeData(realm, {
|
||||||
media: composeMedia,
|
media: composeMedia,
|
||||||
text: composeText
|
text: composeText,
|
||||||
|
mediaDescriptions: mediaDescriptions
|
||||||
})
|
})
|
||||||
scheduleIdleTask(() => store.save())
|
scheduleIdleTask(() => store.save())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
import { auth, basename } from './utils'
|
import { auth, basename } from './utils'
|
||||||
import { postWithTimeout } from '../_utils/ajax'
|
import { postWithTimeout, putWithTimeout } from '../_utils/ajax'
|
||||||
|
|
||||||
export async function uploadMedia (instanceName, accessToken, file, description) {
|
export async function uploadMedia (instanceName, accessToken, file, description) {
|
||||||
let formData = new FormData()
|
let formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
formData.append('description', description)
|
if (description) {
|
||||||
|
formData.append('description', description)
|
||||||
|
}
|
||||||
let url = `${basename(instanceName)}/api/v1/media`
|
let url = `${basename(instanceName)}/api/v1/media`
|
||||||
return postWithTimeout(url, formData, auth(accessToken))
|
return postWithTimeout(url, formData, auth(accessToken))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function putMediaDescription (instanceName, accessToken, mediaId, description) {
|
||||||
|
let url = `${basename(instanceName)}/api/v1/media/${mediaId}`
|
||||||
|
return putWithTimeout(url, {description}, auth(accessToken))
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<ComposeLengthGauge :length :overLimit />
|
<ComposeLengthGauge :length :overLimit />
|
||||||
<ComposeToolbar :realm :postPrivacy :media :contentWarningShown :text />
|
<ComposeToolbar :realm :postPrivacy :media :contentWarningShown :text />
|
||||||
<ComposeLengthIndicator :length :overLimit />
|
<ComposeLengthIndicator :length :overLimit />
|
||||||
<ComposeMedia :realm :media />
|
<ComposeMedia :realm :media :mediaDescriptions />
|
||||||
</div>
|
</div>
|
||||||
<div class="compose-box-button-sentinel {{hideAndFadeIn}}" ref:sentinel></div>
|
<div class="compose-box-button-sentinel {{hideAndFadeIn}}" ref:sentinel></div>
|
||||||
<div class="compose-box-button-wrapper {{realm === 'home' ? 'compose-button-sticky' : ''}} {{hideAndFadeIn}}" >
|
<div class="compose-box-button-wrapper {{realm === 'home' ? 'compose-button-sticky' : ''}} {{hideAndFadeIn}}" >
|
||||||
|
@ -170,7 +170,8 @@
|
||||||
overLimit: (length) => length > CHAR_LIMIT,
|
overLimit: (length) => length > CHAR_LIMIT,
|
||||||
contentWarningShown: (composeData) => composeData.contentWarningShown,
|
contentWarningShown: (composeData) => composeData.contentWarningShown,
|
||||||
contentWarning: (composeData) => composeData.contentWarning || '',
|
contentWarning: (composeData) => composeData.contentWarning || '',
|
||||||
timelineInitialized: ($timelineInitialized) => $timelineInitialized
|
timelineInitialized: ($timelineInitialized) => $timelineInitialized,
|
||||||
|
mediaDescriptions: (composeData) => composeData.mediaDescriptions || []
|
||||||
},
|
},
|
||||||
transitions: {
|
transitions: {
|
||||||
slide
|
slide
|
||||||
|
@ -193,6 +194,7 @@
|
||||||
let mediaIds = media.map(_ => _.data.id)
|
let mediaIds = media.map(_ => _.data.id)
|
||||||
let inReplyTo = (realm === 'home' || realm === 'dialog') ? null : realm
|
let inReplyTo = (realm === 'home' || realm === 'dialog') ? null : realm
|
||||||
let overLimit = this.get('overLimit')
|
let overLimit = this.get('overLimit')
|
||||||
|
let mediaDescriptions = this.get('mediaDescriptions')
|
||||||
|
|
||||||
if (!text || overLimit) {
|
if (!text || overLimit) {
|
||||||
return // do nothing if invalid
|
return // do nothing if invalid
|
||||||
|
@ -200,7 +202,7 @@
|
||||||
|
|
||||||
/* no await */
|
/* no await */
|
||||||
postStatus(realm, text, inReplyTo, mediaIds,
|
postStatus(realm, text, inReplyTo, mediaIds,
|
||||||
sensitive, contentWarning, postPrivacyKey)
|
sensitive, contentWarning, postPrivacyKey, mediaDescriptions)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setupStickyObserver() {
|
setupStickyObserver() {
|
||||||
|
|
|
@ -67,13 +67,13 @@
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setupSyncToStore() {
|
setupSyncToStore() {
|
||||||
const saveText = debounce(() => scheduleIdleTask(() => this.store.save()), 1000)
|
const saveStore = debounce(() => scheduleIdleTask(() => this.store.save()), 1000)
|
||||||
|
|
||||||
this.observe('rawText', rawText => {
|
this.observe('rawText', rawText => {
|
||||||
mark('observe rawText')
|
mark('observe rawText')
|
||||||
let realm = this.get('realm')
|
let realm = this.get('realm')
|
||||||
this.store.setComposeData(realm, {text: rawText})
|
this.store.setComposeData(realm, {text: rawText})
|
||||||
saveText()
|
saveStore()
|
||||||
stop('observe rawText')
|
stop('observe rawText')
|
||||||
}, {init: false})
|
}, {init: false})
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,23 +1,7 @@
|
||||||
{{#if media.length}}
|
{{#if media.length}}
|
||||||
<div class="compose-media-container" style="grid-template-columns: repeat({{media.length}}, 1fr);">
|
<div class="compose-media-container" style="grid-template-columns: repeat({{media.length}}, 1fr);">
|
||||||
{{#each media as mediaItem, i}}
|
{{#each media as mediaItem, index}}
|
||||||
<div class="compose-media">
|
<ComposeMediaItem :realm :mediaItem :index :mediaDescriptions />
|
||||||
<img src="{{mediaItem.data.preview_url}}" alt="{{mediaItem.file.name}}"/>
|
|
||||||
<div class="compose-media-delete">
|
|
||||||
<button class="compose-media-delete-button"
|
|
||||||
aria-label="Delete {{mediaItem.file.name}}"
|
|
||||||
on:click="onDeleteMedia(i)" >
|
|
||||||
<svg class="compose-media-delete-button-svg">
|
|
||||||
<use xlink:href="#fa-times" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="compose-media-alt">
|
|
||||||
<input type="text"
|
|
||||||
placeholder="Description"
|
|
||||||
aria-label="Describe {{mediaItem.file.name}} for the visually impaired">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -33,72 +17,15 @@
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.compose-media {
|
|
||||||
height: 200px;
|
|
||||||
overflow: hidden;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
background: var(--main-bg);
|
|
||||||
}
|
|
||||||
.compose-media img {
|
|
||||||
object-fit: contain;
|
|
||||||
object-position: center center;
|
|
||||||
flex: 1;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.compose-media-alt {
|
|
||||||
z-index: 10;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.compose-media-alt input {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 1.2em;
|
|
||||||
background: var(--alt-input-bg);
|
|
||||||
}
|
|
||||||
.compose-media-alt input:focus {
|
|
||||||
background: var(--main-bg);
|
|
||||||
}
|
|
||||||
.compose-media-delete {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
.compose-media-delete-button {
|
|
||||||
padding: 10px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.compose-media-delete-button:hover {
|
|
||||||
background: var(--toast-border);
|
|
||||||
}
|
|
||||||
.compose-media-delete-button-svg {
|
|
||||||
fill: var(--button-text);
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import { store } from '../../_store/store'
|
import { store } from '../../_store/store'
|
||||||
import { deleteMedia } from '../../_actions/media'
|
import ComposeMediaItem from './ComposeMediaItem.html'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
store: () => store,
|
store: () => store,
|
||||||
methods: {
|
components: {
|
||||||
onDeleteMedia(i) {
|
ComposeMediaItem
|
||||||
deleteMedia(this.get('realm'), i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -0,0 +1,126 @@
|
||||||
|
<div class="compose-media">
|
||||||
|
<img src="{{mediaItem.data.preview_url}}" alt="{{mediaItem.file.name}}"/>
|
||||||
|
<div class="compose-media-delete">
|
||||||
|
<button class="compose-media-delete-button"
|
||||||
|
aria-label="Delete {{mediaItem.file.name}}"
|
||||||
|
on:click="onDeleteMedia()" >
|
||||||
|
<svg class="compose-media-delete-button-svg">
|
||||||
|
<use xlink:href="#fa-times" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="compose-media-alt">
|
||||||
|
<input type="text"
|
||||||
|
placeholder="Description"
|
||||||
|
aria-label="Describe {{mediaItem.file.name}} for the visually impaired"
|
||||||
|
bind:value=rawText
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.compose-media {
|
||||||
|
height: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
background: var(--main-bg);
|
||||||
|
}
|
||||||
|
.compose-media img {
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: center center;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.compose-media-alt {
|
||||||
|
z-index: 10;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.compose-media-alt input {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1.2em;
|
||||||
|
background: var(--alt-input-bg);
|
||||||
|
}
|
||||||
|
.compose-media-alt input:focus {
|
||||||
|
background: var(--main-bg);
|
||||||
|
}
|
||||||
|
.compose-media-delete {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
.compose-media-delete-button {
|
||||||
|
padding: 10px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.compose-media-delete-button:hover {
|
||||||
|
background: var(--toast-border);
|
||||||
|
}
|
||||||
|
.compose-media-delete-button-svg {
|
||||||
|
fill: var(--button-text);
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
import { store } from '../../_store/store'
|
||||||
|
import { deleteMedia } from '../../_actions/media'
|
||||||
|
import debounce from 'lodash-es/debounce'
|
||||||
|
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
oncreate() {
|
||||||
|
this.setupSyncFromStore()
|
||||||
|
this.setupSyncToStore()
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
rawText: ''
|
||||||
|
}),
|
||||||
|
store: () => store,
|
||||||
|
methods: {
|
||||||
|
setupSyncFromStore() {
|
||||||
|
this.observe('mediaDescriptions', mediaDescriptions => {
|
||||||
|
mediaDescriptions = mediaDescriptions || []
|
||||||
|
let index = this.get('index')
|
||||||
|
let text = mediaDescriptions[index] || ''
|
||||||
|
if (this.get('rawText') !== text) {
|
||||||
|
this.set({rawText: text})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setupSyncToStore() {
|
||||||
|
const saveStore = debounce(() => scheduleIdleTask(() => this.store.save()), 1000)
|
||||||
|
|
||||||
|
this.observe('rawText', rawText => {
|
||||||
|
let realm = this.get('realm')
|
||||||
|
let index = this.get('index')
|
||||||
|
let mediaDescriptions = store.getComposeData(realm, 'mediaDescriptions') || []
|
||||||
|
if (mediaDescriptions[index] === rawText) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
while (mediaDescriptions.length <= index) {
|
||||||
|
mediaDescriptions.push(null)
|
||||||
|
}
|
||||||
|
mediaDescriptions[index] = rawText
|
||||||
|
store.setComposeData(realm, {mediaDescriptions})
|
||||||
|
saveStore()
|
||||||
|
}, {init: false})
|
||||||
|
},
|
||||||
|
onDeleteMedia() {
|
||||||
|
deleteMedia(this.get('realm'), this.get('index'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -2,4 +2,4 @@ let count = -1
|
||||||
|
|
||||||
export function createDialogId () {
|
export function createDialogId () {
|
||||||
return ++count
|
return ++count
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,26 @@ async function _post (url, body, headers, timeout) {
|
||||||
return throwErrorIfInvalidResponse(response)
|
return throwErrorIfInvalidResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function _put (url, body, headers, timeout) {
|
||||||
|
let fetchFunc = timeout ? fetchWithTimeout : fetch
|
||||||
|
let opts = {
|
||||||
|
method: 'PUT'
|
||||||
|
}
|
||||||
|
if (body) {
|
||||||
|
opts.headers = Object.assign(headers, {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
opts.body = JSON.stringify(body)
|
||||||
|
} else {
|
||||||
|
opts.headers = Object.assign(headers, {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let response = await fetchFunc(url, opts)
|
||||||
|
return throwErrorIfInvalidResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
async function _get (url, headers, timeout) {
|
async function _get (url, headers, timeout) {
|
||||||
let fetchFunc = timeout ? fetchWithTimeout : fetch
|
let fetchFunc = timeout ? fetchWithTimeout : fetch
|
||||||
let response = await fetchFunc(url, {
|
let response = await fetchFunc(url, {
|
||||||
|
@ -63,6 +83,14 @@ async function _delete (url, headers, timeout) {
|
||||||
return throwErrorIfInvalidResponse(response)
|
return throwErrorIfInvalidResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function put (url, body, headers = {}) {
|
||||||
|
return _put(url, body, headers, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putWithTimeout (url, body, headers = {}) {
|
||||||
|
return _put(url, body, headers, true)
|
||||||
|
}
|
||||||
|
|
||||||
export async function post (url, body, headers = {}) {
|
export async function post (url, body, headers = {}) {
|
||||||
return _post(url, body, headers, false)
|
return _post(url, body, headers, false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
import { composeInput, getNthDeleteMediaButton, getNthMedia, mediaButton, uploadKittenImage } from '../utils'
|
import {
|
||||||
|
composeInput, getNthDeleteMediaButton, getNthMedia, mediaButton,
|
||||||
|
uploadKittenImage
|
||||||
|
} from '../utils'
|
||||||
import { foobarRole } from '../roles'
|
import { foobarRole } from '../roles'
|
||||||
|
|
||||||
fixture`013-compose-media.js`
|
fixture`013-compose-media.js`
|
||||||
|
|
|
@ -48,4 +48,4 @@ test('can use emoji dialog within compose dialog', async t => {
|
||||||
.expect(showMoreButton.innerText).contains('Show 1 more')
|
.expect(showMoreButton.innerText).contains('Show 1 more')
|
||||||
.click(showMoreButton)
|
.click(showMoreButton)
|
||||||
await t.expect(getNthStatus(0).find('img[alt=":blobpats:"]').exists).ok({timeout: 20000})
|
await t.expect(getNthStatus(0).find('img[alt=":blobpats:"]').exists).ok({timeout: 20000})
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import {
|
||||||
|
composeButton, getNthDeleteMediaButton, getNthMedia, getNthMediaAltInput, getNthStatusAndImage, getUrl,
|
||||||
|
homeNavButton,
|
||||||
|
mediaButton, notificationsNavButton,
|
||||||
|
uploadKittenImage
|
||||||
|
} from '../utils'
|
||||||
|
import { foobarRole } from '../roles'
|
||||||
|
|
||||||
|
fixture`109-compose-media.js`
|
||||||
|
.page`http://localhost:4002`
|
||||||
|
|
||||||
|
async function uploadTwoKittens (t) {
|
||||||
|
await (uploadKittenImage(1)())
|
||||||
|
await t.expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg')
|
||||||
|
await (uploadKittenImage(2)())
|
||||||
|
await t.expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg')
|
||||||
|
.expect(getNthMedia(2).getAttribute('alt')).eql('kitten2.jpg')
|
||||||
|
}
|
||||||
|
|
||||||
|
test('uploads alts for media', async t => {
|
||||||
|
await t.useRole(foobarRole)
|
||||||
|
.expect(mediaButton.hasAttribute('disabled')).notOk()
|
||||||
|
await uploadTwoKittens(t)
|
||||||
|
await t.typeText(getNthMediaAltInput(2), 'kitten 2')
|
||||||
|
.typeText(getNthMediaAltInput(1), 'kitten 1')
|
||||||
|
.click(composeButton)
|
||||||
|
.expect(getNthStatusAndImage(0, 0).getAttribute('alt')).eql('kitten 1')
|
||||||
|
.expect(getNthStatusAndImage(0, 1).getAttribute('alt')).eql('kitten 2')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('uploads alts when deleting and re-uploading media', async t => {
|
||||||
|
await t.useRole(foobarRole)
|
||||||
|
.expect(mediaButton.hasAttribute('disabled')).notOk()
|
||||||
|
await (uploadKittenImage(1)())
|
||||||
|
await t.typeText(getNthMediaAltInput(1), 'this will be deleted')
|
||||||
|
.click(getNthDeleteMediaButton(1))
|
||||||
|
.expect(getNthMedia(1).exists).notOk()
|
||||||
|
await (uploadKittenImage(2)())
|
||||||
|
await t.expect(getNthMediaAltInput(1).value).eql('')
|
||||||
|
.expect(getNthMedia(1).getAttribute('alt')).eql('kitten2.jpg')
|
||||||
|
.typeText(getNthMediaAltInput(1), 'this will not be deleted')
|
||||||
|
.click(composeButton)
|
||||||
|
.expect(getNthStatusAndImage(0, 0).getAttribute('alt')).eql('this will not be deleted')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('uploads alts mixed with no-alts', async t => {
|
||||||
|
await t.useRole(foobarRole)
|
||||||
|
.expect(mediaButton.hasAttribute('disabled')).notOk()
|
||||||
|
await uploadTwoKittens(t)
|
||||||
|
await t.typeText(getNthMediaAltInput(2), 'kitten numero dos')
|
||||||
|
.click(composeButton)
|
||||||
|
.expect(getNthStatusAndImage(0, 0).getAttribute('alt')).eql('')
|
||||||
|
.expect(getNthStatusAndImage(0, 1).getAttribute('alt')).eql('kitten numero dos')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('saves alts to local storage', async t => {
|
||||||
|
await t.useRole(foobarRole)
|
||||||
|
.expect(mediaButton.hasAttribute('disabled')).notOk()
|
||||||
|
await uploadTwoKittens(t)
|
||||||
|
await t.typeText(getNthMediaAltInput(1), 'kitten numero uno')
|
||||||
|
.typeText(getNthMediaAltInput(2), 'kitten numero dos')
|
||||||
|
.click(notificationsNavButton)
|
||||||
|
.expect(getUrl()).contains('/notifications')
|
||||||
|
.click(homeNavButton)
|
||||||
|
.expect(getUrl()).eql('http://localhost:4002/')
|
||||||
|
.expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg')
|
||||||
|
.expect(getNthMedia(2).getAttribute('alt')).eql('kitten2.jpg')
|
||||||
|
.expect(getNthMediaAltInput(1).value).eql('kitten numero uno')
|
||||||
|
.expect(getNthMediaAltInput(2).value).eql('kitten numero dos')
|
||||||
|
.click(composeButton)
|
||||||
|
.expect(getNthStatusAndImage(0, 0).getAttribute('alt')).eql('kitten numero uno')
|
||||||
|
.expect(getNthStatusAndImage(0, 1).getAttribute('alt')).eql('kitten numero dos')
|
||||||
|
})
|
|
@ -96,6 +96,10 @@ export const uploadKittenImage = i => (exec(() => {
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
export function getNthMediaAltInput (n) {
|
||||||
|
return $(`.compose-box .compose-media:nth-child(${n}) .compose-media-alt input`)
|
||||||
|
}
|
||||||
|
|
||||||
export function getNthComposeReplyInput (n) {
|
export function getNthComposeReplyInput (n) {
|
||||||
return getNthStatus(n).find('.compose-box-input')
|
return getNthStatus(n).find('.compose-box-input')
|
||||||
}
|
}
|
||||||
|
@ -128,6 +132,10 @@ export function getNthStatus (n) {
|
||||||
return $(`div[aria-hidden="false"] > article[aria-posinset="${n}"]`)
|
return $(`div[aria-hidden="false"] > article[aria-posinset="${n}"]`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNthStatusAndImage (nStatus, nImage) {
|
||||||
|
return getNthStatus(nStatus).find(`.status-media .show-image-button:nth-child(${nImage + 1}) img`)
|
||||||
|
}
|
||||||
|
|
||||||
export function getLastVisibleStatus () {
|
export function getLastVisibleStatus () {
|
||||||
return $(`div[aria-hidden="false"] > article[aria-posinset]`).nth(-1)
|
return $(`div[aria-hidden="false"] > article[aria-posinset]`).nth(-1)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue