parent
							
								
									d49af06fbd
								
							
						
					
					
						commit
						b60d636ee2
					
				
					 9 changed files with 112 additions and 48 deletions
				
			
		|  | @ -22,7 +22,7 @@ export async function insertHandleForReply (statusId) { | |||
| 
 | ||||
| export async function postStatus (realm, text, inReplyToId, mediaIds, | ||||
|   sensitive, spoilerText, visibility, | ||||
|   mediaDescriptions = [], inReplyToUuid) { | ||||
|   mediaDescriptions, inReplyToUuid) { | ||||
|   let { currentInstance, accessToken, online } = store.get() | ||||
| 
 | ||||
|   if (!online) { | ||||
|  | @ -30,6 +30,9 @@ export async function postStatus (realm, text, inReplyToId, mediaIds, | |||
|     return | ||||
|   } | ||||
| 
 | ||||
|   text = text || '' | ||||
|   mediaDescriptions = mediaDescriptions || [] | ||||
| 
 | ||||
|   store.set({ | ||||
|     postingStatus: true | ||||
|   }) | ||||
|  |  | |||
|  | @ -11,13 +11,11 @@ export async function doMediaUpload (realm, file) { | |||
|     let composeMedia = store.getComposeData(realm, 'media') || [] | ||||
|     composeMedia.push({ | ||||
|       data: response, | ||||
|       file: { name: file.name } | ||||
|       file: { name: file.name }, | ||||
|       description: '' | ||||
|     }) | ||||
|     let composeText = store.getComposeData(realm, 'text') || '' | ||||
|     composeText += ' ' + response.text_url | ||||
|     store.setComposeData(realm, { | ||||
|       media: composeMedia, | ||||
|       text: composeText | ||||
|       media: composeMedia | ||||
|     }) | ||||
|     scheduleIdleTask(() => store.save()) | ||||
|   } catch (e) { | ||||
|  | @ -30,20 +28,10 @@ export async function doMediaUpload (realm, file) { | |||
| 
 | ||||
| export function deleteMedia (realm, i) { | ||||
|   let composeMedia = store.getComposeData(realm, 'media') | ||||
|   let deletedMedia = composeMedia.splice(i, 1)[0] | ||||
| 
 | ||||
|   let composeText = store.getComposeData(realm, 'text') || '' | ||||
|   composeText = composeText.replace(' ' + deletedMedia.data.text_url, '') | ||||
| 
 | ||||
|   let mediaDescriptions = store.getComposeData(realm, 'mediaDescriptions') || [] | ||||
|   if (mediaDescriptions[i]) { | ||||
|     mediaDescriptions[i] = null | ||||
|   } | ||||
|   composeMedia.splice(i, 1) | ||||
| 
 | ||||
|   store.setComposeData(realm, { | ||||
|     media: composeMedia, | ||||
|     text: composeText, | ||||
|     mediaDescriptions: mediaDescriptions | ||||
|     media: composeMedia | ||||
|   }) | ||||
|   scheduleIdleTask(() => store.save()) | ||||
| } | ||||
|  |  | |||
|  | @ -16,7 +16,8 @@ export async function postStatus (instanceName, accessToken, text, inReplyToId, | |||
| 
 | ||||
|   for (let key of Object.keys(body)) { | ||||
|     let value = body[key] | ||||
|     if (!value || (Array.isArray(value) && !value.length)) { | ||||
|     // remove any unnecessary fields, except 'status' which must at least be an empty string
 | ||||
|     if (key !== 'status' && (!value || (Array.isArray(value) && !value.length))) { | ||||
|       delete body[key] | ||||
|     } | ||||
|   } | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
|   <ComposeLengthGauge {length} {overLimit} /> | ||||
|   <ComposeToolbar {realm} {postPrivacy} {media} {contentWarningShown} {text} /> | ||||
|   <ComposeLengthIndicator {length} {overLimit} /> | ||||
|   <ComposeMedia {realm} {media} {mediaDescriptions} /> | ||||
|   <ComposeMedia {realm} {media} /> | ||||
| </div> | ||||
| <div class="compose-box-button-sentinel {hideAndFadeIn}" ref:sentinel></div> | ||||
| <div class="compose-box-button-wrapper {realm === 'home' ? 'compose-button-sticky' : ''} {hideAndFadeIn}" > | ||||
|  | @ -186,8 +186,7 @@ | |||
|       overLimit: ({ length, $maxStatusChars }) => length > $maxStatusChars, | ||||
|       contentWarningShown: ({ composeData }) => composeData.contentWarningShown, | ||||
|       contentWarning: ({ composeData }) => composeData.contentWarning || '', | ||||
|       timelineInitialized: ({ $timelineInitialized }) => $timelineInitialized, | ||||
|       mediaDescriptions: ({ composeData }) => composeData.mediaDescriptions || [] | ||||
|       timelineInitialized: ({ $timelineInitialized }) => $timelineInitialized | ||||
|     }, | ||||
|     transitions: { | ||||
|       slide | ||||
|  | @ -214,14 +213,14 @@ | |||
|           contentWarning, | ||||
|           realm, | ||||
|           overLimit, | ||||
|           mediaDescriptions, | ||||
|           inReplyToUuid | ||||
|         } = this.get() | ||||
|         let sensitive = media.length && !!contentWarning | ||||
|         let mediaIds = media.map(_ => _.data.id) | ||||
|         let mediaDescriptions = media.map(_ => _.description) | ||||
|         let inReplyTo = (realm === 'home' || realm === 'dialog') ? null : realm | ||||
| 
 | ||||
|         if (!text || overLimit) { | ||||
|         if (overLimit || (!text && !media.length)) { | ||||
|           return // do nothing if invalid | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| {#if media.length} | ||||
|   <div class="compose-media-container" style="grid-template-columns: repeat({media.length}, 1fr);"> | ||||
|     {#each media as mediaItem, index} | ||||
|       <ComposeMediaItem {realm} {mediaItem} {index} {mediaDescriptions} /> | ||||
|       <ComposeMediaItem {realm} {mediaItem} {index} {media} /> | ||||
|     {/each} | ||||
|   </div> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -95,10 +95,10 @@ | |||
|     methods: { | ||||
|       observe, | ||||
|       setupSyncFromStore () { | ||||
|         this.observe('mediaDescriptions', mediaDescriptions => { | ||||
|           mediaDescriptions = mediaDescriptions || [] | ||||
|         this.observe('media', media => { | ||||
|           media = media || [] | ||||
|           let { index, rawText } = this.get() | ||||
|           let text = mediaDescriptions[index] || '' | ||||
|           let text = (media[index] && media[index].description) || '' | ||||
|           if (rawText !== text) { | ||||
|             this.set({rawText: text}) | ||||
|           } | ||||
|  | @ -108,17 +108,12 @@ | |||
|         const saveStore = debounce(() => scheduleIdleTask(() => this.store.save()), 1000) | ||||
| 
 | ||||
|         this.observe('rawText', rawText => { | ||||
|           let { realm } = this.get() | ||||
|           let { index } = this.get() | ||||
|           let mediaDescriptions = store.getComposeData(realm, 'mediaDescriptions') || [] | ||||
|           if (mediaDescriptions[index] === rawText) { | ||||
|           let { realm, index, media } = this.get() | ||||
|           if (media[index].description === rawText) { | ||||
|             return | ||||
|           } | ||||
|           while (mediaDescriptions.length <= index) { | ||||
|             mediaDescriptions.push(null) | ||||
|           } | ||||
|           mediaDescriptions[index] = rawText | ||||
|           store.setComposeData(realm, {mediaDescriptions}) | ||||
|           media[index].description = rawText | ||||
|           store.setComposeData(realm, {media}) | ||||
|           saveStore() | ||||
|         }, {init: false}) | ||||
|       }, | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| import { Selector as $ } from 'testcafe' | ||||
| import { | ||||
|   composeButton, composeInput, composeLengthIndicator, emojiButton, getComposeSelectionStart, getUrl, | ||||
|   composeButton, composeInput, composeLengthIndicator, emojiButton, getComposeSelectionStart, | ||||
|   getNthStatusContent, getUrl, | ||||
|   homeNavButton, | ||||
|   notificationsNavButton, | ||||
|   notificationsNavButton, sleep, | ||||
|   times | ||||
| } from '../utils' | ||||
| import { loginAsFoobar } from '../roles' | ||||
|  | @ -97,3 +98,13 @@ test('inserts emoji without typing anything', async t => { | |||
|     .click($('button img[title=":blobpeek:"]')) | ||||
|     .expect(composeInput.value).eql(':blobpeek: :blobpats: ') | ||||
| }) | ||||
| 
 | ||||
| test('cannot post an empty status', async t => { | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(getNthStatusContent(0).innerText).contains('pinned toot 1') | ||||
|     .click(composeButton) | ||||
|   await sleep(2) | ||||
|   await t | ||||
|     .expect(getNthStatusContent(0).innerText).contains('pinned toot 1') | ||||
| }) | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import { | ||||
|   composeInput, getNthDeleteMediaButton, getNthMedia, mediaButton, | ||||
|   composeInput, getNthDeleteMediaButton, getNthMedia, getNthMediaAltInput, homeNavButton, mediaButton, | ||||
|   settingsNavButton, sleep, | ||||
|   uploadKittenImage | ||||
| } from '../utils' | ||||
| import { loginAsFoobar } from '../roles' | ||||
|  | @ -51,16 +52,70 @@ test('removes media', async t => { | |||
|     .expect(getNthMedia(2).exists).notOk() | ||||
| }) | ||||
| 
 | ||||
| test('changes URLs as media is added/removed', async t => { | ||||
| test('does not add URLs as media is added/removed', async t => { | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, 'this is a toot') | ||||
|     .expect(mediaButton.exists).ok() | ||||
|   await (uploadKittenImage(1)()) | ||||
|   await t.expect(composeInput.value).eql('this is a toot') | ||||
|   await (uploadKittenImage(1)()) | ||||
|   await t.expect(composeInput.value).eql('this is a toot') | ||||
|     .click(getNthDeleteMediaButton(1)) | ||||
|     .expect(composeInput.value).eql('this is a toot') | ||||
|     .click(getNthDeleteMediaButton(1)) | ||||
|     .expect(composeInput.value).eql('this is a toot') | ||||
| }) | ||||
| 
 | ||||
| test('keeps media descriptions as media is removed', async t => { | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(mediaButton.exists).ok() | ||||
|   await (uploadKittenImage(1)()) | ||||
|   await t.expect(composeInput.value).match(/^ http:\/\/localhost:3000\/media\/\S+$/) | ||||
|   await (uploadKittenImage(1)()) | ||||
|   await t.expect(composeInput.value).match(/^ http:\/\/localhost:3000\/media\/\S+ http:\/\/localhost:3000\/media\/\S+$/) | ||||
|   await t | ||||
|     .typeText(getNthMediaAltInput(1), 'kitten numero uno') | ||||
|   await (uploadKittenImage(2)()) | ||||
|   await t | ||||
|     .typeText(getNthMediaAltInput(2), 'kitten numero dos') | ||||
|     .expect(getNthMediaAltInput(1).value).eql('kitten numero uno') | ||||
|     .expect(getNthMediaAltInput(2).value).eql('kitten numero dos') | ||||
|     .expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg') | ||||
|     .expect(getNthMedia(2).getAttribute('alt')).eql('kitten2.jpg') | ||||
|     .click(getNthDeleteMediaButton(1)) | ||||
|     .expect(composeInput.value).match(/^ http:\/\/localhost:3000\/media\/\S+$/) | ||||
|     .click(getNthDeleteMediaButton(1)) | ||||
|     .expect(composeInput.value).eql('') | ||||
|     .expect(getNthMediaAltInput(1).value).eql('kitten numero dos') | ||||
|     .expect(getNthMedia(1).getAttribute('alt')).eql('kitten2.jpg') | ||||
| }) | ||||
| 
 | ||||
| test('keeps media in local storage', async t => { | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(mediaButton.exists).ok() | ||||
|   await (uploadKittenImage(1)()) | ||||
|   await t | ||||
|     .typeText(getNthMediaAltInput(1), 'kitten numero uno') | ||||
|   await (uploadKittenImage(2)()) | ||||
|   await t | ||||
|     .typeText(getNthMediaAltInput(2), 'kitten numero dos') | ||||
|   await t | ||||
|     .typeText(composeInput, 'hello hello') | ||||
|     .expect(composeInput.value).eql('hello hello') | ||||
|     .expect(getNthMediaAltInput(1).value).eql('kitten numero uno') | ||||
|     .expect(getNthMediaAltInput(2).value).eql('kitten numero dos') | ||||
|     .expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg') | ||||
|     .expect(getNthMedia(2).getAttribute('alt')).eql('kitten2.jpg') | ||||
|   await sleep(1) | ||||
|   await t | ||||
|     .click(settingsNavButton) | ||||
|     .click(homeNavButton) | ||||
|     .expect(composeInput.value).eql('hello hello') | ||||
|     .expect(getNthMediaAltInput(1).value).eql('kitten numero uno') | ||||
|     .expect(getNthMediaAltInput(2).value).eql('kitten numero dos') | ||||
|     .expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg') | ||||
|     .expect(getNthMedia(2).getAttribute('alt')).eql('kitten2.jpg') | ||||
|     .navigateTo('/') | ||||
|     .expect(composeInput.value).eql('hello hello') | ||||
|     .expect(getNthMediaAltInput(1).value).eql('kitten numero uno') | ||||
|     .expect(getNthMediaAltInput(2).value).eql('kitten numero dos') | ||||
|     .expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg') | ||||
|     .expect(getNthMedia(2).getAttribute('alt')).eql('kitten2.jpg') | ||||
| }) | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { | ||||
|   composeButton, getNthDeleteMediaButton, getNthMedia, getNthMediaAltInput, getNthStatusAndImage, getUrl, | ||||
|   composeButton, composeInput, getNthDeleteMediaButton, getNthMedia, getNthMediaAltInput, getNthStatusAndImage, getUrl, | ||||
|   homeNavButton, | ||||
|   mediaButton, notificationsNavButton, | ||||
|   uploadKittenImage | ||||
|  | @ -77,3 +77,15 @@ test('saves alts to local storage', async t => { | |||
|     .expect(getNthStatusAndImage(0, 0).getAttribute('alt')).eql('kitten numero uno') | ||||
|     .expect(getNthStatusAndImage(0, 1).getAttribute('alt')).eql('kitten numero dos') | ||||
| }) | ||||
| 
 | ||||
| test('can post a status with empty content if there is media', async t => { | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(mediaButton.hasAttribute('disabled')).notOk() | ||||
|     .typeText(composeInput, 'this is a toot') | ||||
|   await (uploadKittenImage(1)()) | ||||
|   await t | ||||
|     .typeText(getNthMediaAltInput(1), 'just an image!') | ||||
|   await t.click(composeButton) | ||||
|     .expect(getNthStatusAndImage(0, 0).getAttribute('alt')).eql('just an image!') | ||||
| }) | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue