import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mount, flushPromises } from '@vue/test-utils'; import { createPinia, setActivePinia } from 'pinia'; import { createVuetify } from 'vuetify'; vi.mock('axios'); vi.mock('../../resources/js/api/client', () => ({ apiClient: { post: vi.fn(), patch: vi.fn(), }, ensureCsrfCookie: vi.fn().mockResolvedValue(undefined), extractErrorMessage: vi.fn(() => 'Произошла ошибка.'), })); vi.mock('../../resources/js/api/requisites', () => ({ getRequisites: vi.fn().mockResolvedValue({ subject_type: 'individual', contact_name: 'Денис', contact_phone: '+79221364286', inn: null, }), updateRequisites: vi.fn().mockResolvedValue({}), })); import { apiClient } from '../../resources/js/api/client'; import NewProjectDialog from '../../resources/js/views/projects/NewProjectDialog.vue'; /** * Инцидент 14.07.2026: клиент дважды нажал «Создать» — и НИЧЕГО не произошло. Сервер * отказал по полю delivery_days_mask (человек снял все дни недели), а в форме это поле * не привязано ни к какому месту показа ошибки: ответ раскладывался в errors и исчезал. * Клиент тыкал вслепую 5 минут. * * Правило: любая причина отказа, которой негде показаться у поля, обязана появиться * общим баннером. Форма не имеет права молчать. */ const factory = () => mount(NewProjectDialog, { props: { modelValue: true, mode: 'create' as const }, global: { plugins: [createVuetify(), createPinia()], stubs: { VDialog: { template: '
', props: ['modelValue'], }, }, }, }); type Vm = { submit: () => Promise; confirmVsyaRf: () => void; form: { name: string; signal_identifier: string }; }; beforeEach(() => { setActivePinia(createPinia()); vi.clearAllMocks(); }); describe('NewProjectDialog — форма не молчит при отказе сервера', () => { it('показывает причину отказа по дням недели', async () => { vi.mocked(apiClient.post).mockRejectedValue({ response: { status: 422, data: { message: 'Ошибка', errors: { delivery_days_mask: ['Выберите хотя бы один день недели приёма.'] }, }, }, }); const wrapper = factory(); await flushPromises(); const vm = wrapper.vm as unknown as Vm; vm.confirmVsyaRf(); vm.form.name = 'Сибдент тел'; vm.form.signal_identifier = 'sibdent.ru'; await vm.submit(); await flushPromises(); expect(wrapper.text()).toContain('Выберите хотя бы один день недели приёма.'); }); it('не отправляет проект без единого дня недели — объясняет сразу, не гоняя на сервер', async () => { const wrapper = factory(); await flushPromises(); const vm = wrapper.vm as unknown as Vm & { selectedDays: number[] }; vm.confirmVsyaRf(); vm.form.name = 'Сибдент тел'; vm.form.signal_identifier = 'sibdent.ru'; vm.selectedDays.splice(0, vm.selectedDays.length); // человек снял все дни await vm.submit(); await flushPromises(); expect(apiClient.post).not.toHaveBeenCalled(); expect(wrapper.text()).toContain('день недели'); }); it('показывает причину отказа по полю, которого нет в форме', async () => { vi.mocked(apiClient.post).mockRejectedValue({ response: { status: 422, data: { message: 'Ошибка', errors: { some_unknown_field: ['Что-то не так с этим полем.'] }, }, }, }); const wrapper = factory(); await flushPromises(); const vm = wrapper.vm as unknown as Vm; vm.confirmVsyaRf(); vm.form.name = 'Сибдент тел'; vm.form.signal_identifier = 'sibdent.ru'; await vm.submit(); await flushPromises(); expect(wrapper.text()).toContain('Что-то не так с этим полем.'); }); });