import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mount, flushPromises } from '@vue/test-utils'; import { createVuetify } from 'vuetify'; import { createPinia } from 'pinia'; import { createRouter, createMemoryHistory } from 'vue-router'; import SalesBossOverviewView from '../../resources/js/views/sales/SalesBossOverviewView.vue'; // ─── mock getSalesDashboardOverview ──────────────────────────────────────────── // NOTE: vi.mock is hoisted, so the factory must not reference outer variables. vi.mock('../../resources/js/api/sales', () => ({ getSalesDashboardOverview: vi.fn().mockResolvedValue({ kpi: { managers_count: 4, managers_active: 3, managers_vacation: 1, clients_count: 16, balance_sum_rub: '1248600', oborot_rub: 508950, paid_period_rub: 125000, }, alerts: { invoices_awaiting: 2, attachments_pending: 3, clients_balance_problem: 5, }, performance: [ { manager_id: 11, name: 'Алексей Смирнов', clients_count: 7, leads_delivered: 1567, oborot_rub: 235050, paid_all_time_rub: 85000, earned_rub: 13350, }, { manager_id: 12, name: 'Мария Котова', clients_count: 5, leads_delivered: 1102, oborot_rub: 165300, paid_all_time_rub: 60000, earned_rub: 54795, }, { manager_id: 13, name: 'Игорь Лебедев', clients_count: 4, leads_delivered: 724, oborot_rub: 108600, paid_all_time_rub: 40000, earned_rub: 10000, }, ], }), })); beforeEach(() => vi.clearAllMocks()); // ─── helpers ────────────────────────────────────────────────────────────────── function makeRouter() { const router = createRouter({ history: createMemoryHistory(), routes: [ { path: '/sales/boss', name: 'sales-boss', component: SalesBossOverviewView }, { path: '/sales/invoices', name: 'sales-invoices', component: { template: '
' } }, { path: '/sales/requests', name: 'sales-requests', component: { template: '
' } }, { path: '/sales/performance', name: 'sales-performance', component: { template: '
' } }, { path: '/sales/payouts', name: 'sales-payouts', component: { template: '
' } }, ], }); return router.push('/sales/boss').then(() => router); } describe('SalesBossOverviewView', () => { it('вызывает getSalesDashboardOverview при монтировании', async () => { const router = await makeRouter(); mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); const api = await import('../../resources/js/api/sales'); expect(api.getSalesDashboardOverview).toHaveBeenCalled(); }); it('отрисовывает KPI — Менеджеров с подписью активны/в отпуске', async () => { const router = await makeRouter(); const wrapper = mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); const kpi = wrapper.find('[data-testid="kpi-managers"]'); expect(kpi.text()).toContain('4'); expect(kpi.text()).toContain('3 активны'); expect(kpi.text()).toContain('1 в отпуске'); }); it('отрисовывает KPI — Клиентов закреплено', async () => { const router = await makeRouter(); const wrapper = mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); expect(wrapper.find('[data-testid="kpi-clients"]').text()).toContain('16'); }); it('отрисовывает KPI — Σ баланс клиентов', async () => { const router = await makeRouter(); const wrapper = mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); const kpi = wrapper.find('[data-testid="kpi-balance"]'); expect(kpi.text()).toContain('₽'); expect(kpi.text()).toContain('248'); }); it('отрисовывает KPI — Оборот отдела', async () => { const router = await makeRouter(); const wrapper = mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); const kpi = wrapper.find('[data-testid="kpi-oborot"]'); expect(kpi.text()).toContain('508'); expect(kpi.text()).toContain('₽'); }); it('отрисовывает KPI — Выплачено менеджерам', async () => { const router = await makeRouter(); const wrapper = mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); const kpi = wrapper.find('[data-testid="kpi-paid"]'); expect(kpi.text()).toContain('125'); expect(kpi.text()).toContain('₽'); }); it('отрисовывает 4 плитки-алерта с числами', async () => { const router = await makeRouter(); const wrapper = mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); const cards = wrapper.findAll('[data-alert-card]'); expect(cards.length).toBe(4); expect(wrapper.find('[data-testid="alert-invoices"]').text()).toContain('2'); expect(wrapper.find('[data-testid="alert-invoices"]').text()).toContain('счёта ждут оплаты'); expect(wrapper.find('[data-testid="alert-requests"]').text()).toContain('3'); expect(wrapper.find('[data-testid="alert-requests"]').text()).toContain('заявки на привязку'); expect(wrapper.find('[data-testid="alert-balance"]').text()).toContain('5'); expect(wrapper.find('[data-testid="alert-balance"]').text()).toContain('баланса'); expect(wrapper.find('[data-testid="alert-payout"]').text()).toContain('провести выплату'); }); it('клик по плитке «счета ждут оплаты» ведёт на /sales/invoices', async () => { const router = await makeRouter(); const pushSpy = vi.spyOn(router, 'push'); const wrapper = mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); await wrapper.find('[data-testid="alert-invoices"]').trigger('click'); expect(pushSpy).toHaveBeenCalledWith('/sales/invoices'); }); it('клик по плитке «заявки на привязку» ведёт на /sales/requests', async () => { const router = await makeRouter(); const pushSpy = vi.spyOn(router, 'push'); const wrapper = mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); await wrapper.find('[data-testid="alert-requests"]').trigger('click'); expect(pushSpy).toHaveBeenCalledWith('/sales/requests'); }); it('клик по плитке «проблема баланса» ведёт на /sales/performance', async () => { const router = await makeRouter(); const pushSpy = vi.spyOn(router, 'push'); const wrapper = mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); await wrapper.find('[data-testid="alert-balance"]').trigger('click'); expect(pushSpy).toHaveBeenCalledWith('/sales/performance'); }); it('клик по плитке «провести выплату» ведёт на /sales/payouts', async () => { const router = await makeRouter(); const pushSpy = vi.spyOn(router, 'push'); const wrapper = mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); await wrapper.find('[data-testid="alert-payout"]').trigger('click'); expect(pushSpy).toHaveBeenCalledWith('/sales/payouts'); }); it('таблица результативности менеджеров отрисовывает строки', async () => { const router = await makeRouter(); const wrapper = mount(SalesBossOverviewView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); const rows = wrapper.findAll('[data-testid="performance-row"]'); expect(rows.length).toBe(3); expect(rows[0].text()).toContain('Алексей Смирнов'); expect(rows[1].text()).toContain('Мария Котова'); expect(rows[2].text()).toContain('Игорь Лебедев'); // числовые ячейки первой строки expect(rows[0].text()).toContain('7'); expect(rows[0].text()).toContain('235'); expect(rows[0].text()).toContain('13'); }); });