ed61bae482
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
89 lines
3.7 KiB
TypeScript
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);
|
|
});
|
|
});
|