fix incoming statuses, add tests
This commit is contained in:
parent
8b21505089
commit
b3263e528f
|
@ -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…
Reference in a new issue