import { describe, it, expect, beforeEach, vi } from 'vitest'; import { mount, flushPromises } from '@vue/test-utils'; import { createVuetify } from 'vuetify'; vi.mock('../../resources/js/api/admin', () => ({ updateSystemSetting: vi.fn(), listSystemSettings: vi.fn(), })); vi.mock('../../resources/js/api/client', () => ({ extractValidationErrors: vi.fn(() => null), extractErrorMessage: vi.fn((_e, fb?: string) => fb ?? 'err'), apiClient: {}, ensureCsrfCookie: vi.fn(), })); import * as adminApi from '../../resources/js/api/admin'; import SystemSettingEditDialog from '../../resources/js/components/admin/SystemSettingEditDialog.vue'; const sampleSetting: adminApi.SystemSetting = { key: 'login_max_attempts', value: '5', type: 'int', description: 'Макс. неудачных попыток входа в окне 15 минут', updated_at: '2026-05-09T10:00:00', updated_by: null, }; const factory = ( props: { modelValue: boolean; setting: adminApi.SystemSetting | null } = { modelValue: true, setting: sampleSetting, }, ) => mount(SystemSettingEditDialog, { props: { ...props, requestedBy: 1 }, global: { plugins: [createVuetify()], stubs: { VDialog: { template: '
', props: ['modelValue'], }, }, }, }); describe('SystemSettingEditDialog.vue', () => { beforeEach(() => { vi.clearAllMocks(); }); it('не рендерит content при modelValue=false', () => { const wrapper = factory({ modelValue: false, setting: sampleSetting }); expect(wrapper.find('.dialog-stub').exists()).toBe(false); }); it('step 1 — показывает key + type-chip + текущее значение + 2 input', () => { const wrapper = factory(); const text = wrapper.text(); expect(text).toContain('login_max_attempts'); expect(text).toContain('int'); expect(text).toContain('Макс. неудачных попыток'); expect(wrapper.find('[data-testid="value-input"]').exists()).toBe(true); expect(wrapper.find('[data-testid="reason-input"]').exists()).toBe(true); expect(wrapper.find('[data-testid="next-btn"]').exists()).toBe(true); }); it('newValue по умолчанию = текущее значение setting', () => { const wrapper = factory(); const vm = wrapper.vm as unknown as { newValue: string }; expect(vm.newValue).toBe('5'); }); it('Далее без изменения значения → ошибка «совпадает»', async () => { const wrapper = factory(); const vm = wrapper.vm as unknown as { reason: string; valueError: string | null; step: string }; vm.reason = 'A'.repeat(35); await wrapper.vm.$nextTick(); await wrapper.find('[data-testid="next-btn"]').trigger('click'); await flushPromises(); expect(vm.valueError).toContain('совпадает'); expect(vm.step).toBe('edit'); }); it('Далее с reason < 30 chars → ошибка', async () => { const wrapper = factory(); const vm = wrapper.vm as unknown as { newValue: string; reason: string; reasonError: string | null; step: string; }; vm.newValue = '7'; vm.reason = 'короткое'; await wrapper.vm.$nextTick(); await wrapper.find('[data-testid="next-btn"]').trigger('click'); await flushPromises(); expect(vm.reasonError).toBe('Минимум 30 символов.'); expect(vm.step).toBe('edit'); }); it('Далее с валидными данными → step confirm + diff before/after', async () => { const wrapper = factory(); const vm = wrapper.vm as unknown as { newValue: string; reason: string; step: string }; vm.newValue = '7'; vm.reason = 'Решение CTO от 09.05.2026: ослабляем лимит для UX тестирования.'; await wrapper.vm.$nextTick(); await wrapper.find('[data-testid="next-btn"]').trigger('click'); await flushPromises(); expect(vm.step).toBe('confirm'); const text = wrapper.text(); expect(text).toContain('Было'); expect(text).toContain('Станет'); // Reason отображается на confirm-step expect(text).toContain('CTO от 09.05'); }); it('Применить → API + emit updated + step done', async () => { vi.mocked(adminApi.updateSystemSetting).mockResolvedValue({ key: 'login_max_attempts', value: '7', previous_value: '5', updated_at: '2026-05-09T11:00:00', message: 'OK', }); const wrapper = factory(); const vm = wrapper.vm as unknown as { newValue: string; reason: string; step: string }; vm.newValue = '7'; vm.reason = 'Решение CTO от 09.05.2026: ослабляем лимит для UX тестирования.'; await wrapper.vm.$nextTick(); await wrapper.find('[data-testid="next-btn"]').trigger('click'); await flushPromises(); await wrapper.find('[data-testid="submit-btn"]').trigger('click'); await flushPromises(); expect(adminApi.updateSystemSetting).toHaveBeenCalledWith('login_max_attempts', { value: '7', reason: expect.stringContaining('CTO'), admin_user_id: 1, }); expect(wrapper.emitted('updated')).toBeDefined(); expect(vm.step).toBe('done'); }); it('Назад с confirm возвращает на edit с сохранёнными данными', async () => { const wrapper = factory(); const vm = wrapper.vm as unknown as { newValue: string; reason: string; step: string }; vm.newValue = '7'; vm.reason = 'Решение CTO от 09.05.2026: ослабляем лимит для UX тестирования.'; await wrapper.vm.$nextTick(); await wrapper.find('[data-testid="next-btn"]').trigger('click'); await flushPromises(); expect(vm.step).toBe('confirm'); await wrapper.find('[data-testid="back-btn"]').trigger('click'); await flushPromises(); expect(vm.step).toBe('edit'); expect(vm.newValue).toBe('7'); // сохранилось }); });