fix: empty timelines no longer show infinite loading spinner (#1854)

Instead, they now show "Nothing to show." I only fixed this for VirtualList because List should never be non-empty (threads).

Fixes #1763
This commit is contained in:
Nolan Lawson 2020-08-30 18:08:55 -07:00 committed by GitHub
parent 55b9c8d3b8
commit 430ab4db4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 72 additions and 4 deletions

View file

@ -104,9 +104,9 @@ async function addPagedTimelineItems (instanceName, timelineName, items) {
} }
export async function addPagedTimelineItemSummaries (instanceName, timelineName, newSummaries) { export async function addPagedTimelineItemSummaries (instanceName, timelineName, newSummaries) {
const oldSummaries = store.getForTimeline(instanceName, timelineName, 'timelineItemSummaries') || [] const oldSummaries = store.getForTimeline(instanceName, timelineName, 'timelineItemSummaries')
const mergedSummaries = uniqBy(concat(oldSummaries, newSummaries), byId) const mergedSummaries = uniqBy(concat(oldSummaries || [], newSummaries), byId)
if (!isEqual(oldSummaries, mergedSummaries)) { if (!isEqual(oldSummaries, mergedSummaries)) {
store.setForTimeline(instanceName, timelineName, { timelineItemSummaries: mergedSummaries }) store.setForTimeline(instanceName, timelineName, { timelineItemSummaries: mergedSummaries })
@ -160,10 +160,10 @@ async function addTimelineItems (instanceName, timelineName, items, stale) {
} }
export async function addTimelineItemSummaries (instanceName, timelineName, newSummaries, newStale) { export async function addTimelineItemSummaries (instanceName, timelineName, newSummaries, newStale) {
const oldSummaries = store.getForTimeline(instanceName, timelineName, 'timelineItemSummaries') || [] const oldSummaries = store.getForTimeline(instanceName, timelineName, 'timelineItemSummaries')
const oldStale = store.getForTimeline(instanceName, timelineName, 'timelineItemSummariesAreStale') const oldStale = store.getForTimeline(instanceName, timelineName, 'timelineItemSummariesAreStale')
const mergedSummaries = uniqBy(mergeArrays(oldSummaries, newSummaries, compareTimelineItemSummaries), byId) const mergedSummaries = uniqBy(mergeArrays(oldSummaries || [], newSummaries, compareTimelineItemSummaries), byId)
if (!isEqual(oldSummaries, mergedSummaries)) { if (!isEqual(oldSummaries, mergedSummaries)) {
store.setForTimeline(instanceName, timelineName, { timelineItemSummaries: mergedSummaries }) store.setForTimeline(instanceName, timelineName, { timelineItemSummaries: mergedSummaries })

View file

@ -12,6 +12,11 @@
index={visibleItem.index} index={visibleItem.index}
/> />
{/each} {/each}
{#if !$visibleItems.length}
<div class="nothing-to-show">
Nothing to show.
</div>
{/if}
{/if} {/if}
{#if $showFooter} {#if $showFooter}
<VirtualListFooter component={footerComponent} /> <VirtualListFooter component={footerComponent} />
@ -23,6 +28,12 @@
position: relative; position: relative;
width: 100%; width: 100%;
} }
.nothing-to-show {
font-size: 1.1em;
width: 100%;
padding: 20px 0;
text-align: center;
}
</style> </style>
<script> <script>
import VirtualListContainer from './VirtualListContainer.html' import VirtualListContainer from './VirtualListContainer.html'

View file

@ -37,6 +37,12 @@ export async function postWithSpoilerAndPrivacyAs (username, text, spoiler, priv
null, null, true, spoiler, privacy) null, null, true, spoiler, privacy)
} }
export async function postStatusWithMediaAs (username, status, filename, alt, sensitive) {
const mediaResponse = await submitMedia(users[username].accessToken, filename, alt)
return postStatus(instanceName, users[username].accessToken, status,
null, [mediaResponse.id], !!sensitive, null, 'public')
}
export async function postEmptyStatusWithMediaAs (username, filename, alt, sensitive) { export async function postEmptyStatusWithMediaAs (username, filename, alt, sensitive) {
const mediaResponse = await submitMedia(users[username].accessToken, filename, alt) const mediaResponse = await submitMedia(users[username].accessToken, filename, alt)
return postStatus(instanceName, users[username].accessToken, '', return postStatus(instanceName, users[username].accessToken, '',

View file

@ -0,0 +1,28 @@
import {
communityNavButton,
getUrl, timeline
} from '../utils'
import { loginAsFoobar } from '../roles'
import { Selector as $ } from 'testcafe'
fixture`039-empty-list.js`
.page`http://localhost:4002`
test('Can show an empty list of bookmarks', async t => {
await loginAsFoobar(t)
await t
.click(communityNavButton)
.click($('a').withText('Bookmarks'))
.expect(getUrl()).contains('bookmarks')
.expect(timeline.innerText).contains('Nothing to show.')
})
test('Can show an empty list of media', async t => {
await loginAsFoobar(t)
await t
.click($('a').withText('quux'))
.expect(getUrl()).contains('accounts/3')
.click($('a').withText('Media'))
.expect(getUrl()).contains('accounts/3/media')
.expect(timeline.innerText).contains('Nothing to show.')
})

View file

@ -0,0 +1,22 @@
import {
getNthStatus, sleep,
timeline
} from '../utils'
import { loginAsFoobar } from '../roles'
import { postStatusWithMediaAs } from '../serverActions'
fixture`136-empty-list.js`
.page`http://localhost:4002`
test('An empty list can become non-empty as results stream in', async t => {
await loginAsFoobar(t)
await t
.expect(getNthStatus(1).exists).ok()
.navigateTo('/tags/sweetkitty')
.expect(timeline.innerText).contains('Nothing to show.')
await sleep(500)
await postStatusWithMediaAs('quux', 'look at this sweet kitty #sweetkitty', 'kitten2.jpg', 'hello kitty')
await t
.expect(getNthStatus(1).innerText).contains('look at this sweet kitty', { timeout: 20000 })
})

View file

@ -2,6 +2,7 @@ import { ClientFunction as exec, Selector as $ } from 'testcafe'
import * as images from './images' import * as images from './images'
import * as blobUtils from './blobUtils' import * as blobUtils from './blobUtils'
export const timeline = $('[role=feed]')
export const settingsButton = $('nav a[aria-label=Settings]') export const settingsButton = $('nav a[aria-label=Settings]')
export const instanceInput = $('#instanceInput') export const instanceInput = $('#instanceInput')
export const modalDialog = $('.modal-dialog') export const modalDialog = $('.modal-dialog')