diff --git a/src/routes/(main)/home/_layout/Header/components/InboxButton.tsx b/src/routes/(main)/home/_layout/Header/components/InboxButton.tsx index 5bd68d0bdc..43432e4662 100644 --- a/src/routes/(main)/home/_layout/Header/components/InboxButton.tsx +++ b/src/routes/(main)/home/_layout/Header/components/InboxButton.tsx @@ -7,23 +7,14 @@ import { memo, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { DESKTOP_HEADER_ICON_SMALL_SIZE } from '@/const/layoutTokens'; -import { useClientDataSWR } from '@/libs/swr'; -import { notificationService } from '@/services/notification'; -import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig'; import InboxDrawer from './InboxDrawer'; -import { UNREAD_COUNT_KEY } from './InboxDrawer/constants'; +import { useInboxUnreadCount } from './useInboxUnreadCount'; const InboxButton = memo(() => { const { t } = useTranslation('notification'); const [open, setOpen] = useState(false); - const enableBusinessFeatures = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures); - - const { data: unreadCount = 0 } = useClientDataSWR( - enableBusinessFeatures ? UNREAD_COUNT_KEY : null, - () => notificationService.getUnreadCount(), - { refreshInterval: 10_000 }, - ); + const { enabled, unreadCount } = useInboxUnreadCount(); const handleToggle = useCallback(() => { setOpen((prev) => !prev); @@ -33,7 +24,7 @@ const InboxButton = memo(() => { setOpen(false); }, []); - if (!enableBusinessFeatures) return null; + if (!enabled) return null; return ( <> diff --git a/src/routes/(main)/home/_layout/Header/components/useInboxUnreadCount.test.ts b/src/routes/(main)/home/_layout/Header/components/useInboxUnreadCount.test.ts new file mode 100644 index 0000000000..dafb08b0db --- /dev/null +++ b/src/routes/(main)/home/_layout/Header/components/useInboxUnreadCount.test.ts @@ -0,0 +1,77 @@ +import { renderHook } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { UNREAD_COUNT_KEY } from './InboxDrawer/constants'; +import { INBOX_UNREAD_COUNT_REFRESH_INTERVAL, useInboxUnreadCount } from './useInboxUnreadCount'; + +const mocks = vi.hoisted(() => ({ + state: { + enableBusinessFeatures: true, + isSignedIn: false, + }, + useClientDataSWR: vi.fn(() => ({ data: undefined })), +})); + +vi.mock('@/libs/swr', () => ({ + useClientDataSWR: mocks.useClientDataSWR, +})); + +vi.mock('@/services/notification', () => ({ + notificationService: { + getUnreadCount: vi.fn(), + }, +})); + +vi.mock('@/store/serverConfig', () => ({ + serverConfigSelectors: { + enableBusinessFeatures: (state: { serverConfig: { enableBusinessFeatures: boolean } }) => + state.serverConfig.enableBusinessFeatures, + }, + useServerConfigStore: ( + selector: (state: { serverConfig: { enableBusinessFeatures: boolean } }) => boolean, + ) => selector({ serverConfig: { enableBusinessFeatures: mocks.state.enableBusinessFeatures } }), +})); + +vi.mock('@/store/user', () => ({ + useUserStore: (selector: (state: { isSignedIn: boolean }) => boolean) => + selector({ isSignedIn: mocks.state.isSignedIn }), +})); + +vi.mock('@/store/user/selectors', () => ({ + authSelectors: { + isLogin: (state: { isSignedIn: boolean }) => state.isSignedIn, + }, +})); + +beforeEach(() => { + mocks.state.enableBusinessFeatures = true; + mocks.state.isSignedIn = false; + mocks.useClientDataSWR.mockClear(); + mocks.useClientDataSWR.mockReturnValue({ data: undefined }); +}); + +describe('useInboxUnreadCount', () => { + it('does not request unread count before login', () => { + const { result } = renderHook(() => useInboxUnreadCount()); + + expect(result.current.enabled).toBe(false); + expect(mocks.useClientDataSWR).toHaveBeenCalledWith(null, expect.any(Function), { + refreshInterval: INBOX_UNREAD_COUNT_REFRESH_INTERVAL, + }); + }); + + it('requests unread count when business features are enabled and user is logged in', () => { + mocks.state.isSignedIn = true; + + const { result } = renderHook(() => useInboxUnreadCount()); + + expect(result.current.enabled).toBe(true); + expect(mocks.useClientDataSWR).toHaveBeenCalledWith(UNREAD_COUNT_KEY, expect.any(Function), { + refreshInterval: INBOX_UNREAD_COUNT_REFRESH_INTERVAL, + }); + }); + + it('keeps unread count polling on the same 10 second cadence', () => { + expect(INBOX_UNREAD_COUNT_REFRESH_INTERVAL).toBe(10_000); + }); +}); diff --git a/src/routes/(main)/home/_layout/Header/components/useInboxUnreadCount.ts b/src/routes/(main)/home/_layout/Header/components/useInboxUnreadCount.ts new file mode 100644 index 0000000000..1a1747cea1 --- /dev/null +++ b/src/routes/(main)/home/_layout/Header/components/useInboxUnreadCount.ts @@ -0,0 +1,23 @@ +import { useClientDataSWR } from '@/libs/swr'; +import { notificationService } from '@/services/notification'; +import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig'; +import { useUserStore } from '@/store/user'; +import { authSelectors } from '@/store/user/selectors'; + +import { UNREAD_COUNT_KEY } from './InboxDrawer/constants'; + +export const INBOX_UNREAD_COUNT_REFRESH_INTERVAL = 10_000; + +export const useInboxUnreadCount = () => { + const enableBusinessFeatures = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures); + const isLogin = useUserStore(authSelectors.isLogin); + const enabled = enableBusinessFeatures && isLogin === true; + + const { data: unreadCount = 0 } = useClientDataSWR( + enabled ? UNREAD_COUNT_KEY : null, + () => notificationService.getUnreadCount(), + { refreshInterval: INBOX_UNREAD_COUNT_REFRESH_INTERVAL }, + ); + + return { enabled, unreadCount }; +};