import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mount, flushPromises } from '@vue/test-utils'; import { createVuetify } from 'vuetify'; import { createPinia, setActivePinia } from 'pinia'; import DashboardView from '../../resources/js/views/DashboardView.vue'; import type { DashboardSummary } from '../../resources/js/api/dashboard'; import { useAuthStore } from '../../resources/js/stores/auth'; import type { AuthUser } from '../../resources/js/api/auth'; vi.mock('../../resources/js/api/dashboard', () => ({ getDashboardSummary: vi.fn(), })); const mockUser: AuthUser = { id: 1, email: 'user@liderra.ru', first_name: 'Иван', last_name: 'Петров', tenant_id: 1, totp_enabled: false, last_login_at: null, }; const dashboardApi = await import('../../resources/js/api/dashboard'); function makeSummary(overrides: Partial = {}): DashboardSummary { return { range: '7d', leads_received: { value: 247, delta_pct: 12.3, delta_dir: 'up' }, conversion: { value: 18.4, delta_pp: 2.1, delta_dir: 'up' }, active_projects: { active: 8, limit: 10 }, balance: { amount_rub: '14250.00', runway_days: 4, runway_leads: 285 }, activity: { points: [3, 5, 2, 8, 6, 9, 4], labels: ['сб', 'вс', 'пн', 'вт', 'ср', 'чт', 'сегодня'], max: 10 }, funnel: { new: 18, won: 45 }, ...overrides, }; } const mountView = () => { setActivePinia(createPinia()); useAuthStore().user = mockUser; return mount(DashboardView, { global: { plugins: [createVuetify()] } }); }; beforeEach(() => vi.clearAllMocks()); describe('DashboardView.vue ↔ /api/dashboard/summary', () => { it('getDashboardSummary вызывается на mount', async () => { vi.mocked(dashboardApi.getDashboardSummary).mockResolvedValueOnce(makeSummary()); mountView(); await flushPromises(); expect(dashboardApi.getDashboardSummary).toHaveBeenCalledTimes(1); }); it('успех — KPI и баланс из API видны', async () => { vi.mocked(dashboardApi.getDashboardSummary).mockResolvedValueOnce( makeSummary({ balance: { amount_rub: '99000.00', runway_days: 9, runway_leads: 500 } }), ); const wrapper = mountView(); await flushPromises(); const text = wrapper.text(); expect(text).toContain('Получено лидов'); expect(text).toContain('Конверсия в оплату'); expect(text).toContain('Активные проекты'); expect(text).toContain('Баланс'); expect(text).toContain('99 000'); expect(wrapper.text()).toContain('12.3%'); }); it('ошибка API — fallback на mock, view не падает', async () => { vi.mocked(dashboardApi.getDashboardSummary).mockRejectedValueOnce(new Error('500')); const wrapper = mountView(); await flushPromises(); expect(wrapper.text()).toContain('Получено лидов'); expect(wrapper.find('.runway-fill').exists()).toBe(true); expect(wrapper.find('[data-testid="dashboard-fetch-error"]').exists()).toBe(true); }); it('смена range перезапрашивает summary', async () => { vi.mocked(dashboardApi.getDashboardSummary).mockResolvedValue(makeSummary()); const wrapper = mountView(); await flushPromises(); expect(dashboardApi.getDashboardSummary).toHaveBeenCalledTimes(1); (wrapper.vm as unknown as { range: string }).range = '30d'; await flushPromises(); expect(dashboardApi.getDashboardSummary).toHaveBeenCalledTimes(2); }); });