import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { setActivePinia, createPinia } from 'pinia'; import axios from 'axios'; vi.mock('axios'); import { useProjectsStore } from '../../resources/js/stores/projectsStore'; describe('projectsStore (no polling)', () => { beforeEach(() => { setActivePinia(createPinia()); vi.clearAllMocks(); }); it('fetches and stores list', async () => { (axios.get as unknown as ReturnType).mockResolvedValue({ data: { data: [{ id: 1, name: 'X' }], meta: { total: 1, current_page: 1, per_page: 20 } }, }); const store = useProjectsStore(); await store.fetch(); expect(store.items.length).toBe(1); expect(store.total).toBe(1); }); it('passes filters to API', async () => { (axios.get as unknown as ReturnType).mockResolvedValue({ data: { data: [], meta: { total: 0, current_page: 1, per_page: 20 } }, }); const store = useProjectsStore(); store.filters.signal_type = 'site'; store.filters.search = 'okna'; await store.fetch(); expect(axios.get).toHaveBeenCalledWith( '/api/projects', expect.objectContaining({ params: expect.objectContaining({ signal_type: 'site', search: 'okna' }), }), ); }); it('create dispatches POST + refetches', async () => { (axios.post as unknown as ReturnType).mockResolvedValue({ data: { data: { id: 2, name: 'New' } }, }); (axios.get as unknown as ReturnType).mockResolvedValue({ data: { data: [{ id: 2 }], meta: { total: 1, current_page: 1, per_page: 20 } }, }); const store = useProjectsStore(); await store.create({ name: 'New', signal_type: 'site', signal_identifier: 'x.ru', daily_limit_target: 10, region_mask: 0, region_mode: 'include', delivery_days_mask: 127, }); expect(axios.post).toHaveBeenCalled(); }); it('toggleSelect adds and removes ids from selectedIds', () => { const store = useProjectsStore(); store.toggleSelect(1); expect(store.selectedIds.has(1)).toBe(true); store.toggleSelect(1); expect(store.selectedIds.has(1)).toBe(false); }); it('del() calls DELETE /api/projects/{id}', async () => { const store = useProjectsStore(); vi.spyOn(axios, 'delete').mockResolvedValue({ data: null }); vi.spyOn(axios, 'get').mockResolvedValue({ data: { data: [], meta: { total: 0 } } }); await store.del(7); expect(axios.delete).toHaveBeenCalledWith('/api/projects/7'); }); it('bulkAction sends array of ids and clears selection', async () => { (axios.post as unknown as ReturnType).mockResolvedValue({ data: { updated: 2 } }); (axios.get as unknown as ReturnType).mockResolvedValue({ data: { data: [], meta: { total: 0, current_page: 1, per_page: 20 } }, }); const store = useProjectsStore(); store.selectedIds.add(1); store.selectedIds.add(2); await store.bulkAction('pause'); expect(axios.post).toHaveBeenCalledWith('/api/projects/bulk', { action: 'pause', ids: [1, 2] }); expect(store.selectedIds.size).toBe(0); }); }); describe('projectsStore (polling)', () => { beforeEach(() => { setActivePinia(createPinia()); vi.useFakeTimers(); vi.clearAllMocks(); }); afterEach(() => { vi.useRealTimers(); }); it('starts polling when pendingIds has items', async () => { (axios.get as unknown as ReturnType).mockResolvedValue({ data: { data: [ { id: 1, sync_status: 'pending', name: 'X', signal_type: 'site', signal_identifier: 'x.ru', daily_limit_target: 10, delivered_today: 0, is_active: true, region_mask: 0, region_mode: 'include', delivery_days_mask: 127, }, ], }, }); const store = useProjectsStore(); store.pendingIds.add(1); store.startPolling(); expect(axios.get).not.toHaveBeenCalled(); await vi.advanceTimersByTimeAsync(5000); expect(axios.get).toHaveBeenCalled(); }); it('stops polling when pendingIds becomes empty (sync_status ok)', async () => { (axios.get as unknown as ReturnType).mockResolvedValue({ data: { data: [ { id: 1, sync_status: 'ok', name: 'X', signal_type: 'site', signal_identifier: 'x.ru', daily_limit_target: 10, delivered_today: 0, is_active: true, region_mask: 0, region_mode: 'include', delivery_days_mask: 127, }, ], }, }); const store = useProjectsStore(); store.pendingIds.add(1); store.startPolling(); await vi.advanceTimersByTimeAsync(5000); expect(store.pendingIds.size).toBe(0); vi.clearAllMocks(); await vi.advanceTimersByTimeAsync(5000); expect(axios.get).not.toHaveBeenCalled(); }); it('exponential backoff on error', async () => { (axios.get as unknown as ReturnType).mockRejectedValue(new Error('network')); const store = useProjectsStore(); store.pendingIds.add(1); store.startPolling(); await vi.advanceTimersByTimeAsync(5000); expect(axios.get).toHaveBeenCalledTimes(1); await vi.advanceTimersByTimeAsync(10000); expect(axios.get).toHaveBeenCalledTimes(2); await vi.advanceTimersByTimeAsync(20000); expect(axios.get).toHaveBeenCalledTimes(3); }); });