160 lines
6.5 KiB
TypeScript
160 lines
6.5 KiB
TypeScript
|
|
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: '<div class="dialog-stub" v-if="modelValue"><slot /></div>',
|
|||
|
|
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'); // сохранилось
|
|||
|
|
});
|
|||
|
|
});
|