Files
portal/app/tests/Frontend/SalesManagers.spec.ts
T
Дмитрий 56ffdef079 feat(sales): экран «Менеджеры отдела» (фронт)
Task 7.1b — SalesManagersView (начальник, #page-managers) поверх
GET/POST /api/sales/managers.

- Форма «Новый менеджер» (имя, e-mail, пароль, роль) → POST → snackbar +
  очистка + перезагрузка; ошибки (дубль email) через extractSalesErrorMessage.
- Список менеджеров: роль-чип, клиентов, выплачено всего, статус
  (Активен/Отпуск).
- api/sales.ts: listSalesManagers/createSalesManager. Роут /sales/managers
  со заглушки на экран.

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

Фаза 7 завершена: начальник создаёт менеджеров (логин+пароль) и видит
список отдела.

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

157 lines
6.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 SalesManagersView from '../../resources/js/views/sales/SalesManagersView.vue';
// ─── mock API (listSalesManagers / createSalesManager) ────────────────────────
vi.mock('../../resources/js/api/sales', async () => {
const actual = await vi.importActual<typeof import('../../resources/js/api/sales')>('../../resources/js/api/sales');
return {
...actual,
listSalesManagers: vi.fn().mockResolvedValue([
{
id: 1,
name: 'Виктор Орлов',
email: 'v.orlov@liderra.ru',
role: 'head',
is_active: true,
clients_count: 0,
paid_all_time_rub: 0,
},
{
id: 2,
name: 'Алексей Смирнов',
email: 'a.smirnov@liderra.ru',
role: 'manager',
is_active: true,
clients_count: 7,
paid_all_time_rub: 85000,
},
{
id: 3,
name: 'Игорь Лебедев',
email: 'i.lebedev@liderra.ru',
role: 'manager',
is_active: false,
clients_count: 4,
paid_all_time_rub: 40000,
},
]),
createSalesManager: vi.fn().mockResolvedValue({
id: 4,
name: 'Иван Иванов',
email: 'i.ivanov@liderra.ru',
role: 'manager',
is_active: true,
}),
};
});
beforeEach(() => vi.clearAllMocks());
// ─── helpers ──────────────────────────────────────────────────────────────────
function makeRouter() {
const router = createRouter({
history: createMemoryHistory(),
routes: [{ path: '/sales/managers', name: 'sales-managers', component: SalesManagersView }],
});
return router.push('/sales/managers').then(() => router);
}
async function mountView() {
const router = await makeRouter();
const wrapper = mount(SalesManagersView, {
global: { plugins: [createVuetify(), createPinia(), router] },
});
await flushPromises();
return wrapper;
}
describe('SalesManagersView', () => {
it('вызывает listSalesManagers при монтировании', async () => {
await mountView();
const api = await import('../../resources/js/api/sales');
expect(api.listSalesManagers).toHaveBeenCalled();
});
it('отрисовывает строки менеджеров из мока', async () => {
const wrapper = await mountView();
const rows = wrapper.findAll('[data-testid="manager-row"]');
expect(rows.length).toBe(3);
const text = wrapper.text();
expect(text).toContain('Виктор Орлов');
expect(text).toContain('v.orlov@liderra.ru');
expect(text).toContain('Алексей Смирнов');
expect(text).toContain('Игорь Лебедев');
});
it('роль head → «Начальник», manager → «Менеджер»', async () => {
const wrapper = await mountView();
const text = wrapper.text();
expect(text).toContain('Начальник');
expect(text).toContain('Менеджер');
});
it('статус is_active → «Активен», иначе → «Отпуск»', async () => {
const wrapper = await mountView();
const text = wrapper.text();
expect(text).toContain('Активен');
expect(text).toContain('Отпуск');
});
it('«Создать менеджера» вызывает createSalesManager с верным payload и перезагружает список', async () => {
const wrapper = await mountView();
const api = await import('../../resources/js/api/sales');
wrapper.vm.form.name = 'Иван Иванов';
wrapper.vm.form.email = 'i.ivanov@liderra.ru';
wrapper.vm.form.password = 'secret123';
wrapper.vm.form.role = 'manager';
(api.listSalesManagers as ReturnType<typeof vi.fn>).mockClear();
await wrapper.vm.submit();
await flushPromises();
expect(api.createSalesManager).toHaveBeenCalledWith({
name: 'Иван Иванов',
email: 'i.ivanov@liderra.ru',
password: 'secret123',
role: 'manager',
});
// список перезагружен после успешного создания
expect(api.listSalesManagers).toHaveBeenCalled();
// форма очищена
expect(wrapper.vm.form.name).toBe('');
expect(wrapper.vm.form.email).toBe('');
expect(wrapper.vm.form.password).toBe('');
});
it('ошибка создания (422) показывается пользователю', async () => {
const wrapper = await mountView();
const api = await import('../../resources/js/api/sales');
(api.createSalesManager as ReturnType<typeof vi.fn>).mockRejectedValueOnce({
isAxiosError: true,
response: { status: 422, data: { message: 'E-mail уже занят.' } },
});
wrapper.vm.form.name = 'Дубль';
wrapper.vm.form.email = 'v.orlov@liderra.ru';
wrapper.vm.form.password = 'secret123';
wrapper.vm.form.role = 'manager';
await wrapper.vm.submit();
await flushPromises();
expect(wrapper.vm.snackbarText).toContain('E-mail уже занят');
// форма НЕ очищена при ошибке
expect(wrapper.vm.form.name).toBe('Дубль');
});
});