9ff7bc8fa0
Фронт для правила «баланс блокирует запуск, а не создание»: - ProjectLimitOverloadDialog переформулирован в «Проект не запущен — пополните ~X ₽ или уменьшите объём» (сумма в рублях topup_rub). - NewProjectDialog: создание всегда успешно; при launch.deferred показывает сообщение (не блок). Update-лимита 409 → тот же диалог. - Возобновление (toggle-active) ловит 409 balance_insufficient (стор не переключает is_active), показывает диалог; постоянная метка «Не запущен — не хватает баланса» на карточке/в панели по preflight_blocked_at/balance_blocked. - BulkActionsBar: сводка «Запущено N, отложено M — не хватает баланса» на resume. - Автоподбор: CreateScreen показывает сводку запуска (рубли); FieldCompetitor массовые pause/resume через общий POST /api/projects/bulk (наследует BULK_MAX, слепок-защиту, балансовый гейт). - ProjectResource отдаёт preflight_blocked_at. Новые/обновлённые фронт-тесты зелёные. Пред-существующий красный baseline фронт-набора (ErrorView/Settings/Legal/… + ProjectsView v-show) не трогался. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69 lines
3.7 KiB
TypeScript
69 lines
3.7 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { mount } from '@vue/test-utils';
|
|
import { createPinia, setActivePinia } from 'pinia';
|
|
import { createVuetify } from 'vuetify';
|
|
import { reactive, ref } from 'vue';
|
|
|
|
vi.mock('../../resources/js/api/autopodbor');
|
|
import CreateScreen from '../../resources/js/views/autopodbor/screens/CreateScreen.vue';
|
|
import { useAutopodborStore } from '../../resources/js/stores/autopodborStore';
|
|
|
|
const vuetify = createVuetify();
|
|
function makeNav() {
|
|
return { go: vi.fn(), ctx: reactive({ runId: 5, competitorId: 3, selectedSourceIds: [11, 12], loadMsg: '', loadSub: '', editProjectId: null, createdCount: 0, launched: false }), screen: ref('create') };
|
|
}
|
|
function seed(store: any) {
|
|
store.competitor = { id: 3, name: 'Окна Комфорт', is_federal: false, relevance_pct: 100, origin: 'auto', site_url: 'okna.ru', directory_urls: [], description: 'd', studied_at: '2026-06-28', study_run_id: 9, search_run_id: 5 };
|
|
store.sources = [
|
|
{ id: 11, competitor_id: 3, signal_type: 'site', identifier: 'okna-komfort-kzn.ru', phone_kind: null, provenance_url: null, provenance_label: '2ГИС', created_project_id: null, existing_project_id: null },
|
|
{ id: 12, competitor_id: 3, signal_type: 'call', identifier: '78432001122', phone_kind: 'real', provenance_url: null, provenance_label: '2ГИС', created_project_id: null, existing_project_id: null },
|
|
];
|
|
}
|
|
function mountCreate(nav: any) {
|
|
return mount(CreateScreen, { global: { plugins: [vuetify], provide: { autopodborNav: nav } } });
|
|
}
|
|
|
|
describe('CreateScreen', () => {
|
|
beforeEach(() => { setActivePinia(createPinia()); vi.clearAllMocks(); });
|
|
|
|
it('показывает выбранные источники и число проектов', async () => {
|
|
const store = useAutopodborStore(); seed(store);
|
|
const w = mountCreate(makeNav());
|
|
await new Promise(r => setTimeout(r, 0));
|
|
expect(w.text()).toContain('okna-komfort-kzn.ru');
|
|
expect(w.text()).toContain('Окна Комфорт'); // производное имя
|
|
});
|
|
|
|
it('«Создать (без запуска)» зовёт makeProjects(launch=false) и идёт на done', async () => {
|
|
const store = useAutopodborStore(); seed(store);
|
|
vi.spyOn(store, 'makeProjects').mockResolvedValue({ data: [{ id: 1, name: 'Окна Комфорт', is_active: true }, { id: 2, name: 'Окна Комфорт ✓', is_active: true }], launch: { launched: 2, deferred: 0, balance: null } } as any);
|
|
const nav = makeNav();
|
|
const w = mountCreate(nav);
|
|
(w.vm as any).regionCode = 16;
|
|
await new Promise(r => setTimeout(r, 0));
|
|
const btn = w.findAll('button').find(b => b.text().includes('без запуска'));
|
|
await btn!.trigger('click');
|
|
await new Promise(r => setTimeout(r, 0));
|
|
expect(store.makeProjects).toHaveBeenCalled();
|
|
const arg = (store.makeProjects as any).mock.calls[0][0];
|
|
expect(arg.launch).toBe(false);
|
|
expect(arg.source_ids).toEqual([11, 12]);
|
|
expect(nav.ctx.createdCount).toBe(2);
|
|
expect(nav.go).toHaveBeenCalledWith('done');
|
|
});
|
|
|
|
it('409 нехватки баланса показывает сообщение и возвращает на create', async () => {
|
|
const store = useAutopodborStore(); seed(store);
|
|
vi.spyOn(store, 'makeProjects').mockRejectedValue({ response: { data: { error: 'balance_insufficient' } } });
|
|
const nav = makeNav();
|
|
const w = mountCreate(nav);
|
|
(w.vm as any).regionCode = 16;
|
|
await new Promise(r => setTimeout(r, 0));
|
|
const btn = w.findAll('button').find(b => b.text().includes('запустить'));
|
|
await btn!.trigger('click');
|
|
await new Promise(r => setTimeout(r, 0));
|
|
expect(store.makeProjects).toHaveBeenCalled();
|
|
expect(nav.go).toHaveBeenCalledWith('create');
|
|
});
|
|
});
|