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 SalesAttachView from '../../resources/js/views/sales/SalesAttachView.vue'; // ─── mock sales api ─────────────────────────────────────────────────────────── vi.mock('../../resources/js/api/sales', () => ({ submitAttachment: vi.fn(), listMyAttachments: vi.fn().mockResolvedValue([ { id: 1, login_input: 'sale@kuhni-ekb.ru', status: 'approved', status_label: 'Одобрено', tenant_id: 501, organization_name: 'Кухни на заказ Екб', comment: null, created_at: '2026-06-27T16:02:00Z', decided_at: '2026-06-27T16:10:00Z', }, { id: 2, login_input: 'info@dveri-eco.ru', status: 'rejected', status_label: 'Отклонено', tenant_id: 502, organization_name: 'Двери Эконом', comment: 'Клиент закреплён за другим менеджером', created_at: '2026-06-26T11:48:00Z', decided_at: '2026-06-26T12:00:00Z', }, ]), extractSalesErrorMessage: vi.fn((_e: unknown, fallback = 'Произошла ошибка. Попробуйте позже.') => fallback), })); beforeEach(() => vi.clearAllMocks()); // ─── helpers ────────────────────────────────────────────────────────────────── function makeRouter() { const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/sales/attach', name: 'sales-attach', component: SalesAttachView }], }); return router.push('/sales/attach').then(() => router); } async function mountView() { const router = await makeRouter(); const wrapper = mount(SalesAttachView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); return wrapper; } describe('SalesAttachView', () => { it('рендерит заголовок и подсказку', async () => { const wrapper = await mountView(); const text = wrapper.text(); expect(text).toContain('Привязать клиента'); expect(text).toContain('Один клиент — один менеджер'); }); it('таблица «Мои заявки» рендерит строки из listMyAttachments', async () => { const wrapper = await mountView(); const text = wrapper.text(); expect(text).toContain('sale@kuhni-ekb.ru'); expect(text).toContain('info@dveri-eco.ru'); expect(text).toContain('Одобрено'); expect(text).toContain('Отклонено'); expect(text).toContain('Клиент закреплён за другим менеджером'); const api = await import('../../resources/js/api/sales'); expect(api.listMyAttachments).toHaveBeenCalled(); }); it('отправка заявки со статусом pending → зелёный баннер + перезагрузка списка', async () => { const api = await import('../../resources/js/api/sales'); (api.submitAttachment as ReturnType).mockResolvedValue({ id: 10, login_input: 'new@company.ru', status: 'pending', status_label: 'На рассмотрении', tenant_id: 777, comment: null, }); const wrapper = await mountView(); (api.listMyAttachments as ReturnType).mockClear(); await wrapper.find('[data-testid="attach-login"] input').setValue('new@company.ru'); await wrapper.find('[data-testid="attach-submit"]').trigger('click'); await flushPromises(); expect(api.submitAttachment).toHaveBeenCalledWith('new@company.ru'); const banner = wrapper.find('[data-testid="attach-banner"]'); expect(banner.exists()).toBe(true); expect(banner.text()).toContain('Заявка отправлена начальнику'); // список перезагружается после успешной отправки expect(api.listMyAttachments).toHaveBeenCalled(); }); it('отправка заявки со статусом not_found → жёлтый баннер', async () => { const api = await import('../../resources/js/api/sales'); (api.submitAttachment as ReturnType).mockResolvedValue({ id: 11, login_input: 'info@unknown.ru', status: 'not_found', status_label: 'Клиент не найден', tenant_id: null, comment: null, }); const wrapper = await mountView(); await wrapper.find('[data-testid="attach-login"] input').setValue('info@unknown.ru'); await wrapper.find('[data-testid="attach-submit"]').trigger('click'); await flushPromises(); const banner = wrapper.find('[data-testid="attach-banner"]'); expect(banner.exists()).toBe(true); expect(banner.text()).toContain('Клиент не найден'); }); it('ошибка 422 → красный баннер с текстом message', async () => { const api = await import('../../resources/js/api/sales'); (api.submitAttachment as ReturnType).mockRejectedValue(new Error('boom')); (api.extractSalesErrorMessage as ReturnType).mockReturnValue( 'Этот клиент уже закреплён за вами.', ); const wrapper = await mountView(); await wrapper.find('[data-testid="attach-login"] input').setValue('mine@company.ru'); await wrapper.find('[data-testid="attach-submit"]').trigger('click'); await flushPromises(); const banner = wrapper.find('[data-testid="attach-banner"]'); expect(banner.exists()).toBe(true); expect(banner.text()).toContain('Этот клиент уже закреплён за вами.'); }); });