Files
portal/app/tests/Frontend/SalesPerformance.spec.ts
T
Дмитрий 709478230f feat(sales): результативность менеджеров (фронт)
Task 6.2b — SalesPerformanceView (начальник, #page-performance) поверх
GET /api/sales/managers/performance.

- Поиск по имени (debounce) + таблица: менеджер (имя/email), клиентов,
  активных, лидов пришло, оборот, выплачено, заработал, статус
  (Активен/Отпуск). HelpHint на Оборот/Заработал. Период из salesPeriod store.
- Роут /sales/performance со заглушки на экран.

Vitest 5/5, фронт-набор 1083 без регрессий, ESLint чист.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:23:46 +03:00

133 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 SalesPerformanceView from '../../resources/js/views/sales/SalesPerformanceView.vue';
// ─── mock getSalesManagersPerformance ─────────────────────────────────────────
const perfMock = vi.fn().mockResolvedValue([
{
manager_id: 1,
name: 'Алексей Смирнов',
email: 'a.smirnov@liderra.ru',
clients_count: 7,
active_clients: 5,
leads_delivered: 1567,
oborot_rub: 235050,
paid_all_time_rub: 85000,
earned_rub: 13350,
status: 'active',
},
{
manager_id: 3,
name: 'Игорь Лебедев',
email: 'i.lebedev@liderra.ru',
clients_count: 4,
active_clients: 3,
leads_delivered: 724,
oborot_rub: 108600,
paid_all_time_rub: 40000,
earned_rub: 10000,
status: 'vacation',
},
]);
vi.mock('../../resources/js/api/sales', () => ({
getSalesManagersPerformance: (...args: unknown[]) => perfMock(...args),
}));
beforeEach(() => {
perfMock.mockClear();
});
// ─── helpers ──────────────────────────────────────────────────────────────────
function makeRouter() {
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/sales/performance', name: 'sales-performance', component: SalesPerformanceView },
{ path: '/sales', name: 'sales-overview', component: { template: '<div/>' } },
],
});
return router.push('/sales/performance').then(() => router);
}
async function mountView() {
const router = await makeRouter();
const wrapper = mount(SalesPerformanceView, {
global: { plugins: [createVuetify(), createPinia(), router] },
});
await flushPromises();
return wrapper;
}
describe('SalesPerformanceView', () => {
it('отрисовывает заголовок и колонки', async () => {
const wrapper = await mountView();
const text = wrapper.text();
expect(text).toContain('Результативность менеджеров');
expect(text).toContain('Менеджер');
expect(text).toContain('Клиентов');
expect(text).toContain('Активных');
expect(text).toContain('Лидов пришло');
expect(text).toContain('Оборот клиентов');
expect(text).toContain('Выплачено');
expect(text).toContain('Заработал');
expect(text).toContain('Статус');
});
it('вызывает getSalesManagersPerformance при монтировании', async () => {
await mountView();
expect(perfMock).toHaveBeenCalled();
});
it('отрисовывает строки менеджеров из мока (имя + email)', async () => {
const wrapper = await mountView();
const rows = wrapper.findAll('[data-testid="perf-row"]');
expect(rows.length).toBe(2);
const text = wrapper.text();
expect(text).toContain('Алексей Смирнов');
expect(text).toContain('a.smirnov@liderra.ru');
expect(text).toContain('Игорь Лебедев');
expect(text).toContain('i.lebedev@liderra.ru');
});
it('статус active → чип «Активен», vacation → чип «Отпуск»', async () => {
const wrapper = await mountView();
const text = wrapper.text();
expect(text).toContain('Активен');
expect(text).toContain('Отпуск');
});
it('ввод в поиск вызывает getSalesManagersPerformance с параметром search (после debounce)', async () => {
vi.useFakeTimers();
try {
const router = await makeRouter();
const wrapper = mount(SalesPerformanceView, {
global: { plugins: [createVuetify(), createPinia(), router] },
});
// initial onMounted load
await vi.runAllTimersAsync();
await flushPromises();
perfMock.mockClear();
const input = wrapper.find('[data-testid="search-input"] input');
await input.setValue('Мария');
// Ждём завершения debounce.
await vi.runAllTimersAsync();
await flushPromises();
expect(perfMock).toHaveBeenCalled();
const lastCall = perfMock.mock.calls[perfMock.mock.calls.length - 1];
expect(lastCall[0]).toMatchObject({ search: 'Мария' });
} finally {
vi.useRealTimers();
}
});
});