Files
portal/app/tests/Frontend/FieldCompetitorScreenBulk.spec.ts
T

178 lines
6.8 KiB
TypeScript
Raw Normal View History

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<typeof vi.fn>).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<typeof vi.fn>).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<typeof vi.fn>).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],
}),
);
});
});