diff --git a/src/app/components/notifications/NotificationBell.tsx b/src/app/components/notifications/NotificationBell.tsx index 4e641a56..191cb193 100644 --- a/src/app/components/notifications/NotificationBell.tsx +++ b/src/app/components/notifications/NotificationBell.tsx @@ -6,8 +6,7 @@ import { useNotificationStore } from '@/app/store/notificationStore'; import NotificationCenter from '@/app/components/notifications/NotificationCenter'; export default function NotificationBell() { - const { notifications } = useNotificationStore(); - const unread = notifications.filter((n) => !n.read).length; + const unreadCount = useNotificationStore((s) => s.notifications.filter((n) => !n.read).length); const [open, setOpen] = useState(false); const rootRef = useRef(null); @@ -38,9 +37,9 @@ export default function NotificationBell() { className="relative inline-flex items-center justify-center w-9 h-9 rounded-full border bg-white hover:bg-gray-50" > - {unread > 0 && ( + {unreadCount > 0 && ( - {unread} + {unreadCount} )} diff --git a/src/app/components/notifications/__tests__/NotificationBell.test.tsx b/src/app/components/notifications/__tests__/NotificationBell.test.tsx new file mode 100644 index 00000000..120dd73c --- /dev/null +++ b/src/app/components/notifications/__tests__/NotificationBell.test.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { render, act } from '@testing-library/react'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import NotificationBell from '../NotificationBell'; +import { useNotificationStore } from '@/app/store/notificationStore'; +import { Bell } from 'lucide-react'; + +// Mock lucide-react to spy on Bell rendering +vi.mock('lucide-react', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + Bell: vi.fn((props) => ), + }; +}); + +describe('NotificationBell', () => { + beforeEach(() => { + useNotificationStore.setState({ notifications: [] }); + vi.clearAllMocks(); + }); + + it('renders correctly and has 0 unread initially', () => { + const { queryByText } = render(); + expect(queryByText('1')).toBeNull(); + }); + + it('renders only once (no extra re-render) when a read notification is added', () => { + const { queryByText } = render(); + + // Check initial render count of Bell + expect(Bell).toHaveBeenCalledTimes(1); + + // Add a read notification to the store + act(() => { + useNotificationStore.getState().addNotification({ + id: '1', + message: 'Read notification', + type: 'info', + read: true, + title: 'Info', + }); + }); + + // The unread count badge should not be present + expect(queryByText('1')).toBeNull(); + + // Since the notification was already read, the unread count did not change + // Therefore, the NotificationBell should NOT have re-rendered + expect(Bell).toHaveBeenCalledTimes(1); + }); + + it('re-renders when an unread notification is added (unread count changes)', () => { + const { getByText } = render(); + expect(Bell).toHaveBeenCalledTimes(1); + + // Add an unread notification to the store + act(() => { + useNotificationStore.getState().addNotification({ + id: '2', + message: 'Unread notification', + type: 'info', + read: false, + title: 'Info', + }); + }); + + // The unread count badge should display '1' + expect(getByText('1')).toBeInTheDocument(); + + // Since the unread count changed, the component should re-render + expect(Bell).toHaveBeenCalledTimes(2); + }); +});