Files
portal/app/tests/Frontend/ResetPasswordView.spec.ts
T

129 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import { createVuetify } from 'vuetify';
import { createRouter, createMemoryHistory } from 'vue-router';
vi.mock('../../resources/js/api/auth', () => ({
login: vi.fn(),
register: vi.fn(),
me: vi.fn(),
logout: vi.fn(),
verifyTwoFactor: vi.fn(),
forgotPassword: vi.fn(),
resetPassword: vi.fn(),
}));
vi.mock('../../resources/js/api/client', () => ({
extractRateLimitRetry: vi.fn(() => null),
extractValidationErrors: vi.fn(() => null),
extractErrorMessage: vi.fn(() => 'Ошибка'),
apiClient: {},
ensureCsrfCookie: vi.fn(),
}));
import * as authApi from '../../resources/js/api/auth';
import { useAuthStore } from '../../resources/js/stores/auth';
import ResetPasswordView from '../../resources/js/views/auth/ResetPasswordView.vue';
const mountReset = async (path = '/reset/abc-token-xyz?email=user@example.ru') => {
const pinia = createPinia();
setActivePinia(pinia);
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/reset/:token', name: 'reset-password', component: ResetPasswordView },
{ path: '/login', name: 'login', component: { template: '<div>stub</div>' } },
],
});
await router.push(path);
await router.isReady();
return mount(ResetPasswordView, {
global: { plugins: [pinia, createVuetify(), router] },
});
};
describe('ResetPasswordView.vue', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
});
it('монтируется и содержит заголовок «Новый пароль»', async () => {
const wrapper = await mountReset();
expect(wrapper.text()).toContain('Новый пароль');
});
it('предзаполняет email из query-параметра', async () => {
const wrapper = await mountReset('/reset/some-token?email=preset@example.ru');
const emailInput = wrapper.find('input[type="email"]');
expect((emailInput.element as HTMLInputElement).value).toBe('preset@example.ru');
});
it('содержит поля пароля + подтверждения с autocomplete=new-password', async () => {
const wrapper = await mountReset();
const passwordInputs = wrapper.findAll('input[type="password"]');
expect(passwordInputs.length).toBe(2);
passwordInputs.forEach((input) => {
expect(input.attributes('autocomplete')).toBe('new-password');
});
});
it('после успешного submit показывает success-state и скрывает форму', async () => {
vi.mocked(authApi.resetPassword).mockResolvedValue({ message: 'Пароль успешно изменён.' });
const wrapper = await mountReset('/reset/valid-token?email=user@example.ru');
await wrapper.findAll('input[type="password"]')[0].setValue('new-strong-pass-1234');
await wrapper.findAll('input[type="password"]')[1].setValue('new-strong-pass-1234');
await wrapper.find('form').trigger('submit.prevent');
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
expect(wrapper.find('[data-testid="reset-success"]').exists()).toBe(true);
expect(wrapper.find('form').exists()).toBe(false);
expect(authApi.resetPassword).toHaveBeenCalledWith(
expect.objectContaining({
token: 'valid-token',
email: 'user@example.ru',
password: 'new-strong-pass-1234',
password_confirmation: 'new-strong-pass-1234',
}),
);
});
it('при 429 показывает lockout-alert через auth.lockoutSeconds', async () => {
const wrapper = await mountReset();
const auth = useAuthStore();
auth.lockoutSeconds = 600;
await wrapper.vm.$nextTick();
const alert = wrapper.find('[data-testid="lockout-alert"]');
expect(alert.exists()).toBe(true);
expect(alert.text()).toContain('10 мин');
});
it('A4: показывает ошибку при несовпадении пароля и подтверждения', async () => {
const wrapper = await mountReset();
const pwInputs = wrapper.findAll('input[type="password"]');
await pwInputs[0].setValue('new-strong-pass-1234');
await pwInputs[1].setValue('different-pass-9999');
await wrapper.vm.$nextTick();
expect(wrapper.text()).toContain('Пароли не совпадают');
});
it('A9: переключатель видимости пароля имеет accessible-name и работает', async () => {
const wrapper = await mountReset();
const toggle = wrapper.find('[aria-label="Показать пароль"]');
expect(toggle.exists()).toBe(true);
expect(toggle.attributes('role')).toBe('button');
await toggle.trigger('click');
expect(wrapper.find('[aria-label="Скрыть пароль"]').exists()).toBe(true);
// keyboard activation (Enter) — toggle back
await wrapper.find('[aria-label="Скрыть пароль"]').trigger('keydown', { key: 'Enter' });
expect(wrapper.find('[aria-label="Показать пароль"]').exists()).toBe(true);
});
});