fix incoming statuses, add tests
This commit is contained in:
		
							parent
							
								
									8b21505089
								
							
						
					
					
						commit
						b3263e528f
					
				
					 9 changed files with 98 additions and 24 deletions
				
			
		|  | @ -4,6 +4,7 @@ import { getTimeline } from '../_api/timelines' | ||||||
| import { toast } from '../_utils/toast' | import { toast } from '../_utils/toast' | ||||||
| import { mark, stop } from '../_utils/marks' | import { mark, stop } from '../_utils/marks' | ||||||
| import { mergeArrays } from '../_utils/arrays' | import { mergeArrays } from '../_utils/arrays' | ||||||
|  | import { byItemIds } from '../_utils/sorting' | ||||||
| 
 | 
 | ||||||
| const FETCH_LIMIT = 20 | const FETCH_LIMIT = 20 | ||||||
| 
 | 
 | ||||||
|  | @ -74,39 +75,41 @@ export async function setupTimeline () { | ||||||
|   stop('setupTimeline') |   stop('setupTimeline') | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function fetchTimelineItemsOnScrollToBottom () { | export async function fetchTimelineItemsOnScrollToBottom (instanceName, timelineName) { | ||||||
|   let timelineName = store.get('currentTimeline') |  | ||||||
|   let instanceName = store.get('currentInstance') |  | ||||||
|   store.setForTimeline(instanceName, timelineName, { runningUpdate: true }) |   store.setForTimeline(instanceName, timelineName, { runningUpdate: true }) | ||||||
|   await fetchTimelineItemsAndPossiblyFallBack() |   await fetchTimelineItemsAndPossiblyFallBack() | ||||||
|   store.setForTimeline(instanceName, timelineName, { runningUpdate: false }) |   store.setForTimeline(instanceName, timelineName, { runningUpdate: false }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function showMoreItemsForCurrentTimeline () { | export async function showMoreItemsForTimeline (instanceName, timelineName) { | ||||||
|   mark('showMoreItemsForCurrentTimeline') |   mark('showMoreItemsForTimeline') | ||||||
|   let instanceName = store.get('currentInstance') |   let itemIdsToAdd = store.getForTimeline(instanceName, timelineName, 'itemIdsToAdd') | ||||||
|   let timelineName = store.get('currentTimeline') |   itemIdsToAdd = itemIdsToAdd.sort(byItemIds).reverse() | ||||||
|   let itemIdsToAdd = store.get('itemIdsToAdd') |  | ||||||
|   addTimelineItemIds(instanceName, timelineName, itemIdsToAdd) |   addTimelineItemIds(instanceName, timelineName, itemIdsToAdd) | ||||||
|   store.setForTimeline(instanceName, timelineName, { |   store.setForTimeline(instanceName, timelineName, { | ||||||
|     itemIdsToAdd: [], |     itemIdsToAdd: [], | ||||||
|     shouldShowHeader: false, |     shouldShowHeader: false, | ||||||
|     showHeader: false |     showHeader: false | ||||||
|   }) |   }) | ||||||
|   stop('showMoreItemsForCurrentTimeline') |   stop('showMoreItemsForTimeline') | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function showMoreItemsForCurrentThread () { | export async function showMoreItemsForCurrentTimeline () { | ||||||
|   mark('showMoreItemsForCurrentThread') |   return showMoreItemsForTimeline( | ||||||
|   let instanceName = store.get('currentInstance') |     store.get('currentInstance'), | ||||||
|   let timelineName = store.get('currentTimeline') |     store.get('currentTimeline') | ||||||
|   let itemIdsToAdd = store.get('itemIdsToAdd') |   ) | ||||||
|   // TODO: update database and do the thread merge correctly
 | } | ||||||
|  | 
 | ||||||
|  | export async function showMoreItemsForThread (instanceName, timelineName) { | ||||||
|  |   mark('showMoreItemsForThread') | ||||||
|  |   let itemIdsToAdd = store.getForTimeline(instanceName, timelineName, 'itemIdsToAdd') | ||||||
|   let timelineItemIds = store.getForTimeline(instanceName, timelineName, 'timelineItemIds') |   let timelineItemIds = store.getForTimeline(instanceName, timelineName, 'timelineItemIds') | ||||||
|  |   // TODO: update database and do the thread merge correctly
 | ||||||
|   timelineItemIds = timelineItemIds.concat(itemIdsToAdd) |   timelineItemIds = timelineItemIds.concat(itemIdsToAdd) | ||||||
|   store.setForTimeline(instanceName, timelineName, { |   store.setForTimeline(instanceName, timelineName, { | ||||||
|     itemIdsToAdd: [], |     itemIdsToAdd: [], | ||||||
|     timelineItemIds: timelineItemIds |     timelineItemIds: timelineItemIds | ||||||
|   }) |   }) | ||||||
|   stop('showMoreItemsForCurrentThread') |   stop('showMoreItemsForThread') | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| <div class="more-items-header" role="alert"> | <div class="more-items-header" role="alert"> | ||||||
|   <button class="primary" type="button" on:click="onClick(event)"> |   <button class="primary" type="button" on:click="onClick(event)"> | ||||||
|     Click to show {{count}} more |     Show {{count}} more | ||||||
|   </button> |   </button> | ||||||
| </div> | </div> | ||||||
| <style> | <style> | ||||||
|  |  | ||||||
|  | @ -64,11 +64,12 @@ | ||||||
|     initializeTimeline, |     initializeTimeline, | ||||||
|     fetchTimelineItemsOnScrollToBottom, |     fetchTimelineItemsOnScrollToBottom, | ||||||
|     setupTimeline, |     setupTimeline, | ||||||
|     showMoreItemsForCurrentThread |     showMoreItemsForTimeline, | ||||||
|  |     showMoreItemsForThread, | ||||||
|  |     showMoreItemsForCurrentTimeline | ||||||
|   } from '../../_actions/timeline' |   } from '../../_actions/timeline' | ||||||
|   import LoadingPage from '../LoadingPage.html' |   import LoadingPage from '../LoadingPage.html' | ||||||
|   import { focusWithCapture, blurWithCapture } from '../../_utils/events' |   import { focusWithCapture, blurWithCapture } from '../../_utils/events' | ||||||
|   import { showMoreItemsForCurrentTimeline } from '../../_actions/timeline' |  | ||||||
|   import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' |   import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' | ||||||
|   import { mark, stop } from '../../_utils/marks' |   import { mark, stop } from '../../_utils/marks' | ||||||
|   import { importPseudoVirtualList } from '../../_utils/asyncModules' |   import { importPseudoVirtualList } from '../../_utils/asyncModules' | ||||||
|  | @ -178,7 +179,10 @@ | ||||||
|             this.get('timelineType') === 'status') { // for status contexts, we've already fetched the whole thread |             this.get('timelineType') === 'status') { // for status contexts, we've already fetched the whole thread | ||||||
|           return |           return | ||||||
|         } |         } | ||||||
|         fetchTimelineItemsOnScrollToBottom() |         fetchTimelineItemsOnScrollToBottom( | ||||||
|  |           this.get('currentInstance'), | ||||||
|  |           this.get('timeline') | ||||||
|  |         ) | ||||||
|       }, |       }, | ||||||
|       onScrollToTop() { |       onScrollToTop() { | ||||||
|         if (this.store.get('shouldShowHeader')) { |         if (this.store.get('shouldShowHeader')) { | ||||||
|  | @ -202,11 +206,11 @@ | ||||||
|           let showHeader = this.store.get('showHeader') |           let showHeader = this.store.get('showHeader') | ||||||
|           if (timelineName.startsWith('status/')) { |           if (timelineName.startsWith('status/')) { | ||||||
|             // this is a thread, just insert the statuses already |             // this is a thread, just insert the statuses already | ||||||
|             showMoreItemsForCurrentThread() |             showMoreItemsForThread(instanceName, timelineName) | ||||||
|           } else if (scrollTop === 0 && !shouldShowHeader && !showHeader) { |           } else if (scrollTop === 0 && !shouldShowHeader && !showHeader) { | ||||||
|             // if the user is scrolled to the top and we're not showing the header, then |             // if the user is scrolled to the top and we're not showing the header, then | ||||||
|             // just insert the statuses. this is "chat room mode" |             // just insert the statuses. this is "chat room mode" | ||||||
|             showMoreItemsForCurrentTimeline() |             showMoreItemsForTimeline(instanceName, timelineName) | ||||||
|           } else { |           } else { | ||||||
|             // user hasn't scrolled to the top, show a header instead |             // user hasn't scrolled to the top, show a header instead | ||||||
|             this.store.setForTimeline(instanceName, timelineName, {shouldShowHeader: true}) |             this.store.setForTimeline(instanceName, timelineName, {shouldShowHeader: true}) | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { toPaddedBigInt, toReversePaddedBigInt } from './utils' | import { toPaddedBigInt, toReversePaddedBigInt } from '../_utils/sorting' | ||||||
| import { cloneForStorage } from './helpers' | import { cloneForStorage } from './helpers' | ||||||
| import { dbPromise, getDatabase } from './databaseLifecycle' | import { dbPromise, getDatabase } from './databaseLifecycle' | ||||||
| import { | import { | ||||||
|  |  | ||||||
|  | @ -2,4 +2,4 @@ export const DEFAULT_MEDIA_WIDTH = 300 | ||||||
| export const DEFAULT_MEDIA_HEIGHT = 200 | export const DEFAULT_MEDIA_HEIGHT = 200 | ||||||
| 
 | 
 | ||||||
| export const ONE_TRANSPARENT_PIXEL = | export const ONE_TRANSPARENT_PIXEL = | ||||||
|   'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' |   'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' | ||||||
|  |  | ||||||
|  | @ -12,3 +12,9 @@ export function toReversePaddedBigInt (id) { | ||||||
|   } |   } | ||||||
|   return res |   return res | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export function byItemIds (a, b) { | ||||||
|  |   let aPadded = toPaddedBigInt(a) | ||||||
|  |   let bPadded = toPaddedBigInt(b) | ||||||
|  |   return aPadded < bPadded ? -1 : aPadded === bPadded ? 0 : 1 | ||||||
|  | } | ||||||
|  | @ -1,8 +1,18 @@ | ||||||
| import { favoriteStatus } from '../routes/_api/favorite' | import { favoriteStatus } from '../routes/_api/favorite' | ||||||
| import fetch from 'node-fetch' | import fetch from 'node-fetch' | ||||||
|  | import FileApi from 'file-api' | ||||||
| import { users } from './users' | import { users } from './users' | ||||||
|  | import { postStatus } from '../routes/_api/statuses' | ||||||
|  | 
 | ||||||
| global.fetch = fetch | global.fetch = fetch | ||||||
|  | global.File = FileApi.File | ||||||
|  | global.FormData = FileApi.FormData | ||||||
| 
 | 
 | ||||||
| export async function favoriteStatusAsAdmin (statusId) { | export async function favoriteStatusAsAdmin (statusId) { | ||||||
|   return favoriteStatus('localhost:3000', users.admin.accessToken, statusId) |   return favoriteStatus('localhost:3000', users.admin.accessToken, statusId) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export async function postAsAdmin (text) { | ||||||
|  |   return postStatus('localhost:3000', users.admin.accessToken, text, | ||||||
|  |     null, null, false, null, 'public') | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										44
									
								
								tests/spec/104-streaming.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								tests/spec/104-streaming.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | ||||||
|  | import { foobarRole } from '../roles' | ||||||
|  | import { | ||||||
|  |   getFirstVisibleStatus, getNthReplyButton, getNthStatus, getUrl, homeNavButton, notificationsNavButton, | ||||||
|  |   postStatusButton, scrollContainerToTop, showMoreButton, sleep | ||||||
|  | } from '../utils' | ||||||
|  | import { postAsAdmin } from '../serverActions' | ||||||
|  | 
 | ||||||
|  | fixture`104-streaming.js` | ||||||
|  |   .page`http://localhost:4002` | ||||||
|  | 
 | ||||||
|  | test('new incoming statuses show up immediately', async t => { | ||||||
|  |   await t.useRole(foobarRole) | ||||||
|  |     .hover(getNthStatus(0)) | ||||||
|  |   await postAsAdmin('hello my baby hello my honey') | ||||||
|  |   await t.expect(getNthStatus(0).innerText).contains('hello my baby hello my honey') | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | test('new incoming toots show a button if scrolled down', async t => { | ||||||
|  |   await t.useRole(foobarRole) | ||||||
|  |     .hover(getNthStatus(0)) | ||||||
|  |     .hover(getNthStatus(2)) | ||||||
|  |     .hover(getNthStatus(4)) | ||||||
|  |   await postAsAdmin('hello my ragtime gal') | ||||||
|  |   await postAsAdmin('send me a kiss by wire') | ||||||
|  |   await sleep(4000) | ||||||
|  |   await t.hover(getNthStatus(2)) | ||||||
|  |     .hover(getNthStatus(0)) | ||||||
|  |   await scrollContainerToTop() | ||||||
|  |   await sleep(1000) | ||||||
|  |   await t | ||||||
|  |     .expect(showMoreButton.innerText).contains('Show 2 more') | ||||||
|  |   await postAsAdmin("baby my heart's on fire") | ||||||
|  |   await sleep(4000) | ||||||
|  |   await t.expect(showMoreButton.innerText).contains('Show 3 more') | ||||||
|  |     .click(showMoreButton) | ||||||
|  |   await t | ||||||
|  |     .expect(getNthStatus(0).innerText).contains("baby my heart's on fire") | ||||||
|  |     .expect(getNthStatus(1).innerText).contains('send me a kiss by wire') | ||||||
|  |     .expect(getNthStatus(2).innerText).contains('hello my ragtime gal') | ||||||
|  |     .navigateTo('/') | ||||||
|  |     .expect(getNthStatus(0).innerText).contains("baby my heart's on fire") | ||||||
|  |     .expect(getNthStatus(1).innerText).contains('send me a kiss by wire') | ||||||
|  |     .expect(getNthStatus(2).innerText).contains('hello my ragtime gal') | ||||||
|  | }) | ||||||
|  | @ -26,6 +26,7 @@ export const authorizeInput = $('button[type=submit]:not(.negative)') | ||||||
| export const logInToInstanceLink = $('a[href="/settings/instances/add"]') | export const logInToInstanceLink = $('a[href="/settings/instances/add"]') | ||||||
| export const searchInput = $('.search-input') | export const searchInput = $('.search-input') | ||||||
| export const postStatusButton = $('.compose-box-button') | export const postStatusButton = $('.compose-box-button') | ||||||
|  | export const showMoreButton = $('.more-items-header button') | ||||||
| 
 | 
 | ||||||
| export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({ | export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({ | ||||||
|   innerCount: el => parseInt(el.innerText, 10) |   innerCount: el => parseInt(el.innerText, 10) | ||||||
|  | @ -35,6 +36,8 @@ export const reblogsCountElement = $('.status-favs-reblogs:nth-child(2)').addCus | ||||||
|   innerCount: el => parseInt(el.innerText, 10) |   innerCount: el => parseInt(el.innerText, 10) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | export const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout)) | ||||||
|  | 
 | ||||||
| export const getUrl = exec(() => window.location.href) | export const getUrl = exec(() => window.location.href) | ||||||
| 
 | 
 | ||||||
| export const getActiveElementClass = exec(() => | export const getActiveElementClass = exec(() => | ||||||
|  | @ -51,6 +54,10 @@ export const getComposeSelectionStart = exec(() => composeInput().selectionStart | ||||||
|   dependencies: { composeInput } |   dependencies: { composeInput } | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | export const scrollContainerToTop = exec(() => { | ||||||
|  |   document.getElementsByClassName('container')[0].scrollTop = 0 | ||||||
|  | }) | ||||||
|  | 
 | ||||||
| export const uploadKittenImage = i => (exec(() => { | export const uploadKittenImage = i => (exec(() => { | ||||||
|   let image = images[`kitten${i}`] |   let image = images[`kitten${i}`] | ||||||
|   let blob = blobUtils.base64StringToBlob(image.data, 'image/png') |   let blob = blobUtils.base64StringToBlob(image.data, 'image/png') | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue