62 lines
4.3 KiB
TypeScript
62 lines
4.3 KiB
TypeScript
// @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(<NotificationProvider><NotificationBell /></NotificationProvider>));
|
|
await act(async () => container.querySelector<HTMLButtonElement>('button[aria-label="การแจ้งเตือน"]')!.click());
|
|
const comment = document.querySelector<HTMLAnchorElement>('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);
|
|
});
|
|
});
|