diff --git a/src/views/home/__tests__/home.test.tsx b/src/views/home/__tests__/home.test.tsx index f6863d6b..7f23bca8 100644 --- a/src/views/home/__tests__/home.test.tsx +++ b/src/views/home/__tests__/home.test.tsx @@ -49,6 +49,10 @@ vi.mock('../../../hooks/use-communities-stats', () => ({ useCommunitiesStatsStore: (selector: (state: { communityStats: typeof testState.communityStats }) => unknown) => selector({ communityStats: testState.communityStats }), })); +vi.mock('../../../components/loading-ellipsis', () => ({ + default: ({ string }: { string: string }) => createElement('span', { 'data-testid': 'loading-ellipsis' }, string), +})); + vi.mock('../../../stores/use-directory-modal-store', () => ({ default: () => ({ closeDirectoryModal: testState.closeDirectoryModalMock, @@ -124,6 +128,7 @@ describe('Home', () => { expect(container.querySelector('[data-testid="boards-list"]')?.textContent).toBe('boards:2'); expect(container.querySelector('[data-testid="popular-threads-box"]')?.textContent).toBe('popular:2:2'); expect(container.querySelectorAll('[data-testid="stats-collector"]')).toHaveLength(2); + expect(container.querySelectorAll('[data-testid="loading-ellipsis"]')).toHaveLength(0); expect(container.textContent).toContain('total_posts 12'); expect(container.textContent).toContain('current_users 7'); expect(container.textContent).toContain('boards_tracked 2'); @@ -131,6 +136,35 @@ describe('Home', () => { expect(container.querySelector('a[href="/pass"]')?.textContent).toBe('support_5chan'); }); + it('keeps the stats values loading until every directory has loaded stats', () => { + testState.communityStats = { + 'music-posting.eth': { allPostCount: 5, weekActiveUserCount: 2 }, + }; + + renderHome(); + + const loadingValues = Array.from(container.querySelectorAll('[data-testid="loading-ellipsis"]')); + expect(loadingValues).toHaveLength(3); + expect(loadingValues.map((value) => value.textContent)).toEqual(['loading', 'loading', 'loading']); + expect(container.textContent).not.toContain('total_posts 5'); + expect(container.textContent).not.toContain('current_users 2'); + expect(container.textContent).not.toContain('boards_tracked 1'); + }); + + it('shows zero totals after every directory has loaded zero-count stats', () => { + testState.communityStats = { + 'music-posting.eth': { allPostCount: 0, weekActiveUserCount: 0 }, + 'tech-posting.eth': { allPostCount: 0, weekActiveUserCount: 0 }, + }; + + renderHome(); + + expect(container.querySelectorAll('[data-testid="loading-ellipsis"]')).toHaveLength(0); + expect(container.textContent).toContain('total_posts 0'); + expect(container.textContent).toContain('current_users 0'); + expect(container.textContent).toContain('boards_tracked 2'); + }); + it('navigates to the canonical board path when the search form is submitted', async () => { renderHome(); diff --git a/src/views/home/home.tsx b/src/views/home/home.tsx index b20137c7..3dda7c3f 100644 --- a/src/views/home/home.tsx +++ b/src/views/home/home.tsx @@ -7,6 +7,7 @@ import { CommunityStatsCollector, useCommunitiesStatsStore } from '../../hooks/u import PopularThreadsBox from './popular-threads-box'; import BoardsList from './boards-list'; import SiteLegalMeta from '../../components/site-legal-meta'; +import LoadingEllipsis from '../../components/loading-ellipsis'; import useDirectoryModalStore from '../../stores/use-directory-modal-store'; import DisclaimerModal from '../../components/disclaimer-modal'; import DirectoryModal from '../../components/directory-modal'; @@ -84,27 +85,42 @@ const InfoBox = () => { ); }; +interface StatValueProps { + isLoaded: boolean; + loadingLabel: string; + value: number; +} + +const StatValue = ({ isLoaded, loadingLabel, value }: StatValueProps) => (isLoaded ? <>{value} : ); + const Stats = ({ directoryAddresses }: { directoryAddresses: string[] }) => { const { t } = useTranslation(); const communitiesStats = useCommunitiesStatsStore((state) => state.communityStats); - const { totalPosts, currentUsers, boardsTracked } = useMemo(() => { + const { totalPosts, currentUsers, boardsTracked, allDirectoryStatsLoaded } = useMemo(() => { let totalPosts = 0; let currentUsers = 0; let boardsTracked = 0; + let allDirectoryStatsLoaded = directoryAddresses.length > 0; - directoryAddresses.forEach((address) => { + for (const address of directoryAddresses) { const stat = communitiesStats[address]; - if (stat) { - totalPosts += stat.allPostCount || 0; - currentUsers += stat.weekActiveUserCount || 0; - boardsTracked++; - } - }); - return { totalPosts, currentUsers, boardsTracked }; + if (!stat || stat.allPostCount === undefined) { + allDirectoryStatsLoaded = false; + continue; + } + + totalPosts += stat.allPostCount || 0; + currentUsers += stat.weekActiveUserCount || 0; + boardsTracked++; + } + + return { totalPosts, currentUsers, boardsTracked, allDirectoryStatsLoaded }; }, [communitiesStats, directoryAddresses]); + const loadingLabel = t('loading'); + return ( <> {/* Render collectors to fetch stats for each community */} @@ -117,13 +133,13 @@ const Stats = ({ directoryAddresses }: { directoryAddresses: string[] }) => {
- {t('total_posts')} {totalPosts} + {t('total_posts')}
- {t('current_users')} {currentUsers} + {t('current_users')}
- {t('boards_tracked')} {boardsTracked} + {t('boards_tracked')}