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 {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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