Files
portal/app/tests/Frontend/FieldCompetitorScreenCreate.spec.ts
T
Дмитрий efbad075e8 test(autopodbor): актуализировать 4 устаревших фронт-теста создания проектов
Групповое и одиночное создание теперь ТРЕБУЮТ регион (vsyaRf=false по умолчанию;
«Вся РФ» — явная галочка, дефолта больше нет — подтверждено владельцем). Тесты
были под старый дефолт «Вся РФ» → doCreate бэйлился на гейте региона. Добавлен
явный выбор «Вся РФ» перед doCreate. Плюс requisites_required теперь открывает
форму реквизитов (reqGate), а не тост — тест переведён на новое поведение,
reqGate добавлен в defineExpose для наблюдаемости. Спек 6/6 зелёный.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 15:51:45 +03:00

162 lines
8.2 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import { setActivePinia, createPinia } from 'pinia';
import { AxiosError } from 'axios';
// Частичный мок: сеть (fetchCompetitor/createProjects) — заглушки, но настоящий
// autopodborErrorMessage сохраняем, иначе doCreate не сможет замапить код ошибки.
vi.mock('../../resources/js/api/autopodbor', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../resources/js/api/autopodbor')>();
return {
...actual,
fetchCompetitor: vi.fn(),
createProjects: vi.fn(),
};
});
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' },
});
// Источник БЕЗ проекта в box 'field' — из него создаём проект.
const makeSource = (id: number) => ({
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: null,
project: null,
});
function axErr(status: number, body: unknown): AxiosError {
const e = new AxiosError('err');
// @ts-expect-error — минимальный мок ответа
e.response = { status, data: body, statusText: '', headers: {}, config: {} };
return e;
}
async function mountLoaded() {
(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)],
});
const store = useAutopodborStore();
await store.loadCompetitor(10);
const wrapper = mount(FieldCompetitorScreen, {
global: { provide: { autopodborNav: makeNav() }, stubs: { Transition: false } },
});
await new Promise((r) => setTimeout(r, 50));
return wrapper;
}
beforeEach(() => {
setActivePinia(createPinia());
vi.clearAllMocks();
});
describe('FieldCompetitorScreen — создание проекта из источника (баги 3/5)', () => {
it('Баг 5b: doCreate(true) шлёт launch:true; doCreate(false) — launch:false', async () => {
(api.createProjects as ReturnType<typeof vi.fn>).mockResolvedValue({ data: [{ id: 1, name: 'p' }], launch: null });
const wrapper = await mountLoaded();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vm = wrapper.vm as any;
vm.openCreate(vm.inWork[0]);
vm.create.vsyaRf = true; // регион обязателен: отмечаем «Вся РФ», иначе doCreate не пустит (гейт региона)
await wrapper.vm.$nextTick();
await vm.doCreate(false);
expect(api.createProjects).toHaveBeenLastCalledWith(expect.objectContaining({ launch: false }));
vm.openCreate(vm.inWork[0]);
vm.create.vsyaRf = true;
await wrapper.vm.$nextTick();
await vm.doCreate(true);
expect(api.createProjects).toHaveBeenLastCalledWith(expect.objectContaining({ launch: true }));
});
it('Баг 3: отмечена «Вся РФ» → шлёт regions: []', async () => {
(api.createProjects as ReturnType<typeof vi.fn>).mockResolvedValue({ data: [{ id: 1, name: 'p' }], launch: null });
const wrapper = await mountLoaded();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vm = wrapper.vm as any;
vm.openCreate(vm.inWork[0]);
vm.create.vsyaRf = true; // «Вся РФ» — явный выбор (дефолта «Вся РФ» больше нет, регион обязателен)
await wrapper.vm.$nextTick();
await vm.doCreate(true);
expect(api.createProjects).toHaveBeenLastCalledWith(expect.objectContaining({ regions: [] }));
});
it('Баг 3: выбранные регионы (Вся РФ снята) шлёт массив кодов', async () => {
(api.createProjects as ReturnType<typeof vi.fn>).mockResolvedValue({ data: [{ id: 1, name: 'p' }], launch: null });
const wrapper = await mountLoaded();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vm = wrapper.vm as any;
vm.openCreate(vm.inWork[0]);
vm.create.vsyaRf = false;
vm.create.regions = [24, 16];
await wrapper.vm.$nextTick();
await vm.doCreate(true);
expect(api.createProjects).toHaveBeenLastCalledWith(expect.objectContaining({ regions: [24, 16] }));
});
it('Баг 3: Вся РФ снята, ни один регион не выбран → не создаём, показываем подсказку', async () => {
(api.createProjects as ReturnType<typeof vi.fn>).mockResolvedValue({ data: [{ id: 1, name: 'p' }], launch: null });
const wrapper = await mountLoaded();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vm = wrapper.vm as any;
vm.openCreate(vm.inWork[0]);
vm.create.vsyaRf = false;
vm.create.regions = [];
await wrapper.vm.$nextTick();
await vm.doCreate(true);
expect(api.createProjects).not.toHaveBeenCalled();
expect(String(vm.toast).toLowerCase()).toContain('регион');
});
it('Баг 5a: ошибка requisites_required → открывается форма реквизитов, не общая ошибка/баланс', async () => {
(api.createProjects as ReturnType<typeof vi.fn>).mockRejectedValue(axErr(422, { error: 'requisites_required' }));
const wrapper = await mountLoaded();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vm = wrapper.vm as any;
vm.openCreate(vm.inWork[0]);
vm.create.vsyaRf = true; // регион обязателен: «Вся РФ», чтобы дойти до API (там мок requisites_required)
await wrapper.vm.$nextTick();
await vm.doCreate(true);
// Новое поведение: вместо общей ошибки/тоста «баланс» открывается адресная форма реквизитов (reqGate).
expect(vm.reqGate.open).toBe(true);
expect(String(vm.create.note).toLowerCase()).not.toContain('баланс');
});
it('групповое создание: doCreate шлёт source_ids всех выбранных источников без проекта', async () => {
(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), makeSource(2)],
});
(api.createProjects as ReturnType<typeof vi.fn>).mockResolvedValue({ data: [], launch: null });
const store = useAutopodborStore();
await store.loadCompetitor(10);
const wrapper = mount(FieldCompetitorScreen, {
global: { provide: { autopodborNav: makeNav() }, 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];
vm.openBulkCreate();
vm.create.vsyaRf = true; // регион обязателен и в групповом: отмечаем «Вся РФ»
await wrapper.vm.$nextTick();
await vm.doCreate(true);
expect(api.createProjects).toHaveBeenLastCalledWith(expect.objectContaining({ source_ids: [1, 2], launch: true }));
});
});