From 5b124d44692d31746961c29d11a7e0b830073954 Mon Sep 17 00:00:00 2001 From: Tommaso Casaburi Date: Sun, 19 Apr 2026 15:33:33 +0700 Subject: [PATCH] fix(board): wait for feed before empty state --- src/views/board/__tests__/board.test.tsx | 51 +++++++++++++++++++++++- src/views/board/board.tsx | 16 ++++++-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/views/board/__tests__/board.test.tsx b/src/views/board/__tests__/board.test.tsx index 007dbd83..6d617885 100644 --- a/src/views/board/__tests__/board.test.tsx +++ b/src/views/board/__tests__/board.test.tsx @@ -34,7 +34,8 @@ const testState = vi.hoisted(() => ({ } as Record }>, feed: [] as TestComment[], feedOptionsCalls: [] as Array<{ communitiesLength?: number; newerThan?: number; postsPerPage?: number; sortType?: string }>, - feedStateString: 'syncing', + feedState: undefined as string | undefined, + feedStateString: 'syncing' as string | undefined, filteredDirectoryAddresses: ['music-posting.eth'] as string[], hasMore: false, respectPostsPerPageForNewerThan: new Set(), @@ -136,6 +137,7 @@ vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({ return { feed: getScopedFeed(options), hasMore: testState.hasMore, + state: testState.feedState ?? (testState.hasMore ? 'fetching-ipns' : 'succeeded'), expandTimeWindow: testState.expandTimeWindowMock, loadMore: testState.loadMoreMock, reset: testState.resetMock, @@ -326,6 +328,7 @@ describe('Board', () => { }; testState.feed = []; testState.feedOptionsCalls = []; + testState.feedState = undefined; testState.feedStateString = 'syncing'; testState.filteredDirectoryAddresses = ['music-posting.eth']; testState.hasMore = false; @@ -642,4 +645,50 @@ describe('Board', () => { expect(container.querySelector('[data-testid="error-display"]')?.textContent).toBe('board failed'); expect(container.textContent).toContain('failed'); }); + + it('does not show no threads while a board is still loading', async () => { + testState.feedStateString = undefined; + testState.community = { + error: undefined, + shortAddress: 'music-posting.eth', + state: 'fetching-community-ipfs', + title: '/mu/ - Music', + }; + + await renderBoard({ initialEntry: '/mu', routePath: '/:boardIdentifier/*' }); + + expect(container.textContent).not.toContain('no_threads'); + expect(container.querySelector('[data-testid="loading-ellipsis"]')?.textContent).toBe('loading_feed'); + }); + + it('does not show no threads while an empty board feed is still loading', async () => { + testState.feedStateString = undefined; + testState.hasMore = true; + testState.community = { + error: undefined, + shortAddress: 'music-posting.eth', + state: 'succeeded', + title: '/mu/ - Music', + }; + + await renderBoard({ initialEntry: '/mu', routePath: '/:boardIdentifier/*' }); + + expect(container.textContent).not.toContain('no_threads'); + expect(container.querySelector('[data-testid="loading-ellipsis"]')?.textContent).toBe('loading_feed'); + }); + + it('shows no threads after an empty board feed finishes loading', async () => { + testState.feedStateString = undefined; + testState.community = { + error: undefined, + shortAddress: 'music-posting.eth', + state: 'succeeded', + title: '/mu/ - Music', + }; + + await renderBoard({ initialEntry: '/mu', routePath: '/:boardIdentifier/*' }); + + expect(container.textContent).toContain('no_threads'); + expect(container.querySelector('[data-testid="loading-ellipsis"]')).toBeNull(); + }); }); diff --git a/src/views/board/board.tsx b/src/views/board/board.tsx index 5f57ef0c..8e244430 100644 --- a/src/views/board/board.tsx +++ b/src/views/board/board.tsx @@ -48,7 +48,9 @@ const BOARD_SORT_TYPE = 'active' as const; interface BoardFooterProps { communityAddresses: string[]; hasMore: boolean; + feedState: string | undefined; combinedFeedLength: number; + isSingleCommunityBoard: boolean; isInSubscriptionsView: boolean; isInModView: boolean; currentTimeFilterName: string; @@ -69,7 +71,9 @@ interface BoardFooterProps { const BoardFooter = ({ communityAddresses, hasMore, + feedState, combinedFeedLength, + isSingleCommunityBoard, isInSubscriptionsView, isInModView, currentTimeFilterName, @@ -85,6 +89,9 @@ const BoardFooter = ({ const { t } = useTranslation(); const loadingStateString = useFeedStateString(communityAddresses) || (combinedFeedLength === 0 ? t('loading_feed') : t('looking_for_more_posts')); + const isLoadedCommunityState = communityState === 'succeeded' || communityState === 'ready'; + const canShowNoThreads = !isSingleCommunityBoard || (isLoadedCommunityState && feedState === 'succeeded'); + const isEmptyBoardLoading = isSingleCommunityBoard && combinedFeedLength === 0 && !canShowNoThreads && communityState !== 'failed'; let footerContent; if (moreThreadsSuggestion && moreThreadsSuggestionPathname) { @@ -112,7 +119,7 @@ const BoardFooter = ({ /> ); - } else if (combinedFeedLength === 0) { + } else if (combinedFeedLength === 0 && canShowNoThreads) { footerContent = t('no_threads'); } if (communityAddresses && communityAddresses.length === 0) { @@ -129,7 +136,7 @@ const BoardFooter = ({ ) : isInModView && accountCommunityAddressesLength === 0 ? ( {t('not_mod_of_any_board')} ) : ( - showLoadingEllipsis && hasMore && + showLoadingEllipsis && (hasMore || isEmptyBoardLoading) && )} @@ -213,7 +220,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t [communities, effectiveInfiniteScroll, infiniteFeedPostsPerPage, paginationFeedPostsPerPage, excludeArchivedFilter, multiboardTimeFilterSeconds], ); - const { feed, hasMore, loadMore, reset, expandTimeWindow } = useFeed(feedOptions); + const { feed, hasMore, loadMore, reset, expandTimeWindow, state: feedState } = useFeed(feedOptions); const { currentTimeFilterName, currentTimeFilterSeconds, expandSuggestionTimeWindow } = useExpandedTimeFilter({ timeFilterName, timeFilterSeconds: multiboardTimeFilterSeconds, @@ -422,7 +429,9 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t