fix incoming statuses, add tests

This commit is contained in:
Nolan Lawson 2018-03-10 10:54:16 -08:00
parent 8b21505089
commit b3263e528f
9 changed files with 98 additions and 24 deletions

View File

@ -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')
} }

View File

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

View File

@ -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})

View File

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

View File

@ -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='

View File

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

View File

@ -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')
}

View 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')
})

View File

@ -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')