Merge branch 'codex/fix/board-empty-loading-state'

This commit is contained in:
Tommaso Casaburi
2026-04-19 15:33:45 +07:00
2 changed files with 63 additions and 4 deletions
+50 -1
View File
@@ -34,7 +34,8 @@ const testState = vi.hoisted(() => ({
} as Record<string, { address: string; features?: Record<string, unknown> }>, } as Record<string, { address: string; features?: Record<string, unknown> }>,
feed: [] as TestComment[], feed: [] as TestComment[],
feedOptionsCalls: [] as Array<{ communitiesLength?: number; newerThan?: number; postsPerPage?: number; sortType?: string }>, 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[], filteredDirectoryAddresses: ['music-posting.eth'] as string[],
hasMore: false, hasMore: false,
respectPostsPerPageForNewerThan: new Set<number>(), respectPostsPerPageForNewerThan: new Set<number>(),
@@ -136,6 +137,7 @@ vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
return { return {
feed: getScopedFeed(options), feed: getScopedFeed(options),
hasMore: testState.hasMore, hasMore: testState.hasMore,
state: testState.feedState ?? (testState.hasMore ? 'fetching-ipns' : 'succeeded'),
expandTimeWindow: testState.expandTimeWindowMock, expandTimeWindow: testState.expandTimeWindowMock,
loadMore: testState.loadMoreMock, loadMore: testState.loadMoreMock,
reset: testState.resetMock, reset: testState.resetMock,
@@ -326,6 +328,7 @@ describe('Board', () => {
}; };
testState.feed = []; testState.feed = [];
testState.feedOptionsCalls = []; testState.feedOptionsCalls = [];
testState.feedState = undefined;
testState.feedStateString = 'syncing'; testState.feedStateString = 'syncing';
testState.filteredDirectoryAddresses = ['music-posting.eth']; testState.filteredDirectoryAddresses = ['music-posting.eth'];
testState.hasMore = false; testState.hasMore = false;
@@ -642,4 +645,50 @@ describe('Board', () => {
expect(container.querySelector('[data-testid="error-display"]')?.textContent).toBe('board failed'); expect(container.querySelector('[data-testid="error-display"]')?.textContent).toBe('board failed');
expect(container.textContent).toContain('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();
});
}); });
+13 -3
View File
@@ -48,7 +48,9 @@ const BOARD_SORT_TYPE = 'active' as const;
interface BoardFooterProps { interface BoardFooterProps {
communityAddresses: string[]; communityAddresses: string[];
hasMore: boolean; hasMore: boolean;
feedState: string | undefined;
combinedFeedLength: number; combinedFeedLength: number;
isSingleCommunityBoard: boolean;
isInSubscriptionsView: boolean; isInSubscriptionsView: boolean;
isInModView: boolean; isInModView: boolean;
currentTimeFilterName: string; currentTimeFilterName: string;
@@ -69,7 +71,9 @@ interface BoardFooterProps {
const BoardFooter = ({ const BoardFooter = ({
communityAddresses, communityAddresses,
hasMore, hasMore,
feedState,
combinedFeedLength, combinedFeedLength,
isSingleCommunityBoard,
isInSubscriptionsView, isInSubscriptionsView,
isInModView, isInModView,
currentTimeFilterName, currentTimeFilterName,
@@ -85,6 +89,9 @@ const BoardFooter = ({
const { t } = useTranslation(); const { t } = useTranslation();
const loadingStateString = useFeedStateString(communityAddresses) || (combinedFeedLength === 0 ? t('loading_feed') : t('looking_for_more_posts')); 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; let footerContent;
if (moreThreadsSuggestion && moreThreadsSuggestionPathname) { if (moreThreadsSuggestion && moreThreadsSuggestionPathname) {
@@ -112,7 +119,7 @@ const BoardFooter = ({
/> />
</div> </div>
); );
} else if (combinedFeedLength === 0) { } else if (combinedFeedLength === 0 && canShowNoThreads) {
footerContent = t('no_threads'); footerContent = t('no_threads');
} }
if (communityAddresses && communityAddresses.length === 0) { if (communityAddresses && communityAddresses.length === 0) {
@@ -129,7 +136,7 @@ const BoardFooter = ({
) : isInModView && accountCommunityAddressesLength === 0 ? ( ) : isInModView && accountCommunityAddressesLength === 0 ? (
<span className='red'>{t('not_mod_of_any_board')}</span> <span className='red'>{t('not_mod_of_any_board')}</span>
) : ( ) : (
showLoadingEllipsis && hasMore && <LoadingEllipsis string={loadingStateString} /> showLoadingEllipsis && (hasMore || isEmptyBoardLoading) && <LoadingEllipsis string={loadingStateString} />
)} )}
</div> </div>
</div> </div>
@@ -213,7 +220,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
[communities, effectiveInfiniteScroll, infiniteFeedPostsPerPage, paginationFeedPostsPerPage, excludeArchivedFilter, multiboardTimeFilterSeconds], [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({ const { currentTimeFilterName, currentTimeFilterSeconds, expandSuggestionTimeWindow } = useExpandedTimeFilter({
timeFilterName, timeFilterName,
timeFilterSeconds: multiboardTimeFilterSeconds, timeFilterSeconds: multiboardTimeFilterSeconds,
@@ -422,7 +429,9 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
<BoardFooter <BoardFooter
communityAddresses={communityAddresses} communityAddresses={communityAddresses}
hasMore={hasMore} hasMore={hasMore}
feedState={feedState}
combinedFeedLength={combinedFeed.length} combinedFeedLength={combinedFeed.length}
isSingleCommunityBoard={!isInAllView && !isInSubscriptionsView && !isInModView}
isInSubscriptionsView={isInSubscriptionsView} isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView} isInModView={isInModView}
currentTimeFilterName={currentTimeFilterName} currentTimeFilterName={currentTimeFilterName}
@@ -509,6 +518,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
moreThreadsSuggestionPathname, moreThreadsSuggestionPathname,
expandSuggestionTimeWindow, expandSuggestionTimeWindow,
communityState, communityState,
feedState,
communityAddress, communityAddress,
subscriptions?.length, subscriptions?.length,
accountCommunityAddresses?.length, accountCommunityAddresses?.length,