0af5c5696f
РАЗМЕТКА ДОКУМЕНТАЦИИ. Проверка сторожа давала 237 замечаний в 21 файле — стало
0 во всех файлах под учётом git. Пустые строки вокруг списков и заголовков
поправлены автоматом в 12 файлах, два файла пришлось делать руками, потому что
автоправка там навредила:
- в описании починки инцидента номера шагов 1…7 — это ИМЕНА, на них ссылается
сам текст «из шага 2»; автоправка перенумеровала их в 1,1,1,1,2 и сломала
ссылки. Вывел ровно этот файл из-под одного правила, объяснение — в самом
файле, остальные правила действуют.
- в протоколе архитектуры автоправка склеила «R-* / DR-*» в «R-*/ DR-*».
Вместо этого обозначения взяты в обратные кавычки — смысл сохранён.
Семь файлов не тронуты: они вне учёта git — два в папке второй смены и старые
черновики в корне.
ИНТЕРФЕЙС. «Неизвестный тип» убран целиком: было 123 замечания, стало 0.
- Навигация автоподбора описана ОДИН раз — app/resources/js/views/autopodbor/nav.ts.
Раньше девять экранов повторяли описание с «неизвестным типом», а ещё три
описывали его каждый по-своему и не полностью.
- Чтение ошибки от сервера собрано в один помощник — app/resources/js/api/errors.ts,
с проверками. Он намеренно читает ФОРМУ ответа, а не полагается на axios:
тесты экранов подсовывают ошибку простым объектом той же формы, и экран обязан
вести себя в тестах так же, как в бою.
- В тестах вместо «неизвестного типа» — именованный доступ exposed<T> и полные
заготовки: app/tests/Frontend/support/. Каждый тест теперь объявляет ровно те
поля, до которых дотягивается, и опечатка в имени снова становится ошибкой,
а не молчаливым undefined. Заодно сняты 14 построчных отключений правила.
- Разведены задвоенные имена в шаблонах: в таблице сделок в одном шаблоне жили
ДВЕ разные функции isSelected — одна берёт номер сделки, другая строку таблицы.
- Убрана мёртвая функция dirLabel и три неиспользуемые переменные.
ПРОВЕРКА ТИПОВ: было 7 ошибок, стало 5. Две унаследованные закрылись попутно —
более строгие заготовки вскрыли нехватку обязательного поля elements. Оставшиеся
пять были до меня и не в моих строках.
ПРИЁМКА. Разметка проверена вырезанием: подложил поломку обратно — сторож её
поймал, вернул — снова чисто. Полный набор тестов интерфейса: 233 файла,
1750 тестов, 3 пропущено, 0 падений. Рабочий код на PHP не тронут.
NB: LEFTHOOK_EXCLUDE=cspell — тем же приёмом и по той же причине, что и в записи
e0223de4, которой эти шесть файлов исследования заводились в репозиторий: сырой
внешний материал с чужой терминологией. Замерено: до моих правок сторож
орфографии находил в них ровно столько же слов, сколько после — я не добавил
ни одного. Разметка в этих файлах теперь проходит начисто, отключена ТОЛЬКО
орфография и только на эту запись; остальные семнадцать сторожей отработали.
Решение владельца, спрошено отдельно.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
77 lines
3.8 KiB
TypeScript
77 lines
3.8 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';
|
|
|
|
vi.mock('../../resources/js/api/autopodbor');
|
|
import CreateScreen from '../../resources/js/views/autopodbor/screens/CreateScreen.vue';
|
|
import { useAutopodborStore } from '../../resources/js/stores/autopodborStore';
|
|
import { AUTOPODBOR_NAV, type AutopodborNav } from '../../resources/js/views/autopodbor/nav';
|
|
import { exposed } from './support/exposed';
|
|
import { competitor, fieldSource, navStub } from './support/fixtures';
|
|
|
|
const vuetify = createVuetify();
|
|
|
|
/** Экран создания отдаёт наружу выбранный регион. */
|
|
interface CreateVm {
|
|
regionCode: number;
|
|
}
|
|
|
|
function makeNav(): AutopodborNav {
|
|
return navStub('create', { runId: 5, competitorId: 3, selectedSourceIds: [11, 12] });
|
|
}
|
|
function seed(store: ReturnType<typeof useAutopodborStore>) {
|
|
store.competitor = competitor({ id: 3, name: 'Окна Комфорт', site_url: 'okna.ru', description: 'd', studied_at: '2026-06-28', study_run_id: 9, search_run_id: 5 });
|
|
store.sources = [
|
|
fieldSource({ id: 11, competitor_id: 3, signal_type: 'site', identifier: 'okna-komfort-kzn.ru', provenance_label: '2ГИС' }),
|
|
fieldSource({ id: 12, competitor_id: 3, signal_type: 'call', identifier: '78432001122', phone_kind: 'real', provenance_label: '2ГИС' }),
|
|
];
|
|
}
|
|
function mountCreate(nav: AutopodborNav) {
|
|
return mount(CreateScreen, { global: { plugins: [vuetify], provide: { [AUTOPODBOR_NAV]: 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 } });
|
|
const nav = makeNav();
|
|
const w = mountCreate(nav);
|
|
exposed<CreateVm>(w).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 = vi.mocked(store.makeProjects).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);
|
|
exposed<CreateVm>(w).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');
|
|
});
|
|
});
|