import { describe, it, expect, beforeEach, vi } from 'vitest'; import { createPinia, setActivePinia } from 'pinia'; // Мокаем api/notifications до import'а store. vi.mock('../../resources/js/api/notifications', () => ({ listNotifications: vi.fn(), markNotificationRead: vi.fn(), markAllNotificationsRead: vi.fn(), deleteNotification: vi.fn(), })); vi.mock('../../resources/js/api/client', () => ({ apiClient: {}, ensureCsrfCookie: vi.fn(), extractValidationErrors: vi.fn(() => null), extractErrorMessage: vi.fn(() => 'Произошла ошибка.'), extractRateLimitRetry: vi.fn(() => null), })); import * as notificationsApi from '../../resources/js/api/notifications'; import { useNotificationsStore } from '../../resources/js/stores/notifications'; import type { ApiInAppNotification } from '../../resources/js/api/notifications'; const mockNotif = (id: number, overrides: Partial = {}): ApiInAppNotification => ({ id, event: 'new_lead', title: `Уведомление #${id}`, body: 'Тестовое тело', deal_id: 100 + id, payload: { deal_id: 100 + id, project_name: 'Caranga' }, read_at: null, created_at: new Date(2026, 4, 9, 12, id).toISOString(), ...overrides, }); describe('useNotificationsStore', () => { beforeEach(() => { setActivePinia(createPinia()); vi.clearAllMocks(); }); it('initial state: items=[], unreadCount=0, total=0', () => { const store = useNotificationsStore(); expect(store.items).toEqual([]); expect(store.unreadCount).toBe(0); expect(store.total).toBe(0); expect(store.fetchError).toBe(false); }); it('load() заполняет items + unreadCount + total из API', async () => { vi.mocked(notificationsApi.listNotifications).mockResolvedValue({ items: [mockNotif(1), mockNotif(2, { read_at: '2026-05-09T10:00:00Z' })], unread_count: 1, total: 2, }); const store = useNotificationsStore(); await store.load(); expect(store.items).toHaveLength(2); expect(store.unreadCount).toBe(1); expect(store.total).toBe(2); expect(store.fetchError).toBe(false); }); it('load() при reject ставит fetchError=true, items не меняются', async () => { vi.mocked(notificationsApi.listNotifications).mockRejectedValue(new Error('network')); const store = useNotificationsStore(); await store.load(); expect(store.fetchError).toBe(true); expect(store.items).toEqual([]); }); it('markRead() optimistic ставит read_at + уменьшает unreadCount', async () => { vi.mocked(notificationsApi.listNotifications).mockResolvedValue({ items: [mockNotif(1)], unread_count: 1, total: 1, }); vi.mocked(notificationsApi.markNotificationRead).mockResolvedValue({ id: 1, read_at: '2026-05-09T13:00:00Z', }); const store = useNotificationsStore(); await store.load(); expect(store.unreadCount).toBe(1); await store.markRead(1); expect(store.unreadCount).toBe(0); expect(store.items[0]!.read_at).toBe('2026-05-09T13:00:00Z'); }); it('markRead() при reject — revert read_at + unreadCount', async () => { vi.mocked(notificationsApi.listNotifications).mockResolvedValue({ items: [mockNotif(1)], unread_count: 1, total: 1, }); vi.mocked(notificationsApi.markNotificationRead).mockRejectedValue(new Error('500')); const store = useNotificationsStore(); await store.load(); await store.markRead(1); expect(store.unreadCount).toBe(1); expect(store.items[0]!.read_at).toBeNull(); }); it('markRead() для уже прочитанного НЕ вызывает API', async () => { vi.mocked(notificationsApi.listNotifications).mockResolvedValue({ items: [mockNotif(1, { read_at: '2026-05-09T10:00:00Z' })], unread_count: 0, total: 1, }); const store = useNotificationsStore(); await store.load(); await store.markRead(1); expect(notificationsApi.markNotificationRead).not.toHaveBeenCalled(); }); it('markAllRead() optimistic + вызывает API', async () => { vi.mocked(notificationsApi.listNotifications).mockResolvedValue({ items: [mockNotif(1), mockNotif(2)], unread_count: 2, total: 2, }); vi.mocked(notificationsApi.markAllNotificationsRead).mockResolvedValue({ updated: 2 }); const store = useNotificationsStore(); await store.load(); expect(store.unreadCount).toBe(2); await store.markAllRead(); expect(store.unreadCount).toBe(0); expect(store.items.every((n) => n.read_at !== null)).toBe(true); expect(notificationsApi.markAllNotificationsRead).toHaveBeenCalledOnce(); }); it('markAllRead() при unreadCount=0 НЕ вызывает API', async () => { const store = useNotificationsStore(); await store.markAllRead(); expect(notificationsApi.markAllNotificationsRead).not.toHaveBeenCalled(); }); it('remove() optimistic убирает из items + decrement total', async () => { vi.mocked(notificationsApi.listNotifications).mockResolvedValue({ items: [mockNotif(1), mockNotif(2)], unread_count: 2, total: 2, }); vi.mocked(notificationsApi.deleteNotification).mockResolvedValue(); const store = useNotificationsStore(); await store.load(); await store.remove(1); expect(store.items).toHaveLength(1); expect(store.items[0]!.id).toBe(2); expect(store.total).toBe(1); expect(store.unreadCount).toBe(1); }); it('remove() при reject — revert items + total + unreadCount', async () => { vi.mocked(notificationsApi.listNotifications).mockResolvedValue({ items: [mockNotif(1), mockNotif(2)], unread_count: 2, total: 2, }); vi.mocked(notificationsApi.deleteNotification).mockRejectedValue(new Error('500')); const store = useNotificationsStore(); await store.load(); await store.remove(1); expect(store.items).toHaveLength(2); expect(store.total).toBe(2); expect(store.unreadCount).toBe(2); }); it('sortedItems сортирует по created_at DESC', async () => { const earlier = mockNotif(1, { created_at: '2026-05-09T10:00:00Z' }); const later = mockNotif(2, { created_at: '2026-05-09T15:00:00Z' }); vi.mocked(notificationsApi.listNotifications).mockResolvedValue({ items: [earlier, later], unread_count: 2, total: 2, }); const store = useNotificationsStore(); await store.load(); expect(store.sortedItems[0]!.id).toBe(2); // later first expect(store.sortedItems[1]!.id).toBe(1); }); it('reset() очищает state', async () => { vi.mocked(notificationsApi.listNotifications).mockResolvedValue({ items: [mockNotif(1)], unread_count: 1, total: 1, }); const store = useNotificationsStore(); await store.load(); store.reset(); expect(store.items).toEqual([]); expect(store.unreadCount).toBe(0); expect(store.total).toBe(0); expect(store.fetchError).toBe(false); }); });