Skip to content

Commit aee636b

Browse files
Merge pull request #810 from barnabasolutayo-lgtm/NotificationBell
perf: optimize NotificationBell store subscription and add unit tests
2 parents 419e7b0 + 233ed0a commit aee636b

2 files changed

Lines changed: 77 additions & 4 deletions

File tree

src/app/components/notifications/NotificationBell.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import { useNotificationStore } from '@/app/store/notificationStore';
66
import NotificationCenter from '@/app/components/notifications/NotificationCenter';
77

88
export default function NotificationBell() {
9-
const { notifications } = useNotificationStore();
10-
const unread = notifications.filter((n) => !n.read).length;
9+
const unreadCount = useNotificationStore((s) => s.notifications.filter((n) => !n.read).length);
1110
const [open, setOpen] = useState(false);
1211
const rootRef = useRef<HTMLDivElement>(null);
1312

@@ -38,9 +37,9 @@ export default function NotificationBell() {
3837
className="relative inline-flex items-center justify-center w-9 h-9 rounded-full border bg-white hover:bg-gray-50"
3938
>
4039
<Bell size={18} className="text-gray-700" />
41-
{unread > 0 && (
40+
{unreadCount > 0 && (
4241
<span className="absolute -top-1 -right-1 inline-flex items-center justify-center px-1.5 py-0.5 text-[10px] leading-none rounded-full bg-red-600 text-white">
43-
{unread}
42+
{unreadCount}
4443
</span>
4544
)}
4645
</button>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React from 'react';
2+
import { render, act } from '@testing-library/react';
3+
import { describe, it, expect, beforeEach, vi } from 'vitest';
4+
import NotificationBell from '../NotificationBell';
5+
import { useNotificationStore } from '@/app/store/notificationStore';
6+
import { Bell } from 'lucide-react';
7+
8+
// Mock lucide-react to spy on Bell rendering
9+
vi.mock('lucide-react', async (importOriginal) => {
10+
const original = await importOriginal<typeof import('lucide-react')>();
11+
return {
12+
...original,
13+
Bell: vi.fn((props) => <original.Bell {...props} />),
14+
};
15+
});
16+
17+
describe('NotificationBell', () => {
18+
beforeEach(() => {
19+
useNotificationStore.setState({ notifications: [] });
20+
vi.clearAllMocks();
21+
});
22+
23+
it('renders correctly and has 0 unread initially', () => {
24+
const { queryByText } = render(<NotificationBell />);
25+
expect(queryByText('1')).toBeNull();
26+
});
27+
28+
it('renders only once (no extra re-render) when a read notification is added', () => {
29+
const { queryByText } = render(<NotificationBell />);
30+
31+
// Check initial render count of Bell
32+
expect(Bell).toHaveBeenCalledTimes(1);
33+
34+
// Add a read notification to the store
35+
act(() => {
36+
useNotificationStore.getState().addNotification({
37+
id: '1',
38+
message: 'Read notification',
39+
type: 'info',
40+
read: true,
41+
title: 'Info',
42+
});
43+
});
44+
45+
// The unread count badge should not be present
46+
expect(queryByText('1')).toBeNull();
47+
48+
// Since the notification was already read, the unread count did not change
49+
// Therefore, the NotificationBell should NOT have re-rendered
50+
expect(Bell).toHaveBeenCalledTimes(1);
51+
});
52+
53+
it('re-renders when an unread notification is added (unread count changes)', () => {
54+
const { getByText } = render(<NotificationBell />);
55+
expect(Bell).toHaveBeenCalledTimes(1);
56+
57+
// Add an unread notification to the store
58+
act(() => {
59+
useNotificationStore.getState().addNotification({
60+
id: '2',
61+
message: 'Unread notification',
62+
type: 'info',
63+
read: false,
64+
title: 'Info',
65+
});
66+
});
67+
68+
// The unread count badge should display '1'
69+
expect(getByText('1')).toBeInTheDocument();
70+
71+
// Since the unread count changed, the component should re-render
72+
expect(Bell).toHaveBeenCalledTimes(2);
73+
});
74+
});

0 commit comments

Comments
 (0)