import { describe, it, expect, beforeEach, vi } from 'vitest'; import { mount } from '@vue/test-utils'; import { setActivePinia, createPinia } from 'pinia'; import axios from 'axios'; vi.mock('axios'); vi.mock('../../resources/js/api/autopodbor'); import * as api from '../../resources/js/api/autopodbor'; import { useAutopodborStore } from '../../resources/js/stores/autopodborStore'; import FieldCompetitorScreen from '../../resources/js/views/autopodbor/screens/FieldCompetitorScreen.vue'; const makeNav = (competitorId = 10) => ({ go: vi.fn(), ctx: { competitorId, editProjectId: null, selectedSourceIds: [] }, screen: { value: 'field-competitor' }, }); // Фабрика источника с проектом const makeSource = (id: number, projectId: number, isActive: boolean) => ({ id, competitor_id: 10, signal_type: 'site' as const, identifier: `site-${id}.ru`, phone_kind: null, phone_type: null, box: 'field' as const, provenance_url: null, provenance_label: null, created_project_id: projectId, project: { id: projectId, name: `Проект ${id}`, signal_identifier: `site-${id}.ru`, is_active: isActive, paused_at: null, preflight_blocked_at: null, daily_limit_target: 5, delivered_in_month: 10, delivery_days_mask: 127, regions: [16], }, }); beforeEach(() => { setActivePinia(createPinia()); vi.clearAllMocks(); vi.mocked(axios.post).mockResolvedValue({ data: { updated: 2, skipped: [], warnings: [] } }); vi.mocked(axios.get).mockResolvedValue({ data: { data: [], meta: { total: 0 } } }); }); describe('FieldCompetitorScreen — bulk actions via /api/projects/bulk (Task 17b)', () => { it('bulkWork("pause") шлёт POST /api/projects/bulk с project_id выбранных источников', async () => { const nav = makeNav(); // Настраиваем store с двумя источниками с проектами (api.fetchCompetitor as ReturnType).mockResolvedValue({ competitor: { id: 10, name: 'Окна', is_federal: false, relevance_pct: 90, origin: 'auto', box: 'field', site_url: 'okna.ru', directory_urls: [], studied_at: '2026-01-01', study_run_id: 1, search_run_id: 1, description: '' }, sources: [makeSource(1, 101, true), makeSource(2, 102, true)], }); const store = useAutopodborStore(); await store.loadCompetitor(10); const wrapper = mount(FieldCompetitorScreen, { global: { provide: { autopodborNav: nav }, stubs: { Transition: false }, }, }); // Ждём onMounted reload await new Promise((r) => setTimeout(r, 50)); // Выбираем оба источника // eslint-disable-next-line @typescript-eslint/no-explicit-any const vm = wrapper.vm as any; vm.selected = [1, 2]; await wrapper.vm.$nextTick(); // Вызываем bulkWork('pause') напрямую через exposed await vm.bulkWork('pause'); await new Promise((r) => setTimeout(r, 30)); // Должен быть вызван POST /api/projects/bulk с ids проектов [101, 102] expect(vi.mocked(axios.post)).toHaveBeenCalledWith( '/api/projects/bulk', expect.objectContaining({ action: 'pause', ids: expect.arrayContaining([101, 102]), }), ); }); it('bulkWork("resume") шлёт POST /api/projects/bulk с project_id', async () => { const nav = makeNav(); (api.fetchCompetitor as ReturnType).mockResolvedValue({ competitor: { id: 10, name: 'Окна', is_federal: false, relevance_pct: 90, origin: 'auto', box: 'field', site_url: 'okna.ru', directory_urls: [], studied_at: '2026-01-01', study_run_id: 1, search_run_id: 1, description: '' }, sources: [makeSource(1, 101, false), makeSource(2, 102, false)], }); const store = useAutopodborStore(); await store.loadCompetitor(10); const wrapper = mount(FieldCompetitorScreen, { global: { provide: { autopodborNav: nav }, stubs: { Transition: false }, }, }); await new Promise((r) => setTimeout(r, 50)); // eslint-disable-next-line @typescript-eslint/no-explicit-any const vm = wrapper.vm as any; vm.selected = [1, 2]; await wrapper.vm.$nextTick(); await vm.bulkWork('resume'); await new Promise((r) => setTimeout(r, 30)); expect(vi.mocked(axios.post)).toHaveBeenCalledWith( '/api/projects/bulk', expect.objectContaining({ action: 'resume', ids: expect.arrayContaining([101, 102]), }), ); }); it('bulkWork пропускает источники без проекта', async () => { const nav = makeNav(); (api.fetchCompetitor as ReturnType).mockResolvedValue({ competitor: { id: 10, name: 'Окна', is_federal: false, relevance_pct: 90, origin: 'auto', box: 'field', site_url: 'okna.ru', directory_urls: [], studied_at: '2026-01-01', study_run_id: 1, search_run_id: 1, description: '' }, sources: [ makeSource(1, 101, true), // Источник без проекта { id: 2, competitor_id: 10, signal_type: 'site' as const, identifier: 'b.ru', phone_kind: null, phone_type: null, box: 'field' as const, provenance_url: null, provenance_label: null, created_project_id: null, project: null, }, ], }); const store = useAutopodborStore(); await store.loadCompetitor(10); const wrapper = mount(FieldCompetitorScreen, { global: { provide: { autopodborNav: nav }, stubs: { Transition: false }, }, }); await new Promise((r) => setTimeout(r, 50)); // eslint-disable-next-line @typescript-eslint/no-explicit-any const vm = wrapper.vm as any; vm.selected = [1, 2]; await wrapper.vm.$nextTick(); await vm.bulkWork('pause'); await new Promise((r) => setTimeout(r, 30)); // Должен отправить bulk только с id проекта источника 1 (101), источник 2 без проекта пропущен expect(vi.mocked(axios.post)).toHaveBeenCalledWith( '/api/projects/bulk', expect.objectContaining({ action: 'pause', ids: [101], }), ); }); });