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 SalesIncomeView from '../../resources/js/views/sales/SalesIncomeView.vue'; // ─── mock api/sales ─────────────────────────────────────────────────────────── vi.mock('../../resources/js/api/sales', () => { const income = { totals: { oborot_rub: 235050, accrued_rub: 13350, paid_all_time_rub: 85000, to_pay_period_rub: 13350, }, per_client: [ { tenant_id: 101, organization_name: 'Кухни на заказ Екб', tariff_kind: 'topup_step', tariff_label: 'Пополнения·Станд.', topups_rub: 40000, earned_rub: 4000, }, { tenant_id: 202, organization_name: 'Натяжные потолки СПб', tariff_kind: 'topup_step', tariff_label: 'Пополнения·Агро', topups_rub: 25000, earned_rub: 1500, }, ], payouts: [ { id: 1, sales_user_id: 11, manager_name: 'Алексей Смирнов', amount_rub: 35000, paid_on: '2026-06-25', comment: 'Зарплата за июнь, часть 1', created_at: '2026-06-25T09:00:00Z', creator_name: 'Виктор Орлов', }, { id: 2, sales_user_id: 11, manager_name: 'Алексей Смирнов', amount_rub: 25000, paid_on: '2026-06-10', comment: 'Аванс', created_at: '2026-06-10T09:00:00Z', creator_name: 'Виктор Орлов', }, ], }; return { getSalesIncome: vi.fn().mockResolvedValue(income), extractSalesErrorMessage: vi.fn().mockReturnValue('Произошла ошибка. Попробуйте позже.'), }; }); beforeEach(() => vi.clearAllMocks()); // ─── helpers ────────────────────────────────────────────────────────────────── function makeRouter() { const router = createRouter({ history: createMemoryHistory(), routes: [ { path: '/sales/income', name: 'sales-income', component: SalesIncomeView }, { path: '/sales', name: 'sales-overview', component: { template: '
' } }, ], }); return router.push('/sales/income').then(() => router); } async function mountView() { const router = await makeRouter(); const wrapper = mount(SalesIncomeView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); return wrapper; } describe('SalesIncomeView', () => { it('вызывает getSalesIncome при монтировании', async () => { await mountView(); const api = await import('../../resources/js/api/sales'); expect(api.getSalesIncome).toHaveBeenCalled(); }); it('рендерит заголовок «Мой доход» и 4 KPI', async () => { const wrapper = await mountView(); const text = wrapper.text(); expect(text).toContain('Мой доход'); expect(text).toContain('Оборот моих клиентов'); expect(text).toContain('Начислено мне'); expect(text).toContain('Выплачено'); expect(text).toContain('К выплате'); // Значения (ru-RU форматирование) expect(text).toContain('235'); expect(text).toContain('13'); expect(text).toContain('85'); const tiles = wrapper.findAll('[data-testid="income-kpi"]'); expect(tiles.length).toBe(4); }); it('рендерит таблицу «Начислено по клиентам» с итогом', async () => { const wrapper = await mountView(); const rows = wrapper.findAll('[data-testid="per-client-row"]'); expect(rows.length).toBe(2); const text = wrapper.text(); expect(text).toContain('Кухни на заказ Екб'); expect(text).toContain('Натяжные потолки СПб'); expect(text).toContain('Пополнения·Станд.'); // ИТОГО earned = 4000 + 1500 = 5500 const total = wrapper.find('[data-testid="per-client-total"]'); expect(total.exists()).toBe(true); expect(total.text()).toContain('5'); }); it('рендерит журнал выплат мне с итогом', async () => { const wrapper = await mountView(); const rows = wrapper.findAll('[data-testid="income-payout-row"]'); expect(rows.length).toBe(2); const text = wrapper.text(); expect(text).toContain('Зарплата за июнь, часть 1'); expect(text).toContain('Аванс'); expect(text).toContain('Виктор Орлов'); // ИТОГО выплачено = 35000 + 25000 = 60000 const total = wrapper.find('[data-testid="income-payout-total"]'); expect(total.exists()).toBe(true); expect(total.text()).toContain('60'); }); });