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:
parent
55b9c8d3b8
commit
430ab4db4c
|
@ -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 })
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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, '',
|
||||||
|
|
|
@ -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.')
|
||||||
|
})
|
|
@ -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 })
|
||||||
|
})
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in New Issue