56ffdef079
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>
157 lines
6.0 KiB
TypeScript
157 lines
6.0 KiB
TypeScript
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('Дубль');
|
||
});
|
||
});
|