// @vitest-environment happy-dom
import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('@/lib/auth/client', () => ({ authClient: { useSession: () => ({ data: { user: { id: 'reader' } } }) } }));
vi.mock('./push-client', () => ({ useSitePushDisabled: () => false }));
import { NotificationBell, NotificationProvider } from './notification-bell';
let container: HTMLDivElement;
let root: Root;
beforeEach(() => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
vi.stubGlobal('EventSource', class { addEventListener() {} close() {} });
vi.spyOn(HTMLImageElement.prototype, 'complete', 'get').mockReturnValue(true);
vi.spyOn(HTMLImageElement.prototype, 'naturalWidth', 'get').mockReturnValue(64);
const item = { body: 'A comment', readAt: '2026-10-12T00:00:00Z', authorName: 'Gunshin - dev', authorImage: 'https://profile.example/author.png', imageUrl: null };
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, json: async () => ({ unread: 0, nextCursor: null, items: [
{ ...item, id: 'comment', title: 'Gunshin - dev แสดงความคิดเห็นใหม่ในไกด์ Lohen', url: '/admin/comments?comment=comment' },
{ ...item, id: 'reply', title: 'Reader ตอบกลับความคิดเห็นของคุณ', url: '/lohen/comment?reply=reply', authorName: 'Reader', authorImage: null },
{ ...item, id: 'heart', title: 'คุณได้รับหัวใจจากแอดมิน', url: '/lohen/comment#comment-hearted', authorName: 'Admin', authorImage: 'https://profile.example/admin.png' },
{ ...item, id: 'commission', title: 'Commission message', url: '/commission/tickets/test', authorName: null, authorImage: null },
{ ...item, id: 'guide', title: 'มีไกด์ใหม่แล้ว!', body: 'คุณสามารถดูไกด์ Lohen ได้แล้ว!', url: '/lohen', authorName: null, authorImage: null, imageUrl: 'https://buzz-cdn.astrxl.dev/guides/lohen.png' },
] }) }));
container = document.createElement('div');
document.body.append(container);
root = createRoot(container);
});
afterEach(async () => {
await act(async () => root.unmount());
container.remove();
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
describe('comment profile pictures in the notification bell', () => {
it.each([false, true])('shows the avatar or its initials when the image is broken: %s', async (broken) => {
if (broken) vi.spyOn(HTMLImageElement.prototype, 'naturalWidth', 'get').mockReturnValue(0);
await act(async () => root.render());
await act(async () => container.querySelector('button[aria-label="การแจ้งเตือน"]')!.click());
const comment = document.querySelector('a[href="/admin/comments?comment=comment"]');
expect(comment).not.toBeNull();
expect(comment?.textContent).toContain('แสดงความคิดเห็นใหม่ในไกด์ Lohen');
if (broken) expect(comment?.querySelector('[data-slot="avatar-fallback"]')?.textContent).toBe('Gu');
else expect(comment?.querySelector('img')?.getAttribute('src')).toBe('https://profile.example/author.png');
expect(document.querySelector('a[href="/lohen/comment?reply=reply"] [data-slot="avatar-fallback"]')?.textContent).toBe('Re');
const heart = document.querySelector('a[href="/lohen/comment#comment-hearted"]');
if (broken) expect(heart?.querySelector('[data-slot="avatar-fallback"]')?.textContent).toBe('Ad');
else expect(heart?.querySelector('img')?.getAttribute('src')).toBe('https://profile.example/admin.png');
expect(document.querySelector('a[href="/commission/tickets/test"] [data-slot="avatar"]')).toBeNull();
const guide = document.querySelector('a[href="/lohen"]');
expect(guide?.querySelector('[data-slot="avatar"]')).toBeNull();
const overview = guide?.querySelector('img');
expect(overview?.getAttribute('width')).toBe('64');
expect(overview?.getAttribute('height')).toBe('64');
expect(overview?.classList.contains('rounded-full')).toBe(false);
expect(overview?.getAttribute('src')).toContain('lohen.png');
expect(guide?.firstElementChild).toBe(overview);
});
});