// @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: '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'); 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); }); });