2c59a00714
Code-quality review of Task 2: documents why ProfileTab needs no watch-resync of auth.user (router beforeEach awaits fetchMe before requiresAuth navigation); tightens the save-error test to assert the exact fallback message instead of mere truthiness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
111 lines
4.0 KiB
TypeScript
111 lines
4.0 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/auth', () => ({
|
||
updateProfile: vi.fn(),
|
||
}));
|
||
|
||
vi.mock('../../resources/js/api/client', () => ({
|
||
apiClient: {},
|
||
ensureCsrfCookie: vi.fn(),
|
||
extractValidationErrors: vi.fn(() => null),
|
||
extractErrorMessage: vi.fn((_e, fallback) => fallback ?? 'Произошла ошибка.'),
|
||
extractRateLimitRetry: vi.fn(() => null),
|
||
}));
|
||
|
||
import * as authApi from '../../resources/js/api/auth';
|
||
import ProfileTab from '../../resources/js/views/settings/ProfileTab.vue';
|
||
import { useAuthStore } from '../../resources/js/stores/auth';
|
||
import type { AuthUser } from '../../resources/js/api/auth';
|
||
|
||
const vuetify = createVuetify();
|
||
|
||
const mockUser: AuthUser = {
|
||
id: 1,
|
||
email: 'ivan@example.ru',
|
||
first_name: 'Иван',
|
||
last_name: 'Петров',
|
||
phone: '+7 916 000-00-00',
|
||
timezone: 'Europe/Moscow',
|
||
tenant_id: 1,
|
||
totp_enabled: false,
|
||
last_login_at: null,
|
||
};
|
||
|
||
const factory = (user: AuthUser | null = mockUser) => {
|
||
setActivePinia(createPinia());
|
||
const auth = useAuthStore();
|
||
auth.user = user;
|
||
return mount(ProfileTab, { global: { plugins: [vuetify] } });
|
||
};
|
||
|
||
describe('ProfileTab.vue', () => {
|
||
beforeEach(() => {
|
||
vi.clearAllMocks();
|
||
});
|
||
|
||
it('подставляет имя/фамилию/телефон/тайм-зону из auth-store', () => {
|
||
const wrapper = factory();
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
const vm = wrapper.vm as any;
|
||
expect(vm.firstName).toBe('Иван');
|
||
expect(vm.lastName).toBe('Петров');
|
||
expect(vm.phone).toBe('+7 916 000-00-00');
|
||
expect(vm.timezone).toBe('Europe/Moscow');
|
||
});
|
||
|
||
it('save() вызывает updateProfile с payload и показывает успех', async () => {
|
||
(authApi.updateProfile as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||
...mockUser,
|
||
first_name: 'Пётр',
|
||
});
|
||
const wrapper = factory();
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
const vm = wrapper.vm as any;
|
||
vm.firstName = 'Пётр';
|
||
await vm.save();
|
||
expect(authApi.updateProfile).toHaveBeenCalledWith({
|
||
first_name: 'Пётр',
|
||
last_name: 'Петров',
|
||
phone: '+7 916 000-00-00',
|
||
timezone: 'Europe/Moscow',
|
||
});
|
||
expect(vm.saveSuccess).toBe(true);
|
||
expect(vm.saveError).toBe(null);
|
||
});
|
||
|
||
it('save() с пустым phone отправляет null', async () => {
|
||
(authApi.updateProfile as ReturnType<typeof vi.fn>).mockResolvedValue(mockUser);
|
||
const wrapper = factory();
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
const vm = wrapper.vm as any;
|
||
vm.phone = ' ';
|
||
await vm.save();
|
||
expect(authApi.updateProfile).toHaveBeenCalledWith(
|
||
expect.objectContaining({ phone: null }),
|
||
);
|
||
});
|
||
|
||
it('save() показывает ошибку при reject', async () => {
|
||
(authApi.updateProfile as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('boom'));
|
||
const wrapper = factory();
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
const vm = wrapper.vm as any;
|
||
await vm.save();
|
||
expect(vm.saveError).toBe('Не удалось сохранить профиль.');
|
||
expect(vm.saveSuccess).toBe(false);
|
||
expect(vm.saving).toBe(false);
|
||
});
|
||
|
||
it('resetForm() возвращает поля к значениям из store', async () => {
|
||
const wrapper = factory();
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
const vm = wrapper.vm as any;
|
||
vm.firstName = 'Изменено';
|
||
vm.resetForm();
|
||
expect(vm.firstName).toBe('Иван');
|
||
});
|
||
});
|