Files
portal/app/tests/Frontend/DashboardView.spec.ts
T

89 lines
3.7 KiB
TypeScript

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> = {}): 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, paid: 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);
});
});