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 SalesRequestsView from '../../resources/js/views/sales/SalesRequestsView.vue'; // ─── mock sales api ─────────────────────────────────────────────────────────── vi.mock('../../resources/js/api/sales', () => ({ listAttachmentQueue: vi.fn().mockResolvedValue({ pending: [ { id: 1, login_input: 'info@potolki-spb.ru', status: 'pending', status_label: 'На рассмотрении', tenant_id: 601, organization_name: 'Натяжные потолки СПб', manager_name: 'Алексей Смирнов', hint: 'свободен', comment: null, created_at: '2026-06-28T10:14:00Z', decided_at: null, }, { id: 2, login_input: 'info@dveri-prem.ru', status: 'pending', status_label: 'На рассмотрении', tenant_id: 602, organization_name: 'Двери Премиум', manager_name: 'Мария Котова', hint: 'уже за: А. Смирнов', comment: null, created_at: '2026-06-28T09:02:00Z', decided_at: null, }, { id: 3, login_input: 'info@unknown.ru', status: 'pending', status_label: 'На рассмотрении', tenant_id: null, organization_name: null, manager_name: 'Игорь Лебедев', hint: null, comment: null, created_at: '2026-06-28T08:00:00Z', decided_at: null, }, ], history: [ { id: 90, login_input: 'sale@kuhni-ekb.ru', status: 'approved', status_label: 'Одобрено', tenant_id: 501, organization_name: 'Кухни на заказ Екб', manager_name: 'Алексей Смирнов', comment: null, created_at: '2026-06-27T16:10:00Z', decided_at: '2026-06-27T16:12:00Z', }, ], }), decideAttachment: vi.fn().mockResolvedValue({ id: 1, login_input: 'info@potolki-spb.ru', status: 'approved', status_label: 'Одобрено', tenant_id: 601, comment: null, }), extractSalesErrorMessage: vi.fn((_e: unknown, fallback = 'Произошла ошибка. Попробуйте позже.') => fallback), })); beforeEach(() => vi.clearAllMocks()); // ─── helpers ────────────────────────────────────────────────────────────────── function makeRouter() { const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/sales/requests', name: 'sales-requests', component: SalesRequestsView }], }); return router.push('/sales/requests').then(() => router); } async function mountView() { const router = await makeRouter(); const wrapper = mount(SalesRequestsView, { global: { plugins: [createVuetify(), createPinia(), router] }, }); await flushPromises(); return wrapper; } describe('SalesRequestsView', () => { it('заголовок + счётчик = число pending', async () => { const wrapper = await mountView(); const text = wrapper.text(); expect(text).toContain('Заявки на привязку'); expect(text).toContain('3 ждут решения'); const api = await import('../../resources/js/api/sales'); expect(api.listAttachmentQueue).toHaveBeenCalled(); }); it('очередь рендерит chip по hint (свободен / уже за / не найден)', async () => { const wrapper = await mountView(); const text = wrapper.text(); expect(text).toContain('Алексей Смирнов'); expect(text).toContain('info@potolki-spb.ru'); expect(text).toContain('НАЙДЕН · свободен'); expect(text).toContain('уже за: А. Смирнов'); expect(text).toContain('не найден'); }); it('история рендерит строки', async () => { const wrapper = await mountView(); const text = wrapper.text(); expect(text).toContain('Кухни на заказ Екб'); expect(text).toContain('Одобрено'); }); it('клик «Подтвердить» на свободном → decideAttachment(id, approve)', async () => { const wrapper = await mountView(); const api = await import('../../resources/js/api/sales'); const btn = wrapper.find('[data-testid="approve-1"]'); expect(btn.exists()).toBe(true); await btn.trigger('click'); await flushPromises(); expect(api.decideAttachment).toHaveBeenCalledWith(1, 'approve', undefined); }); it('клик «Переназначить» на конфликте → decideAttachment(id, approve)', async () => { const wrapper = await mountView(); const api = await import('../../resources/js/api/sales'); const btn = wrapper.find('[data-testid="approve-2"]'); expect(btn.exists()).toBe(true); await btn.trigger('click'); await flushPromises(); expect(api.decideAttachment).toHaveBeenCalledWith(2, 'approve', undefined); }); it('клик «Отклонить» → decideAttachment(id, reject)', async () => { const wrapper = await mountView(); const api = await import('../../resources/js/api/sales'); const btn = wrapper.find('[data-testid="reject-1"]'); expect(btn.exists()).toBe(true); await btn.trigger('click'); await flushPromises(); expect(api.decideAttachment).toHaveBeenCalledWith(1, 'reject', undefined); }); it('после решения список перезагружается', async () => { const wrapper = await mountView(); const api = await import('../../resources/js/api/sales'); (api.listAttachmentQueue as ReturnType).mockClear(); await wrapper.find('[data-testid="approve-1"]').trigger('click'); await flushPromises(); expect(api.listAttachmentQueue).toHaveBeenCalled(); }); });